chore: 重构数据库迁移与 nginx 配置目录
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
## Security & Configuration Tips
|
## Security & Configuration Tips
|
||||||
- Backend configuration is stored under `sproutgate-backend/data/config/` (admin, auth, email, check-in, registration invites).
|
- 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`.
|
- 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.
|
- 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`.
|
- API reference lives in `sproutgate-backend/API_DOCS.md` and is served at `GET /api/docs`. Brief JSON overview: `GET /` and `GET /api`.
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal
|
|
||||||
cd /d "%~dp0"
|
|
||||||
call "%~dp0build-linux-amd64.bat"
|
|
||||||
exit /b %errorlevel%
|
|
||||||
@@ -22,15 +22,16 @@ echo [INFO] Working directory: %CD%
|
|||||||
echo [INFO] Building %APP_NAME% for linux amd64...
|
echo [INFO] Building %APP_NAME% for linux amd64...
|
||||||
echo [INFO] Output: %OUTPUT_FILE%
|
echo [INFO] Output: %OUTPUT_FILE%
|
||||||
|
|
||||||
set "CGO_ENABLED=0"
|
|
||||||
set "GOOS=linux"
|
|
||||||
set "GOARCH=amd64"
|
|
||||||
set "GOCACHE=%GOCACHE_DIR%"
|
set "GOCACHE=%GOCACHE_DIR%"
|
||||||
|
|
||||||
echo [INFO] Regenerating Swagger docs (swag)...
|
echo [INFO] Regenerating Swagger docs (swag)...
|
||||||
go run github.com/swaggo/swag/cmd/swag@latest init -g main.go -o docs --parseInternal
|
go run github.com/swaggo/swag/cmd/swag@latest init -g main.go -o docs --parseInternal
|
||||||
if errorlevel 1 goto fail_build
|
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%" .
|
go build -trimpath -ldflags "-s -w" -o "%OUTPUT_FILE%" .
|
||||||
if errorlevel 1 goto fail_build
|
if errorlevel 1 goto fail_build
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
//
|
//
|
||||||
// 环境变量(与主程序相同):
|
// 环境变量(与主程序相同):
|
||||||
//
|
//
|
||||||
// APP_ENV=production 使用生产数据库(默认使用开发测试库)
|
// DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME 连接参数(DB_NAME 默认 sproutgate)
|
||||||
// DB_DSN 自定义 DSN(优先级最高)
|
// DB_DSN 自定义 DSN(优先级最高)
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
PORT: "8080"
|
PORT: "8080"
|
||||||
DATA_DIR: /data
|
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:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -4,12 +4,40 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"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.
|
// DBConfig holds MySQL connection parameters.
|
||||||
type DBConfig struct {
|
type DBConfig struct {
|
||||||
Host string
|
Host string
|
||||||
@@ -24,39 +52,42 @@ func (c DBConfig) DSN() string {
|
|||||||
c.User, c.Password, c.Host, c.Port, c.DBName)
|
c.User, c.Password, c.Host, c.Port, c.DBName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func devConfig() DBConfig {
|
// envConfig 从环境变量读取连接参数。Host/User/Password 没有内置默认值,必须由
|
||||||
return DBConfig{
|
// 部署环境(本地 shell、.env、docker-compose 等)显式提供;DBName 默认统一为
|
||||||
Host: "10.1.1.100",
|
// "sproutgate",开发和生产环境共用同一个库名。
|
||||||
Port: "3306",
|
func envConfig() (DBConfig, error) {
|
||||||
User: "sproutgate-test",
|
host := os.Getenv("DB_HOST")
|
||||||
Password: "sproutgate-test",
|
user := os.Getenv("DB_USER")
|
||||||
DBName: "sproutgate-test",
|
password := os.Getenv("DB_PASSWORD")
|
||||||
|
if host == "" || user == "" || password == "" {
|
||||||
|
return DBConfig{}, fmt.Errorf("缺少数据库环境变量,请设置 DB_HOST/DB_USER/DB_PASSWORD(或直接设置 DB_DSN)")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func prodConfig() DBConfig {
|
port := os.Getenv("DB_PORT")
|
||||||
return DBConfig{
|
if port == "" {
|
||||||
Host: "192.168.1.100",
|
port = "3306"
|
||||||
Port: "3306",
|
|
||||||
User: "sproutgate",
|
|
||||||
Password: "sproutgate",
|
|
||||||
DBName: "sproutgate",
|
|
||||||
}
|
}
|
||||||
|
dbName := os.Getenv("DB_NAME")
|
||||||
|
if dbName == "" {
|
||||||
|
dbName = "sproutgate"
|
||||||
|
}
|
||||||
|
|
||||||
|
return DBConfig{Host: host, Port: port, User: user, Password: password, DBName: dbName}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open 根据环境变量打开数据库连接。
|
// Open 根据环境变量打开数据库连接。
|
||||||
// 优先级:DB_DSN > APP_ENV(production/prod -> 生产库) > 默认开发库
|
// 优先级:DB_DSN(完整自定义连接串)> DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME
|
||||||
func Open() (*gorm.DB, error) {
|
func Open() (*gorm.DB, error) {
|
||||||
|
loadDotEnv()
|
||||||
|
|
||||||
dsn := os.Getenv("DB_DSN")
|
dsn := os.Getenv("DB_DSN")
|
||||||
if dsn == "" {
|
if dsn == "" {
|
||||||
env := os.Getenv("APP_ENV")
|
cfg, err := envConfig()
|
||||||
if env == "production" || env == "prod" {
|
if err != nil {
|
||||||
dsn = prodConfig().DSN()
|
return nil, err
|
||||||
log.Println("[database] 使用生产数据库 192.168.1.100:3306/sproutgate")
|
|
||||||
} else {
|
|
||||||
dsn = devConfig().DSN()
|
|
||||||
log.Println("[database] 使用开发数据库 10.1.1.100:3306/sproutgate-test")
|
|
||||||
}
|
}
|
||||||
|
dsn = cfg.DSN()
|
||||||
|
log.Printf("[database] 使用数据库 %s:%s/%s", cfg.Host, cfg.Port, cfg.DBName)
|
||||||
} else {
|
} else {
|
||||||
log.Println("[database] 使用自定义 DB_DSN")
|
log.Println("[database] 使用自定义 DB_DSN")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
| 路径 | 说明 |
|
| 路径 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `main.go` | 入口:连接数据库、初始化 `Store`、注册路由 |
|
| `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/handlers/` | HTTP 处理:认证、资料、签到、公开页、管理端等 |
|
||||||
| `internal/models/` | 领域模型(用户、待激活、重置密码、辅助邮箱等) |
|
| `internal/models/` | 领域模型(用户、待激活、重置密码、辅助邮箱等) |
|
||||||
| `internal/storage/` | 持久化:`Store` + GORM 模型与业务方法 |
|
| `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、容器或临时切换)。
|
1. **若设置 `DB_DSN`**:直接使用该完整 DSN(适合 CI、容器或临时切换)。
|
||||||
2. **否则**:根据 `APP_ENV` 选择内置库:
|
2. **否则**:用 `DB_HOST`/`DB_USER`/`DB_PASSWORD` 拼接 DSN,`DB_HOST`/`DB_USER`/`DB_PASSWORD` 三者缺一即报错退出;`DB_PORT` 默认 `3306`,`DB_NAME` 默认 `sproutgate`(开发、生产统一用这个库名,仅 host/账号不同)。
|
||||||
- `APP_ENV=production` 或 `prod` → 生产库:`192.168.1.100:3306` / 库名 `sproutgate` / 用户 `sproutgate`
|
|
||||||
- 未设置或其它值 → 开发/测试库:`10.1.1.100:3306` / 库名 `sproutgate-test` / 用户 `sproutgate-test`
|
本地开发时,在 `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` |
|
| `PORT` | HTTP 监听端口 | `8080` |
|
||||||
| `APP_ENV` | 影响默认 MySQL 选择与 `/api/health` 返回的 `env` | 空则 health 中为 `development` |
|
| `APP_ENV` | 仅影响 `/api/health` 返回的 `env` 字段,不再决定数据库选择 | 空则 health 中为 `development` |
|
||||||
| `GIN_MODE` | `release` 时 GORM 日志级别更安静(警告级) | debug 模式 |
|
| `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` |
|
| `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)
|
## 数据库表(AutoMigrate)
|
||||||
|
|
||||||
@@ -66,16 +77,18 @@
|
|||||||
在 **`sproutgate-backend`** 目录下:
|
在 **`sproutgate-backend`** 目录下:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 导入到当前环境对应的库(默认开发库,除非设置 APP_ENV 或 DB_DSN)
|
# 导入到 .env(或当前环境变量)指向的库
|
||||||
go run ./cmd/migrate --data-dir ./data
|
go run ./cmd/migrate --data-dir ./data
|
||||||
```
|
```
|
||||||
|
|
||||||
Windows PowerShell 示例(写入生产库):
|
Windows PowerShell 示例(临时指定生产库连接参数):
|
||||||
|
|
||||||
```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
|
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**:同账号、同配置键会更新为迁移文件中的值。
|
迁移内容:`data/config/*.json`(写入 `app_configs` 与 `invite_codes`)、`data/users/*.json`(写入 `users`)。对已存在主键执行 **UPSERT**:同账号、同配置键会更新为迁移文件中的值。
|
||||||
|
|||||||
Reference in New Issue
Block a user