# 萌芽小店 · 后端（mengyastore-backend-go）

基于 **Go + Gin + GORM** 的 REST API：商品与**发货方式**、订单、SproutGate 用户校验、站点设置、聊天、收藏夹；可选 **RabbitMQ** 发送订单邮件、可选 **Redis**；管理端提供 **系统状态** 聚合接口。

## 技术依赖（核心）

| 包 | / 用途 |
|----|--------|
| gin | HTTP 路由 |
| gorm.io/gorm + driver/mysql | MySQL |
| gin-contrib/cors | CORS |
| google/uuid | 订单 ID |
| github.com/rabbitmq/amqp091-go | RabbitMQ（可选） |
| github.com/redis/go-redis/v9 | Redis 探测（可选） |
| github.com/joho/godotenv | `.env.development`（或 `ENV_FILE`）加载 |

## 目录结构（与仓库一致）

```
mengyastore-backend-go/
├── main.go                 # 路由注册、MQ 生命周期
├── docker-compose.yml
├── init.sql                # 可选：建库参考
├── cmd/migrate/main.go     # JSON → MySQL 历史数据迁移
├── internal/
│   ├── auth/sproutgate.go
│   ├── cache/              # Redis 等扩展
│   ├── config/config.go    # 环境变量与默认值（APP_ENV、DSN、MQ、Redis…）
│   ├── database/db.go      # Open + AutoMigrate
│   ├── database/models.go  # GORM 表模型
│   ├── email/
│   ├── mq/                 # 连接、拓扑、Publish、Consumer、断线重连
│   ├── models/             # JSON 业务模型
│   ├── storage/            # Product / Order / Site / Wishlist / Chat
│   └── handlers/
│       ├── public.go
│       ├── order.go
│       ├── stats.go
│       ├── wishlist.go
│       ├── chat.go
│       ├── admin.go
│       ├── admin_product.go
│       ├── admin_orders.go
│       ├── admin_site.go
│       ├── admin_chat.go
│       └── admin_status.go   # GET /api/admin/system-status
```

## API 路由一览

### 公开

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/health` | 健康检查（可含 rabbitmq 状态） |
| GET | `/api/products` | 上架商品列表（无卡密、无 `fixedContent`） |
| POST | `/api/products/:id/view` | 记录浏览 |
| GET | `/api/stats` | 订单数、访问量等 |
| POST | `/api/site/visit` | 站点访问计数 |
| GET | `/api/site/maintenance` | 维护状态 |
| POST | `/api/checkout` | 创建订单：`paymentMethod` 默认 `mengya`，见下文「萌芽支付」 |
| GET | `/api/orders/:id/payment-status` | 结账轮询：`status`、`paymentExpiresAt`（不回卡密） |
| POST | `/api/webhooks/mengya-pay` | 到账通知 Webhook：`X-Webhook-Secret` 与配置一致时可省略 |
| GET | `/api/orders` | 当前用户订单（Bearer） |
| POST | `/api/orders/:id/confirm` | 确认订单（返回发货内容等） |

### 收藏夹（Bearer）

| GET | `/api/wishlist` |
| POST | `/api/wishlist` |
| DELETE | `/api/wishlist/:id` |

### 聊天（Bearer）

| GET | `/api/chat/messages` |
| POST | `/api/chat/messages` |

### 管理端（`X-Admin-Token` / `Authorization` / `?token=`）

| POST | `/api/admin/verify` | 校验 token，返回 `valid` |
| GET/POST | `/api/admin/products` … | 商品 CRUD（见 payload 含 `fulfillmentType`、`fixedContent`） |
| PUT | `/api/admin/products/:id` |
| PATCH | `/api/admin/products/:id/status` |
| DELETE | `/api/admin/products/:id` |
| POST | `/api/admin/site/maintenance` |
| GET/POST | `/api/admin/site/smtp` |
| GET | `/api/admin/orders` |
| DELETE | `/api/admin/orders/:id` |
| GET … POST … DELETE | `/api/admin/chat`… | 会话与回复 |
| **GET** | **`/api/admin/system-status`** | **backend / mysql / redis / rabbitmq 摘要** |

> 文档中若出现已废弃的 `GET /api/admin/token`，以仓库 `main.go` 为准。

## 数据库表（字段摘要）

### products

| 字段 | 说明 |
|------|------|
| fulfillment_type | `card` 卡密逐条；`fixed` 固定内容（不限库存） |
| fixed_content | 固定发货正文（网盘链接、说明等）；公开接口不返回 |
| payment_qr_urls | JSON 数组：`paymentQrUrls`，萌芽支付收款码图片直链（最多 10），管理端填写 |
| delivery_mode | 订单维度习惯字段：`auto` / `manual`（与 fulfillment 独立） |
| 其余 | name, price, discount_price, tags, cover_url, screenshot_urls, description, active, require_login, max_per_account, total_sold, view_count, show_note, show_contact … |

### product_codes

卡密一行一条；`fulfillment_type = fixed` 时可为空。

### orders

含 `delivered_codes`（JSON）、`notify_email`、`delivery_mode`、支付方式字段 `payment_method`、`payment_expected_total`、`payment_expires_at`。**`status`** 含：`pending`、`pending_payment`（萌芽待到账）、`completed`、`cancelled`（超时未付释放库存）等。

### site_settings

KV：如 `totalVisits`、`maintenance`、`smtpHost`、`smtpPassword` 等；缺失键时 `sitestore.get` 不报错（不向日志刷 ErrRecordNotFound：`Find` 替代 `First`）。

## 配置说明

配置由 **`config.Load()`** 从**进程环境变量**读取。未设置 **`ENV_FILE`** 时，会在当前工作目录尝试加载 **`./.env.development`**（文件不存在则跳过，不报错）。生产或其它环境请用 **`ENV_FILE=/path/to/.env.production`** 等方式显式指定文件。

要点：

- `DATABASE_DSN` 为空时按 `APP_ENV` 使用内建测试/生产默认 DSN（仅开发便利）。
- RabbitMQ、Redis 开关与地址见 `internal/config/config.go` 注释。
- `HTTP_LISTEN_ADDR`、`PUBLIC_API_BASE_URL` 供系统状态 JSON 展示。
- `WEBHOOK_MENGYA_SECRET`：若设置，到账 Webhook 须在请求头带相同 `X-Webhook-Secret`。
- `PAYMENT_PENDING_SECONDS`：萌芽支付待支付窗口（秒，默认 60）。

### API 文档（Swagger UI）开关

OpenAPI 规范由 **`swag`** 从代码注释生成（`docs/swagger.json`；重生成见仓库根目录 **`Makefile`** 的 `swagger` 目标）。运行期是否挂载 UI 由配置决定：

| 变量 | 说明 |
|------|------|
| **`ENABLE_SWAGGER`** | 为 `1` / `true` / `yes` / `on`（不区分大小写）时，注册 **`GET /swagger/*`**，浏览器打开 **`/swagger/index.html`**。 |
| **`GIN_DEBUG`** | 为真时除 Gin Debug 模式外，也会**开启** Swagger UI（与 `ENABLE_SWAGGER` 二选一满足即可）。 |

生产环境若未设置上述变量，默认**不**暴露 Swagger，减少接口探测面。

## 萌芽支付（Webhook 核销）

- 结账 `paymentMethod: mengya`（默认）且**非免费**：预留卡密或固定交付内容，`status=pending_payment`，写入 `payment_expected_total`、`payment_expires_at`（UTC）；**此时不计销量**，到账后再 `completed` 并 `IncrementSold` + 邮件。
- **`POST /api/webhooks/mengya-pay`**：JSON 体与 `test/server.py` 打印结构兼容，从 `body.notice` / `notice` 等字段抽取 `到账¥x.xx` 金额，与待支付订单快照匹配（FIFO，`ABS` 容差）。
- **等额归因**：多笔同额待支付可能被核销到最早一单，需业务规避或后续增强。
- 超时：后台定时扫描将订单 `cancelled` 并退回卡密库存（固定内容类仅清空订单预留）。
- 旧版 `paymentMethod=legacy_manual` 等请求已停用（返回 400）。

## 发货与订单逻辑

### fulfillment_type = `card`（默认）

1. `POST /api/checkout` 校验库存 ≥ 购买数量（**萌芽支付**下为预留待付，见上文）。
2. 从 `product_codes` 取出对应条数写入订单 `delivered_codes`，并 `Update` 商品去掉已发码。
3. 销量 `IncrementSold`（**免费单**在结账时计入；**付费萌芽支付**在 Webhook 核销后 `IncrementSold`）；邮件/MQ 通知。

### fulfillment_type = `fixed`

1. 不校验卡密条数；不修改 `product_codes`。
2. 将 `fixed_content` 复制 `quantity` 次填入 `delivered_codes`（与多件购买展示一致）。
3. 仍 `IncrementSold`（规则同 card）；邮件/MQ 同自动发货路径。

### delivery_mode（订单）

业务上仍可区分用户确认后展示「等待人工发货」等；与 `fulfillment_type` 正交。

## RabbitMQ 说明

- 启用时声明交换机/队列/绑定；订单邮件可 `Publish`，失败降级直发 SMTP。
- **504 channel closed** 等可回收错误：客户端会 **重连** 并在成功后 **重启消费协程**（见 `internal/mq/client.go`）。

## 本地开发

在 **`mengyastore-backend-go`** 目录放置 **`.env.development`**（可被 git 忽略），或导出环境变量后再启动。

```bash
go run .
go build -o mengyastore-backend.exe .
go run ./cmd/migrate/main.go   # 按需迁移旧 JSON；同样读取 .env.development 或 ENV_FILE
```

---

维护时请以 **`main.go` 与 `internal/database/models.go`** 为最终准据；本文随版本迭代更新。
