Files
LokiStack/backend/services/ldap_import.go
T
opsx f3733f0bfc feat: 品牌统一 LokiStack + 全站中英 i18n + 用户批量删除 + 主题定时切换与浅色侧栏
- 品牌: Go 模块/数据文件/二进制/构建部署资产全部 opsx→lokistack;前端 localStorage 命名空间迁移(读旧写新,含按用户 id 前缀迁移);修复 nginx upstream 与 Makefile 引用已改名服务的断裂
- i18n: react-i18next 落地,common + 11 个页面域命名空间全站中英(菜单/顶栏/登录/列表页/表单/确认弹窗/状态徽章);设置页语言即时切换;修复词典自注册导致的循环导入白屏
- 用户管理: 批量删除(POST /users/batch-delete,不删自己/最后管理员,部分失败不阻断)+ 行复选/全选/批量工具条
- 主题: 浅色侧栏(中性灰激活态)、四态模式(浅色/深色/跟随系统/定时切换,按时区判定)、顶栏太阳月亮二态、设置页紧凑分段选择器
- UI: 登录记住用户名、CardActionButton 统一五页卡片操作钮、shadcn Badge 替换手写徽章、语义色修正
- 清理: 移除主机 Web 终端(前后端)、k8s-helm/Helm 遗留代码与字段、README 重写精简
- 修复: 侧栏与日志告警 order 重复、路由标题 i18n key 未翻译、词典双逗号等
2026-10-27 17:11:38 +08:00

109 lines
2.9 KiB
Go

package services
import (
"time"
"lokistack/models"
"lokistack/pkg/crypto"
"lokistack/repository"
)
// LDAPImportUser 批量导入请求中的用户条目(数据来自预览接口)
type LDAPImportUser struct {
DN string `json:"dn"`
Username string `json:"username" binding:"required"`
Nickname string `json:"nickname"`
Email string `json:"email"`
}
// LDAPImportResult 导入结果(与前端展示契约对齐)
type LDAPImportResult struct {
SuccessCount int `json:"successCount"`
SkippedCount int `json:"skippedCount"`
UpdatedCount int `json:"updatedCount"`
FailedCount int `json:"failedCount"`
FailedUsers []map[string]interface{} `json:"failedUsers"`
}
// BatchImportLDAPUsers 批量导入 LDAP 用户:
// 已存在且 LDAP 用户 → 跳过;本地同名 → 更新昵称并转 LDAP;否则新建(user 角色,密码占位)。
func BatchImportLDAPUsers(users []LDAPImportUser) *LDAPImportResult {
result := &LDAPImportResult{FailedUsers: []map[string]interface{}{}}
db := repository.GetDB()
for _, in := range users {
if in.Username == "" {
continue
}
// 查找本地同名用户(读锁短持有;NextID/CreateUser 不可在持锁时调用)
db.RLock()
idx := -1
for i := range db.Users {
if db.Users[i].Username == in.Username {
idx = i
break
}
}
db.RUnlock()
if idx >= 0 {
db.Lock()
if db.Users[idx].AuthSource == "ldap" {
db.Unlock()
result.SkippedCount++
continue
}
db.Users[idx].AuthSource = "ldap" // 本地同名 → 转 LDAP 并同步昵称
if in.Nickname != "" {
db.Users[idx].Nickname = in.Nickname
}
db.Users[idx].UpdatedAt = time.Now()
db.RequestSave()
db.Unlock()
result.UpdatedCount++
continue
}
hash, err := crypto.HashPassword("ldap-user-" + in.Username + "-no-local-login")
if err != nil {
result.FailedCount++
result.FailedUsers = append(result.FailedUsers, map[string]interface{}{
"username": in.Username, "error": err.Error(),
})
continue
}
u := &models.User{
Username: in.Username,
Password: hash,
Nickname: in.Nickname,
Role: "user",
AuthSource: "ldap",
}
if err := db.CreateUser(u); err != nil {
result.FailedCount++
result.FailedUsers = append(result.FailedUsers, map[string]interface{}{
"username": in.Username, "error": err.Error(),
})
continue
}
result.SuccessCount++
}
return result
}
// SearchLDAPUsersForImport 预览入口(服务层薄封装,便于 controller 调用)
func SearchLDAPUsersForImport(cfg *LDAPConfig, ouDN string) ([]map[string]interface{}, error) {
users, err := searchLDAPUsers(cfg, ouDN, 500)
if err != nil {
return nil, err
}
out := make([]map[string]interface{}, 0, len(users))
for _, u := range users {
out = append(out, map[string]interface{}{
"dn": u.DN, "username": u.Username,
"displayName": u.DisplayName, "email": u.Email,
})
}
return out, nil
}