苍穹外卖Day02整理

苍穹外卖Day02整理 苍穹外卖 Day02本节目标实现两大模块员工管理新增员工员工分页查询启用/禁用员工账号编辑员工菜品分类管理导入分类模块代码并测试一、新增员工1. 功能说明后台管理员可以新增员工账号。业务规则用户名必须唯一手机号必须是合法 11 位身份证号必须是合法 18 位默认密码123456员工状态默认启用2. 接口设计请求信息路径/admin/employee方式POST参数JSONDTODatapublic class EmployeeDTO implements Serializable {private Long id;private String username;private String name;private String phone;private String sex;private String idNumber;}前端传入参数和实体类不完全一致因此使用 DTO 更合适。3. 表结构employee关键字段idnameusername唯一passwordphonesexid_numberstatuscreate_timeupdate_timecreate_userupdate_user4. 分层实现ControllerPostMappingApiOperation(新增员工)public Result save(RequestBody EmployeeDTO employeeDTO){log.info(新增员工{},employeeDTO);employeeService.save(employeeDTO);return Result.success();}Service 接口void save(EmployeeDTO employeeDTO);Service 实现public void save(EmployeeDTO employeeDTO) {Employee employee new Employee();BeanUtils.copyProperties(employeeDTO, employee);employee.setStatus(StatusConstant.ENABLE);employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());employee.setCreateUser(BaseContext.getCurrentId());employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.insert(employee);}MapperInsert(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}))void insert(Employee employee);5. 测试要点接口文档测试时出现 401 的原因因为新增员工接口被 JWT 拦截器拦截但请求头中没有合法 token。解决办法先调用登录接口拿到 token再把 token 放到请求头中。6. 问题完善问题 1用户名重复时异常未处理数据库对username加了唯一约束重复插入会抛出异常。全局异常处理ExceptionHandlerpublic Result exceptionHandler(SQLIntegrityConstraintViolationException ex){String message ex.getMessage();if(message.contains(Duplicate entry)){String[] split message.split( );String username split[2];String msg username MessageConstant.ALREADY_EXISTS;return Result.error(msg);}else{return Result.error(MessageConstant.UNKNOWN_ERROR);}}常量补充public static final String ALREADY_EXISTS 已存在;问题 2创建人和修改人写死原来是employee.setCreateUser(10L);employee.setUpdateUser(10L);应该改成动态获取当前登录用户 id。7. ThreadLocal 传递当前登录用户 idBaseContextpublic class BaseContext {public static ThreadLocalLong threadLocal new ThreadLocal();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}public static void removeCurrentId() {threadLocal.remove();}}在拦截器中存入当前用户 idClaims claims JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());BaseContext.setCurrentId(empId);在 Service 中获取employee.setCreateUser(BaseContext.getCurrentId());employee.setUpdateUser(BaseContext.getCurrentId());二、员工分页查询1. 功能说明按照页码分页查询员工数据并支持按员工姓名模糊查询。业务规则每页展示 10 条支持姓名查询按创建时间倒序2. DTODatapublic class EmployeePageQueryDTO implements Serializable {private String name;private int page;private int pageSize;}3. 分页结果封装DataAllArgsConstructorNoArgsConstructorpublic class PageResult implements Serializable {private long total;private List records;}4. 分层实现ControllerGetMapping(/page)ApiOperation(员工分页查询)public ResultPageResult page(EmployeePageQueryDTO employeePageQueryDTO){log.info(员工分页查询参数为{}, employeePageQueryDTO);PageResult pageResult employeeService.pageQuery(employeePageQueryDTO);return Result.success(pageResult);}ServicePageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);Service 实现public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());PageEmployee page employeeMapper.pageQuery(employeePageQueryDTO);long total page.getTotal();ListEmployee records page.getResult();return new PageResult(total, records);}Mapper 接口PageEmployee pageQuery(EmployeePageQueryDTO employeePageQueryDTO);Mapper XMLselect idpageQuery resultTypecom.sky.entity.Employeeselect * from employeewhereif testname ! null and name ! and name like concat(%,#{name},%)/if/whereorder by create_time desc/select5. 时间格式显示问题前端显示时间格式不友好。推荐解决方案统一扩展消息转换器Overrideprotected void extendMessageConverters(ListHttpMessageConverter? converters) {log.info(扩展消息转换器...);MappingJackson2HttpMessageConverter converter new MappingJackson2HttpMessageConverter();converter.setObjectMapper(new JacksonObjectMapper());converters.add(0, converter);}时间格式定义public static final String DEFAULT_DATE_TIME_FORMAT yyyy-MM-dd HH:mm;三、启用/禁用员工账号1. 功能说明可以切换员工账号状态1启用0禁用禁用账号后不可登录。2. 接口设计路径/admin/employee/status/{status}方式POST参数路径参数status请求参数id3. 分层实现ControllerPostMapping(/status/{status})ApiOperation(启用禁用员工账号)public Result startOrStop(PathVariable Integer status, Long id){log.info(启用禁用员工账号{},{},status,id);employeeService.startOrStop(status,id);return Result.success();}Servicevoid startOrStop(Integer status, Long id);Service 实现public void startOrStop(Integer status, Long id) {Employee employee Employee.builder().status(status).id(id).build();employeeMapper.update(employee);}Mappervoid update(Employee employee);Mapper XMLupdate idupdate parameterTypeEmployeeupdate employeesetif testname ! nullname #{name},/ifif testusername ! nullusername #{username},/ifif testpassword ! nullpassword #{password},/ifif testphone ! nullphone #{phone},/ifif testsex ! nullsex #{sex},/ifif testidNumber ! nullid_Number #{idNumber},/ifif testupdateTime ! nullupdate_Time #{updateTime},/ifif testupdateUser ! nullupdate_User #{updateUser},/ifif teststatus ! nullstatus #{status},/if/setwhere id #{id}/update四、编辑员工包含两个接口根据 id 查询员工信息回显编辑员工信息1. 根据 id 查询员工ControllerGetMapping(/{id})ApiOperation(根据id查询员工信息)public ResultEmployee getById(PathVariable Long id){Employee employee employeeService.getById(id);return Result.success(employee);}ServiceEmployee getById(Long id);Service 实现public Employee getById(Long id) {Employee employee employeeMapper.getById(id);employee.setPassword(****);return employee;}MapperSelect(select * from employee where id #{id})Employee getById(Long id);2. 编辑员工信息ControllerPutMappingApiOperation(编辑员工信息)public Result update(RequestBody EmployeeDTO employeeDTO){log.info(编辑员工信息{}, employeeDTO);employeeService.update(employeeDTO);return Result.success();}Servicevoid update(EmployeeDTO employeeDTO);Service 实现public void update(EmployeeDTO employeeDTO) {Employee employee new Employee();BeanUtils.copyProperties(employeeDTO, employee);employee.setUpdateTime(LocalDateTime.now());employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.update(employee);}五、导入分类模块功能代码1. 分类模块说明分类包括两种1菜品分类2套餐分类功能点新增分类分类分页查询根据 id 删除分类修改分类启用/禁用分类根据类型查询分类业务规则分类名称唯一新增分类默认禁用若分类已关联菜品或套餐不允许删除2. 表结构category关键字段idnametypesortstatuscreate_timeupdate_timecreate_userupdate_user3. Mapper 层DishMapperMapperpublic interface DishMapper {Select(select count(id) from dish where category_id #{categoryId})Integer countByCategoryId(Long categoryId);}SetmealMapperMapperpublic interface SetmealMapper {Select(select count(id) from setmeal where category_id #{categoryId})Integer countByCategoryId(Long id);}这里方法参数名是idSQL 中是categoryId。如果未开启参数名映射建议统一写成Integer countByCategoryId(Long categoryId);或者使用Param(categoryId)。CategoryMapperMapperpublic interface CategoryMapper {Insert(insert into category(type, name, sort, status, create_time, update_time, create_user, update_user) VALUES (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}))void insert(Category category);PageCategory pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);Delete(delete from category where id #{id})void deleteById(Long id);void update(Category category);ListCategory list(Integer type);}CategoryMapper.xmlselect idpageQuery resultTypecom.sky.entity.Categoryselect * from categorywhereif testname ! null and name ! and name like concat(%,#{name},%)/ifif testtype ! nulland type #{type}/if/whereorder by sort asc , create_time desc/selectupdate idupdate parameterTypeCategoryupdate categorysetif testtype ! nulltype #{type},/ifif testname ! nullname #{name},/ifif testsort ! nullsort #{sort},/ifif teststatus ! nullstatus #{status},/ifif testupdateTime ! nullupdate_time #{updateTime},/ifif testupdateUser ! nullupdate_user #{updateUser}/if/setwhere id #{id}/updateselect idlist resultTypeCategoryselect * from categorywhere status 1if testtype ! nulland type #{type}/iforder by sort asc,create_time desc/select4. Service 层CategoryServicepublic interface CategoryService {void save(CategoryDTO categoryDTO);PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);void deleteById(Long id);void update(CategoryDTO categoryDTO);void startOrStop(Integer status, Long id);ListCategory list(Integer type);}5. Controller 层CategoryControllerRestControllerRequestMapping(/admin/category)Api(tags 分类相关接口)Slf4jpublic class CategoryController {Autowiredprivate CategoryService categoryService;PostMappingApiOperation(新增分类)public ResultString save(RequestBody CategoryDTO categoryDTO){log.info(新增分类{}, categoryDTO);categoryService.save(categoryDTO);return Result.success();}GetMapping(/page)ApiOperation(分类分页查询)public ResultPageResult page(CategoryPageQueryDTO categoryPageQueryDTO){log.info(分页查询{}, categoryPageQueryDTO);PageResult pageResult categoryService.pageQuery(categoryPageQueryDTO);return Result.success(pageResult);}DeleteMappingApiOperation(删除分类)public ResultString deleteById(Long id){log.info(删除分类{}, id);categoryService.deleteById(id);return Result.success();}PutMappingApiOperation(修改分类)public ResultString update(RequestBody CategoryDTO categoryDTO){categoryService.update(categoryDTO);return Result.success();}PostMapping(/status/{status})ApiOperation(启用禁用分类)public ResultString startOrStop(PathVariable(status) Integer status, Long id){categoryService.startOrStop(status,id);return Result.success();}GetMapping(/list)ApiOperation(根据类型查询分类)public ResultListCategory list(Integer type){ListCategory list categoryService.list(type);return Result.success(list);}}六、本节核心知识点总结1. DTO 的使用场景当前端参数和实体类字段不一致时优先使用 DTO 接收请求参数。2. 统一返回结果 Result所有接口统一返回Result.success()Result.success(data)Result.error(msg)这样前后端交互更规范。3. JWT 拦截器登录成功后生成 token。后续请求都必须携带 token拦截器统一校验。4. ThreadLocal 的作用用于在一次请求链路中共享当前登录用户 id。典型流程登录后生成 JWT拦截器解析 JWT将员工 id 存入BaseContextService 层取出当前 id用于createUser/updateUser5. PageHelper 分页插件分页步骤固定PageHelper.startPage(page, pageSize);PageT page mapper.pageQuery(dto);return new PageResult(page.getTotal(), page.getResult());6. 全局异常处理通过全局异常处理器统一处理数据库唯一约束异常提升用户体验。7. 动态 SQL在 MyBatis XML 中使用if、where、set实现条件查询和动态更新。七、面试/复习高频问题1. 为什么新增员工不用实体类接收参数因为前端传入字段与实体类字段不完全一致使用 DTO 更清晰也能降低耦合。2. 为什么创建人和修改人不能写死因为不同登录用户操作的数据审计信息不同必须根据当前登录人动态获取。3. ThreadLocal 有什么用为每个线程提供独立变量副本实现线程隔离适合在请求处理链中传递当前用户信息。4. 为什么分页查询要封装 PageResult统一分页返回结构便于后续所有分页接口复用。5. 为什么要扩展消息转换器统一处理时间格式避免每个时间字段单独加注解。八、本节最终完成内容员工管理新增员工员工分页查询启用/禁用员工查询员工详情编辑员工异常处理动态获取当前操作人分类管理导入并完成分类模块基础功能分类新增/删除/修改/分页/启禁用/按类型查询