From 90590c7cb06c5d59ec452a15f24518f17bb4b20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=91=E8=90=8C=E8=8A=BD?= <3205788256@qq.com> Date: Sat, 28 Mar 2026 21:00:27 +0800 Subject: [PATCH] update: 2026-03-28 21:00 --- README.md | 112 ++++++++++++++---- sproutgate.bat => dev.bat | 0 sproutgate.sh => dev.sh | 0 .../internal/storage/storage.go | 52 ++++---- sproutgate-backend/main.go | 2 +- 项目面试询问相关.md | 0 6 files changed, 114 insertions(+), 52 deletions(-) rename sproutgate.bat => dev.bat (100%) rename sproutgate.sh => dev.sh (100%) create mode 100644 项目面试询问相关.md diff --git a/README.md b/README.md index bc0b29d..1602a66 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,89 @@ # SproutGate(萌芽账户认证中心) -前后端分离的统一账户与轻量用户中心:注册登录、邮箱验证、找回密码、副邮箱、签到与资料管理;管理员可维护用户与签到配置。数据以 JSON 文件落盘,适合自建与小规模部署。 +前后端分离的统一账户与轻量用户中心:注册登录、邮箱验证、找回密码、副邮箱、签到与资料管理;管理员可维护用户、签到与注册/邀请码策略。数据以 **JSON 文件落盘**(无传统数据库),适合自建与小规模部署。第三方站点(如万象口袋)通过 **OAuth 式查询参数** 与 **JWT / 校验接口** 接入,详见 [`sproutgate-backend/API_DOCS.md`](./sproutgate-backend/API_DOCS.md)。 -## 架构一览 +--- + +## 架构总览 + +```mermaid +flowchart LR + subgraph client [浏览器] + FE[React + Vite SPA] + end + subgraph api [sproutgate-backend] + Gin[Gin HTTP] + H[handlers] + S[storage JSON] + J[auth JWT] + Gin --> H --> S + H --> J + end + FE -->|HTTPS /api| Gin + S --> FS[(data/config + data/users)] +``` | 部分 | 技术栈 | 目录 | 说明 | |------|--------|------|------| -| 前端 | React 18 + Vite 5 | [`sproutgate-frontend/`](./sproutgate-frontend/) | 用户门户、公开用户页、管理后台;`VITE_API_BASE` 指向后端 | -| 后端 | Go + Gin | [`sproutgate-backend/`](./sproutgate-backend/) | REST API、JWT、CORS;默认端口 `8080` | -| 数据 | JSON 文件 | [`sproutgate-backend/data/`](./sproutgate-backend/data/) | `config/`(管理员、认证、邮件等)、`users/`(用户记录) | +| 前端 | React 18 + Vite 5 | [`sproutgate-frontend/`](./sproutgate-frontend/) | 无 `react-router`;由 [`App.jsx`](./sproutgate-frontend/src/App.jsx) 按 `pathname` 切换视图;`VITE_API_BASE` 指向后端 | +| 后端 | Go 1.20 + Gin | [`sproutgate-backend/`](./sproutgate-backend/) | 单体 REST;默认端口 `8080`;CORS 放行常用头(含 `Authorization`、`X-Admin-Token`、`X-Auth-Client*`) | +| 数据 | JSON 文件 | [`sproutgate-backend/data/`](./sproutgate-backend/data/) | `config/`(`admin.json`、`auth.json` 含 JWT 密钥、`email.json`、`checkin.json`、`registration.json` 等)、`users/`(用户记录) | -### 前端路由与模块 +--- -- **`/`** — [`UserPortal`](./sproutgate-frontend/src/components/UserPortal.jsx):登录、注册、验证邮件、OAuth 式 `redirect_uri` 回跳等流程。 -- **`/user` / `/user/:account`** — [`PublicUserPage`](./sproutgate-frontend/src/components/PublicUserPage.jsx):公开资料展示(Markdown 等)。 -- **`/admin`** — [`AdminPanel`](./sproutgate-frontend/src/components/AdminPanel.jsx):用户 CRUD、签到配置;请求头携带管理员 Token(见下文配置)。 +## 后端代码结构(`sproutgate-backend/`) -启动页与全局壳层见 [`App.jsx`](./sproutgate-frontend/src/App.jsx)、[`SplashScreen`](./sproutgate-frontend/src/components/SplashScreen.jsx)。 +| 包 / 目录 | 职责 | +|-----------|------| +| [`main.go`](./sproutgate-backend/main.go) | 初始化 [`storage.Store`](./sproutgate-backend/internal/storage/storage.go)、注册路由、监听 `PORT` | +| [`internal/handlers/`](./sproutgate-backend/internal/handlers/) | HTTP 处理:`auth_login`、`auth_password`、`profile`、`checkin`、`admin`、`public_registration`、`registration_admin`、`secondary_email`、`auth_client` 等;[`handler.go`](./sproutgate-backend/internal/handlers/handler.go) 聚合依赖 | +| [`internal/storage/`](./sproutgate-backend/internal/storage/storage.go) | 数据目录布局、读写 JSON、待验证/重置令牌等 | +| [`internal/models/`](./sproutgate-backend/internal/models/) | `User`、`Activity`、待审核、副邮箱、接入方元数据等结构体 | +| [`internal/auth/jwt.go`](./sproutgate-backend/internal/auth/jwt.go) | JWT 签发与解析(HS256) | +| [`internal/email/mailer.go`](./sproutgate-backend/internal/email/mailer.go) | SMTP 发信(配置来自 `data/config/email.json`) | +| [`internal/clientgeo/`](./sproutgate-backend/internal/clientgeo/clientgeo.go) | 与访问来源等辅助逻辑配合(请求头见 `main.go` CORS) | -### 后端能力摘要 +**API 前缀(与 [`main.go`](./sproutgate-backend/main.go) 一致)** -- **认证**:登录、注册、邮箱验证、忘记/重置密码、副邮箱申请与验证、JWT 校验、`/api/auth/me`、签到、资料更新。 -- **公开接口**:按账号获取公开用户信息;`GET /api/public/registration-policy` 查询是否强制邀请码注册。 -- **管理接口**:`/api/admin/*`(需 `X-Admin-Token`),用户管理、签到与**注册策略/邀请码**(`data/config/registration.json`)。 -- **运维**:`GET /` 与 `GET /api`(JSON 服务说明)、`GET /api/health`、`GET /api/docs`(返回 [`API_DOCS.md`](./sproutgate-backend/API_DOCS.md))。 +| 前缀 | 说明 | +|------|------| +| `/api/auth/*` | 登录、注册、邮箱验证、忘记/重置密码、副邮箱、`POST /verify`、`GET /me`、签到、`PUT /profile`;可带 `X-Auth-Client` / `X-Auth-Client-Name` 记录第三方应用 | +| `/api/public/*` | 公开用户资料、注册策略(是否强制邀请码等) | +| `/api/admin/*` | 需 [`AdminMiddleware`](./sproutgate-backend/internal/handlers/admin.go):请求头 `X-Admin-Token` 或 Query `token`,与用户 JWT 分离 | -完整契约见 [`sproutgate-backend/API_DOCS.md`](./sproutgate-backend/API_DOCS.md)(文内 **「统一登录前端:查询参数」**、**「回跳 URL」**、**`POST /api/auth/login` / `verify` / `me`** 等章节为第三方接入主参考)。 +**运维与文档**:`GET /`、`GET /api`(JSON 服务说明)、`GET /api/health`、`GET /api/docs`(返回仓库内 `API_DOCS.md`)。 + +--- + +## 前端代码结构(`sproutgate-frontend/`) + +| 路径 | 说明 | +|------|------| +| [`src/App.jsx`](./sproutgate-frontend/src/App.jsx) | 根组件:根据路径渲染门户 / 公开用户页 / 管理端;顶栏 **连点 Logo** 进入管理员流程 | +| [`src/config.js`](./sproutgate-frontend/src/config.js) | `VITE_API_BASE`、开发与生产默认 API 地址 | +| [`src/components/UserPortal.jsx`](./sproutgate-frontend/src/components/UserPortal.jsx) | `/`:登录、注册、邮件验证、`redirect_uri` 统一登录回跳等 | +| [`src/components/PublicUserPage.jsx`](./sproutgate-frontend/src/components/PublicUserPage.jsx) | `/user`、`/user/:account`:公开资料 | +| [`src/components/AdminPanel.jsx`](./sproutgate-frontend/src/components/AdminPanel.jsx) | `/admin`:用户与签到、注册策略管理 | + +--- + +## 认证与安全(摘要) + +1. **用户会话**:登录成功后签发 JWT(`Authorization: Bearer`);受保护接口解析 JWT 后按 `account` 加载用户;封禁等状态见 handlers 内校验。 +2. **令牌校验**:`POST /api/auth/verify` 供第三方后端校验用户 token(见 API 文档)。 +3. **管理员**:与业务 JWT 独立,使用 **`X-Admin-Token`**(或 Query `token`)与 `data/config/admin.json` 比对。 +4. **第三方统一登录门户**:入口 URL 查询参数 `redirect_uri`、`client_id`、`client_name` 等;前端通过 `sessionStorage` 与请求头 **`X-Auth-Client`** / **`X-Auth-Client-Name`** 与后端协同(CORS 已允许上述头)。 + +部署到公网前:**修改默认 JWT 密钥、管理员 Token、SMTP 密码**,且勿将含真实密钥的 `data/config` 提交到公开仓库。 + +--- ## 环境要求 -- **后端**:Go 1.21+(以 `go.mod` 为准) -- **前端**:Node.js 18+(建议 LTS) +- **后端**:Go **1.20+**(以 [`sproutgate-backend/go.mod`](./sproutgate-backend/go.mod) 为准) +- **前端**:Node.js **18+**(建议 LTS) + +--- ## 快速开始 @@ -49,7 +102,7 @@ chmod +x sproutgate.sh ./sproutgate.sh dev ``` -会启动后端 `go run .` 与前端 `npm run dev`(前端默认 `5173`,后端默认 `8080`)。 +会启动后端 `go run .` 与前端 `npm run dev`(前端默认 **5173**,后端默认 **8080**)。 仅构建前端: @@ -87,7 +140,7 @@ npm run dev VITE_API_BASE=http://localhost:8080 ``` -生产环境改为实际 API 地址即可。 +生产环境改为实际 API 地址(如 `https://auth.api.example.com`)。 ### 配置与安全 @@ -96,24 +149,33 @@ VITE_API_BASE=http://localhost:8080 ### 可选:Docker 仅跑后端 API -在 `sproutgate-backend` 目录: +在 [`sproutgate-backend`](./sproutgate-backend) 目录: ```bash docker compose up -d --build ``` -默认将容器内 `8080` 映射到主机 `28080`(可通过环境变量 `AUTH_API_PORT` 修改)。数据目录通过卷挂载到 `./data`。 +默认将容器内 `8080` 映射到主机 **`28080`**(可通过环境变量 **`AUTH_API_PORT`** 修改)。数据目录通过卷挂载到 **`./data`**。 + +--- ## 环境变量(后端) | 变量 | 说明 | |------|------| | `PORT` | 监听端口,默认 `8080` | -| `DATA_DIR` | 数据根目录;不设置时使用仓库内默认 `data` 布局 | +| `DATA_DIR` | 数据根目录;不设置时使用仓库内相对路径下的 `data` 布局 | -## 仓库维护说明 +--- -更细的目录约定、代码风格与 PR 建议见 **[`AGENTS.md`](./AGENTS.md)**。 +## 相关文档 + +| 文档 | 内容 | +|------|------| +| [`sproutgate-backend/API_DOCS.md`](./sproutgate-backend/API_DOCS.md) | HTTP 接口契约(第三方接入主参考) | +| [`AGENTS.md`](./AGENTS.md) | 目录约定、协作与 PR 建议 | + +--- ## 许可证 diff --git a/sproutgate.bat b/dev.bat similarity index 100% rename from sproutgate.bat rename to dev.bat diff --git a/sproutgate.sh b/dev.sh similarity index 100% rename from sproutgate.sh rename to dev.sh diff --git a/sproutgate-backend/internal/storage/storage.go b/sproutgate-backend/internal/storage/storage.go index 4c9925d..7034705 100644 --- a/sproutgate-backend/internal/storage/storage.go +++ b/sproutgate-backend/internal/storage/storage.go @@ -37,23 +37,23 @@ type CheckInConfig struct { } type Store struct { - dataDir string - usersDir string - pendingDir string - resetDir string - secondaryDir string - adminConfigPath string - authConfigPath string - emailConfigPath string - checkInPath string - registrationPath string + dataDir string + usersDir string + pendingDir string + resetDir string + secondaryDir string + adminConfigPath string + authConfigPath string + emailConfigPath string + checkInPath string + registrationPath string registrationConfig RegistrationConfig - adminToken string - jwtSecret []byte - issuer string - emailConfig EmailConfig - checkInConfig CheckInConfig - mu sync.Mutex + adminToken string + jwtSecret []byte + issuer string + emailConfig EmailConfig + checkInConfig CheckInConfig + mu sync.Mutex } func NewStore(dataDir string) (*Store, error) { @@ -85,16 +85,16 @@ func NewStore(dataDir string) (*Store, error) { return nil, err } store := &Store{ - dataDir: absDir, - usersDir: usersDir, - pendingDir: pendingDir, - resetDir: resetDir, - secondaryDir: secondaryDir, - adminConfigPath: filepath.Join(configDir, "admin.json"), - authConfigPath: filepath.Join(configDir, "auth.json"), - emailConfigPath: filepath.Join(configDir, "email.json"), - checkInPath: filepath.Join(configDir, "checkin.json"), - registrationPath: filepath.Join(configDir, "registration.json"), + dataDir: absDir, + usersDir: usersDir, + pendingDir: pendingDir, + resetDir: resetDir, + secondaryDir: secondaryDir, + adminConfigPath: filepath.Join(configDir, "admin.json"), + authConfigPath: filepath.Join(configDir, "auth.json"), + emailConfigPath: filepath.Join(configDir, "email.json"), + checkInPath: filepath.Join(configDir, "checkin.json"), + registrationPath: filepath.Join(configDir, "registration.json"), } if err := store.loadOrCreateAdminConfig(); err != nil { return nil, err diff --git a/sproutgate-backend/main.go b/sproutgate-backend/main.go index d582fda..9dbaf47 100644 --- a/sproutgate-backend/main.go +++ b/sproutgate-backend/main.go @@ -17,7 +17,7 @@ func main() { dataDir := os.Getenv("DATA_DIR") store, err := storage.NewStore(dataDir) if err != nil { - log.Fatalf("failed to init storage: %v", err) + log.Fatalf("初始化储存失败: %v", err) } router := gin.Default() diff --git a/项目面试询问相关.md b/项目面试询问相关.md new file mode 100644 index 0000000..e69de29