java/PROJECT_REFERENCE.md
liRQ 49cce2dbb5 fix: 统一 wp_drive_movie 表映射,移除废弃的 wp_movie 表引用
- MovieDO/MovieMapper 改为映射 wp_drive_movie 表

- 移除 wp_movie 建表逻辑,由 drive-service 统一管理

- 修复 TMDB 刮削查询条件,放开 scrape_type=none 限制

- 修复 api-doc.html 中 9 处与实际代码不一致

- 统一所有服务 Jasypt 加密算法

- 移除默认加密密钥,生产环境需通过环境变量设置

- 新增 PROJECT_REFERENCE.md 项目规范文件

- 新增 IconCleanupScheduler 定时清理图片

- 调整日志级别为 INFO,移除 MyBatis-Plus SQL 日志
2026-06-18 19:02:43 +08:00

18 KiB
Raw Blame History

网盘系统 (WangPan) - 项目全局参考文件

用途:本文档记录项目规范、命名约定、常见问题与解决方案,供 TraeAI 在每次调整代码前阅读参考。

规则每次修改项目代码前TraeAI 必须读取本文件以确保遵循项目规范。


一、项目概览

项目 说明
名称 WangPan网盘系统
架构 微服务Spring Boot 3.2.5 + Spring Cloud Gateway
语言 Java 17
构建工具 Maven
包名根路径 com.wangpan
数据库 MySQL 8.0.33
对象存储 MinIO
ORM MyBatis-Plus 3.5.7
配置加密 JasyptJasypt Spring Boot Starter

模块结构与端口

模块 端口 说明
wangpan-common - 公共模块DO/Mapper/Repository/Entity/Util/Interceptor
wangpan-user-service 8081 用户认证服务登录、注册、Token管理
wangpan-drive-service 8082 网盘管理服务(文件管理、电影管理)
wangpan-tmdb-service 8083 TMDB 刮削服务(电影元数据抓取)
wangpan-gateway 8888 API 网关(统一路由入口)

二、命名规范

2.1 包命名

com.wangpan                         # 根包
├── common                          # 公共模块
│   ├── annotation                  # 自定义注解
│   ├── config                      # 配置类
│   ├── dal                         # 数据访问层
│   │   ├── dataobject              # 数据对象DO
│   │   ├── mapper                  # MyBatis Mapper 接口
│   │   └── repository              # Repository 层(封装数据访问)
│   ├── entity                      # 业务实体
│   ├── interceptor                 # 拦截器
│   ├── listener                    # 监听器 / 定时任务
│   └── util                        # 工具类
├── controller                      # 控制器(各子模块各自持有)
├── service                         # 业务服务层(各子模块各自持有)
└── Application.java                # 启动类(各子模块各自持有)

规则

  • 各子服务的 Controller 和 Service 放在各自模块的 src/main/java/com/wangpan/
  • 公共的 DO、Mapper、Repository、Entity、Util 放在 wangpan-common
  • 所有 Mapper XML 文件统一放在 wangpan-common/src/main/resources/com/wangpan/common/dal/mapper/

2.2 类命名

类型 命名规则 示例
DO数据对象 {实体名}DO DriveMovieDO, UserDO, MovieDO
Mapper 接口 {实体名}Mapper DriveMovieMapper, UserMapper
Repository {实体名}Repository DriveMovieRepository, UserRepository
Entity业务实体 {实体名} DriveMovie, User, Movie
Controller {模块名}Controller DriveController, AuthController, TmdbController
Service {模块名}Service DriveService, UserService, TmdbService
工具类 {功能}Util TokenUtil, MinioUtil, AuthUtil, EncryptUtil
配置类 {功能}Config WebMvcConfig, MyBatisPlusConfig
启动类 {模块名}Application UserServiceApplication, DriveServiceApplication
拦截器 {功能}Interceptor AuthInterceptor
定时任务 {功能}Scheduler TmdbScheduler, IconCleanupScheduler

2.3 方法命名

操作 命名规则 示例
查询单个 findBy{条件} findById, findByMovieId
查询列表 queryBy{条件} queryByUserId, queryByDriveAndFolder
统计数量 countBy{条件} countByDrive, countByUser
插入 insert insert(DriveMovie movie)
更新 update / update{字段} update, updateScrapeInfo
逻辑删除 logicalDelete logicalDelete(Long id, Long userId)
物理删除 delete delete(Long id, Long userId)
移动 moveTo{目标} moveToFolder

2.4 数据库命名

元素 命名规则 示例
表名 wp_{模块}_{实体} wp_drive_movie, wp_user, wp_tmdb_cache
字段名 小写 + 下划线分隔 drive_id, user_id, create_time, movie_title
主键 idBIGINT AUTO_INCREMENT id
索引 idx_{字段名} idx_drive_id, idx_user_id, idx_status
状态字段 字符串枚举值 'active', 'deleted', 'expired'

2.5 API 命名

模块 路径前缀 示例
用户服务 /api/login, /api/register, /api/token/**, /api/users/**, /api/login-logs/** POST /api/login
网盘服务 /api/drive/**, /api/icon/** GET /api/drive/movies/duplicates
TMDB服务 /api/movies/**, /api/tmdb/**, /api/tmdb-scraper/** GET /api/movies/{id}

三、技术架构模式

3.1 分层架构

Controller  →  接收请求、参数校验、调用 Service、返回 Result
    ↓
Service     →  业务逻辑处理、事务管理
    ↓
Repository  →  数据访问封装、Entity ↔ DO 转换、MyBatis-Plus LambdaQueryWrapper 查询
    ↓
Mapper      →  MyBatis-Plus BaseMapper 接口 + XML 复杂 SQL
    ↓
DO          →  数据库表映射(@TableName 注解)

关键规则

  • Controller 直接返回 Result<T> 对象,不要直接返回原始数据
  • Repository 继承 ServiceImpl<Mapper, DO>,自动获得 MyBatis-Plus CRUD 能力
  • Mapper 继承 BaseMapper<DO>,简单 CRUD 自动生成
  • 复杂 SQL 写在 Mapper XML 中,方法签名定义在 Mapper 接口中
  • Repository 负责 Entity ↔ DO 转换,对外暴露 Entity 或 Map 类型

3.2 Result 统一响应

// 成功
Result.success("操作成功", data);

// 各类错误
Result.badRequest("参数错误");         // 400
Result.unauthorized("登录已过期");      // 401
Result.forbidden("权限不足");           // 403
Result.notFound("资源不存在");          // 404
Result.tooManyRequests("请求过于频繁"); // 429
Result.error("服务器错误");            // 500
Result.error(code, "自定义错误");      // 自定义状态码

3.3 认证模式

  • 使用 @RequireAuth 注解标记需要登录的接口
  • 使用 @RequireAdmin 注解标记需要管理员权限的接口
  • AuthInterceptor 拦截器统一处理认证逻辑
  • Controller 通过 request.getAttribute("currentUser") 获取当前用户
  • Token 使用 TokenUtil 生成和验证

3.4 MyBatis-Plus 查询模式

// 简单查询 → LambdaQueryWrapper
var wrapper = new LambdaQueryWrapper<DriveMovieDO>()
    .eq(DriveMovieDO::getDriveId, driveId)
    .eq(DriveMovieDO::getUserId, userId)
    .eq(DriveMovieDO::getStatus, "active")
    .orderByDesc(DriveMovieDO::getCreateTime);

// 分页查询
var page = new Page<DriveMovieDO>(pageNo, pageSize);
var result = page(page, wrapper);

// 更新 → LambdaUpdateWrapper
var wrapper = new LambdaUpdateWrapper<DriveMovieDO>()
    .eq(DriveMovieDO::getId, id)
    .set(DriveMovieDO::getStatus, "deleted");

3.5 Jasypt 配置加密模式

所有服务(必须一致):

jasypt:
  encryptor:
    password: ${JASYPT_ENCRYPTOR_PASSWORD}          # 必须通过环境变量传入
    algorithm: PBEWITHHMACSHA512ANDAES_256           # 统一使用强加密算法
    key-obtention-iterations: 1000
    pool-size: 1
    salt-generator-classname: org.jasypt.salt.RandomSaltGenerator
    iv-generator-classname: org.jasypt.iv.RandomIvGenerator
    string-output-type: base64

加密密钥wangpan-secret-key-2024

启动命令(必须带 Jasypt 密钥参数):

java -DJASYPT_ENCRYPTOR_PASSWORD=wangpan-secret-key-2024 -jar xxx.jar

3.6 MinIO 存储模式

  • MinioUtil 作为 Spring Bean使用 @PostConstruct 初始化
  • 支持静态方法调用和 Spring Bean 注入两种方式
  • 预签名 URL 有效期7 天
  • 存储桶按服务区分:wangpan(网盘服务)、tmdbTMDB服务

3.7 定时任务模式

@Component
public class TmdbScheduler {
    @Scheduled(fixedDelay = 6 * 60 * 60 * 1000, initialDelay = 5 * 60 * 1000)
    public void executeScrape() { ... }
}

四、MyBatis XML 映射规则(重要!)

4.1 列别名规则

XML 中 SELECT 返回的列别名 必须与 Java 代码中 Map.get() 使用的 key 完全一致

<!-- XML 中 -->
<select id="findDuplicateMovies" resultType="java.util.Map">
    SELECT title, COUNT(*) AS cnt    <!-- 注意:列别名是 cnt -->
    FROM wp_drive_movie
    ...
</select>
// Java 代码中必须使用相同的 key
g.get("cnt")   // ✅ 正确 - 与 XML 别名 cnt 一致
g.get("count") // ❌ 错误 - 别名是 cnt 不是 count

4.2 数值类型转换

XML 中 resultType="java.util.Map" 返回的数值Java 中获取时需要安全转换:

// ✅ 推荐:使用 Number 父类转换
((Number) g.get("cnt")).intValue()

// ❌ 不推荐:直接强制转换可能失败
(int) g.get("cnt")

4.3 分页查询

XML 中使用 LIMIT #{pageSize} OFFSET #{pageNo} 时,分页偏移量计算规则:

// 前端传 pageNo 从 1 开始XML 中 offset = (pageNo - 1) * pageSize
// 或者前端直接传 offset 值

五、已发现的问题与解决方案

问题 1SQL 列别名与 Java Map key 不匹配导致 NPE

日期2026-06-15

问题描述:调用 GET /api/drive/movies/duplicates 返回 500报错

java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" 
because the return value of "java.util.Map.get(Object)" is null

根因DriveMovieMapper.xml 中 SQL 别名是 cnt,但 DriveController.java 中使用的 key 是 count

解决方案

  1. 将 Controller 中的 g.get("count") 改为 g.get("cnt")
  2. 使用安全转换 ((Number) g.get("cnt")).intValue() 替代 (int) g.get("cnt")

影响文件

  • wangpan-drive-service/.../controller/DriveController.java 第 676 行
  • wangpan-common/.../mapper/DriveMovieMapper.xml 第 10 行SQL 别名 cnt

教训:修改 XML 中的 SQL 列别名时,必须同步修改所有引用该结果集的 Java 代码中的 Map key。

问题 2网关 Jasypt 加密算法不一致

日期2026-06-15

问题描述:网关服务使用 PBEWithMD5AndDES 算法,其他服务使用 PBEWITHHMACSHA512ANDAES_256,不一致且 MD5+DES 算法已过时。

解决方案:统一所有服务使用 PBEWITHHMACSHA512ANDAES_256

影响文件wangpan-gateway/src/main/resources/application.yml

问题 3硬编码默认 Jasypt 密钥

日期2026-06-15

问题描述:所有服务配置了 ${JASYPT_ENCRYPTOR_PASSWORD:wangpan-secret-key-2024} 默认值,存在安全风险。

解决方案:移除默认值,改为 ${JASYPT_ENCRYPTOR_PASSWORD},启动时强制通过命令行参数 -DJASYPT_ENCRYPTOR_PASSWORD=xxx 传入。

影响文件:所有服务的 application.yml

问题 4日志级别过高

日期2026-06-15

问题描述:所有服务日志级别为 DEBUG,生产环境会产生大量日志。

解决方案:改为 INFO 级别。

问题 5MyBatis-Plus SQL 日志输出到控制台

日期2026-06-15

问题描述:配置了 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl,生产环境会打印所有 SQL。

解决方案:移除该配置。

问题 6启动时 Jasypt 环境变量未传递

日期2026-06-15

问题描述:使用 $env:JASYPT_ENCRYPTOR_PASSWORD="xxx" + Start-Process 启动服务时,子进程未继承环境变量,导致 Failed to bind properties under 'spring.datasource.password'

解决方案:使用 -DJASYPT_ENCRYPTOR_PASSWORD=xxx 命令行参数替代环境变量。

问题 7TMDB 刮削查询了错误的电影表

日期2026-06-18

问题描述TMDB 刮削器(MovieMapper/MovieDO/MovieRepository)映射的是 wp_movie 表,但项目中实际使用的电影表是 wp_drive_movie(由 drive-service 管理)。wp_movie 表无数据,导致刮削始终返回"没有需要刮削的电影"。

根因:项目中存在两张结构相同的电影表(wp_moviewp_drive_movie),但实际只有 wp_drive_movie 有数据。

解决方案

  1. MovieDO@TableNamewp_movie 改为 wp_drive_movie
  2. 将所有 MovieMapper 的 SQL含 XML 和注解)中的 wp_movie 改为 wp_drive_movie
  3. TmdbCacheMapper.xml 中 JOIN 的 wp_movie 改为 wp_drive_movie
  4. 移除 MovieMapper.CREATE_TABLE_SQLwp_drive_movieDriveMovieMapper 负责建表)
  5. 从 tmdb-service 的 DatabaseInitializer 中移除 wp_movie 表创建
  6. 修改 findMoviesNeedingScrape 查询条件,移除 scrape_type != 'none' 限制,允许未刮削的电影被选中

影响文件

  • wangpan-common/.../dataobject/MovieDO.java@TableName 改为 wp_drive_movie
  • wangpan-common/.../mapper/MovieMapper.java — 移除 CREATE_TABLE_SQLSQL 改表名
  • wangpan-common/.../mapper/MovieMapper.xml — SQL 表名改为 wp_drive_movie,放开 scrape_type 限制
  • wangpan-common/.../mapper/TmdbCacheMapper.xml — JOIN 表名改为 wp_drive_movie
  • wangpan-common/.../mapper/TmdbCacheMapper.java — 注释更新
  • wangpan-common/.../entity/Movie.java — 注释更新
  • wangpan-tmdb-service/.../config/DatabaseInitializer.java — 移除 wp_movie 建表
  • wangpan-tmdb-service/.../service/TmdbScraperService.java — 注释更新

教训:项目中统一使用 wp_drive_movie 作为电影表所有模块drive-service、tmdb-service都应操作同一张表。wp_movie 表已被废弃,不应再使用。


六、配置文件关键信息

6.1 数据库连接

  • 地址:118.178.238.159:33306
  • 数据库名:wangpan
  • 连接参数:useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
  • 所有密码使用 Jasypt ENC(...) 加密

6.2 MinIO 连接

  • 地址:118.178.238.159:39000
  • 访问密钥和秘密密钥使用 Jasypt ENC(...) 加密
  • 存储桶:wangpan(网盘服务)、tmdbTMDB服务

6.3 网关路由规则

路由路径 转发目标
/api/login, /api/register, /api/token/**, /api/login-logs/**, /api/users/** http://localhost:8081
/api/drive/**, /api/icon/** http://localhost:8082
/api/movies/**, /api/tmdb/**, /api/tmdb-scraper/** http://localhost:8083

所有路由 StripPrefix=0(不裁剪路径前缀)。


七、TraeAI 操作规则

7.1 每次修改代码前必须执行

  1. 读取本文件PROJECT_REFERENCE.md)了解项目规范
  2. 查找相关代码,理解现有模式后再修改
  3. 修改代码时遵循项目命名规范

7.2 修改规范

  1. 禁止创建不必要的新文件,优先编辑现有文件
  2. 禁止创建文档(*.md除非用户明确要求
  3. 修改 SQL 列别名时,必须同步检查所有 Java 代码中对 Map key 的引用
  4. 修改配置文件时,确保所有服务配置一致Jasypt 算法、日志级别等)
  5. 使用 Result 类统一返回,不要返回原始类型
  6. 数据库操作使用 MyBatis-Plus LambdaQueryWrapper/LambdaUpdateWrapper不要拼接 SQL 字符串
  7. 复杂 SQL 写在 Mapper XML 中,不要写在 Java 代码中

7.3 编译与启动

# 编译
mvn clean install -DskipTests

# 启动单个服务
java -DJASYPT_ENCRYPTOR_PASSWORD=wangpan-secret-key-2024 -jar xxx\target\xxx-1.0-SNAPSHOT.jar

7.4 遇到新问题时

在本文档 第五章 中追加新问题记录,包含:

  • 日期
  • 问题描述
  • 根因
  • 解决方案
  • 影响文件
  • 教训

八、MyBatis-Plus 配置要点

mybatis-plus:
  mapper-locations: classpath*:com/wangpan/common/dal/mapper/**/*.xml
  type-aliases-package: com.wangpan.common.dal.dataobject
  configuration:
    map-underscore-to-camel-case: true    # 自动驼峰映射
  global-config:
    db-config:
      id-type: auto                        # 主键自增

注意mapper-locations 使用 classpath*: 前缀,因为 Mapper XML 在 wangpan-common 模块中,所有子服务都需要加载。


九、api-doc.html 维护规范

9.1 文件位置

wangpan-gateway/src/main/resources/META-INF/resources/api-doc.html

9.2 历史不一致问题记录

问题 8api-doc.html 与代码多处不一致

日期2026-06-18

发现的不一致

接口 文档描述 实际代码 严重程度
GET /api/movies 查询参数 status pending / scraped / failed active / deletedstatus 是记录状态scrapeType 是刮削状态)
GET /api/movies 缺少参数 sortBy/sortOrder 支持 sortBysortOrder 排序参数
POST /api/movies 请求体 只有 { "title": "..." } 支持 title, subtitle, resourceLibrary, maintainer, status, category, scrapeType, rating, posterUrl, tags
PUT /api/movies/{id} 请求体 空白 支持全部字段的部分更新
GET /api/movies/stats 响应 空白 返回 totalCount, scrapedCount, pendingCount
POST /api/tmdb-scraper/scrape 仅请求体方式 同时支持 URL 参数 ?movieId=1 和请求体
POST /api/tmdb-scraper/batch-scrape movieIds 为必填 movieIds 可选,不传则自动查找
多个 /api/tmdb-scraper/* 端点 响应文档空白 stats, trigger, scraper-logs, cache delete 等缺少响应
路由表缺少 /api/icon/** 未列出 /api/icon/** → drive-service

解决方案:逐一修复 api-doc.html 中所有不一致的字段、参数和响应示例。

教训:每次新增或修改 Controller 接口时,必须同步更新 api-doc.html。


最后更新2026-06-18