# AGENTS.md 本仓库是 [youlai-boot](https://gitee.com/youlaiorg/youlai-boot) 的 TTSTD 定制分支,用于**老年平板设备管理**:设备端(sn)、家属端(client)、运营后台(device / app)。 ## 1. 与 Gitee 上游的关系(最高优先级) 远程仓库: | remote | 地址 | 用途 | | --- | --- | --- | | `gitee` | `git@gitee.com:youlaiorg/youlai-boot.git` | **上游只读**(push 已禁用),只用于比对/同步 | | `origin` | `ssh://git@gitea.ttstd.com:2222/tt/youlai-boot.git` | 我方仓库 | **只有以下目录是我方新增的业务代码,可自由新增/修改:** | 目录 | 职责 | | --- | --- | | `src/main/java/com/youlai/boot/app/` | 客户端应用管理:应用分类、应用包(APK/XAPK/APKM 解析、上传、Mongo 存储) | | `src/main/java/com/youlai/boot/client/` | 家属端:App 用户、通讯录、设备操作、文件 | | `src/main/java/com/youlai/boot/device/` | 设备管理:sn 设备、截图、壁纸、策略、告警、APK 安装、硬件/网络/安全/系统信息 | | `src/main/java/com/youlai/boot/open/` | 家属端开放认证:短信登录、密码登录、注册、重置密码 | | `src/main/java/com/youlai/boot/common/config/` | `FilePath`(本地存储路径)、`WebMvcConfig`(静态资源映射) | **其余所有目录(`auth/`、`system/`、`framework/`、`support/`、`config/`、`module/`、`codegen/` 等)都来自上游,尽量保持不动。** ### 修改前必做检查 ```bash git fetch gitee git ls-tree -r --name-only gitee/master -- src/main/java/com/youlai/boot/xxx # 上游是否存在该文件 git log --oneline gitee/master -- src/main/java/com/youlai/boot/xxx # 上游是否有近期改动 ``` - **上游存在该文件 → 尽量不改**。优先在五个自有目录内新增类/扩展点(继承、实现接口、新增 Controller)来满足需求。 - **确实必须改上游文件时**:只做最小必要改动,不做格式化/重命名/顺带重构,提交信息注明原因,例如 `chore(upstream): 安全配置放行 /api/v1/xxx 以支持新模块`。 - **上游不存在该文件 → 可新增**,但应落在上述五个目录内。 ### 已登记的例外清单 以下改动位于上游目录,属结构性必要或历史遗留。**请勿删除、重构或格式化**,修改前先确认调用方。 #### A. 上游目录中新增的文件(上游不存在,因框架结构必须留在原包) | 位置 | 说明 | | --- | --- | | `common/constant/ClientTypeConstants.java` | 端标识(admin / client) | | `common/util/{HashUtils, FileUtils, RedisSafeOps, CodeGeneratorUtil}.java` | 校验值、文件、Redis 安全操作、编码生成工具 | | `config/{DataSourceConfig, RedissonConfig, MqttConfig, RedisHealthConfig}.java`、`config/property/MqttProperties.java` | 多数据源、分布式锁、MQTT、Redis 健康检查 | | `framework/security/filter/{MobileApiSignatureFilter, CachedBodyRequestWrapper}.java` | 设备端 ECDSA 验签过滤器与请求体缓存 | | `framework/security/signature/DeviceSignatureVerifier.java` | 设备签名校验 | | `framework/security/authorization/ClientAccessAuthorizationManager.java` | 家属端令牌隔离 | | `support/mqtt/{MqttController, MqttMessageHandler, MqttProducer}.java` | MQTT 上报/推送 | | `support/webrtc/**` | WebRTC 信令(`/ws/signal`) | | `support/sms/SmsResult.java` | 短信发送结果 | | `resources/mapper/system/DeviceMapper.xml` | 设备分页查询 | | `test/java/com/youlai/boot/app/packages/component/ApkMetaParserTest.java` | 安装包解析测试 | #### B. 被修改的上游文件(只允许最小增补,不做重构/格式化) | 文件 | 允许的改动范围 | | --- | --- | | `config/SecurityConfig.java` | 放行 `/api/v1/sn/**`、`/api/v1/open/**`、`/ws/signal`;`/api/v1/client/**` 走 `ClientAccessAuthorizationManager` | | `config/JacksonConfig.java` | **保持上游原样**(不得添加未使用的 import / 注释代码) | | `config/RedisConfig.java` | 仅 `@Lazy` 延迟初始化;序列化方式必须与上游一致 | | `framework/security/config/SecurityProperties.java` | client 端令牌 TTL 两个字段 | | `framework/security/model/{SecurityUser, SecurityUserDetails}.java` | `clientType` 字段 | | `framework/security/token/JwtTokenManager.java` | client 端 TTL 解析、`clientType` claim、校验失败日志 | | `common/constant/JwtClaimConstants.java`、`common/constant/RedisConstants.java` | `clientType` claim、设备在线 key、短信验证码 key | | `common/enums/{ActionTypeEnum, LogModuleEnum}.java`、`common/result/ResultCode.java` | 仅追加新枚举值/错误码 | | `framework/web/advice/GlobalExceptionHandler.java` | Redis 相关异常、SSE 超时、静态资源 404 | | `framework/web/log/OperationLogAspect.java` | 设备无登录会话时的操作人兜底;`error_msg` 上限 2000 | | `system/service/impl/ConfigServiceImpl.java` | 缓存预热失败不阻断启动 | | `support/file/model/FileInfo.java`、`support/file/service/impl/LocalFileServiceImpl.java` | md5/sha1/sha256 校验值 | | `support/file/service/FileService.java`、`support/file/service/impl/{AliyunFileServiceImpl, S3FileServiceImpl, LocalFileServiceImpl}.java` | 仅新增 `uploadFile(MultipartFile, String directory)` 重载(供我方新增接口使用);阿里云 `domain` 配置及 `deleteFile` 的 key 解析适配(兼容自定义域名)。**`uploadFile(MultipartFile)` 保持上游日期目录行为**;`system/controller/FileController.java` **保持上游原样** | | `support/sms/{SmsTypeEnum, service/SmsService, service/impl/AliyunSmsServiceImpl}.java` | `sendWithResult`,按响应码判定成败 | | `support/sse/{SseService, SseSessionManager, constant/SseEventConstants}.java` | 设备事件主题 | | `pom.xml` | 新增依赖(websocket、mongodb、mqtt、apk-parser、apkanalyzer、redisson 等) | | `.gitignore` | 目前仅新增 `/uploadFile/` | | `resources/application-dev.yml`、`resources/application-prod.yml` | 数据源(mysql/tdengine)、redis、mongodb、oss、sms、mqtt、安全白名单、令牌 TTL | | `sql/youlai-admin.sql` | `sys_log.error_msg` 由 1000 调整为 2000 | #### C. 数据与运维脚本(非 Java 代码,允许新增) `sql/*.sql`、`docs/sql/*.sql`、`docker/*`。 #### D. 禁止事项 - 上游文件里做格式化、重命名、import 重排、注释掉代码等无关改动。 - 配置文件提交真实凭证:密码/AccessKey 一律用环境变量或本地 profile 覆盖,不写入版本库。 - 在上游目录新增 Java 类(应放到自有目录,例如 `ByteArrayMultipartFile` 放在 `app/packages/component/`)。 - 为适配我方上传规则去改 gitee 上游已有的上传接口(`POST /api/v1/files`、`system/controller/FileController`):上游已有的不管,规则只加在我方新增接口上。 ## 2. 技术栈 Java 17 · Spring Boot 4.0.5 · Spring Security · MyBatis-Plus 3.5.15 · MySQL · Redis + Redisson · MongoDB(`spring-boot-starter-data-mongodb`) · WebSocket · knife4j/springdoc · MapStruct + Lombok · Hutool · apk-parser / apkanalyzer ## 3. 构建与运行 ```bash mvn -DskipTests clean package # 打包,产物 target/youlai-boot-4.12.0.jar mvn test # 测试 java -jar target/youlai-boot-4.12.0.jar --spring.profiles.active=dev ``` - 默认 profile 为 `dev`(`application.yml`),配置见 `application-dev.yml` / `application-prod.yml`;代码生成配置在 `codegen.yml`。 - 数据库连接请使用本地 `/ 环境变量` 覆盖,**不要提交真实账号密码**。 - Windows 上 shell 为 PowerShell,勿使用 `head`/`tail`/`cd /d` 等 cmd/Unix 语法。 ## 4. 代码约定 包结构(自有模块统一遵循): ``` xxx/ ├── controller/ # @RestController,路径 /api/v1/... ├── service/ # 接口 │ └── impl/ # 实现,继承 MyBatis-Plus ServiceImpl ├── converter/ # MapStruct 转换器 ├── mapper/ # MyBatis-Plus Mapper(自有模块一般不写 XML) ├── repository/ # Mongo 仓储(接口 + impl,用 MongoTemplate) └── model/ ├── entity/ # MySQL 实体(设备相关以 Sn 前缀命名,如 SnDeviceInfo) ├── document/ # Mongo 文档(如 AppPackageDocument) ├── form/ req/ query/ # 入参 └── vo/ dto/ # 出参 ``` - 统一返回 `Result` / `PageResult`,分页参数用 `PageQuery`,分页结果用 `IPage`。 - 构造器注入:类加 `@RequiredArgsConstructor`,字段 `private final`。 - 接口文档:`@Tag(name = "序号.名称")` + `@Operation(summary = ...)`。 - 操作日志:`@Log(module = LogModuleEnum.X, value = ActionTypeEnum.Y)`。 - 鉴权分区: - `/api/v1/sn/**`:设备端,`MobileApiSignatureFilter` 做 ECDSA 设备签名校验(非 JWT) - `/api/v1/client/**`:家属端,仅接受 `clientType=client` 的令牌 - `/api/v1/open/**`:免登录(登录/注册/找回密码) - 后台接口(`/api/v1/device`、`/api/v1/app/**`、`/api/v1/developers` 等):走 Spring Security,按需 `@PreAuthorize` - 我方新增接口的业务文件统一通过 `FileService#uploadFile(MultipartFile, String directory)` 存到 `StorageDir` 定义的固定目录(`app/icon`、`app/package-icon`、`app/package`、`app/screenshot`、`app/avatar`、`app/wallpaper`、`app/category-icon`、`app/file`),实际存储由 `storage.type`(s3 / aliyun / local)决定;不按上传日期分目录。 - 上游 `FileService#uploadFile(MultipartFile)` 及其 `Aliyun/S3/Local` 三个实现保持 gitee 行为(`yyyyMMdd/` 日期目录,本地实现不算哈希),只由上游 `POST /api/v1/files` 使用,不得修改其语义。 - 头像:家属端走 `POST /api/v1/client/files/avatar`,落 `app/avatar`。管理端头像继续用上游 `POST /api/v1/files`,`system/controller/FileController` 保持 gitee 原样(不再提供 `/api/v1/files/avatar`)。 - **文件上传规则的改动边界**:涉及存储目录、对象命名、扩展名校验、URL 生成等上传规则调整时,只允许改我方新增的接口与实现(`/api/v1/client/files/**`、`/api/v1/sn/**` 等);gitee 上游已有的上传接口(`POST /api/v1/files`、`system/controller/FileController` 及其调用的 `FileService` 原方法)保持上游原样,不因我方需求改其入参、出参、校验与行为。 - `FilePath` + `/static/{子目录}/**` 仅用于设备截图等纯本地资源,以及存量历史文件的访问。 - 新增后台菜单/表结构请补充 `sql/` 脚本,保持与代码同步。 ## 5. 提交约定 采用 Conventional Commits(`feat(device): ...`、`fix(client): ...`、`refactor(config): ...`、`chore: ...`)。涉及上游文件的改动需在正文说明原因。 ## 6. 不做兼容(强制) 变更直接定案,**不写向后兼容代码**: - 接口入参/出参、字段语义、存储路径变更时,直接改成目标形态;禁止可选参数、双分支、"不传则走旧逻辑"、"新老格式都支持"之类的兜底写法。 - 禁止为旧前端/旧设备端保留旧接口、旧字段或旧 URL 前缀;需要它们同步修改时,在提交说明中列明改动点。 - 存储位置由业务语义决定:一类资源对应 `StorageDir` 中的一个固定常量,使用**专用接口**落盘(如 `POST /api/v1/client/files/avatar`),不得让调用方通过 `directory` 之类的参数决定目录。该约定仅约束我方新增接口,不改动上游 `POST /api/v1/files`。 - 存量历史数据(旧本地路径、旧对象目录、旧字段格式)只允许用一次性迁移脚本或管理接口处理,不得在业务代码中长期保留运行时回退分支。