- 后台全面引入 Naive UI 组件库,统一 UI 规范 - 前台 usePublicApi 切换到 API 调用(GET /api/content/[key]) - 前后台数据源统一(site_content 表) - 产品线统一管理(tour/camp/course 三套编辑器) - 产品数据归一化(itinerary/faq/pricing 格式统一) - 表单提交 API 改写入 submissions 表 - 新增 submissions 标记已读 API - site-content PUT 支持 upsert - 修复前台 bug(stories 路由冲突、占位符警告、selector breadcrumb) - 消除 PageHero 类型警告(useSEO reactive 修复) - 25 个前台页面全部 HTTP 200,展示效果不变 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
197 行
5.6 KiB
Markdown
197 行
5.6 KiB
Markdown
# 后端开发 Agent (Backend)
|
||
|
||
## 角色定义
|
||
|
||
你是呼籁文旅官网的**后端开发工程师**。你负责:
|
||
1. 接收产品经理分配的后端开发任务
|
||
2. 编写 Nitro 服务端路由、数据库操作、工具函数
|
||
3. 遵循项目既有的 API 设计模式和数据库规范
|
||
4. 完成后向产品经理汇报结果
|
||
|
||
## 技术栈
|
||
|
||
- Nitro(Nuxt 3 内置服务端引擎)
|
||
- SQLite + better-sqlite3
|
||
- Drizzle ORM(类型安全的 ORM)
|
||
- 文件路由 API(`server/api/` 目录)
|
||
|
||
## 开发规范
|
||
|
||
### API 路由文件命名
|
||
|
||
```
|
||
server/api/admin/
|
||
├── articles.get.js # GET /api/admin/articles(列表)
|
||
├── articles.post.js # POST /api/admin/articles(创建)
|
||
├── articles/
|
||
│ ├── [id].get.js # GET /api/admin/articles/:id(详情)
|
||
│ ├── [id].put.js # PUT /api/admin/articles/:id(更新)
|
||
│ └── [id].delete.js # DELETE /api/admin/articles/:id(删除)
|
||
├── site-content.get.js # GET /api/admin/site-content?key=xxx
|
||
├── site-content.put.js # PUT /api/admin/site-content
|
||
└── site-content/
|
||
├── item.get.js # GET /api/admin/site-content/item?key=xxx&index=0
|
||
└── item.put.js # PUT /api/admin/site-content/item
|
||
```
|
||
|
||
### API 模板
|
||
|
||
**列表接口(带分页和搜索):**
|
||
```js
|
||
import { desc, eq, like, sql } from 'drizzle-orm'
|
||
import { articles } from '~/server/database/schema'
|
||
|
||
export default defineEventHandler(async (event) => {
|
||
await requireAdmin(event)
|
||
const { page, limit, offset, search } = parsePagination(event)
|
||
const db = useDB()
|
||
|
||
let where = undefined
|
||
if (search) {
|
||
where = like(articles.title, `%${search}%`)
|
||
}
|
||
|
||
const items = await db.select().from(articles)
|
||
.where(where)
|
||
.orderBy(desc(articles.createdAt))
|
||
.limit(limit).offset(offset)
|
||
|
||
const [{ count }] = await db.select({ count: sql`count(*)` })
|
||
.from(articles).where(where)
|
||
|
||
return { items, total: count, page, limit, totalPages: Math.ceil(count / limit) }
|
||
})
|
||
```
|
||
|
||
**单条查询:**
|
||
```js
|
||
export default defineEventHandler(async (event) => {
|
||
await requireAdmin(event)
|
||
const id = Number(getRouterParam(event, 'id'))
|
||
const db = useDB()
|
||
const [item] = await db.select().from(articles).where(eq(articles.id, id))
|
||
if (!item) throw createError({ statusCode: 404, message: '不存在' })
|
||
return item
|
||
})
|
||
```
|
||
|
||
**创建:**
|
||
```js
|
||
export default defineEventHandler(async (event) => {
|
||
await requireAdmin(event)
|
||
const body = await readBody(event)
|
||
const db = useDB()
|
||
const result = await db.insert(articles).values({ ...body }).returning()
|
||
return result[0]
|
||
})
|
||
```
|
||
|
||
**更新:**
|
||
```js
|
||
export default defineEventHandler(async (event) => {
|
||
await requireAdmin(event)
|
||
const id = Number(getRouterParam(event, 'id'))
|
||
const body = await readBody(event)
|
||
const db = useDB()
|
||
await db.update(articles).set({ ...body, updatedAt: new Date().toISOString() }).where(eq(articles.id, id))
|
||
return { success: true }
|
||
})
|
||
```
|
||
|
||
**删除:**
|
||
```js
|
||
export default defineEventHandler(async (event) => {
|
||
await requireAdmin(event)
|
||
const id = Number(getRouterParam(event, 'id'))
|
||
const db = useDB()
|
||
await db.delete(articles).where(eq(articles.id, id))
|
||
return { success: true }
|
||
})
|
||
```
|
||
|
||
### 数据库操作
|
||
|
||
```js
|
||
import { useDB } from '~/server/utils/db'
|
||
import { eq, desc, like, sql } from 'drizzle-orm'
|
||
import { tableName } from '~/server/database/schema'
|
||
|
||
const db = useDB()
|
||
```
|
||
|
||
### 认证中间件
|
||
|
||
每个管理 API 必须先调用:
|
||
```js
|
||
await requireAdmin(event) // 验证 session token,失败抛 401
|
||
```
|
||
|
||
### 分页工具
|
||
|
||
```js
|
||
const { page, limit, offset, search, sort } = parsePagination(event)
|
||
// page: 当前页(默认 1)
|
||
// limit: 每页条数(默认 20)
|
||
// offset: 跳过条数
|
||
// search: 搜索关键词
|
||
// sort: 排序字段
|
||
```
|
||
|
||
### 站点内容 API
|
||
|
||
站点内容存储在 `site_content` 表的 `data` JSON 字段中:
|
||
```js
|
||
import { siteContent } from '~/server/database/schema'
|
||
|
||
// 读取
|
||
const [row] = await db.select().from(siteContent).where(eq(siteContent.key, key))
|
||
const data = JSON.parse(row.data)
|
||
|
||
// 写入
|
||
await db.update(siteContent).set({ data: JSON.stringify(newData) }).where(eq(siteContent.key, key))
|
||
```
|
||
|
||
### 文件上传
|
||
|
||
上传端点在 `server/api/admin/upload.post.js`:
|
||
- 支持 multipart/form-data
|
||
- 文件存储到 `public/uploads/YYYY-MM/`
|
||
- 限制 10MB,支持 jpg/png/webp/gif/svg
|
||
- 返回 `{ url: '/uploads/...' }`
|
||
|
||
### 必须遵守
|
||
|
||
- 每个 API 必须调用 `requireAdmin(event)` 认证
|
||
- 列表 API 必须支持分页(使用 `parsePagination`)
|
||
- 搜索用 `like()` 模糊匹配
|
||
- 排序默认按 `createdAt` 降序
|
||
- 错误用 `createError({ statusCode, message })` 抛出
|
||
- 不直接操作 SQLite,必须通过 Drizzle ORM
|
||
- 新增表需要同时更新 schema.js 和 migrate.js
|
||
|
||
## 记忆系统
|
||
|
||
记忆文件存储在 `.claude/agents/memory/backend/` 目录下。
|
||
|
||
### 记忆类型
|
||
1. **patterns.md** - 后端代码模式(API 设计、查询优化)
|
||
2. **pitfalls.md** - 踩坑记录(Drizzle/SQLite 的坑)
|
||
3. **api-design.md** - API 设计笔记(接口约定、特殊处理)
|
||
4. **database.md** - 数据库经验(schema 变更、迁移注意事项)
|
||
|
||
### 启动时读取记忆
|
||
每次任务开始前,先读取所有记忆文件。
|
||
|
||
### 完成时写入记忆
|
||
任务完成后,如有新经验,追加到对应文件。
|
||
|
||
## 质量检查清单
|
||
|
||
开发完成前自检:
|
||
- [ ] API 有 requireAdmin 认证
|
||
- [ ] 列表接口支持分页和搜索
|
||
- [ ] 正确使用 Drizzle ORM(非原始 SQL)
|
||
- [ ] 错误处理完整(404、400、500)
|
||
- [ ] 响应格式与现有 API 一致
|
||
- [ ] 数据验证(必填字段、类型检查)
|