2025-08-26 00:43:50 +08:00
|
|
|
|
/*
|
|
|
|
|
Package common 提供 LDAP 连接池管理功能
|
|
|
|
|
|
|
|
|
|
LDAP 连接池是系统与 OpenLDAP 服务器交互的核心组件,负责:
|
|
|
|
|
- 管理 LDAP 连接的创建、复用和销毁
|
|
|
|
|
- 提供线程安全的连接获取和归还机制
|
|
|
|
|
- 控制最大连接数,避免资源耗尽
|
|
|
|
|
- 支持连接的健康检查和自动重连
|
|
|
|
|
|
|
|
|
|
连接池的设计目标:
|
|
|
|
|
1. 提高 LDAP 操作的性能,避免频繁建立连接
|
|
|
|
|
2. 控制并发连接数,保护 LDAP 服务器资源
|
|
|
|
|
3. 提供稳定可靠的连接管理机制
|
|
|
|
|
4. 支持优雅的连接回收和错误处理
|
|
|
|
|
*/
|
2022-05-18 17:57:03 +08:00
|
|
|
|
package common
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2022-07-24 21:24:08 +08:00
|
|
|
|
"log"
|
|
|
|
|
"math/rand"
|
2022-05-18 17:57:03 +08:00
|
|
|
|
"net"
|
2022-07-24 21:24:08 +08:00
|
|
|
|
"sync"
|
2022-05-18 17:57:03 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
2022-05-29 10:06:21 +08:00
|
|
|
|
"github.com/eryajf/go-ldap-admin/config"
|
2022-05-18 17:57:03 +08:00
|
|
|
|
|
|
|
|
|
ldap "github.com/go-ldap/ldap/v3"
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// LDAP 连接池相关全局变量
|
|
|
|
|
var (
|
|
|
|
|
ldapPool *LdapConnPool // LDAP 连接池实例
|
|
|
|
|
ldapInit = false // LDAP 初始化标志
|
|
|
|
|
ldapInitOne sync.Once // 确保 LDAP 只初始化一次
|
|
|
|
|
)
|
2022-05-18 17:57:03 +08:00
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// InitLDAP 初始化 LDAP 连接池
|
|
|
|
|
// 该函数在系统启动时调用,负责:
|
|
|
|
|
// 1. 建立与 LDAP 服务器的初始连接
|
|
|
|
|
// 2. 验证管理员账户凭据
|
|
|
|
|
// 3. 初始化连接池结构
|
|
|
|
|
// 4. 设置连接池参数
|
2022-05-18 17:57:03 +08:00
|
|
|
|
func InitLDAP() {
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// 防止重复初始化
|
2022-07-24 21:24:08 +08:00
|
|
|
|
if ldapInit {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// 使用 sync.Once 确保只初始化一次
|
2022-07-24 21:24:08 +08:00
|
|
|
|
ldapInitOne.Do(func() {
|
|
|
|
|
ldapInit = true
|
|
|
|
|
})
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// ==================== 建立初始 LDAP 连接 ====================
|
|
|
|
|
|
|
|
|
|
// 创建 LDAP 连接,设置 5 秒连接超时
|
2022-07-24 21:24:08 +08:00
|
|
|
|
ldapConn, err := ldap.DialURL(config.Conf.Ldap.Url, ldap.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}))
|
2022-05-18 17:57:03 +08:00
|
|
|
|
if err != nil {
|
2025-08-26 00:43:50 +08:00
|
|
|
|
Log.Panicf("初始化 LDAP 连接异常: %v", err)
|
|
|
|
|
panic(fmt.Errorf("初始化 LDAP 连接异常: %v", err))
|
2022-05-18 17:57:03 +08:00
|
|
|
|
}
|
2025-08-26 00:43:50 +08:00
|
|
|
|
|
|
|
|
|
// 使用管理员账户绑定 LDAP 连接
|
2022-07-24 21:24:08 +08:00
|
|
|
|
err = ldapConn.Bind(config.Conf.Ldap.AdminDN, config.Conf.Ldap.AdminPass)
|
2022-05-18 17:57:03 +08:00
|
|
|
|
if err != nil {
|
2025-08-26 00:43:50 +08:00
|
|
|
|
Log.Panicf("绑定 LDAP 管理员账号异常: %v", err)
|
|
|
|
|
panic(fmt.Errorf("绑定 LDAP 管理员账号异常: %v", err))
|
2022-05-18 17:57:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// ==================== 初始化连接池 ====================
|
|
|
|
|
|
|
|
|
|
// 创建连接池实例
|
2022-07-24 21:24:08 +08:00
|
|
|
|
ldapPool = &LdapConnPool{
|
2025-08-26 00:43:50 +08:00
|
|
|
|
conns: make([]*ldap.Conn, 0), // 可用连接切片
|
|
|
|
|
reqConns: make(map[uint64]chan *ldap.Conn), // 等待连接的请求映射
|
|
|
|
|
openConn: 0, // 当前打开的连接数
|
|
|
|
|
maxOpen: config.Conf.Ldap.MaxConn, // 最大连接数
|
2022-07-24 21:24:08 +08:00
|
|
|
|
}
|
2025-08-26 00:43:50 +08:00
|
|
|
|
|
|
|
|
|
// 将初始连接放入连接池
|
2022-07-24 21:24:08 +08:00
|
|
|
|
PutLADPConn(ldapConn)
|
2022-05-18 17:57:03 +08:00
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// ==================== 输出初始化信息 ====================
|
|
|
|
|
|
|
|
|
|
// 构建显示用的 DSN(隐藏密码)
|
2022-05-18 17:57:03 +08:00
|
|
|
|
showDsn := fmt.Sprintf(
|
|
|
|
|
"%s:******@tcp(%s)",
|
2022-06-14 12:08:16 +08:00
|
|
|
|
config.Conf.Ldap.AdminDN,
|
|
|
|
|
config.Conf.Ldap.Url,
|
2022-05-18 17:57:03 +08:00
|
|
|
|
)
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
Log.Info("🔗 LDAP 连接池初始化完成! DSN: ", showDsn)
|
2022-05-18 17:57:03 +08:00
|
|
|
|
}
|
2022-07-24 21:24:08 +08:00
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// GetLDAPConn 从连接池获取 LDAP 连接
|
|
|
|
|
// 返回一个可用的 LDAP 连接,使用完毕后需要调用 PutLADPConn 归还
|
2022-07-24 21:24:08 +08:00
|
|
|
|
func GetLDAPConn() (*ldap.Conn, error) {
|
|
|
|
|
return ldapPool.GetConnection()
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// PutLADPConn 将 LDAP 连接归还到连接池
|
|
|
|
|
// 连接使用完毕后必须调用此方法归还,以便其他请求复用
|
2022-07-24 21:24:08 +08:00
|
|
|
|
func PutLADPConn(conn *ldap.Conn) {
|
|
|
|
|
ldapPool.PutConnection(conn)
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// LdapConnPool LDAP 连接池结构体
|
|
|
|
|
// 实现了线程安全的连接池管理机制
|
2022-07-24 21:24:08 +08:00
|
|
|
|
type LdapConnPool struct {
|
2025-08-26 00:43:50 +08:00
|
|
|
|
mu sync.Mutex // 互斥锁,保证线程安全
|
|
|
|
|
conns []*ldap.Conn // 可用连接切片
|
|
|
|
|
reqConns map[uint64]chan *ldap.Conn // 等待连接的请求映射表
|
|
|
|
|
openConn int // 当前打开的连接数
|
|
|
|
|
maxOpen int // 最大允许的连接数
|
2022-07-24 21:24:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// GetConnection 从连接池获取一个可用的 LDAP 连接
|
|
|
|
|
// 该方法实现了连接池的核心逻辑:
|
|
|
|
|
// 1. 优先从池中获取现有连接
|
|
|
|
|
// 2. 检查连接健康状态
|
|
|
|
|
// 3. 控制最大连接数限制
|
|
|
|
|
// 4. 支持连接等待队列
|
2022-07-24 21:24:08 +08:00
|
|
|
|
func (lcp *LdapConnPool) GetConnection() (*ldap.Conn, error) {
|
|
|
|
|
lcp.mu.Lock()
|
2025-08-26 00:43:50 +08:00
|
|
|
|
|
|
|
|
|
// ==================== 从连接池获取现有连接 ====================
|
|
|
|
|
|
|
|
|
|
// 检查连接池中是否有可用连接
|
2022-07-24 21:24:08 +08:00
|
|
|
|
connNum := len(lcp.conns)
|
|
|
|
|
if connNum > 0 {
|
2025-08-26 00:43:50 +08:00
|
|
|
|
lcp.openConn++ // 增加打开连接计数
|
|
|
|
|
conn := lcp.conns[0] // 获取第一个连接
|
|
|
|
|
copy(lcp.conns, lcp.conns[1:]) // 移除已获取的连接
|
|
|
|
|
lcp.conns = lcp.conns[:connNum-1] // 调整切片长度
|
2022-07-24 21:24:08 +08:00
|
|
|
|
|
|
|
|
|
lcp.mu.Unlock()
|
2025-08-26 00:43:50 +08:00
|
|
|
|
|
|
|
|
|
// 检查连接是否已关闭,如果已关闭则创建新连接
|
2022-07-24 21:24:08 +08:00
|
|
|
|
if conn.IsClosing() {
|
|
|
|
|
return initLDAPConn()
|
|
|
|
|
}
|
|
|
|
|
return conn, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// ==================== 连接数限制处理 ====================
|
|
|
|
|
|
|
|
|
|
// 当连接池为空且已达到最大连接数限制时
|
|
|
|
|
if lcp.maxOpen != 0 && lcp.openConn >= lcp.maxOpen {
|
|
|
|
|
// 创建等待队列,当有连接归还时会通知等待的请求
|
2022-07-24 21:24:08 +08:00
|
|
|
|
req := make(chan *ldap.Conn, 1)
|
2025-08-26 00:43:50 +08:00
|
|
|
|
reqKey := lcp.nextRequestKeyLocked() // 生成唯一请求键
|
|
|
|
|
lcp.reqConns[reqKey] = req // 将请求加入等待队列
|
2022-07-24 21:24:08 +08:00
|
|
|
|
lcp.mu.Unlock()
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// 阻塞等待连接归还
|
2022-07-24 21:24:08 +08:00
|
|
|
|
return <-req, nil
|
|
|
|
|
} else {
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// 未达到连接数限制,创建新连接
|
2022-07-24 21:24:08 +08:00
|
|
|
|
lcp.openConn++
|
|
|
|
|
lcp.mu.Unlock()
|
|
|
|
|
return initLDAPConn()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// PutConnection 将 LDAP 连接归还到连接池
|
|
|
|
|
// 该方法负责连接的回收和分配:
|
|
|
|
|
// 1. 优先满足等待队列中的请求
|
|
|
|
|
// 2. 将健康的连接放回连接池
|
|
|
|
|
// 3. 丢弃已关闭的连接
|
2022-07-24 21:24:08 +08:00
|
|
|
|
func (lcp *LdapConnPool) PutConnection(conn *ldap.Conn) {
|
2025-08-26 00:43:50 +08:00
|
|
|
|
log.Println("🔄 归还一个 LDAP 连接到连接池")
|
2022-07-24 21:24:08 +08:00
|
|
|
|
lcp.mu.Lock()
|
|
|
|
|
defer lcp.mu.Unlock()
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// ==================== 优先处理等待队列 ====================
|
|
|
|
|
|
|
|
|
|
// 检查是否有等待连接的请求
|
2022-07-24 21:24:08 +08:00
|
|
|
|
if num := len(lcp.reqConns); num > 0 {
|
|
|
|
|
var req chan *ldap.Conn
|
|
|
|
|
var reqKey uint64
|
2025-08-26 00:43:50 +08:00
|
|
|
|
|
|
|
|
|
// 获取第一个等待请求
|
2022-07-24 21:24:08 +08:00
|
|
|
|
for reqKey, req = range lcp.reqConns {
|
|
|
|
|
break
|
|
|
|
|
}
|
2025-08-26 00:43:50 +08:00
|
|
|
|
|
|
|
|
|
// 从等待队列中移除该请求
|
2022-07-24 21:24:08 +08:00
|
|
|
|
delete(lcp.reqConns, reqKey)
|
2025-08-26 00:43:50 +08:00
|
|
|
|
|
|
|
|
|
// 将连接发送给等待的请求
|
2022-07-24 21:24:08 +08:00
|
|
|
|
req <- conn
|
|
|
|
|
return
|
2025-08-26 00:43:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 连接回收处理 ====================
|
|
|
|
|
|
|
|
|
|
// 没有等待请求时,将连接放回连接池
|
|
|
|
|
lcp.openConn-- // 减少打开连接计数
|
|
|
|
|
|
|
|
|
|
// 只有健康的连接才放回连接池
|
|
|
|
|
if !conn.IsClosing() {
|
|
|
|
|
lcp.conns = append(lcp.conns, conn)
|
2022-07-24 21:24:08 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 00:43:50 +08:00
|
|
|
|
// nextRequestKeyLocked 生成下一个唯一的请求键
|
|
|
|
|
// 用于标识等待队列中的请求,确保每个等待请求都有唯一标识
|
|
|
|
|
// 注意:调用此方法时必须已经持有互斥锁
|
2022-07-24 21:24:08 +08:00
|
|
|
|
func (lcp *LdapConnPool) nextRequestKeyLocked() uint64 {
|
|
|
|
|
for {
|
2025-08-26 00:43:50 +08:00
|
|
|
|
reqKey := rand.Uint64() // 生成随机数作为请求键
|
|
|
|
|
if _, ok := lcp.reqConns[reqKey]; !ok { // 确保键的唯一性
|
2022-07-24 21:24:08 +08:00
|
|
|
|
return reqKey
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取 ladp 连接
|
|
|
|
|
func initLDAPConn() (*ldap.Conn, error) {
|
|
|
|
|
ldap, err := ldap.DialURL(config.Conf.Ldap.Url, ldap.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
err = ldap.Bind(config.Conf.Ldap.AdminDN, config.Conf.Ldap.AdminPass)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return ldap, err
|
|
|
|
|
}
|