三、员工信息管理
3.1 添加员工
注意:在设计数据库表字段时,给userName添加了唯一索引(所以员工用户名是无法重复的)
流程:
前端页面发送POST请求,后端接收到请求和数据,将用户数据添加到数据库内,返回重成功信息 !
实现:
employController :
@ApiOperation("添加新员工")
@PostMapping
public MyResult addEmployee(HttpServletRequest request,@RequestBody Employee employee){
// 1.打印日志信息
log.info("添加新员工:"+employee.getUsername());
// 2.设置初始密码并加密
String MD5Password = DigestUtils.md5DigestAsHex("123456".getBytes());
// 3.添加一些员工创建信息
employee.setPassword(MD5Password);
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
Long emplId = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(emplId);
employee.setUpdateUser(emplId);
// 4.调用方法将数据保存到数据库捏
employService.save(employee);
// 5.返回成功信息
return MyResult.success("添加新员工成功!");
}
完善:
-
因为Employee数据库表的字段username加入了唯一索引,所以在添加了重复数据时会爆出异常,我们需要自定义处理该异常
- 方法一:直接使用try…catch捕获异常
- 方法二:定义全局异常处理器处理异常(推荐)
-
@ControllerAdvice(annotations = {RestController.class, Controller.class})
指定该异常处理器会拦截处理哪些异常 -
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
指定方法处理那些异常情况
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
//进行异常处理方法
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if(ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg=split[2]+"已存在";
return R.error(msg);
}
// 打印异常信息 指明哪个账号信息重复了
return R.error("未知错误");
}
3.2 分页查询员工信息
流程:
- 前端页面发送get请求 将分页查询参数(page.pageSize、name)提交到服务端
- controller接收调用service的page方法获取数据 将数据存储到page对象内(所以需要提前创建好page对象作为参数传入方法)
- 返回数据
employController:
/**
*
* @param page 当前椰树
* @param pageSize 一页数据数目
* @param name 搜索条件(没有的话为 “”)
* @return 返回封装好的查询page数据
*/
@ApiOperation("带搜索条件的分页查询")
@GetMapping("/page")
public MyResult<Page> page(int page, int pageSize, String name){
log.info("page={},pageSize={},name={}", page, pageSize, name);
// 1.初始化page对象
Page pageInfo = new Page(page,pageSize);
// 2.定义查询条件 模糊匹配 + 根据修改时间降序排序
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.like(!StringUtils.isEmpty(name),Employee::getName,name).orderByDesc(Employee::getUpdateTime);
// 3.调用page方法获取分页查询数据
Page page1 = employService.page(pageInfo,wrapper);
// 4.封装返回信息
return MyResult.success(page1);
}
3.3 启用/禁用 员工账号状态
需求:
-
在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。
-
需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示。
实现:
- 只有管理员能看到和操作相关启用/禁用 按钮是由前端设置的,在此就不多进行讲解
(但是这样做其实还会有一个问题:如果直接向后端发送对应恶意请求(不通过前端发送),会造成数据库信息的恶意修改,所以后端也需要进行对权限的检验——检验当前登录用户是否为admin
)
流程:
1、页面发送ajax请求,将参数(id、 status)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service更新数据
3、Service调用Mapper操作数据库
employController:
@ApiOperation("修改员工状态")
@PutMapping
public MyResult<String> update(HttpServletRequest request,@RequestBody Employee employee){
log.info(employee.toString());
Long empId = (Long) request.getSession().getAttribute("employee");
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(empId);
employService.updateById(employee);
return MyResult.success("员工信息修改成功");
}
问题发现与修复:
通过观察控制台输出的SQL发现页面传递过来的员工id的值和数据库中的id值不一致,这是怎么回事呢?
分页查询时服务端响应给页面的数据中id的值为19位数字,类型为long
页面中js处理long型数字只能精确到前16位,所以最终通过ajax请求提交给服务端的时候id就改变了
前面我们已经发现了问题的原因,即js对long型数据进行处理时丢失精度,导致提交的id和数据库中的id不一致。
如何解决这个问题?
我们可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串。
具体实现步骤:
1) 提供对象转换器JacksonobjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)
/**
* 对象映射器:基于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);
}
}
2) 在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建消息转换器
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java转换为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
super.extendMessageConverters(converters);
}
3.4 编辑员工信息
流程:
点击编辑按钮后,请求携带员工id查询员工,将员工信息回显到页面上,当我们编辑好新的信息后,点击保存,执行之前编写的update方法,将编辑修改后的信息保存到数据库内。
实现:
employController:
@ApiOperation("根据id获取员工信息")
@GetMapping("/{id}")
public MyResult<Employee> getByID(@PathVariable("id") Long id){
log.info("根据id查询员工信息!");
Employee employee = employService.getById(id);
if (employee != null)
return MyResult.success(employee);
return MyResult.error("获取失败!");
}
四、优化——公共字段自动填充
4.1 简单介绍与实现
问题分析:
公共字段自动填充:
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码
实现步骤:
1、在实体类的属性上加入@TableField注解,指定自动填充的策略
@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; // 账号更新者
自动填充策略:
DEFAULT, // 默认
INSERT, // 插入时
UPDATE, // 更新时
INSERT_UPDATE; // 插入与更新时
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口
- 就是要编写一个实现自动填充策略的处理器类(要求该类需要实现
MetaObjectHandler
接口)
// 元数据处理器类
@Slf4j
public class MyMetaHandler implements MetaObjectHandler {
// 插入数据时自动填充字段
@Override
public void insertFill(MetaObject metaObject) {
log.info("插入时自动填充字段.....");
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("createUser",new Long(1)); // 后续会修改为动态数据
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",new Long(1));
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("更新时自动填入数据");
metaObject.setValue("createUser",new Long(1));
metaObject.setValue("updateUser",new Long(1));
}
}
4.2 问题完善与优化
问题完善:
问题:
在创建者,修改者这些数据字段上的自动填入我们出现了问题,我们无法通过获取HTTPSession来获取保存的当前用户ID (因为重写的方法内没有该参数传入) 所以我们需要改变方式来动态获取到当前用户ID
于是我们使用ThreadLocal类来实现,
一个小知识:一次请求,对服务器来将都是会创建一个线程来单独处理
什么是ThreadLocal?
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
(相当于ThreadLocal为当前线程开辟的一份全局独立空间,可以存储专属的数据)
ThreadLocal常用方法:
- public void set(T value) 设置当前线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
我们可以在拦截器方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
完善问题:
- 编写一个用于保存和获取当前线程ThreadLocal变量值的工具类
MyBaseContext
// ThreadLocal工具类 用于保存获取当前登录用户Id
public class MyBaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static Long getCurrentID(){ // 获取当前用户ID
return threadLocal.get();
}
public static void setCurrentID(Long id){ // 保存当前用户ID
threadLocal.set(id);
}
}
- 修改
MyMetaHandler
类
// 元数据处理器类
@Slf4j
public class MyMetaHandler implements MetaObjectHandler {
// 插入数据时自动填充字段
@Override
public void insertFill(MetaObject metaObject) {
log.info("插入时自动填充字段.....");
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("createUser",MyBaseContext.getCurrentID());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",MyBaseContext.getCurrentID());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("更新时自动填入数据");
metaObject.setValue("createUser",MyBaseContext.getCurrentID());
metaObject.setValue("updateUser",MyBaseContext.getCurrentID());
}
}