技术优化手册:从工具类到 MyBatis 配置与业务逻辑

一、工具类

  1. 三个工具类的定位与联动逻辑
工具类 核心定位 核心方法与作用
MD5Utils 加密工具(不可逆哈希加密) ①md5(str):基础加密(非敏感数据);②md5(str, key):固定密钥加密(接口签名 / 临时数据);③md5Salt(str, salt):加盐加密(用户密码);④verifyOriginalAndCiphertext:密码验证(对比加密结果)
UUIDUtils 唯一 ID 生成工具 ①UUID_32():32 位无分隔符 UUID(盐值 / 数据库主键,紧凑省空间);②UUID_36():36 位标准 UUID(第三方对接 / 日志 ID,兼容性强)
StringUtils 字符串通用处理工具 封装isEmpty()等方法(参数校验,避免重复代码)
方法 核心场景 核心价值
md5(String str) 简单加密(非敏感数据) 快速加密,无需额外参数
md5(String str, key) 固定密钥加密(接口签名、临时数据) 统一密钥,便于系统级验证
md5Salt(str, salt) 用户密码加密 每个用户唯一密文,防批量破解
UUID_32() 盐值、数据库主键、短唯一 ID 紧凑、无特殊字符,节省存储
UUID_36() 标准 UUID 场景、第三方对接 符合行业标准,兼容性强
方法 格式特点 适用场景
UUID_32() 32 位、无 - 分隔符 1. 数据库主键 / 用户 ID / 盐值(节省存储,32 位字符串比 36 位更紧凑) 2. 文件名 / 临时文件名(无特殊字符,避免解析问题)
UUID_36() 36 位、带 - 分隔符 1. 符合 UUID 标准格式的场景(比如对接要求标准 UUID 的第三方系统) 2. 日志追踪 ID(可读性稍高,能快速识别是 UUID)

联动逻辑(用户注册 / 登录核心流程)

  • 注册:StringUtils校验参数非空 → UUIDUtils.UUID_32()生成唯一盐值 → MD5Utils.md5Salt()加密密码 → 用户名 + 密码密文 + 盐值存入数据库;
  • 登录:StringUtils校验参数 → 从数据库查询用户的盐值 + 密码密文 → MD5Utils.verifyOriginalAndCiphertext()用 "旧盐值" 加密输入密码,对比密文验证。
2. 密码加密与验证的核心逻辑(解决 "UUID 随机生成" 的核心疑问)
  • 盐值(UUID)的生成时机:仅在用户注册时生成一次 ,与用户账号永久绑定并存入数据库,登录时仅读取不重新生成
  • 验证的本质:用 "注册时的旧盐值" 对登录输入的明文密码重新执行md5Salt加密,对比加密结果与数据库中的密文是否一致,一致则验证通过;
  • UUID 的作用:不是 "每次变化",而是靠 "UUID 算法(时间戳 + MAC 地址 + 随机数)" 保证全局唯一性,让每个用户的盐值唯一,即使密码相同,加密后的密文也不同(防彩虹表破解)。
3. 盐值唯一性与 Session 的核心区别(解决 "唯一性靠 Session" 的疑问)
维度 盐值(UUID) Session(会话)
生成时机 注册时(永久) 登录成功后(临时)
存储位置 数据库(与用户绑定) 服务器 / Redis(临时存储)
核心作用 保证密码加密的唯一性 / 安全性 维护用户登录状态(在线标识)
关联性 与 Session 无任何关联 仅在登录验证成功后生成
4. 工具类方法的场景适配逻辑(解决 "方法设计意义" 的疑问)
  • md5(str, key):非密码场景专用,靠 "固定密钥" 实现加密(如接口签名、临时 Token),即使 str 相同,key 不同则密文不同;
  • UUID_32()/UUID_36():适配不同场景的唯一 ID 生成,前者紧凑省空间(盐值 / 主键),后者符合标准(第三方对接 / 日志),避免业务代码重复写格式处理逻辑;
  • 工具类设计本质:"一个方法解决一类场景",覆盖项目中所有加密 / 唯一 ID 生成需求,而非仅服务于 "密码加密" 单一场景。

总结

  1. 核心安全逻辑:用户密码的安全性靠 "唯一盐值 + MD5 加盐加密" 保障,盐值是注册时绑定用户的固定值,而非登录时动态生成;
  2. 工具类协作逻辑:UUIDUtils 提供唯一盐值,MD5Utils 负责加密 / 验证,StringUtils 做参数校验,三者构成 "参数校验→生成盐值→加密存库→验证登录" 的完整链路;
  3. 方法设计逻辑 :工具类中每个方法对应特定业务场景,如md5(str, key)服务于固定密钥加密,UUID_32()服务于紧凑存储,避免 "一刀切" 的设计缺陷;
  4. 概念边界:盐值的唯一性靠 UUID 算法本身保证,与 Session(登录状态)无关联,二者是完全独立的技术概念。

二、MyBatis 核心配置解析

1.核心

理解自动生成的 Mapper XML 中关键配置的关联关系、定义位置及复用方式,避免重复开发。

2.关键知识点

  1. 配置关联与独立关系
    • 关联:Mapper 接口方法与 XML 中 <select>/<insert> 标签通过 id 强绑定(如 selectByUserName 方法与同名 XML 标签);
    • 复用:BaseResultMap(数据库字段与实体类映射规则)和 Base_Column_List(表全数字段集合)是自动生成的可复用片段,通过 resultMap<include> 标签引用;
    • 独立:不同 Mapper XML(如 UserMapper.xmlUserExtMapper.xml)是平级关系,需通过命名空间控制复用范围。
  2. 核心标签 / 属性作用
    • parameterType:声明 SQL 入参类型(如 java.lang.String、实体类全路径),直接定义在 SQL 标签上,无需额外查找;
    • resultMap:定义查询结果与实体类的映射规则,需在同 XML 中查找同名 <resultMap> 标签(如 BaseResultMap 对应 User 实体映射);
    • <include refid="Base_Column_List">:复用表全数字段,避免重复写字段名,定义在 XML 中的 <sql> 标签内。
  3. 复用与拓展方式
    • 同命名空间:拓展 XML(如 UserExtMapper.xml)若与原 Mapper 同命名空间(com.example.forum.dao.UserMapper),可直接引用 BaseResultMapBase_Column_List
    • 不同命名空间:需通过全路径引用(如 com.example.forum.dao.UserMapper.BaseResultMap);
    • Base_Column_List 灵活使用:不仅适用于全字段查询,也可作为自定义查询的字段参考(挑选用需字段),避免漏写字段。

三、日志打印规范与实践

1.核心

明确 log.info() 的传参规则,确保日志输出有意义、无报错,同时优化日志写法。

2.关键知识点

  1. 传参规则
    • 支持类型:字符串、基本数据类型、任意对象(框架自动调用 toString() 转字符串);
    • 不建议传参:null(输出无意义)、未初始化对象(抛空指针)、超大内容(日志冗余)、敏感信息(如密码)。
  2. toString() 方法的作用
    • 自动调用:日志占位符 {} 会自动调用对象的 toString(),无需手动调用(如枚举 ResultCode.FAILED_USER_EXISTS 可直接传入占位符);
    • 自定义必要性:若对象(实体类、枚举)未重写 toString(),会使用 Object 类默认实现(输出 "类名 @哈希值"),无业务价值;核心类需重写(如 User 类包含 id/username 等关键字段,枚举包含 code/msg)。
  3. 优化写法
    • 用占位符 {} 替代字符串拼接(性能更好、可读性更强),如 log.info("{} username = {}", ResultCode.FAILED_USER_EXISTS, user.getUsername())
    • 单个属性打印:直接通过 user.getUsername() 获取属性(String / 数字等类型天然有意义 toString()),无需关心实体类是否重写 toString()

四、论坛项目核心优化总结(实体类、DTO、MyBatis、异常处理)

一、背景与诉求

在开发论坛用户 / 文章模块时,面临以下问题:

  1. 实体类返回前端时包含敏感 / 冗余字段(如时间、删除状态),需精准控制返回字段;
  2. MyBatis 自动生成的 XML 易覆盖自定义 SQL,需隔离自动生成与自定义 SQL;
  3. 接口参数接收不规范(普通参数),校验 / 文档需硬编码,效率低;
  4. 接口返回错误码不精准(默认返回 1000),需全局异常处理;
  5. 非必填字段(如头像)需在 Service 层设置默认值,同时支持用户后续修改。

二、分模块核心优化方案

模块 1:实体类优化(控制返回字段 + 日志可读性)

1. 控制前端返回字段(解决 "不想返回时间 / 删除状态" 问题)
  • 核心思路:通过 Jackson 注解过滤字段,或用 DTO 解耦(推荐);
  • 具体方案
    • 临时方案(简单场景):给无需返回的字段加@JsonIgnore(如deleteState/updateTime),序列化时直接忽略;
    • 规范方案(推荐):创建专属返回 DTO(如ArticleDTO/UserDTO),只包含前端需要的字段,避免实体类耦合前端逻辑;
2. 增强日志可读性(补充 toString)
  • 核心思路 :用 Lombok 的@ToString替代手动重写,过滤敏感字段;

  • 具体方案

    // 实体类上添加,排除无需打印的字段
    @ToString(exclude = {"deleteState", "salt", "password"})
    @Getter
    @Setter
    @NoArgsConstructor
    public class User implements Serializable { ... }

模块 2:MyBatis 配置优化(解决 "自定义 SQL 被覆盖" 问题)

1. 核心痛点

自动生成的UserMapper.xml会覆盖自定义 SQL,且子包 XML 无法被扫描。

2. 解决方案(物理隔离 + 配置适配)
  • 步骤 1:隔离 XML 文件

    • 自动生成的 XML:放在resources/mapper根目录(如UserMapper.xml);
    • 自定义 SQL:放在resources/mapper/extension子包(如UserExtMapper.xml);
    • 关键:自定义 XML 的namespace必须与接口全路径一致(com.example.forum.dao.UserMapper),MyBatis 会自动合并同 namespace 的 SQL。
  • 步骤 2:修改 MyBatis 配置

    mybatis:
    # 扫描mapper根目录+所有子包的XML
    mapper-locations: classpath:mapper/**/*.xml
    # 指向实体类包(而非DAO包),修正你之前的配置错误
    type-aliases-package: com.example.forum.model
    configuration:
    map-underscore-to-camel-case: true # 开启下划线转驼峰

  • 补充@MapperScan("com.example.forum.dao")保证 DAO 接口被扫描,与 XML 扫描逻辑互补。

模块 3:接口参数优化(引入 DTO,解决 "参数校验 / 文档繁琐" 问题)

1. DTO 核心价值
  • 定义:Data Transfer Object(数据传输对象),是 Controller 层专属的 "参数容器",与接口一一对应;
  • 优势:一行注解搞定 "必填校验 + 接口文档示例",替代硬编码的StringUtils.isEmpty
2. 实操步骤(以用户注册为例)
  • 步骤 1:新建 DTO 包与类

    package com.example.forum.dto;

    @Data // 自动生成getter/setter
    @Schema(description = "用户注册请求参数")
    public class UserRegisterDTO {
    // 必填字段:加@NotBlank校验 + Swagger注解
    @NotBlank(message = "用户名不能为空")
    @Schema(description = "用户名", required = true, example = "zhangsan123")
    private String username;

    复制代码
      // 非必填字段:不加校验注解,前端没传则为null
      @Schema(description = "头像地址(可选,不传用默认)", required = false)
      private String avatarUrl;
      // 其他字段...

    }

步骤 2:改造 Controller 接口

复制代码
// 用@Valid触发校验,@RequestBody接收JSON参数
@PostMapping("/register")
public AppResult register(@Valid @RequestBody UserRegisterDTO dto) {
    // 1. 手动校验业务逻辑(如密码一致性)
    if (!dto.getPassword().equals(dto.getPasswordRepeat())) {
        return AppResult.failed(ResultCode.FAILED_TWO_PWD_NOT_SAME);
    }
    // 2. DTO转实体类,非必填字段没传则为null
    User user = new User();
    user.setUsername(dto.getUsername());
    user.setAvatarUrl(dto.getAvatarUrl());
    // 3. 调用Service(默认值在Service层处理)
    userService.createNormalUser(user);
    return AppResult.success();
}

步骤 3:Service 层处理默认值

复制代码
// Service层原有逻辑,无需修改
if (user.getAvatarUrl() == null || user.getAvatarUrl().trim().isEmpty()) {
    // 非必填字段没传,设置默认值
    user.setAvatarUrl("/static/images/default-avatar.png");
}

模块 4:全局异常处理(解决 "错误码返回不精准" 问题)

1. 核心痛点

未捕获的异常(如 MyBatis 绑定异常、参数校验异常)默认返回 1000(FAILED),无法精准定位问题。

2. 解决方案(全局异常处理器)
复制代码
package com.example.forum.exception;

import com.example.forum.common.AppResult;
import com.example.forum.common.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.BindingException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice // 全局捕获Controller层异常
@Slf4j
public class GlobalExceptionHandler {
    // 处理自定义业务异常(如用户已存在)
    @ExceptionHandler(ApplicationException.class)
    public AppResult handleApplicationException(ApplicationException errorResult) {
        return AppResult.failed(errorResult);
    }

    // 处理DTO参数校验异常
    @ExceptionHandler(BindException.class)
    public AppResult handleBindException(BindException e) {
        FieldError fieldError = e.getFieldError();
        String msg = fieldError != null ? fieldError.getDefaultMessage() : "参数校验失败";
        return AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE, msg);
    }

    // 处理MyBatis绑定异常(如SQL未找到)
    @ExceptionHandler(BindingException.class)
    public AppResult handleBindingException(BindingException e) {
        log.error("MyBatis绑定异常: {}", e.getMessage());
        return AppResult.failed(ResultCode.ERROR_SERVICES); // 返回服务器内部错误
    }

    // 兜底处理所有未捕获异常
    @ExceptionHandler(Exception.class)
    public AppResult handleException(Exception e) {
        log.error("系统异常: {}", e.getMessage(), e);
        return AppResult.failed(ResultCode.UNKNOWN);
    }
}

模块 5:业务逻辑优化(头像默认值 + 修改接口)

1. 注册时默认头像
  • 逻辑:Service 层判断avatarUrl为 null / 空字符串时,设置统一默认地址(如/static/images/default-avatar.png);
  • 进阶:将默认地址配置在application.yml中,通过@Value注入,避免硬编码。
2. 新增头像修改接口(RESTful 规范)
复制代码
@PutMapping("/avatar")
@Operation(summary = "修改用户头像", description = "传入用户名和新头像地址")
public AppResult updateAvatar(
        @RequestParam String username,
        @RequestParam String avatarUrl) {
    // 1. 校验参数
    if (StringUtils.isEmpty(username) || StringUtils.isEmpty(avatarUrl)) {
        return AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE);
    }
    // 2. 查询用户并更新
    User user = userMapper.selectByUserName(username);
    if (user == null) {
        return AppResult.failed(ResultCode.FAILED_USER_NOT_EXISTS);
    }
    user.setAvatarUrl(avatarUrl.trim());
    user.setUpdateTime(LocalDateTime.now());
    userMapper.updateByPrimaryKeySelective(user);
    return AppResult.success();
}

四、核心优化效果总结

  1. 代码规范:DTO 解耦前端参数与数据库实体,MyBatis 物理隔离自动生成 / 自定义 SQL,职责清晰;
  2. 效率提升:注解替代硬编码的参数校验 / 日志 toString / 接口文档,减少重复代码;
  3. 稳定性增强:全局异常处理器覆盖所有异常场景,错误码精准,便于定位问题;
  4. 扩展性更好:非必填字段默认值在 Service 层统一处理,新增字段只需修改 DTO,无需改动核心业务逻辑。

五、后续

  1. 所有接口统一使用 DTO 接收 / 返回参数(如登录接口创建UserLoginDTO,文章列表返回ArticleListDTO);
  2. 敏感字段(如密码、salt)在实体类中加@JsonIgnore,且toString排除,避免泄露;
  3. 默认值(如头像、性别)配置在application.yml中,通过@Value注入,便于环境切换;
  4. 接口文档(Swagger)自动生成后,测试前先核对参数必填 / 示例是否符合预期。

注意:/ 的路径 = 服务器根地址 + / 后面的内容比如:

  • /sign-up.htmlhttp://127.0.0.1:58080/ + sign-up.html = http://127.0.0.1:58080/sign-up.html
  • /user/registerhttp://127.0.0.1:58080/ + user/register = http://127.0.0.1:58080/user/register
路径写法 跳转 / 匹配的最终地址 适用场景
/sign-up.html 固定指向 http://127.0.0.1:58080/sign-up.html 不管当前在哪个页面,都跳根目录的注册页
sign-up.html 随当前页面变化: ① 当前在 http://127.0.0.1:58080/ → 跳 http://127.0.0.1:58080/sign-up.html ② 当前在 http://127.0.0.1:58080/user/ → 跳 http://127.0.0.1:58080/user/sign-up.html 仅适用于「同级目录」的跳转
相关推荐
追随者永远是胜利者3 小时前
(LeetCode-Hot100)253. 会议室 II
java·算法·leetcode·go
宇木灵4 小时前
C语言基础-十、文件操作
c语言·开发语言·学习
追随者永远是胜利者4 小时前
(LeetCode-Hot100)207. 课程表
java·算法·leetcode·go
yanghuashuiyue5 小时前
lambda+sealed+record
java·开发语言
山岚的运维笔记5 小时前
SQL Server笔记 -- 第73章:排序/对行进行排序
数据库·笔记·后端·sql·microsoft·sqlserver
盟接之桥5 小时前
盟接之桥EDI软件:API数据采集模块深度解析,打造企业数据协同新引擎
java·运维·服务器·网络·数据库·人工智能·制造
苍何6 小时前
豆包还能这么玩?附 13 大隐藏玩法,效率起飞(建议收藏)
后端
苍何6 小时前
Kimi 版 OpenClaw 来了,5000+ Skills 随便用,确实给力!
后端
HoneyMoose6 小时前
Spring Boot 2.4 部署你的第一个 Spring Boot 应用需要的环境
java
皮皮林5516 小时前
为什么 Spring 和 IDEA 都不推荐使用 @Autowired 注解??
java