第一阶段:基本功能实现

24 阅读12分钟

一、基本功能实现与完善

A. 用户注册与登录模块

模块功能: 作为系统统一的认证入口,负责承接用户注册与登录请求:在注册场景中,接收用户凭据并调用应用服务完成账号创建,同时对重复账号、重复用户名等典型约束冲突进行业务化提示;在登录场景中,控制器完成登录校验流程编排,认证成功后返回包含 JWT 信息的登录结果对象,认证失败或账号禁用时返回明确、可读的失败原因。模块通过统一的 Result<T> 响应协议对外提供标准化认证能力。

完成状态: 基本实现功能,postman测试通过。

a. controller层:明确边界,强化校验

初始状态: Controller 层承担了过多的业务逻辑,包括生成 JWT 令牌、硬编码捕获数据库异常等,且直接使用实体类接收缺乏校验的参数 。

修改动作:

  1. 解耦实体,引入 DTO: 新增了 RegisterRequestLoginRequest DTO 用于接收前端参数,彻底与数据库实体 UserEntity 解耦 。
  2. 强化参数校验: 在接口入参处增加 @Valid 注解,并在 DTO 字段上配合使用 @NotBlank@Size,避免空指针和非法请求风险 。
  3. 业务逻辑下沉: 将注册逻辑、登录验证以及 JWT Token 的生成逻辑全部剥离 Controller,下沉至 Service 层统一处理(userService.register()userService.login()),使 Controller 层回归只负责请求转发和响应的本职工作 。
  4. 规范代码细节: 将参数名由首字母大写修正为小驼峰命名(registerRequest),并将日志字符串拼接统一修改为更规范的 {} 占位符 。

主要存在问题与代码还原:

  1. 参数命名不规范:参数名首字母大写,与类名混淆,应采用小驼峰命名。
// 修改前
public Result register(@RequestBody RegisterRequest RegisterRequest) { ... }

// 修改后
public Result register(@RequestBody RegisterRequest registerRequest) { ... }
  1. 异常信息硬编码表名:直接判断数据库表的约束名,耦合度高且易出错。
// 修改前
if (e.getMessage().contains("table_name_pk_2")) { 
    return Result.error("用户名已存在"); 
}

(修改后:移交 Service 层统一处理,判断通用的 Duplicate entry 关键字)

  1. 逻辑混杂 & 4. JWT 生成逻辑不应在 Controller 层 & 5. 缺少参数校验 & 6. 接口层实体使用不规范
// 修改前:Controller 层直接用实体接参、缺乏校验、包含过多业务及 Token 生成逻辑
@PostMapping("/login")
public Result login(@RequestBody UserEntity user) {
    if(user.getUsername() == null || user.getPassword() == null) { ... } // 手动校验
    boolean isLogin = userService.checkLogin(user.getUsername(), user.getPassword());
    if (isLogin) {
        String token = jwtUtils.generateToken(user); // JWT 在 Controller 生成
        return Result.success(token);
    }
    // ...
}

// 修改后:引入 DTO、@Valid 校验,业务全部下沉
@PostMapping("/login")
public Result<String> login(@Valid @RequestBody LoginRequest loginRequest) {
    String token = userService.login(loginRequest);
    return Result.success(token);
}

修改内容总结:

  1. 新增 RegisterRequest / LoginRequest DTO,与 UserEntity 解耦
  2. 入参加 @Valid,DTO 字段加 @NotBlank / @Size
  3. 注册/登录逻辑全部下沉至 userService.register() / userService.login()
  4. DTO 参数名改为小驼峰 registerRequest / loginRequest
  5. 日志改为 {} 占位符

b. Service层:高内聚处理,完善语义

初始状态: Service 层的登录逻辑存在语义模糊的情况,日志拼接不规范,且暴露了不必要的内部方法。

修改动作:

  1. 重构登录逻辑,解决语义模糊:checkLogin 返回 boolean,导致调用方无法区分“账号不存在”还是“密码错误” 。重构后将其与 checkUserStatus(原单独暴露的公开方法)合并至 login() 方法中高内聚处理,直接返回带有清晰错误提示的 Result 对象,并将状态校验方法改为内部调用,不再对外暴露 。
  2. 封装异常处理: 针对注册时的账号冲突问题,抽取了私有方法 resolveRegisterException 内聚在 Service 中,并将原先硬编码判断数据库具体约束名的高耦合逻辑,优化为判断通用的 Duplicate entry 关键字 。
  3. 处理内部转换与规范: 在 Service 内部完成 DTO 到 Entity 的转换,并将命名不规范的变量(MD5PasswordUserEntity)修正为小驼峰,日志记录全部替换为占位符格式 。

具体的问题与代码还原:

  1. 参数/变量命名不规范 & 5. 变量命名不规范
// 修改前
String MD5Password = md5(password);
UserEntity UserEntity = new UserEntity();

// 修改后
String md5Password = md5(password);
UserEntity userEntity = new UserEntity();
  1. 日志拼接方式不规范
// 修改前
log.info("用户登录:" + username);

// 修改后
log.info("用户登录:{}", username);
  1. checkLogin 返回值语义模糊 & 6. checkUserStatus 可合并至 login()
// 修改前:boolean 类型无法区分具体错误,且状态校验对外暴露
public boolean checkLogin(String username, String password) { ... }
public boolean checkUserStatus(String username) { ... }

// 修改后:合并至 login() 方法内部处理,返回具体结果/Token,不再对外暴露
public String login(LoginRequest loginRequest) {
    // 1. 调用内部的 checkUserStatus(loginRequest.getUsername())
    // 2. 校验密码,失败抛出自定义异常
    // 3. 生成并返回 Token
}
private void checkUserStatus(String username) { ... }

结合之前 controller 存在的问题,修改内容总结:

  1. 参数名首字母大写:UserEntityuserEntityMD5Passwordmd5Password
  2. 日志字符串拼接:全部改为 {} 占位符
  3. 直接接收 Entity:改为接收 RegisterRequest / LoginRequest,内部做 DTO → Entity 转换
  4. 缺少 register() / login() 方法:新增并实现,对应 Controller 调用
  5. checkLogin 语义模糊:合并进 login() 方法,返回 Result(或 Token)
  6. checkUserStatus 对外暴露:改为 login() 内部调用,不再对外暴露
  7. resolveRegisterException 缺失:实现为 private 方法,内聚在 Service 中
  8. 异常判断硬编码表约束名:改为判断通用的 Duplicate entry 关键字

c. DAO层:精简冗余,准确命名

初始状态: 存在冗余的修饰符、与父类重复的方法定义以及语义不清的命名 。

完善动作:

  1. 清理冗余代码: 移除了接口方法中默认自带但显式写出显得冗余的 public 修饰符 。已经继承了 BaseMapper,直接删除了与其重复的 selectById 和废弃的 deleteAccount 方法,保持 API 与 Service 调用层的一致性 。
  2. 方法合并与重命名: 将功能相似且命名无法体现区别的 updateUserupdateUser2 方法,合并为 updateUserSelective,并将动态 SQL 逻辑移至 XML 中统一管理 。针对原先返回 boolean 但使用了 get 前缀的方法,重命名为 existsByAccountAndPassword
  3. 命名规范统一: 将所有参数名修正为小驼峰形式(userEntity)。

具体的问题与代码还原:

  1. 参数命名不规范 & 2. 方法多余 public 修饰符
// 修改前
public void insertUser(UserEntity UserEntity);

// 修改后
void insertUser(UserEntity userEntity);
  1. deleteById 与父类 BaseMapper 重复 & 4. deleteAccount 与调用不一致
// 修改前
public interface UserMapper extends BaseMapper<UserEntity> {
    UserEntity selectById(Long id); // 与 BaseMapper 重复
    void deleteAccount(Long id);    // Service 中使用的是 deleteById
}

// 修改后:直接删除上述冗余和废弃方法
  1. updateUser 与 updateUser2 方法语义重复 & 6. 命名不符合规范
// 修改前
void updateUser(UserEntity user);
void updateUser2(UserEntity user);
Boolean getUserByAccountAndPassword(String account, String password); // get 前缀通常用于查询对象

// 修改后
void updateUserSelective(UserEntity userEntity); // 合并并改名,使用动态 SQL
Boolean existsByAccountAndPassword(String account, String password); // 语义更准确

修改内容总结:

  1. 参数名首字母大写 UserEntity:全部改为小驼峰 userEntity
  2. 方法冗余 public 修饰符:全部移除,接口方法默认 public
  3. selectById 与 BaseMapper 重复:直接删除,使用 BaseMapper 提供的方法
  4. deleteAccount 与 Service 调用不一致:删除废弃方法,deleteById 使用 BaseMapper 提供的即可
  5. updateUserupdateUser2 语义重复:合并为 updateUserSelective,动态 SQL 写在 XML 中
  6. getUserByAccountAndPassword 命名语义不清:改为 existsByAccountAndPassword,语义更准确

B. 设备管理模块

模块功能: 面向前端提供设备信息全量查询、条件分页检索、批量删除、新增与更新,以及本地/云端图片上传等核心能力; Controller 负责请求接入、参数承载与统一响应封装,Service 负责分页参数处理、业务规则执行与流程编排,Mapper负责动态 SQL 组装及设备数据持久化访问,整体能够支撑设备台账维护、状态筛选检索与设备资料管理等典型后台运维场景。

完成状态: 基本实现功能,postman测试通过。

a. controller层:明确边界,强化校验

初始状态: Controller 层承担了过多的业务判断、侵入了属于核心业务范畴的事务控制逻辑,存在硬编码配置,且违背了防腐层原则,直接使用底层 Entity 接收前端参数。

修改动作:

  1. 剥离事务控制:@Transactional 注解从 Controller 层彻底移除,下沉至 Service 层,防止网络 I/O 异常导致事务被意外拉长。
  2. 解耦实体,引入 DTO: 废弃在接口中直接暴露 DeviceEntity 的做法,引入业务专属的 DTO 对象,防止底层数据结构被恶意穿透篡改。
  3. 强化参数校验与默认值处理: 移除条件查询接口中冗长的 if 空值判断,改为在 DTO 中配置初始值,并结合 @Validated 进行声明式校验。
  4. 消除硬编码: 抽离本地文件上传路径的物理绝对路径,改用配置文件动态注入。
  5. 规范RESTful语义与代码细节: 修正非标准的不规则 URI,日志输出采用 {} 占位符替代 + 拼接,采用构造器注入替代字段注入。

主要存在问题与代码还原:

  1. 事务控制越界
// 修改前
@Transactional
@DeleteMapping("/devices")
public Result deleteDevices(@RequestBody List<Integer> ids) { ... }

// 修改后:去掉 @Transactional,移交给 Service 层
@DeleteMapping("/devices")
public Result deleteDevices(@RequestBody List<Integer> ids) { ... }
  1. 业务判断逻辑滞留
// 修改前:内部堆砌大量 if 语句
public Result getDevicesByPagehelperConditional(...) {
    if (page == null) page = 1;
    if (pageSize == null) pageSize = 10;
    // ...
}
  1. 领域实体穿透
// 修改前:直接暴露 DeviceEntity
public Result addDevice(@RequestBody DeviceEntity device) { ... }

// 修改后:引入业务专属 DTO
public Result addDevice(@RequestBody DeviceAddRequest request) { ... }
  1. 存储路径硬编码
// 修改前:系统完全丧失可移植性
File file = new File("D:\\project\\images\\" + fileName);

// 修改后:使用配置文件注入
@Value("${file.upload.path}")
private String uploadPath;
File file = new File(uploadPath + fileName);
  1. RESTful 语义违规
// 修改前:包含动词且大驼峰
@PostMapping("/PageConditional")
@PostMapping("/uploadImage")

// 修改后:基于资源表述
@PostMapping("/query")
@PostMapping("/images/local")
  1. 依赖注入规范落后
// 修改前
@Autowired
private DeviceService deviceService;

// 修改后
@RequiredArgsConstructor
@RestController
public class DeviceController {
    private final DeviceService deviceService;
}

修改内容总结:

  1. 移除 Controller 层的 @Transactional 注解。
  2. 新增 DeviceAddRequest / DeviceUpdateRequest 替换底层实体 DeviceEntity 作为入参,Service 内部实现转换。
  3. 在分页条件查询 DeviceQueryDTO 内部设定时间与分页的默认值,Controller 入参添加 @Validated 替代手工 if 校验流。
  4. 将图片存储路径写入 application.yml,使用 @Value 注解动态读取。
  5. 重构接口 URI 为标准 RESTful 风格,分页查询改为 POST /devices/query,本地上传改为 POST /devices/images/local
  6. 将日志全部改为 {} 占位符格式;引入 @RequiredArgsConstructor 注解实现依赖的构造器注入。

b. Service与Mapper层:高内聚处理,消除冗余

(注:此处主要针对 Service 层逻辑)

初始状态: Service 层存在与底层 AOP 拦截器功能重复的赋值逻辑,且接管 Controller 层移交的事务控制;部分核心方法的依赖管理与日志输出缺乏工程级规范。

修改动作:

  1. 完善事务管理: 承接从 Controller 层剥离的事务控制逻辑,确保批量操作、修改或删除时的数据一致性。
  2. 消除冗余自动填充逻辑: Mapper 层已定义了 @AutoFill / MetaObjectHandler 机制用于自动注入时间,Service 层中手动 set 赋值的代码属于重复建设,予以剔除。
  3. 规范化日志格式: 全面排查并替换 Service 层中的低效字符串拼接日志。

主要存在问题与代码还原:

  1. 写操作事务控制缺失
// 修改前
public void addDevice(DeviceAddRequest request) { ... }

// 修改后
@Transactional(rollbackFor = Exception.class)
public void addDevice(DeviceAddRequest request) { ... }
  1. 自动填充逻辑冲突与冗余
// 修改前:冗余的手动设值代码
dev.setCreateTime(LocalDateTime.now());
dev.setUpdateTime(LocalDateTime.now());
deviceMapper.insertDevice(dev);

// 修改后:彻底删除,强制统一信任底层的拦截器/MetaObjectHandler代理执行
deviceMapper.insert(dev);
  1. 顶层异常直接向外抛出: 在涉及文件流处理的方法中,直接声明 throws Exception。修改后补全了全局异常拦截器(GlobalExceptionHandler),统一接管并封装为 Result.error()

修改内容总结:

  1. DeviceServiceImpl 的核心写操作方法上补充 @Transactional(rollbackFor = Exception.class)
  2. 彻底删除 Service 中关于 createTimeupdateTime 的手动赋值逻辑改为强制统一信任底层的自动填充。
  3. 补全全局异常拦截器(GlobalExceptionHandler),对 Exception 和特化的 IOException 进行统一接管。
  4. 将 Service 内部所有涉及 + 拼接的日志信息,统一修正为 log.info("...信息为:{}", dev)

c. DAO (Mapper)层:依托框架能力,精简冗余设计

初始状态: Mapper 层未能充分利用底层持久化框架的内置能力,存在大量与基础 CRUD 重叠的冗余方法定义;分页方案碎片化;自定义的审计字段填充机制设计存在局限性;且部分 SQL 语句存在严重的硬编码现象。

修改动作:

  1. 清理冗余代码: 全面删除与 BaseMapper 内置方法重叠的自定义方法。
  2. 重构自动填充机制: 废弃侵入 Mapper 方法的自定义 @AutoFill AOP 切面,改用 MyBatis-Plus 官方标准的 MetaObjectHandler 实现全局公共字段的自动填充。
  3. 统一分页技术栈: 移除手工计算 offset 的底层分页代码,统一收拢为标准的分页插件实现。
  4. 消除 SQL 硬编码: 将写死的查询条件参数化。
  5. 规范命名约定: 对齐 MyBatis 体系的接口命名规范,统一使用 selectinsertupdatedelete 前缀。

主要存在问题与代码还原:

  1. 严重重复造轮子
// 修改前
public interface DeviceMapper extends BaseMapper<DeviceEntity> {
    List<DeviceEntity> getAllDevices();
    void deleteDevices(List<Integer> ids);
}
// 修改后:直接删除自定义方法,使用 BaseMapper 的 selectList(null) 和 deleteBatchIds()
  1. 自动填充机制设计不合理: 使用自定义 @AutoFill 切面导致无法使用 BaseMapper 的原生方法。修改后采用 MyBatis-Plus 原生的 MetaObjectHandler

  2. 分页实现方案碎片化与混乱: 存在 getAllDevices()(强转Page,内存溢出隐患)、getDevicesByPage(int offset, int size)(手工计算物理分页)、pageQuery(DeviceQueryDTO)(PageHelper)。修改后统一收拢为标准的 PageHelper 或 MyBatis-Plus 分页插件实现。

  3. 测试步骤不完善遗留的硬编码查询

// 修改前
@Select("SELECT ... FROM vibration_data WHERE id = 1")
List<Data> selectShowAccData();

// 修改后:参数化查询
@Select("SELECT ... FROM vibration_data WHERE id = #{id}")
List<Data> selectShowAccData(@Param("id") Long id);
  1. 命名规范违背数据访问层契约: 将 getDevices 统一重命名为 selectDevices,保持与 MyBatis 体系的一致性。