新增员工

DTO作用:封装前端提交的JSON格式数据,精确匹配接口参数

Tip

当前端提交数据与实体类属性差别较大时建议使用只包含前端提交的字段,不包含额外属性

属性拷贝

  • 拷贝规则:属性名必须完全一致
  • 顺序:源对象(DTO)→目标对象(Entity)
  • 优势:简化代码,避免逐个setter
BeanUtils.copyProperties(employeeDTO, employee);

数据库操作

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
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;
 
    //@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
 
    //@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
 
    private Long createUser;
 
    private Long updateUser;
 
}
@Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user,status) "
            + "values "
            + " (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser},#{status})"
	    )

Mybatics可自动实现下划线变量与驼峰命名法变量之间的映射,在配置文件中设置:

mybatis:
  #mapper配置文件
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.sky.entity
  configuration:
    #开启驼峰命名
    map-underscore-to-camel-case: true

JWT

sky:  
  jwt:  
    # 设置jwt签名加密时使用的秘钥  
    admin-secret-key: itcast  
    # 设置jwt过期时间  
    admin-ttl: 7200000  
    # 设置前端传递过来的令牌名称  
    admin-token-name: token

Info

这段代码是一个JWT令牌校验拦截器,主要功能包括:

  1. 拦截请求,判断是否为Controller方法
  2. 从请求头获取管理员token
  3. 使用JWT工具校验token有效性
  4. 校验通过则放行,失败则返回401状态码

这是一个用于后台管理接口权限验证的拦截器。

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
 
    @Autowired
    private JwtProperties jwtProperties;
 
    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }
 
        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());
 
        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

异常处理

录入的用户名已存在,抛出异常后没有处理

sky-server/src/main/java/com/sky/handler/GlobalExceptionHandler.java

/**
     * 捕获SQL异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.error("异常信息:{}", ex.getMessage());
        String message = ex.getMessage();
        if (message.contains("Duplicate entry")) {
            String[] split = message.split(" ");
            String msg = split[2] + MessageConstant.ALREADY_EXIST_USER;
            return Result.error(msg);
        }else {
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }

用户信息的获取

参考JWT认证流程,解析得到用户id后,通过ThreadLocal存储,在后续要用到的地方再获取

// 设置创建人
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());

员工分页查询

封装分页查询结果

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
 
    private long total; //总记录数
 
    private List records; //当前页数据集合
 
}

引入pagehelper

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

Info

PageHelper是MyBatis的一个非常流行的分页插件,它极大地简化了数据库分页查询的实现。

  public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
        Page<Employee> page= employeeMapper.pageQuery(employeePageQueryDTO);
        long total = page.getTotal();
        List<Employee> records = page.getResult();
        return new PageResult(total, records);
    }
<select id="pageQuery" resultType="com.sky.entity.Employee">
	select * from employee
	<where>
		<if test="name != null and name != ''">
			and name like '%${name}%'
		</if>
	</where>
	order by create_time desc
</select>

日期格式化

{
  "code": 1,
  "msg": null,
  "data": {
    "total": 2,
    "records": [
      {
        "createTime": [
          2025,
          8,
          11,
          21,
          53,
          44
        ],
        "updateTime": [
          2025,
          8,
          11,
          21,
          53,
          44
        ],
      },
    ]
  }
}

接口响应中日期字段是一个数组,这在前端展示效果不及预期

方式一:属性注解

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;

缺点

一个个处理太过繁琐

{
  "code": 1,
  "msg": null,
  "data": {
    "total": 2,
    "records": [
      {
        "createTime": [
          2025,
          8,
          11,
          21,
          53,
          44
        ],
        "updateTime": "2025-08-11 21:53:44",
        "createUser": 1,
        "updateUser": 1
      }
    ]
  }
}

方式二:扩展Spring MVC消息转换器

sky-server/src/main/java/com/sky/config/WebMvcConfiguration.java

@Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器...");
        // 创建消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        // 设置对象转换器,底层使用Jackson将Java对象转为json
        converter.setObjectMapper(new JacksonObjectMapper());
        // 将上面的消息转换器对象追加到mvc框架的转换器集合中
        converters.add(0, converter);
    }
 
/**
 * 对象映射器:基于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);
    }
}
 

代码通过扩展Spring MVC的消息转换器来实现统一的日期格式化处理。具体实现过程如下:

  1. 自定义ObjectMapper: 在JacksonObjectMapper类中,定义了统一的日期时间格式:

    • 日期格式:yyyy-MM-dd
    • 日期时间格式:yyyy-MM-dd HH:mm
    • 时间格式:HH:mm:ss
  2. 注册序列化器和反序列化器: 在JacksonObjectMapper构造函数中,创建了一个SimpleModule,并向其中添加了针对不同时间类型的序列化器和反序列化器:

    • LocalDateTime的序列化器和反序列化器
    • LocalDate的序列化器和反序列化器
    • LocalTime的序列化器和反序列化器
  3. 扩展消息转换器: 在WebMvcConfiguration.javaextendMessageConverters方法中:

    • 创建了一个MappingJackson2HttpMessageConverter对象
    • 将自定义的JacksonObjectMapper设置为该转换器的对象映射器
    • 将这个转换器添加到Spring MVC的转换器列表的第一个位置(确保优先使用

这样,当系统需要将Java对象转换为JSON或从JSON转换为Java对象时,就会使用这个自定义的JacksonObjectMapper,从而实现全局统一的日期时间格式化处理。

优点

  • 全局统一:一次配置,全局生效
  • 集中管理:所有日期时间格式都在一个地方定义和维护
  • 自动化处理:无需在每个需要序列化/反序列化的地方单独处理日期格式

启用禁用员工账号

  • 可以对状态为“启用” 的员工账号进行“禁用” 燥作
  • 可以对状态为“禁用”的员工账号进行“启用” 操作
  • 状态为“禁用”的员工账号不能登录系统

POST /admin/employee/status/{status}?id={id}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
}
public void startOrStop(Integer status, Long id) {
	Employee employee = Employee.builder()
			.status(status)
			.id(id)
			.build();
	employeeMapper.update(employee);
}

语法可参考Builder构建者模式

编辑员工

  1. 根据id查询员工信息GET /admin/employee/{id}
 public Employee getById(Long id) {
	Employee employee = employeeMapper.getById(id);
	employee.setPassword("****");
	return employee;
}
  1. 编辑员工信息PUT /admin/employee
public void update(EmployeeDTO employeeDTO) {
	Employee employee = new Employee();
	BeanUtils.copyProperties(employeeDTO, employee);
	employee.setUpdateTime(LocalDateTime.now());
	employee.setUpdateUser(BaseContext.getCurrentId());
	employeeMapper.update(employee);
}

BeanUtils.copyProperties可参考属性拷贝

导入分类模块功能代码

业务规则:

  • 分类名称必须是唯一的
  • 分类按照类型可以分为菜品分类和套餐分类
  • 新添加的分类状态默认为“禁用”

根据id删除

逻辑如下:

  1. 检查分类是否关联了菜品,如果有则抛出异常禁止删除
  2. 检查分类是否关联了套餐,如果有则抛出异常禁止删除
  3. 如果都没有关联,则执行分类删除操作
  4. 这是典型的外键约束检查逻辑,确保数据完整性。
public void deleteById(Long id) {
	//查询当前分类是否关联了菜品,如果关联了就抛出业务异常
	Integer count = dishMapper.countByCategoryId(id);
	if(count > 0){
		//当前分类下有菜品,不能删除
		throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_DISH);
	}
 
	//查询当前分类是否关联了套餐,如果关联了就抛出业务异常
	count = setmealMapper.countByCategoryId(id);
	if(count > 0){
		//当前分类下有菜品,不能删除
		throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_SETMEAL);
	}
 
	//删除分类数据
	categoryMapper.deleteById(id);
}