- 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 日志
18 KiB
网盘系统 (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 |
| 配置加密 | Jasypt(Jasypt 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 |
| 主键 | id(BIGINT 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(网盘服务)、tmdb(TMDB服务)
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 值
五、已发现的问题与解决方案
问题 1:SQL 列别名与 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。
解决方案:
- 将 Controller 中的
g.get("count")改为g.get("cnt") - 使用安全转换
((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 级别。
问题 5:MyBatis-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 命令行参数替代环境变量。
问题 7:TMDB 刮削查询了错误的电影表
日期:2026-06-18
问题描述:TMDB 刮削器(MovieMapper/MovieDO/MovieRepository)映射的是 wp_movie 表,但项目中实际使用的电影表是 wp_drive_movie(由 drive-service 管理)。wp_movie 表无数据,导致刮削始终返回"没有需要刮削的电影"。
根因:项目中存在两张结构相同的电影表(wp_movie 和 wp_drive_movie),但实际只有 wp_drive_movie 有数据。
解决方案:
- 将
MovieDO的@TableName从wp_movie改为wp_drive_movie - 将所有
MovieMapper的 SQL(含 XML 和注解)中的wp_movie改为wp_drive_movie - 将
TmdbCacheMapper.xml中 JOIN 的wp_movie改为wp_drive_movie - 移除
MovieMapper.CREATE_TABLE_SQL(wp_drive_movie由DriveMovieMapper负责建表) - 从 tmdb-service 的
DatabaseInitializer中移除wp_movie表创建 - 修改
findMoviesNeedingScrape查询条件,移除scrape_type != 'none'限制,允许未刮削的电影被选中
影响文件:
wangpan-common/.../dataobject/MovieDO.java—@TableName改为wp_drive_moviewangpan-common/.../mapper/MovieMapper.java— 移除CREATE_TABLE_SQL,SQL 改表名wangpan-common/.../mapper/MovieMapper.xml— SQL 表名改为wp_drive_movie,放开scrape_type限制wangpan-common/.../mapper/TmdbCacheMapper.xml— JOIN 表名改为wp_drive_moviewangpan-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(网盘服务)、tmdb(TMDB服务)
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 每次修改代码前必须执行
- 读取本文件(
PROJECT_REFERENCE.md)了解项目规范 - 查找相关代码,理解现有模式后再修改
- 修改代码时遵循项目命名规范
7.2 修改规范
- 禁止创建不必要的新文件,优先编辑现有文件
- 禁止创建文档(*.md)除非用户明确要求
- 修改 SQL 列别名时,必须同步检查所有 Java 代码中对 Map key 的引用
- 修改配置文件时,确保所有服务配置一致(Jasypt 算法、日志级别等)
- 使用
Result类统一返回,不要返回原始类型 - 数据库操作使用 MyBatis-Plus LambdaQueryWrapper/LambdaUpdateWrapper,不要拼接 SQL 字符串
- 复杂 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 历史不一致问题记录
问题 8:api-doc.html 与代码多处不一致
日期:2026-06-18
发现的不一致:
| 接口 | 文档描述 | 实际代码 | 严重程度 |
|---|---|---|---|
GET /api/movies 查询参数 status |
pending / scraped / failed |
active / deleted(status 是记录状态,scrapeType 是刮削状态) |
高 |
GET /api/movies 缺少参数 |
无 sortBy/sortOrder |
支持 sortBy 和 sortOrder 排序参数 |
中 |
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