chore: 重构数据库迁移与 nginx 配置目录

This commit is contained in:
2026-06-24 21:58:50 +08:00
parent 0d8377c2fd
commit 5e0c1dd2c1
11 changed files with 91 additions and 45 deletions

View File

@@ -33,6 +33,7 @@
## Security & Configuration Tips
- Backend configuration is stored under `sproutgate-backend/data/config/` (admin, auth, email, check-in, registration invites).
- Backend MySQL connection comes only from environment variables (`DB_DSN`, or `DB_HOST`/`DB_PORT`/`DB_USER`/`DB_PASSWORD`/`DB_NAME`); no credentials are hardcoded. Set them in `sproutgate-backend/.env` (gitignored) for local dev — it's auto-loaded on `go run .`.
- Frontend API base URL can be set via `sproutgate-frontend/.env` using `VITE_API_BASE`.
- Avoid committing real credentials or production tokens; use local overrides for secrets.
- API reference lives in `sproutgate-backend/API_DOCS.md` and is served at `GET /api/docs`. Brief JSON overview: `GET /` and `GET /api`.

View File

@@ -1,5 +0,0 @@
@echo off
setlocal
cd /d "%~dp0"
call "%~dp0build-linux-amd64.bat"
exit /b %errorlevel%

View File

@@ -22,15 +22,16 @@ echo [INFO] Working directory: %CD%
echo [INFO] Building %APP_NAME% for linux amd64...
echo [INFO] Output: %OUTPUT_FILE%
set "CGO_ENABLED=0"
set "GOOS=linux"
set "GOARCH=amd64"
set "GOCACHE=%GOCACHE_DIR%"
echo [INFO] Regenerating Swagger docs (swag)...
go run github.com/swaggo/swag/cmd/swag@latest init -g main.go -o docs --parseInternal
if errorlevel 1 goto fail_build
set "CGO_ENABLED=0"
set "GOOS=linux"
set "GOARCH=amd64"
go build -trimpath -ldflags "-s -w" -o "%OUTPUT_FILE%" .
if errorlevel 1 goto fail_build

View File

@@ -6,8 +6,8 @@
//
// 环境变量(与主程序相同):
//
// APP_ENV=production 使用生产数据库(默认使用开发测试库
// DB_DSN 自定义 DSN优先级最高
// DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME 连接参数DB_NAME 默认 sproutgate
// DB_DSN 自定义 DSN优先级最高
package main
import (

View File

@@ -8,6 +8,11 @@ services:
environment:
PORT: "8080"
DATA_DIR: /data
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT:-3306}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME:-sproutgate}
volumes:
- ./data:/data
ports:

View File

@@ -4,12 +4,40 @@ import (
"fmt"
"log"
"os"
"strings"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// loadDotEnv 在当前工作目录查找 .env 文件并加载到进程环境变量中(已存在的变量不覆盖)。
// 仅用于本地开发场景:生产部署通过 docker-compose / 容器环境变量直接注入,没有 .env 文件时静默跳过。
func loadDotEnv() {
data, err := os.ReadFile(".env")
if err != nil {
return
}
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, ok := strings.Cut(line, "=")
if !ok {
continue
}
key = strings.TrimSpace(key)
if key == "" {
continue
}
if _, exists := os.LookupEnv(key); exists {
continue
}
os.Setenv(key, strings.TrimSpace(value))
}
}
// DBConfig holds MySQL connection parameters.
type DBConfig struct {
Host string
@@ -24,39 +52,42 @@ func (c DBConfig) DSN() string {
c.User, c.Password, c.Host, c.Port, c.DBName)
}
func devConfig() DBConfig {
return DBConfig{
Host: "10.1.1.100",
Port: "3306",
User: "sproutgate-test",
Password: "sproutgate-test",
DBName: "sproutgate-test",
// envConfig 从环境变量读取连接参数。Host/User/Password 没有内置默认值,必须由
// 部署环境(本地 shell、.env、docker-compose 等显式提供DBName 默认统一为
// "sproutgate",开发和生产环境共用同一个库名。
func envConfig() (DBConfig, error) {
host := os.Getenv("DB_HOST")
user := os.Getenv("DB_USER")
password := os.Getenv("DB_PASSWORD")
if host == "" || user == "" || password == "" {
return DBConfig{}, fmt.Errorf("缺少数据库环境变量,请设置 DB_HOST/DB_USER/DB_PASSWORD或直接设置 DB_DSN")
}
}
func prodConfig() DBConfig {
return DBConfig{
Host: "192.168.1.100",
Port: "3306",
User: "sproutgate",
Password: "sproutgate",
DBName: "sproutgate",
port := os.Getenv("DB_PORT")
if port == "" {
port = "3306"
}
dbName := os.Getenv("DB_NAME")
if dbName == "" {
dbName = "sproutgate"
}
return DBConfig{Host: host, Port: port, User: user, Password: password, DBName: dbName}, nil
}
// Open 根据环境变量打开数据库连接。
// 优先级DB_DSN > APP_ENV(production/prod -> 生产库) > 默认开发库
// 优先级DB_DSN(完整自定义连接串)> DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME
func Open() (*gorm.DB, error) {
loadDotEnv()
dsn := os.Getenv("DB_DSN")
if dsn == "" {
env := os.Getenv("APP_ENV")
if env == "production" || env == "prod" {
dsn = prodConfig().DSN()
log.Println("[database] 使用生产数据库 192.168.1.100:3306/sproutgate")
} else {
dsn = devConfig().DSN()
log.Println("[database] 使用开发数据库 10.1.1.100:3306/sproutgate-test")
cfg, err := envConfig()
if err != nil {
return nil, err
}
dsn = cfg.DSN()
log.Printf("[database] 使用数据库 %s:%s/%s", cfg.Host, cfg.Port, cfg.DBName)
} else {
log.Println("[database] 使用自定义 DB_DSN")
}

View File

@@ -15,7 +15,7 @@
| 路径 | 说明 |
|------|------|
| `main.go` | 入口:连接数据库、初始化 `Store`、注册路由 |
| `internal/database/` | MySQL 连接与环境选择`DB_DSN` / `APP_ENV` |
| `internal/database/` | MySQL 连接(`DB_DSN` `DB_HOST`/`DB_USER`/`DB_PASSWORD`/`DB_NAME`/`DB_PORT`,支持 `.env` 本地加载 |
| `internal/handlers/` | HTTP 处理:认证、资料、签到、公开页、管理端等 |
| `internal/models/` | 领域模型(用户、待激活、重置密码、辅助邮箱等) |
| `internal/storage/` | 持久化:`Store` + GORM 模型与业务方法 |
@@ -25,24 +25,35 @@
## 环境与数据库
连接字符串优先级:**`DB_DSN` > 内置规则**。
代码中**不再内置任何数据库地址或账号密码**,连接参数完全来自环境变量,连接字符串优先级:**`DB_DSN` > `DB_HOST`/`DB_PORT`/`DB_USER`/`DB_PASSWORD`/`DB_NAME`**。
1. **若设置 `DB_DSN`**:直接使用该完整 DSN适合 CI、容器或临时切换
2. **否则**根据 `APP_ENV` 选择内置库:
- `APP_ENV=production``prod` → 生产库:`192.168.1.100:3306` / 库名 `sproutgate` / 用户 `sproutgate`
- 未设置或其它值 → 开发/测试库:`10.1.1.100:3306` / 库名 `sproutgate-test` / 用户 `sproutgate-test`
2. **否则** `DB_HOST`/`DB_USER`/`DB_PASSWORD` 拼接 DSN`DB_HOST`/`DB_USER`/`DB_PASSWORD` 三者缺一即报错退出;`DB_PORT` 默认 `3306``DB_NAME` 默认 `sproutgate`(开发、生产统一用这个库名,仅 host/账号不同)。
本地开发时,在 `sproutgate-backend/.env`(已在 `.gitignore` 中忽略,不会提交)里写:
```
DB_HOST=10.1.1.100
DB_PORT=3306
DB_USER=sproutgate-test
DB_PASSWORD=sproutgate-test
DB_NAME=sproutgate
```
`go run .` / `go run ./cmd/migrate` 启动时会自动从当前目录读取并加载 `.env`(已存在的环境变量不会被覆盖);生产部署通过容器/`docker-compose` 环境变量注入,不依赖 `.env` 文件。
其它常用环境变量:
| 变量 | 说明 | 默认 |
|------|------|------|
| `PORT` | HTTP 监听端口 | `8080` |
| `APP_ENV` | 影响默认 MySQL 选择与 `/api/health` 返回的 `env` | 空则 health 中为 `development` |
| `APP_ENV` | 影响 `/api/health` 返回的 `env` 字段,不再决定数据库选择 | 空则 health 中为 `development` |
| `GIN_MODE` | `release` 时 GORM 日志级别更安静(警告级) | debug 模式 |
| `DB_DSN` | 完整 MySQL DSN覆盖上述内置地址 | 未设置 |
| `DB_DSN` | 完整 MySQL DSN覆盖 `DB_HOST` 等单项配置 | 未设置 |
| `DB_HOST` / `DB_PORT` / `DB_USER` / `DB_PASSWORD` / `DB_NAME` | MySQL 连接参数;`DB_HOST`/`DB_USER`/`DB_PASSWORD` 必填,无内置默认值 | `DB_PORT=3306``DB_NAME=sproutgate` |
| `GEO_LOOKUP_URL` | `GET /api/auth/me` 未带 `X-Visit-Location` 时,服务端按 IP 请求该基址反查展示地理位置(实现见 `internal/clientgeo`,会拼接 `?ip=` | 默认 `https://cf-ip-geo.smyhub.com/api` |
**建议**:生产部署时设置 `APP_ENV=production`,并确保 MySQL 防火墙与账号权限正确;敏感连接信息也可只通过 `DB_DSN` 注入,无需改代码
**建议**:生产部署通过 `docker-compose.yml` 旁的 `.env`(同样被 `.gitignore` 忽略)注入 `DB_HOST`/`DB_USER`/`DB_PASSWORD`,不要把真实账号密码写进任何会提交到仓库的文件
## 数据库表AutoMigrate
@@ -66,16 +77,18 @@
**`sproutgate-backend`** 目录下:
```bash
# 导入到当前环境对应的库(默认开发库,除非设置 APP_ENV 或 DB_DSN
# 导入到 .env或当前环境变量指向的库
go run ./cmd/migrate --data-dir ./data
```
Windows PowerShell 示例(写入生产库
Windows PowerShell 示例(临时指定生产库连接参数
```powershell
$env:APP_ENV = "production"
$env:DB_HOST = "192.168.1.100"
$env:DB_USER = "sproutgate"
$env:DB_PASSWORD = "<生产密码>"
go run ./cmd/migrate --data-dir ./data
Remove-Item Env:APP_ENV # 可选:清除变量,避免影响本机其它命令
Remove-Item Env:DB_HOST, Env:DB_USER, Env:DB_PASSWORD # 可选:清除变量,避免影响本机其它命令
```
迁移内容:`data/config/*.json`(写入 `app_configs``invite_codes`)、`data/users/*.json`(写入 `users`)。对已存在主键执行 **UPSERT**:同账号、同配置键会更新为迁移文件中的值。