《FGO》影之国圣杯战线图文教程汇总 影之国的舞斗会圣杯战线图文操作步骤
2026-08-08
2026-08-13 0
大家好,我是 JavaPub。
最近又重新折腾了一下我自己的开源后台项目 ShiyuAdmin。
项目地址:
https://github.com/Rodert/ShiyuAdmin
这次主要做的一件事情,是给项目补上了 SQL Server 支持。

如果只是站在“能不能连接 SQL Server”的角度来看,这件事情其实非常简单。
Go 里面加一个驱动:
gorm.io/driver/sqlserver然后:
gorm.Open(sqlserver.Open(dsn))理论上数据库就连上了。
但对于一个已经存在用户、角色、菜单、权限、日志、数据管理、系统监控等模块的后台脚手架来说,真正的问题并不是:
而是:
这就是这次改造比较有意思的地方。
今天就借这个机会,聊聊现在 ShiyuAdmin 的整体架构,以及 SQL Server 是怎么接进去的。
ShiyuAdmin 本身定位并不是一个具体业务系统。
它更像是一套:
目前后端主要使用:
GoGinGormViperJWTRedis前端则是:
ReactTypeScriptUmi MaxAnt Design ProECharts项目采用标准的前后端分离模式。后端负责 API、权限、数据库和业务逻辑,前端负责管理后台 UI。
项目现在主要包含这些基础能力:
用户管理角色管理菜单管理部门管理RBAC 权限JWT 登录认证动态菜单超级管理员操作日志Redis 管理系统监控数据库管理数据仪表盘这些东西本质上都是后台系统绕不开的公共能力。
所以我做这个项目一直有一个思路:
真正做业务的时候,在这个脚手架上继续增加自己的业务模块就可以了。
现在 ShiyuAdmin 后端目录大概是这样的:
backend/shiyu-admin-backend├── cmd│ └── server├── configs├── internal│ ├── api│ ├── bootstrap│ ├── config│ ├── middleware│ ├── model│ ├── repository│ ├── server│ └── service├── migrations├── pkg│ ├── database│ ├── redis│ ├── jwtutil│ └── logger└── sql如果把它抽象一下,大致可以理解成:
React / Ant Design Pro││ HTTP▼Gin Router│Middleware LayerJWT / RBAC / Log / Trace│▼API Layer│▼Service Layer│▼Repository Layer│▼GORM│ ┌──────────────┼──────────────┐ ▼▼▼ PostgreSQL MySQLSQL Server│ SQLite这个结构看起来并不复杂。
但对后台脚手架来说,其实我反而比较喜欢这种架构。
因为:
比如用户管理接口,API 层应该解决的是:
接收参数参数校验调用 Service返回 JSONHTTP 状态处理而不应该在 API Handler 里面直接写:
db.Where(...)db.Create(...)db.Delete(...)否则随着业务越来越多,很快就会出现这种代码:
Handler ├── HTTP ├── SQL ├── 权限 ├── 业务判断 ├── 数据转换 └── 日志最后一个接口可能几百行。
所以 ShiyuAdmin 里面真正的业务逻辑主要继续往下走:
API ↓Service ↓RepositoryService 层做什么?
举个用户管理的例子。
比如:
创建用户它并不仅仅对应:
INSERT INTO sys_users还可能涉及:
检查用户名检查用户编码密码 BCrypt 加密部门是否合法分配角色数据权限记录日志所以这些东西应该属于 Service。
Service 不应该过于关心:
底层到底是 PostgreSQL还是 MySQL还是 SQL Server这也是这次接 SQL Server 能比较顺利的关键原因之一。
再向下一层就是 Repository。
现在 Server 初始化的时候,会统一创建各种 Repository:
authRepo = repoDB.NewAuthRepository(db)userRepo = repoDB.NewUserRepository(db)roleRepo = repoDB.NewRoleRepository(db)menuRepo = repoDB.NewMenuRepository(db)deptRepo = repoDB.NewDeptRepository(db)userRoleRepo = repoDB.NewUserRoleRepository(db)roleMenuRepo = repoDB.NewRoleMenuRepository(db)roleDeptRepo = repoDB.NewRoleDeptRepository(db)operationLogRepo = repoDB.NewOperationLogRepository(db)dbMetaRepo = repoDB.NewDBMetaRepository(db)然后再注入对应的 Service。
于是就形成了:
User API ↓User Service ↓User Repository ↓GORM这时候有一个非常重要的好处出现了。
上面的:
APIServiceRepository基本不用知道底下是什么数据库。
这就是多数据库架构最关键的一点。
这次 SQL Server 接入最核心的位置,其实在:
pkg/database/database.go数据库统一从:
database.Connect(cfg)进去。
然后根据:
cfg.Database.Driver决定加载哪个 GORM Dialector。
现在结构大概就是:
switch cfg.Database.Driver { case "postgres", "postgresql":dialector = postgres.Open(dsn)case "mysql":dialector = mysql.Open(dsn)case "sqlite":dialector = sqlite.Open(...)case "sqlserver", "mssql":dialector = sqlserver.Open(dsn)default:return nil, errors.Errorf("unsupported database driver: %s",cfg.Database.Driver,)}也就是说:
它只是在数据库基础设施层新增了一个 Dialector。
于是上层仍然拿到同样的:
*gorm.DB后面:
RepositoryServiceAPI基本不需要发生变化。
我认为这才是一套通用后台应该有的数据库接入方式。
一种比较容易出现的写法是:
if driver == "mysql" { }if driver == "postgres" { }if driver == "sqlserver" { }然后慢慢演变成:
if sqlserver { ...} else if mysql { ...} else { ...}如果这些判断出现在 Repository、Service 甚至 API 层,整个项目很快就废了。
因为增加一个数据库意味着:
改连接层改 Repository改 Service改 Handler改初始化而现在 ShiyuAdmin 的原则更接近:
database.Connect │ ┌─────────────┼─────────────┐ │ │ │PostgreSQL MySQL SQL Server │ │ │ └─────────────┼─────────────┘ │*gorm.DB │Repository │Service │ API数据库差异尽量截止在:
Database / Repository这一层。
PostgreSQL 的连接方式一般类似:
host=user=password=dbname=port=sslmode=MySQL 又是另外一套:
user:password@tcp(host:port)/databaseSQL Server 则使用:
sqlserver://username:password@host:1433所以项目里没有强行让所有数据库拼同一种 DSN。
SQL Server 使用 URL 形式构造:
dsnURL := &url.URL{ Scheme: "sqlserver",User: url.UserPassword(cfg.Database.Username,cfg.Database.Password,),Host: net.JoinHostPort(cfg.Database.Host,fmt.Sprintf("%d", cfg.Database.Port),),}然后增加:
query.Set("database", cfg.Database.Database)以及加密配置:
query.Set("encrypt", cfg.Database.SSLMode)最后:
sqlserver.Open(dsnURL.String())这样做还有个好处。
账号密码如果带一些特殊字符,不需要自己疯狂处理:
@:/#?URL 编码可以减少很多 DSN 拼接问题。
不管使用哪一种数据库,最终都会得到:
sqlDB, err := db.DB()然后统一设置连接池:
sqlDB.SetMaxOpenConns(50)sqlDB.SetMaxIdleConns(10)sqlDB.SetConnMaxLifetime(30 * time.Minute)所以现在:
PostgreSQLMySQLSQL ServerSQLite在应用层看到的都是:
GORM↓database/sql↓数据库驱动这也意味着后续如果需要做:
连接池配置化连接超时慢 SQLMetrics数据库健康检查都可以继续统一放到 Database 层,而不是污染业务代码。
SQL Server 已经有单独的配置:
configs/config.sqlserver.yaml例如:
database:driver: "sqlserver"host: "shiyu-sqlserver"port: 1433username: "sa"password: "******"database: "shiyu_admin_scaffold"ssl_mode: "disable"也就是说切换数据库本质变成:
换配置而不是改业务代码这点非常重要。
理想情况下,你甚至可以:
CONFIG_FILE=configs/config.sqlserver.yaml启动 SQL Server 环境。
换一个配置:
CONFIG_FILE=configs/config.mysql.yaml就是 MySQL。
整个应用架构不变。
目前项目已经不只是代码层支持多数据库,部署层也开始分离。
现在分别提供:
docker-compose.ymldocker-compose.mysql.ymldocker-compose.sqlserver.ymldocker-compose.sqlite.yml对应:
| 数据库 | 使用场景 |
|---|---|
| PostgreSQL | 默认生产环境 |
| MySQL 8.4 | 已有 MySQL 基础设施 |
| SQL Server 2022 | 企业 SQL Server 环境 |
| SQLite | 本地、演示、轻量部署 |
SQL Server 可以直接:
docker compose -f docker-compose.sqlserver.yml up -d启动。
这一点我觉得比“代码里支持 SQL Server”更重要。
因为一个开源脚手架真正要降低的是:
很多做互联网项目的同学可能会问:
原因其实很现实。
如果只看互联网创业项目:
MySQLPostgreSQL确实已经占据了绝大多数场景。
但是到了:
传统企业制造业ERP医院学校政府项目.NET 老系统内部管理系统SQL Server 依然非常常见。
尤其很多企业已经存在大量:
SQL ServerWindows Server.NETERPOAMES基础设施。
你不可能跟甲方说:
这显然不现实。
所以对于一套“通用后台脚手架”来说,多数据库支持其实不是炫技。
而是在解决一个非常现实的问题:
很多人会讨论:
如果是一个非常极致的高性能服务,我可能会倾向于:
sqlc原生 SQL手写 DAO但 ShiyuAdmin 的定位不一样。
它是:
这里更重要的目标是:
快速开发CRUD 效率代码一致性多数据库适配维护成本GORM 在这个场景里其实非常合适。
比如启动的时候项目统一执行:
db.AutoMigrate(&entity.User{ },&entity.Role{ },&entity.Menu{ },&entity.Dept{ },&entity.UserRole{ },&entity.RoleMenu{ },&entity.RoleDept{ },&entity.OperationLog{ },...)核心 RBAC 表可以直接根据 Entity 初始化。
所以:
同一套 Entity↓ GORM↓PostgreSQL / MySQL / SQL Server / SQLite这让多数据库维护成本低了非常多。
数据库之外,我觉得 ShiyuAdmin 另一个比较核心的设计就是 RBAC。
目前整体关系可以理解成:
User│▼UserRole│▼Role│├────────► RoleMenu ────────► Menu│└────────► RoleDept ────────► Dept也就是:
用户 ↓角色 ↓菜单 / 权限同时角色还可以继续关联:
部门数据范围这也是为什么系统不仅可以做到:
这个用户能不能看到菜单还可以继续向:
这个用户能不能调用 API这个角色可以看哪些部门这个角色可以操作哪些数据扩展。
项目的 JWT 中还单独加入了:
IsSuperAdmin bool超级管理员可以直接绕过普通 RBAC 权限检查。普通账号则继续根据用户、角色、菜单关系计算权限。
这个设计对于后台系统很实用。
因为一定要给运维留一条:
现在启动过程大概是:
main.go ↓加载 Config ↓server.Run() ↓初始化 Logger ↓初始化 Gin ↓database.Connect() ↓AutoMigrate() ↓初始化默认管理员 ↓初始化 RBAC ↓初始化 Redis ↓创建 Repository ↓创建 Service ↓注册 API ↓启动 HTTP ServerServer 层实际上扮演的是一个:
数据库、Repository、Service 都在这里完成组合。
所以业务代码本身不会到处:
gorm.Open(...)redis.NewClient(...)所有基础设施集中初始化。
这一点对于后面做:
Mock单元测试替换数据库增加缓存增加 MQ都会比较舒服。
如果只是:
✔ SQL Server其实没有什么技术含量。
真正让我觉得这次改造有价值的是:
换句话说:
增加一个基础设施没有演变成:
重写一遍业务系统。这其实就是架构设计真正有价值的地方。
好的架构不是:
而是:
接下来其实还有几个方向可以继续完善。
不同数据库并不是百分百一样。
以后可以做一份明确的:
PostgreSQL ✅MySQL✅SQL Server ✅SQLite ✅然后针对:
表注释字段类型分页JSON全文索引事务DDL元数据查询分别测试兼容性。
未来如果出现:
SQL Server 特有 SQLPostgreSQL 特有 SQLMySQL 特有 SQL不要让它进入 Service。
而应该继续下沉到:
RepositoryDBMetaRepositoryDialect Adapter例如:
DBMetaRepository │ ├── PostgreSQL Metadata ├── MySQL Metadata └── SQL Server Metadata这样数据管理、表结构查看这些功能才能真正做到跨数据库。
现在连接池参数是统一配置的:
MaxOpenConns = 50MaxIdleConns = 10ConnMaxLifetime = 30min未来最好进一步配置化:
database:max_open_conns: 50max_idle_conns: 10conn_max_lifetime: 30m因为不同部署规模的数据库并不应该使用完全相同的参数。
这是我认为非常重要的一步。
以后提交代码的时候最好直接跑:
PostgreSQL TestMySQL TestSQL Server TestSQLite Test否则:
和:
完全是两回事。
ShiyuAdmin 从一开始我就没有想把它做成一个特别重的“大而全框架”。
我的目标一直比较简单:
例如:
登录JWTRBAC用户角色菜单部门日志Redis监控数据库Docker前端后台这些东西一旦稳定下来,以后真正开发业务系统的时候,就可以直接:
写业务。而不是每开一个新项目:
第 1 天:写登录第 2 天:写用户第 3 天:写角色第 4 天:写菜单第 5 天:写权限……这次加入 SQL Server,也是沿着这个方向继续走。
现在底层已经可以覆盖:
PostgreSQLMySQLSQL ServerSQLite我觉得对于一套 Go 后台脚手架来说,数据库层基本已经有了一个比较完整的雏形。SQL Server 2022 也已经有独立的 Docker Compose 部署方案。
后面我还会继续完善:
多数据库兼容数据权限代码生成文件管理系统监控部署能力自动化测试如果你平时也在做:
GoGinGORMReact企业后台RBACSQL Server可以看看这个项目。
services:shiyu-sqlserver:image: mcr.microsoft.com/mssql/server:2022-latestplatform: linux/amd64container_name: shiyu-sqlserverrestart: unless-stoppedenvironment:ACCEPT_EULA: "Y"MSSQL_PID: "Developer"MSSQL_SA_PASSWORD: "ShiyuAdmin@123"ports:- "11433:1433"volumes:- shiyu-sqlserver-data:/var/opt/mssqlhealthcheck:test: ["CMD-SHELL", "timeout 2 bash -c '/opt/mssql-tools18/bin/sqlcmd -S shiyu-sqlserver -U sa -P "$$MSSQL_SA_PASSWORD" -C-Q "IF DB_ID(N'shiyu_admin_scaffold') IS NULL CREATE DATABASE [shiyu_admin_scaffold]"networks: [shiyu-network]shiyu-redis:image: redis:7container_name: shiyu-redisrestart: unless-stoppedvolumes:- shiyu-redis-data:/datacommand: redis-server --appendonly yeshealthcheck:test: ["CMD", "redis-cli", "ping"]interval: 10stimeout: 5sretries: 5networks: [shiyu-network]shiyu-app:image: ${SHIYU_IMAGE:-ghcr.io/rodert/shiyuadmin:latest}container_name: shiyu-apprestart: unless-stoppedenvironment:TZ: Asia/ShanghaiCONFIG_FILE: configs/config.sqlserver.yamlports:- "18000:80"depends_on:shiyu-sqlserver-init:condition: service_completed_successfullyshiyu-redis:condition: service_healthynetworks: [shiyu-network]networks:shiyu-network:driver: bridgevolumes:shiyu-sqlserver-data:shiyu-redis-data:
我是王仕宇
GitHub:
https://github.com/Rodert/ShiyuAdmin
也欢迎一起提 Issue、PR,或者把它直接拿去作为自己项目的基础骨架。
一起成为勇猛精进的人类。