后端

kjnotes / 2023-08-05 / 原文

  参考地址:https://blog.csdn.net/u011781521/article/details/79052725

  方式一:原生

public static void exportFileZip(HttpServletRequest request, HttpServletResponse response, String[] fileNames, InputStream[] fileInputStreams,Long contentLength) {
        System.out.println(contentLength);
        response.setContentType("application/zip;charset=utf-8");
//        response.setContentLengthLong(contentLength + 4751);
        OutputStream outputStream = null;
        ZipOutputStream zos = null;
        BufferedInputStream bis = null;
        try {
            outputStream = response.getOutputStream();
            // zip输出流,可以把zip文件输出到文件流或者响应流中返回给前端
            zos = new ZipOutputStream(outputStream);
            // 缓冲数组
            byte[] bytes = new byte[1024 * 10];
            for (int i = 0; i < fileNames.length; i++) {
                ZipEntry zipEntry = new ZipEntry(fileNames[i]);
                zos.putNextEntry(zipEntry);
                bis = new BufferedInputStream(fileInputStreams[i], 1024 * 10);
                int read = 0;
                while ((read = bis.read(bytes, 0, 1024 * 10)) != -1) {
                    zos.write(bytes, 0, read);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭流
            try {
                if (null != bis) {
                    bis.close();
                }
                if (null != zos) {
                    zos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

  方法二:使用hutool工具类

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-core</artifactId>
    <version>5.7.16</version>
</dependency>
 public static void exportFileZip(HttpServletRequest request, HttpServletResponse response, String[] fileNames, InputStream[] fileInputStreams, Long contentLength,String fileName) {
        response.setContentType("application/zip;charset=utf-8");
        try {
            response.setHeader("content-disposition", "attachment;filename=" + getFileName(request.getHeader("user-agent"), fileName) + ".zip");
            ZipUtil.zip(response.getOutputStream(), fileNames, fileInputStreams);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

<!--打包时应排除 这个依赖,只是方便开发-->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-configuration-processor</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

  1、request 请求对象,可以获取请求信息 response 对象

  2、response 响应对象,可以设置响应信息

  3、pageContext 当前页面上下文对象。可以在当前上下文保存属性信息

  4、session 会话对象。可以获取会话信息。

  5、application 异常对象只有在 jsp 页面的 page 指令中设置 isErrorPage="true" 的时候才会存在

  6、exception ServletContext 对象实例,可以获取整个工程的一些信息

  7、config ServletConfig 对象实例,可以获取 Servlet 的配置信息

  8、out 输出流

  9、page 表示当前 Servlet 对象实例(无用,用它不如使用 this 对象)。

  web-inf下 lib文件夹

  名字必须为lib ,不能为其他的

推荐使用hutool 工具类

  https://www.hutool.cn/docs/#/captcha/%E6%A6%82%E8%BF%B0?id=%e7%94%b1%e6%9d%a5

1、java代码

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1、创建一个图片对象,内存中的一个图片
    int width = 100;
    int height = 50;
    BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    //2、美化
    //2.1填充背景色
    Graphics g = bufferedImage.getGraphics();//获取画笔对象
    g.setColor(Color.pink);//设置画笔的颜色
    g.fillRect(0, 0, width, height);//填充
    //2.2 画边框
    g.setColor(Color.blue);
    g.drawRect(0, 0, width - 1, height - 1);
    //2.3 写验证码
    String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    // 获取随机角标
    Random random = new Random();
    for (int i = 1; i <= 4; i++) {
        int index = random.nextInt(str.length());
        char c = str.charAt(index);
        g.drawString(c + "", width / 5 * i, height / 2);
    }
    //2.4 画干扰线
    for (int i = 0; i < 6; i++) {
        int x1 = random.nextInt(width);
        int x2 = random.nextInt(width);
        int y1 = random.nextInt(height);
        int y2 = random.nextInt(height);
        g.setColor(Color.green);
        g.drawLine(x1,y1,x2,y2);
    }
    //3、输出到页面
    ImageIO.write(bufferedImage, "jpg", response.getOutputStream());
}

2、页面验证码的动态刷新

服务器获取验证码之后应该删除session中的验证码,确保安全!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<img src="/day14/checkCodeServlet" id="code">
<a href="#" id="code2">看不清?点击换一张图片</a>
<script>
    var img = document.getElementById("code");
    img.onclick = function (){
        //加一个时间戳,防止浏览器读取缓存,不能刷新图片
        img.src = "/day14/checkCodeServlet?"+new Date().getTime();
    }
    var img2 = document.getElementById("code2");
    img2.onclick = function (){
        //加一个时间戳,防止浏览器读取缓存,不能刷新图片
        img.src = "/day14/checkCodeServlet?"+new Date().getTime();
    }
</script>
</body>
</html>

1、普通后台下载

  • ajax上传文件,提交表单

  后端接收

上传文件的请求路径必须是绝对路经

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>处理错误的照片</title>
</head>
<style>
    .error{color: red}
    .success{color: green}
    #center{
        margin-left: 35%;
        margin-top: 5%;
        width: 960px;
    }
</style>
<body>
<div id="center">
    <form id="spellForm" action="/api/images/dealErrorImages" method="post" enctype="multipart/form-data">
        <label>单词:</label>
        <input type="text" name="spell"/>
        <br>
        <br>
        <label>标签:</label>
        <input type="text" name="tag"/>
        <br>
        <br>
        <label>图片:</label>
        <input type="file" name="file">
        <br>
        <br>
        <input type="button" onclick="subFile()" value="提交"/>
        <br>
        <span id="msg"></span>
    </form>
</div>
</body>
<script src="/js/jquery.min.js"></script>
<script th:inline="javascript">
    function subFile() {
        let formData = new FormData($("#spellForm")[0])
        $.ajax({
            type: 'post',
            url: "http://localhost:8000/api/images/dealErrorImages", //上传文件的请求路径必须是绝对路劲
            data: formData,
            cache: false,
            processData: false,
            contentType: false,
        }).success(function (data) {
            console.log(data)
            console.log(data.retCode)
            console.log(data.message)
            $("#msg").html(data.message)
            if (data.retCode == 200){
                $("#msg").attr("class","success")
            }else {
                $("#msg").attr("class","error")
            }
        })
    }
</script>
</html>
@PostMapping("/dealErrorImages")
    @ResponseBody
    @ApiOperation(value = "处理错误照片")
    public ResponseJson dealErrorImages(String spell, String tag, MultipartFile file,) {
        int flag = this.imagesAllService.dealErrorImages(spell, tag, file);
        if (flag == 1) {
            return ResponseJson.ok("修改成功");
        } else if (flag == 0) {
            return ResponseJson.failed("没有该单词对应信息");
        } else if (flag == -1) {
            return ResponseJson.failed("请输入完整信息");
        } else {
            return ResponseJson.failed("修改失败");
        }
    }

enctype="multipart/form-data"<br />前端表单添加

  文件上传:

  1、导入依赖

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

  2、在spring-mvc文件中,配置文件解析器

<!--    文件上传解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="500000"/>
        <property name="defaultEncoding" value="UTF-8"/>
    </bean>

  3、编写服务器端代码,文件上传

  单文件上传:

@RequestMapping("/save22")
@ResponseBody
public void save22(String username, MultipartFile uploadFile,HttpServletRequest request){
    //文件真实名字
    String originalFilename = uploadFile.getOriginalFilename();
    //将文件保存在服务器
    //获取保存文件夹的真实路径
    String path = request.getRealPath("/upload");
    //要保存的文件路径
    String filePath = path + "\\" + originalFilename;
    try {
        uploadFile.transferTo(new File(filePath));
    } catch (IOException e) {
        e.printStackTrace();
    }

}

  多文件上传:

/**
 * 多文件上传
 * @param username
 * @param uploadFiles
 * @param request
 */
@RequestMapping("/save23")
@ResponseBody
public void save23(String username, MultipartFile[] uploadFiles,HttpServletRequest request){
    //获取保存文件夹的真实路径
    String path = request.getRealPath("/upload");
    //对多个文件进行遍历
    for (MultipartFile uploadFile : uploadFiles) {
        //文件真实名字
        String originalFilename = uploadFile.getOriginalFilename();
        //将文件保存在服务器
        //要保存的文件路径
        String filePath = path + "\\" + originalFilename;
        try {
            uploadFile.transferTo(new File(filePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

文件下载

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1、获取资源的名称
    request.setCharacterEncoding("utf-8");
    String filename = request.getParameter("filename");
    //2、使用字节输入流,将文件加载进内存
    ServletContext context = this.getServletContext();
    //2.1、获取文件的真实路径
    String realPath = context.getRealPath("/img/" + filename);
    // 使用字节流关联文件
    FileInputStream fis = new FileInputStream(realPath);
    //3、设置响应头信息
    //3.1 设置文件类型
    String mimeType = context.getMimeType(filename); //获取文件类型
    response.setContentType(mimeType);
    //3.2 解决浏览器中文乱码
    String agent = request.getHeader("user-agent"); //获取浏览器版本信息
    String lastFilename = DownLoadUtils.getFileName(agent, filename);//对文件名进行编码转化
    response.setHeader("content-disposition","attachment;filename="+lastFilename);
    //一个输出流
    ServletOutputStream sos = response.getOutputStream();
    //不断地写出数据
    byte[] buff = new byte[1024];
    int len = 0;
    while ((len = fis.read(buff)) != -1) {
        sos.write(buff,0,len);
    }
    fis.close();
}

  编码转化的工具类:

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.Locale;

public class DownLoadUtils {

    /**
     * 下载文件
     *
     * @param fileName    文件名,带后缀格式
     * @param inputStream 文件输入流
     * @param request
     * @param response
     */
    public static void downloadFile(String fileName, InputStream inputStream, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String mimeType = request.getServletContext().getMimeType(fileName);
        response.setContentType(mimeType);
        String agent = request.getHeader("user-agent"); //获取浏览器版本信息
        String lastFilename = getFileName(agent, fileName);//对文件名进行编码转化
        response.setHeader("content-disposition", "attachment;filename=" + lastFilename);
        ServletOutputStream sos = response.getOutputStream();
        byte[] buff = new byte[1024];
        int len = 0;
        while ((len = inputStream.read(buff)) != -1) {
            sos.write(buff, 0, len);
        }
        inputStream.close();
    }

    /**
     * 对文件名进行编码转化
     *
     * @param agent
     * @param filename
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String getFileName(String agent, String filename) throws UnsupportedEncodingException {
        if (agent.contains("MSIE")) {
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
            filename = filename.replace("+", " ");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            Base64.Encoder base64Encoder = Base64.getEncoder();
            filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
        } else {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }

    /**
     * @param bytes 转换得字节
     * @param si    是否需要单位
     * @return
     */
    public static String byteFormat(long bytes, boolean si) {
        String[] units = new String[]{" B", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB"};
        int unit = 1024;
        int exp = (int) (Math.log(bytes) / Math.log(unit));
        double pre = 0;
        if (bytes > 1024) {
            pre = bytes / Math.pow(unit, exp);
        } else {
            pre = (double) bytes / (double) unit;
        }
        if (si) {
            return String.format(Locale.ENGLISH, "%.1f%s", pre, units[(int) exp]);
        }
        return String.format(Locale.ENGLISH, "%.1f", pre);
    }
}

2、vue中下载文件

  博客https://blog.csdn.net/mobile18611667978/article/details/88988884

  1、a标签下载

<a :href='"/user/downloadExcel"' >下载模板</a>

// 另一种情况是创建div标签,动态创建a标签:
<div name="downloadfile" onclick="downloadExcel()">下载</div>
function downloadExcel() {
    let a = document.createElement('a')
    a.href ="/user/downloadExcel"
    a.click();
} 
// 还有一种补充:
 function downloadExcel() {
    window.location.href = "/tUserHyRights/downloadUsersUrl";
} 

  2、会对后端发的post请求,使用blob格式

注意返回类型 responseType: 'blob'

import request from './request'
const basePath = '/project'
export default {
  // 获取全部数据导出为excel表
  exportAllDataExcel(){
    return request({
      url: basePath + '/exportAllDataExcel',
      method: 'get',
      responseType: 'blob'
    })
  }
}

exportData() {
      // document.getElementById("excel").href = 'http://localhost:9814/project/exportAllDataExcel'
      project.exportAllDataExcel()
        .then((data) => {
        })
        .catch((data) => {
          let blob = new Blob([data], {
            type: 'application/vnd.ms-excel'      //将会被放入到blob中的数组内容的MIME类型
          })
          let fileName = '数据表.xlsx'
          if (window.navigator.msSaveOrOpenBlob) {
            navigator.msSaveBlob(blob, fileName)
          } else {
            let link = document.createElement('a')
            link.href = window.URL.createObjectURL(blob)
            link.download = fileName
            link.click()
            //释放内存
            window.URL.revokeObjectURL(link.href)
          }
        })
    }
package com.kj.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;

/**
 * 敏感词过滤
 * 杀币  傻逼  替换为 ***
 */
@WebFilter("/*")
public class GuoLvFilter implements Filter {
    ArrayList<String> list = new ArrayList<>();
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

        //创建代理对象
        ServletRequest proxy_req = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * 代理对象执行的方法,
             * @param proxy
             * @param method
             * @param args
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //对获取参数的方法进行增强
                if ("getParameter".equals(method.getName())) {
                    String str = (String) method.invoke(req, args);
                    for (String s : list) {
                        if (str.contains(s)){
                            str = str.replaceAll(s,"***");
                        }
                    }
                    return str;
                }else {
                    return method.invoke(req, args);
                }
            }
        });
        //传递的是创建的代理对象,传递处理过后的数据
        chain.doFilter(proxy_req, resp);
    }

    /**
     * 加载资源文件
     * @param config
     * @throws ServletException
     */
    public void init(FilterConfig config) throws ServletException {
        ServletContext servletContext = config.getServletContext();
        //获取真实路径
        String realPath = servletContext.getRealPath("/WEB-INF/classes/敏感词汇.txt");
        //创建流,加载进内存
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(realPath),"utf-8"));
            String str;
            while((str = br.readLine())!= null){
            list.add(str);
            }
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  服务器获取验证码之后应该删除session中的验证码,确保安全!

  服务器获取验证码之后应该删除session中的验证码,确保安全!

public class ProvinceServiceImpl implements ProvinceService {
    private ProvinceDao provinceDao = new ProvinceDaoImpl();
    private Jedis jedis = JedisUtil.getJedis();
    /**
     * 在其他数据库增删改操作中,要对redis缓存的数据删除,来更新数据
     *      jedis.del("Province");
     * @return
     */
    @Override
    public String findAllRedis() {
        //先从redis中读取数据
        String Json = jedis.get("Province");
        //判断是否为空
        if (Json == null || Json.length() == 0){
            //空则从数据库查询
            List<Province> provinceList = provinceDao.findAll();
            ObjectMapper mapper = new ObjectMapper();
            try {
                Json = mapper.writeValueAsString(provinceList);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            //保存到redis中
            jedis.set("Province",provinceJson);
            jedis.close();
            //返回结果
            return Json;
        }else {
      //如果有则直接返回结果
            return Json;
        }
    }
}
## 必须用jdbc.xxx 不然后期会与配置文件中的username冲突报错
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>

删除pom.xml上的pluginsmanagement标签

<!--    tomcat7 不支持太高版本Jackson    -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>
// 重定向:
return "redirect:/role/findAll";
// 请求转发:
return "forward:/role/findAll";    
// 只能用转发,才可以拼接视图解析器,重定向不可以

①在springboot中 在pojo对象上使用注解

@Data //自动生成getter/setter   toString equls方法
//指定与数据库那个表对应,通用mapper将自动进行sql语句的拼接
@Table(name = "tb_user")
public class User {
    //主键
    @Id
    //开启主键回填,在添加数据后回填主键数据
    @KeySql(useGeneratedKeys = true)
    private Long id;
    private String user_name;
    private String password;
}
//在添加操作执行后,主键回填,就可以获取对象中的主键id
System.out.println(user.getId());

②原始方式(PreparedStatementCreator ):

  获取用户的自生成主键id,用于对其角色的id保存的对应的多表关系中

public Long save(final User user) {
       //创建PreparedStatementCreator
       PreparedStatementCreator creator = new PreparedStatementCreator() {
           public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
               //使用原始jdbc完成有个PreparedStatement的组建
               PreparedStatement preparedStatement = connection.prepareStatement("insert into sys_user values(?,?,?,?,?)", PreparedStatement.RETURN_GENERATED_KEYS);
               preparedStatement.setObject(1,null);
               preparedStatement.setString(2,user.getUsername());
               preparedStatement.setString(3,user.getEmail());
               preparedStatement.setString(4,user.getPassword());
               preparedStatement.setString(5,user.getPhoneNum());
               return preparedStatement;
           }
       };
       //创建keyHolder
       GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
       jdbcTemplate.update(creator,keyHolder);
       //获得生成的主键
       long userId = keyHolder.getKey().longValue();
       return userId; //返回当前保存用户的id 该id是数据库自动生成的
   }

   public void saveUserRoleRel(Long userId, Long[] roleIds) {
       for (Long roleId : roleIds) {
           jdbcTemplate.update("insert into sys_user_role values(?,?)",userId,roleId);
       }
   }

  ①是加入以下配置

<resources>
    <resource>
        <directory>src/main/resources</directory>
        <includes>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>

  ②是将mapper映射文件放入resource文件下,发布时才会有配置文件

package com.kj.handler;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

public class DateTypeHandler extends BaseTypeHandler<Date> {
    //将数据转化为数据库数据格式
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
        long time = parameter.getTime();
        ps.setLong(i,time);
    }
  //读取
    @Override
    public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
        long aLong = rs.getLong(columnName);
        return new Date(aLong);
    }
  //读取
    @Override
    public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        long aLong = rs.getLong(columnIndex);
        return new Date(aLong);
    }
  //读取
    @Override
    public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        long aLong = cs.getLong(columnIndex);
        return new Date(aLong);
    }
}

  ①、导入jar包坐标

<!--高版本会报错 -->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>4.1.6</version>
</dependency>
<dependency>
  <groupId>com.github.jsqlparser</groupId>
  <artifactId>jsqlparser</artifactId>
  <version>1.0</version>
</dependency>

  ②在mybatis核心文件注册插件

<plugins>
    <!--    分页插件-->
    <plugin interceptor="com.github.pagehelper.PageHelper">
     <!--            指定方言为mysql使用limit关键字-->
        <property name="dialect" value="mysql"/>
    </plugin>
</plugins>

  ③编写查询代码

//查询全部,使用pagehelper分页查询
@Test
public void test5() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //mybatis动态代理生成的mapper实现类
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //设置分页参数,只要设置开始页,以及条数就可以,不用进行页码的计算了
    PageHelper.startPage(1, 5);
    List<User> users = mapper.findAll_13();
    for (User user : users) {
        System.out.println(user);
    }
    //其他分页的数据,参数是你查询出的想要进行分页的数据
    PageInfo<User> pageInfo = new PageInfo<User>(users);
    System.out.println("总条数:"+pageInfo.getTotal());
    System.out.println("总页数:"+pageInfo.getPages());
    System.out.println("当前页:"+pageInfo.getPageNum());
    System.out.println("每页显示长度:"+pageInfo.getPageSize());
    System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
    System.out.println("是否最后一页:"+pageInfo.isIsLastPage());
}

  一对一查询:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.kj.mapper.OrderMapper">
    <resultMap id="orderMap" type="order">
        <!--
        手动指定数据库字段与实体属性的映射关系
        column:查询出的数据表的字段名称
        property:实体的属性名称
        -->
        <id column="oid" property="id"/>
        <result column="ordertime" property="ordertime"/>
        <result column="total" property="total"/>
        <!--<result column="uid" property="user.id"/>
        <result column="username" property="user.username"/>
        <result column="password" property="user.password"/>
        <result column="birthday" property="user.birthday"/>-->
        <!--
        property:当前实体(order)的属性名称 private User user
        javaType:当前实体中属性的类型 com.kj.domain.User
        只是将上边的user给提取出来进行封装
        -->
        <association property="user" javaType="user">
            <id column="uid" property="id"/>
            <result column="username" property="username"/>
            <result column="password" property="password"/>
            <result column="birthday" property="birthday"/>
        </association>
    </resultMap>
    <select id="findAll" resultMap="orderMap">
        SELECT o.`id` oid,o.`ordertime`,o.`total`,o.`uid` uid,u.`username`,u.`password`,u.`birthday`
        FROM orders o,USER u
        WHERE o.`uid` = u.`id`
    </select>
</mapper>

  一对多查询:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.kj.mapper.UserMapper">
    <resultMap id="userMap" type="user">
        <id column="uid" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="birthday" property="birthday"/>
        <!--集合的封装
        property:实体类的集合属性
        ofType:集合中保存的实体类属性
        -->
        <collection property="orderList" ofType="order">
            <id column="oid" property="id"/>
            <result column="ordertime" property="ordertime"/>
            <result column="total" property="total"/>
        </collection>
    </resultMap>
    <select id="findAll" resultMap="userMap">
        SELECT u.`id` uid,u.`username`,u.`password`,u.`birthday`,o.`id` oid,o.`ordertime`,o.`total`
        FROM orders o,USER u
        WHERE o.`uid` = u.`id`
    </select>
</mapper>

  注解方式的一对一:

  调用了另一个方法去查询想要的数据

OrderMapper接口文件:
@Select("select * from orders")
@Results({
        @Result(column = "id",property = "id"),
        @Result(column = "ordertime",property = "ordertime"),
        @Result(column = "total",property = "total"),
        @Result(
                property = "user", //要封装的属性名称
                column = "uid",     //根据查询出来的结果的哪个字段去查询user表数据
                javaType = User.class,//要封装的实体类型
                //@one 一对一查询,select:去调用哪个接口的方法去获得要查询的数据
                one = @One(select = "com.kj.mapper.UserMapper_15.findUserById")
        )
})
List<Order> findAll();

UserMapper接口文件:
@Select("select * from user where id=#{id}")
    User findUserById(int id);

  一对多的注解:

UserMapper接口文件
@Select("select * from user")
@Results({
    //表示是主键id
        @Result(id=true,column = "id",property = "id"),
        @Result(column = "username",property = "username"),
        @Result(column = "password",property = "password"),
        @Result(column = "birthday",property = "birthday"),
        @Result(
                property = "orderList",
                column = "id",
                javaType = List.class,
                many = @Many(select = "com.kj.mapper.OrderMapper_15.findOrderByUid")
        )

})
List<User> findUserAndOrderAll();

OrderMapper接口文件
@Select("select * from orders where uid = #{uid}")
    Order findOrderByUid(int uid);
produces = "text/html;charset=UTF-8"设置响应格式

//保存
@RequestMapping(value = "/save",produces = "text/html;charset=UTF-8")
@ResponseBody
public String save(Account account){
    accountService.save(account);
    return "保存成功";
}

①:打包项目上传jar包到私服

  在maven配置文件中添加

<server>
  <id>releases</id>
  <username>admin</username>
  <password>admin123</password>
</server>
<server>
  <id>snapshots</id>
  <username>admin</username>
  <password>admin123</password>
</server>

releases 连接发布版本项目仓库
snapshots 连接测试版本项目仓库

  在要上传的模块的pom文件中配置:

  配置私服仓库的地址,本公司的自己的jar包会上传到私服的宿主仓库,根据工程的版本号决定上传到哪个宿主仓库,如果版本为release则上传到私服的release仓库,如果版本为snapshot则上传到私服的snapshot仓库

<distributionManagement>
    <repository>
        <id>releases</id>
        <url>http://localhost:8081/nexus/content/repositories/releases/</url>
    </repository>
    <snapshotRepository>
        <id>snapshots</id>
        <url>http://localhost:8081/nexus/content/repositories/snapshots/</url>
    </snapshotRepository>
</distributionManagement>

②从中央仓库下载jar包

  首先在maven配置文件中配置

<profile>   
  <!--profile的id-->
  <id>dev</id>   
  <repositories> 
    <repository> 
      <!--仓库id,repositories可以配置多个仓库,保证id不重复-->
      <id>nexus</id>     
      <!--仓库地址,即nexus仓库组的地址-->
      <url>http://localhost:8081/nexus/content/groups/public/</url>   
      <!--是否下载releases构件-->
      <releases>  
        <enabled>true</enabled>   
      </releases> 
      <!--是否下载snapshots构件-->
      <snapshots>  
        <enabled>true</enabled>  
      </snapshots>  
    </repository>  
  </repositories>
  <pluginRepositories> 
    <!--插件仓库,maven的运行依赖插件,也需要从私服下载插件-->
    <pluginRepository> 
      <!--插件仓库的id不允许重复,如果重复后边配置会覆盖前边-->
      <id>public</id>  
      <name>Public Repositories</name>  
      <url>http://localhost:8081/nexus/content/groups/public/</url>   
    </pluginRepository>  
  </pluginRepositories> 
</profile> 

进行激活:
  <activeProfiles>
    <activeProfile>dev</activeProfile>
  </activeProfiles>

③安装第三方jar包到本地仓库或者私服

1、安装到本地仓库,上传其他的修改参数就可以

  mvn install:install-file -Dfile=ojdbc14-10.2.0.4.0.jar -DgroupId=com.oracle -DartifactId=ojdbc14 -Dversion=10.2.0.4.0 -Dpackaging=jar

mvn install:install-file 
-DgroupId=com.aliyun 
-DartifactId=aliyun-sdk-vod-upload 
-Dversion=1.4.14
-Dpackaging=jar 
-Dfile=aliyun-java-vod-upload-1.4.14.jar
mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-java-vod-upload -Dversion=1.4.14 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.14.jar

2、安装到私服:

  首先配置maven配置文件

<server>  
   <id>thirdparty</id>     
   <username>admin</username>
   <password>admin123</password>  
 </server>

  执行:上传其他的修改参数就可以

mvn deploy:deploy-file 
-Dfile=ojdbc14-10.2.0.4.0.jar 
-DgroupId=com.oracle 
-DartifactId=ojdbc14 
-Dversion=10.2.0.4.0 
-Dpackaging=jar 
-DrepositoryId=thirdparty 
-Durl=[http://localhost:8081/nexus/content/repositories/thirdparty/](http://localhost:8081/nexus/content/repositories/thirdparty/)
<dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>

  ① Junit5 使用

@ExtendWith(SpringExtension.class)

  ② Juni4 使用

@RunWith(SpringJUnit4ClassRunner.class)
<build>
    <plugins><!-- 打jar包时如果不配置该插件,打出来的jar包没有清单文件 -->
        <!--springboot项目打包插件-->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        
        <!--,如果上边不能打包,在这里修改版本-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.4.3</version>
            </plugin>
    </plugins>
</build>

#日志,此处用的默认日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 导入插件

/** 
 * SQL执行效率插件
 */
@Bean 
@Profile({"dev","test"})// 设置 dev test 环境开启,保证我们的效率
public PerformanceInterceptor performanceInterceptor() { 
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    performanceInterceptor.setMaxTime(100); // ms设置sql执行的最大时间,如果超过了则不执行
    performanceInterceptor.setFormat(true); // 是否格式化代码
    return performanceInterceptor;
}
  • 配置环境

spring:
  profiles:
    active: dev
mvn install:install-file 
-Dfile=C:\Users\zcf\Desktop\aspose-words-18.6-jdk16.jar
-DgroupId=com.aspose 
-DartifactId=aspose-words 
-Dversion=18.6 -Dpackaging=jar 
-Dclassifier=jdk16
  1. 生成tex文件

  2. 使用pandoc将tex文件转为docx

  3. 使用 pdftex将docx转为pdf ,需要安装 MiKTe X 环境

  引入 aspose-words-20.12-jdk17-crack.jar (是破解过的,地址https://blog.csdn.net/xiaostuart/article/details/111479549)依赖

  1. 测试代码如下:

public class Test {

    public static void main(String[] args) throws Exception {
        File file = new File("C:\\Users\\kuangjie\\Desktop\\hh.tex");
        file.createNewFile();
        long start = System.currentTimeMillis();
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file));
        bufferedWriter.write("第一题:$\\frac{1}{2}-\\frac{1}{2}=0$");
        bufferedWriter.newLine();
        bufferedWriter.write("\r\n");
        bufferedWriter.write("第二题:$\\frac{1}{2}-\\frac{1}{2}=0$");
        bufferedWriter.newLine();
        bufferedWriter.write("\r\n");
        bufferedWriter.write("第三题:$\\frac{1}{2}-\\frac{1}{2}=0$");

        bufferedWriter.flush();
        bufferedWriter.close();

        System.out.println("写入完成:" + (System.currentTimeMillis() - start));
        //开始生成docx文件
        Runtime runtime = Runtime.getRuntime();
        //调用本地命令行
        Process process = runtime.exec("pandoc C:\\Users\\kuangjie\\Desktop\\hh.tex -o C:\\Users\\kuangjie\\Desktop\\hh.docx");
        System.out.println("tex->docx 完成:" + (System.currentTimeMillis() - start));
        while (true){
            //当docx确定生成后在进行pdf的转化
            if (!process.isAlive()){
                Document document = new Document("C:\\\\Users\\\\kuangjie\\\\Desktop\\\\hh.docx");
                document.save("C:\\\\Users\\\\kuangjie\\\\Desktop\\\\hh.pdf");
                break;
            }
        }
        System.out.println("docx->pdf 完成:" + (System.currentTimeMillis() - start));

    }

}

配置docker远程访问

vim /lib/systemd/system/docker.service

# 注释掉
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
# 替换为
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock

# 重启docker
systemctl daemon-reload
systemctl restart docker

# 查看2375端口是否被监听
netstat -nlpt

# 服务器防火墙开启2375端口,阿里云服务器还要开启
firewall-cmd --add-port=2375/tcp --permanent
firewall-cmd --reload
firewall-cmd --zone=public --list-ports

# 测试
curl http://127.0.0.1:2375/version
curl http://47.110.82.22:2375/version

  两个容器保证服务的安全性。

  全部由前端控制器加载也可以正常启动,但是所有数据都存在子容器当中,失去了安全性。

  

启动采用 mvnspring-boot:run

  • 依赖

    <!--使用jsp导入的依赖-->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-tomcat</artifactId>
    </dependency>
    <dependency>
       <groupId>org.apache.tomcat.embed</groupId>
       <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>
    
  • 创建webapp文件夹放置资源,打包方式改为war包

  • yml中添加

spring:
  mvc:
    view:
      prefix: /pages/
      suffix: .jsp
  1. 配置文件

    • application.yml

spring:
  profiles:
    active: @profileActive@

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: root

#mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: root2

#mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  1. pom文件

  然后就可以通过maven切换环境

<project>
  <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <profileActive>dev</profileActive>
            </properties>
        </profile>

        <profile>
            <id>prod</id>
            <properties>
                <profileActive>prod</profileActive>
            </properties>
        </profile>

        <profile>
            <id>test</id>
            <properties>
                <profileActive>test</profileActive>
            </properties>
        </profile>

    </profiles>
</project>

  1. 在方法上设置作为范围

    /**
      * SQL 执行性能分析插件
      * 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
      */
     @Bean
     @Profile({"dev","test"})// 设置 dev test 环境开启
     public PerformanceInterceptor performanceInterceptor() {
         PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
         performanceInterceptor.setMaxTime(100);//ms,超过此处设置的ms则sql不执行
         performanceInterceptor.setFormat(true);
         return performanceInterceptor;
     }
    

  • application-test.yml

  • application-dev.yml

一、什么是统一异常处理

1、制造异常

  除以0

int a = 10/0;

  img

2、什么是统一异常处理

  我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要统一异常处理

二、统一异常处理

1、创建统一异常处理器

  在service-base中创建统一异常处理类GlobalExceptionHandler.java:

/**
 * 统一异常处理类
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e){
        e.printStackTrace();
        return R.error();
    }
}

2、测试

  返回统一错误结果

  img

三、处理特定异常

1、添加异常处理方法

  GlobalExceptionHandler.java中添加

@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public R error(ArithmeticException e){
    e.printStackTrace();
    return R.error().message("执行了自定义异常");
}

2、测试

  img

四、自定义异常

1、创建自定义异常类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GuliException extends RuntimeException {
    @ApiModelProperty(value = "状态码")
    private Integer code;
    private String msg;
    
}

2、业务中需要的位置抛出GuliException

try {
    int a = 10/0;
}catch(Exception e) {
    throw new GuliException(20001,"出现自定义异常");
}

3、添加异常处理方法

  GlobalExceptionHandler.java中添加

@ExceptionHandler(GuliException.class)
@ResponseBody
public R error(GuliException e){
    e.printStackTrace();
    return R.error().message(e.getMsg()).code(e.getCode());
}

4、测试

  img

1.R

package com.kj.commonutils;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.HashMap;
import java.util.Map;

@Data
@ApiModel("返回结果封装类")
public class R {
    @ApiModelProperty(value = "是否成功")
    private Boolean success;

    @ApiModelProperty(value = "返回码")
    private Integer code;

    @ApiModelProperty(value = "返回消息")
    private String message;

    @ApiModelProperty(value = "返回数据")
    private Map<String, Object> data = new HashMap<String, Object>();

    private R(){}

    public static R ok(){
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS.getCode());
        r.setMessage("成功");
        return r;
    }

    public static R error(){
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR.getCode());
        r.setMessage("失败");
        return r;
    }

    public R success(Boolean success){
        this.setSuccess(success);
        return this;
    }

    public R message(String message){
        this.setMessage(message);
        return this;
    }

    public R code(ResultCode code){
        this.setCode(code.getCode());
        return this;
    }

    public R data(ResultDataKey key, Object value){
        this.data.put(key.getKey(), value);
        return this;
    }

    public R data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}

2、ResultCode

package com.kj.commonutils;

/**
 * @Author: kj
 * @Date: 2021/07/29/14:31
 */
public enum ResultCode {
    SUCCESS(20000,"成功"),
    ERROR(20001,"失败");

    private Integer code;
    private String message;

    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

}

3、ResultDataKey

package com.kj.commonutils;

/**
 * @Author: kj
 * @Date: 2021/07/30/10:06
 */
public enum ResultDataKey {

    TOTAL("total", "总记录数"),
    ITEMS("items", "结果集");

    private String key;
    private String message;

    private ResultDataKey(String key, String message) {
        this.key = key;
        this.message = message;
    }

    public String getKey() {
        return key;
    }

    public String getMessage() {
        return message;
    }
}
// BigDecimal保留两位小数 四舍五入

Number v=2.225;

BigDecimal d = new BigDecimal(v).setScale(2, BigDecimal.ROUND_HALF_UP);

// 运行结果:2.22

String v=2.225;

BigDecimal d = new BigDecimal(v).setScale(2, BigDecimal.ROUND_HALF_UP);

// 运行结果 2.23 

// 所以保留两位小数 四舍五入的情况 用String

1.导入依赖

poi 和easyexcel 的版本要对应
java.lang.ExceptionInInitializerError<br />注意cglib版本

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
</dependency>

2.写操作

  • 实体类@ExcelProperty注解

@Data
public class Student {
    @ExcelProperty("学生编号")
    private Integer sno;
    @ExcelProperty("学生姓名")
    private String name;
}
  • 写文件

public class EasyExcelTest {
    public static void main(String[] args) {

        String fileNme = "G:\\excel.xlsx";
    
        //写
        EasyExcel.write(fileNme,Student.class).sheet("学生信息表").doWrite(data());

    }
    public static List<Student> data(){
        List<Student> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Student student = new Student();
            student.setSno(i + 1);
            student.setName("name" + (i + 1));
            list.add(student);
        }
        return list;
    }
}

3.读操作

  • 修改实体类,增加index

@Data
@ColumnWidth(50)
public class Student {
    @ExcelProperty(value = "学生编号",index = 0)
    private Integer sno;
    @ExcelProperty(value = "学生姓名",index = 1)
    private String name;
}

  • 编写监听器

public class ExcelListener extends AnalysisEventListener<Student> {

    //最终的结果集
    List<Student> list = new ArrayList<>();
    
    
    //读表格内数据
    @Override
    public void invoke(Student student, AnalysisContext analysisContext) {
        System.out.println("student***:"+student);
        //添加到最终集合
        list.add(student);
    }

    //读取表头
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头***:"+headMap);
    }

    //读完的操作
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

  • 读取数据

//读
@Test
public void t2(){
    String fileNme = "G:\\excel.xlsx";
    EasyExcel.read(fileNme,Student.class,new ExcelListener()).sheet("学生信息表").doRead();
}

  @Transactional 的写法

  @Transactional如果只这样写,Spring框架的事务基础架构代码将默认地 只 在抛出运行时和unchecked exceptions时才标识事务回滚。也就是说,当抛出个RuntimeException 或其子类例的实例时。(Errors 也一样 - 默认地 - 标识事务回滚。)从事务方法中抛出的Checked exceptions将 不 被标识进行事务回滚。

  • 让checked例外也回滚

  @Transactional(rollbackFor=Exception.class)

  • 让unchecked例外不回滚

  @Transactional(notRollbackFor=RunTimeException.class)

  • 不需要事务管理的(只查询的)方法

  @Transactional(propagation=Propagation.NOT_SUPPORTED)

如果异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{}catch{throw Exception}。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.2</version>
</dependency>
<dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>3.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

  微服务注册名 不要加 下划线 _

# 单机模式
set MODE="standalone"
# 集群
set MODE="cluster"
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
import org.springframework.util.DigestUtils;

public class PasswordUtil {
  public  final static  String slat = "kajfajfkajeiraf*jkfja^&fakjf^#$@jkfja%jfaie%";
  public static  String getMd5(String pawd){
    String base = pawd+slat;
    // 使用spring的工具类进加密
    String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
    return md5;
  }

  public static void main(String[] args) {
    System.out.println(PasswordUtil.getMd5("123"));
  }

}
import java.net.URLDecoder;

public ResponseJson thirdSaveUser(@RequestBody String userJson) {
        String userInfo = "";
        try {
            userInfo = URLDecoder.decode(userJson,"utf-8");
        } catch (UnsupportedEncodingException e) {
            logger.warn("userinfo转码失败");
            e.printStackTrace();
        }
        Map<String, String> map = JSON.parseObject(userInfo, Map.class);

  使用MapperScannerConfigurer组件扫描的是org/tarena/dao文件夹中的所有DAO接口,将这些接口的实现类引入到spring容器中

  2、应当注意到一个细节,org/tarena/dao文件夹保存了很多的接口,如果其中有些接口不是按照Mapper映射接口规则写的,那么,在扫描并生成该接口的实现类时,程序就会出错!

  改进:MapperScannerConfigurer组件支持按注解标记来扫描DAO接口,这样就不会把不符合规则的接口也给实例化了。

  步骤:

  第一步--在spring容器中,为MapperScannerConfigurer组件设置annotationClass(自定义注解)

  操作为:

  <property name="annotationClass" value="org.tarena.annotation.fuckDao"> </property>

  其中:

  (1)annotationClass是关键字,property的name属性必须是这个字符串才表示注入的是自定义注解

  (2)value的值表示用于自定义注解的完整类名,即:包名.类名

  (可以看出,该用于在org.tarena.annotation包下,创建了一个叫fuckDao的注解)

  作用:org/tarena/dao文件夹下,只有标记了"@fuckDao"这个注解的接口才会被自动生成实现类

  第二步--创建我们自己定义的注解

  (1)在当前项目src文件夹下,创建一个包,叫"org.tarena.annotation"

  (2)在这个包下,创建一个注解,代码为:

  package org.tarena.annotation;

  public @interface fuckDao {

  }

  第三步--在MapperScannerConfigurer组件扫描的包中(org.tarena.dao),你想要生成哪个接口的实现类就为该接口标记注解"@fuckDao"

  例:为CostDao接口标记注解(该接口是该映射规则创建的)

  package org.tarena.dao;

  import java.util.List;

  import java.util.Map;

  import org.tarena.annotation.MyBatisDao;

  import org.tarena.entity.Cost;

  @fuckDao //标记注解,将自动创建该接口的实现类

  public interface CostDao {

  public List findAll();

  public List findPage(int begin);

  public List<Map<String, Object>> findMap();

  public int findRows();

  }

  至此,就将mybatis自动生成的DAO接口的实现类给注入到spring容器中了,spring可以通过--

  ApplicationContext.getBean("id值",接口名.class)

  来获取该接口的实现类。

  例:

  String conf = "applicationContext.xml";

  ApplicationContext ac =

  new ClassPathXmlApplicationContext(conf);

  //直接获取接口CostDao的实现类(我们没有写她的实现类,该类是MyBatis自动生成并由MapperFactoryBean引入到spring容器中的)

  CostDao costDao = ac.getBean("costDao",CostDao.class);

  PS:附上spring容器(applicationContext.xml)中的代码:

  <beans ......>

  //不用MapperFactoryBean组件来整合,故把整个bean注掉

<!-- 可以根据指定路径批量生成Dao实现 -->

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

   <!-- 扫描org.tarena.dao包下所有接口,批量生成实现 -->

   <property name="basePackage"   value="org.tarena.dao">  </property>

   <!--注入annotationClass(自定义注解),表示org/tarena/dao文件夹下,只有标记了"@fuckDao"这个注解的接口才会被自动生成实现类-->

   <property name="annotationClass"    value="org.tarena.annotation.fuckDao">

</bean>

gateway网关下配置,不允许加入web依赖
注意这个包下 reactive<br />使用 CorsWebFilter

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

@Configuration
public class CorsConfig{
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //允许的域(写*无法使用cookie),可以写多个
        corsConfiguration.addAllowedOrigin("http://localhost:3000");
//        corsConfiguration.addAllowedOrigin("http://localhost:3001");
        //允许cookie
        corsConfiguration.setAllowCredentials(true);
        //允许的请求头信息
        corsConfiguration.addAllowedHeader("*");
        //允许的请求方式
        corsConfiguration.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsWebFilter(corsConfigurationSource);
    }
}

zuul网关下,其他地方用着没有问题
CorsFilter

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter(){
        //添加一个cors配置信息
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //允许的域(写*无法使用cookie),可以写多个
        corsConfiguration.addAllowedOrigin("http://localhost:3000");
//        corsConfiguration.addAllowedOrigin("http://localhost:3001");
        //允许cookie
        corsConfiguration.setAllowCredentials(true);
        //允许的请求头信息
        corsConfiguration.addAllowedHeader("*");
        //允许的请求方式
        corsConfiguration.addAllowedMethod("*");
        //跨域配置源
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        //注册一个cors,配置拦截路径
        corsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);
        //返回一个corsFilter
        return new CorsFilter(corsConfigurationSource);
    }
}
# 不加在顶层pom中,加在要打包的工程中,
# 缺少基础包的先对依赖的基础包进行install  缺少类的   clear在complie在install
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

  CAP定理:

  指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得。

  一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(所有节点在同一时间的数据完全一致,越多节点,数据同步越耗时)

  可用性(A):负载过大后,集群整体是否还能响应客户端的读写请求。(服务一直可用,而且是正常响应时间)

  分区容错性(P):分区容错性,就是高可用性,一个节点崩了,并不影响其它的节点(100个节点,挂了几个,不影响服务,越多机器越好)

  CA 满足的情况下,P不能满足的原因:

  数据同步(C)需要时间,也要正常的时间内响应(A),那么机器数量就要少,所以P就不满足

  CP 满足的情况下,A不能满足的原因:

  数据同步(C)需要时间, 机器数量也多(P),但是同步数据需要时间,所以不能再正常时间内响应,所以A就不满足

  AP 满足的情况下,C不能满足的原因:

  机器数量也多(P),正常的时间内响应(A),那么数据就不能及时同步到其他节点,所以C不满足

  注册中心选择的原则:

  Zookeeper:CP设计,保证了一致性,集群搭建的时候,某个节点失效,则会进行选举行的leader,或者半数以上节点不可用,则无法提供服务,因此可用性没法满足

  Eureka:AP原则,无主从节点,一个节点挂了,自动切换其他节点可以使用,去中心化

  结论:

  分布式系统中P,肯定要满足,所以我们只能在一致性和可用性之间进行权衡

  如果要求一致性,则选择zookeeper,如金融行业

  如果要求可用性,则Eureka,如教育、电商系统

  没有最好的选择,最好的选择是根据业务场景来进行架构设计

final修饰,底层是char[] 数组保存。

  image-20210901175207047

  image-20210901175147468

  • 字面量 是在堆内存的常量池中开辟空间

  • new的对象,先在堆内存开辟空间,然后再常量池中(有对应的字符串则不开辟新的空间),然后堆内存的空间中保存,常量池中字符串的地址值。

  • 两个 字面量(常量string a ="abc") + 运算进行拼接,还是在常量池中

  • 涉及到变量运算拼接,返回的值是堆内存的地址值。

  • 在调用intent( )方法后,s1=new String("ab");String s4=s1.intern(); 说明:堆空间的s1对象在调用intern( )之后,会将常量池中已经存在的"ab"字符串 赋值给s4。

  结果是 good best 因为change的str是局部变量(在栈内存中),传来的是成员变量str的地址,不影响原字符串对象。

  image-20210901174810211

  • final 修饰后,存在常量池中

  image-20210901181032649

  • 一些常见的类似算法题

    • 模拟一个trim方法,去除字符串两端的空格。

    public String myTrim(String str) {
        int start = 0;
        int end = str.length() - 1;
        while (" ".charAt(0) == str.charAt(start)) {
            start++;
        }
        while (" ".charAt(0) == str.charAt(end)) {
            end--;
        }
        if (start == end) {
            return " ";
        }
        return str.substring(start, end + 1);
    }
    
    
    • 将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反 转为”abfedcg”
      image-20210902100409361

// 翻转
    public String myRever(String str,int start,int end){
        char[] c = str.toCharArray();
        for(int i = start,j = end;i < j;i++,j--){
            char temp = c[i];
            c[i] = c[j];
            c[j] = temp;
        }
        //字符数组--->字符串
        return new String(c);
    }
public static int getTime(String str1,String str2){
  int count = 0;
  int len;
  while((len = str1.indexOf(str2)) != -1){
    count++;
    str1 = str1.substring(len + str2.length());
  }
  return count;
}
@Test
public void t4() {
    String str1 = "hello";
    String str2 = "lloa";
    System.out.println(getMaxSubString(str1,str2));
}

public static List<String> getMaxSubString(String str1, String str2){
    String maxStr = (str1.length() > str2.length())? str1 : str2;
    String minStr = (str1.length() < str2.length())? str1 : str2;
    int len = minStr.length();
    List<String> list = new ArrayList<>();
    for(int i = 0;i < len;i++){
        for(int x = 0,y = len - i;y <= len;x++,y++){
            String str = minStr.substring(x, y);
            if(maxStr.contains(str)){
                list.add(str);
            }
        }
        if(list.size() != 0){
            return list;
        }
    }
    return null;
}
public static String sort(String str){
  char[] c = str.toCharArray();
  Arrays.sort(c);
  return new String(c);
}
  • stringBufferstringBuilder

    • 多线程场景下使用stringBuffer,安全 stringBuilder,效率更高

    • 建议使用指定初始长度的构造器,避免多次扩容,降低效率

    • 效率 stringBuilder > stringBuffer > string

    • String(JDK1.0):不可变字符序列

    • StringBuffer(JDK1.0):可变字符序列、效率低、线程安全

    • StringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全

    • 初始容量为16 ,char[] 数组可动态扩容

    • 指定字符串创建时,容量为字符串长度+16

    • append时 ,判断长度是否满足,不满足进行扩容,扩容( << 位运算)为原来的两倍+2,如果还不满足,直接将传来的长度作为要扩容的长度。

  • 元数据:描述数据的数据,描述一个图片信息的数据,大小、宽高。。。

  • 元注解:描述注解的注解

  只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命 周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值: RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的 注释 RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值 RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会 保留注释。程序可以通过反射获取该注释。

  用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于 修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。

  用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。 定义为Documented的注解必须设置Retention值为RUNTIME。

  被它修饰的 Annotation 将具有继承性。如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解。

  • Retention

  • Target

  • Documented

  • Inherited

  • 自定义注解,注意

    • 定义新的 Annotation 类型使用 @interface 关键字

    • 自定义注解自动继承了java.lang.annotation.Annotation接口

    • Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其 方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能 是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、 以上所有类型的数组。

    • 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始 值可使用 default 关键字

    • 如果只有一个参数成员,建议使用参数名为value

    • 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认 值。格式是“参数名 = 参数值” ,如果只有一个参数成员,且名称为value, 可以省略“value=”

    • 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数 据 Annotation

    • 注意:自定义注解必须配上注解的信息处理流程才有意义

  通过反射获取注解的属性:

@Test
@MyAnnotation(my = {"test", "java"})
public void t6() throws InterruptedException, NoSuchMethodException {
    Class aClass = SortTest.class;
    Method method = aClass.getDeclaredMethod("t6");
    MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
    String[] my = annotation.my();
    System.out.println(my[0]);
    System.out.println(my[1]);
}

// 注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String[] my();
}
  • 新特性

    • JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个: TYPE_PARAMETER,TYPE_USE。

  • 在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用 在任何地方。

    • ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语 句中(如:泛型声明)。

  • ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

(Arrays.asList(1, 2, 3, 4, 5));
直接用aslist会抛异常
Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是 Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合

@Test
public void t7(){
    List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
    list.add(6);
    Iterator iterator = list.iterator();
    while (iterator.hasNext()){
        Integer next = (Integer) iterator.next();
        if (next == 2){
            iterator.remove();
        }
    }
    System.out.println(list);
}

面试:
请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层 是什么?扩容机制?Vector和ArrayList的最大区别?

  • ArrayList和LinkedList的异同 二者都线程不安全,相对线程安全的Vector,执行效率高。 此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于 随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增 和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。

  • ArrayList和Vector的区别 Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于 强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用 ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大 小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

<dependency>
  <groupId>joda-time</groupId>
  <artifactId>joda-time</artifactId>
  <version>2.10.10</version>
</dependency>
/**
    * 转化图片的路径
    * 年月日+系统毫秒值+随机数+文件名
    * 防止文件重复发生覆盖
    * @param fileName
    * @return
    */
   private String changeFilePath(String fileName){
       DateTime dateTime = new DateTime();
       return "images/"+dateTime.toString("yyyy")+"/"
               + dateTime.toString("MM")+"/"
               + dateTime.toString("dd")+"/"
               + System.currentTimeMillis()
               + RandomUtils.nextInt(100,9999)
               + StringUtils.substringBeforeLast(fileName,".")
               + "."
               + StringUtils.substringAfterLast(fileName,".");
   }

  • 对字符串中字符进行自然顺序排序。 提示: 1)字符串变成字符数组。 2)对数组排序,选择,冒泡,Arrays.sort(); 3)将排序后的数组变成字符串。

  • 获取两个字符串中最大相同子串。比如: str1 = "abcwerthelloyuiodef“;str2 = "cvhellobnm" 提示:将短的那个串进行长度依次递减的子串与较长的串比较。

  • 获取一个字符串在另一个字符串中出现的次数。 比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数

  • 依赖

<dependency>
    <groupId>com.belerweb</groupId>
    <artifactId>pinyin4j</artifactId>
    <version>2.5.1</version>
</dependency>
  • 简单工具类

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
/**
 * @Author: kj
 * @Date: 2021/09/24/11:55
 */
public class PinyinUtil {
    /**
     * 获取姓名的首字母小写
     *
     * @param name
     * @return
     */
    public static String getFirstLetterName(String name) {
        char[] chars = name.toCharArray();
        StringBuilder stringBuilder = new StringBuilder();
        for (char n : chars) {
            stringBuilder.append(PinyinHelper.toHanyuPinyinStringArray(n)[0].charAt(0));
        }
        return stringBuilder.toString();
    }
    public static void main(String[] args) {
        System.out.println(getFirstLetterName("匡杰"));
      }
} 
/**
 *
 * @param bytes 转换得字节
 * @param si 是否需要单位
 * @return
 */
public static String byteFormat(long bytes, boolean si) {
    String[] units = new String[]{" B", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB"};
    int unit = 1024;
    int exp = (int) (Math.log(bytes) / Math.log(unit));
    double pre = 0;
    if (bytes > 1024) {
        pre = bytes / Math.pow(unit, exp);
    } else {
        pre = (double) bytes / (double) unit;
    }
    if (si) {
        return String.format(Locale.ENGLISH, "%.1f%s", pre, units[(int) exp]);
    }
    return String.format(Locale.ENGLISH, "%.1f", pre);
}

  使用方式:通过 cmd 命令行,使用 jconsole ,大概如下

  pom依赖:

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.4.1</version>
</dependency>

  工具类:

import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import java.nio.file.FileSystems;
import java.nio.file.Path;

/**
 * @Author: kj
 * @Date: 2022/03/28/15:29
 */
public class QRCodeGenerator {
    private static final String DEFAULT_FORMAT = "png";
    private static final Integer DEFAULT_WIDTH = 300;
    private static final Integer DEFAULT_HEIGHT = 300;

    /**
     * 生成二维码
     *
     * @param content    内容
     * @param width      宽
     * @param height     高
     * @param targetPath 保存图片的位置
     */
    public static void QRCodeGeneratorImg(String content, Integer width, Integer height, String targetPath) {
        try {
            QRCodeWriter qrCodeWriter = new QRCodeWriter();
            BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height);
            Path path = FileSystems.getDefault().getPath(targetPath);
            MatrixToImageWriter.writeToPath(bitMatrix, DEFAULT_FORMAT, path);
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    public static void main(String[] args) {
        String content = "https://www.yuque.com/u21195183/jvm/nwkhey";
        String filePath = "D:\\Desktop\\111.png";
        QRCodeGenerator.QRCodeGeneratorImg(content, QRCodeGenerator.DEFAULT_WIDTH, QRCodeGenerator.DEFAULT_HEIGHT, filePath);
    }
}
  • 方法一

    IPage<AuthUserWorksVo> list = worksService.getAwardWorks(type, userId, state, page, subjectId, award, limit, queryName);
            List<AuthUserWorksVo> records = list.getRecords();
    // records 数据集
            for (int i = 0; i < records.size(); i++) {
                AuthUserWorksVo worksVo = records.get(i);
                if((i - 1) >= 0){
                    worksVo.setPrev(records.get(i - 1).getId());
                }
                if((i + 1) < records.size()){
                    worksVo.setNext(records.get(i + 1).getId());
                }
            }
    

  配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <!--    <property name="log.path" value="D:/mintelog" />-->
    <property name="log.path" value="logs" />


    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">-->
            <pattern>%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger).%M\(%L\) |%cyan(%msg%n)
            </pattern>
            <charset>UTF-8</charset>-->
        </encoder>
    </appender>


    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
        <logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              如果未设置此属性,那么当前logger将会继承上级的级别。
    -->
    <logger name="kj" level="DEBUG" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>

    <root level="ERROR">
        <appender-ref ref="CONSOLE"/>
    </root>

</configuration>

  pom依赖

  <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.11</version>
        </dependency>

  代码中使用

package com.kj.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.*;

/**
 * @Author: kj
 * @Date: 2022/04/26/13:48
 * 创建线程的几种方式
 */
@Slf4j(topic = "kj.log")
public class threadNew {


    //    方法一
    @Test
    public void t1() {
        class myThread extends Thread {
            @Override
            public void run() {
                super.run();
                log.debug("方法一");
            }
        }
        new myThread().start();
    }

    //    方法2
    @Test
    public void t2() {
        log.debug("@222");
        Thread thread = new Thread(() -> log.debug("lambda 使用 runnable 接口实现"));
        thread.start();
    }

    //    方法3
    @Test
    public void t3() {
        log.debug("线程池创建");
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.submit(() -> log.debug("线程池创建"));
    }

    //    方法4
    @Test
    public void t4() throws ExecutionException, InterruptedException {
        log.debug("callable创建");
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("callable创建...");
                return 100;
            }
        });
        Thread t = new Thread(futureTask, "callable 接口实现,可返回值");
        t.start();
        // 等待 新开线程执行完毕,返回值,才执行
        log.debug("{}", futureTask.get());
    }
}

  控制台效果

  ​

  • TPS:

    Transactions Per Second,意思是每秒事务数。一个事务是指客户端向服务器发送请求然后服务器做出反应的过程,具体的事务定义,可以是一个接口、多个接口、一个业务流程等等。以单接口定义为事务举例,每个事务包括了如下3个过程:

    (1)向服务器发请求
    (2)服务器自己的内部处理(包含应用服务器、数据库服务器等)
    (3)服务器返回结果给客户端
    如果每秒能够完成 N 次以上3个过程,TPS 就是 N。

    TPS 是软件测试结果的测量单位。我们在进行服务性能压测时,接口层面最常关注的是最大 TPS 以及接口响应时间,个人理解 TPS 可以指一组逻辑相关的请求,而服务整体处理能力取决于处理能力最低模块的TPS值。

  • QPS:

    Queries Per Second,意思是每秒查询率。指一台服务器每秒能够响应的查询次数,用于衡量特定的查询服务器在规定时间内所处理流量多少,主要针对专门用于查询的服务器的性能指标,比如dns,它不包含复杂的业务逻辑处理,比如数据库中的每秒执行查询sql的次数。QPS 只是一个简单查询的统计显然,不能描述增删改等操作,显然它不够全面,所以不建议用 QPS 来描述系统整体的性能;

    QPS 基本类似于 TPS,但是不同的是,对于一个事务访问,会形成一个 “ T ”;但一次 " T " 中,可能产生多次对服务器的请求,服务器对这些请求,就可计入 QPS 之中。

  区别:

  (1)如果是对一个查询接口压测,且这个接口内部不会再去请求其它接口,那么 TPS = QPS,否则,TPS ≠ QPS

  (2)如果是容量场景,假设 N 个接口都是查询接口,且这个接口内部不会再去请求其它接口,QPS = N * TPS

  当模块A有下面依赖

        <dependency>
            <groupId>com.zhg</groupId>
            <artifactId>interface</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <optional>true</optional>
        </dependency>

  当模块B依赖模块A时,如果不写optional会进行依赖传递,
当optional为true是,这个依赖项不会传递到B
当设置为fasle的时候,这个依赖项会传递到B

  也就是模块B引用了模块A,模块B也不会有模块A中写了<optional>true</optional>的依赖

在线正则地址 https://c.runoob.com/front-end/854/

使用 while 寻找字符串中全部匹配的数据

public static void main(String[] args) {
        String line = "[SC-03]饼图数据、[SC-04]基础饼图、[SC-15]单色玫瑰图、[SD-11]极坐标柱状、[SE-11]排行统计2、[SE-12]排行统计3、[SE-15]排行统计4";
        String regex = "\\[\\w*-\\w*\\]";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(line);
        while (matcher.find()){
            System.out.println(matcher.group(0));
        }
    }
=====================
// 结果
[SC-03]
[SC-04]
[SC-15]
[SD-11]
[SE-11]
[SE-12]
[SE-15]
// regexpStrList 字符串集合
String.join("|", regexpStrList);

  ​Ctrl+alt+H​:查看方法在哪里被引用

  可以再yml中配置多个数据源,然后写配置类,不同的数据源扫描不同的包,获取指定前缀的参数。

  • LocalDateTime类型转换String类型

    public static void main(String[] args) {
            DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            LocalDateTime localDateTime = LocalDateTime.now();
            String dateStr = localDateTime.format(fmt);
            System.out.println(dateStr);
        }
    
  • String类型转换LocalDateTime类型

    public static void main(String[] args) {
            DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            String dateStr = "2021-08-19 15:11:30";
            LocalDateTime date2 = LocalDateTime.parse(dateStr, fmt);
            System.out.println(date2);
    }
    

  ​@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)

  控制,只在请求写入时序列化。这样可以接受到请求参数,但是不能写回给前端

不注册插件的话 需要再数据库连接上加上下面参数。

rewriteBatchedStatements=true

  https://blog.csdn.net/weixin_36380516/article/details/129471230

  真实的批量插入 ​InsertBatchSomeColumn

  ​image

ALTER TABLE t_operation_log ADD COLUMN "col_name" varchar(255) DEFAULT NULL COMMENT '操作的字段';

  ​ mapper.addConverter​ 添加自定义类型转换逻辑

  例如:

mapper.addConverter(new AbstractConverter<String, BigDecimal>() {
            @Override
            protected BigDecimal convert(String s) {
                if (StringUtils.isEmpty(s) || "null".equalsIgnoreCase(s) || "\\".equalsIgnoreCase(s)) {
                    return null;
                }
                if (s.contains("%")) {
                    return new BigDecimal(s.replace("%", "").trim());
                }
                if (s.contains(",")) {
                    return new BigDecimal(s.replace(",", "").trim());
                }
                return new BigDecimal(s.trim());
            }
        });

  先处理下,在格式化,不能直接对string格式化

String data = "12.34";
String data1 = String.format("%.2f",Double.parseDouble(data));

在线cron生成器:https://cron.qqe2.com/

  一、cron概念与格式:

    cron:又叫:计划任务,是在约定的时间执行既定的任务。

   cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式

  1. Seconds Minutes Hours DayofMonth Month DayofWeek Year

    秒 分 小时 月份中的日期 月份 星期中的日期 年份

  2. Seconds Minutes Hours DayofMonth Month DayofWeek

    秒 分 小时 月份中的日期 月份 星期中的日期

  二、格式中每个字段的含义

字段 是否必填 允许值 允许的特殊字符
秒(Seconds) y 0~59的整数 , - * / 四个字符
分(Minutes) y 0~59的整数 , - * / 四个字符
小时(Hours) y 0~23的整数 , - * / 四个字符
日期(DayofMonth) y 1~31的整数(但是你需要考虑你月的天数) ,- * / ? L W C 八个字符
月份(Month) y 1~12的整数或者 JAN-DEC , - * / 四个字符
星期(DayofWeek) y 1~7的整数或者 SUN-SAT (1=SUN) , - * / ? L C # 八个字符
年(Year) n(可选,留空) 1970~2099 , - * / 四个字符

  注意事项:

    每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:

  1. ,​ :表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。

  2. -​ :表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次。

  3. *​ :表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。

  4. /​ :表示步长,在初始值后累加一次步长触发一次。例如在Minutes域使用5/20,则意味着在5分钟时触发一次,而5+20分钟,5+20+20分钟等分别触发一次。

  5. ?​ :只能用在DayofMonth和DayofWeek两个域。表示不关注该域,防止DayofMonth和DayofWeek会相互影响。例如:* * * 20 * ?, 表示任务设置在每月的20日触发调度,不管20日到底是星期几都会触发,而如果把?换成 *,则表示不管星期几都会触发调度, 而DayofMonth又设置为20,表示只能在每个月的20日触发调度,这样就会引起冲突,所以必须要对其中一个设置? 来表示并不关心它为何值。

  6. L​:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

  7. W​:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。

  8. LW​:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

  9. #​:用于确定每个月第几个星期几,只能出现在DayofWeek域。例如在42,表示某月的第二个星期三。

  10. C​:字符“C”允许在日期域和星期域出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)使用较少.

  工具类方法:

 /**
     * 表头动态数据写出
     *
     * @param data      数据
     * @param fileName  文件名
     * @param sheetName sheet表名
     * @param response  response
     * @param clazz     Excel注解类
     * @param head      表头数据,外层list表示多行表头;第二层list表示第几列,第三层list表示该列有几行
     * @param <T>       Excel注解类型一致
     */
    public static <T> void writeByStream(List<T> data,
                                         String fileName,
                                         String sheetName,
                                         HttpServletResponse response,
                                         Class<T> clazz,
                                         List<List<List<String>>> head) {
        try {

            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            response.setHeader("content-disposition", "attachment; filename=" +
                    URLEncoder.encode(fileName + ".xlsx", "UTF-8"));

            ExcelWriter excelWriter = EasyExcel
                    .write(response.getOutputStream())
                    .build();

            WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).needHead(Boolean.FALSE).build();

            int headSize = head.size();
            for (int i = 0; i < headSize; i++) {
                WriteTable writeTable = EasyExcel.writerTable(i).needHead(Boolean.TRUE).head(head.get(i)).build();
                excelWriter.write(new ArrayList<>(), writeSheet, writeTable);
            }

            WriteTable writeTable =
                    EasyExcel.writerTable(headSize + 1).needHead(Boolean.TRUE).head(clazz).build();

            excelWriter.write(data, writeSheet, writeTable);
            excelWriter.finish();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

  表头生成参考:

	List<List<List<String>>> head;

        List<List<String>> headSingleDept1 = new ArrayList<>();
        headSingleDept1.add(Collections.singletonList("财务编码"));
        headSingleDept1.add(Collections.singletonList("CW11")));

        List<List<String>> headSingleDept2 = new ArrayList<>();
        headSingleDept2.add(Collections.singletonList("项目名称"));
        headSingleDept2.add(Collections.singletonList("项目1-124"));

        List<List<String>> headSingleDept3 = new ArrayList<>();
        headSingleDept3.add(Collections.singletonList("部门名称"));
        headSingleDept3.add(Collections.singletonList("政企业务部"));

        head = CollectionUtil.newArrayList(headSingleDept1, headSingleDept2, headSingleDept3);

  表头效果参考:

  ​image

  1. Git bash 命令行,敲4次生成公私钥

    ssh-keygen -t rsa -C '1760141843@qq.com'
    
  2. 查看公钥内容

    cat ~/.ssh/id_rsa.pub
    
  3. 在Gitee、GitHub网站个人设置中,添加 SSH 的公钥

package com.citc.tra.task;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("bda-test")
class SynchronousDataTaskTest {

    private final SynchronousDataTask synchronousDataTask;

    @Autowired
    SynchronousDataTaskTest(SynchronousDataTask synchronousDataTask) {
        this.synchronousDataTask = synchronousDataTask;
    }

    @Test
    void analyseTransLogSendEmail() {
        synchronousDataTask.analyseTransLogSendEmail();
    }
}

一、基本方法

  有了SpringBoot貌似一切都变了非常简单,定时任务也不用整合quartz了,直接schedule就欧了。具体咋用?非常简单。

1、引入POM依赖

  Spring的Schedule包含在spring-boot-starter模块中,无需引入其他依赖。

2、开启注解支持

  在启动类增加注解:@EnableScheduling

3、轻松上手使用

A、Cron表达式

// 当方法的执行时间超过任务调度频率时,调度器会在下个周期执行
@Scheduled(cron = "0/10 * * * * *") 
private void cronSchedule() {
  LOGGER.info("cronSchedule");
}

B、固定间隔

// 固定间隔,等上一次调度完成后,再开始计算间隔,再执行
@Scheduled(fixedDelay = 5000) 
public void fixedDelaySchedule() {
    LOGGER.info("fixedDelaySchedule");
}

C、固定频率

// 固定频率,如果上次调度超过了频率时间,那么在其完成后,立刻执行
@Scheduled(fixedRate = 3000) 
public void fixedRateSchedule() {
  LOGGER.info("fixedRateSchedule");
}

二、注意要点

  Spring的Schecule默认是单线程执行的,如果你定义了多个任务,那么他们将会被串行执行,会严重不满足你的预期。

  所以为了解决该问题,需要自定义线程池,具体如下:

/**

 * 自定义线程池
   */
   @Component
   public class ScheduleConfig implements SchedulingConfigurer {

   @Override
   public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
     taskRegistrar.setScheduler(customScheduler());
   }

   @Bean(destroyMethod = "shutdown")
   public ExecutorService customScheduler() {
     return Executors.newScheduledThreadPool(20);
   }

}

1、依赖

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
    </dependency>
</dependencies>

2、controller层

import com.kj.commonutils.R;
import com.kj.edusms.serivce.SmsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @Author: kj
 * @Date: 2021/08/19/14:21
 */
@RestController
@RequestMapping("/edusms/sms")
@CrossOrigin
@Api(description = "短信验证码管理")
public class SmsController {

    @Autowired
    private SmsService smsService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @GetMapping("/sendCode/{phone}")
    @ApiOperation("发送验证码")
    public R sendCode(@PathVariable String phone) {
        // redis 中获取
        String code = redisTemplate.opsForValue().get(phone);
        if(!StringUtils.isEmpty(code)) return R.ok();

        code = new Random().nextInt(1000000) + "";
        Map<String,Object> param = new HashMap<>();
        param.put("code", code);
        boolean isSend = smsService.send(phone, "SMS_180051135", param);
        if(isSend) {
            // 存到redis中
            redisTemplate.opsForValue().set(phone, code,5, TimeUnit.MINUTES);
            return R.ok();
        } else {
            return R.error().message("发送短信失败");
        }
    }
}

3、发送短信

/**
     * 发送短信
     */
    public boolean send(String PhoneNumbers, String templateCode, Map<String,Object> param) {

        if(StringUtils.isEmpty(PhoneNumbers)) return false;

        DefaultProfile profile =
                DefaultProfile.getProfile("default", "LTAIq6nIPY09VROj", "FQ7UcixT9wEqMv9F35nORPqKr8XkTF");
        IAcsClient client = new DefaultAcsClient(profile);

        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");

        request.putQueryParameter("PhoneNumbers", PhoneNumbers);
        request.putQueryParameter("SignName", "我的谷粒在线教育网站");
        request.putQueryParameter("TemplateCode", templateCode);
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));

        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            return response.getHttpResponse().isSuccess();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return false;
    }

1.配置微信

#appid  appsecret 是尚硅谷的   redirecturl:内网穿透的本机
wx:
  appid: wxed9954c01bb89b47
  appsecret: a7482517235173ddb4083788de60b90e
  redirecturl: http://3936p604k5.qicp.vip:29375/ucenter/wx/callback

2.读取配置文件

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: kj
 * @Date: 2021/08/24/14:20
 */
@Configuration
@ConfigurationProperties(prefix = "wx")
@Data
public class WxProperties {
    private String appid;
    private String appsecret;
    private String redirecturl;
}

3.生成二维码、获取用户信息

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kj.common.exceptionHandler.GuliException;
import com.kj.common.utils.JsonUtils;
import com.kj.commonutils.HttpClientUtils;
import com.kj.commonutils.R;
import com.kj.commonutils.ResultCode;
import com.kj.eduucenter.config.WxProperties;
import com.kj.eduucenter.entity.UcenterMember;
import com.kj.eduucenter.service.UcenterMemberService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: kj
 * @Date: 2021/08/24/14:53
 */
@Controller
@CrossOrigin
//@RequestMapping("/eduucenter/wx")
@RequestMapping("/api/ucenter/wx")
public class WxController {

    @Autowired
    private WxProperties wxProperties;

    @Autowired
    private UcenterMemberService ucenterMemberService;

    /**
     * 获取微信登录二维码
     *
     * @return
     */
    @GetMapping("/login")
    public String wxLogin() {
        String redirect_uri = URLEncoder.encode(wxProperties.getRedirecturl(), StandardCharsets.UTF_8);
        String url = "https://open.weixin.qq.com/connect/qrconnect?" +
                "appid=" + wxProperties.getAppid() +
                "&redirect_uri=" + redirect_uri +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=kjcyy" +
                "#wechat_redirect";
        return "redirect:" + url;
    }

    /**
     * 回调地址
     *
     * @param code  微信返回的临时code
     * @param state 验证
     * @return
     */
    @GetMapping("/callback")
    public String callback(String code, String state) {
        if (!"kjcyy".equals(state)) throw new GuliException(ResultCode.ERROR.getCode(), "非法访问");

        //根据code获取token和openId
        String tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
                "appid=" + wxProperties.getAppid() +
                "&secret=" + wxProperties.getAppsecret() +
                "&code=" + code +
                "&grant_type=authorization_code";
        try {
            String tokenResult = HttpClientUtils.get(tokenUrl, "utf-8");
            HashMap<String, String> hashMap = JSON.parseObject(tokenResult, HashMap.class);
            String access_token = hashMap.get("access_token");
            String openid = hashMap.get("openid");
            //根据 token 和 openid 去获取用户信息
            String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?" +
                    "access_token=" + access_token +
                    "&openid=" + openid;
            String userResult = HttpClientUtils.get(userInfoUrl);
            HashMap<String, String> userInfoMap = JSON.parseObject(userResult, HashMap.class);

            //构建一个用户对象
            UcenterMember ucenterMember = new UcenterMember();
            ucenterMember.setOpenid(userInfoMap.get("openid"));
            ucenterMember.setNickname(userInfoMap.get("nickname"));
            ucenterMember.setAvatar(userInfoMap.get("headimgurl"));
            ucenterMember.setSex(1);
            // 从数据库查询用户,不存在创建,存在就更新
            UcenterMember memberByOpenid = this.ucenterMemberService.getOne(new QueryWrapper<UcenterMember>().eq("openid", openid));
            if (memberByOpenid == null) {
                // 用户不存在
                this.ucenterMemberService.save(ucenterMember);
            } else {
                // 用户存在
                BeanUtils.copyProperties(ucenterMember, memberByOpenid);
                this.ucenterMemberService.updateById(memberByOpenid);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "redirect:http://localhost:3000";
    }
}

  • 依赖

<!--邮件-->
<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4.3</version>
</dependency>  
  • 工具类

package com.kj.edusms.util;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;

/**
 * 发邮件工具类
 */
public final class MailUtil {
    private static final String USER = "ql76817@126.com"; // 发件人称号,同邮箱地址
    private static final String PASSWORD = "GLKTJULVSQVMHBVE"; // 如果是qq邮箱可以使户端授权码,或者登录密码

    /**
     *
     * @param to 收件人邮箱
     * @param text 邮件正文
     * @param title 标题
     */
    /* 发送验证信息的邮件 */
    public static boolean sendMail(String to, String text, String title){
        try {
            final Properties props = new Properties();
            props.put("mail.smtp.auth", "true");
            props.put("mail.smtp.host", "smtp.126.com");

            // 发件人的账号
            props.put("mail.user", USER);
            //发件人的密码
            props.put("mail.password", PASSWORD);

            // 构建授权信息,用于进行SMTP进行身份验证
            Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    // 用户名、密码
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };
            // 使用环境属性和授权信息,创建邮件会话
            Session mailSession = Session.getInstance(props, authenticator);
            // 创建邮件消息
            MimeMessage message = new MimeMessage(mailSession);
            // 设置发件人
            String username = props.getProperty("mail.user");
            InternetAddress form = new InternetAddress(username);
            message.setFrom(form);

            // 设置收件人
            InternetAddress toAddress = new InternetAddress(to);
            message.setRecipient(Message.RecipientType.TO, toAddress);

            // 设置邮件标题
            message.setSubject(title);

            // 设置邮件的内容体
            message.setContent(text, "text/html;charset=UTF-8");
            // 发送邮件
            Transport.send(message);
            return true;
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) throws Exception { // 做测试用
        MailUtil.sendMail("1760141843@qq.com","你好,这是一封测试邮件,无需回复。","测试邮件");
        System.out.println("发送成功");
    }

}

DTO 全部使用 String 类型参数接受解析的结果

对于其业务的校验因该在校验逻辑中、或在 Entity 业务对象中进行校验、和数据类型的转变。

  • 自定义注解检验

    • 自定义注解

    package com.citc.tra.validate;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.*;
    
    @Constraint(validatedBy = NumberValidator.class)
    @Documented
    @Target(value = { ElementType.PARAMETER, ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface Number {
    
        String message() default "不为数据格式";
    
        boolean allowNull() default false;
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
    }
    
    • @Constraint(validatedBy = NumberValidator.class) ​指定校验实现类,放入spring容器

      实现 ConstraintValidator​接口,重写校验逻辑

      package com.citc.tra.validate;
      
      import org.apache.commons.lang3.math.NumberUtils;
      import org.springframework.stereotype.Component;
      import org.springframework.util.StringUtils;
      
      import javax.validation.ConstraintValidator;
      import javax.validation.ConstraintValidatorContext;
      
      @Component
      public class NumberValidator implements ConstraintValidator<Number, String> {
      
          private boolean allowNull;
      
          @Override
          public void initialize(Number constraintAnnotation) {
              allowNull = constraintAnnotation.allowNull();
          }
      
          @Override
          public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
              if (allowNull && StringUtils.isEmpty(s)) {
                  return false;
              }
              return NumberUtils.isParsable(s);
          }
      }
      
      
  • 校验工具类

    • Validator

    • 可以单属性进行校验。

      image

    • 可以分组,在不同逻辑场景下出发校验

      image

    package com.citc.budget.validate;
    
    import com.citc.budget.utils.StringUtils;
    import com.citc.budget.excepption.ParamValidateException;
    import org.apache.commons.collections4.CollectionUtils;
    import org.hibernate.validator.HibernateValidator;
    import org.hibernate.validator.HibernateValidatorConfiguration;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;
    import javax.validation.ValidatorFactory;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    public class ValidatorUtils {
    
        public static Set<String> validate(Object o) {
            HibernateValidatorConfiguration configure = Validation.byProvider(HibernateValidator.class).configure();
            ValidatorFactory validatorFactory = configure.failFast(false).buildValidatorFactory();
            Validator validator = validatorFactory.getValidator();
            Set<ConstraintViolation<Object>> set = validator.validate(o);
            return set.stream().map(msg -> msg.getMessage()).collect(Collectors.toSet());
        }
    
        public static void checkParams(Object o) {
            Set<String> messages = validate(o);
            if (CollectionUtils.isNotEmpty(messages)) {
                throw new ParamValidateException(StringUtils.join(messages, ","));
            }
        }
    
    }
    
  • 在需要校验的对象上使用注解,调用校验工具类的方法,获取校验结果

  文件路径:.idea/misc.xml

<component name="yapi">
    <option name="projectToken">295919ea639ebac4cfdc73c933b035cf9f62b91913cbfac1878b640ef1e7e042</option>
    <option name="projectId">25</option>
    <option name="yapiUrl">http://10.10.48.32:30001/</option>
    <option name="projectType">api</option>
  </component>
  • 注册插件

    注意版本不同,注册方式可能有区别

    @Configuration
    @EnableTransactionManagement
    public class MybatisPlusConfig{
    
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
            paginationInnerInterceptor.setOptimizeJoin(true);
            paginationInnerInterceptor.setDbType(DbType.MYSQL);
            paginationInnerInterceptor.setOverflow(true);
            interceptor.addInnerInterceptor(paginationInnerInterceptor);
            OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
            interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
            return interceptor;
        }
    
    }
    
    
  • 在调用dao时,传入分页对象 page​ ,然后不管是复杂的使用xml查询还是使用 wrapper 的简单查询。都会返回一个ipage​ (官方提供的一个封装的结果对象,可以进行自定义)对象,

    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
    	 /**
         * <p>
         * 查询 : 根据gender性别查询用户列表,分页显示
         * </p>
         *
         * @param page 指定当前页码1 ,每页显示2行
         * @param state 状态
         * @return 分页对象
         */
        @RequestMapping("/{gender}")
        public void test(@PathVariable Integer gender) {
    		// 模拟复杂分页查询
            IPage<UserEntity> userEntityIPage = userService.selectUserByGender(new Page<>(1, 2), gender);
            System.out.println("总页数: " + userEntityIPage.getPages());
            System.out.println("总记录数: " + userEntityIPage.getTotal());
            userEntityIPage.getRecords().forEach(System.out::println);
    		// 简单分页查询
            QueryWrapper<UserEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("gender", gender);
            userService.pageByGender(new Page<>(1, 2), queryWrapper);
            System.out.println("总页数: " + userEntityIPage.getPages());
            System.out.println("总记录数: " + userEntityIPage.getTotal());
            userEntityIPage.getRecords().forEach(System.out::println);
        }
    }