12 KiB
name, description, agent_created
| name | description | agent_created |
|---|---|---|
| allapp-conventions | Allapp 项目开发规范和数据库配置指南。该 skill 应在以下场景使用:(1) 在 Allapp 项目中新增功能、 创建数据库表、或调用后端 API 时;(2) 需要了解项目通用 API 接口用法、数据库连接信息、 表结构约定时;(3) 需要遵循项目的代码规范时。触发词包括:allapp、记账、app-go、app-rn、 数据库连接、API 接口、建表、收藏功能等。 | true |
Allapp 项目开发规范
项目概览
Allapp 是一个家庭/个人空间型 mini 程序平台:
- 后端
app-go/:Go + Fiber v3 + PostgreSQL (pgx) + JWT - 前端
app-rn/:React Native + Expo 55 + Expo Router + Zustand + Alova - 数据库 PostgreSQL,库名
allapp,schemapublic
数据库连接信息见 references/db-config.md。
项目结构速查
app-go/ # Go 后端
├── configs/ # YAML 配置(config.yaml / config.dev.yaml)
├── internal/
│ ├── bootstrap/ # 启动入口
│ ├── config/ # 配置加载
│ ├── handle/ # 请求处理(auth / data / data_save / data_list / s3)
│ ├── httpx/ # 请求/响应工具
│ ├── middleware/ # 中间件(auth / logger)
│ ├── router/ # 路由注册
│ └── types/ # 请求/响应类型
├── pkg/
│ ├── db/ # 数据库操作(insert / update / meta_field)
│ └── jwtx/ # JWT 工具
└── main.go
app-rn/ # React Native 前端
├── app.json
├── package.json # ⚠️ 写页面必须先读这个
├── tailwind.config.ts
├── src/
│ ├── app/ # Expo Router 页面
│ │ ├── _layout.tsx # 根布局
│ │ ├── (main)/ # 主应用(route group,不影响 URL)
│ │ │ ├── (tabs)/ # 主 Tab(home / chat / my)
│ │ │ ├── auth/ # 登录页
│ │ │ ├── family/ # 家庭管理
│ │ │ ├── tools/ # 工具广场
│ │ │ └── activities/ # 动态列表
│ │ └── finance/ # 记账子模块(独立于 main)
│ ├── components/ # 通用组件(layout/view / AZIndex 等)
│ ├── configs/ # pages.ts(子应用列表配置)
│ ├── layouts/ # 布局(ModuleLayout 等)
│ ├── request/ # API 封装(index.ts / api.ts)
│ ├── store/ # Zustand 状态管理
│ └── types/ # TypeScript 类型定义
前端关键依赖库
写页面/组件时必须优先使用项目已安装的库,不要引入新包:
| 库 | 用途 | 导入方式 |
|---|---|---|
heroui-native |
UI 组件库(PressableFeedback、SearchField、Button 等) | import { PressableFeedback, SearchField } from "heroui-native" |
@legendapp/list |
高性能长列表 | import { LegendList } from "@legendapp/list" |
lucide-react-native |
图标库 | import { Star, ... } from "lucide-react-native" |
zustand |
状态管理 | import { create } from "zustand" |
alova + @alova/adapter-axios |
请求库 | import { createAlova } from "alova" |
dayjs |
日期处理 | import dayjs from "dayjs" |
expo-router |
路由 | import { useRouter } from "expo-router" |
react-native-reanimated |
动画 | import Animated from "react-native-reanimated" |
react-native-gesture-handler |
手势 | 与 reanimated 配合 |
react-native-safe-area-context |
安全区域 | import { useSafeAreaInsets } from "react-native-safe-area-context" |
tailwindcss + tailwind-merge |
样式 | className 方式,import { twMerge } from "tailwind-merge" |
sonner-native |
Toast 提示 | import { toast } from "sonner-native" |
@gorhom/bottom-sheet |
底部弹出面板 | |
expo-image |
图片组件 | import { Image } from "expo-image" |
样式约定:项目使用 NativeWind(Tailwind CSS),className 方式写样式,比如 className="bg-white rounded-xl shadow-lg"。不要写内联 style={{}} 除非必要。
核心开发规范
规则 0:写页面前必须先读 package.json
每次写新页面或新组件之前,必须先用 Read 工具读取 app-rn/package.json,了解项目已有的依赖库,确保:
- 使用项目已有的组件库和工具库,不要引入未安装的包
- 了解可用的第三方组件(如 heroui-native、@legendapp/list 等),优先复用
规则 1:禁止新增专用 API 接口
项目已有 4 个通用数据 API,所有 CRUD 操作必须复用它们,不得再创建专用 REST 端点:
| 接口 | 路径 | 用途 |
|---|---|---|
| loadData | POST /data/loadData |
查询:传入 view_name(表/视图名)、search_condition(WHERE)、order_by、search_columns、args |
| saveData | POST /data/saveData |
增删改:传入 [{table_name, key_field, inserts, updates, deletes}] 数组 |
| loadDataBySql | POST /data/loadDataBySql |
原生 SQL:传入 sql 和 args |
| getUniqueId | POST /data/getUniqueId |
获取雪花 ID:传入 count(1-100) |
新增接口必须满足两个条件:
- 先向用户确认是否允许新增接口
- 新接口必须是通用接口(类似上面四种),不能专属于某个功能
前端调用示例:
// 查询
import { loadDataApi, saveDataApi, getUniqueIdApi } from "@/request/api";
const res = await loadDataApi({
view_name: "b_user",
search_condition: "id = ?",
search_columns: [],
order_by: "",
args: [userId],
});
// 插入
const idRes = await getUniqueIdApi(1);
await saveDataApi([{
table_name: "b_user",
key_field: "id",
inserts: [{ id: idRes.data[0], nickname: "test", ... }],
updates: [],
deletes: [],
}]);
规则 2:数据库表必须包含审计字段
所有业务表的必需列:
id BIGINT PRIMARY KEY, -- 雪花 ID
create_time TIMESTAMPTZ NOT NULL, -- 创建时间
update_time TIMESTAMPTZ NOT NULL, -- 更新时间
create_by BIGINT, -- 创建者 user_id
update_by BIGINT -- 更新者 user_id
后端 pkg/db/meta_field.go 中的 applyMetaFields 函数会为 insert/update 自动注入这些字段。
只有 b_user 和 b_user_oauth 两类系统表在 auditExcludeTables 中豁免,其他新表一律不要加豁免。
ID 获取方式:
- 前端:调用
getUniqueIdApi(count)获取 - 后端:调用
uniqueid.NextId()获取
规则 3:不要重复造轮子
- 前端 API 请求:统一使用
@/request/api中已有的方法,不要创建新的 API 封装 - 前端状态管理:优先复用已有的 Zustand store(
@/store/),新 Store 只用通用 API - 前端组件:
- UI 组件优先用
heroui-native(PressableFeedback、SearchField、Button、Spinner 等) - 长列表用
@legendapp/list(LegendList),不要用 FlatList/ScrollView 处理大量数据 - 图标统一用
lucide-react-native - Toast 用
sonner-native - 底部面板用
@gorhom/bottom-sheet - 样式用 NativeWind className,避免内联 style
- UI 组件优先用
- 后端:不要创建新的 handler 和路由,除非是通用基础设施
规则 4:家庭/空间为中心的设计
系统以家庭(Family)为核心实体。所有业务数据(记账、收藏等)都跟 family_id 关联。
用户通过 family_member 表关联到家庭。
关键文件索引
| 文件 | 说明 | 写页面必读 |
|---|---|---|
app-rn/package.json |
前端依赖清单,了解可用库 | ⭐ |
PROJECT_CONTEXT.md |
完整项目文档 | |
db.local.yml |
本地数据库连接备忘 | |
app-go/internal/router/router.go |
后端路由定义 | |
app-go/pkg/db/meta_field.go |
审计字段自动注入逻辑 | |
app-rn/src/request/api.ts |
前端 API 方法 | |
app-rn/src/store/ |
Zustand 状态管理 | |
app-rn/src/configs/pages.ts |
子应用列表配置 | |
app-rn/src/components/ |
通用组件 |
当前数据库表
b_user - 用户
b_user_oauth - 第三方登录绑定
b_family - 家庭/空间
b_family_member - 家庭成员
b_finance - 记账记录
b_finance_category - 记账分类
b_finance_category_default - 默认分类模板
b_app_favorite - 子应用收藏
b_family_feed - 家庭动态
视图:v_family_member(join b_family_member + b_user)
家庭动态(b_family_feed)记录规范
以下场景必须调用 recordFeed() 写入动态。
type 简码对照:join(加入/创建)、rename(改名)、bill(记账)、fav(收藏)
| 触发场景 | type | content 模板 | 触发位置 |
|---|---|---|---|
| 创建家庭 | join |
{nickname} 创建了家庭「{名称}」 |
family/entry.tsx handleCreate |
| 加入家庭 | join |
{nickname} 加入了家庭 |
family/entry.tsx handleJoin |
| 修改昵称 | rename |
{nickname} 修改了昵称为「{新昵称}」 |
family/user.tsx handleRename |
| 记账 | bill |
{nickname} 记了一笔「{备注}」¥{金额} |
finance/add/ 保存成功 |
| 收藏应用 | fav |
{nickname} 收藏了「{应用名}」 |
tools/index.tsx 收藏操作 |
⚠️ content 自包含原则:所有展示信息直接写在 content 字符串中,不依赖 meta 字段。recordFeed() 不接收 meta 参数,loadFeeds() 只做 {nickname} 替换。meta 列保留但始终为 NULL。
content 占位符规则:content 存储模板字符串,用户昵称用 {nickname} 占位,不在写入时拼接。loadFeeds() 会自动将 {nickname} 替换为 b_family_member.nickname 的最新值,确保改名后历史动态也显示最新昵称。
import { recordFeed } from "@/helpers/feed";
// 正确写法 — 用 {nickname} 占位符,不拼接昵称
recordFeed(familyId, userId, "join", "{nickname} 加入了家庭");
// 错误写法 — 禁止拼接昵称写死
recordFeed(familyId, userId, "join", `${nick} 加入了家庭`); // ❌
修改昵称时同步更新:family/user.tsx 的 handleRename 除了更新 b_user.name,还必须同步更新 b_family_member.nickname:
saveDataApi([
{ table_name: "b_user", updates: [{ id: uid, name: newName }] },
{ table_name: "b_family_member", updates: [{ id: memberId, nickname: newName }] },
]);
前端:拉取数据
import { loadDataApi } from "@/request/api";
// 查当前家庭的收藏
const res = await loadDataApi({
view_name: "b_app_favorite",
search_condition: "family_id = ?",
order_by: "id ASC",
search_columns: [],
args: [familyId],
});
if (res.isSuccess) {
const list = res.data; // any[]
}
前端:保存数据(新增)
import { saveDataApi, getUniqueIdApi } from "@/request/api";
const idRes = await getUniqueIdApi(1);
await saveDataApi([{
table_name: "b_app_favorite",
key_field: "id",
inserts: [{
id: idRes.data[0],
family_id: familyId,
user_id: userId,
app_id: "finance",
}],
updates: [],
deletes: [],
}]);
前端:保存数据(删除)
await saveDataApi([{
table_name: "b_app_favorite",
key_field: "id",
inserts: [],
updates: [],
deletes: [{ id: recordId }],
}]);
Python:连接数据库
import psycopg2
conn = psycopg2.connect(
'postgres://postgres:<password>@117.72.182.135:5432/allapp?sslmode=disable'
)
# 具体密码见 references/db-config.md