一聚教程网:一个值得你收藏的教程网站

热门教程

我给自己的 Go 后台接入了 SQL Server,顺便聊聊 ShiyuAdmin 的架构设计

时间:2026-08-13 11:41:50 编辑:袖梨 来源:一聚教程网

大家好,我是 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 是什么

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 层只负责 HTTP

比如用户管理接口,API 层应该解决的是:

接收参数参数校验调用 Service返回 JSONHTTP 状态处理

而不应该在 API Handler 里面直接写:

db.Where(...)db.Create(...)db.Delete(...)

否则随着业务越来越多,很快就会出现这种代码:

Handler ├── HTTP ├── SQL ├── 权限 ├── 业务判断 ├── 数据转换 └── 日志

最后一个接口可能几百行。

所以 ShiyuAdmin 里面真正的业务逻辑主要继续往下走:

API ↓Service ↓Repository

四、Service 才是业务核心

Service 层做什么?

举个用户管理的例子。

比如:

创建用户

它并不仅仅对应:

INSERT INTO sys_users

还可能涉及:

检查用户名检查用户编码密码 BCrypt 加密部门是否合法分配角色数据权限记录日志

所以这些东西应该属于 Service。

Service 不应该过于关心:

底层到底是 PostgreSQL还是 MySQL还是 SQL Server

这也是这次接 SQL Server 能比较顺利的关键原因之一。

五、Repository 才真正负责数据库

再向下一层就是 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 到底是怎么接进来的?

这次 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

这一层。

八、SQL Server 的 DSN 也单独处理了

PostgreSQL 的连接方式一般类似:

host=user=password=dbname=port=sslmode=

MySQL 又是另外一套:

user:password@tcp(host:port)/database

SQL 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 也拆成了四套数据库模式

目前项目已经不只是代码层支持多数据库,部署层也开始分离。

现在分别提供:

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”更重要。

因为一个开源脚手架真正要降低的是:

十二、为什么我要支持 SQL Server?

很多做互联网项目的同学可能会问:

原因其实很现实。

如果只看互联网创业项目:

MySQLPostgreSQL

确实已经占据了绝大多数场景。

但是到了:

传统企业制造业ERP医院学校政府项目.NET 老系统内部管理系统

SQL Server 依然非常常见。

尤其很多企业已经存在大量:

SQL ServerWindows Server.NETERPOAMES

基础设施。

你不可能跟甲方说:

这显然不现实。

所以对于一套“通用后台脚手架”来说,多数据库支持其实不是炫技。

而是在解决一个非常现实的问题:

十三、GORM 在这里的价值就体现出来了

很多人会讨论:

如果是一个非常极致的高性能服务,我可能会倾向于:

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

这让多数据库维护成本低了非常多。

十四、RBAC 其实也是整个项目最核心的一层

数据库之外,我觉得 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 Server

Server 层实际上扮演的是一个:

数据库、Repository、Service 都在这里完成组合。

所以业务代码本身不会到处:

gorm.Open(...)redis.NewClient(...)

所有基础设施集中初始化。

这一点对于后面做:

Mock单元测试替换数据库增加缓存增加 MQ

都会比较舒服。

十六、这次 SQL Server 改造,我最满意的并不是“支持 SQL Server”

如果只是:

✔ SQL Server

其实没有什么技术含量。

真正让我觉得这次改造有价值的是:

换句话说:

增加一个基础设施

没有演变成:

重写一遍业务系统。

这其实就是架构设计真正有价值的地方。

好的架构不是:

而是:

十七、现在这套架构可以继续怎么演进?

接下来其实还有几个方向可以继续完善。

1. 数据库能力矩阵

不同数据库并不是百分百一样。

以后可以做一份明确的:

PostgreSQL ✅MySQL✅SQL Server ✅SQLite ✅

然后针对:

表注释字段类型分页JSON全文索引事务DDL元数据查询

分别测试兼容性。

2. 数据库差异继续下沉

未来如果出现:

SQL Server 特有 SQLPostgreSQL 特有 SQLMySQL 特有 SQL

不要让它进入 Service。

而应该继续下沉到:

RepositoryDBMetaRepositoryDialect Adapter

例如:

DBMetaRepository │ ├── PostgreSQL Metadata ├── MySQL Metadata └── SQL Server Metadata

这样数据管理、表结构查看这些功能才能真正做到跨数据库。

3. 连接池配置化

现在连接池参数是统一配置的:

MaxOpenConns = 50MaxIdleConns = 10ConnMaxLifetime = 30min

未来最好进一步配置化:

database:max_open_conns: 50max_idle_conns: 10conn_max_lifetime: 30m

因为不同部署规模的数据库并不应该使用完全相同的参数。

4. 把多数据库加入 CI

这是我认为非常重要的一步。

以后提交代码的时候最好直接跑:

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 '</dev/tcp/127.0.0.1/1433'"]interval: 10stimeout: 5sretries: 15start_period: 30snetworks: [shiyu-network]shiyu-sqlserver-init:image: mcr.microsoft.com/mssql-tools18:latestplatform: linux/amd64container_name: shiyu-sqlserver-initrestart: "no"environment:MSSQL_SA_PASSWORD: "ShiyuAdmin@123"depends_on:shiyu-sqlserver:condition: service_healthycommand:- /bin/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:

image.png

我是王仕宇

GitHub:

https://github.com/Rodert/ShiyuAdmin

也欢迎一起提 Issue、PR,或者把它直接拿去作为自己项目的基础骨架。

一起成为勇猛精进的人类。

热门栏目