# 项目开发规范(AI必读) ## 数据库 ### 1. 表命名 **格式**:`b_{模块}_{实体}` 所有业务表以 `b_` 前缀开头,使用 `snake_case`。例如:`b_clothing_clothes`、`b_meal_dish`、`b_chat_file`。 ### 2. 关联表命名 M:N 关联表直接用「**主实体 + 子实体**」拼接: **格式**:`b_{模块}_{主实体}_{子实体}` 例如:`b_clothing_outfit_clothes`、`b_meal_dish_ingredient`。 **禁止**:多态关联(`target_type` + `target_id`),应拆分为独立表。 ### 3. 标签表 标签表统一用 `_tag` 后缀: | 表名 | 用途 | |------|------| | `b_clothing_tag` | 衣物标签(父子结构) | | `b_meal_tag` | 餐食标签(父子结构) | | `b_clothing_clothes_tag` | 衣服 ↔ 标签关联 | | `b_meal_dish_tag` | 菜品 ↔ 标签关联 | - 父子结构:`parent_id BIGINT`,`NULL` 表示顶层,**不要用 `0`** - 颜色字段:`color varchar(7)`,存储 hex 色值如 `#EF4444` ### 4. 字段命名 **审计字段(所有业务表必须包含)**: ```sql id BIGINT PRIMARY KEY, -- 雪花 ID create_time TIMESTAMPTZ DEFAULT now(), update_time TIMESTAMPTZ DEFAULT now(), create_by BIGINT, update_by BIGINT ``` > 审计字段由 `pkg/db/meta_field.go` 自动注入,仅有 `b_user` 和 `b_user_oauth` 豁免。 **通用业务字段**: ```sql family_id BIGINT NOT NULL, -- 家庭 ID(多租户隔离) status SMALLINT NOT NULL DEFAULT 1, -- 0=停用, 1=启用 sort_number INT NOT NULL DEFAULT 0, -- 排序序号 ``` **命名约定**: - 全部 `snake_case` - 外键:`{实体}_id`,如 `family_id`、`category_id`、`dish_id` - 图片:`image_file_key` 或 `{用途}_file_key`,如 `cover_file_key` - 数值金额:`amount numeric(12,2)` - 金额:`amount numeric(12,2)` ### 4. 索引命名 **格式**:`idx_{表名}_{用途}` - 主键约束:`{表名}_pkey` - 唯一约束:`{表名}_{字段}_key` ```sql -- 按业务查询 idx_b_clothing_clothes_family_status_category -- 关联表双向索引 idx_b_meal_dish_tag_dish -- 按主实体查 idx_b_meal_dish_tag_tag -- 按子实体反查 -- 唯一约束 idx_b_meal_plan_family_date_slot ``` ### 6. 不允许的坏味道 - ❌ 时间戳用 `timestamp without time zone` → 必须用 `TIMESTAMPTZ` - ❌ `parent_id` 用 `0` 表示顶层 → 必须用 `NULL` - ❌ `create_by` / `update_by` 设 `NOT NULL` → 可为空,由应用层注入 - ❌ 表名/索引名中包含过时模块名(如 `b_space` 残留) - ❌ 一张表用多态字段关联多种实体 --- ## 前端 ### 1. 项目结构 ``` app-rn/src/ app/ Expo Router 页面(文件即路由) components/ 可复用组件 configs/ 配置定义 helpers/ 业务辅助函数 hooks/ 自定义 Hooks layouts/ 布局组件 request/ API 封装 store/ Zustand 状态 types/ TypeScript 类型 utils/ 通用工具 ``` ### 2. 命名规范 | 成员 | 规范 | 示例 | |------|------|------| | 组件 | `PascalCase` | `PageLayout`、`Home` | | 普通函数 | `camelCase` | `formatMoney` | | Hooks | `use` 开头 | `useLoading`、`useFamilyStore` | | API 函数 | `Api` 结尾 | `loadDataApi`、`saveDataApi` | | 类型/接口 | `PascalCase` | `ClothingTag`、`FilterState` | | 文件名 | `camelCase.ts(x)` | `useLoading.ts`、`finance.tsx` | - 类型定义优先用 `interface` 而非 `type` - 领域类型按文件拆分:`auth.ts`、`finance.ts`、`clothing.ts` ### 3. 数据访问 所有数据操作通过 `src/request/api.ts` 的 5 个封装函数: ```typescript loadDataApi({ view_name, search_condition, args, ... }) loadDataPageApi({ view_name, page, page_size, ... }) loadDataBySqlApi({ sql, args }) saveDataApi([{ table_name, key_field, inserts, updates, deletes }]) getUniqueIdApi(count) ``` - 必须传递 `family_id`:`useFamilyStore((s) => s.getFamilyId())` - 响应通过 `res.isSuccess` 判断结果 ### 4. 操作记录 所有增/改/删操作完成后必须通过 `useModuleChatNotifier` 记录操作: ```typescript import { useModuleChatNotifier } from "@/hooks/useModuleChatNotifier"; const notifyOp = useModuleChatNotifier(); notifyOp({ module: "餐食", // 模块名 action: "创建菜单", // 操作描述 target: planDate, // 操作目标 sourceTable: "b_meal_plan", sourceId: targetId, }); ``` ### 5. 状态管理 - 全局持久化(用户/家庭):Zustand + `persist` 中间件 + `AsyncStorage` - 模块内部:Zustand 内存 store,不持久化 - Store 接口同时包含状态和方法 ### 6. 样式与组件 - 样式用 NativeWind `className`,**禁止内联 `style={}`** - **尽量使用主题色**:颜色统一用 `useThemeColor()` 获取,不硬编码色值 - 图标用 `lucide-react-native`,颜色通过 `useThemeColor("accent")` 获取(`className="text-accent"` 对图标不生效) - UI 组件优先用 HeroUI Native(`heroui-native`) - 页面用 `PageLayout` / `ModuleLayout` / `AppLayout` 包裹 - **页面布局尽量使用 `LayoutView`** + `LayoutView.ScrollView` - 页面搜索/筛选参考:`app-rn/src/app/finance/(tabs)/home/index.tsx` - 嵌套 BottomSheet 筛选参考:`app-rn/src/app/clothes/outfit/edit/index.tsx` ### 7. BottomSheet 规范 - **Title 必须居中**:`` - **必须限制最大高度**:内容区设置 `maxHeight`,防止弹出层过高 - **搜索/筛选过多时加独立筛选 BottomSheet**:如果选择器内筛选条件过多(如选择衣服时按分类、标签、归属者筛选),筛选按钮触发第二个 BottomSheet,避免第一个 BottomSheet 内容过载 ### 8. 异步操作 所有保存/更新/删除操作必须使用 `useLoading` + ``: ```typescript const { isLoading, startLoading, stopLoading } = useLoading(); startLoading("保存中..."); const res = await saveDataApi([...]); if (res.isSuccess) { stopLoading({ type: "success", message: "已保存" }); } else { stopLoading({ type: "error", message: "操作失败" }); } ``` 禁止手动管理多个 loading 布尔值。 ## 后端 ### 1. 项目结构 ``` app-go/ internal/ config/ 配置加载 handle/ HTTP handler httpx/ 请求/响应工具 middleware/ Fiber 中间件 router/ 路由注册 types/ DTO ws/ WebSocket pkg/ db/ PostgreSQL 客户端 jwtx/ JWT logger/ 日志 s3store/ S3 存储 uniqueid/ 雪花 ID 生成 ``` ### 2. 命名规范 | 成员 | 规范 | 示例 | |------|------|------| | 导出函数/类型 | `PascalCase` | `LoadData`、`LoginQqReq` | | 非导出函数 | `camelCase` | `handleThirdLogin` | | 包名 | 单个小写单词 | `package handle` | | 文件名 | `snake_case.go` | `meta_field.go` | | JSON 标签 | `snake_case` | `json:"family_id"` | ### 3. API 设计 **不允许创建专用 REST 端点。** 所有数据操作复用 5 个通用 API: | 端点 | 用途 | |------|------| | `POST /app/data/loadData` | 条件查询 | | `POST /app/data/loadDataPage` | 分页查询 | | `POST /app/data/loadDataBySql` | 原生 SQL | | `POST /app/data/saveData` | 批量增/改/删 | | `POST /app/data/getUniqueId` | 获取雪花 ID | **非必要不新增接口。** 新增任何接口前必须先询问并获得确认,且新增接口必须是通用型的,不能为某个业务功能单独定制。 ### 4. 数据访问 - 所有业务数据关联 `family_id`,查询时必须按当前用户的 `family_id` 过滤 - 审计字段(`create_time`、`update_time`、`create_by`、`update_by`)由 `pkg/db/meta_field.go` 自动注入,仅 `b_user` 和 `b_user_oauth` 豁免 - 事务使用闭包模式:`dbClient.WithTx(ctx, func(tx *db.Client) error { ... })` - 外部调用错误用 `errors.WithStack(err)` 包装,业务错误用 `httpx.Fail(c, "消息")` 返回 ### 5. 导入顺序 内部包(`allapp-go/`)在前,第三方包在后,两组之间空一行: ```go import ( "allapp-go/internal/httpx" "allapp-go/internal/types" "github.com/gofiber/fiber/v3" ) ```