265 lines
8.0 KiB
Markdown
265 lines
8.0 KiB
Markdown
# 项目开发规范(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"
|
||
)
|
||
```
|