苍穹外卖day02记录

发布时间:2026/5/18 6:24:24

苍穹外卖day02记录 苍穹外卖day02我们几天内主要完成员工管理的模块的实现1.新增员工我们先看这个接口的设计强调管理端发出的请求统一使用 /admin 作为前缀用户端发出的请求统一使用 /user 作为前缀主要是区分管理员和用户的请求接着我们要根据新增的员工设计对应的DTO因为DTO 就是专门用来“接收前端数据 / 传数据”的对象我们看到前端和实体类当中的对应的属性差别比较大如果直接传的话就会导致那些没有匹配到的字段被设置成null或者其他空的类型所以我们需要使用DTO来接收前端数据然后转换成实体类对象这样前端和后端的数据结构可以不一致。接下来我们来编写这个模块的代码controller层我们先写controller层的代码PostMappingApiOperation(新增员工)publicResultsave(RequestBodyEmployeeDTOemployeeDTO){log.info(新增员工员工数据{},employeeDTO);employeeService.save(employeeDTO);returnResult.success();}postMapping: 表示这个接口是添加员工接口它相当于加在前面这个RequestMapping(“/admin/employee”)他们合并在一起就是POST /admin/employee这个前缀。ApiOperation(“新增员工”)就是加在这个方法上面的一个注释相当于表示这个接口是添加员工接口RequestBody:把前端传来的 JSON 数据自动封装成 Java 对象我们观察到这个Result作为返回值我们来看看他的源码privateIntegercode;//编码1成功0和其它数字为失败privateStringmsg;//错误信息privateTdata;//数据我们就是知道这个Result对象就是用来封装接口返回的数据的返回的是json格式这个对象是前后端分离开发中最常用的对象。最关键的一句 employeeService.save(employeeDTO);他将controller层接收到的数据转换成实体类对象然后调用service层来处理这个数据。service层我们先写service层的代码,我们之前传递了调用了save这个方法但是这个方法没有实现所以现在我们来实现这个方法/** * 新增员工 * param employeeDTO */publicvoidsave(EmployeeDTOemployeeDTO){EmployeeemployeenewEmployee();//对象属性拷贝BeanUtils.copyProperties(employeeDTO,employee);//设置账号的状态 也就是补全DTO里面没有的但是Employee这个类里面有的属性employee.setStatus(StatusConstant.ENABLE);//设置密码默认密码为123456记得MD5加密employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));//设置创建时间、更新时间employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());//设置当前记录创建人id和修改人idemployee.setCreateUser(10L);employee.setUpdateUser(10L);//调用mapper层employeeMapper.insert(employee);}我们来简单分析一下我们刚刚传的是DTO对象但是数据库中保存的是实体类对象所以我们需要使用BeanUtils.copyProperties()方法将DTO对象中的数据复制到实体类对象中。但是我们在实体类中没有DTO对象中没有的属性所以需要补全。我们补了账号的状态默认密码为123456记得MD5加密设置创建时间、更新时间设置当前记录创建人id和修改人id调用mapper层。mapper层在最后我们有这样一个代码//调用mapper层employeeMapper.insert(employee);java insert方法需要在mapper的实现类当中创建和实现 javaInsert(insert into employee (username, name, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user) values (#{username}, #{name}, #{password}, #{phone}, #{sex}, #{idNumber}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}))voidinsert(Employeeemployee);这个Insert label表示这个方法对应数据库的insert语句我们在插入语句的时候用了类似这样的格式#{username}这其实是一个占位符表示这个字段的值这个字段的值是从employee对象中获取的。这样我们就从DTO转到Employee对象然后获取到里面的对象进行插入操作。这就是我们最终要实现的功能我们调用了后端经典的三层架构分别是controller层、service层、mapper层2.ThreadLocalThreadLocal 并不是一个 Thread而是 Thread 的局部变量ThreadLocal 为每个线程提供单独一份存储空间具有线程隔离的效果只有在线程内才能获取到对应的值线程外则不能访问。ThreadLocal 为每个线程提供单独一份存储空间具有线程隔离的效果只有在线程内才能获取到对应的值。为什么需要 ThreadLocal拦截器解析 JWT 后拿到用户 ID但 Service/Mapper 层需要用到这个 ID。不用 ThreadLocal每个方法都要传 userId 参数publicvoidsave(EmployeeDTOdto,LonguserId){// 每层都要加参数employee.setCreateUser(userId);}用 ThreadLocal只在拦截器存一次之后随时取// 拦截器BaseContext.setCurrentId(userId);// Serviceemployee.setCreateUser(BaseContext.getCurrentId());// 直接取不用传参核心谁获取谁存储用的人不需要知道怎么存的分页查询我们先来看看需求需要明确的点后面所有的分页查询统一都封装成PageResult对象返回给前端。/** * 封装分页查询结果 */DataAllArgsConstructorNoArgsConstructorpublicclassPageResultimplementsSerializable{privatelongtotal;//总记录数privateListrecords;//当前页数据集合}分页查询的代码/** * 分页查询 * param employeePageQueryDTO * return */GetMapping(/page)ApiOperation(员工分页查询)publicResultPageResultpage(EmployeePageQueryDTOemployeePageQueryDTO){log.info(员工分页查询参数为{},employeePageQueryDTO);PageResultpageResultemployeeService.pageQuery(employeePageQueryDTO);returnResult.success(pageResult);}代码是这样的PageResult这个是我要传递回来的参数AllArgsConstructor表示这个类有全参的构造方法由于调用的后端的三层架构之前已经讲过了我们主要来分析为什么这个employeeService.pageQuery(employeePageQueryDTO);在调用service层的时候能自动在动态sql里面添加limit操作。PageHelper 分页原理项目用的是 PageHelper 分页插件原理如下1. 设置分页参数publicPageResultpageQuery(EmployeePageQueryDTOdto){PageHelper.startPage(dto.getPage(),dto.getPageSize());// 存到 ThreadLocalListEmployeelistemployeeMapper.pageQuery();// 执行 SQLPageEmployeepage(PageEmployee)list;// 取出分页结果returnnewPageResult(page.getTotal(),page.getResult());}2. 底层原理拦截器 ThreadLocal// PageHelper 内部protectedstaticfinalThreadLocalPageLOCAL_PAGEnewThreadLocal();publicstaticvoidstartPage(intpageNum,intpageSize){PagepagenewPage(pageNum,pageSize);LOCAL_PAGE.set(page);// 存到当前线程}// MyBatis 拦截器插件Intercepts({Signature(typeStatementHandler.class,methodquery,args{Statement.class,ResultHandler.class})})publicclassPageInterceptorimplementsInterceptor{OverridepublicObjectintercept(Invocationinvocation){PagepageLOCAL_PAGE.get();// 取出分页参数if(page!null){// 改 SQL原 SQL limit page.getStart() , page.getPageSize()boundSqlboundSql limit page.getStart(), page.getPageSize();}returninvocation.proceed();}}3. 流程PageHelper.startPage() → ThreadLocal.set(page) → MyBatis拦截器 → 取ThreadLocal → 改SQL加limit4. PageHelper 插件配置pluginsplugininterceptorcom.github.pagehelper.PageInterceptor/plugin/plugins核心PageHelper 把分页参数存 ThreadLocalMyBatis 执行 SQL 前拦截自动拼接 limit消息转换我们还需要完善一个点就是json默认传递进去的时间是格式混乱的我们需要添加相应的注释或者转换器才可以我们目前主流的时间格式是yyyy-MM-dd HH:mm:ss转换的方式有两种1. 添加JsonFormat// JsonFormat(pattern yyyy-MM-dd HH:mm:ss)privateLocalDateTimecreateTime;// JsonFormat(pattern yyyy-MM-dd HH:mm:ss)这种方式如果设置的时间多多话每次都要添加会很麻烦所以我们需要添加一个全局的转换器也就是接下来的第二种方式2. 添加全局转换器/** * 扩展消息转换器 * param converters */OverrideprotectedvoidextendMessageConverters(ListHttpMessageConverter?converters){log.info(扩展消息转换器...);//创建一个消息转换器MappingJackson2HttpMessageConverterconverternewMappingJackson2HttpMessageConverter();//设置对象转换器底层使用Jackson将Java对象转为json(序列化)converter.setObjectMapper(newJacksonObjectMapper());//将自己的消息转换器追加到mvc框架的转换器集合中converters.add(0,converter);publicJacksonObjectMapper(){super();//收到未知属性时不报异常this.configure(FAIL_ON_UNKNOWN_PROPERTIES,false);//反序列化时属性不存在的兼容处理this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);SimpleModulesimpleModulenewSimpleModule().addDeserializer(LocalDateTime.class,newLocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addDeserializer(LocalDate.class,newLocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addDeserializer(LocalTime.class,newLocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))).addSerializer(LocalDateTime.class,newLocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addSerializer(LocalDate.class,newLocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addSerializer(LocalTime.class,newLocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));//注册功能模块 例如可以添加自定义序列化器和反序列化器this.registerModule(simpleModule);}JacksonObjectMapper 作用自定义 Java 对象 ↔ JSON 的转换规则publicJacksonObjectMapper(){// LocalDateTime 序列化Java → JSON.addSerializer(LocalDateTime.class,newLocalDateTimeSerializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)))// LocalDateTime 反序列化JSON → Java.addDeserializer(LocalDateTime.class,newLocalDateTimeDeserializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)))}流程Java对象 → JacksonObjectMapper → JSON日期格式统一为 yyyy-MM-dd HH:mm:ss总结MappingJackson2HttpMessageConverter- Spring 消息转换器负责 JSON 互转JacksonObjectMapper- 自定义转换规则统一日期格式编辑员工功能模块修改信息我们先看看这个文档的需求分析和设计编辑员工功能涉及到两个接口• 根据id查询员工信息• 编辑员工信息所以涉及到两个sql语句一个是select一个是update。根据id查询员工信息Controller层GetMapping(/{id})ApiOperation(根据id查询员工信息)publicResultEmployeegetById(PathVariableLongid){log.info(根据id查询员工信息{},id);EmployeeemployeeemployeeService.getById(id);returnResult.success(employee);}Service层OverridepublicEmployeegetById(Longid){EmployeeemployeeemployeeMapper.getById(id);employee.setPassword(****);returnemployee;}Mapper层Select(select * from employee where id#{id})Select- 标注查询语句#{id}占位符三层职责相对基础比较简单Controller- 接收请求调用 Service返回结果Service- 业务逻辑查询后隐藏密码Mapper- 操作数据库执行 SQL编辑员工信息功能编写由于是修改我们之前的mapper里面的方法已经写过了service层直接调用就行Controller层PutMappingApiOperation(编辑员工信息)publicResultupdate(RequestBodyEmployeeDTOemployeeDTO){log.info(编辑员工信息{},employeeDTO);employeeService.update(employeeDTO);returnResult.success();}Service层/** * 修改员工信息 * param employeeDTO */Overridepublicvoidupdate(EmployeeDTOemployeeDTO){EmployeeemployeenewEmployee();BeanUtils.copyProperties(employeeDTO,employee);employee.setUpdateTime(LocalDateTime.now());employee.setUpdateUser(BaseContext.getCurrentId());employeeMapper.update(employee);}

相关新闻