feat: 实现用户批量导入(LDAP+本地+CSV模板)
- BatchImportLdapUsers: 从LDAP导入用户到本地DB 解析 dn/cn/uid/mail 字段,默认密码 admin123,角色 developer 重复用户自动跳过 - BatchImportUsers: 本地CSV批量导入 支持 username/display_name/email/role/password skip_existing 选项控制重复行为 - ImportTemplate: 真实CSV模板下载(UTF-8 BOM) - 3个endpoint从placeholder改为真实实现
This commit is contained in:
1 parent
add9416c0e
commit
ed43f6f398
1 file changed
+160
-3
@@ -181,17 +181,174 @@ func ResetPassword(c *gin.Context) {
|
|||||||
|
|
||||||
// POST /api/v1/users/batch-import
|
// POST /api/v1/users/batch-import
|
||||||
func BatchImportUsers(c *gin.Context) {
|
func BatchImportUsers(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "Batch import (placeholder)", "count": 0})
|
var input struct {
|
||||||
|
Users []struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
} `json:"users"`
|
||||||
|
ImportType string `json:"import_type"`
|
||||||
|
SkipExisting bool `json:"skip_existing"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
successCount := 0
|
||||||
|
var errors []string
|
||||||
|
|
||||||
|
for _, u := range input.Users {
|
||||||
|
if u.Username == "" {
|
||||||
|
errors = append(errors, "skip: empty username")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already exists
|
||||||
|
var existing int64
|
||||||
|
model.DB.Model(&model.User{}).Where("username = ?", u.Username).Count(&existing)
|
||||||
|
if existing > 0 {
|
||||||
|
if input.SkipExisting {
|
||||||
|
errors = append(errors, "skip: "+u.Username+" already exists")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
errors = append(errors, "fail: "+u.Username+" already exists")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
password := u.Password
|
||||||
|
if password == "" {
|
||||||
|
password = "admin123"
|
||||||
|
}
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, "fail: "+u.Username+" hash error")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
role := u.Role
|
||||||
|
if role == "" {
|
||||||
|
role = "developer"
|
||||||
|
}
|
||||||
|
displayName := u.DisplayName
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = u.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
emailPtr := &u.Email
|
||||||
|
if u.Email == "" {
|
||||||
|
emailPtr = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user := model.User{
|
||||||
|
Username: u.Username,
|
||||||
|
DisplayName: displayName,
|
||||||
|
Email: emailPtr,
|
||||||
|
HashedPassword: string(hash),
|
||||||
|
Role: role,
|
||||||
|
}
|
||||||
|
if err := model.DB.Create(&user).Error; err != nil {
|
||||||
|
errors = append(errors, "fail: "+u.Username+" - "+err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
successCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success_count": successCount,
|
||||||
|
"fail_count": len(input.Users) - successCount,
|
||||||
|
"errors": errors,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/v1/users/batch-import-ldap
|
// POST /api/v1/users/batch-import-ldap
|
||||||
func BatchImportLdapUsers(c *gin.Context) {
|
func BatchImportLdapUsers(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "LDAP import (placeholder)", "count": 0})
|
var input struct {
|
||||||
|
Users []struct {
|
||||||
|
DN string `json:"dn"`
|
||||||
|
CN string `json:"cn"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Mail string `json:"mail"`
|
||||||
|
} `json:"users"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
successCount := 0
|
||||||
|
var errors []string
|
||||||
|
defaultPassword := "admin123"
|
||||||
|
hash, _ := bcrypt.GenerateFromPassword([]byte(defaultPassword), bcrypt.DefaultCost)
|
||||||
|
|
||||||
|
for _, u := range input.Users {
|
||||||
|
// Derive username: uid > username > cn
|
||||||
|
username := u.UID
|
||||||
|
if username == "" {
|
||||||
|
username = u.Username
|
||||||
|
}
|
||||||
|
if username == "" {
|
||||||
|
username = u.CN
|
||||||
|
}
|
||||||
|
if username == "" {
|
||||||
|
errors = append(errors, "skip: empty username for DN="+u.DN)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already exists
|
||||||
|
var existing int64
|
||||||
|
model.DB.Model(&model.User{}).Where("username = ?", username).Count(&existing)
|
||||||
|
if existing > 0 {
|
||||||
|
errors = append(errors, "skip: "+username+" already exists")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive display name: cn > nickname > username
|
||||||
|
displayName := u.CN
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = u.Nickname
|
||||||
|
}
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = username
|
||||||
|
}
|
||||||
|
|
||||||
|
email := u.Mail
|
||||||
|
emailPtr := &email
|
||||||
|
if email == "" {
|
||||||
|
emailPtr = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user := model.User{
|
||||||
|
Username: username,
|
||||||
|
DisplayName: displayName,
|
||||||
|
Email: emailPtr,
|
||||||
|
HashedPassword: string(hash),
|
||||||
|
Role: "developer",
|
||||||
|
}
|
||||||
|
if err := model.DB.Create(&user).Error; err != nil {
|
||||||
|
errors = append(errors, "fail: "+username+" - "+err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
successCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success_count": successCount,
|
||||||
|
"fail_count": len(input.Users) - successCount,
|
||||||
|
"errors": errors,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/v1/users/import-template
|
// GET /api/v1/users/import-template
|
||||||
func ImportTemplate(c *gin.Context) {
|
func ImportTemplate(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "Template download (placeholder)"})
|
csv := "username,display_name,email,role,password\nzhangsan,张三,zhangsan@test.com,developer,\nlisi,李四,lisi@test.com,dba,pass123\n"
|
||||||
|
c.Header("Content-Type", "text/csv; charset=utf-8")
|
||||||
|
c.Header("Content-Disposition", "attachment; filename=users_import_template.csv")
|
||||||
|
c.Writer.Write([]byte("\xEF\xBB\xBF" + csv))
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/v1/users/:userId/reset-password
|
// POST /api/v1/users/:userId/reset-password
|
||||||
|
|||||||
Reference in new issue
Block a user