Files
workspace/code/app/项目开发规范.md
T
2026-07-06 17:03:39 +08:00

265 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 项目开发规范(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 必须居中**:`<Text className="text-center text-lg font-semibold">`
- **必须限制最大高度**:内容区设置 `maxHeight`,防止弹出层过高
- **搜索/筛选过多时加独立筛选 BottomSheet**:如果选择器内筛选条件过多(如选择衣服时按分类、标签、归属者筛选),筛选按钮触发第二个 BottomSheet,避免第一个 BottomSheet 内容过载
### 8. 异步操作
所有保存/更新/删除操作必须使用 `useLoading` + `<LoadingOverlay>`:
```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"
)
```