diff --git a/.gitignore b/.gitignore index 4c12a01..e2bb856 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ debug-logs/ node_modules/ +vendor/ build/ dist/ coverage/ diff --git a/API_DOCS.md b/API_DOCS.md deleted file mode 100644 index 7b3d087..0000000 --- a/API_DOCS.md +++ /dev/null @@ -1,543 +0,0 @@ -# 萌芽账户认证中心 API 文档 - -访问 **`GET /`** 或 **`GET /api`**(无鉴权)可得到 JSON 格式的简要说明(服务名、版本、`/api/docs` 与 `/api/health` 入口、路由前缀摘要)。 - -接入地址: -- 统一登录前端:`https://auth.shumengya.top` -- 后端 API:`https://auth.api.shumengya.top` -- 本地开发 API:`http://:8080` - -对外接入建议: -1. 第三方应用按钮跳转到统一登录前端。 -2. 登录成功后回跳到业务站点。 -3. 业务站点使用回跳带回的 `token` 调用后端 API。 - -示例按钮: -```html - - 使用萌芽统一账户认证登录 - -``` - -回跳说明: -- 用户已登录时,统一登录前端会提示“继续授权”或“切换账号”。 -- 登录成功后会回跳到 `redirect_uri`(或 `return_url`),并在 URL **`#fragment`**(哈希)中带上令牌与用户信息(见下表)。 -- 第三方应用拿到 `token` 后,建议调用 **`POST /api/auth/verify`**(无副作用、适合网关鉴权)或 **`GET /api/auth/me`**(会更新访问记录,适合业务拉全量资料)校验并解析用户身份。 - -### 统一登录前端:查询参数 - -| 参数 | 必填 | 说明 | -|------|------|------| -| `redirect_uri` | 与 `return_url` 至少其一 | 登录成功后的回跳地址,须进行 URL 编码;可为绝对 URL 或相对路径(相对路径相对统一登录站点解析)。 | -| `return_url` | 同上 | 与 `redirect_uri` 同义,二者都传时优先 `redirect_uri`。 | -| `state` | 否 | OAuth 风格透传字符串;回跳时原样写入哈希参数,供业务防 CSRF 或关联会话。 | -| `prompt` | 否 | 预留;前端可读,当前可用于将来扩展交互策略。 | -| `client_id` | 否 | 第三方应用稳定标识(字母数字开头,可含 `_.:-`,最长 64)。写入用户「应用接入记录」,并随登录请求提交给后端。 | -| `client_name` | 否 | 展示用名称(最长 128),与 `client_id` 配对;可选。 | - -### 回跳 URL:`#` 哈希参数 - -成功授权后,前端将使用 [`URLSearchParams`](https://developer.mozilla.org/zh-CN/docs/Web/API/URLSearchParams) 写入哈希,例如:`https://app.example.com/auth/callback#token=...&expiresAt=...&account=...&username=...&state=...`。 - -| 参数 | 说明 | -|------|------| -| `token` | JWT,调用受保护接口时放在请求头 `Authorization: Bearer `。 | -| `expiresAt` | 过期时间,RFC3339(与签发侧一致,当前默认为登录时起算 **7 天**)。 | -| `account` | 账户名(与 JWT `sub` 一致)。 | -| `username` | 展示用昵称,可能为空。 | -| `state` | 若登录请求携带了 `state`,则原样返回。 | - -业务站点回调页应用脚本读取 `location.hash`,解析后**仅在 HTTPS 环境**将 `token` 存于内存或安全存储,并尽快用后端 **`POST /api/auth/verify`** 校验(勿仅信任哈希中的明文字段)。 - -### 第三方后端接入建议 - -1. **仅信服务端**:回调页将 `token` 交给自有后端,由后端请求 `POST https:///api/auth/verify`(JSON body:`{"token":"..."}`),根据 `valid` 与 `user.account` 建立会话。 -2. **CORS**:浏览器直连 API 时须后端已配置 CORS(本服务默认允许任意 `Origin`);若从服务端发起请求则不受 CORS 限制。 -3. **令牌过期**:`verify` / `me` 返回 401 或 `verify` 中 `valid:false` 时,应引导用户重新走统一登录。 - -## 认证与统一登录 - -### 登录获取统一令牌 -`POST /api/auth/login` - -请求: -```json -{ - "account": "demo", - "password": "demo123", - "clientId": "my-app", - "clientName": "我的应用" -} -``` - -`clientId` / `clientName` 可选;规则与请求头 `X-Auth-Client` / `X-Auth-Client-Name` 一致。传入且格式合法时,会在登录成功后写入该用户的 **应用接入记录**(见下文 `authClients`)。 - -响应: -```json -{ - "token": "jwt-token", - "expiresAt": "2026-03-14T12:00:00Z", - "user": { - "account": "demo", - "username": "示例用户", - "email": "demo@example.com", - "level": 0, - "sproutCoins": 10, - "secondaryEmails": ["demo2@example.com"], - "phone": "13800000000", - "avatarUrl": "https://example.com/avatar.png", - "websiteUrl": "https://example.com", - "bio": "### 简介", - "createdAt": "2026-03-14T12:00:00Z", - "updatedAt": "2026-03-14T12:00:00Z" - } -} -``` - -若账户已被管理员封禁,返回 **403**,且**不会签发 JWT**,响应示例: - -```json -{ - "error": "account is banned", - "banReason": "违规内容" -} -``` - -`banReason` 可能为空字符串或省略。 - -**常见 HTTP 状态码(登录)** - -| 状态码 | 含义 | -|--------|------| -| 200 | 成功,返回 `token`、`expiresAt`、`user`。 | -| 400 | 请求体非法或缺少 `account` / `password`。 | -| 401 | 账户不存在或密码错误(统一文案 `invalid credentials`)。 | -| 403 | 账户已封禁(见上文 JSON)。 | -| 500 | 服务器内部错误(读库、签发 JWT 失败等)。 | - -**JWT 概要**:算法 **HS256**;载荷含 `account`(与 `sub` 一致)、`iss`(见 `data/config/auth.json`)、`iat` / `exp`。客户端只需透传字符串,**勿在前端解析密钥**。 - -### 校验令牌 -`POST /api/auth/verify` - -请求: -```json -{ - "token": "jwt-token" -} -``` - -响应: -```json -{ - "valid": true, - "user": { "account": "demo", "...": "..." } -} -``` - -若账户已封禁,返回 **200** 且 `valid` 为 **false**(不返回 `user` 对象),示例: - -```json -{ - "valid": false, - "error": "account is banned", - "banReason": "违规内容" -} -``` - -令牌过期、签名错误、issuer 不匹配等解析失败时返回 **401**,示例:`{"valid": false, "error": "invalid token"}`。 - -`verify` 与 `me` 的取舍:**仅校验身份、不改变用户数据**时用 `verify`;需要最新资料、签到状态或写入「最后访问」时用 `GET /api/auth/me`(需 Bearer)。 - -**应用接入记录(可选)**:第三方在 **`POST /api/auth/verify`** 或 **`GET /api/auth/me`** 上携带请求头: - -- `X-Auth-Client`:应用 ID(格式同登录 JSON 的 `clientId`) -- `X-Auth-Client-Name`:可选展示名 - -校验成功且用户未封禁时,服务端会更新该用户 JSON 中的 `authClients` 数组(`clientId`、`displayName`、`firstSeenAt`、`lastSeenAt`)。**`POST /api/auth/verify` 的响应体 `user` 仍为 `Public()`,不含 `authClients`**,避免向调用方泄露用户在其他应用的接入情况;**`GET /api/auth/me`** 与管理员列表中的 `user`(`OwnerPublic`)**包含** `authClients`,用户可在统一登录前端的个人中心查看。 - -### 获取当前用户信息 -`GET /api/auth/me` - -请求头: -`Authorization: Bearer ` - -可选(由前端调用 `https://cf-ip-geo.smyhub.com/api` 等接口解析后传入,用于记录「最后访问 IP」与「最后显示位置」): -- `X-Visit-Ip`:客户端公网 IP(与地理接口返回的 `ip` 一致即可) -- `X-Visit-Location`:展示用位置文案(例如将 `geo.countryName`、`regionName`、`cityName` 拼接为 `中国 四川 成都`) - -**服务端回退(避免浏览器跨域导致头缺失)**:若未传 `X-Visit-Location`,后端会用 `X-Visit-Ip`;若也未传 `X-Visit-Ip`,则用连接的 `ClientIP()`(请在前置反向代理上正确传递 `X-Forwarded-For` 等,并在生产环境为 Gin 配置可信代理)。随后服务端请求 `GEO_LOOKUP_URL`(默认 `https://cf-ip-geo.smyhub.com/api?ip=`)解析展示位置并写入用户记录。 - -响应: -```json -{ - "user": { "account": "demo", "...": "..." }, - "checkIn": { - "rewardCoins": 1, - "checkedInToday": false, - "lastCheckInDate": "", - "lastCheckInAt": "", - "today": "2026-03-14" - } -} -``` - -> `user` 还会包含 `lastVisitAt`、`lastVisitDate`、`checkInDays`、`checkInStreak`、`visitDays`、`visitStreak` 等统计字段。 - -> 在登录用户本人、管理员列表等场景下,`user` 还可包含 `lastVisitIp`、`lastVisitDisplayLocation`(最近一次通过 `/api/auth/me` 上报的访问 IP 与位置文案)。**公开用户资料接口** `GET /api/public/users/:account` 与 **`POST /api/auth/verify` 的 `user` 中不包含这两项**(避免公开展示或第三方校验时令牌响应携带访问隐私)。 - -> 说明:密码不会返回。 - -若账户在登录后被封禁,持旧 JWT 调用 `GET /api/auth/me`、`PUT /api/auth/profile`、`POST /api/auth/check-in`、辅助邮箱等需登录接口时,返回 **403**,正文同登录封禁响应(`error` + 可选 `banReason`)。客户端应作废本地令牌。 - -### 每日签到 -`POST /api/auth/check-in` - -请求头: -`Authorization: Bearer ` - -响应: -```json -{ - "checkedIn": true, - "alreadyCheckedIn": false, - "rewardCoins": 1, - "awardedCoins": 1, - "message": "签到成功", - "user": { "account": "demo", "...": "..." } -} -``` - -### 更新当前用户资料 -`PUT /api/auth/profile` - -请求头: -`Authorization: Bearer ` - -请求(字段可选): -```json -{ - "password": "newpass", - "username": "新昵称", - "phone": "13800000000", - "avatarUrl": "https://example.com/avatar.png", - "websiteUrl": "https://example.com", - "bio": "### 新简介" -} -``` - -说明:`websiteUrl` 须为 `http`/`https` 地址;可传空字符串清除;未写协议时服务端会补全为 `https://`。 - -响应: -```json -{ - "user": { "account": "demo", "...": "..." } -} -``` - -## 用户广场 - -### 获取用户公开主页 -`GET /api/public/users/{account}` - -说明: -- 仅支持账户名 `account`,不支持昵称查询。 -- 适合第三方应用展示用户公开资料。 -- 若该账户已被封禁,返回 **404** `{"error":"user not found"}`(与不存在账户相同,避免公开资料泄露)。 -- 响应中含该用户**最近一次被服务端记录的**访问 IP(`lastVisitIp`)与展示用地理位置(`lastVisitDisplayLocation`,与本人中心一致);`POST /api/auth/verify` 返回的用户 JSON **不含**上述两项。 - -响应: -```json -{ - "user": { - "account": "demo", - "username": "示例用户", - "level": 3, - "sproutCoins": 10, - "avatarUrl": "https://example.com/avatar.png", - "websiteUrl": "https://example.com", - "lastVisitIp": "203.0.113.1", - "lastVisitDisplayLocation": "中国 广东省 深圳市", - "bio": "### 简介" - } -} -``` - -### 公开注册策略 -`GET /api/public/registration-policy` - -无需鉴权。用于前端判断是否展示「邀请码」输入框。 - -响应: -```json -{ - "requireInviteCode": false -} -``` - -当 `requireInviteCode` 为 **true** 时,`POST /api/auth/register` 必须携带有效 `inviteCode`(见下节)。 - -### 注册账号(发送邮箱验证码) -`POST /api/auth/register` - -请求: -```json -{ - "account": "demo", - "password": "demo123", - "username": "示例用户", - "email": "demo@example.com", - "inviteCode": "ABCD1234" -} -``` - -- `inviteCode`:可选。若服务端开启「强制邀请码」,则必填且须为管理员发放的未过期、未用尽邀请码。邀请码**不区分大小写**;成功完成 `verify-email` 创建用户后才会扣减使用次数。 - -响应: -```json -{ - "sent": true, - "expiresAt": "2026-03-14T12:10:00Z" -} -``` - -### 验证邮箱并完成注册 -`POST /api/auth/verify-email` - -请求: -```json -{ - "account": "demo", - "code": "123456" -} -``` - -响应: -```json -{ - "created": true, - "user": { "account": "demo", "...": "..." } -} -``` - -### 忘记密码(发送重置验证码) -`POST /api/auth/forgot-password` - -请求: -```json -{ - "account": "demo", - "email": "demo@example.com" -} -``` - -响应: -```json -{ - "sent": true, - "expiresAt": "2026-03-14T12:10:00Z" -} -``` - -### 重置密码 -`POST /api/auth/reset-password` - -请求: -```json -{ - "account": "demo", - "code": "123456", - "newPassword": "newpass" -} -``` - -响应: -```json -{ "reset": true } -``` - -### 申请添加辅助邮箱(发送验证码) -`POST /api/auth/secondary-email/request` - -请求头: -`Authorization: Bearer ` - -请求: -```json -{ - "email": "demo2@example.com" -} -``` - -响应: -```json -{ - "sent": true, - "expiresAt": "2026-03-14T12:10:00Z" -} -``` - -### 验证辅助邮箱 -`POST /api/auth/secondary-email/verify` - -请求头: -`Authorization: Bearer ` - -请求: -```json -{ - "email": "demo2@example.com", - "code": "123456" -} -``` - -响应: -```json -{ - "verified": true, - "user": { "account": "demo", "...": "..." } -} -``` - -## 管理端接口(需要管理员 Token) - -管理员 Token 存放在 `data/config/admin.json` 中;如果文件不存在,后端启动时会自动生成并写入该文件。 -请求时可使用以下任一方式携带: -- Query:`?token=` -- Header:`X-Admin-Token: ` - -### 签到奖励设置 -`GET /api/admin/check-in/config` - -`PUT /api/admin/check-in/config` - -请求: -```json -{ - "rewardCoins": 1 -} -``` -- Header:`Authorization: Bearer ` - -### 注册策略与邀请码 - -`GET /api/admin/registration` - -响应含 `requireInviteCode` 与 `invites` 数组(每项含 `code`、`note`、`maxUses`、`uses`、`expiresAt`、`createdAt`)。`maxUses` 为 0 表示不限次数。 - -`PUT /api/admin/registration` - -请求: -```json -{ "requireInviteCode": true } -``` - -`POST /api/admin/registration/invites` - -请求: -```json -{ - "note": "内测批次", - "maxUses": 10, - "expiresAt": "2026-12-31T15:59:59Z" -} -``` - -`expiresAt` 可省略;须为 RFC3339。响应 `201`,`invite` 内含服务端生成的 8 位邀请码。 - -`DELETE /api/admin/registration/invites/{code}` - -删除指定邀请码(`code` 与存储大小写可能不同,按不区分大小写匹配)。 - -### 获取用户列表 -`GET /api/admin/users` - -响应: -```json -{ - "total": 1, - "users": [{ "account": "demo", "...": "..." }] -} -``` - -### 新建用户 -`POST /api/admin/users` - -请求: -```json -{ - "account": "demo", - "password": "demo123", - "username": "示例用户", - "email": "demo@example.com", - "level": 0, - "sproutCoins": 10, - "secondaryEmails": ["demo2@example.com"], - "phone": "13800000000", - "avatarUrl": "https://example.com/avatar.png", - "websiteUrl": "https://example.com", - "bio": "### 简介" -} -``` - -### 更新用户 -`PUT /api/admin/users/{account}` - -请求(字段可选): -```json -{ - "password": "newpass", - "username": "新昵称", - "level": 1, - "secondaryEmails": ["demo2@example.com"], - "sproutCoins": 99, - "websiteUrl": "https://example.com", - "banned": true, - "banReason": "违规说明(最多 500 字)" -} -``` - -- `banned`:是否封禁;解封时请传 `false`,并可将 `banReason` 置为空字符串。 -- `banReason`:仅当用户处于封禁状态时允许设为非空;封禁时若首次写入会记录 `bannedAt`(RFC3339,存于用户 JSON)。 - -管理员列表 `GET /api/admin/users` 中每条 `user` 可含 `banned`、`banReason`(不含 `bannedAt` 亦可从存储文件中查看)。 - -### 删除用户 -`DELETE /api/admin/users/{account}` - -响应: -```json -{ "deleted": true } -``` - -## 数据存储说明 - -- 用户数据:`data/users/*.json` -- 注册待验证:`data/pending/*.json` -- 密码重置记录:`data/reset/*.json` -- 辅助邮箱验证:`data/secondary/*.json` -- 管理员 Token:`data/config/admin.json` -- JWT 配置:`data/config/auth.json` -- 邮件配置:`data/config/email.json` -- 注册策略与邀请码:`data/config/registration.json` - -## 快速联调用示例 - -```bash -# 服务根路径 JSON 说明 -curl -s http://localhost:8080/ | jq . - -# 登录 -curl -X POST http://localhost:8080/api/auth/login \ - -H 'Content-Type: application/json' \ - -d '{"account":"demo","password":"demo123"}' - -# 校验令牌(推荐第三方网关先调此接口) -curl -X POST http://localhost:8080/api/auth/verify \ - -H 'Content-Type: application/json' \ - -d '{"token":""}' - -# 使用令牌获取用户信息(会更新访问记录) -curl http://localhost:8080/api/auth/me \ - -H 'Authorization: Bearer ' -``` diff --git a/DEPLOY.md b/DEPLOY.md deleted file mode 100644 index 4f5ff82..0000000 --- a/DEPLOY.md +++ /dev/null @@ -1,399 +0,0 @@ -# 萌芽小店 · 生产环境部署指南 - -> 本指南覆盖从零开始将萌芽小店完整部署到生产服务器的全部步骤,包括后端 Docker 部署与前端静态文件托管。 - ---- - -## 目录 - -1. [环境要求](#1-环境要求) -2. [准备 MySQL 数据库](#2-准备-mysql-数据库) -3. [部署后端(Docker)](#3-部署后端docker) -4. [构建并部署前端](#4-构建并部署前端) -5. [Nginx 反向代理配置](#5-nginx-反向代理配置) -6. [验证部署](#6-验证部署) -7. [常见问题排查](#7-常见问题排查) -8. [升级与回滚](#8-升级与回滚) - ---- - -## 1. 环境要求 - -| 组件 | 最低版本 | 说明 | -|------|----------|------| -| 服务器 OS | Linux (Ubuntu 22.04 / Debian 12 推荐) | 也支持 CentOS / AlmaLinux | -| Docker | 24.x | `docker --version` 确认 | -| Docker Compose | v2.x | `docker compose version` 确认 | -| MySQL | 8.x | 可使用内网 MySQL 实例 | -| Nginx | 1.20+ | 用于前端静态托管 + API 反向代理 | -| Node.js(构建时) | 18+ | 仅前端打包时需要 | - ---- - -## 2. 准备 MySQL 数据库 - -> **表结构无需手动创建**,后端启动时 GORM 会自动执行 `AutoMigrate` 建表。 - -### 2.1 创建数据库和用户 - -在 MySQL 服务器上执行: - -```sql -CREATE DATABASE IF NOT EXISTS `mengyastore` - CHARACTER SET utf8mb4 - COLLATE utf8mb4_unicode_ci; - -CREATE USER IF NOT EXISTS 'mengyastore'@'%' - IDENTIFIED BY 'your_strong_password'; - -GRANT ALL PRIVILEGES ON `mengyastore`.* TO 'mengyastore'@'%'; - -FLUSH PRIVILEGES; -``` - -或者使用项目提供的初始化脚本: - -```bash -mysql -u root -p < mengyastore-backend/init.sql -``` - -### 2.2 验证连接 - -```bash -mysql -h 192.168.1.100 -P 3306 -u mengyastore -p mengyastore -# 出现 mysql> 提示符即表示连接成功 -``` - ---- - -## 3. 部署后端(Docker) - -### 3.1 将代码上传到服务器 - -```bash -# 方式一:git clone(推荐) -git clone https://your-repo-url/mengyastore.git -cd mengyastore/mengyastore-backend - -# 方式二:scp 上传 -scp -r mengyastore-backend/ user@your-server:/opt/mengyastore/ -``` - -### 3.2 创建 `config.json` - -在 `mengyastore-backend/` 目录下创建配置文件(**此文件不会被提交到 Git,需手动创建**): - -```bash -cat > /opt/mengyastore/mengyastore-backend/config.json << 'EOF' -{ - "adminToken": "your_strong_admin_token", - "databaseDsn": "mengyastore:your_password@tcp(192.168.1.100:3306)/mengyastore?charset=utf8mb4&parseTime=True&loc=Local" -} -EOF -``` - -> ⚠️ `adminToken` 请设置为足够强的随机字符串(建议 16 位以上),这是进入管理后台的唯一凭证。 - -### 3.3 检查 `docker-compose.yml` - -确认 `mengyastore-backend/docker-compose.yml` 中的 `DATABASE_DSN` 与你的生产数据库匹配: - -```yaml -services: - backend: - build: - context: . - container_name: mengyastore-backend - ports: - - "28081:8080" # 宿主机端口:容器端口,可按需修改 - environment: - GIN_MODE: release - TZ: Asia/Shanghai - DATABASE_DSN: "mengyastore:your_password@tcp(192.168.1.100:3306)/mengyastore?charset=utf8mb4&parseTime=True&loc=Local" - volumes: - - ./config.json:/app/config.json:ro # 挂载配置文件(只读) - restart: unless-stopped -``` - -> `DATABASE_DSN` 环境变量优先级高于 `config.json`,两者保持一致即可。 - -### 3.4 构建并启动容器 - -```bash -cd /opt/mengyastore/mengyastore-backend - -# 首次部署 -docker compose up -d --build - -# 查看启动日志 -docker compose logs -f - -# 预期看到: -# [DB] 数据库连接成功,表结构已同步 -# 萌芽小店后端启动于 http://localhost:8080 -``` - -### 3.5 验证后端 - -```bash -curl http://localhost:28081/api/health -# 应返回:{"status":"ok"} - -curl http://localhost:28081/api/products -# 应返回:{"data":[...]}(空数组或商品列表) -``` - ---- - -## 4. 构建并部署前端 - -### 4.1 本地构建(推荐在本地完成后上传 dist) - -```bash -cd mengyastore-frontend - -# 安装依赖 -npm install - -# 生产构建 -npm run build -# 输出目录:dist/ -``` - -### 4.2 配置 API 地址 - -前端通过 Vite 的 `VITE_API_BASE_URL` 环境变量指定后端地址。如果前端与后端同域(通过 Nginx 代理),也可以不配置,默认会使用当前站点域名。 - -如果前后端跨域,在 `mengyastore-frontend/` 目录创建 `.env.production`: - -```ini -VITE_API_BASE_URL=https://store.api.shumengya.top -``` - -然后重新 `npm run build`。 - -### 4.3 上传 dist 到服务器 - -```bash -# 假设静态文件托管在 /var/www/mengyastore/ -scp -r dist/* user@your-server:/var/www/mengyastore/ - -# 或使用 rsync(增量同步更快) -rsync -avz --delete dist/ user@your-server:/var/www/mengyastore/ -``` - ---- - -## 5. Nginx 反向代理配置 - -### 5.1 安装 Nginx - -```bash -# Ubuntu / Debian -sudo apt update && sudo apt install -y nginx - -# CentOS / AlmaLinux -sudo yum install -y nginx -``` - -### 5.2 创建站点配置 - -```bash -sudo nano /etc/nginx/sites-available/mengyastore -``` - -写入以下内容(替换 `your-domain.com` 为你的实际域名): - -```nginx -server { - listen 80; - server_name your-domain.com; - - # 前端静态文件 - root /var/www/mengyastore; - index index.html; - - # Vue Router History 模式:所有路由回退到 index.html - location / { - try_files $uri $uri/ /index.html; - } - - # 反向代理后端 API - location /api/ { - proxy_pass http://127.0.0.1:28081; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_connect_timeout 30s; - proxy_read_timeout 60s; - } - - # 静态资源缓存 - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2|webmanifest)$ { - expires 7d; - add_header Cache-Control "public, immutable"; - } -} -``` - -### 5.3 启用配置并重启 - -```bash -sudo ln -s /etc/nginx/sites-available/mengyastore /etc/nginx/sites-enabled/ -sudo nginx -t # 测试配置语法 -sudo systemctl reload nginx -``` - -### 5.4 (可选)配置 HTTPS(Let's Encrypt) - -```bash -sudo apt install -y certbot python3-certbot-nginx -sudo certbot --nginx -d your-domain.com -# 按提示操作,certbot 会自动修改 Nginx 配置并续签证书 -``` - ---- - -## 6. 验证部署 - -### 6.1 检查列表 - -- [ ] `https://your-domain.com` 能正常打开首页 -- [ ] 商品列表加载正常(若为空请先登录后台添加商品) -- [ ] 登录 SproutGate 账号后,收藏夹/聊天等功能正常 -- [ ] **Logo 点击 5 次**弹出管理员登录框,输入 `adminToken` 能进入后台 -- [ ] 后台可以新建/编辑/删除商品 -- [ ] 下单流程(自动发货)能正常获取卡密 -- [ ] PWA:在 Chrome 地址栏点击安装图标,可添加到桌面 - -### 6.2 后端日志查看 - -```bash -docker compose -f /opt/mengyastore/mengyastore-backend/docker-compose.yml logs -f -``` - ---- - -## 7. 常见问题排查 - -### 问题:前端白屏 / 控制台报 CORS 错误 - -**原因**:Nginx 未正确代理 `/api/` 请求,或后端未启动。 - -**排查**: -```bash -curl http://127.0.0.1:28081/api/health -docker compose ps # 确认容器正在运行 -``` - ---- - -### 问题:管理员登录失败("验证失败,无法连接服务器") - -**原因**:后端容器未运行,或 Nginx `/api/` 代理配置有误。 - -**排查**: -```bash -docker compose ps -curl http://127.0.0.1:28081/api/health -``` - ---- - -### 问题:管理员登录失败("令牌错误,请重试") - -**原因**:输入的令牌与 `config.json` 中的 `adminToken` 不一致。 - -**排查**:检查服务器上的 `config.json`: -```bash -cat /opt/mengyastore/mengyastore-backend/config.json -``` - ---- - -### 问题:商品列表为空 - -**原因**:数据库为空,需要通过管理后台添加商品。 - -**步骤**: -1. 登录管理后台 -2. 点击"商品管理" → "新增商品" -3. 填写商品名称、价格、卡密(自动发货)或选择手动发货模式 - ---- - -### 问题:数据库连接失败(启动时报错) - -**原因**:`DATABASE_DSN` 配置错误,或 MySQL 服务不可达。 - -**排查**: -```bash -# 在宿主机上测试数据库连接 -mysql -h 192.168.1.100 -P 3306 -u mengyastore -p - -# 查看后端详细日志 -docker compose logs backend | head -50 -``` - ---- - -### 问题:`record not found` 日志警告 - -**说明**:这是 GORM 的正常行为,表示某些站点设置(如维护模式)在数据库中尚未有记录。**不影响功能**,首次设置后会自动写入。 - ---- - -## 8. 升级与回滚 - -### 升级 - -```bash -cd /opt/mengyastore/mengyastore-backend - -# 拉取最新代码(如果使用 git) -git pull - -# 重新构建并重启 -docker compose up -d --build - -# 查看新版本日志 -docker compose logs -f -``` - -### 回滚 - -```bash -# 查看历史镜像 -docker images | grep mengyastore - -# 停止当前容器,启动旧版本 -docker compose down -docker tag mengyastore-backend-backend:previous mengyastore-backend-backend:latest -docker compose up -d -``` - ---- - -## 附:目录结构参考 - -``` -/opt/mengyastore/ - mengyastore-backend/ - config.json ← 生产配置(不要提交到 Git!) - docker-compose.yml - Dockerfile - ...(Go 源码) - -/var/www/mengyastore/ ← 前端 dist 文件 - index.html - assets/ - ... -``` - ---- - -> 如遇到本指南未覆盖的问题,请查看后端实时日志: -> `docker compose -f /opt/mengyastore/mengyastore-backend/docker-compose.yml logs -f` - -**部署完成 🎉** 访问 `https://your-domain.com` 即可看到萌芽小店正式上线。 diff --git a/README.md b/README.md index 59d17a0..23efe53 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 萌芽小店 · Mengyastore -一个前后端分离的轻量级商城,支持自动/手动发货、邮件通知、聊天客服、收藏夹与 PWA 安装。 +一个前后端分离的轻量级数字商品商城,支持**卡密 / 固定内容**两种发货、扫码收款演示流程、邮件通知(直发或 RabbitMQ 队列)、聊天客服、收藏夹、管理端系统状态面板与 PWA 安装。 --- @@ -8,231 +8,141 @@ ### 前端 `mengyastore-frontend/` -| 分类 | 技术 | 版本 | 说明 | -|------|------|------|------| -| 框架 | Vue 3 | ^3.4 | Composition API + ` + + diff --git a/mengyastore-frontend/src/assets/payment/微信个人收款码.png b/mengyastore-frontend/src/assets/payment/微信个人收款码.png new file mode 100644 index 0000000..a290c90 Binary files /dev/null and b/mengyastore-frontend/src/assets/payment/微信个人收款码.png differ diff --git a/mengyastore-frontend/src/assets/payment/微信赞赏码.png b/mengyastore-frontend/src/assets/payment/微信赞赏码.png new file mode 100644 index 0000000..3964f34 Binary files /dev/null and b/mengyastore-frontend/src/assets/payment/微信赞赏码.png differ diff --git a/mengyastore-frontend/src/assets/payment/支付宝个人收款码.png b/mengyastore-frontend/src/assets/payment/支付宝个人收款码.png new file mode 100644 index 0000000..aad4af2 Binary files /dev/null and b/mengyastore-frontend/src/assets/payment/支付宝个人收款码.png differ diff --git a/mengyastore-frontend/src/assets/styles.css b/mengyastore-frontend/src/assets/styles.css index 531b657..60bb992 100644 --- a/mengyastore-frontend/src/assets/styles.css +++ b/mengyastore-frontend/src/assets/styles.css @@ -132,6 +132,10 @@ .form-field input, .form-field textarea { + width: 100%; + max-width: 100%; + min-width: 0; + box-sizing: border-box; border-radius: 8px; border: 1px solid var(--line-color); background: rgba(255, 255, 255, 0.7); diff --git a/mengyastore-frontend/src/modules/admin/components/AdminProductModal.vue b/mengyastore-frontend/src/modules/admin/components/AdminProductModal.vue index 0783375..63f200d 100644 --- a/mengyastore-frontend/src/modules/admin/components/AdminProductModal.vue +++ b/mengyastore-frontend/src/modules/admin/components/AdminProductModal.vue @@ -42,19 +42,45 @@
-
- - 共 {{ form.inventoryItems.length }} 条 - + +
+ +
-

每个输入框保存一条可发放内容,购买后会直接展示给用户。

+
+ +
+ + +

保存后顾客每笔订单会收到这份内容(若一次买多件,会复制多行相同内容便于核对)。

+
+ +
+
+ + 共 {{ form.inventoryItems.length }} 条 + +
+

每条一行,手动逐个添加;购买后按顺序发放并扣减库存。

@@ -30,6 +30,7 @@ import { computed, onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import { fetchProducts } from '../shared/api' +import { isSoldOutProduct } from '../shared/fulfillment' import { getWishlistProducts, loadWishlist } from '../shared/useWishlist' import ProductCard from '../store/components/ProductCard.vue' @@ -37,18 +38,19 @@ const router = useRouter() const allProducts = ref([]) const loading = ref(true) +const isUnavailable = (item) => !item.active || isSoldOutProduct(item) + const wishlistItems = computed(() => { const items = getWishlistProducts(allProducts.value) return items.sort((a, b) => { - const aOk = a.active && a.quantity > 0 ? 0 : 1 - const bOk = b.active && b.quantity > 0 ? 0 : 1 + const aOk = a.active && !isSoldOutProduct(a) ? 0 : 1 + const bOk = b.active && !isSoldOutProduct(b) ? 0 : 1 return aOk - bOk }) }) const handleClick = (item) => { - if (!item) return - if (!item.active || item.quantity === 0) return + if (!item || isUnavailable(item)) return router.push(`/product/${item.id}`) } diff --git a/mengyastore-frontend/前端文档.md b/mengyastore-frontend/前端文档.md new file mode 100644 index 0000000..3546df7 --- /dev/null +++ b/mengyastore-frontend/前端文档.md @@ -0,0 +1,143 @@ +# 萌芽小店 · 前端(mengyastore-frontend) + +基于 **Vue 3 + Vite** + **Tailwind CSS(Vite 插件)** 的数字商品前端:商店、结算(含扫码收款演示)、管理后台、收藏、订单、聊天、PWA。 + +## 技术依赖 + +| 包 | 用途 | +|----|------| +| vue ^3.x | 框架 | +| vite ^5.x | 构建 | +| @tailwindcss/vite | 工具类样式 | +| vue-router ^4.x | 路由 | +| pinia ^2.x | 状态(与 reactive 并存) | +| axios ^1.6 | HTTP | +| markdown-it ^14 | 商品描述 | +| vite-plugin-pwa | PWA | + +## 目录结构 + +``` +src/ +├── assets/ +│ ├── styles.css # 全局样式、.ghost / .page-card / . form-field 等 +│ └── payment/ # 支付宝/微信收款码 PNG(import 进 CheckoutPage) +├── router/index.js # 路由;维护模式 beforeEach +├── modules/ +│ ├── shared/ +│ │ ├── api.js # API 封装(含 fetchSystemStatus) +│ │ ├── auth.js # 登录态、getLoginUrl +│ │ ├── fulfillment.js # isFixedFulfillment、stockLabel、isSoldOutProduct +│ │ └── useWishlist.js +│ ├── store/ +│ │ ├── StorePage.vue +│ │ ├── ProductDetail.vue +│ │ ├── CheckoutPage.vue # 响应式布局、支付渠道 Tab、订单号复制、收款图 +│ │ └── components/ProductCard.vue +│ ├── admin/ +│ │ ├── AdminPage.vue # 侧边栏:商品/订单/聊天/设置/系统状态 +│ │ └── components/ +│ │ ├── AdminProductModal.vue # 发货方式:卡密 | 固定内容 +│ │ ├── AdminProductTable.vue +│ │ ├── AdminOrderTable.vue +│ │ ├── AdminChatPanel.vue +│ │ ├── AdminMaintenanceRow.vue +│ │ ├── AdminSMTPRow.vue +│ │ └── AdminSystemStatusPanel.vue +│ ├── user/MyOrdersPage.vue +│ ├── wishlist/WishlistPage.vue +│ ├── maintenance/MaintenancePage.vue +│ ├── auth/AuthCallback.vue +│ └── chat/ChatWidget.vue +└── App.vue # 顶栏:桌面横排导航;移动端汉堡菜单 + 下拉面板 +``` + +## 路由 + +| 路径 | 组件 | 说明 | +|------|------|------| +| `/` | StorePage | 商品列表 | +| `/product/:id` | ProductDetail | 详情 | +| `/checkout/:id` | CheckoutPage | 结算(路径参数为商品 id) | +| `/my/orders` | MyOrdersPage | 我的订单(建议登录) | +| `/wishlist` | WishlistPage | 收藏 | +| `/admin` | AdminPage | 管理后台(token) | +| `/auth/callback` | AuthCallback | OAuth 回调 | +| `/maintenance` | MaintenancePage | 维护中 | + +### 路由守卫 + +- 请求 `GET /api/site/maintenance`;维护中时跳转 `/maintenance`。 +- 豁免:`/maintenance`、`/wishlist`、`/admin`、`/auth/callback`(与 `router/index.js` 保持一致)。 + +## 认证 + +1. 「萌芽账号登录」→ SproutGate 登录页,`callback` 回指本站。 +2. Token 存 `localStorage`,`authState` 共享用户信息。 +3. 需登录接口带 `Authorization: Bearer `。 + +## 功能要点 + +### 商品与库存展示 + +- API 字段 **`fulfillmentType`**:`card` | `fixed`。 +- **`fixed`**:列表/详情/卡片显示库存为 **「不限」**,不按 `quantity===0` 售罄。 +- 工具函数:`modules/shared/fulfillment.js`。 + +### 管理后台 · 商品 + +- **卡密**:多行输入,逐条对应 `codes`。 +- **固定内容**:大文本 `fixedContent`;保存后无需维护条数。 + +### 结算页(CheckoutPage) + +- 栅格与表单单列适配窄屏。 +- 下单成功后:应付金额、支付宝/微信/赞赏 **本地图片** 切换、`订单号` **一键复制**、可选展开服务端 `qrCodeUrl`。 +- 数量上限:卡密=min(库存, 每账号限购);固定内容=仅每账号限购(若设置)。 + +### 顶栏(App.vue) + +- `md` 及以上:横向按钮。 +- 以下:汉堡按钮 + 全宽大标题项 + 遮罩;Escape 关闭;打开时锁 body 滚动。 + +### 系统状态 + +- `AdminSystemStatusPanel` 调用 `GET /api/admin/system-status`,展示后端/API、MySQL、Redis、RabbitMQ(需管理 Token)。 + +### PWA + +- `vite-plugin-pwa`:`registerType: 'autoUpdate'`,构建后生成 SW;根组件有更新 Toast 与 `SplashScreen`。 + +## 开发命令 + +```bash +npm install +npm run dev # :5173 +npm run build # dist/ +npm run preview +``` + +### 环境变量 + +开发示例(`.env.development`): + +```env +VITE_API_BASE_URL=http://localhost:8080 +``` + +生产部署设置实际 API 基地址(或同源反代可不设)。 + +## 部署(静态资源) + +```nginx +location / { + try_files $uri $uri/ /index.html; +} +location /api/ { + proxy_pass http://127.0.0.1:8080; +} +``` + +--- + +字体与色板以 `src/assets/styles.css` 及 Tailwind 主题为准;文档若与代码不一致,以仓库当前实现为准。 diff --git a/生产环境部署.md b/生产环境部署.md new file mode 100644 index 0000000..6699926 --- /dev/null +++ b/生产环境部署.md @@ -0,0 +1,260 @@ +# 萌芽小店 · 生产环境部署指南 + +> 覆盖生产环境部署要点:后端目录为 **`mengyastore-backend-go`**,配置以 **环境变量** 为主(与本地 `.env` / `.env.production` 一致),无需再依赖 `config.json`。 + +--- + +## 目录 + +1. [环境要求](#1-环境要求) +2. [准备 MySQL](#2-准备-mysql) +3. [部署后端(Docker)](#3-部署后端docker) +4. [构建并部署前端](#4-构建并部署前端) +5. [Nginx 反向代理](#5-nginx-反向代理) +6. [验证部署](#6-验证部署) +7. [常见问题](#7-常见问题) +8. [升级与回滚](#8-升级与回滚) + +--- + +## 1. 环境要求 + +| 组件 | 说明 | +|------|------| +| Linux 服务器 | Ubuntu 22.04 / Debian 12 等 | +| Docker 24+、Compose v2 | `docker compose version` | +| MySQL 8.x | 可内网独立实例 | +| Nginx 1.20+ | 静态资源 + `/api/` 反代 | +| Node.js 18+ | 仅前端 **构建** 时使用 | + +可选:内网 **RabbitMQ**、**Redis**(与 `docker-compose` / 环境变量一致即可)。 + +--- + +## 2. 准备 MySQL + +表结构由后端 **GORM AutoMigrate** 自动创建,只需 **建库 + 授权**: + +```sql +CREATE DATABASE IF NOT EXISTS `mengyastore` + CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE USER IF NOT EXISTS 'mengyastore'@'%' IDENTIFIED BY 'your_strong_password'; +GRANT ALL PRIVILEGES ON `mengyastore`.* TO 'mengyastore'@'%'; +FLUSH PRIVILEGES; +``` + +也可参考仓库内 `mengyastore-backend-go/init.sql`。 + +--- + +## 3. 部署后端(Docker) + +### 3.1 代码路径 + +```bash +git clone https://github.com/shumengya/mengyastore.git # 或你的仓库 +cd mengyastore/mengyastore-backend-go +``` + +> 若历史文档写成 `mengyastore-backend`,请改为 **`mengyastore-backend-go`**。 + +### 3.2 配置方式(推荐) + +生产 **不要** 把真实口令写进 Git。任选其一: + +- **A.** 在宿主机导出环境变量后执行 `docker compose up`(Compose 中 `${VAR:-默认值}` 会读取宿主机环境)。 +- **B.** 在服务器的 `mengyastore-backend-go/` 放置 **`.env`**(勿提交),并在 `docker-compose.yml` 中增加 `env_file: .env`(若你自行加上该条目);当前仓库默认 compose **未挂载** `.env`,请通过 **environment** 或 **export** 注入。 + +**必填 / 常用变量(与 `internal/config/config.go` 一致):** + +| 变量 | 说明 | +|------|------| +| `APP_ENV` | `production` | +| `DATABASE_DSN` | 生产 MySQL 连接串 | +| `ADMIN_TOKEN` | 管理后台令牌(强随机) | +| `AUTH_API_URL` | SproutGate 认证网关 Base URL | +| `RABBITMQ_ENABLED` | `true` / `false` | +| `RABBITMQ_URL` | 启用 MQ 时完整 `amqp://…`(或用各 `RABBITMQ_*` 拼装,见配置注释) | +| `REDIS_ENABLED` | `true` / `false` | +| `REDIS_ADDR`、`REDIS_PASSWORD`、`REDIS_DB` | 启用 Redis 探测/缓存时 | +| `HTTP_LISTEN_ADDR` | 可选,默认容器内 `:8080` | +| `PUBLIC_API_BASE_URL` | 可选,管理端「系统状态」展示的对外 API 基址(反代 HTTPS 时建议设) | + +根据业务关闭不需要的中间件时,将 `RABBITMQ_ENABLED` 或 `REDIS_ENABLED` 设为 `false`,避免无实例仍去连接。 + +### 3.3 修改 `docker-compose.yml` + +部署前请 **核对并本地化**: + +- `DATABASE_DSN` 指向你的库。 +- `ports`(示例 `28081:8080`)是否与 Nginx `proxy_pass` 一致。 +- **删除或替换** 仓库中带默认口令的示例值;通过宿主机环境注入生产密钥。 + +示例(**仅结构示意,口令用环境变量**): + +```yaml +services: + backend: + build: { context: . } + container_name: mengyastore-backend + ports: + - "28081:8080" + environment: + APP_ENV: production + GIN_MODE: release + TZ: Asia/Shanghai + DATABASE_DSN: ${DATABASE_DSN} + ADMIN_TOKEN: ${ADMIN_TOKEN} + AUTH_API_URL: ${AUTH_API_URL:-} + REDIS_ENABLED: ${REDIS_ENABLED:-false} + REDIS_ADDR: ${REDIS_ADDR:-} + REDIS_PASSWORD: ${REDIS_PASSWORD:-} + RABBITMQ_ENABLED: ${RABBITMQ_ENABLED:-false} + RABBITMQ_ENV: prod + RABBITMQ_URL: ${RABBITMQ_URL:-} + restart: unless-stopped +``` + +### 3.4 启动 + +```bash +cd /opt/mengyastore/mengyastore-backend-go # 你的实际路径 +export DATABASE_DSN='...' +export ADMIN_TOKEN='...' +# … 其他变量按需 export +docker compose up -d --build +docker compose logs -f +``` + +预期日志含:`[DB] 数据库连接成功,表结构已同步`、`萌芽小店后端启动于 http://localhost:8080`。 + +### 3.5 健康检查 + +```bash +curl -s http://127.0.0.1:28081/api/health +curl -s http://127.0.0.1:28081/api/products +``` + +--- + +## 4. 构建并部署前端 + +```bash +cd mengyastore-frontend +npm ci || npm install +``` + +**同源部署**(Nginx 把 `/api/` 反代到后端):构建前可不设 `VITE_API_BASE_URL`,打包后请求会走当前站点域名。 + +**前后端不同域**:创建 `.env.production`: + +```env +VITE_API_BASE_URL=https://你的-api-域名 +``` + +```bash +npm run build +``` + +将 `dist/` 同步到服务器,例如 `/var/www/mengyastore/`: + +```bash +rsync -avz --delete dist/ user@server:/var/www/mengyastore/ +``` + +--- + +## 5. Nginx 反向代理 + +```nginx +server { + listen 80; + server_name your-domain.com; + root /var/www/mengyastore; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://127.0.0.1:28081; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_read_timeout 60s; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2|webmanifest)$ { + expires 7d; + add_header Cache-Control "public, immutable"; + } +} +``` + +HTTPS 可使用 `certbot --nginx` 签发 Let's Encrypt。 + +--- + +## 6. 验证部署 + +- [ ] 首页与商品列表正常。 +- [ ] SproutGate 登录后收藏、聊天、我的订单可用。 +- [ ] **Logo 连点 5 次** → 管理员验证 → 带 `?token=` 或使用 `X-Admin-Token` 访问后台。 +- [ ] 后台 **系统状态**:MySQL / Redis / RabbitMQ(若启用)显示合理。 +- [ ] 商品:**卡密** 与 **固定内容** 两种类型可分别保存;固定内容商品前台库存显示「不限」。 +- [ ] 下一单:结算页金额、收款码区、**复制订单号**、确认后内容与邮件/MQ 预期一致(依你的 SMTP/MQ 配置)。 +- [ ] PWA 可安装(如需要)。 + +--- + +## 7. 常见问题 + +**CORS / 白屏** +确认 Nginx `location /api/` 指向后端端口,且容器 `docker compose ps` 为运行中。 + +**管理 Token 无效** +核对容器内实际生效的 `ADMIN_TOKEN`(与前端输入一致)。当前项目 **不再依赖** `config.json`。 + +**RabbitMQ 报 504 / channel closed** +服务端已实现重连与消费重启;若仍异常,查 Broker 日志、网络与 `RABBITMQ_URL`/vhost/权限。 + +**数据库连不上** +在宿主机用 `mysql` 客户端测 `DATABASE_DSN` 同款主机端口账号;Docker 网络须能访问 MySQL。 + +**站点设置 KV 查询** +后端对缺失的 `site_settings` 键使用不产生「record not found」噪声的查询方式;首次保存 SMTP 等后会写入库表。 + +--- + +## 8. 升级与回滚 + +```bash +cd mengyastore-backend-go +git pull +docker compose up -d --build +docker compose logs -f --tail=100 +``` + +回滚:保留上一版本的镜像 tag 或 git tag,重新 `docker compose up` 指定旧镜像(视你实际镜像命名而定)。 + +--- + +## 附:目录结构示例 + +``` +/opt/mengyastore/ + mengyastore-backend-go/ + docker-compose.yml + Dockerfile + .env ← 仅存服务器本地,勿提交 Git + ... + +/var/www/mengyastore/ ← 前端 dist + index.html + assets/ +``` + +部署完成后访问你的域名即可。 diff --git a/萌芽小店项目经历.md b/萌芽小店项目经历.md index 56d42c5..4b64d1e 100644 --- a/萌芽小店项目经历.md +++ b/萌芽小店项目经历.md @@ -1,12 +1,5 @@ -**项目名称:** 萌芽小店-网关登录商品售卖平台 -**时间:** 2026.1 - 2026.03 -**技术栈:** Golang,Gin,MySQL,Redis,Docker,Nginx,Vue,Vite,Tailwind CSS -**描述:** 前后端分离的轻量级商城,支持商品展示与下单、自动/手动发货、邮件通知、收藏夹、客服消息、维护模式与 PWA 离线缓存;后台覆盖商品/订单/对话与 SMTP、站点运维。后端分层(Handler + Storage + GORM),对接第三方网关登录与多环境配置、容器化部署;公开接口、用户态与管理员令牌分层鉴权,限购与订单状态约束降低并发超卖风险。引入 Redis:商品列表与站点统计走 Cache-Aside、管理端写后失效,浏览去重用 SET NX 适配多实例,缓存不可用时降级直查数据库;管理后台提供 API/MySQL/Redis 连接与基础信息面板,便于排查。前端路由守卫与接口封装统一调用方式。 -**项目地址:** https://store.smyhub.com | **开源地址:** https://github.com/shumengya/mengyastore - - - - - - - +**项目名称:** 萌芽小店-网关登录的轻量级商城系统 +**时间:** 2026.01 – 2026.04 +**技术栈:** Go(Gin、GORM)、MySQL、可选 RabbitMQ / Redis、Docker、Nginx、Vue 3、Vite、Tailwind CSS、PWA +**描述:** 前后端分离的轻量级商城系统,支持商品展示与下单、自动/手动发货、邮件通知、收藏夹、客服消息、维护模式与 PWA 离线缓存;后台覆盖商品/订单/对话与 SMTP、站点运维。后端分层(Handler + Storage + GORM),对接第三方网关登录与多环境配置、容器化部署;公开接口、用户态与管理员令牌分层鉴权,限购与订单状态约束降低并发超卖风险。引入 Redis:商品列表与站点统计走 Cache-Aside、管理端写后失效,浏览去重用 SET NX 适配多实例,缓存不可用时降级直查数据库;管理后台提供 API/MySQL/Redis 连接与基础信息面板,便于排查。前端路由守卫与接口封装统一调用方式。 +**项目地址:** https://store.smyhub.com | **开源地址:** https://github.com/shumengya/mengyastore