做外商备案的网站,wordpress 文件全文检索,网站开发毕设文献,网站管理是什么工作苍穹外卖项目复习笔记
一、 MyBatis-Plus (MP) 核心应用
这部分是开发数据持久层的核心#xff0c;重点在于理解MP如何简化开发以及如何处理复杂场景。
1. 基础 CRUD 与架构关系
BaseMapper vs ServiceImpl :
BaseMapperT: 位于DAO层。提供了最底层的数据库原子操作重点在于理解MP如何简化开发以及如何处理复杂场景。1. 基础 CRUD 与架构关系BaseMapper vs ServiceImpl :BaseMapperT: 位于DAO层。提供了最底层的数据库原子操作insert, deleteById, update, selectList等。直接操作数据库。ServiceImplM, T: 位于Service层。实现了IServiceT接口内部默认注入了BaseMapper。它在BaseMapper的基础上进行了封装提供了更高级的业务逻辑支持如批量操作、链式调用。总结: 简单SQL直接调Mapper复杂业务逻辑或批量操作调Service。2. 分页查询单表分页:配置: 必须配置MybatisPlusInterceptor并添加PaginationInnerInterceptor拦截器否则分页无效只会查全部。实现: 使用PageT对象作为参数传入BaseMapper的查询方法中。多表关联分页查询:难点: MP的Wrapper主要针对单表。解决: 手写SQLXML或注解。关键: 只要Service方法定义的第一个参数是Page对象MP拦截器会自动拦截该SQL执行SELECT count(0)查询总数然后注入LIMIT语句无需手动写分页逻辑。具体实现自定义 SQL (XML 方式) —— 推荐核心思路编写一个 SQL使用LEFT JOIN连接菜品表和分类表直接查出你需要的所有字段让 MP 帮你做分页。定义 DishVO, 确保你的 DishVO 里有 categoryName 字段。DatapublicclassDishVOextendsDish{// 继承了 Dish 的所有属性// 额外添加分类名称privateStringcategoryName;// 可能还需要口味列表根据你的业务需求决定是否在这里查或者分开查// private ListDishFlavor flavors;}修改 Mapper 接口在DishMapper.java中定义一个方法。 注意入参必须包含 IPageMP 会自动识别它并进行分页拦截。返回值也是 IPage。MapperpublicinterfaceDishMapperextendsBaseMapperDish{/** * 自定义分页查询 DishVO * param page MP 分页对象必须作为第一个参数 * param dishPageQueryDTO 查询条件 * return 分页结果 */PageDishVOpageQuery(IPageDishVOpage,Param(dto)DishPageQueryDTOdishPageQueryDTO);}编写 Mapper XML在DishMapper.xml中编写 SQL。mapper namespacecom.sky.mapper.DishMapper select idpageQuery resultTypecom.sky.vo.DishVO SELECT d.*, c.name AS category_name FROM dish d LEFT JOIN category c ON d.category_id c.id where if testdto.name ! null and dto.name ! AND d.name LIKE CONCAT(%, #{dto.name}, %) /if if testdto.categoryId ! null AND d.category_id #{dto.categoryId} /if if testdto.status ! null AND d.status #{dto.status} /if /where ORDER BY d.create_time DESC /select /mapperService 层调用直接调用 Mapper 即可非常简洁。Override public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) { // 1. 准备分页对象 PageDishVO page new Page(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize()); // 2. 调用 Mapper 自定义方法 // MP 会自动执行两条 SQL // 第一条SELECT COUNT(*) ... LEFT JOIN ... // 第二条SELECT d.*, c.name ... LEFT JOIN ... LIMIT ?, ? PageDishVO voPage dishMapper.pageQuery(page, dishPageQueryDTO); // 3. 封装结果 return new PageResult(voPage.getTotal(), voPage.getRecords()); }3. 批量操作与链式调用 (Point 6)批量操作: 使用IService接口提供的方法如saveBatch(ListT)或updateBatchById(ListT)。底层是基于JDBC的batch操作性能优于循环调用Mapper。链式调用 (LambdaQueryChainWrapper):场景: 避免创建繁琐的Wrapper对象。写法:lambdaQuery().eq(User::getId, id).one();优点: 代码更优雅可读性更强。4. 公共字段填充 (Point 4)MP 原生方式 (MetaObjectHandler):原理: 实现MetaObjectHandler接口重写insertFill和updateFill方法。配置: 在实体类字段上添加TableField(fill FieldFill.INSERT)等注解。场景: 适用于纯数据库层面的统一赋值如create_time, update_time。为什么推荐 MetaObjectHandler更底层 它直接介入 MyBatis-Plus 的 CRUD 操作是专门为数据填充设计的。更简单 避免了复杂的 AOP 反射和切入点配置。更清晰 逻辑集中在一个处理器类中明确区分了插入和更新两种场景。实现步骤步骤 1创建元对象处理器创建一个类继承自com.baomidou.mybatisplus.core.handlers.MetaObjectHandler并实现insertFill和updateFill方法。importcom.baomidou.mybatisplus.core.handlers.MetaObjectHandler;importorg.apache.ibatis.reflection.MetaObject;importorg.springframework.stereotype.Component;importjava.time.LocalDateTime;ComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{// 假设您有一个工具类来获取当前用户IDprivateLonggetCurrentUserId(){// 实际应用中从 ThreadLocal 或 SecurityContext 获取当前登录用户IDreturn1L;// 示例值}/** * 插入操作时自动填充 */OverridepublicvoidinsertFill(MetaObjectmetaObject){// 1. 填充创建时间if(metaObject.hasSetter(createTime)){this.strictInsertFill(metaObject,createTime,LocalDateTime.class,LocalDateTime.now());}// 2. 填充创建人if(metaObject.hasSetter(createUser)){this.strictInsertFill(metaObject,createUser,Long.class,getCurrentUserId());}// 3. 插入时通常也填充更新时间和更新人if(metaObject.hasSetter(updateTime)){this.strictInsertFill(metaObject,updateTime,LocalDateTime.class,LocalDateTime.now());}if(metaObject.hasSetter(updateUser)){this.strictInsertFill(metaObject,updateUser,Long.class,getCurrentUserId());}}/** * 更新操作时自动填充 */OverridepublicvoidupdateFill(MetaObjectmetaObject){// 1. 填充更新时间if(metaObject.hasSetter(updateTime)){this.strictUpdateFill(metaObject,updateTime,LocalDateTime.class,LocalDateTime.now());}// 2. 填充更新人if(metaObject.hasSetter(updateUser)){this.strictUpdateFill(metaObject,updateUser,Long.class,getCurrentUserId());}}}步骤2在实体类上添加注解在您的实体类如Employee中只需在需要自动填充的字段上添加TableField注解指定填充策略。importcom.baomidou.mybatisplus.annotation.FieldFill;importcom.baomidou.mybatisplus.annotation.TableField;importjava.time.LocalDateTime;publicclassEmployee{// ... 其他字段TableField(fillFieldFill.INSERT)privateLocalDateTimecreateTime;TableField(fillFieldFill.INSERT)privateLongcreateUser;// 插入和更新时都需要填充TableField(fillFieldFill.INSERT_UPDATE)privateLocalDateTimeupdateTime;TableField(fillFieldFill.INSERT_UPDATE)privateLongupdateUser;}AOP 方式:原理: 自定义注解 Aspect切面。在切面中通过反射给参数对象的属性赋值。场景: 适用于需要获取上下文信息如从ThreadLocal获取当前登录用户的ID并填充到实体的场景。苍穹外卖中常结合两者使用。苍穹外卖中AOP机制的分析切面配置使用了前置通知和一个非常精确的 切入点表达式。A. 切面类AutoFillAspect在项目中AutoFillAspect 是一个使用 Aspect 注解的 Spring Bean。它很可能使用了 前置通知 (Before) 来实现公共字段填充。执行逻辑 在 Mapper 方法执行之前通过反射获取方法参数通常是实体对象然后根据当前操作类型INSERT 或 UPDATE和当前登录的用户信息为实体对象的 createTime、createUser、updateTime、updateUser 字段赋值。B. 切入点表达式解析切入点表达式是com.sky.mapper.*.*(..) annotation(com.sky.annotation.autoFill)\text{com.sky.mapper.*.*(..) \\ annotation(com.sky.annotation.autoFill)}com.sky.mapper.*.*(..) annotation(com.sky.annotation.autoFill)方法匹配部分com.sky.mapper.*.*(..)com.sky.mapper.*: 匹配 com.sky.mapper 包下的所有类 (通常是 Mapper 接口)。.*(…): 匹配这些类中的所有方法 (方法名和参数数量不限)。效果 限制了切面只在 mapper 包下的方法上查找连接点。注解匹配部分annotation(com.sky.annotation.autoFill)annotation(...): 匹配所有被括号内指定的注解标注的方法。效果 只有当 com.sky.mapper 包下的某个方法同时被AutoFill注解标记时切面才会被织入。总结 只有当您在 Mapper 接口中定义的方法如 insert 或 update 方法上同时使用了AutoFill注解时AutoFillAspect 的逻辑才会在该方法执行时被触发。二、 架构设计与难点解决这部分涉及企业级开发的规范和常见“坑”的解决方案。1. 雪花算法精度丢失问题现象: 后端ID为Long类型19位前端JS的Number类型最大安全整数是253−12^{53}-1253−1约16位。传递到前端时后几位会被四舍五入导致ID不一致。解决方案:后端将Long转为String传输。实现方式:局部处理: 在实体类ID字段上加注解JsonSerialize(using ToStringSerializer.class)。全局处理: 配置Jackson的消息转换器 (MappingJackson2HttpMessageConverter)将所有Long类型序列化为String。2. 企业级文件上传设计技术栈: 阿里云 OSS (Object Storage Service)。设计模式:XXXProperties: 使用ConfigurationProperties读取application.yml中的配置endpoint, key, secret, bucket实现配置与代码分离。XXXConfiguration: 使用Configuration和Bean利用Properties创建OSS客户端对象交给Spring容器管理。接口设计: 定义标准的FileStorageService接口包含upload方法。多实现类: 如果未来切换到腾讯云COS或MinIO只需新增实现类并修改配置无需修改业务代码符合开闭原则。三、 中间件集成 (Redis)StringRedisTemplate vs RedisTemplate特性RedisTemplateStringRedisTemplate序列化方式默认使用JdkSerializationRedisSerializer默认使用StringRedisSerializer存储格式二进制流乱码状不可读纯字符串清晰可读占用空间较大包含类信息较小适用场景存储复杂的Java对象且不关心Redis中数据的可读性存储简单的KV结构或者需要与其他语言交互或者需要人工排查Redis数据推荐一般不推荐直接用默认的。通常会自定义配置JSON序列化器。推荐使用。手动将对象转为JSON字符串存入取出来再转回对象。