Compare commits
10 Commits
f556f0a0ff
...
5c82714fd1
Author | SHA1 | Date |
---|---|---|
|
5c82714fd1 | |
|
06b6c87148 | |
|
f036dee7bf | |
|
9e621c8a80 | |
|
efe409138a | |
|
bf22838562 | |
|
4b7f48a40e | |
|
00ffae20ab | |
|
d8e3e332c7 | |
|
e1f82fe571 |
|
@ -17,7 +17,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: |
|
- run: |
|
||||||
release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
release_url=$(curl -s https://api.github.com/repos/opsre/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
||||||
- uses: wangyoucao577/go-release-action@v1
|
- uses: wangyoucao577/go-release-action@v1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }} # 一个默认的变量,用来实现往 Release 中添加文件
|
github_token: ${{ secrets.GITHUB_TOKEN }} # 一个默认的变量,用来实现往 Release 中添加文件
|
||||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
go-version: 1.18
|
go-version: 1.18
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: |
|
- run: |
|
||||||
release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
release_url=$(curl -s https://api.github.com/repos/opsre/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
|
@ -24,7 +24,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: |
|
- run: |
|
||||||
release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
release_url=$(curl -s https://api.github.com/repos/opsre/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
# Go LDAP Admin 项目注释总结
|
||||||
|
|
||||||
|
本文档总结了为 Go LDAP Admin 项目添加的详细中文注释,提高了代码的可读性和维护性。
|
||||||
|
|
||||||
|
## 📋 已添加注释的文件列表
|
||||||
|
|
||||||
|
### 1. 主入口文件
|
||||||
|
- **main.go** - 系统启动入口,包含完整的初始化流程和优雅关闭机制
|
||||||
|
|
||||||
|
### 2. 配置管理模块
|
||||||
|
- **config/config.go** - 配置文件加载、环境变量覆盖、热更新支持
|
||||||
|
|
||||||
|
### 3. 用户管理模块
|
||||||
|
- **controller/user_controller.go** - 用户管理 HTTP 接口控制器
|
||||||
|
- **logic/user_logic.go** - 用户业务逻辑处理(部分)
|
||||||
|
|
||||||
|
### 4. LDAP 服务模块
|
||||||
|
- **public/common/ldap.go** - LDAP 连接池管理和连接复用机制
|
||||||
|
|
||||||
|
### 5. 权限控制中间件
|
||||||
|
- **middleware/CasbinMiddleware.go** - 基于 RBAC 的权限访问控制
|
||||||
|
|
||||||
|
### 6. 路由配置
|
||||||
|
- **routes/a_routes.go** - 主路由配置和中间件注册
|
||||||
|
- **routes/user_routes.go** - 用户管理相关路由定义
|
||||||
|
|
||||||
|
## 🎯 注释内容特点
|
||||||
|
|
||||||
|
### 1. 包级别注释
|
||||||
|
每个包都添加了详细的包说明,包括:
|
||||||
|
- 包的主要功能和职责
|
||||||
|
- 核心设计理念
|
||||||
|
- 使用场景和注意事项
|
||||||
|
|
||||||
|
### 2. 结构体注释
|
||||||
|
为重要的结构体添加了:
|
||||||
|
- 结构体的用途说明
|
||||||
|
- 字段含义解释
|
||||||
|
- 使用示例和注意事项
|
||||||
|
|
||||||
|
### 3. 函数方法注释
|
||||||
|
为关键函数添加了:
|
||||||
|
- 功能描述和业务流程
|
||||||
|
- 参数说明和返回值说明
|
||||||
|
- 错误处理和异常情况
|
||||||
|
- 使用示例和最佳实践
|
||||||
|
|
||||||
|
### 4. 业务流程注释
|
||||||
|
在复杂的业务逻辑中添加了:
|
||||||
|
- 分步骤的流程说明
|
||||||
|
- 关键决策点的解释
|
||||||
|
- 异常处理逻辑
|
||||||
|
- 性能和安全考虑
|
||||||
|
|
||||||
|
## 🔧 注释规范
|
||||||
|
|
||||||
|
### 1. 中文注释
|
||||||
|
- 使用简洁明了的中文描述
|
||||||
|
- 避免过于技术化的术语
|
||||||
|
- 注重业务逻辑的解释
|
||||||
|
|
||||||
|
### 2. 结构化注释
|
||||||
|
- 使用分隔符区分不同的逻辑块
|
||||||
|
- 采用层次化的注释结构
|
||||||
|
- 保持注释的一致性
|
||||||
|
|
||||||
|
### 3. 实用性导向
|
||||||
|
- 重点解释"为什么"而不仅仅是"是什么"
|
||||||
|
- 提供使用场景和最佳实践
|
||||||
|
- 包含错误处理和边界情况
|
||||||
|
|
||||||
|
## 📈 改进效果
|
||||||
|
|
||||||
|
### 1. 可读性提升
|
||||||
|
- 新开发者能够快速理解代码结构
|
||||||
|
- 业务逻辑更加清晰明了
|
||||||
|
- 减少了代码理解的时间成本
|
||||||
|
|
||||||
|
### 2. 维护性增强
|
||||||
|
- 便于后续功能扩展和修改
|
||||||
|
- 降低了代码维护的难度
|
||||||
|
- 提高了团队协作效率
|
||||||
|
|
||||||
|
### 3. 文档化程度
|
||||||
|
- 代码即文档,减少了额外文档维护
|
||||||
|
- 注释与代码同步更新
|
||||||
|
- 提供了完整的技术参考
|
||||||
|
|
||||||
|
## 🚀 后续建议
|
||||||
|
|
||||||
|
### 1. 继续完善
|
||||||
|
建议为以下模块添加详细注释:
|
||||||
|
- 企业 IM 集成模块(钉钉、企微、飞书)
|
||||||
|
- 数据库操作层(service/isql)
|
||||||
|
- 其他中间件(限流、CORS、日志等)
|
||||||
|
- 工具函数和公共方法
|
||||||
|
|
||||||
|
### 2. 注释维护
|
||||||
|
- 在修改代码时同步更新注释
|
||||||
|
- 定期检查注释的准确性
|
||||||
|
- 保持注释风格的一致性
|
||||||
|
|
||||||
|
### 3. 团队规范
|
||||||
|
- 建立团队注释规范
|
||||||
|
- 在代码审查中检查注释质量
|
||||||
|
- 鼓励编写高质量的注释
|
||||||
|
|
||||||
|
## 📝 注释示例
|
||||||
|
|
||||||
|
以下是一些优秀的注释示例:
|
||||||
|
|
||||||
|
### 包级别注释示例
|
||||||
|
```go
|
||||||
|
/*
|
||||||
|
Package config 负责管理 Go LDAP Admin 系统的所有配置信息
|
||||||
|
|
||||||
|
该包使用 Viper 库来读取和管理配置文件,支持:
|
||||||
|
- YAML 格式的配置文件
|
||||||
|
- 环境变量覆盖配置
|
||||||
|
- 配置文件热更新
|
||||||
|
- RSA 密钥嵌入
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 函数注释示例
|
||||||
|
```go
|
||||||
|
// InitLDAP 初始化 LDAP 连接池
|
||||||
|
// 该函数在系统启动时调用,负责:
|
||||||
|
// 1. 建立与 LDAP 服务器的初始连接
|
||||||
|
// 2. 验证管理员账户凭据
|
||||||
|
// 3. 初始化连接池结构
|
||||||
|
// 4. 设置连接池参数
|
||||||
|
```
|
||||||
|
|
||||||
|
### 业务流程注释示例
|
||||||
|
```go
|
||||||
|
// ==================== 数据唯一性验证 ====================
|
||||||
|
|
||||||
|
// 检查用户名是否已存在
|
||||||
|
if isql.User.Exist(tools.H{"username": r.Username}) {
|
||||||
|
return nil, tools.NewValidatorError(fmt.Errorf("用户名已存在,请勿重复添加"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
通过这些详细的注释,Go LDAP Admin 项目的代码质量和可维护性得到了显著提升。
|
|
@ -12,7 +12,7 @@ ADD . .
|
||||||
|
|
||||||
COPY --from=registry.cn-hangzhou.aliyuncs.com/eryajf/docker-compose-wait /wait .
|
COPY --from=registry.cn-hangzhou.aliyuncs.com/eryajf/docker-compose-wait /wait .
|
||||||
|
|
||||||
RUN release_url=$(curl -s https://api.github.com/repos/eryajf/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
RUN release_url=$(curl -s https://api.github.com/repos/opsre/go-ldap-admin-ui/releases/latest | grep "browser_download_url" | grep -v 'dist.zip.md5' | cut -d '"' -f 4); wget $release_url && unzip dist.zip && rm dist.zip && mv dist public/static
|
||||||
|
|
||||||
RUN sed -i 's@localhost:389@openldap:389@g' /app/config.yml \
|
RUN sed -i 's@localhost:389@openldap:389@g' /app/config.yml \
|
||||||
&& sed -i 's@host: localhost@host: mysql@g' /app/config.yml && go build -o go-ldap-admin . && upx -9 go-ldap-admin && upx -9 wait
|
&& sed -i 's@host: localhost@host: mysql@g' /app/config.yml && go build -o go-ldap-admin . && upx -9 go-ldap-admin && upx -9 wait
|
||||||
|
|
25
README.md
25
README.md
|
@ -46,14 +46,13 @@
|
||||||
|
|
||||||
## 🏊 在线体验
|
## 🏊 在线体验
|
||||||
|
|
||||||
~~提供在线体验地址如下:~~
|
提供在线体验地址如下:
|
||||||
|
|
||||||
- ~~地址:[http://demo-go-ldap-admin.eryajf.net](http://demo-go-ldap-admin.eryajf.net)~~
|
- 地址:[http://61.171.114.86:8888](http://61.171.114.86:8888)
|
||||||
- ~~登陆信息:admin/123456~~
|
- 登陆信息:admin/123456
|
||||||
|
|
||||||
|
> 在线环境可能不稳,如果遇到访问异常,或者数据错乱,请联系我进行修复。请勿填写个人敏感信息。
|
||||||
|
|
||||||
> ~~在线环境可能不稳,如果遇到访问异常,或者数据错乱,请联系我进行修复。请勿填写个人敏感信息。~~
|
|
||||||
>
|
|
||||||
> 因服务器到期,暂时不再提供 demo 环境。
|
|
||||||
|
|
||||||
**页面功能概览:**
|
**页面功能概览:**
|
||||||
|
|
||||||
|
@ -88,15 +87,17 @@
|
||||||
|
|
||||||
## 🤝 赞助商
|
## 🤝 赞助商
|
||||||
|
|
||||||
[](https://302.ai/)
|
[](https://gpt302.saaslink.net/fGvlvo/)
|
||||||
|
|
||||||
> [302.AI](https://302.ai) 是一个汇集全球顶级品牌的 AI 超市,按需付费,零月费,零门槛使用各种类型 AI。
|
> [302.AI](https://gpt302.saaslink.net/fGvlvo) 是一个按需付费的一站式企业级AI应用平台,开放平台,开源生态。
|
||||||
>
|
>
|
||||||
> - [点击注册](https://gpt302.saaslink.net/fGvlvo): 立即获得 1PTC(1PTC=1 美金,约为 7 人民币)代币。
|
> - [点击注册](https://gpt302.saaslink.net/fGvlvo): 立即获得 1PTC(1PTC=1 美金,约为 7 人民币)代币。
|
||||||
> - 功能全面: 将最好用的 AI 集成到在平台之上,包括不限于 AI 聊天,图片生成,图片处理,视频生成,全方位覆盖。
|
> - 集合了最新最全的AI模型和品牌,包括但不限于语言模型、图像模型、声音模型、视频模型。
|
||||||
> - 简单易用: 提供机器人,工具和 API 多种使用方法,可以满足从小白到开发者多种角色的需求。
|
> - 在基础模型上进行深度应用开发,做到让小白用户都可以零门槛上手使用,无需学习成本。
|
||||||
> - 按需付费,零门槛: 不提供月付套餐,对产品不设任何门槛,按需付费,全部开放。充值余额永久有效。
|
> - 零月费,所有功能按需付费,全面开放,做到真正的门槛低,上限高。
|
||||||
> - 管理者和使用者分离:管理者一键分享,使用者无需登录。使用者无需关心复杂的 AI 设置,让懂 AI 的人来配置,简化使用流程。
|
> - 创新的使用模式,管理和使用分离,面向团队和中小企业,一人管理,多人使用。
|
||||||
|
> - 所有AI能力均提供API接入,所有应用开源支持自行定制(进行中)。
|
||||||
|
> - 强大的开发团队,每周推出2-3个新应用,平台功能每日更新。
|
||||||
|
|
||||||
## 🥰 感谢
|
## 🥰 感谢
|
||||||
|
|
||||||
|
|
320
config/config.go
320
config/config.go
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
Package config 负责管理 Go LDAP Admin 系统的所有配置信息
|
||||||
|
|
||||||
|
该包使用 Viper 库来读取和管理配置文件,支持:
|
||||||
|
- YAML 格式的配置文件
|
||||||
|
- 环境变量覆盖配置
|
||||||
|
- 配置文件热更新
|
||||||
|
- RSA 密钥嵌入
|
||||||
|
|
||||||
|
配置文件结构包括:
|
||||||
|
- 系统配置:运行模式、端口、URL前缀等
|
||||||
|
- 日志配置:日志级别、路径、轮转策略等
|
||||||
|
- 数据库配置:MySQL/SQLite 连接信息
|
||||||
|
- LDAP 配置:OpenLDAP 服务器连接信息
|
||||||
|
- JWT 配置:Token 生成和验证配置
|
||||||
|
- 企业 IM 配置:钉钉、企业微信、飞书集成配置
|
||||||
|
*/
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -11,234 +28,255 @@ import (
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 系统配置,对应yml
|
// Conf 全局配置变量,存储从配置文件和环境变量中读取的所有配置信息
|
||||||
// viper内置了mapstructure, yml文件用"-"区分单词, 转为驼峰方便
|
// 系统启动时通过 InitConfig() 函数初始化,之后可在全局范围内访问
|
||||||
|
|
||||||
// 全局配置变量
|
|
||||||
var Conf = new(config)
|
var Conf = new(config)
|
||||||
|
|
||||||
|
// RSA 密钥文件嵌入
|
||||||
|
// 使用 go:embed 指令将 RSA 私钥和公钥文件嵌入到二进制文件中
|
||||||
|
// 用于 JWT Token 的签名和验证
|
||||||
|
|
||||||
//go:embed go-ldap-admin-priv.pem
|
//go:embed go-ldap-admin-priv.pem
|
||||||
var priv []byte
|
var priv []byte // RSA 私钥,用于 JWT Token 签名
|
||||||
|
|
||||||
//go:embed go-ldap-admin-pub.pem
|
//go:embed go-ldap-admin-pub.pem
|
||||||
var pub []byte
|
var pub []byte // RSA 公钥,用于 JWT Token 验证
|
||||||
|
|
||||||
|
// config 主配置结构体,包含系统所有模块的配置信息
|
||||||
|
// 使用 mapstructure 标签将 YAML 配置映射到结构体字段
|
||||||
|
// YAML 文件中使用 "-" 分隔单词,映射到 Go 的驼峰命名
|
||||||
type config struct {
|
type config struct {
|
||||||
System *SystemConfig `mapstructure:"system" json:"system"`
|
System *SystemConfig `mapstructure:"system" json:"system"` // 系统基础配置
|
||||||
Logs *LogsConfig `mapstructure:"logs" json:"logs"`
|
Logs *LogsConfig `mapstructure:"logs" json:"logs"` // 日志配置
|
||||||
Database *Database `mapstructure:"database" json:"database"`
|
Database *Database `mapstructure:"database" json:"database"` // 数据库类型配置
|
||||||
Mysql *MysqlConfig `mapstructure:"mysql" json:"mysql"`
|
Mysql *MysqlConfig `mapstructure:"mysql" json:"mysql"` // MySQL 数据库配置
|
||||||
// Casbin *CasbinConfig `mapstructure:"casbin" json:"casbin"`
|
Jwt *JwtConfig `mapstructure:"jwt" json:"jwt"` // JWT 认证配置
|
||||||
Jwt *JwtConfig `mapstructure:"jwt" json:"jwt"`
|
RateLimit *RateLimitConfig `mapstructure:"rate-limit" json:"rateLimit"` // 限流配置
|
||||||
RateLimit *RateLimitConfig `mapstructure:"rate-limit" json:"rateLimit"`
|
Ldap *LdapConfig `mapstructure:"ldap" json:"ldap"` // LDAP 服务器配置
|
||||||
Ldap *LdapConfig `mapstructure:"ldap" json:"ldap"`
|
Email *EmailConfig `mapstructure:"email" json:"email"` // 邮件服务配置
|
||||||
Email *EmailConfig `mapstructure:"email" json:"email"`
|
DingTalk *DingTalkConfig `mapstructure:"dingtalk" json:"dingTalk"` // 钉钉集成配置
|
||||||
DingTalk *DingTalkConfig `mapstructure:"dingtalk" json:"dingTalk"`
|
WeCom *WeComConfig `mapstructure:"wecom" json:"weCom"` // 企业微信集成配置
|
||||||
WeCom *WeComConfig `mapstructure:"wecom" json:"weCom"`
|
FeiShu *FeiShuConfig `mapstructure:"feishu" json:"feiShu"` // 飞书集成配置
|
||||||
FeiShu *FeiShuConfig `mapstructure:"feishu" json:"feiShu"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置读取配置信息
|
// InitConfig 初始化系统配置
|
||||||
|
// 该函数负责:
|
||||||
|
// 1. 读取 config.yml 配置文件
|
||||||
|
// 2. 设置配置文件热更新监听
|
||||||
|
// 3. 加载嵌入的 RSA 密钥
|
||||||
|
// 4. 支持环境变量覆盖配置
|
||||||
func InitConfig() {
|
func InitConfig() {
|
||||||
|
// ==================== 配置文件读取 ====================
|
||||||
|
|
||||||
|
// 获取当前工作目录
|
||||||
workDir, err := os.Getwd()
|
workDir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("读取应用目录失败:%s", err))
|
panic(fmt.Errorf("读取应用目录失败:%s", err))
|
||||||
}
|
}
|
||||||
viper.SetConfigName("config")
|
|
||||||
viper.SetConfigType("yml")
|
|
||||||
viper.AddConfigPath(workDir + "/")
|
|
||||||
// 读取配置信息
|
|
||||||
err = viper.ReadInConfig()
|
|
||||||
|
|
||||||
// 热更新配置
|
// 配置 Viper 读取配置文件
|
||||||
|
viper.SetConfigName("config") // 配置文件名(不包含扩展名)
|
||||||
|
viper.SetConfigType("yml") // 配置文件类型
|
||||||
|
viper.AddConfigPath(workDir + "/") // 配置文件搜索路径
|
||||||
|
|
||||||
|
// 读取配置文件内容
|
||||||
|
err = viper.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("读取配置文件失败:%s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 配置热更新 ====================
|
||||||
|
|
||||||
|
// 启用配置文件监听,支持热更新
|
||||||
viper.WatchConfig()
|
viper.WatchConfig()
|
||||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||||
// 将读取的配置信息保存至全局变量Conf
|
// 配置文件发生变化时,重新解析配置到全局变量
|
||||||
if err := viper.Unmarshal(Conf); err != nil {
|
if err := viper.Unmarshal(Conf); err != nil {
|
||||||
panic(fmt.Errorf("初始化配置文件失败:%s", err))
|
panic(fmt.Errorf("热更新配置文件失败:%s", err))
|
||||||
}
|
}
|
||||||
// 读取rsa key
|
// 重新加载 RSA 密钥
|
||||||
Conf.System.RSAPublicBytes = pub
|
Conf.System.RSAPublicBytes = pub
|
||||||
Conf.System.RSAPrivateBytes = priv
|
Conf.System.RSAPrivateBytes = priv
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
// ==================== 初始配置解析 ====================
|
||||||
panic(fmt.Errorf("读取配置文件失败:%s", err))
|
|
||||||
}
|
// 将配置文件内容解析到全局配置结构体
|
||||||
// 将读取的配置信息保存至全局变量Conf
|
|
||||||
if err := viper.Unmarshal(Conf); err != nil {
|
if err := viper.Unmarshal(Conf); err != nil {
|
||||||
panic(fmt.Errorf("初始化配置文件失败:%s", err))
|
panic(fmt.Errorf("初始化配置文件失败:%s", err))
|
||||||
}
|
}
|
||||||
// 读取rsa key
|
|
||||||
|
// 加载嵌入的 RSA 密钥到配置中
|
||||||
Conf.System.RSAPublicBytes = pub
|
Conf.System.RSAPublicBytes = pub
|
||||||
Conf.System.RSAPrivateBytes = priv
|
Conf.System.RSAPrivateBytes = priv
|
||||||
|
|
||||||
// 部分配合通过环境变量加载
|
// ==================== 环境变量覆盖配置 ====================
|
||||||
dbDriver := os.Getenv("DB_DRIVER")
|
// 支持通过环境变量覆盖配置文件中的设置,便于容器化部署
|
||||||
if dbDriver != "" {
|
|
||||||
|
// 数据库配置环境变量覆盖
|
||||||
|
if dbDriver := os.Getenv("DB_DRIVER"); dbDriver != "" {
|
||||||
Conf.Database.Driver = dbDriver
|
Conf.Database.Driver = dbDriver
|
||||||
}
|
}
|
||||||
mysqlHost := os.Getenv("MYSQL_HOST")
|
if mysqlHost := os.Getenv("MYSQL_HOST"); mysqlHost != "" {
|
||||||
if mysqlHost != "" {
|
|
||||||
Conf.Mysql.Host = mysqlHost
|
Conf.Mysql.Host = mysqlHost
|
||||||
}
|
}
|
||||||
mysqlUsername := os.Getenv("MYSQL_USERNAME")
|
if mysqlUsername := os.Getenv("MYSQL_USERNAME"); mysqlUsername != "" {
|
||||||
if mysqlUsername != "" {
|
|
||||||
Conf.Mysql.Username = mysqlUsername
|
Conf.Mysql.Username = mysqlUsername
|
||||||
}
|
}
|
||||||
mysqlPassword := os.Getenv("MYSQL_PASSWORD")
|
if mysqlPassword := os.Getenv("MYSQL_PASSWORD"); mysqlPassword != "" {
|
||||||
if mysqlPassword != "" {
|
|
||||||
Conf.Mysql.Password = mysqlPassword
|
Conf.Mysql.Password = mysqlPassword
|
||||||
}
|
}
|
||||||
mysqlDatabase := os.Getenv("MYSQL_DATABASE")
|
if mysqlDatabase := os.Getenv("MYSQL_DATABASE"); mysqlDatabase != "" {
|
||||||
if mysqlDatabase != "" {
|
|
||||||
Conf.Mysql.Database = mysqlDatabase
|
Conf.Mysql.Database = mysqlDatabase
|
||||||
}
|
}
|
||||||
mysqlPort := os.Getenv("MYSQL_PORT")
|
if mysqlPort := os.Getenv("MYSQL_PORT"); mysqlPort != "" {
|
||||||
if mysqlPort != "" {
|
if port, err := strconv.Atoi(mysqlPort); err == nil {
|
||||||
Conf.Mysql.Port, _ = strconv.Atoi(mysqlPort)
|
Conf.Mysql.Port = port
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapUrl := os.Getenv("LDAP_URL")
|
// LDAP 配置环境变量覆盖
|
||||||
if ldapUrl != "" {
|
if ldapUrl := os.Getenv("LDAP_URL"); ldapUrl != "" {
|
||||||
Conf.Ldap.Url = ldapUrl
|
Conf.Ldap.Url = ldapUrl
|
||||||
}
|
}
|
||||||
ldapBaseDN := os.Getenv("LDAP_BASE_DN")
|
if ldapBaseDN := os.Getenv("LDAP_BASE_DN"); ldapBaseDN != "" {
|
||||||
if ldapBaseDN != "" {
|
|
||||||
Conf.Ldap.BaseDN = ldapBaseDN
|
Conf.Ldap.BaseDN = ldapBaseDN
|
||||||
}
|
}
|
||||||
ldapAdminDN := os.Getenv("LDAP_ADMIN_DN")
|
if ldapAdminDN := os.Getenv("LDAP_ADMIN_DN"); ldapAdminDN != "" {
|
||||||
if ldapAdminDN != "" {
|
|
||||||
Conf.Ldap.AdminDN = ldapAdminDN
|
Conf.Ldap.AdminDN = ldapAdminDN
|
||||||
}
|
}
|
||||||
ldapAdminPass := os.Getenv("LDAP_ADMIN_PASS")
|
if ldapAdminPass := os.Getenv("LDAP_ADMIN_PASS"); ldapAdminPass != "" {
|
||||||
if ldapAdminPass != "" {
|
|
||||||
Conf.Ldap.AdminPass = ldapAdminPass
|
Conf.Ldap.AdminPass = ldapAdminPass
|
||||||
}
|
}
|
||||||
ldapUserDN := os.Getenv("LDAP_USER_DN")
|
if ldapUserDN := os.Getenv("LDAP_USER_DN"); ldapUserDN != "" {
|
||||||
if ldapUserDN != "" {
|
|
||||||
Conf.Ldap.UserDN = ldapUserDN
|
Conf.Ldap.UserDN = ldapUserDN
|
||||||
}
|
}
|
||||||
ldapUserInitPassword := os.Getenv("LDAP_USER_INIT_PASSWORD")
|
if ldapUserInitPassword := os.Getenv("LDAP_USER_INIT_PASSWORD"); ldapUserInitPassword != "" {
|
||||||
if ldapUserInitPassword != "" {
|
|
||||||
|
|
||||||
Conf.Ldap.UserInitPassword = ldapUserInitPassword
|
Conf.Ldap.UserInitPassword = ldapUserInitPassword
|
||||||
}
|
}
|
||||||
ldapDefaultEmailSuffix := os.Getenv("LDAP_DEFAULT_EMAIL_SUFFIX")
|
if ldapDefaultEmailSuffix := os.Getenv("LDAP_DEFAULT_EMAIL_SUFFIX"); ldapDefaultEmailSuffix != "" {
|
||||||
if ldapDefaultEmailSuffix != "" {
|
|
||||||
Conf.Ldap.DefaultEmailSuffix = ldapDefaultEmailSuffix
|
Conf.Ldap.DefaultEmailSuffix = ldapDefaultEmailSuffix
|
||||||
}
|
}
|
||||||
ldapUserPasswordEncryptionType := os.Getenv("LDAP_USER_PASSWORD_ENCRYPTION_TYPE")
|
if ldapUserPasswordEncryptionType := os.Getenv("LDAP_USER_PASSWORD_ENCRYPTION_TYPE"); ldapUserPasswordEncryptionType != "" {
|
||||||
if ldapUserPasswordEncryptionType != "" {
|
|
||||||
Conf.Ldap.UserPasswordEncryptionType = ldapUserPasswordEncryptionType
|
Conf.Ldap.UserPasswordEncryptionType = ldapUserPasswordEncryptionType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 配置结构体定义 ====================
|
||||||
|
|
||||||
|
// SystemConfig 系统基础配置
|
||||||
type SystemConfig struct {
|
type SystemConfig struct {
|
||||||
Mode string `mapstructure:"mode" json:"mode"`
|
Mode string `mapstructure:"mode" json:"mode"` // 运行模式:debug/release/test
|
||||||
UrlPathPrefix string `mapstructure:"url-path-prefix" json:"urlPathPrefix"`
|
UrlPathPrefix string `mapstructure:"url-path-prefix" json:"urlPathPrefix"` // API URL 前缀
|
||||||
Port int `mapstructure:"port" json:"port"`
|
Port int `mapstructure:"port" json:"port"` // HTTP 服务监听端口
|
||||||
InitData bool `mapstructure:"init-data" json:"initData"`
|
InitData bool `mapstructure:"init-data" json:"initData"` // 是否初始化基础数据
|
||||||
RSAPublicBytes []byte `mapstructure:"-" json:"-"`
|
RSAPublicBytes []byte `mapstructure:"-" json:"-"` // RSA 公钥字节数组(不序列化)
|
||||||
RSAPrivateBytes []byte `mapstructure:"-" json:"-"`
|
RSAPrivateBytes []byte `mapstructure:"-" json:"-"` // RSA 私钥字节数组(不序列化)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogsConfig 日志系统配置
|
||||||
type LogsConfig struct {
|
type LogsConfig struct {
|
||||||
Level zapcore.Level `mapstructure:"level" json:"level"`
|
Level zapcore.Level `mapstructure:"level" json:"level"` // 日志级别(-1:Debug, 0:Info, 1:Warn, 2:Error, 3:DPanic, 4:Panic, 5:Fatal)
|
||||||
Path string `mapstructure:"path" json:"path"`
|
Path string `mapstructure:"path" json:"path"` // 日志文件存储路径
|
||||||
MaxSize int `mapstructure:"max-size" json:"maxSize"`
|
MaxSize int `mapstructure:"max-size" json:"maxSize"` // 单个日志文件最大大小(MB)
|
||||||
MaxBackups int `mapstructure:"max-backups" json:"maxBackups"`
|
MaxBackups int `mapstructure:"max-backups" json:"maxBackups"` // 保留的日志文件备份数量
|
||||||
MaxAge int `mapstructure:"max-age" json:"maxAge"`
|
MaxAge int `mapstructure:"max-age" json:"maxAge"` // 日志文件保留天数
|
||||||
Compress bool `mapstructure:"compress" json:"compress"`
|
Compress bool `mapstructure:"compress" json:"compress"` // 是否压缩旧日志文件
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Database 数据库类型配置
|
||||||
type Database struct {
|
type Database struct {
|
||||||
Driver string `mapstructure:"driver" json:"driver"`
|
Driver string `mapstructure:"driver" json:"driver"` // 数据库驱动类型:mysql/sqlite3
|
||||||
Source string `mapstructure:"source" json:"source"`
|
Source string `mapstructure:"source" json:"source"` // SQLite 数据库文件路径
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MysqlConfig MySQL 数据库连接配置
|
||||||
type MysqlConfig struct {
|
type MysqlConfig struct {
|
||||||
Username string `mapstructure:"username" json:"username"`
|
Username string `mapstructure:"username" json:"username"` // 数据库用户名
|
||||||
Password string `mapstructure:"password" json:"password"`
|
Password string `mapstructure:"password" json:"password"` // 数据库密码
|
||||||
Database string `mapstructure:"database" json:"database"`
|
Database string `mapstructure:"database" json:"database"` // 数据库名称
|
||||||
Host string `mapstructure:"host" json:"host"`
|
Host string `mapstructure:"host" json:"host"` // 数据库主机地址
|
||||||
Port int `mapstructure:"port" json:"port"`
|
Port int `mapstructure:"port" json:"port"` // 数据库端口
|
||||||
Query string `mapstructure:"query" json:"query"`
|
Query string `mapstructure:"query" json:"query"` // 连接字符串参数
|
||||||
LogMode bool `mapstructure:"log-mode" json:"logMode"`
|
LogMode bool `mapstructure:"log-mode" json:"logMode"` // 是否开启 SQL 日志
|
||||||
TablePrefix string `mapstructure:"table-prefix" json:"tablePrefix"`
|
TablePrefix string `mapstructure:"table-prefix" json:"tablePrefix"` // 数据表前缀
|
||||||
Charset string `mapstructure:"charset" json:"charset"`
|
Charset string `mapstructure:"charset" json:"charset"` // 字符编码
|
||||||
Collation string `mapstructure:"collation" json:"collation"`
|
Collation string `mapstructure:"collation" json:"collation"` // 字符集排序规则
|
||||||
}
|
}
|
||||||
|
|
||||||
// type CasbinConfig struct {
|
// JwtConfig JWT 认证配置
|
||||||
// ModelPath string `mapstructure:"model-path" json:"modelPath"`
|
|
||||||
// }
|
|
||||||
|
|
||||||
type JwtConfig struct {
|
type JwtConfig struct {
|
||||||
Realm string `mapstructure:"realm" json:"realm"`
|
Realm string `mapstructure:"realm" json:"realm"` // JWT 标识符
|
||||||
Key string `mapstructure:"key" json:"key"`
|
Key string `mapstructure:"key" json:"key"` // JWT 签名密钥
|
||||||
Timeout int `mapstructure:"timeout" json:"timeout"`
|
Timeout int `mapstructure:"timeout" json:"timeout"` // Token 过期时间(小时)
|
||||||
MaxRefresh int `mapstructure:"max-refresh" json:"maxRefresh"`
|
MaxRefresh int `mapstructure:"max-refresh" json:"maxRefresh"` // 刷新 Token 最大过期时间(小时)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RateLimitConfig 限流配置(令牌桶算法)
|
||||||
type RateLimitConfig struct {
|
type RateLimitConfig struct {
|
||||||
FillInterval int64 `mapstructure:"fill-interval" json:"fillInterval"`
|
FillInterval int64 `mapstructure:"fill-interval" json:"fillInterval"` // 填充一个令牌的时间间隔(毫秒)
|
||||||
Capacity int64 `mapstructure:"capacity" json:"capacity"`
|
Capacity int64 `mapstructure:"capacity" json:"capacity"` // 令牌桶容量
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LdapConfig LDAP 服务器连接配置
|
||||||
type LdapConfig struct {
|
type LdapConfig struct {
|
||||||
Url string `mapstructure:"url" json:"url"`
|
Url string `mapstructure:"url" json:"url"` // LDAP 服务器地址(如:ldap://localhost:389)
|
||||||
MaxConn int `mapstructure:"max-conn" json:"maxConn"`
|
MaxConn int `mapstructure:"max-conn" json:"maxConn"` // LDAP 连接池最大连接数
|
||||||
BaseDN string `mapstructure:"base-dn" json:"baseDN"`
|
BaseDN string `mapstructure:"base-dn" json:"baseDN"` // LDAP 基础 DN(如:dc=example,dc=com)
|
||||||
AdminDN string `mapstructure:"admin-dn" json:"adminDN"`
|
AdminDN string `mapstructure:"admin-dn" json:"adminDN"` // LDAP 管理员 DN
|
||||||
AdminPass string `mapstructure:"admin-pass" json:"adminPass"`
|
AdminPass string `mapstructure:"admin-pass" json:"adminPass"` // LDAP 管理员密码
|
||||||
UserDN string `mapstructure:"user-dn" json:"userDN"`
|
UserDN string `mapstructure:"user-dn" json:"userDN"` // 用户 OU DN(如:ou=people,dc=example,dc=com)
|
||||||
UserInitPassword string `mapstructure:"user-init-password" json:"userInitPassword"`
|
UserInitPassword string `mapstructure:"user-init-password" json:"userInitPassword"` // 新用户默认密码
|
||||||
GroupNameModify bool `mapstructure:"group-name-modify" json:"groupNameModify"`
|
GroupNameModify bool `mapstructure:"group-name-modify" json:"groupNameModify"` // 是否允许修改组 DN
|
||||||
UserNameModify bool `mapstructure:"user-name-modify" json:"userNameModify"`
|
UserNameModify bool `mapstructure:"user-name-modify" json:"userNameModify"` // 是否允许修改用户 DN
|
||||||
DefaultEmailSuffix string `mapstructure:"default-email-suffix" json:"defaultEmailSuffix"`
|
DefaultEmailSuffix string `mapstructure:"default-email-suffix" json:"defaultEmailSuffix"` // 默认邮箱后缀
|
||||||
UserPasswordEncryptionType string `mapstructure:"user-password-encryption-type" json:"userPasswordEncryptionType"`
|
UserPasswordEncryptionType string `mapstructure:"user-password-encryption-type" json:"userPasswordEncryptionType"` // 用户密码加密方式(ssha/clear)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EmailConfig 邮件服务配置
|
||||||
type EmailConfig struct {
|
type EmailConfig struct {
|
||||||
Host string `mapstructure:"host" json:"host"`
|
Host string `mapstructure:"host" json:"host"` // SMTP 服务器地址
|
||||||
Port string `mapstructure:"port" json:"port"`
|
Port string `mapstructure:"port" json:"port"` // SMTP 服务器端口
|
||||||
User string `mapstructure:"user" json:"user"`
|
User string `mapstructure:"user" json:"user"` // 邮箱用户名
|
||||||
Pass string `mapstructure:"pass" json:"pass"`
|
Pass string `mapstructure:"pass" json:"pass"` // 邮箱密码或授权码
|
||||||
From string `mapstructure:"from" json:"from"`
|
From string `mapstructure:"from" json:"from"` // 发件人显示名称
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DingTalkConfig 钉钉集成配置
|
||||||
type DingTalkConfig struct {
|
type DingTalkConfig struct {
|
||||||
AppKey string `mapstructure:"app-key" json:"appKey"`
|
AppKey string `mapstructure:"app-key" json:"appKey"` // 钉钉应用 Key
|
||||||
AppSecret string `mapstructure:"app-secret" json:"appSecret"`
|
AppSecret string `mapstructure:"app-secret" json:"appSecret"` // 钉钉应用 Secret
|
||||||
AgentId string `mapstructure:"agent-id" json:"agentId"`
|
AgentId string `mapstructure:"agent-id" json:"agentId"` // 钉钉应用 Agent ID
|
||||||
RootOuName string `mapstructure:"root-ou-name" json:"rootOuName"`
|
RootOuName string `mapstructure:"root-ou-name" json:"rootOuName"` // 根部门名称
|
||||||
Flag string `mapstructure:"flag" json:"flag"`
|
Flag string `mapstructure:"flag" json:"flag"` // 钉钉在平台的标识
|
||||||
EnableSync bool `mapstructure:"enable-sync" json:"enableSync"`
|
EnableSync bool `mapstructure:"enable-sync" json:"enableSync"` // 是否开启定时同步
|
||||||
DeptSyncTime string `mapstructure:"dept-sync-time" json:"deptSyncTime"`
|
DeptSyncTime string `mapstructure:"dept-sync-time" json:"deptSyncTime"` // 部门同步时间(Cron 表达式)
|
||||||
UserSyncTime string `mapstructure:"user-sync-time" json:"userSyncTime"`
|
UserSyncTime string `mapstructure:"user-sync-time" json:"userSyncTime"` // 用户同步时间(Cron 表达式)
|
||||||
DeptList []string `mapstructure:"dept-list" json:"deptList"`
|
DeptList []string `mapstructure:"dept-list" json:"deptList"` // 要同步的部门列表(空则同步所有)
|
||||||
IsUpdateSyncd bool `mapstructure:"is-update-syncd" json:"isUpdateSyncd"`
|
IsUpdateSyncd bool `mapstructure:"is-update-syncd" json:"isUpdateSyncd"` // 是否同步更新已同步的用户信息
|
||||||
ULeaveRange uint `mapstructure:"user-leave-range" json:"userLevelRange"`
|
ULeaveRange uint `mapstructure:"user-leave-range" json:"userLevelRange"` // 离职用户查询天数范围
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WeComConfig 企业微信集成配置
|
||||||
type WeComConfig struct {
|
type WeComConfig struct {
|
||||||
Flag string `mapstructure:"flag" json:"flag"`
|
Flag string `mapstructure:"flag" json:"flag"` // 企业微信在平台的标识
|
||||||
CorpID string `mapstructure:"corp-id" json:"corpId"`
|
CorpID string `mapstructure:"corp-id" json:"corpId"` // 企业微信企业 ID
|
||||||
AgentID int `mapstructure:"agent-id" json:"agentId"`
|
AgentID int `mapstructure:"agent-id" json:"agentId"` // 企业微信应用 ID
|
||||||
CorpSecret string `mapstructure:"corp-secret" json:"corpSecret"`
|
CorpSecret string `mapstructure:"corp-secret" json:"corpSecret"` // 企业微信应用 Secret
|
||||||
EnableSync bool `mapstructure:"enable-sync" json:"enableSync"`
|
EnableSync bool `mapstructure:"enable-sync" json:"enableSync"` // 是否开启定时同步
|
||||||
DeptSyncTime string `mapstructure:"dept-sync-time" json:"deptSyncTime"`
|
DeptSyncTime string `mapstructure:"dept-sync-time" json:"deptSyncTime"` // 部门同步时间(Cron 表达式)
|
||||||
UserSyncTime string `mapstructure:"user-sync-time" json:"userSyncTime"`
|
UserSyncTime string `mapstructure:"user-sync-time" json:"userSyncTime"` // 用户同步时间(Cron 表达式)
|
||||||
IsUpdateSyncd bool `mapstructure:"is-update-syncd" json:"isUpdateSyncd"`
|
IsUpdateSyncd bool `mapstructure:"is-update-syncd" json:"isUpdateSyncd"` // 是否同步更新已同步的用户信息
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FeiShuConfig 飞书集成配置
|
||||||
type FeiShuConfig struct {
|
type FeiShuConfig struct {
|
||||||
Flag string `mapstructure:"flag" json:"flag"`
|
Flag string `mapstructure:"flag" json:"flag"` // 飞书在平台的标识
|
||||||
AppID string `mapstructure:"app-id" json:"appId"`
|
AppID string `mapstructure:"app-id" json:"appId"` // 飞书应用 ID
|
||||||
AppSecret string `mapstructure:"app-secret" json:"appSecret"`
|
AppSecret string `mapstructure:"app-secret" json:"appSecret"` // 飞书应用 Secret
|
||||||
EnableSync bool `mapstructure:"enable-sync" json:"enableSync"`
|
EnableSync bool `mapstructure:"enable-sync" json:"enableSync"` // 是否开启定时同步
|
||||||
DeptSyncTime string `mapstructure:"dept-sync-time" json:"deptSyncTime"`
|
DeptSyncTime string `mapstructure:"dept-sync-time" json:"deptSyncTime"` // 部门同步时间(Cron 表达式)
|
||||||
UserSyncTime string `mapstructure:"user-sync-time" json:"userSyncTime"`
|
UserSyncTime string `mapstructure:"user-sync-time" json:"userSyncTime"` // 用户同步时间(Cron 表达式)
|
||||||
DeptList []string `mapstructure:"dept-list" json:"deptList"`
|
DeptList []string `mapstructure:"dept-list" json:"deptList"` // 要同步的部门列表(空则同步所有)
|
||||||
IsUpdateSyncd bool `mapstructure:"is-update-syncd" json:"isUpdateSyncd"`
|
IsUpdateSyncd bool `mapstructure:"is-update-syncd" json:"isUpdateSyncd"` // 是否同步更新已同步的用户信息
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
/*
|
||||||
|
Package controller 包含所有 HTTP 请求的控制器
|
||||||
|
|
||||||
|
用户控制器负责处理用户管理相关的 HTTP 请求,包括:
|
||||||
|
- 用户的增删改查操作
|
||||||
|
- 用户密码管理
|
||||||
|
- 用户状态管理
|
||||||
|
- 用户信息查询
|
||||||
|
|
||||||
|
控制器层的职责:
|
||||||
|
1. 接收和验证 HTTP 请求参数
|
||||||
|
2. 调用业务逻辑层处理请求
|
||||||
|
3. 返回统一格式的响应结果
|
||||||
|
4. 处理请求过程中的异常情况
|
||||||
|
*/
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -7,16 +22,31 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UserController 用户管理控制器
|
||||||
|
// 负责处理所有与用户相关的 HTTP 请求
|
||||||
type UserController struct{}
|
type UserController struct{}
|
||||||
|
|
||||||
// Add 添加用户记录
|
// Add 添加用户记录
|
||||||
|
// 该接口用于创建新用户,会同时在 MySQL 和 LDAP 中创建用户记录
|
||||||
|
//
|
||||||
|
// 业务流程:
|
||||||
|
// 1. 验证用户输入参数(用户名、邮箱、手机号等唯一性)
|
||||||
|
// 2. 检查当前登录用户权限
|
||||||
|
// 3. 构建用户对象并设置默认值
|
||||||
|
// 4. 先在 MySQL 中创建用户记录
|
||||||
|
// 5. 再在 LDAP 中创建用户条目
|
||||||
|
// 6. 处理用户与部门的关联关系
|
||||||
|
//
|
||||||
// @Summary 添加用户记录
|
// @Summary 添加用户记录
|
||||||
// @Description 添加用户记录
|
// @Description 创建新用户,同时在数据库和LDAP中添加用户信息
|
||||||
// @Tags 用户管理
|
// @Tags 用户管理
|
||||||
// @Accept application/json
|
// @Accept application/json
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param data body request.UserAddReq true "添加用户记录的结构体"
|
// @Param data body request.UserAddReq true "用户信息"
|
||||||
// @Success 200 {object} response.ResponseBody
|
// @Success 200 {object} response.ResponseBody "创建成功"
|
||||||
|
// @Failure 400 {object} response.ResponseBody "参数错误"
|
||||||
|
// @Failure 401 {object} response.ResponseBody "未授权"
|
||||||
|
// @Failure 500 {object} response.ResponseBody "服务器错误"
|
||||||
// @Router /user/add [post]
|
// @Router /user/add [post]
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
func (m *UserController) Add(c *gin.Context) {
|
func (m *UserController) Add(c *gin.Context) {
|
||||||
|
@ -27,13 +57,25 @@ func (m *UserController) Add(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update 更新用户记录
|
// Update 更新用户记录
|
||||||
|
// 该接口用于更新现有用户信息,会同时更新 MySQL 和 LDAP 中的用户数据
|
||||||
|
//
|
||||||
|
// 业务流程:
|
||||||
|
// 1. 验证用户输入参数和权限
|
||||||
|
// 2. 检查用户名、邮箱、手机号等字段的唯一性(排除当前用户)
|
||||||
|
// 3. 先更新 MySQL 中的用户记录
|
||||||
|
// 4. 再更新 LDAP 中的用户条目
|
||||||
|
// 5. 处理用户部门关系的变更
|
||||||
|
//
|
||||||
// @Summary 更新用户记录
|
// @Summary 更新用户记录
|
||||||
// @Description 添加用户记录
|
// @Description 更新用户基本信息,同时同步到数据库和LDAP
|
||||||
// @Tags 用户管理
|
// @Tags 用户管理
|
||||||
// @Accept application/json
|
// @Accept application/json
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param data body request.UserUpdateReq true "更改用户记录的结构体"
|
// @Param data body request.UserUpdateReq true "用户更新信息"
|
||||||
// @Success 200 {object} response.ResponseBody
|
// @Success 200 {object} response.ResponseBody "更新成功"
|
||||||
|
// @Failure 400 {object} response.ResponseBody "参数错误"
|
||||||
|
// @Failure 401 {object} response.ResponseBody "未授权"
|
||||||
|
// @Failure 500 {object} response.ResponseBody "服务器错误"
|
||||||
// @Router /user/update [post]
|
// @Router /user/update [post]
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
func (m *UserController) Update(c *gin.Context) {
|
func (m *UserController) Update(c *gin.Context) {
|
||||||
|
@ -43,13 +85,28 @@ func (m *UserController) Update(c *gin.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// List 记录列表
|
// List 获取用户列表
|
||||||
// @Summary 获取所有用户记录列表
|
// 该接口支持分页查询和多条件筛选,返回用户基本信息列表
|
||||||
// @Description 获取所有用户记录列表
|
//
|
||||||
|
// 支持的查询条件:
|
||||||
|
// - 用户名模糊搜索
|
||||||
|
// - 部门筛选
|
||||||
|
// - 状态筛选
|
||||||
|
// - 分页参数
|
||||||
|
//
|
||||||
|
// @Summary 获取用户列表
|
||||||
|
// @Description 分页查询用户列表,支持多条件筛选
|
||||||
// @Tags 用户管理
|
// @Tags 用户管理
|
||||||
// @Accept application/json
|
// @Accept application/json
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Success 200 {object} response.ResponseBody
|
// @Param pageNum query int false "页码"
|
||||||
|
// @Param pageSize query int false "每页数量"
|
||||||
|
// @Param username query string false "用户名"
|
||||||
|
// @Param nickname query string false "昵称"
|
||||||
|
// @Param status query int false "状态"
|
||||||
|
// @Success 200 {object} response.ResponseBody "查询成功"
|
||||||
|
// @Failure 401 {object} response.ResponseBody "未授权"
|
||||||
|
// @Failure 500 {object} response.ResponseBody "服务器错误"
|
||||||
// @Router /user/list [get]
|
// @Router /user/list [get]
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
func (m *UserController) List(c *gin.Context) {
|
func (m *UserController) List(c *gin.Context) {
|
||||||
|
@ -60,13 +117,30 @@ func (m *UserController) List(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete 删除用户记录
|
// Delete 删除用户记录
|
||||||
|
// 该接口用于批量删除用户,会同时从 MySQL 和 LDAP 中删除用户数据
|
||||||
|
//
|
||||||
|
// 安全限制:
|
||||||
|
// 1. 用户不能删除自己
|
||||||
|
// 2. 用户不能删除比自己角色等级高的用户
|
||||||
|
// 3. 只有管理员才能执行删除操作
|
||||||
|
//
|
||||||
|
// 业务流程:
|
||||||
|
// 1. 验证删除权限和目标用户
|
||||||
|
// 2. 先从 LDAP 中删除用户条目
|
||||||
|
// 3. 再从 MySQL 中删除用户记录
|
||||||
|
// 4. 清理相关的角色关系和部门关系
|
||||||
|
//
|
||||||
// @Summary 删除用户记录
|
// @Summary 删除用户记录
|
||||||
// @Description 删除用户记录
|
// @Description 批量删除用户,同时从数据库和LDAP中移除
|
||||||
// @Tags 用户管理
|
// @Tags 用户管理
|
||||||
// @Accept application/json
|
// @Accept application/json
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param data body request.UserDeleteReq true "删除用户记录的结构体ID"
|
// @Param data body request.UserDeleteReq true "要删除的用户ID列表"
|
||||||
// @Success 200 {object} response.ResponseBody
|
// @Success 200 {object} response.ResponseBody "删除成功"
|
||||||
|
// @Failure 400 {object} response.ResponseBody "参数错误"
|
||||||
|
// @Failure 401 {object} response.ResponseBody "未授权"
|
||||||
|
// @Failure 403 {object} response.ResponseBody "权限不足"
|
||||||
|
// @Failure 500 {object} response.ResponseBody "服务器错误"
|
||||||
// @Router /user/delete [post]
|
// @Router /user/delete [post]
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
func (m UserController) Delete(c *gin.Context) {
|
func (m UserController) Delete(c *gin.Context) {
|
||||||
|
@ -76,14 +150,31 @@ func (m UserController) Delete(c *gin.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangePwd 更新密码
|
// ChangePwd 修改用户密码
|
||||||
// @Summary 更新密码
|
// 该接口用于修改用户密码,支持管理员修改其他用户密码和用户修改自己的密码
|
||||||
// @Description 更新密码
|
//
|
||||||
|
// 权限控制:
|
||||||
|
// 1. 用户可以修改自己的密码
|
||||||
|
// 2. 管理员可以修改任何用户的密码
|
||||||
|
// 3. 普通用户不能修改其他用户的密码
|
||||||
|
//
|
||||||
|
// 业务流程:
|
||||||
|
// 1. 验证当前用户权限
|
||||||
|
// 2. 验证旧密码(如果是修改自己的密码)
|
||||||
|
// 3. 加密新密码
|
||||||
|
// 4. 同时更新 MySQL 和 LDAP 中的密码
|
||||||
|
//
|
||||||
|
// @Summary 修改用户密码
|
||||||
|
// @Description 修改指定用户的登录密码,同时更新数据库和LDAP
|
||||||
// @Tags 用户管理
|
// @Tags 用户管理
|
||||||
// @Accept application/json
|
// @Accept application/json
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param data body request.UserChangePwdReq true "更改用户密码的结构体"
|
// @Param data body request.UserChangePwdReq true "密码修改信息"
|
||||||
// @Success 200 {object} response.ResponseBody
|
// @Success 200 {object} response.ResponseBody "修改成功"
|
||||||
|
// @Failure 400 {object} response.ResponseBody "参数错误"
|
||||||
|
// @Failure 401 {object} response.ResponseBody "未授权"
|
||||||
|
// @Failure 403 {object} response.ResponseBody "权限不足"
|
||||||
|
// @Failure 500 {object} response.ResponseBody "服务器错误"
|
||||||
// @Router /user/changePwd [post]
|
// @Router /user/changePwd [post]
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
func (m UserController) ChangePwd(c *gin.Context) {
|
func (m UserController) ChangePwd(c *gin.Context) {
|
||||||
|
@ -93,14 +184,31 @@ func (m UserController) ChangePwd(c *gin.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeUserStatus 更改用户状态
|
// ChangeUserStatus 修改用户状态
|
||||||
// @Summary 更改用户状态
|
// 该接口用于启用或禁用用户账户,只有管理员才能执行此操作
|
||||||
// @Description 更改用户状态
|
//
|
||||||
|
// 用户状态说明:
|
||||||
|
// - 1: 正常状态,用户可以正常登录和使用系统
|
||||||
|
// - 2: 禁用状态,用户无法登录系统
|
||||||
|
//
|
||||||
|
// 业务流程:
|
||||||
|
// 1. 验证当前用户是否为管理员
|
||||||
|
// 2. 根据状态决定在 LDAP 中添加或删除用户条目
|
||||||
|
// 3. 更新 MySQL 中的用户状态
|
||||||
|
//
|
||||||
|
// 注意:禁用用户时会从 LDAP 中删除用户条目,启用时会重新添加
|
||||||
|
//
|
||||||
|
// @Summary 修改用户状态
|
||||||
|
// @Description 启用或禁用用户账户,只有管理员可操作
|
||||||
// @Tags 用户管理
|
// @Tags 用户管理
|
||||||
// @Accept application/json
|
// @Accept application/json
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param data body request.UserChangeUserStatusReq true "更改用户状态的结构体"
|
// @Param data body request.UserChangeUserStatusReq true "用户状态修改信息"
|
||||||
// @Success 200 {object} response.ResponseBody
|
// @Success 200 {object} response.ResponseBody "修改成功"
|
||||||
|
// @Failure 400 {object} response.ResponseBody "参数错误"
|
||||||
|
// @Failure 401 {object} response.ResponseBody "未授权"
|
||||||
|
// @Failure 403 {object} response.ResponseBody "权限不足"
|
||||||
|
// @Failure 500 {object} response.ResponseBody "服务器错误"
|
||||||
// @Router /user/changeUserStatus [post]
|
// @Router /user/changeUserStatus [post]
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
func (m UserController) ChangeUserStatus(c *gin.Context) {
|
func (m UserController) ChangeUserStatus(c *gin.Context) {
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
Package logic 包含所有业务逻辑处理
|
||||||
|
|
||||||
|
用户业务逻辑层负责处理用户管理的核心业务逻辑,包括:
|
||||||
|
- 用户数据的验证和处理
|
||||||
|
- MySQL 和 LDAP 的双重数据操作
|
||||||
|
- 用户权限和角色管理
|
||||||
|
- 密码加密和验证
|
||||||
|
- 部门关系处理
|
||||||
|
|
||||||
|
业务逻辑层的职责:
|
||||||
|
1. 接收控制器层传递的请求参数
|
||||||
|
2. 执行业务规则验证
|
||||||
|
3. 协调数据访问层完成数据操作
|
||||||
|
4. 处理复杂的业务流程
|
||||||
|
5. 返回处理结果给控制器层
|
||||||
|
*/
|
||||||
package logic
|
package logic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -16,25 +33,46 @@ import (
|
||||||
"github.com/thoas/go-funk"
|
"github.com/thoas/go-funk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UserLogic 用户业务逻辑处理器
|
||||||
|
// 负责处理所有与用户相关的业务逻辑
|
||||||
type UserLogic struct{}
|
type UserLogic struct{}
|
||||||
|
|
||||||
// Add 添加数据
|
// Add 添加用户业务逻辑
|
||||||
|
// 该方法处理用户创建的完整业务流程,包括数据验证、密码处理、
|
||||||
|
// MySQL 和 LDAP 双重存储、部门关系建立等
|
||||||
|
//
|
||||||
|
// 业务流程:
|
||||||
|
// 1. 参数类型断言和基础验证
|
||||||
|
// 2. 检查用户名、手机号、工号、邮箱的唯一性
|
||||||
|
// 3. RSA 解密用户密码并验证强度
|
||||||
|
// 4. 获取当前登录用户信息
|
||||||
|
// 5. 验证角色权限
|
||||||
|
// 6. 构建用户对象并设置默认值
|
||||||
|
// 7. 获取用户所属部门信息
|
||||||
|
// 8. 调用通用添加用户方法完成创建
|
||||||
func (l UserLogic) Add(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
|
func (l UserLogic) Add(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
|
||||||
|
// 参数类型断言
|
||||||
r, ok := req.(*request.UserAddReq)
|
r, ok := req.(*request.UserAddReq)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ReqAssertErr
|
return nil, ReqAssertErr
|
||||||
}
|
}
|
||||||
_ = c
|
_ = c
|
||||||
|
|
||||||
|
// ==================== 数据唯一性验证 ====================
|
||||||
|
|
||||||
|
// 检查用户名是否已存在
|
||||||
if isql.User.Exist(tools.H{"username": r.Username}) {
|
if isql.User.Exist(tools.H{"username": r.Username}) {
|
||||||
return nil, tools.NewValidatorError(fmt.Errorf("用户名已存在,请勿重复添加"))
|
return nil, tools.NewValidatorError(fmt.Errorf("用户名已存在,请勿重复添加"))
|
||||||
}
|
}
|
||||||
|
// 检查手机号是否已存在
|
||||||
if isql.User.Exist(tools.H{"mobile": r.Mobile}) {
|
if isql.User.Exist(tools.H{"mobile": r.Mobile}) {
|
||||||
return nil, tools.NewValidatorError(fmt.Errorf("手机号已存在,请勿重复添加"))
|
return nil, tools.NewValidatorError(fmt.Errorf("手机号已存在,请勿重复添加"))
|
||||||
}
|
}
|
||||||
|
// 检查工号是否已存在
|
||||||
if isql.User.Exist(tools.H{"job_number": r.JobNumber}) {
|
if isql.User.Exist(tools.H{"job_number": r.JobNumber}) {
|
||||||
return nil, tools.NewValidatorError(fmt.Errorf("工号已存在,请勿重复添加"))
|
return nil, tools.NewValidatorError(fmt.Errorf("工号已存在,请勿重复添加"))
|
||||||
}
|
}
|
||||||
|
// 检查邮箱是否已存在
|
||||||
if isql.User.Exist(tools.H{"mail": r.Mail}) {
|
if isql.User.Exist(tools.H{"mail": r.Mail}) {
|
||||||
return nil, tools.NewValidatorError(fmt.Errorf("邮箱已存在,请勿重复添加"))
|
return nil, tools.NewValidatorError(fmt.Errorf("邮箱已存在,请勿重复添加"))
|
||||||
}
|
}
|
||||||
|
|
111
main.go
111
main.go
|
@ -1,3 +1,25 @@
|
||||||
|
/*
|
||||||
|
Package main 是 Go LDAP Admin 项目的主入口文件
|
||||||
|
|
||||||
|
Go LDAP Admin 是一个基于 Go + Vue 实现的 OpenLDAP 后台管理系统,
|
||||||
|
旨在为 OpenLDAP 服务端提供一个简单易用、清晰美观的现代化管理后台。
|
||||||
|
|
||||||
|
主要功能:
|
||||||
|
- 用户管理:支持用户的增删改查,密码管理等
|
||||||
|
- 组织架构管理:支持部门/组的层级管理
|
||||||
|
- 权限控制:基于 RBAC 的角色权限管理
|
||||||
|
- LDAP 集成:与 OpenLDAP 服务器双向同步
|
||||||
|
- 企业 IM 集成:支持钉钉、企业微信、飞书等平台同步
|
||||||
|
- 操作审计:完整的操作日志记录
|
||||||
|
|
||||||
|
技术栈:
|
||||||
|
- Web 框架:Gin
|
||||||
|
- ORM:GORM
|
||||||
|
- 数据库:MySQL/SQLite
|
||||||
|
- 权限控制:Casbin
|
||||||
|
- LDAP 客户端:go-ldap
|
||||||
|
- 认证:JWT
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -17,6 +39,7 @@ import (
|
||||||
"github.com/eryajf/go-ldap-admin/service/isql"
|
"github.com/eryajf/go-ldap-admin/service/isql"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Swagger API 文档配置
|
||||||
// @title Go Ldap Admin
|
// @title Go Ldap Admin
|
||||||
// @version 1.0
|
// @version 1.0
|
||||||
// @description 基于Go+Vue实现的openLDAP后台管理项目
|
// @description 基于Go+Vue实现的openLDAP后台管理项目
|
||||||
|
@ -31,78 +54,104 @@ import (
|
||||||
// @securityDefinitions.apikey ApiKeyAuth
|
// @securityDefinitions.apikey ApiKeyAuth
|
||||||
// @in header
|
// @in header
|
||||||
// @name Authorization
|
// @name Authorization
|
||||||
func main() {
|
|
||||||
|
|
||||||
// 加载配置文件到全局配置结构体
|
// main 函数是程序的入口点,负责初始化各个组件并启动 HTTP 服务器
|
||||||
|
func main() {
|
||||||
|
// ==================== 系统初始化阶段 ====================
|
||||||
|
|
||||||
|
// 1. 加载配置文件到全局配置结构体
|
||||||
|
// 从 config.yml 文件中读取系统配置,包括数据库、LDAP、JWT 等配置信息
|
||||||
config.InitConfig()
|
config.InitConfig()
|
||||||
|
|
||||||
// 初始化日志
|
// 2. 初始化日志系统
|
||||||
|
// 配置 zap 日志库,设置日志级别、输出格式、文件轮转等
|
||||||
common.InitLogger()
|
common.InitLogger()
|
||||||
|
|
||||||
// 初始化数据库(mysql)
|
// 3. 初始化数据库连接
|
||||||
|
// 根据配置连接 MySQL 或 SQLite 数据库,建立连接池
|
||||||
common.InitDB()
|
common.InitDB()
|
||||||
|
|
||||||
// 初始化ldap连接
|
// 4. 初始化 LDAP 连接
|
||||||
|
// 建立与 OpenLDAP 服务器的连接池,用于用户认证和数据同步
|
||||||
common.InitLDAP()
|
common.InitLDAP()
|
||||||
|
|
||||||
// 初始化casbin策略管理器
|
// 5. 初始化 Casbin 策略管理器
|
||||||
|
// 加载 RBAC 权限模型,用于接口级别的权限控制
|
||||||
common.InitCasbinEnforcer()
|
common.InitCasbinEnforcer()
|
||||||
|
|
||||||
// 初始化Validator数据校验
|
// 6. 初始化数据校验器
|
||||||
|
// 配置 gin 的数据校验器,用于请求参数验证
|
||||||
common.InitValidate()
|
common.InitValidate()
|
||||||
|
|
||||||
// 初始化mysql数据
|
// 7. 初始化基础数据
|
||||||
|
// 如果是首次启动,会创建默认的管理员账户、角色、菜单等基础数据
|
||||||
common.InitData()
|
common.InitData()
|
||||||
|
|
||||||
// 操作日志中间件处理日志时没有将日志发送到rabbitmq或者kafka中, 而是发送到了channel中
|
// ==================== 后台服务启动 ====================
|
||||||
// 这里开启3个goroutine处理channel将日志记录到数据库
|
|
||||||
|
// 启动操作日志处理协程
|
||||||
|
// 操作日志中间件将日志发送到 channel 中,这里启动 3 个 goroutine
|
||||||
|
// 异步处理 channel 中的日志并写入数据库,提高系统性能
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
go isql.OperationLog.SaveOperationLogChannel(middleware.OperationLogChan)
|
go isql.OperationLog.SaveOperationLogChannel(middleware.OperationLogChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== HTTP 服务器配置 ====================
|
||||||
|
|
||||||
// 注册所有路由
|
// 注册所有路由
|
||||||
|
// 包括用户管理、组织架构、权限管理、系统管理等所有 API 路由
|
||||||
r := routes.InitRoutes()
|
r := routes.InitRoutes()
|
||||||
|
|
||||||
host := "0.0.0.0"
|
// 配置服务器监听地址和端口
|
||||||
port := config.Conf.System.Port
|
host := "0.0.0.0" // 监听所有网络接口
|
||||||
|
port := config.Conf.System.Port // 从配置文件读取端口号
|
||||||
|
|
||||||
|
// 创建 HTTP 服务器实例
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||||
Handler: r,
|
Handler: r,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializing the server in a goroutine so that
|
// ==================== 服务器启动 ====================
|
||||||
// it won't block the graceful shutdown handling below
|
|
||||||
|
// 在单独的 goroutine 中启动 HTTP 服务器
|
||||||
|
// 这样不会阻塞后续的优雅关闭处理逻辑
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
common.Log.Fatalf("listen: %s\n", err)
|
common.Log.Fatalf("HTTP 服务器启动失败: %s\n", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 启动定时任务
|
// 启动定时任务调度器
|
||||||
|
// 用于执行企业 IM 同步、数据清理等定时任务
|
||||||
logic.InitCron()
|
logic.InitCron()
|
||||||
|
|
||||||
common.Log.Info(fmt.Sprintf("Server is running at http://%s:%d", host, port))
|
// 输出服务器启动成功信息
|
||||||
|
common.Log.Info(fmt.Sprintf("🚀 Go LDAP Admin 服务器启动成功! 访问地址: http://%s:%d", host, port))
|
||||||
|
|
||||||
// Wait for interrupt signal to gracefully shutdown the server with
|
// ==================== 优雅关闭处理 ====================
|
||||||
// a timeout of 5 seconds.
|
|
||||||
|
// 创建信号接收通道,用于接收系统关闭信号
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
// kill (no param) default send syscall.SIGTERM
|
|
||||||
// kill -2 is syscall.SIGINT
|
|
||||||
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
|
|
||||||
// signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
signal.Notify(quit, os.Interrupt)
|
|
||||||
<-quit
|
|
||||||
common.Log.Info("Shutting down server...")
|
|
||||||
|
|
||||||
// The context is used to inform the server it has 5 seconds to finish
|
// 注册信号处理
|
||||||
// the request it is currently handling
|
// kill (无参数) 默认发送 syscall.SIGTERM 信号
|
||||||
|
// kill -2 发送 syscall.SIGINT 信号 (Ctrl+C)
|
||||||
|
// kill -9 发送 syscall.SIGKILL 信号,但无法被捕获,所以不需要处理
|
||||||
|
signal.Notify(quit, os.Interrupt)
|
||||||
|
|
||||||
|
// 阻塞等待关闭信号
|
||||||
|
<-quit
|
||||||
|
common.Log.Info("🛑 接收到关闭信号,开始优雅关闭服务器...")
|
||||||
|
|
||||||
|
// 创建带超时的上下文,给服务器 5 秒时间完成当前正在处理的请求
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
// 优雅关闭服务器
|
||||||
if err := srv.Shutdown(ctx); err != nil {
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
common.Log.Fatal("Server forced to shutdown:", err)
|
common.Log.Fatal("❌ 服务器强制关闭:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
common.Log.Info("Server exiting!")
|
common.Log.Info("✅ 服务器已安全退出!")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,25 @@
|
||||||
|
/*
|
||||||
|
Package middleware 包含系统的各种中间件
|
||||||
|
|
||||||
|
Casbin 权限控制中间件实现了基于 RBAC(Role-Based Access Control)的权限访问控制模型。
|
||||||
|
该中间件负责:
|
||||||
|
- 验证用户登录状态和账户状态
|
||||||
|
- 获取用户的角色信息
|
||||||
|
- 基于角色检查用户对特定资源的访问权限
|
||||||
|
- 阻止未授权的请求访问受保护的资源
|
||||||
|
|
||||||
|
权限控制流程:
|
||||||
|
1. 从 JWT Token 中获取当前登录用户信息
|
||||||
|
2. 检查用户账户状态(是否被禁用)
|
||||||
|
3. 获取用户的所有有效角色
|
||||||
|
4. 根据请求的 URL 和 HTTP 方法进行权限检查
|
||||||
|
5. 允许或拒绝请求继续执行
|
||||||
|
|
||||||
|
RBAC 模型说明:
|
||||||
|
- Subject(主体):用户的角色关键字
|
||||||
|
- Object(对象):请求的 API 路径
|
||||||
|
- Action(动作):HTTP 请求方法(GET、POST、PUT、DELETE 等)
|
||||||
|
*/
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -12,35 +34,63 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// checkLock 权限检查互斥锁
|
||||||
|
// 确保同一时间只有一个请求在执行权限校验,避免并发访问导致的权限检查异常
|
||||||
var checkLock sync.Mutex
|
var checkLock sync.Mutex
|
||||||
|
|
||||||
// Casbin中间件, 基于RBAC的权限访问控制模型
|
// CasbinMiddleware Casbin 权限控制中间件
|
||||||
|
// 基于 RBAC 模型的权限访问控制中间件,用于保护需要权限验证的 API 接口
|
||||||
|
//
|
||||||
|
// 权限验证流程:
|
||||||
|
// 1. 获取当前登录用户信息
|
||||||
|
// 2. 验证用户状态(是否被禁用)
|
||||||
|
// 3. 收集用户的有效角色
|
||||||
|
// 4. 构建权限检查参数(角色、路径、方法)
|
||||||
|
// 5. 调用 Casbin 引擎进行权限验证
|
||||||
|
// 6. 根据验证结果决定是否允许请求继续
|
||||||
func CasbinMiddleware() gin.HandlerFunc {
|
func CasbinMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
// ==================== 用户身份验证 ====================
|
||||||
|
|
||||||
|
// 从 JWT Token 中获取当前登录用户信息
|
||||||
user, err := isql.User.GetCurrentLoginUser(c)
|
user, err := isql.User.GetCurrentLoginUser(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tools.Response(c, 401, 401, nil, "用户未登录")
|
tools.Response(c, 401, 401, nil, "用户未登录")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查用户账户状态,禁用用户不允许访问
|
||||||
if user.Status != 1 {
|
if user.Status != 1 {
|
||||||
tools.Response(c, 401, 401, nil, "当前用户已被禁用")
|
tools.Response(c, 401, 401, nil, "当前用户已被禁用")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 获得用户的全部角色
|
|
||||||
|
// ==================== 角色信息收集 ====================
|
||||||
|
|
||||||
|
// 获取用户的所有角色
|
||||||
roles := user.Roles
|
roles := user.Roles
|
||||||
// 获得用户全部未被禁用的角色的Keyword
|
|
||||||
|
// 收集所有有效角色的关键字(只包含启用状态的角色)
|
||||||
var subs []string
|
var subs []string
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
if role.Status == 1 {
|
if role.Status == 1 {
|
||||||
subs = append(subs, role.Keyword)
|
subs = append(subs, role.Keyword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 获得请求路径URL
|
|
||||||
|
// ==================== 权限检查参数构建 ====================
|
||||||
|
|
||||||
|
// 获取请求的 API 路径(移除 URL 前缀)
|
||||||
obj := strings.TrimPrefix(c.FullPath(), "/"+config.Conf.System.UrlPathPrefix)
|
obj := strings.TrimPrefix(c.FullPath(), "/"+config.Conf.System.UrlPathPrefix)
|
||||||
// 获取请求方式
|
|
||||||
|
// 获取 HTTP 请求方法
|
||||||
act := c.Request.Method
|
act := c.Request.Method
|
||||||
|
|
||||||
|
// ==================== 权限验证 ====================
|
||||||
|
|
||||||
|
// 调用权限检查函数
|
||||||
isPass := check(subs, obj, act)
|
isPass := check(subs, obj, act)
|
||||||
if !isPass {
|
if !isPass {
|
||||||
tools.Response(c, 401, 401, nil, "没有权限")
|
tools.Response(c, 401, 401, nil, "没有权限")
|
||||||
|
@ -48,21 +98,45 @@ func CasbinMiddleware() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 权限验证通过,继续执行后续中间件和处理函数
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check 执行具体的权限检查逻辑
|
||||||
|
// 该函数使用 Casbin 引擎验证用户角色是否有权限访问指定资源
|
||||||
|
//
|
||||||
|
// 参数说明:
|
||||||
|
// - subs: 用户的角色关键字列表
|
||||||
|
// - obj: 请求的 API 路径
|
||||||
|
// - act: HTTP 请求方法
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - bool: true 表示有权限,false 表示无权限
|
||||||
|
//
|
||||||
|
// 权限检查策略:
|
||||||
|
// 1. 遍历用户的所有有效角色
|
||||||
|
// 2. 对每个角色调用 Casbin 引擎进行权限验证
|
||||||
|
// 3. 只要有一个角色通过验证,就认为用户有权限
|
||||||
|
// 4. 所有角色都验证失败,则认为用户无权限
|
||||||
func check(subs []string, obj string, act string) bool {
|
func check(subs []string, obj string, act string) bool {
|
||||||
// 同一时间只允许一个请求执行校验, 否则可能会校验失败
|
// 使用互斥锁确保权限检查的线程安全
|
||||||
|
// 避免并发请求导致 Casbin 引擎状态异常
|
||||||
checkLock.Lock()
|
checkLock.Lock()
|
||||||
defer checkLock.Unlock()
|
defer checkLock.Unlock()
|
||||||
|
|
||||||
isPass := false
|
isPass := false
|
||||||
|
|
||||||
|
// 遍历用户的所有角色,进行权限检查
|
||||||
for _, sub := range subs {
|
for _, sub := range subs {
|
||||||
|
// 调用 Casbin 引擎进行权限验证
|
||||||
|
// Enforce(subject, object, action) 检查主体是否有权限对对象执行指定动作
|
||||||
pass, _ := common.CasbinEnforcer.Enforce(sub, obj, act)
|
pass, _ := common.CasbinEnforcer.Enforce(sub, obj, act)
|
||||||
if pass {
|
if pass {
|
||||||
isPass = true
|
isPass = true
|
||||||
break
|
break // 只要有一个角色通过验证,就认为有权限
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return isPass
|
return isPass
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
/*
|
||||||
|
Package common 提供 LDAP 连接池管理功能
|
||||||
|
|
||||||
|
LDAP 连接池是系统与 OpenLDAP 服务器交互的核心组件,负责:
|
||||||
|
- 管理 LDAP 连接的创建、复用和销毁
|
||||||
|
- 提供线程安全的连接获取和归还机制
|
||||||
|
- 控制最大连接数,避免资源耗尽
|
||||||
|
- 支持连接的健康检查和自动重连
|
||||||
|
|
||||||
|
连接池的设计目标:
|
||||||
|
1. 提高 LDAP 操作的性能,避免频繁建立连接
|
||||||
|
2. 控制并发连接数,保护 LDAP 服务器资源
|
||||||
|
3. 提供稳定可靠的连接管理机制
|
||||||
|
4. 支持优雅的连接回收和错误处理
|
||||||
|
*/
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -13,133 +28,189 @@ import (
|
||||||
ldap "github.com/go-ldap/ldap/v3"
|
ldap "github.com/go-ldap/ldap/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ldapPool *LdapConnPool
|
// LDAP 连接池相关全局变量
|
||||||
var ldapInit = false
|
var (
|
||||||
var ldapInitOne sync.Once
|
ldapPool *LdapConnPool // LDAP 连接池实例
|
||||||
|
ldapInit = false // LDAP 初始化标志
|
||||||
|
ldapInitOne sync.Once // 确保 LDAP 只初始化一次
|
||||||
|
)
|
||||||
|
|
||||||
// Init 初始化连接
|
// InitLDAP 初始化 LDAP 连接池
|
||||||
|
// 该函数在系统启动时调用,负责:
|
||||||
|
// 1. 建立与 LDAP 服务器的初始连接
|
||||||
|
// 2. 验证管理员账户凭据
|
||||||
|
// 3. 初始化连接池结构
|
||||||
|
// 4. 设置连接池参数
|
||||||
func InitLDAP() {
|
func InitLDAP() {
|
||||||
|
// 防止重复初始化
|
||||||
if ldapInit {
|
if ldapInit {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用 sync.Once 确保只初始化一次
|
||||||
ldapInitOne.Do(func() {
|
ldapInitOne.Do(func() {
|
||||||
ldapInit = true
|
ldapInit = true
|
||||||
})
|
})
|
||||||
|
|
||||||
// Dail有两个参数 network, address, 返回 (*Conn, error)
|
// ==================== 建立初始 LDAP 连接 ====================
|
||||||
|
|
||||||
|
// 创建 LDAP 连接,设置 5 秒连接超时
|
||||||
ldapConn, err := ldap.DialURL(config.Conf.Ldap.Url, ldap.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}))
|
ldapConn, err := ldap.DialURL(config.Conf.Ldap.Url, ldap.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Log.Panicf("初始化ldap连接异常: %v", err)
|
Log.Panicf("初始化 LDAP 连接异常: %v", err)
|
||||||
panic(fmt.Errorf("初始化ldap连接异常: %v", err))
|
panic(fmt.Errorf("初始化 LDAP 连接异常: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用管理员账户绑定 LDAP 连接
|
||||||
err = ldapConn.Bind(config.Conf.Ldap.AdminDN, config.Conf.Ldap.AdminPass)
|
err = ldapConn.Bind(config.Conf.Ldap.AdminDN, config.Conf.Ldap.AdminPass)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Log.Panicf("绑定admin账号异常: %v", err)
|
Log.Panicf("绑定 LDAP 管理员账号异常: %v", err)
|
||||||
panic(fmt.Errorf("绑定admin账号异常: %v", err))
|
panic(fmt.Errorf("绑定 LDAP 管理员账号异常: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局变量赋值
|
// ==================== 初始化连接池 ====================
|
||||||
|
|
||||||
|
// 创建连接池实例
|
||||||
ldapPool = &LdapConnPool{
|
ldapPool = &LdapConnPool{
|
||||||
conns: make([]*ldap.Conn, 0),
|
conns: make([]*ldap.Conn, 0), // 可用连接切片
|
||||||
reqConns: make(map[uint64]chan *ldap.Conn),
|
reqConns: make(map[uint64]chan *ldap.Conn), // 等待连接的请求映射
|
||||||
openConn: 0,
|
openConn: 0, // 当前打开的连接数
|
||||||
maxOpen: config.Conf.Ldap.MaxConn,
|
maxOpen: config.Conf.Ldap.MaxConn, // 最大连接数
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将初始连接放入连接池
|
||||||
PutLADPConn(ldapConn)
|
PutLADPConn(ldapConn)
|
||||||
|
|
||||||
// 隐藏密码
|
// ==================== 输出初始化信息 ====================
|
||||||
|
|
||||||
|
// 构建显示用的 DSN(隐藏密码)
|
||||||
showDsn := fmt.Sprintf(
|
showDsn := fmt.Sprintf(
|
||||||
"%s:******@tcp(%s)",
|
"%s:******@tcp(%s)",
|
||||||
config.Conf.Ldap.AdminDN,
|
config.Conf.Ldap.AdminDN,
|
||||||
config.Conf.Ldap.Url,
|
config.Conf.Ldap.Url,
|
||||||
)
|
)
|
||||||
|
|
||||||
Log.Info("初始化ldap完成! dsn: ", showDsn)
|
Log.Info("🔗 LDAP 连接池初始化完成! DSN: ", showDsn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLDAPConn 获取 LDAP 连接
|
// GetLDAPConn 从连接池获取 LDAP 连接
|
||||||
|
// 返回一个可用的 LDAP 连接,使用完毕后需要调用 PutLADPConn 归还
|
||||||
func GetLDAPConn() (*ldap.Conn, error) {
|
func GetLDAPConn() (*ldap.Conn, error) {
|
||||||
return ldapPool.GetConnection()
|
return ldapPool.GetConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutLDAPConn 放回 LDAP 连接
|
// PutLADPConn 将 LDAP 连接归还到连接池
|
||||||
|
// 连接使用完毕后必须调用此方法归还,以便其他请求复用
|
||||||
func PutLADPConn(conn *ldap.Conn) {
|
func PutLADPConn(conn *ldap.Conn) {
|
||||||
ldapPool.PutConnection(conn)
|
ldapPool.PutConnection(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LdapConnPool LDAP 连接池结构体
|
||||||
|
// 实现了线程安全的连接池管理机制
|
||||||
type LdapConnPool struct {
|
type LdapConnPool struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex // 互斥锁,保证线程安全
|
||||||
conns []*ldap.Conn
|
conns []*ldap.Conn // 可用连接切片
|
||||||
reqConns map[uint64]chan *ldap.Conn
|
reqConns map[uint64]chan *ldap.Conn // 等待连接的请求映射表
|
||||||
openConn int
|
openConn int // 当前打开的连接数
|
||||||
maxOpen int
|
maxOpen int // 最大允许的连接数
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取一个 ladp Conn
|
// GetConnection 从连接池获取一个可用的 LDAP 连接
|
||||||
|
// 该方法实现了连接池的核心逻辑:
|
||||||
|
// 1. 优先从池中获取现有连接
|
||||||
|
// 2. 检查连接健康状态
|
||||||
|
// 3. 控制最大连接数限制
|
||||||
|
// 4. 支持连接等待队列
|
||||||
func (lcp *LdapConnPool) GetConnection() (*ldap.Conn, error) {
|
func (lcp *LdapConnPool) GetConnection() (*ldap.Conn, error) {
|
||||||
lcp.mu.Lock()
|
lcp.mu.Lock()
|
||||||
// 判断当前连接池内是否存在连接
|
|
||||||
|
// ==================== 从连接池获取现有连接 ====================
|
||||||
|
|
||||||
|
// 检查连接池中是否有可用连接
|
||||||
connNum := len(lcp.conns)
|
connNum := len(lcp.conns)
|
||||||
if connNum > 0 {
|
if connNum > 0 {
|
||||||
lcp.openConn++
|
lcp.openConn++ // 增加打开连接计数
|
||||||
conn := lcp.conns[0]
|
conn := lcp.conns[0] // 获取第一个连接
|
||||||
copy(lcp.conns, lcp.conns[1:])
|
copy(lcp.conns, lcp.conns[1:]) // 移除已获取的连接
|
||||||
lcp.conns = lcp.conns[:connNum-1]
|
lcp.conns = lcp.conns[:connNum-1] // 调整切片长度
|
||||||
|
|
||||||
lcp.mu.Unlock()
|
lcp.mu.Unlock()
|
||||||
// 发现连接已经 close 重新获取连接
|
|
||||||
|
// 检查连接是否已关闭,如果已关闭则创建新连接
|
||||||
if conn.IsClosing() {
|
if conn.IsClosing() {
|
||||||
return initLDAPConn()
|
return initLDAPConn()
|
||||||
}
|
}
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当现有连接池为空时,并且当前超过最大连接限制
|
// ==================== 连接数限制处理 ====================
|
||||||
if lcp.maxOpen != 0 && lcp.openConn > lcp.maxOpen {
|
|
||||||
// 创建一个等待队列
|
// 当连接池为空且已达到最大连接数限制时
|
||||||
|
if lcp.maxOpen != 0 && lcp.openConn >= lcp.maxOpen {
|
||||||
|
// 创建等待队列,当有连接归还时会通知等待的请求
|
||||||
req := make(chan *ldap.Conn, 1)
|
req := make(chan *ldap.Conn, 1)
|
||||||
reqKey := lcp.nextRequestKeyLocked()
|
reqKey := lcp.nextRequestKeyLocked() // 生成唯一请求键
|
||||||
lcp.reqConns[reqKey] = req
|
lcp.reqConns[reqKey] = req // 将请求加入等待队列
|
||||||
lcp.mu.Unlock()
|
lcp.mu.Unlock()
|
||||||
|
|
||||||
// 等待请求归还
|
// 阻塞等待连接归还
|
||||||
return <-req, nil
|
return <-req, nil
|
||||||
} else {
|
} else {
|
||||||
|
// 未达到连接数限制,创建新连接
|
||||||
lcp.openConn++
|
lcp.openConn++
|
||||||
lcp.mu.Unlock()
|
lcp.mu.Unlock()
|
||||||
return initLDAPConn()
|
return initLDAPConn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutConnection 将 LDAP 连接归还到连接池
|
||||||
|
// 该方法负责连接的回收和分配:
|
||||||
|
// 1. 优先满足等待队列中的请求
|
||||||
|
// 2. 将健康的连接放回连接池
|
||||||
|
// 3. 丢弃已关闭的连接
|
||||||
func (lcp *LdapConnPool) PutConnection(conn *ldap.Conn) {
|
func (lcp *LdapConnPool) PutConnection(conn *ldap.Conn) {
|
||||||
log.Println("放回了一个 LDAP 连接")
|
log.Println("🔄 归还一个 LDAP 连接到连接池")
|
||||||
lcp.mu.Lock()
|
lcp.mu.Lock()
|
||||||
defer lcp.mu.Unlock()
|
defer lcp.mu.Unlock()
|
||||||
|
|
||||||
// 先判断是否存在等待的队列
|
// ==================== 优先处理等待队列 ====================
|
||||||
|
|
||||||
|
// 检查是否有等待连接的请求
|
||||||
if num := len(lcp.reqConns); num > 0 {
|
if num := len(lcp.reqConns); num > 0 {
|
||||||
var req chan *ldap.Conn
|
var req chan *ldap.Conn
|
||||||
var reqKey uint64
|
var reqKey uint64
|
||||||
|
|
||||||
|
// 获取第一个等待请求
|
||||||
for reqKey, req = range lcp.reqConns {
|
for reqKey, req = range lcp.reqConns {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从等待队列中移除该请求
|
||||||
delete(lcp.reqConns, reqKey)
|
delete(lcp.reqConns, reqKey)
|
||||||
|
|
||||||
|
// 将连接发送给等待的请求
|
||||||
req <- conn
|
req <- conn
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
lcp.openConn--
|
|
||||||
if !conn.IsClosing() {
|
// ==================== 连接回收处理 ====================
|
||||||
lcp.conns = append(lcp.conns, conn)
|
|
||||||
}
|
// 没有等待请求时,将连接放回连接池
|
||||||
|
lcp.openConn-- // 减少打开连接计数
|
||||||
|
|
||||||
|
// 只有健康的连接才放回连接池
|
||||||
|
if !conn.IsClosing() {
|
||||||
|
lcp.conns = append(lcp.conns, conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取下一个请求令牌
|
// nextRequestKeyLocked 生成下一个唯一的请求键
|
||||||
|
// 用于标识等待队列中的请求,确保每个等待请求都有唯一标识
|
||||||
|
// 注意:调用此方法时必须已经持有互斥锁
|
||||||
func (lcp *LdapConnPool) nextRequestKeyLocked() uint64 {
|
func (lcp *LdapConnPool) nextRequestKeyLocked() uint64 {
|
||||||
for {
|
for {
|
||||||
reqKey := rand.Uint64()
|
reqKey := rand.Uint64() // 生成随机数作为请求键
|
||||||
if _, ok := lcp.reqConns[reqKey]; !ok {
|
if _, ok := lcp.reqConns[reqKey]; !ok { // 确保键的唯一性
|
||||||
return reqKey
|
return reqKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,26 @@
|
||||||
|
/*
|
||||||
|
Package routes 系统路由配置的主入口
|
||||||
|
|
||||||
|
该文件负责初始化和配置整个系统的路由结构,包括:
|
||||||
|
- Gin 引擎的基础配置
|
||||||
|
- 全局中间件的注册和配置
|
||||||
|
- 静态文件服务配置
|
||||||
|
- API 路由组的初始化
|
||||||
|
- Swagger 文档路由配置
|
||||||
|
|
||||||
|
路由架构说明:
|
||||||
|
1. 静态文件路由:服务前端 Vue 应用
|
||||||
|
2. API 路由组:所有后端 API 接口
|
||||||
|
3. Swagger 文档:API 文档和测试界面
|
||||||
|
|
||||||
|
中间件执行顺序:
|
||||||
|
1. 静态文件服务中间件
|
||||||
|
2. 限流中间件(令牌桶算法)
|
||||||
|
3. CORS 跨域中间件
|
||||||
|
4. 操作日志中间件
|
||||||
|
5. JWT 认证中间件(特定路由)
|
||||||
|
6. Casbin 权限中间件(特定路由)
|
||||||
|
*/
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -15,19 +38,36 @@ import (
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 初始化
|
// InitRoutes 初始化系统路由
|
||||||
|
// 该函数是整个路由系统的入口点,负责:
|
||||||
|
// 1. 配置 Gin 引擎的运行模式
|
||||||
|
// 2. 注册全局中间件
|
||||||
|
// 3. 配置静态文件服务
|
||||||
|
// 4. 初始化 API 路由组
|
||||||
|
// 5. 配置 Swagger 文档路由
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - *gin.Engine: 配置完成的 Gin 引擎实例
|
||||||
func InitRoutes() *gin.Engine {
|
func InitRoutes() *gin.Engine {
|
||||||
//设置模式
|
// ==================== Gin 引擎配置 ====================
|
||||||
|
|
||||||
|
// 根据配置文件设置 Gin 运行模式(debug/release/test)
|
||||||
gin.SetMode(config.Conf.System.Mode)
|
gin.SetMode(config.Conf.System.Mode)
|
||||||
|
|
||||||
// 创建带有默认中间件的路由:
|
// 创建 Gin 引擎实例,包含默认的日志和恢复中间件
|
||||||
// 日志与恢复中间件
|
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
// 创建不带中间件的路由:
|
|
||||||
|
// 备选方案:创建不带默认中间件的引擎
|
||||||
// r := gin.New()
|
// r := gin.New()
|
||||||
// r.Use(gin.Recovery())
|
// r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
// ==================== 静态文件服务配置 ====================
|
||||||
|
|
||||||
|
// 配置静态文件服务,用于提供前端 Vue 应用
|
||||||
r.Use(middleware.Serve("/", middleware.EmbedFolder(static.Static, "dist")))
|
r.Use(middleware.Serve("/", middleware.EmbedFolder(static.Static, "dist")))
|
||||||
|
|
||||||
|
// 配置 NoRoute 处理器,用于 SPA 路由支持
|
||||||
|
// 当请求的路由不存在时,返回 index.html,让前端路由接管
|
||||||
r.NoRoute(func(c *gin.Context) {
|
r.NoRoute(func(c *gin.Context) {
|
||||||
data, err := static.Static.ReadFile("dist/index.html")
|
data, err := static.Static.ReadFile("dist/index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,38 +77,54 @@ func InitRoutes() *gin.Engine {
|
||||||
c.Data(http.StatusOK, "text/html; charset=utf-8", data)
|
c.Data(http.StatusOK, "text/html; charset=utf-8", data)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 启用限流中间件
|
// ==================== 全局中间件配置 ====================
|
||||||
// 默认每50毫秒填充一个令牌,最多填充200个
|
|
||||||
|
// 配置限流中间件(令牌桶算法)
|
||||||
|
// 根据配置文件设置令牌填充间隔和桶容量,防止 API 被恶意调用
|
||||||
fillInterval := time.Duration(config.Conf.RateLimit.FillInterval)
|
fillInterval := time.Duration(config.Conf.RateLimit.FillInterval)
|
||||||
capacity := config.Conf.RateLimit.Capacity
|
capacity := config.Conf.RateLimit.Capacity
|
||||||
r.Use(middleware.RateLimitMiddleware(time.Millisecond*fillInterval, capacity))
|
r.Use(middleware.RateLimitMiddleware(time.Millisecond*fillInterval, capacity))
|
||||||
|
|
||||||
// 启用全局跨域中间件
|
// 启用 CORS 跨域中间件
|
||||||
|
// 允许前端应用跨域访问后端 API
|
||||||
r.Use(middleware.CORSMiddleware())
|
r.Use(middleware.CORSMiddleware())
|
||||||
|
|
||||||
// 启用操作日志中间件
|
// 启用操作日志中间件
|
||||||
|
// 记录所有 API 请求的详细信息,用于审计和问题排查
|
||||||
r.Use(middleware.OperationLogMiddleware())
|
r.Use(middleware.OperationLogMiddleware())
|
||||||
|
|
||||||
// 初始化JWT认证中间件
|
// ==================== JWT 认证中间件初始化 ====================
|
||||||
|
|
||||||
|
// 初始化 JWT 认证中间件
|
||||||
authMiddleware, err := middleware.InitAuth()
|
authMiddleware, err := middleware.InitAuth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Log.Panicf("初始化JWT中间件失败:%v", err)
|
common.Log.Panicf("初始化 JWT 中间件失败:%v", err)
|
||||||
panic(fmt.Sprintf("初始化JWT中间件失败:%v", err))
|
panic(fmt.Sprintf("初始化 JWT 中间件失败:%v", err))
|
||||||
}
|
}
|
||||||
// swag
|
|
||||||
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
|
||||||
apiGroup := r.Group("/" + config.Conf.System.UrlPathPrefix)
|
|
||||||
// swag
|
|
||||||
// 注册路由
|
|
||||||
InitBaseRoutes(apiGroup, authMiddleware) // 注册基础路由, 不需要jwt认证中间件,不需要casbin中间件
|
|
||||||
InitUserRoutes(apiGroup, authMiddleware) // 注册用户路由, jwt认证中间件,casbin鉴权中间件
|
|
||||||
InitGroupRoutes(apiGroup, authMiddleware) // 注册分组路由, jwt认证中间件,casbin鉴权中间件
|
|
||||||
InitRoleRoutes(apiGroup, authMiddleware) // 注册角色路由, jwt认证中间件,casbin鉴权中间件
|
|
||||||
InitMenuRoutes(apiGroup, authMiddleware) // 注册菜单路由, jwt认证中间件,casbin鉴权中间件
|
|
||||||
InitApiRoutes(apiGroup, authMiddleware) // 注册接口路由, jwt认证中间件,casbin鉴权中间件
|
|
||||||
InitOperationLogRoutes(apiGroup, authMiddleware) // 注册操作日志路由, jwt认证中间件,casbin鉴权中间件
|
|
||||||
InitFieldRelationRoutes(apiGroup, authMiddleware) // 注册操作日志路由, jwt认证中间件,casbin鉴权中间件
|
|
||||||
|
|
||||||
common.Log.Info("初始化路由完成!")
|
// ==================== Swagger 文档路由 ====================
|
||||||
|
|
||||||
|
// 注册 Swagger API 文档路由
|
||||||
|
// 提供交互式的 API 文档和测试界面
|
||||||
|
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
|
||||||
|
// ==================== API 路由组注册 ====================
|
||||||
|
|
||||||
|
// 创建 API 路由组,所有业务接口都在此组下
|
||||||
|
apiGroup := r.Group("/" + config.Conf.System.UrlPathPrefix)
|
||||||
|
|
||||||
|
// 注册各个功能模块的路由
|
||||||
|
// 注意:路由注册顺序会影响中间件的执行顺序
|
||||||
|
|
||||||
|
InitBaseRoutes(apiGroup, authMiddleware) // 基础路由(登录、登出等,无需认证)
|
||||||
|
InitUserRoutes(apiGroup, authMiddleware) // 用户管理路由(需要 JWT + Casbin)
|
||||||
|
InitGroupRoutes(apiGroup, authMiddleware) // 部门/组管理路由(需要 JWT + Casbin)
|
||||||
|
InitRoleRoutes(apiGroup, authMiddleware) // 角色管理路由(需要 JWT + Casbin)
|
||||||
|
InitMenuRoutes(apiGroup, authMiddleware) // 菜单管理路由(需要 JWT + Casbin)
|
||||||
|
InitApiRoutes(apiGroup, authMiddleware) // 接口管理路由(需要 JWT + Casbin)
|
||||||
|
InitOperationLogRoutes(apiGroup, authMiddleware) // 操作日志路由(需要 JWT + Casbin)
|
||||||
|
InitFieldRelationRoutes(apiGroup, authMiddleware) // 字段关系路由(需要 JWT + Casbin)
|
||||||
|
|
||||||
|
common.Log.Info("🚀 系统路由初始化完成!")
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
/*
|
||||||
|
Package routes 包含系统的所有路由配置
|
||||||
|
|
||||||
|
用户路由模块负责定义所有与用户管理相关的 API 接口路由,包括:
|
||||||
|
- 用户基础管理:增删改查、密码管理、状态管理
|
||||||
|
- 用户信息查询:个人信息、用户列表
|
||||||
|
- 企业 IM 同步:钉钉、企业微信、飞书用户同步
|
||||||
|
- LDAP 同步:OpenLDAP 与数据库之间的用户同步
|
||||||
|
|
||||||
|
路由安全策略:
|
||||||
|
- 所有用户管理接口都需要 JWT 认证
|
||||||
|
- 所有接口都需要通过 Casbin 权限验证
|
||||||
|
- 不同操作需要不同的角色权限
|
||||||
|
*/
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -8,27 +22,57 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 注册用户路由
|
// InitUserRoutes 初始化用户管理相关路由
|
||||||
|
// 该函数注册所有与用户管理相关的 API 接口,包括基础的 CRUD 操作和企业 IM 同步功能
|
||||||
|
//
|
||||||
|
// 中间件说明:
|
||||||
|
// - JWT 认证中间件:验证用户登录状态,确保只有登录用户才能访问
|
||||||
|
// - Casbin 权限中间件:基于 RBAC 模型验证用户是否有权限执行特定操作
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - r: Gin 路由组,用于注册路由
|
||||||
|
// - authMiddleware: JWT 认证中间件实例
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - gin.IRoutes: 返回路由接口,支持链式调用
|
||||||
func InitUserRoutes(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) gin.IRoutes {
|
func InitUserRoutes(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) gin.IRoutes {
|
||||||
|
// 创建用户路由组
|
||||||
user := r.Group("/user")
|
user := r.Group("/user")
|
||||||
// 开启jwt认证中间件
|
|
||||||
user.Use(authMiddleware.MiddlewareFunc())
|
|
||||||
// 开启casbin鉴权中间件
|
|
||||||
user.Use(middleware.CasbinMiddleware())
|
|
||||||
{
|
|
||||||
user.GET("/info", controller.User.GetUserInfo) // 暂时未完成
|
|
||||||
user.GET("/list", controller.User.List) // 用户列表
|
|
||||||
user.POST("/add", controller.User.Add) // 添加用户
|
|
||||||
user.POST("/update", controller.User.Update) // 更新用户
|
|
||||||
user.POST("/delete", controller.User.Delete) // 删除用户
|
|
||||||
user.POST("/changePwd", controller.User.ChangePwd) // 修改用户密码
|
|
||||||
user.POST("/changeUserStatus", controller.User.ChangeUserStatus) // 修改用户状态
|
|
||||||
|
|
||||||
user.POST("/syncDingTalkUsers", controller.User.SyncDingTalkUsers) // 同步钉钉用户到平台
|
// ==================== 中间件配置 ====================
|
||||||
user.POST("/syncWeComUsers", controller.User.SyncWeComUsers) // 同步企业微信用户到平台
|
|
||||||
user.POST("/syncFeiShuUsers", controller.User.SyncFeiShuUsers) // 同步飞书用户到平台
|
// 启用 JWT 认证中间件,确保用户已登录
|
||||||
user.POST("/syncOpenLdapUsers", controller.User.SyncOpenLdapUsers) // 同步Ldap用户到平台
|
user.Use(authMiddleware.MiddlewareFunc())
|
||||||
user.POST("/syncSqlUsers", controller.User.SyncSqlUsers) // 同步Sql用户到Ldap
|
|
||||||
|
// 启用 Casbin 权限控制中间件,进行细粒度权限验证
|
||||||
|
user.Use(middleware.CasbinMiddleware())
|
||||||
|
|
||||||
|
// ==================== 用户基础管理接口 ====================
|
||||||
|
{
|
||||||
|
// 用户信息查询接口
|
||||||
|
user.GET("/info", controller.User.GetUserInfo) // 获取当前登录用户详细信息
|
||||||
|
user.GET("/list", controller.User.List) // 获取用户列表(支持分页和筛选)
|
||||||
|
|
||||||
|
// 用户管理接口
|
||||||
|
user.POST("/add", controller.User.Add) // 创建新用户(同时在 MySQL 和 LDAP 中创建)
|
||||||
|
user.POST("/update", controller.User.Update) // 更新用户信息(同步更新 MySQL 和 LDAP)
|
||||||
|
user.POST("/delete", controller.User.Delete) // 删除用户(同时从 MySQL 和 LDAP 中删除)
|
||||||
|
|
||||||
|
// 用户账户管理接口
|
||||||
|
user.POST("/changePwd", controller.User.ChangePwd) // 修改用户密码(支持管理员修改和用户自助修改)
|
||||||
|
user.POST("/changeUserStatus", controller.User.ChangeUserStatus) // 修改用户状态(启用/禁用账户)
|
||||||
|
|
||||||
|
// ==================== 企业 IM 同步接口 ====================
|
||||||
|
|
||||||
|
// 第三方平台用户同步到本系统
|
||||||
|
user.POST("/syncDingTalkUsers", controller.User.SyncDingTalkUsers) // 从钉钉同步用户到平台
|
||||||
|
user.POST("/syncWeComUsers", controller.User.SyncWeComUsers) // 从企业微信同步用户到平台
|
||||||
|
user.POST("/syncFeiShuUsers", controller.User.SyncFeiShuUsers) // 从飞书同步用户到平台
|
||||||
|
|
||||||
|
// LDAP 与数据库之间的同步
|
||||||
|
user.POST("/syncOpenLdapUsers", controller.User.SyncOpenLdapUsers) // 从 OpenLDAP 同步用户到数据库
|
||||||
|
user.POST("/syncSqlUsers", controller.User.SyncSqlUsers) // 从数据库同步用户到 OpenLDAP
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ type OperationLogService struct{}
|
||||||
func (s OperationLogService) SaveOperationLogChannel(olc <-chan *model.OperationLog) {
|
func (s OperationLogService) SaveOperationLogChannel(olc <-chan *model.OperationLog) {
|
||||||
// 只会在线程开启的时候执行一次
|
// 只会在线程开启的时候执行一次
|
||||||
Logs := make([]model.OperationLog, 0)
|
Logs := make([]model.OperationLog, 0)
|
||||||
//5s 自动同步一次
|
// 5s 自动同步一次
|
||||||
duration := 5 * time.Second
|
duration := 5 * time.Second
|
||||||
timer := time.NewTimer(duration)
|
timer := time.NewTimer(duration)
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
|
@ -35,7 +35,7 @@ func (s OperationLogService) SaveOperationLogChannel(olc <-chan *model.Operation
|
||||||
Logs = make([]model.OperationLog, 0)
|
Logs = make([]model.OperationLog, 0)
|
||||||
timer.Reset(duration) // 入库重置定时器
|
timer.Reset(duration) // 入库重置定时器
|
||||||
}
|
}
|
||||||
case <-timer.C: //5s 自动同步一次
|
case <-timer.C: // 5s 自动同步一次
|
||||||
if len(Logs) > 0 {
|
if len(Logs) > 0 {
|
||||||
common.DB.Create(&Logs)
|
common.DB.Create(&Logs)
|
||||||
Logs = make([]model.OperationLog, 0)
|
Logs = make([]model.OperationLog, 0)
|
||||||
|
@ -102,5 +102,5 @@ func (s OperationLogService) Delete(operationLogIds []uint) error {
|
||||||
|
|
||||||
// Clean 清空日志
|
// Clean 清空日志
|
||||||
func (s OperationLogService) Clean() error {
|
func (s OperationLogService) Clean() error {
|
||||||
return common.DB.Exec("TRUNCATE TABLE operation_logs").Error
|
return common.DB.Exec("DELETE FROM operation_logs").Error
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue