server:
port: 8080
spring:
application:
# 应用的名称 可选择配置
name: reggie_take_out
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
mybatis-plus:
configuration:
# address_book ----->AddressBook
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
@Slf4j
@Configuration
public class WebMVCConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射...");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
@RequestBody
主要用于接收前端传递给后端的json字符串(请求体中的数据)HttpServletRequest
作用:如果登录成功,将员工对应的id存到session一份,这样想获取一份登录用户的信息就可以随时获取出来@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 登录功能
*
* @param request
* @param employee
* @return
* @RequestBody 主要用于接收前端传递给后端的json字符串(请求体中的数据)
* HttpServletRequest 作用:如果登录成功,将员工对应的id存到session一份,这样想获取一份登录用户的信息就可以随时获取出来
*/
@PostMapping("/login")
// wrapper.eq("实体类::查询字段", "条件值"); //相当于where条件
public R login(HttpServletRequest request, @RequestBody Employee employee) {
//1 将页面提交的密码进行md5加密
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
//2 根据页面提交的用户名username查询数据库
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("username", employee.getUsername());
Employee emp = employeeService.getOne(wrapper);
//3 如果没有查询到则返回登陆失败信息
if (emp == null) {
return R.error("登陆失败");
}
//4 密码比对 如果不一致返回登陆失败结果
if (!emp.getPassword().equals(password)) {
return R.error("登陆失败");
}
//5 查看员工状态 如果说已禁用状态 返回禁用结果
if (emp.getStatus() == 0) {
return R.error("账号已经禁用");
}
//6 登陆成功 将员工id存入Session并返回登陆成功结果
request.getSession().setAttribute("employee", emp.getId());
return R.success(emp);
}
问题分析:
/**
* 检查用户是否完成登陆的过滤器
*/
@Slf4j
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
log.info("拦截到请求:{}", request.getRequestURI());
filterChain.doFilter(request, response);
}
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class, args);
}
}
/**
* 检查用户是否完成登陆的过滤器
*/
@Slf4j
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
/**
* 路径匹配器
* 判断本次请求是否需要处理
* 使用Spring概念模型: PathMatcher
*/
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1 获取本次请求的URI
String requestURI = request.getRequestURI();
//定义不需要的路径请求
String[] urls = new String[]{"/employee/login", "/employee/logout", "/backend/**", "/front/**"};
//2 判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3 如果不需要处理 则直接放行
if (check) {
log.info("本次请求:{},不需要处理",requestURI);
filterChain.doFilter(request, response);
return;
}
//4 判断用户登录状态,如果已经登录,则直接放行 当初存的 session是employee 所以拿到他来判断
if (request.getSession().getAttribute("employee") != null) {
filterChain.doFilter(request, response);
return;
}
//5 如果未登录返回未登录结果 通过输出流 向客户端页面响应数据
//未登录状态为什么要返回一个error呢?而且msg为NOTLOGIN
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
/**
* 路径匹配, (check)检查本次请求是否需要放行
* @param requestURI
* @param urls
* @return
*/
public boolean check(String[] urls, String requestURI) {
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match) {
//匹配
return true;
}
}
//不匹配
return false;
}
}
当我们直接访问 http://localhost/backend/index.html 时,日志输出如下
: 用户未登录
: 用户id为:null
随后将自动跳转至登录页面
: 拦截到请求:/employee/login
: 本次请求:/employee/login,不需要处理
成功登录后
: 拦截到请求:/employee/page
: 用户已登录,id为1
那么至此,登录功能就已经做好了
登出功能的后端操作很简单,只要删除session就好了
/**
* 员工退出
* @param request
* @return
*/
@PostMapping("/logout")
public R logout(HttpServletRequest request){
// 清理Session中的用户id
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
实现功能之前,我们先梳理一下整个执行流程
我们主要做第二步和第三步
先测试一下是否能成功接收到员工信息,用日志输出看一下
JAVA
1 2 3 4 5 |
|
启动服务器,测试添加员工功能,随便输点数据,查看日志
: 新增的员工信息:Employee(id=null, username=kyle, name=KKK, password=null, phone=15811234568, sex=1, idNumber=111111222222333333, status=null, createTime=null, updateTime=null, createUser=null, updateUser=null)
但此时的表单中只有部分数据,id,password,status,createTime等都还没有指定。
那么我们现在来逐一分析这几项该如何设置
id
这个就用自动生成的就好了(雪花算法/自动递增)password
当你注册某些教育网站的时候,一般都会给你默认指定一个密码(身份证后六位,123456等),所以我们这里的解决策略就直接指定一个123456了,但是这个密码不能直接在数据库中设为默认值,因为数据库设置的默认值无法加密status
设定员工的状态,1表示启用,0表示禁用,这个就可以直接用默认值了,不需要加密,默认给个1即可createTime
创建时间,这个就指定当前时间就好了updateTime
作用同上createUser
这个是创建人的ID,我们首先需要一个管理员账号登录到后台管理界面,然后才能添加员工信息,所以我们也需要对这个员工信息的创建人,进行设置,避免出现莫名的员工账号,依靠这个可以溯源updateUser
作用同上综上所述,我们只需要设置密码,创建时间和更新时间,创建人ID和修改人ID
从前端代码来看,我们需要发送Post请求,且不需要参数
/**
* 新增员工
*
* @param employee
* @return
*/
@PostMapping
public R save(HttpServletRequest request, @RequestBody Employee employee) {
log.info("新增员工信息:{}", employee.toString());
//设置初始密码123456进行md5加密处理
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//设置创建时间和修改时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获得当前登录用户的id
Long empId = (Long) request.getSession().getAttribute("employee");
//并设置
employee.setCreateUser(empId);
employee.setUpdateUser(empId);
//存入数据库
employeeService.save(employee);
return R.success("添加员工成功");
}
那么至此添加员工的功能就开发完毕了,启动服务器,测试一下添加员工,添加完毕后,如果没有问题,会显示添加员工成功,之后去数据库查看,数据库中也有对应的数据,且密码也经过了加密,createTime和createUser等数据也都有。
值得注意的一点是,username不能重复,因为在建表的时候设定了unique,只能存在唯一的username,如果存入相同的username则会报错
控制台报错java.sql.SQLIntegrityConstraintViolationException: Duplicate entry ‘zhangsan' for key 'employee.idx_username'
但是这个报错目前也不太人性化,咱也不知道具体为啥添加失败了,所以我们还得继续完善一下,那么具体该怎么完善呢?我们在之前使用过统一异常处理。
在com.itheima.common
包下创建一个全局异常处理类GlobalExceptionHandler
,并添加exceptionHandler方法用来捕获异常,并返回结果
/**
* 全局异常处理类
* 底层基于代理 aop
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class}) //通知
@ResponseBody // 封装成为json数据来返回
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R exceptionHandler(SQLIntegrityConstraintViolationException exception){
log.error(exception.getMessage());
return R.error("失败了");
}
}
先用日志输出一下看看能不能正常运行,这也是代码开发的一个好习惯
启动服务器,新增员工测试,输入数据库中已存在的username,这次会报错未知错误
(如果你还没报未知错误,建议先调试好再往下进行)
控制台日志输出的错误信息为Duplicate entry 'zhangsan' for key 'employee.idx_username'
然后我们再来开发具体的异常处理逻辑
我们希望给出的错误信息为该用户名已存在,所以我们就需要对错误信息来进行判断,如果错误信息中包含Duplicate entry
,则说明有条目是重复的,在本案例中,只可能是username重复了,所以我们在用split()方法来对错误信息切片,取出重复的username,采用字符串拼接的方式,告知该用户已经存在了
/**
* 全局异常处理类
* 底层基于代理 aop
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class}) //通知
@ResponseBody // 封装成为json数据来返回
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
*
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R exceptionHandler(SQLIntegrityConstraintViolationException exception) {
log.error(exception.getMessage());
//如果包含Duplicate entry 说明有条目重复
if (exception.getMessage().contains("Duplicate entry")) {
//对字符串进行切片
String[] split = exception.getMessage().split(" ");
String username = split[2];
//拼串作为错误信息 返回
return R.error("用户名:" + username + "已存在");
}
//如果是别的错误也没办法
return R.error("未知错误");
}
接下来重启服务器,测试添加功能,输入已经存在的username,输出的错误信息符合我们的预期员工信息分页查询
新建com.itheima包,并在其中新建MPConfig
类
@Configuration
public class MPConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
我们先来访问页面,打开开发者工具,点击员工管理,监测一下Network请求,会看到这么个东西
请求网址: http://localhost/employee/page?page=1&pageSize=10
请求方法: GET
使用GET发送的请求,请求参数在URL中
在搜索框中输入123,进行查询,发现name也出现在URL中了
请求网址: http://localhost/employee/page?page=1&pageSize=10&name=123
请求方法: GET
我们先来用日志输出一下,看看能不能正常接收到数据
JAVA
1 2 3 4 5 |
|
重新启动服务器,在搜索框输入123并搜索,查看日志输出,符合我们的预期
: page=1,pageSize=10,name=123
@GetMapping("/page")
public R page(int page, int pageSize, String name) {
log.info("page={},pageSize={},name={}", page, pageSize, name);
//构造分页构造器
Page pageInfo = new Page(page, pageSize);
//构造条件构造器
LambdaQueryWrapper wrapper = new LambdaQueryWrapper();
//添加过滤条件 意思就是name!=null 才会添加
wrapper.like(StringUtils.isNotEmpty(name), Employee::getName, name);
//对查询的结果进行 降序 排列 根据时间进行更新
wrapper.orderByDesc(Employee::getUpdateTime);
//执行查询
employeeService.page(pageInfo, wrapper);
return R.success(pageInfo);
}
HTML
1 2 3 4 |
|
怎么才能做到:只有当登录的是管理员账号时,才能看到启用/禁用按钮呢?
JS
1 2 3 4 |
created() { this.init() this.user = JSON.parse(localStorage.getItem('userInfo')).username } |
admin
,如果是的话就显示启用/禁用,否则不显示v-if
来判断 HTML
1 2 3 4 5 6 7 8 9 |
|
前端代码分析
从禁用/启用的按钮中,我们可以看到是绑定了一个名为statusHandle(scope.row)
函数
HTML
1 2 3 4 5 6 7 8 9 |
|
后端代码分析
启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作在Controller中创建update方法,此方法是一个通用的修改员工信息的方法
只不过现在我们的update只需要修改status,而后面我们还有修改员工其他信息的业务,根据传进来的employee
JAVA
1 2 3 4 5 |
@PutMapping public Result |
按照惯例,我们先启动一下服务器,看看是否能接收到employee对象数据
点击禁用按钮,日志输出如下
: Employee(id=1575840690817011700, username=null, name=null, password=null, phone=null, sex=null, idNumber=null, status=0, createTime=null, updateTime=null, createUser=null, updateUser=null)
id和status均有值,符合我们的预期,那我们继续往下进行
完善update方法的代码逻辑
状态修改我们已经在前面完成了,这里来编写一下更新时间和更新用户
依旧是通过我们之前存的session来获取当前user的id
JAVA
1 2 3 4 5 6 7 8 9 |
@PutMapping public Result |
查看数据库,我们发现status并没有被修改
通过查看日志,我们发现更新操作并没有完成,这是怎么回事呢?
==> Preparing: UPDATE employee SET status=?, update_time=?, update_user=? WHERE id=?
==> Parameters: 0(Integer), 2022-10-04T09:37:21.459(LocalDateTime), 1(Long), 1575840690817011700(Long)
<== Updates: 0
1575840690817011700
,而实际的id值为1575840690817011713
配置对象映射器JacksonObjectMapper,继承ObjectMapper
直接Copy这份代码也行
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import java.math.BigInteger; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; /** * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象] * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON] */ public class JacksonObjectMapper extends ObjectMapper { public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; public JacksonObjectMapper() { super(); //收到未知属性时不报异常 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); //反序列化时,属性不存在的兼容处理 this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); SimpleModule simpleModule = new SimpleModule() .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))) .addSerializer(BigInteger.class, ToStringSerializer.instance) .addSerializer(Long.class, ToStringSerializer.instance) .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); //注册功能模块 例如,可以添加自定义序列化器和反序列化器 this.registerModule(simpleModule); } } |
扩展Mvc框架的消息转换器
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Configuration @Slf4j public class WebMvcConfig extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/"); registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/"); } @Override protected void extendMessageConverters(List |
启动服务器,尝试禁用按钮
数据库中的status字段数据发生了改变,且页面上也显示已禁用,再次点击启用,也能正常操作
在开发代码之前,我们先来梳理一下整个操作流程与对应程序的执行顺序
add.html
,并在url中携带参数员工id
add.html
页面中获取url中的参数员工id
ajax
请求,请求服务端,同时提交员工id
参数员工id
查询员工信息,并将员工信息以json
形式响应给页面json
数据,并通过Vue的双向绑定
进行员工信息回显add.html
,并在url中携带参数员工id
addMemberHandle(scope.row.id)
HTML
1 2 3 4 5 6 7 8 9 |
|
add.html
页面中获取url中的参数员工id
JS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
addMemberHandle (st) { if (st === 'add'){ window.parent.menuHandle({ id: '2', url: '/backend/page/member/add.html', name: '添加员工' },true) } else { window.parent.menuHandle({ id: '2', url: '/backend/page/member/add.html?id='+st, name: '修改员工' },true) } } |
发送ajax
请求,请求服务端,同时提交员工id
参数
add.html加载完毕之后,调用钩子函数,主要看其中requestUrlParam
函数
JS
1 2 3 4 5 6 7 |
created() { this.id = requestUrlParam('id') this.actionType = this.id ? 'edit' : 'add' if (this.id) { this.init() } } |
服务端接受请求,并根据员工id
查询员工信息,并将员工信息以json
形式响应给页面
JAVA
1 2 3 4 5 6 |
@GetMapping("/{id}") public Result |
json
数据,并通过Vue的双向绑定
进行员工信息回显created
钩子函数中还调用了init
函数json
数据之后,先判断一下状态码,如果是1,则说明是操作成功JS
1 2 3 4 5 6 7 8 9 10 11 12 13 |
async init () { queryEmployeeById(this.id).then(res => { console.log(res) if (String(res.code) === '1') { console.log(res.data) this.ruleForm = res.data this.ruleForm.sex = res.data.sex === '0' ? '女' : '男' // this.ruleForm.password = '' } else { this.$message.error(res.msg || '操作失败') } }) } |
HTML
1 2 3 4 5 6 |
|
submitForm
函数,而在submitForm
函数中我们又调用了editEmployee
函数,发送PUT请求,实现修改功能 JAVA
1 2 3 4 5 6 7 8 9 |
@PutMapping public Result |
goBack
函数,跳转至员工管理页面 JS
1 2 3 4 5 6 7 |
goBack(){ window.parent.menuHandle({ id: '2', url: '/backend/page/member/list.html', name: '员工管理' },false) } |
那么至此,编辑员工信息的功能就完成了
MybatisPlus
给我们提供的公共字段自动填充功能@TableFiled
注解,指定自动填充的策略
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
@Data public class Employee implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String username; private String name; private String password; private String phone; private String sex; private String idNumber;//身份证号码 private Integer status; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; } |
MetaObjectHandler
接口setValue
来实现MyMetaObjectHandler
类中不能获得HttpSession对象,所以我们需要用其他方式来获取登录用户Id JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Component @Slf4j public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("公共字段自动填充(insert)..."); log.info(metaObject.toString()); metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime", LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { log.info("公共字段自动填充(update)..."); log.info(metaObject.toString()); metaObject.setValue("updateTime", LocalDateTime.now()); } } |
现在存在一个问题,如何获取当前登录用户的id值
ThreadLocal
来解决这个问题在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
LocalCheekFilter
中的doFilter
方法EmployeeController
中的update
方法MyMetaObjectHandler
中的updateFill
方法现在我们在这三个方法中添加日志输出测试
doFilter
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //强转 HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; //1.获取本次请求的URI String requestURI = request.getRequestURI(); log.info("拦截到请求:{}", requestURI); //定义不需要处理的请求 String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**" }; //2.判断本次请求是否需要处理 boolean check = check(urls, requestURI); //3.如果不需要处理,则直接放行 if (check) { log.info("本次请求:{},不需要处理", requestURI); filterChain.doFilter(request, response); return; } //4.判断登录状态,如果已登录,则直接放行 if (request.getSession().getAttribute("employee") != null) { log.info("用户已登录,id为{}", request.getSession().getAttribute("employee")); //在这里获取一下线程id long id = Thread.currentThread().getId(); log.info("doFilter的线程id为:{}", id); filterChain.doFilter(request, response); return; } //5.如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据 log.info("用户未登录"); log.info("用户id{}", request.getSession().getAttribute("employee")); response.getWriter().write(JSON.toJSONString(Result.error("NOTLOGIN"))); } |
update
JAVA
1 2 3 4 5 6 7 8 9 |
@PutMapping public Result |
updateFill
JAVA
1 2 3 4 5 6 7 8 9 10 11 |
@Override public void updateFill(MetaObject metaObject) { log.info("公共字段自动填充(update)..."); log.info(metaObject.toString()); long id = Thread.currentThread().getId(); log.info("updateFill的线程id为:{}", id); metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("updateUser", new Long(1)); metaObject.setValue("createUser", new Long(1)); } |
com.blog.filter.LoginCheckFilter : doFilter的线程id为:34
com.blog.controller.EmployeeController : update的线程id为:34
com.blog.common.MyMetaObjectHandler : updateFill的线程id为:34
发现这三者确实是在同一个线程中
那么什么是ThreadLocal?
ThreadLocal常用方法:
public void set(T value)
设置当前线程的线程局部变量的值public T get()
返回当前线程所对应的线程局部变量的值那么我们如何用ThreadLocal来解决我们上述的问题呢?
LoginCheckFilter
的doFilter
方法中获取当前登录用户id,并调用ThreadLocal
的set
方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler
的updateFill
方法中调用ThreadLocal
的get
方法来获得当前线程所对应的线程局部变量的值(用户id)。具体实现
JAVA
1 2 3 4 5 6 7 8 9 10 11 |
public class BaseContext { private static ThreadLocal |
request.getSession
来获取当前登录用户的id值 JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//4.判断登录状态,如果已登录,则直接放行 if (request.getSession().getAttribute("employee") != null) { log.info("用户已登录,id为{}", request.getSession().getAttribute("employee")); //在这里获取一下线程id long id = Thread.currentThread().getId(); log.info("doFilter的线程id为:{}", id); //根据session来获取之前我们存的id值 Long empId = (Long) request.getSession().getAttribute("employee"); //使用BaseContext封装id BaseContext.setCurrentId(empId); filterChain.doFilter(request, response); return; } |
在MyMetaObjectHandler类中,添加设置id的代码
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@Component @Slf4j public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("公共字段填充(create)..."); metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime", LocalDateTime.now()); //设置创建人id metaObject.setValue("createUser", BaseContext.getCurrentId()); metaObject.setValue("updateUser", BaseContext.getCurrentId()); } @Override public void updateFill(MetaObject metaObject) { log.info("公共字段填充(insert)..."); metaObject.setValue("updateTime", LocalDateTime.now()); //设置更新人id metaObject.setValue("updateUser", BaseContext.getCurrentId()); } } |
重新启动服务器,并登录一个非管理员账户,然后进行添加用户操作,观察数据库中的updateUser
是否符合预期
例如我这里登录的账号是Kyle
,添加了Tony
,Tony的create_user的id是Kyle的
id | name | username | password | phone | sex | id_number | status | create_time | update_time | create_user | update_user |
---|---|---|---|---|---|---|---|---|---|---|---|
1575840690817011713 | KKK | Kyle | e10adc3949ba59abbe56e057f20f883e | 15811233075 | 1 | 111222333444555666 | 1 | 2022-10-05 17:02:53 | 2022-10-05 17:02:53 | 1 | 1 |
1577590825519423490 | 史塔克 | Tony | e10adc3949ba59abbe56e057f20f883e | 15732165478 | 1 | 111333222444666555 | 1 | 2022-10-05 17:25:38 | 2022-10-05 17:25:38 | 1575840690817011713 | 1575840690817011713 |