update: 2026-03-28 21:00

This commit is contained in:
2026-03-28 21:00:27 +08:00
parent e6866feb29
commit 90590c7cb0
6 changed files with 114 additions and 52 deletions

112
README.md
View File

@@ -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 建议 |
---
## 许可证

View File

@@ -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

View File

@@ -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()

View File