员工管理
1.1 新增员工
1.1.1 需求分析和设计

有以下要求:
- 账号必须是唯一的
- 手机号为合法的11位手机号码
- 身份证号位合法的18位身份证号码
- 密码默认为123456,后续可自行修改
1.1.2 接口设计

1.1.3 数据库设计

1.1.4 代码开发
当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据
使用DTO时,其属性需要补充时,将其映射到原本的对象,可通过工具类实现:
java// 对象属性拷贝 BeanUtils.copyProperties(employeeDTO,employee);
1.1.5 代码完善
目前新增员工模块存在两个问题:
- 违反数据库唯一约束异常未处理
- 处理方法:捕捉特定异常,在全局异常处理器添加处理逻辑
- 新增员工的两个字段(创建人和修改人id)需要查询当前登录用户信息
处理方法:解析JWT令牌的token,获得登录用户的用户信息(因为在生成token的时候,将用户id放进去了,所以可以解析令牌,获取到cliams)

- 新问题:得到用户信息后,如何将信息传递给service?
- 解决方法:有一个线程空间的概念,ThreadLocal,是Thread的局部变量,为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问
- 客户端发起的每一次请求都对应一次单独的线程,可以将获取到的用户id存储到ThreadLocal里面,service再去拿到该信息即可
- ThreadLocal常用方法:
- public void set(T value) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程局部变量的值
- public void remove() 移除当前线程的线程局部变量的值
- 新问题:得到用户信息后,如何将信息传递给service?
1.2 员工分页查询
使用第三方插件 PageHelper,用法:
java/** * 分页查询 */ @Override public PageResult page(EmployeePageQueryDTO employeePageQueryDTO) { // 设置分页参数 PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize()); // 执行分页查询,返回 Page<Employee> 对象 Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO); return new PageResult(page.getTotal(),page.getResult()); }问题:后端返回给前端的日期数据格式不正确

时间字段为数组格式,原因是SpringBoot默认使用的JSON序列化工具Jackson处理LocalDateTime类型时,默认将其转化为了数组格式
解决方法:
在属性上加入注解,对日期进行格式化,缺点是一个一个属性配置太麻烦
java@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime;继承
WebMvcConfigurationSupport类或实现WebMvcConfigurer接口,重写extendMessageConverters方法,扩展SpringMVC的消息转换器,统一对日期类型进行格式化处理,消息转换器使用对象映射器的规则,对Java对象和JSON数据之间进行序列化和反序列化。通俗讲,就是我们自己设计一套规则,后端返回数据时会应用这套规则。代码实现:
- 消息转换器:
java@Configuration public class WebMvcConfiguration extends WebMvcConfigurationSupport { /** * 扩展消息转换器 * @param converters */ @Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { // 1. 创建一个新的消息转换器对象 MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); // 2. 为对象转换器设置一个自定义的对象映射器(Jackson 的核心) // 这里推荐使用一个自定义的 JacksonObjectMapper (见下文) converter.setObjectMapper(new JacksonObjectMapper()); // 3. 将自己的转换器容器放到最前面,让它优先执行 converters.add(0, converter); } }- 对象映射器
java/** * 对象映射器:基于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_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm"; 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(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); } }
1.3 启用禁用员工账号
1.3.1 需求分析和设计

1.4 编辑员工
需要两个接口:
- 根据ID查询用户
- 注意:对于返回前端的数据,我们保持”最小化“原则,即只传给前端需要的数据,这里我们将传给前端的数据中的用户的密码置空
- 修改用户信息