# 萌芽小店 · 后端 基于 **Go + Gin + GORM** 构建的 RESTful API 服务,负责商品管理、订单处理、用户认证、聊天消息等核心业务。 ## 技术依赖 | 包 | 版本 | 用途 | |----|------|------| | gin | v1.9 | HTTP 路由框架 | | gorm | v1.31 | ORM | | gorm/driver/mysql | v1.6 | MySQL 驱动 | | go-sql-driver/mysql | v1.9 | 底层 MySQL 连接 | | gin-contrib/cors | latest | CORS 中间件 | | google/uuid | latest | UUID 生成 | ## 目录结构 ``` mengyastore-backend/ ├── main.go # 程序入口,路由注册 ├── cmd/ │ └── migrate/ │ └── main.go # 一次性 JSON→MySQL 数据迁移脚本 ├── data/ │ └── json/ │ └── settings.json # 服务配置(adminToken、DSN 等) ├── internal/ │ ├── config/ │ │ └── config.go # 配置加载 │ ├── database/ │ │ ├── db.go # GORM 初始化 + AutoMigrate │ │ └── models.go # 数据库行结构体(GORM 模型) │ ├── models/ │ │ ├── product.go # 业务模型 Product │ │ ├── order.go # 业务模型 Order │ │ └── chat.go # 业务模型 ChatMessage │ ├── storage/ │ │ ├── jsonstore.go # 商品存储(GORM 实现) │ │ ├── orderstore.go # 订单存储 │ │ ├── sitestore.go # 站点设置存储 │ │ ├── wishliststore.go # 收藏夹存储 │ │ └── chatstore.go # 聊天消息存储(含内存级频率限制) │ ├── handlers/ │ │ ├── admin.go # AdminHandler 结构体 + requireAdmin │ │ ├── admin_product.go # 商品 CRUD 接口 │ │ ├── admin_site.go # 维护模式接口 │ │ ├── admin_orders.go # 订单管理接口 │ │ ├── admin_chat.go # 管理员聊天接口 │ │ ├── public.go # 公开接口(商品列表、浏览量) │ │ ├── order.go # 下单、确认订单接口 │ │ ├── stats.go # 统计信息接口 │ │ ├── wishlist.go # 收藏夹接口(用户) │ │ └── chat.go # 聊天接口(用户) │ └── auth/ │ └── sproutgate.go # SproutGate OAuth 客户端 ``` ## API 路由一览 ### 公开接口 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/health` | 健康检查 | | GET | `/api/products` | 获取商品列表(仅 active) | | POST | `/api/products/:id/view` | 记录商品浏览量 | | GET | `/api/stats` | 获取总订单数和总访问量 | | POST | `/api/site/visit` | 记录站点访问 | | GET | `/api/site/maintenance` | 获取维护状态 | | POST | `/api/checkout` | 创建订单(生成支付二维码) | | GET | `/api/orders` | 获取当前用户订单(需 Bearer token) | | POST | `/api/orders/:id/confirm` | 确认付款(触发发货) | ### 收藏夹(需登录) | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/wishlist` | 获取收藏商品 ID 列表 | | POST | `/api/wishlist` | 添加收藏 | | DELETE | `/api/wishlist/:id` | 取消收藏 | ### 聊天(需登录) | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/chat/messages` | 获取自己的聊天记录 | | POST | `/api/chat/messages` | 发送消息(1 秒频率限制) | ### 管理员接口(需 `?token=xxx` 或 `Authorization: `) | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/admin/token` | 获取令牌(用于验证) | | GET | `/api/admin/products` | 获取全部商品(含卡密) | | POST | `/api/admin/products` | 创建商品 | | PUT | `/api/admin/products/:id` | 编辑商品 | | PATCH | `/api/admin/products/:id/status` | 切换上下架 | | DELETE | `/api/admin/products/:id` | 删除商品 | | POST | `/api/admin/site/maintenance` | 设置维护模式 | | GET | `/api/admin/orders` | 获取全部订单 | | DELETE | `/api/admin/orders/:id` | 删除订单 | | GET | `/api/admin/chat` | 获取全部用户对话 | | GET | `/api/admin/chat/:account` | 获取指定用户对话 | | POST | `/api/admin/chat/:account` | 管理员回复 | | DELETE | `/api/admin/chat/:account` | 清除对话 | ## 数据库表结构 ### products | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(36) | UUID 主键 | | name | varchar(255) | 商品名称 | | price | double | 原价 | | discount_price | double | 折扣价(0 = 无折扣)| | tags | json | 标签数组 | | cover_url | varchar(500) | 封面图 URL | | screenshot_urls | json | 截图 URL 数组(最多 5 张)| | verification_url | varchar(500) | 验证链接 | | description | text | Markdown 描述 | | active | tinyint(1) | 是否上架 | | require_login | tinyint(1) | 是否必须登录购买 | | max_per_account | bigint | 每账户最大购买数(0=不限)| | total_sold | bigint | 累计销量 | | view_count | bigint | 累计浏览量 | | delivery_mode | varchar(20) | 发货模式:`auto` / `manual` | | show_note | tinyint(1) | 下单时显示备注输入框 | | show_contact | tinyint(1) | 下单时显示联系方式输入框 | | created_at | datetime(3) | 创建时间 | ### product_codes | 字段 | 类型 | 说明 | |------|------|------| | id | bigint unsigned | 自增主键 | | product_id | varchar(36) | 关联商品 ID(索引)| | code | text | 卡密内容 | ### orders | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(36) | UUID 主键 | | product_id | varchar(36) | 商品 ID(索引)| | product_name | varchar(255) | 商品名称快照 | | user_account | varchar(255) | 用户账号(可空,匿名)| | user_name | varchar(255) | 用户昵称 | | quantity | bigint | 购买数量 | | delivered_codes | json | 已发放卡密 | | status | varchar(20) | `pending` / `completed` | | delivery_mode | varchar(20) | `auto` / `manual` | | note | text | 用户备注 | | contact_phone | varchar(50) | 联系手机号 | | contact_email | varchar(255) | 联系邮箱 | | created_at | datetime(3) | 下单时间 | ### site_settings 键值对存储,当前使用的键: | Key | 说明 | |-----|------| | `totalVisits` | 总访问量 | | `maintenance` | 维护模式(`true` / `false`)| | `maintenanceReason` | 维护原因文本 | ### wishlists | 字段 | 类型 | 说明 | |------|------|------| | id | bigint unsigned | 自增主键 | | account_id | varchar(255) | 用户账号(唯一索引)| | product_id | varchar(36) | 商品 ID(联合唯一)| ### chat_messages | 字段 | 类型 | 说明 | |------|------|------| | id | varchar(36) | UUID 主键 | | account_id | varchar(255) | 用户账号(索引)| | account_name | varchar(255) | 用户昵称 | | content | text | 消息内容 | | sent_at | datetime(3) | 发送时间 | | from_admin | tinyint(1) | 是否来自管理员 | ## 配置文件 `data/json/settings.json`: ```json { "adminToken": "你的管理员令牌", "authApiUrl": "https://auth.api.shumengya.top", "databaseDsn": "" } ``` `databaseDsn` 为空时自动使用测试数据库。也可以通过环境变量 `DATABASE_DSN` 覆盖。 ## 发货逻辑 ### 自动发货(`deliveryMode = "auto"`) 1. `POST /api/checkout` → 从 `product_codes` 提取指定数量的卡密 2. 商品 `quantity` 减少,卡密从数据库删除 3. 卡密保存到订单 `delivered_codes` 4. 用户 `POST /api/orders/:id/confirm` 确认付款后,订单状态变为 `completed`,响应中返回卡密内容 5. 同时调用 `IncrementSold` 增加销量统计 ### 手动发货(`deliveryMode = "manual"`) 1. `POST /api/checkout` → 创建订单,不提取卡密 2. 用户 `POST /api/orders/:id/confirm` 后,订单变为 `completed`,但 `delivered_codes` 为空 3. 管理员在后台查看订单的备注、手机号、邮箱后手动发货 ## 本地开发 ```bash go run . # 启动服务(默认 :8080) go build -o mengyastore-backend.exe . # 构建可执行文件 go run ./cmd/migrate/main.go # 迁移旧 JSON 数据到数据库 ``` ### 切换数据库 ```bash # 测试库(默认) # host: 10.1.1.100:3306 / db: mengyastore-test # 生产库 set DATABASE_DSN=mengyastore:mengyastore@tcp(192.168.1.100:3306)/mengyastore?charset=utf8mb4&parseTime=True&loc=Local ./mengyastore-backend.exe ``` ## 认证说明 ### 用户认证 通过 SproutGate OAuth 服务验证 Bearer Token: ```go result, err := authClient.VerifyToken(token) // result.Valid, result.User.Account, result.User.Username ``` ### 管理员认证 管理员令牌通过查询参数或 Authorization 头传入: ``` GET /api/admin/products?token=xxx Authorization: xxx ``` 令牌与 `settings.json` 中的 `adminToken` 比对。