后端架构
设计目标
零外部依赖、单二进制、生产可用、性能足够
- 不依赖 Docker、Redis、Postgres
go build后 ≤ 30MB 单文件- 100 并发 P95 < 50ms
- 6300 行代码总规模
技术栈
| 组件 | 选型 | 版本 |
|---|---|---|
| 语言 | Go | 1.25 |
| HTTP | net/http 标准库 | - |
| 数据库 | SQLite | 3.x |
| SQLite 驱动 | modernc.org/sqlite | 纯 Go,无 CGo |
| 路由 | http.ServeMux(Go 1.22+ 增强版) | - |
| 密码哈希 | golang.org/x/crypto/bcrypt | - |
目录结构
text
backend/
├── main.go # 入口、路由注册
├── go.mod
├── go.sum
├── auth.go # 认证、Session、Cookie
├── quota.go # 三类配额管理
├── handlers.go # API Handler
├── providers.go # 多供应商路由
├── graphrag.go # GraphRAG 引擎
├── db.go # SQLite 连接 + 迁移
├── reaper.go # 配额回滚 goroutine
├── data/
│ └── app.db # 自动生成的 SQLite 文件
└── migrations/
├── 0001_init_users.sql
├── 0002_sessions.sql
├── 0003_quotas.sql
├── 0004_providers.sql
├── 0005_model_configs.sql
├── 0006_activation_keys.sql
├── 0007_request_logs.sql
├── 0008_kg_entities.sql
├── 0009_kg_relations.sql
├── 0010_indexes.sql
├── 0011_constraints.sql
└── 0012_seed.sql启动流程
go
// main.go
func main() {
// 1. 打开数据库
db := openDatabase("./data/app.db")
defer db.Close()
// 2. 自动执行所有迁移
runMigrations(db, "./migrations")
// 3. 启动配额回滚 goroutine
go startReaper(db, 30*time.Second)
// 4. 注册路由
mux := http.NewServeMux()
registerRoutes(mux, db)
// 5. 静态文件托管(生产模式)
mux.Handle("/", http.FileServer(http.Dir("../frontend/dist")))
// 6. 启动 HTTP 服务
log.Println("Starting on :8788")
log.Fatal(http.ListenAndServe(":8788", mux))
}路由表
go
POST /api/auth/login // 普通用户登录
POST /api/admin/auth/login // 管理员登录
POST /api/auth/logout
GET /api/mego
POST /api/generate/image/prepare // 配额预扣
POST /api/generate/image/complete // 扣次/回滚
POST /api/generate/copy // Listing 文案
POST /api/generate/prompt // 提示词智能生成
POST /api/generate/product-analysis // 选品对话
POST /api/generate/video // 视频生成
GET /api/generate/image/providers // 获取可用供应商go
GET /api/admin/users
POST /api/admin/users
PATCH /api/admin/users/:id
GET /api/admin/providers
POST /api/admin/providers
PATCH /api/admin/providers/:id
GET /api/admin/model-configs
PUT /api/admin/model-configs
POST /api/admin/model-configs
GET /api/admin/activation-keys
POST /api/admin/activation-keys
PATCH /api/admin/activation-keys/:id
POST /api/activation/redeem中间件
go
// 中间件链:日志 → CORS → 认证 → 业务
func chain(handler http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
// 注册示例
mux.HandleFunc("/api/me", chain(
handleMe,
loggingMiddleware,
corsMiddleware,
authMiddleware,
))
mux.HandleFunc("/api/admin/users", chain(
handleAdminUsers,
loggingMiddleware,
corsMiddleware,
authMiddleware,
requireAdminMiddleware, // 只有 admin 才能进入
))认证与 Session
Cookie 配置
go
http.SetCookie(w, &http.Cookie{
Name: "hyzc_session",
Value: plaintextToken, // 仅在浏览器
HttpOnly: true, // JS 无法读取
Secure: true, // HTTPS Only(生产)
SameSite: http.SameSiteStrictMode, // 防 CSRF
Path: "/",
MaxAge: 7 * 24 * 3600, // 7 天
})
// 数据库只存 hash
hash := sha256.Sum256([]byte(plaintextToken))
db.Exec("INSERT INTO sessions(token_hash, user_id) VALUES(?, ?)",
hex.EncodeToString(hash[:]), userID)安全设计
- 明文 token 仅存浏览器 Cookie,服务端只存 SHA-256 hash
- 即使数据库被拖库,攻击者也无法登录现有用户
- bcrypt 哈希密码(cost=10)
配额管理
三类配额表
sql
CREATE TABLE quotas (
user_id TEXT PRIMARY KEY,
image_total INTEGER NOT NULL DEFAULT 0,
image_used INTEGER NOT NULL DEFAULT 0,
copy_total INTEGER NOT NULL DEFAULT 0,
copy_used INTEGER NOT NULL DEFAULT 0,
video_total INTEGER NOT NULL DEFAULT 0,
video_used INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE usage_logs (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
quota_type TEXT NOT NULL, -- image / copy / video
amount INTEGER NOT NULL, -- 扣减数量
status TEXT NOT NULL, -- pending / success / failed
created_at TEXT NOT NULL,
completed_at TEXT
);两段式协议

关键交互时序图:

go
// Phase 1: 预扣
func handlePrepareImage(w http.ResponseWriter, r *http.Request) {
user := getUser(r)
tx, _ := db.Begin()
defer tx.Rollback()
// 检查并预扣
res, _ := tx.Exec(`
UPDATE quotas
SET image_used = image_used + 1
WHERE user_id = ? AND image_used < image_total
`, user.ID)
affected, _ := res.RowsAffected()
if affected == 0 {
http.Error(w, "Quota exhausted", 429)
return
}
// 写 pending 日志
ticket := uuid.New().String()
tx.Exec(`
INSERT INTO usage_logs(id, user_id, quota_type, amount, status, created_at)
VALUES(?, ?, 'image', 1, 'pending', ?)
`, ticket, user.ID, time.Now())
tx.Commit()
// 返回供应商配置 + ticket
json.NewEncoder(w).Encode(map[string]interface{}{
"ticket": ticket,
"provider": chooseProvider("image"),
})
}
// Phase 2: 完成
func handleCompleteImage(w http.ResponseWriter, r *http.Request) {
var req struct {
Ticket string `json:"ticket"`
Status string `json:"status"` // success / failed
}
json.NewDecoder(r.Body).Decode(&req)
tx, _ := db.Begin()
defer tx.Rollback()
if req.Status == "failed" {
// 回滚配额
tx.Exec(`
UPDATE quotas
SET image_used = image_used - 1
WHERE user_id = (SELECT user_id FROM usage_logs WHERE id = ?)
`, req.Ticket)
}
// 更新日志状态
tx.Exec(`
UPDATE usage_logs
SET status = ?, completed_at = ?
WHERE id = ?
`, req.Status, time.Now(), req.Ticket)
tx.Commit()
w.WriteHeader(204)
}Reaper Goroutine
go
// reaper.go
func startReaper(db *sql.DB, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
sweepStalePending(db)
}
}
func sweepStalePending(db *sql.DB) {
tx, _ := db.Begin()
defer tx.Rollback()
// 找到所有 pending 超过 30 秒的日志
rows, _ := tx.Query(`
SELECT id, user_id, quota_type, amount FROM usage_logs
WHERE status = 'pending'
AND created_at < datetime('now', '-30 seconds')
`)
type stale struct{ ID, UserID, QType string; Amount int }
var stales []stale
for rows.Next() {
var s stale
rows.Scan(&s.ID, &s.UserID, &s.QType, &s.Amount)
stales = append(stales, s)
}
rows.Close()
for _, s := range stales {
// 标记 failed
tx.Exec(`UPDATE usage_logs SET status = 'failed' WHERE id = ?`, s.ID)
// 回滚配额
col := s.QType + "_used"
tx.Exec(`UPDATE quotas SET ` + col + ` = ` + col + ` - ? WHERE user_id = ?`,
s.Amount, s.UserID)
}
tx.Commit()
log.Printf("Reaper swept %d stale entries", len(stales))
}多供应商路由
go
// providers.go
type Provider struct {
ID string
Type string
BaseURL string
APIKey string
Priority int
Weight int
Status string
}
func chooseProvider(category string) *Provider {
// 1. 取所有 active 的供应商
providers := loadActiveProviders(category)
// 2. 按 priority 升序分组
sort.Slice(providers, func(i, j int) bool {
return providers[i].Priority < providers[j].Priority
})
// 3. 取最小 priority 组
minPri := providers[0].Priority
var topGroup []*Provider
for _, p := range providers {
if p.Priority == minPri {
topGroup = append(topGroup, p)
} else {
break
}
}
// 4. 在该组内按 weight 加权随机
totalWeight := 0
for _, p := range topGroup { totalWeight += p.Weight }
r := rand.Intn(totalWeight)
cum := 0
for _, p := range topGroup {
cum += p.Weight
if r < cum { return p }
}
return topGroup[0] // fallback
}数据库迁移
go
// db.go
func runMigrations(db *sql.DB, dir string) {
files, _ := filepath.Glob(filepath.Join(dir, "*.sql"))
sort.Strings(files)
// 创建迁移记录表
db.Exec(`CREATE TABLE IF NOT EXISTS migrations (
id TEXT PRIMARY KEY,
applied_at TEXT NOT NULL
)`)
for _, file := range files {
name := filepath.Base(file)
// 检查是否已应用
var count int
db.QueryRow("SELECT COUNT(*) FROM migrations WHERE id = ?", name).Scan(&count)
if count > 0 { continue }
// 执行 SQL
sql, _ := os.ReadFile(file)
if _, err := db.Exec(string(sql)); err != nil {
log.Fatalf("Migration %s failed: %v", name, err)
}
// 记录已应用
db.Exec("INSERT INTO migrations(id, applied_at) VALUES(?, ?)", name, time.Now())
log.Printf("Applied migration: %s", name)
}
}性能与可扩展性
| 指标 | 当前 | 扩展方案 |
|---|---|---|
| 单进程并发 | 100+ | Go runtime 自动调度 goroutine |
| 数据库锁 | SQLite WAL | 读写并发良好 |
| 横向扩展 | 单机 | 切到 Postgres + Redis 后可水平扩展 |
| 文件存储 | 本地磁盘 | 切到 S3 / OSS 后可分布式 |
测试
bash
# 后端测试
npm run test:backend
# 单独运行某个测试文件
cd backend && go test -v ./auth_test.go ./auth.go测试覆盖范围:
- ✅ 认证流程(登录、Cookie、SessionExpiry)
- ✅ 配额事务(预扣、回滚、并发安全)
- ✅ Reaper goroutine
- ✅ 激活码兑换(防重复、过期、上限)
- ✅ SQL 注入防护
- ✅ GraphRAG 实体抽取(基于 Mock LLM)
下一步
- 📦 数据库设计 —— 13 张主表 ER 图
- 🔌 API 接口 —— 完整 API 文档
- 🧠 GraphRAG 框架 —— 核心创新模块