后端
参考地址: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
-
生成tex文件
-
使用pandoc将tex文件转为docx
-
使用 pdftex将docx转为pdf ,需要安装 MiKTe X 环境
引入 aspose-words-20.12-jdk17-crack.jar (是破解过的,地址https://blog.csdn.net/xiaostuart/article/details/111479549)依赖
-
测试代码如下:
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
-
配置文件
-
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
-
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>
-
在方法上设置作为范围
/** * 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);
}
-
stringBuffer 与 stringBuilder
-
多线程场景下使用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
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有如下两种语法格式:
-
Seconds Minutes Hours DayofMonth Month DayofWeek Year
秒 分 小时 月份中的日期 月份 星期中的日期 年份
-
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 | , - * / 四个字符 |
注意事项:
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
-
, :表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
-
- :表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次。
-
* :表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。
-
/ :表示步长,在初始值后累加一次步长触发一次。例如在Minutes域使用5/20,则意味着在5分钟时触发一次,而5+20分钟,5+20+20分钟等分别触发一次。
-
? :只能用在DayofMonth和DayofWeek两个域。表示不关注该域,防止DayofMonth和DayofWeek会相互影响。例如:* * * 20 * ?, 表示任务设置在每月的20日触发调度,不管20日到底是星期几都会触发,而如果把?换成 *,则表示不管星期几都会触发调度, 而DayofMonth又设置为20,表示只能在每个月的20日触发调度,这样就会引起冲突,所以必须要对其中一个设置? 来表示并不关心它为何值。
-
L:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
-
W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。
-
LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
-
#:用于确定每个月第几个星期几,只能出现在DayofWeek域。例如在42,表示某月的第二个星期三。
-
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);
表头效果参考:
-
Git bash 命令行,敲4次生成公私钥
ssh-keygen -t rsa -C '1760141843@qq.com'
-
查看公钥内容
cat ~/.ssh/id_rsa.pub
-
在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
-
可以单属性进行校验。
-
可以分组,在不同逻辑场景下出发校验
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); } }