# 萌芽小店 · 后端(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`** 为最终准据;本文随版本迭代更新。