# cfc-backend Spring Boot 2.7.18 后端服务,MyBatis-Plus ORM,JWT 认证。 ## WHERE TO LOOK | Task | Location | Notes | |------|----------|-------| | REST API 端点 | `src/main/java/com/etotem/cfc/controller/` | 按功能分组: admin/, auth/, assessment/, family/, guide/, task/, reward/ | | 业务逻辑 | `src/main/java/com/etotem/cfc/service/` | Service 命名: `XxxService.java` | | 数据实体 | `src/main/java/com/etotem/cfc/entity/` | 使用 MyBatis-Plus 注解 | | MyBatis Mapper | `src/main/java/com/etotem/cfc/mapper/` | 接口文件,与XML映射对应 | | SQL 映射文件 | `src/main/resources/mapper/` | MyBatis XML 文件 | | 配置类 | `src/main/java/com/etotem/cfc/config/` | JWT, Security, Swagger, DatabaseInitializer | | DTO传输对象 | `src/main/java/com/etotem/cfc/dto/` | 请求/响应数据结构 | | 数据迁移 | `src/main/java/com/etotem/cfc/service/DataMigrationService.java` | sfms → cfc | ## CODE MAP (关键符号) | Symbol | Type | Location | Role | |--------|------|----------|------| | `AuthController` | Controller | `controller/auth/AuthController.java` | 微信登录、JWT 认证 | | `TaskService` | Service | `service/TaskService.java` | 任务业务逻辑核心 | | `DanAssessmentController` | Controller | `controller/DanAssessmentController.java` | DAN测评全链路(订单/预约/支付/结果) | | `AssessmentAppointmentController` | Controller | `controller/assessment/AssessmentAppointmentController.java` | 测评预约管理 | | `DataMigrationService` | Service | `service/DataMigrationService.java` | sfms → cfc 全量数据迁移 | | `User` | Entity | `entity/User.java` | 用户实体 (parent/child/teacher) | | `Task` | Entity | `entity/Task.java` | 任务实体 | | `DanAssessmentResult` | Entity | `entity/DanAssessmentResult.java` | DAN测评结果(含孩子快照字段) | | `DatabaseInitializer` | Config | `config/DatabaseInitializer.java` | 数据库迁移和初始化 | | `SfmsDataSourceConfig` | Config | `config/SfmsDataSourceConfig.java` | sfms 多数据源配置 | ## CONVENTIONS - **分层架构:** Controller → Service → Mapper,禁止跨层调用 - **ORM:** MyBatis-Plus,实体使用 `@TableName`、`@TableId(type = IdType.AUTO)` - **响应包装:** 统一 `Result` 返回,包含 code/message/data - **认证:** JWT Token,Header `Authorization: Bearer {token}` - **命名:** Controller 以 `Controller` 结尾,Service 以 `Service` 结尾 - **控制器分组:** 按功能模块分为 admin, auth, assessment, family, guide, task, reward 等子目录 - **DI注解:** 使用 `@Resource` 代替 `@Autowired`,字段名必须与类型默认 Bean Name 一致(如 `PaymentService` 的字段名必须为 `paymentService`) - **接口方法:** 所有接口统一使用 `@PostMapping`(POST 方法),禁止使用 `@GetMapping`/`@PutMapping`/`@DeleteMapping` - **新增接口前:** 必须运行 mvn clean compile 并启动项目验证无冲突;使用以下命令检查重复路由: ``` grep -rn '@GetMapping\|@PostMapping\|@PutMapping\|@DeleteMapping' src/main/java/com/etotem/cfc/controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u ``` - **Bean名冲突检查:** 新增 Controller/Service 时,确保类名不与其他包中的类重名(Spring 默认 Bean Name 为类名首字母小写) ## ANTI-PATTERNS (本项目禁止) - **DO NOT** 在 Controller 直接操作 Mapper - **DO NOT** 跳过 Service 层直接调用 Mapper - **DO NOT** 硬编码数据库连接,使用 `application.yml` - **NEVER** 在代码中硬编码 JWT 密钥 - **NEVER** 使用 `@ts-ignore`、`as any` 绕过类型检查 - **NEVER** 跳过 JWT 认证直接调用需登录接口 - **NEVER** 使用 `@Resource(name="xxx")` 与字段名不一致的注入方式,保持字段名与类型默认 Bean Name 一致 - **NEVER** 使用 `@GetMapping`/`@PutMapping`/`@DeleteMapping` — 统一使用 `@PostMapping` - **NEVER** 直接 `new XxxMapper()` — Mapper 必须通过 `@Resource` 注入 - **NEVER** 使用 `awk '!seen[$0]++'` 对 Java 源文件去重 — 会删除合法的重复行(如多个方法的 `}` 闭合括号) ## UNIQUE STYLES - 双角色系统:家长端/孩子端,登录后根据 `role` 字段分流,家长端可切孩子端,切换过来的孩子端可退出切换,即时出需要密码。直接登录的孩子端不能切换到家长 端 - 成长规划师角色系统:成长规划师是个单独的角色,经过认证的成长规划师可以以成长规划师身份登录,成长规划师可切换自己作为家长端 - 积分机制:完成任务加分、超时扣分,逻辑在 `PointsService` - 心愿审批流程:孩子创建 → 家长审批 → 扣减积分 - 测评订单:家长申请 → 选择规划师 → 孩子信息快照 → 支付 → 规划师录入结果 - 街道地址匹配:四级地址结构(省市区街道),区域回退匹配逻辑 - 孩子快照:每次测评时保存孩子身高/体重/学校等动态信息到 DanAssessmentResult - 多数据源:cfc 主库 `spring.datasource.*` + sfms 源库 `sfms.datasource.*`(只读) ## DATABASE MIGRATION WORKFLOW 当出现 `Unknown column 'xxx' in 'field list'` 等字段不存在错误,或需要进行表结构变更时,按以下流程处理: ### 工作流程 ``` 错误日志定位 → 查找实体类 → 检查 schema.sql → 添加迁移 → 同步 schema.sql → 编译验证 ``` ### Step 1: 诊断错误 后端日志中出现类似以下错误: ``` Unknown column 'xxx' in 'field list' Table 'xxx' doesn't exist ``` 从错误信息中提取:**表名、列名/对象名**,并从异常堆栈找到触发点(哪个 Service/Controller)。 ### Step 2: 查找实体与建表 SQL - 在 `entity/` 目录找到对应实体类,确认字段已定义(`@TableField` 或成员变量) - 在 `src/main/resources/schema.sql` 中找到该表的 `CREATE TABLE` 语句,确认列是否已包含 ### Step 3: 判断修复位置 | 情况 | 修复方式 | |------|---------| | schema.sql 建表语句有该列,但生产库没有(旧表) | 只需在 `DatabaseInitializer.runMigrations()` 添加 ALTER TABLE | | schema.sql 没有该列,实体也没有 | 在实体添加字段 + schema.sql 建表语句补列 + DatabaseInitializer 加迁移 | | 全新表 | schema.sql 添加 CREATE TABLE + DatabaseInitializer 的 runMigrations() 加迁移 | | 仅调整约束/类型/默认值 | DatabaseInitializer.runMigrations() 加 MODIFY COLUMN | ### Step 4: 在 DatabaseInitializer 添加迁移 `DatabaseInitializer.java` 的 `runMigrations()` 方法是所有 DDL 迁移的唯一入口。 #### 添加列(首选 ensureColumn 辅助方法) `ensureColumn(table, column, definition)` 是已有的辅助方法(第 4099 行),自动处理重复列异常: ```java // 迁移N: xxx表添加yyy字段 ensureColumn("table_name", "column_name", "VARCHAR(50) COMMENT '字段说明'"); ``` `ensureColumn` 不可用时(如需要 MODIFY / 多个列在同一个 try 块),使用标准模式: ```java // 迁移N: xxx表添加yyy字段(原因描述/需求编号) try { jdbcTemplate.execute("ALTER TABLE xxx ADD COLUMN yyy VARCHAR(50) COMMENT '字段说明'"); log.info("已添加yyy列到xxx表"); } catch (Exception e) { // 列已存在,忽略错误 } ``` #### 创建新表 ```java // 迁移N: 创建 xxx 表(功能描述) try { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS xxx (" + "id BIGINT AUTO_INCREMENT PRIMARY KEY, " + "...字段定义..." + "INDEX idx_xxx (column)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='表注释'"); log.info("已创建xxx表"); } catch (Exception e) { // 表已存在,忽略错误 } ``` #### 添加索引 ```java try { jdbcTemplate.execute("ALTER TABLE xxx ADD INDEX idx_xxx (column)"); log.info("已添加idx_xxx索引到xxx表"); } catch (Exception e) { // 索引已存在,忽略错误 } ``` #### 修改字段 ```java try { jdbcTemplate.execute("ALTER TABLE xxx MODIFY COLUMN yyy VARCHAR(100) COMMENT '新注释'"); log.info("已修改xxx.yyy字段"); } catch (Exception e) { // 修改失败,忽略错误 } ``` ### Step 5: 同步 schema.sql schema.sql 中保持该表最新的完整定义: - **新增列**:在 `schema.sql` 对应表的 `CREATE TABLE` 语句中也加上该列定义(保持建表即包含所有列) - **新建表**:在 `schema.sql` 追加该表的 `CREATE TABLE IF NOT EXISTS` 语句 - **仅修改约束/默认值**:无需更新 schema.sql - **迁移中 `CREATE TABLE IF NOT EXISTS` 创建的表**:也追加到 schema.sql(保持 schema.sql 为完整定义快照) ### Step 6: 验证 ```bash mvn clean compile # 编译验证通过即可 ``` 迁移代码是幂等的(try-catch 忽略已存在的列/表/索引),无需运行时验证。 ### 规范要点 - **迁移编号**:按现有顺序递增(当前最大编号 `迁移N`,在文件末尾搜索 `// 迁移` 确认最新编号) - **注释格式**:`// 迁移N: 表名 操作说明(原因/需求编号)` - **日志**:每个成功的 ALTER/CREATE 后加 `log.info()`,失败统一用空 catch 或 `// 忽略` - **幂等性**:所有迁移必须可重复执行而不报错(try-catch 包裹 + 列存在检查) - **辅助方法**:优先使用 `ensureColumn()` 添加列,它自动处理重复列异常;原始 `try-catch` 模式用于索引/表/非标准操作 - **同步原则**:**对 schema.sql 的每一条 DDL 变更,必须在 DatabaseInitializer 中有对应的迁移**,反之亦然——两者保持最终一致 ## COMMANDS ```bash mvn clean compile # 编译验证(唯一验证方式) mvn spring-boot:run # 启动开发服务器 (localhost:9082) mvn test # 运行测试 (目前只有1个测试类) ``` ## 工作流程 1. 修改代码后执行 `mvn clean compile` 验证编译通过 2. 编译通过即视为验证完成,不做运行时/启动验证 ## NOTES - 数据库:MySQL 8.0,地址 `192.168.16.251:3306/cfc` - 后端端口:9082,JWT 密钥在 `application.yml` - 街道数据:使用静态种子数据,街道匹配有回退机制 - API冲突已修复:检查所有Controller确保无重复路径映射 - sfms 源库连接信息在 `application.yml` 的 `sfms.datasource.*` - 数据迁移 POST `/api/migration/run` ## 后端接口规范 - 后台/api的接口规范都用post方式