# Forge-Hotel 编码习惯与规范 > 本文档记录个人在 Forge 项目中的编码习惯和偏好,所有代码编写(含 AI 辅助生成)必须遵循以下规范。 > > **适用范围**: forge-hotel 模块及后续所有业务模块 > **最后更新**: 2026-08-14 --- ## 一、后端编码习惯 ### 1.1 依赖注入 - **使用 `@Autowired` 字段注入**,不使用 `@RequiredArgsConstructor` 构造器注入 - 每个依赖字段单独加 `@Autowired` 注解 - 示例: ```java @RestController @RequestMapping("/hotel/dish") public class HotelDishController { @Autowired private HotelDishService hotelDishService; } ``` ### 1.2 Controller 查询参数 - **分页查询接口的筛选参数必须用实体类接收**,禁止逐个 `@RequestParam` - 只有 `@PathVariable`(路径参数如 id)和确实无法用实体类表达的参数(如安全校验参数)才单独接收 - 状态更新类接口(如 `/{id}/status`)属于操作类,可保持 `@RequestParam` - 示例: ```java // ✅ 正确 public RespInfo> dishPage(PageQuery pageQuery, HotelDish query) { ... } // ❌ 错误 public RespInfo> dishPage(PageQuery pageQuery, @RequestParam(required = false) Long categoryId, @RequestParam(required = false) String status, @RequestParam(required = false) String name) { ... } ``` - Service 接口和 Mapper 方法签名同步使用实体类参数 - Mapper XML 中用 `@Param("query")` 标注,引用时用 `#{query.fieldName}` ### 1.3 统一返回封装 - 所有接口统一使用 `RespInfo` 封装返回值 - 成功:`RespInfo.success(data)` 或 `RespInfo.success()`(无返回值时) - 失败:`RespInfo.error(msg)` 或抛出 `BusinessException` ### 1.4 实体类规范 - 实体继承 `TenantEntity`(自带 tenantId + 审计字段) - 主键使用 `@TableId(value = "id", type = IdType.ASSIGN_ID)`(雪花算法) - 逻辑删除字段:`@TableLogic(value = "0", delval = "id")`,类型为 `Long`(bigint),删除时写入主键 ID - 使用 Lombok:`@Data` + `@EqualsAndHashCode(callSuper = true)` + `@Serial` - 每个字段必须有 Javadoc 注释 - 示例: ```java @Data @EqualsAndHashCode(callSuper = true) @TableName("hotel_business_hours") public class HotelBusinessHours extends TenantEntity { @Serial private static final long serialVersionUID = 1L; /** 主键ID(雪花算法) */ @TableId(value = "id", type = IdType.ASSIGN_ID) private Long id; /** 状态: ENABLED-启用, DISABLED-禁用 */ private String status; /** 删除标记(0=未删除,删除时写入主键ID) */ @TableLogic(value = "0", delval = "id") private Long delFlag; } ``` ### 1.5 SQL 编写规范 - **SQL 必须写在 Mapper XML 中**,禁止在 Service 层用 `LambdaQueryWrapper` 构建查询 - 例外:仅 `selectById`、`insert`、`updateById`、`deleteById` 等 MyBatis-Plus 内置方法允许 - 原因:`DataScopeInterceptor` 按 `mapperMethod` 精确匹配改写 SQL - Mapper XML 中的查询必须显式过滤未删除数据:`AND del_flag = 0` - 原因:自定义 XML SQL 不会被 MP 自动补全逻辑删除条件 ### 1.6 操作日志 - 每个 Controller 方法必须加 `@OperationLog` 注解 - 格式:`@OperationLog(module = "模块名", type = OperationType.XXX, desc = "操作描述")` - 操作类型:`QUERY` / `ADD` / `UPDATE` / `DELETE` ### 1.7 包结构 ``` controller/ # REST 控制器 domain/ # 数据库实体(Entity) dto/ # 请求 DTO(新增/修改用) vo/ # 响应 VO(查询返回用) mapper/ # MyBatis Mapper 接口 service/ # 服务接口 └── impl/ # 服务实现 constant/ # 常量 ``` ### 1.8 Service 层规范 - Service 之间**禁止互相注入**(避免循环依赖) - 跨 Service 协调逻辑上提到 Controller 层 - 事务注解:`@Transactional(rollbackFor = Exception.class)` - 校验方法命名:`validateXxx(Xxx entity/dto)` - 存在性校验方法命名:`requireXxx(Long id)`(不存在直接抛异常) --- ## 二、前端编码习惯 ### 2.1 页面组件选择 - **标准 CRUD 页面**:使用 `AiCrudPage` 组件(自动处理搜索栏、工具栏、表格、分页布局) - **分页位置**:右下角(AiCrudPage 内置) - **配置少的简单列表**:不加多余筛选条件,保持界面简洁 - 页面容器样式:`height: 100%` ### 2.2 AiCrudPage 配置规范 - `api-config` 格式: ```javascript :api-config="{ list: 'get@/hotel/xxx/page', detail: 'get@/hotel/xxx/:id', add: 'post@/hotel/xxx', update: 'put@/hotel/xxx', delete: 'post@/hotel/xxx/remove/:id', }" ``` - URL 占位符用**冒号格式**:`:id`,禁止花括号 `{id}` - `tableColumns` 和 `editSchema` 必须定义为 `computed`(确保字典异步加载后响应式更新) - 操作列使用 `h('a', { class: 'text-primary cursor-pointer hover:opacity-80', onClick: ... })` 模式 - 刷新方法:`crudRef.value?.refresh()`(不是 reload) - 编辑方法:`crudRef.value?.handleEdit(row)` ### 2.3 字段命名 - 前端字段统一使用 **camelCase** 命名 - 与后端 API 返回字段保持一致 ### 2.4 表格渲染规范 - **空值处理**:空值字段显示空白,**不使用 `-` 作为占位符** - **图片列**: - 列宽设置 `100px` - 用 `div` 包裹 `AuthImage` 组件,设置 `overflow: hidden` 防止图片溢出 - 禁止直接用 `NAvatar` 的 src 属性 - **状态标签**:使用 `NTag` 组件,配合颜色映射 ```javascript const statusColorMap = { ENABLED: 'success', DISABLED: 'default' } render: row => h(NTag, { type: statusColorMap[row.status] || 'default', size: 'small' }, { default: () => statusLabelMap[row.status] || row.status }) ``` ### 2.5 操作按钮样式 使用 UnoCSS 语义化颜色类区分操作类型: | 类名 | 颜色 | 场景 | |------|------|------| | `text-primary` | 蓝 | 编辑、查看、授权 | | `text-warning` | 黄 | 刷新缓存、重置、封禁 | | `text-error` | 红 | 删除、强制下线 | | `text-success` | 绿 | 启用、发布、通过 | | `text-info` | 灰蓝 | 详情、统计 | ### 2.6 字典使用 - **禁止硬编码**下拉选项和状态标签 - 下拉使用 `DictSelect` 组件或 `useDict()` composable - 标签展示使用 `DictTag` 组件 - Schema 中的字典选项必须用 `computed` 包裹 ### 2.7 图片预览 - 采用全屏覆盖层实现(`Teleport to="body"`) - `position: fixed` + 半透明黑色背景 - 右上角 × 关闭按钮,支持点击背景或 ESC 关闭 - 左右箭头切换图片,底部圆点指示器 + 页码计数 - 图片自适应:`max-width: 85vw` + `max-height: 80vh` ### 2.8 时间段选择 - 时间类字段使用**下拉选择**(Select),不使用手动输入框 - 时间选项按业务场景设定间隔(如每 30 分钟一档:09:00, 09:30, 10:00...23:30) ### 2.9 API 文件规范 - 导入路径:`import { request } from '@/utils'`(不是 `@/utils/request`) - 函数命名:`getXxxPage` / `getXxxDetail` / `createXxx` / `updateXxx` / `deleteXxx` - 按模块用注释分隔:`// ==================== 模块名 ====================` - 每个函数加 JSDoc 注释 --- ## 三、数据库编码习惯 ### 3.1 表结构规范 - 所有业务表必须包含:`id`, `tenant_id`, `create_by`, `create_time`, `create_dept`, `update_by`, `update_time` - 字符集 `utf8mb4`,引擎 `InnoDB` - 主键用 `bigint`(雪花算法),不用自增 - 金额字段用 `bigint`,单位**分** - 时间字段用 `datetime`(对应 Java `LocalDateTime`) - 状态字段用 `varchar`,值为大写英文(如 `ENABLED` / `DISABLED`、`ON_SALE` / `OFF_SHELF`) ### 3.2 逻辑删除规范 - 业务表默认使用逻辑删除 - `del_flag` 字段类型 `bigint NOT NULL DEFAULT 0` - 删除时写入当前行主键 ID(不是固定值 `1`) - 实体必须显式声明 `@TableLogic(value = "0", delval = "id")` ### 3.3 Flyway 迁移脚本 - 命名格式:`V<版本号>__.sql` - 版本号必须大于 `1.0.0`,单调递增 - 脚本必须具备**防重复保护**: - 建表:`CREATE TABLE IF NOT EXISTS` - 插入:`INSERT ... SELECT ... WHERE NOT EXISTS` - 新增列/索引前查 `information_schema` - `INSERT` 必须显式写列名,禁止依赖表字段顺序 - 业务内置数据 `tenant_id` 必须为 `1`,禁止写 `0` - 菜单资源 SQL 使用动态 `parent_id`(子查询),避免硬编码 ID - 字典数据格式:`sys_dict_type` + `sys_dict_data`,`dict_value` 与后端枚举值保持一致 ### 3.4 字典规范 - 字典类型命名:小写下划线,业务级用 `hotel_` 前缀,系统级用 `sys_` 前缀 - `dict_value` 必须与后端枚举/状态值严格一致 - `dict_label` 只负责展示文案 - `list_class` 负责标签样式(success/primary/warning/info/default) - 每个字典数据单独一行 INSERT + WHERE NOT EXISTS 防重复 --- ## 四、API 设计习惯 ### 4.1 RESTful 风格 | 操作 | HTTP 方法 | 路径 | 说明 | |------|-----------|------|------| | 分页查询 | GET | `/xxx/page` | 筛选参数用实体类接收 | | 查询全部 | GET | `/xxx/all` 或 `/xxx/list` | 不分页列表 | | 查询详情 | GET | `/xxx/:id` | 路径参数 | | 新增 | POST | `/xxx` | RequestBody | | 修改 | PUT | `/xxx` | RequestBody | | 删除 | POST | `/xxx/remove/:id` | 路径参数 | | 状态切换 | POST | `/xxx/:id/toggle` 或 `/:id/toggle-status` | 路径参数 | | 状态更新 | PUT | `/xxx/:id/status` | `@RequestParam String status` | | 批量操作 | POST | `/xxx/batch` | RequestBody | ### 4.2 权限标识 - 格式:`模块:资源:操作`,如 `hotel:dish:query`、`hotel:businessHours:add` - 按钮权限资源类型 `resource_type = 3` --- ## 五、交互设计习惯 ### 5.1 列表页面 - 配置项少的列表页不加多余筛选(如营业时段不加餐段筛选) - 分页统一在右下角 - 操作列包含:编辑 + 状态切换 + 删除 ### 5.2 批量操作 - 有批量生成的管理页面,应同步提供批量删除、批量下载(ZIP)、单条下载功能 - 保证操作完整性与用户体验一致性 ### 5.3 危险操作 - 删除、解绑等不可逆操作必须弹出**二次确认** - 已绑定的二维码显示"解绑"按钮而非"重新绑定" ### 5.4 扫码绑定 - 绑定成功后,点击"完成"跳转至扫码绑定主界面(无参数状态) - 解绑后返回扫码界面 --- ## 六、多租户习惯 ### 6.1 租户上下文 - 业务数据 `tenant_id` 必须设为 `1`(默认租户),禁止设 `0` - 免登录场景(如扫码):先忽略租户过滤查到实体,再根据实体的 `tenantId` 动态设置租户上下文 - 使用 `TenantContextHolder.getTenantId()` 获取当前租户 ID - 兜底逻辑:`return tenantId == null ? 1L : tenantId;` --- ## 七、其他习惯 ### 7.1 注释风格 - 实体类每个字段必须有 Javadoc 注释 - Controller 每个方法必须有 Javadoc(描述接口用途) - Service 接口方法必须有 Javadoc(含参数说明) - 关键业务逻辑加行内注释说明 ### 7.2 安全红线 - 禁止硬编码密钥、AK/SK、数据库密码 - 禁止在日志中打印手机号、身份证、银行卡 - API Key/Secret 返回前端必须脱敏(保留前4后4,中间 `****`) ### 7.3 构建与验证 - Maven 构建:`mvn compile -pl forge-business/forge-hotel -am -DskipTests` - 前端 Lint:`pnpm lint:fix` - 改完代码必须编译/Lint 验证通过