# 大数据研究院购物车跨端同步后端实现提示词

请实现“大数据研究院购物车跨端同步”后端功能。小程序端已经按本方案预接完成，后端上线后即可同步；后端没上线前小程序会本地兜底。

## 重要要求

1. 先只读检查现有迁移命名规则、表名/字段名风格、UUID 默认函数、API 路由挂载方式。
2. 不要盲目照抄 SQL。如果项目现有表名是无下划线风格、字段是驼峰映射或有自定义 `genrandomuuid()`，必须按项目现有风格实现。
3. 实现前先输出检查结论；确认风格后再改代码。
4. 不要改无关功能，不要动权限、env、支付逻辑。

## 目标

实现登录用户购物车在 Web 和小程序之间跨端同步。

当前小程序已按以下接口调用：

- `GET /api/account/cart`
- `POST /api/account/cart/sync`
- `POST /api/account/cart/items`
- `DELETE /api/account/cart/items/:id`
- `DELETE /api/account/cart`

请后端按这个协议实现。

## 一、数据库迁移

新增购物车表。迁移文件名请先检查现有规则，例如：

- 如果现有是 `068cartitems.sql` 风格，就用这个风格
- 如果现有是 `068_cart_items.sql` 风格，就用这个风格

建议表语义：

```sql
CREATE TABLE IF NOT EXISTS cart_items (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  product_id text NOT NULL,
  product_type text NOT NULL,
  quantity integer NOT NULL DEFAULT 1,
  client_snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  UNIQUE (user_id, product_id, product_type)
);

CREATE INDEX IF NOT EXISTS idx_cart_items_user_id ON cart_items(user_id);
```

注意：

- UUID 默认函数必须按项目现有迁移写法
- 表名/字段名必须按项目现有 SQL 查询风格
- 如果项目实际使用 `cartitems/userid/productid` 这类无下划线风格，也请按项目风格处理
- API 返回字段必须按下面 JSON 约定

只同步这些类型：

- `course`
- `event`
- `agent`
- `data`

不要同步：

- `expert`
- `consulting`
- `membership`

## 二、API 约定

所有接口都需要 Bearer token，使用现有 `requireUser` 鉴权。

### 1. GET `/api/account/cart`

返回当前用户购物车。

响应：

```json
{
  "items": [
    {
      "id": "cart_items.id",
      "productId": "course-xxx",
      "productType": "course",
      "quantity": 1,
      "snapshot": {
        "id": "小程序本地商品id",
        "type": "course",
        "title": "...",
        "subtitle": "...",
        "price": 99,
        "cover": "..."
      },
      "valid": true,
      "createdAt": "...",
      "updatedAt": "..."
    }
  ],
  "warnings": []
}
```

`valid` 规则：

- 查 `products` 表
- `products.id = productId`
- `status = 'published'` 时 `valid=true`
- 不存在或下架时 `valid=false`

### 2. POST `/api/account/cart/items`

添加购物车项，要求幂等。

请求：

```json
{
  "productId": "course-xxx",
  "productType": "course",
  "quantity": 1,
  "snapshot": {
    "id": "course-xxx",
    "type": "course",
    "title": "...",
    "subtitle": "...",
    "price": 99,
    "cover": "..."
  }
}
```

逻辑：

1. 校验 `productType` 只能是 `course/event/agent/data`
2. 校验 `products.id = productId` 且 `status='published'`
3. 已存在则更新 `client_snapshot`、`quantity`、`updated_at`
4. 返回当前用户全量购物车 `{ items, warnings }`

### 3. DELETE `/api/account/cart/items/:id`

按购物车行 ID 删除，不是按商品 ID 删除。

要求：

- `:id` 是 `cart_items.id`
- 必须校验该 item 属于当前 user
- 删除不存在也可以返回成功，保持幂等
- 返回当前用户全量购物车 `{ items, warnings }`

### 4. DELETE `/api/account/cart`

清空当前用户购物车。

响应：

```json
{
  "items": [],
  "warnings": [],
  "deleted": 3
}
```

### 5. POST `/api/account/cart/sync`

登录后合并本地购物车到服务器。

请求：

```json
{
  "items": [
    {
      "productId": "course-xxx",
      "productType": "course",
      "quantity": 1,
      "snapshot": {
        "id": "course-xxx",
        "type": "course",
        "title": "...",
        "subtitle": "...",
        "price": 99,
        "cover": "..."
      }
    }
  ]
}
```

逻辑：

1. 过滤 `productType` 不在 `course/event/agent/data` 的项，并写入 warnings
2. 校验商品是否 published，不合法或下架的项不要插入，写入 warnings
3. 对合法项执行 upsert：
   - conflict key：当前用户 + productId + productType
   - 已存在则更新 `client_snapshot`、`quantity`、`updated_at`
4. 最后返回当前用户全量购物车 `{ items, warnings }`

## 三、代码位置建议

请先确认真实项目路径和运行 compose 目录。

候选文件：

- `backend/db/migrations/...`
- `backend/src/lite/business-pg.mjs`
- `backend/src/lite/api.mjs`

建议新增后端函数：

- `getAccountCart(token)`
- `addAccountCartItem(token, payload)`
- `removeAccountCartItem(token, cartItemId)`
- `clearAccountCart(token)`
- `syncAccountCart(token, payload)`

并在 API 层挂路由。

## 四、必须兼容小程序当前实现

小程序当前逻辑：

- 本地 `CartItem.id` 仍是商品 ID，用于结算
- 后端返回的购物车行 ID 会保存到本地 `cartItemId`
- 删除会调用：`DELETE /api/account/cart/items/:cartItemId`
- 添加/同步传字段名：`snapshot`
- 如果接口 404/405，小程序会自动回退本地购物车

所以后端返回字段必须至少包含：

```json
{
  "id": "cart_items.id",
  "productId": "...",
  "productType": "...",
  "quantity": 1,
  "snapshot": {},
  "valid": true
}
```

## 五、验收要求

完成后请输出：

1. 实际新增迁移文件名
2. 实际数据库表名和字段名
3. 实际 UUID 默认函数
4. 修改了哪些文件
5. `node --check` 结果
6. 迁移执行结果
7. backend 容器重建/重启结果
8. `/api/health` 是否 200

请用有效 token 验证以下接口：

```bash
GET /api/account/cart
POST /api/account/cart/sync
POST /api/account/cart/items
DELETE /api/account/cart/items/:id
DELETE /api/account/cart
```

验收场景：

- Web 或小程序添加 `course/event/agent/data` 后，另一个端登录能看到
- 删除后另一个端刷新消失
- 下单成功后购物车清空
- `expert/consulting/membership` 不进入同步购物车
- 下架商品不会被插入，返回 warnings
- 删除接口确认使用的是购物车行 ID，不是商品 ID

## 六、注意

不要把后端购物车行 ID 当成商品 ID。

商品 ID 继续用于 checkout/order：

- `productId`
- `productType`

购物车行 ID 只用于删除：

- `DELETE /api/account/cart/items/:id`
