package handlers import ( "encoding/json" "fmt" "net" "net/http" "strings" "time" "ezops/internal/models" "github.com/gin-gonic/gin" "github.com/go-ldap/ldap/v3" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // LDAPHandler LDAP处理器 type LDAPHandler struct { db *gorm.DB } // NewLDAPHandler 创建LDAP处理器 func NewLDAPHandler(db *gorm.DB) *LDAPHandler { return &LDAPHandler{db: db} } // LDAPAuthInfo LDAP认证返回的用户信息 type LDAPAuthInfo struct { Username string DisplayName string Email string } // dialLDAP 连接LDAP服务器 func dialLDAP(cfg *models.LDAPConfig) (*ldap.Conn, error) { if cfg.LDAPHost == "" { return nil, fmt.Errorf("LDAP not configured") } protocol := "ldap" if cfg.UseLDAPS != nil && *cfg.UseLDAPS { protocol = "ldaps" } port := 389 if cfg.LDAPPort != nil && *cfg.LDAPPort > 0 { port = *cfg.LDAPPort } uri := fmt.Sprintf("%s://%s:%d", protocol, cfg.LDAPHost, port) dialer := &net.Dialer{Timeout: 5 * time.Second} return ldap.DialURL(uri, ldap.DialWithDialer(dialer)) } // tryLDAPAuth 尝试LDAP认证,返回用户信息 func tryLDAPAuth(db *gorm.DB, username, password string) (*LDAPAuthInfo, error) { var cfg models.LDAPConfig if err := db.Order("id").First(&cfg).Error; err != nil { return nil, fmt.Errorf("LDAP未配置") } if cfg.LDAPHost == "" { return nil, fmt.Errorf("LDAP not configured") } conn, err := dialLDAP(&cfg) if err != nil { return nil, fmt.Errorf("LDAP连接失败: %w", err) } defer conn.Close() // Admin bind if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil { return nil, fmt.Errorf("LDAP管理员绑定失败: %w", err) } // Build search filter userFilter := "" baseFilter := "(objectClass=inetOrgPerson)" if cfg.SearchFilter != nil && *cfg.SearchFilter != "" { sf := *cfg.SearchFilter if strings.Contains(sf, "%s") { userFilter = strings.ReplaceAll(sf, "%s", ldap.EscapeFilter(username)) } else { baseFilter = sf userFilter = fmt.Sprintf("(&%s(uid=%s))", baseFilter, ldap.EscapeFilter(username)) } } else { userFilter = fmt.Sprintf("(&%s(uid=%s))", baseFilter, ldap.EscapeFilter(username)) } // Search user searchReq := ldap.NewSearchRequest( cfg.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 10, false, userFilter, []string{"uid", "cn", "mail", "telephoneNumber"}, nil, ) sr, err := conn.Search(searchReq) if err != nil || len(sr.Entries) == 0 { return nil, fmt.Errorf("LDAP用户不存在") } entry := sr.Entries[0] userDN := entry.DN // Verify password by binding as user if err := conn.Bind(userDN, password); err != nil { return nil, fmt.Errorf("LDAP密码验证失败: %w", err) } // Extract user info using mapping userMapping := map[string]string{ "username": "uid", "email": "mail", "display_name": "cn", } if cfg.UserMapping != nil && *cfg.UserMapping != "" { json.Unmarshal([]byte(*cfg.UserMapping), &userMapping) } getAttr := func(key string) string { if v := entry.GetAttributeValue(key); v != "" { return v } return "" } info := &LDAPAuthInfo{ Username: getAttr(userMapping["username"]), DisplayName: getAttr(userMapping["display_name"]), Email: getAttr(userMapping["email"]), } if info.Username == "" { info.Username = username } return info, nil } // GetLDAPConfig 获取LDAP配置 // GET /api/v1/admin/ldap-config func (h *LDAPHandler) GetLDAPConfig(c *gin.Context) { var configs []models.LDAPConfig h.db.Order("id").Find(&configs) if len(configs) == 0 { c.JSON(http.StatusOK, gin.H{}) return } cfg := configs[0] // mask password cfg.AdminPassword = "******" c.JSON(http.StatusOK, models.Response{Code: 200, Data: cfg}) } // UpdateLDAPConfig 更新LDAP配置 // PUT /api/v1/admin/ldap-config func (h *LDAPHandler) UpdateLDAPConfig(c *gin.Context) { var input map[string]interface{} if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) return } var configs []models.LDAPConfig h.db.Order("id").Find(&configs) getStr := func(key string) string { if v, ok := input[key].(string); ok { return v } return "" } getInt := func(key string) int { if v, ok := input[key].(float64); ok { return int(v) } return 0 } getBool := func(key string) *bool { if v, ok := input[key].(bool); ok { return &v } return nil } userMappingStr := "" if um, ok := input["user_mapping"]; ok { if bytes, err := json.Marshal(um); err == nil { userMappingStr = string(bytes) } } if len(configs) == 0 { // Create cfg := models.LDAPConfig{ LDAPHost: getStr("ldap_host"), AdminDN: getStr("admin_dn"), BaseDN: getStr("base_dn"), AdminPassword: getStr("admin_password"), } if sf := getStr("search_filter"); sf != "" { cfg.SearchFilter = &sf } if userMappingStr != "" { cfg.UserMapping = &userMappingStr } if port := getInt("ldap_port"); port > 0 { cfg.LDAPPort = &port } if b := getBool("use_ldaps"); b != nil { cfg.UseLDAPS = b } h.db.Create(&cfg) cfg.AdminPassword = "******" c.JSON(http.StatusOK, models.Response{Code: 200, Message: "LDAP配置已创建", Data: cfg}) } else { // Update cfg := configs[0] updates := map[string]interface{}{} if v := getStr("ldap_host"); v != "" { updates["ldap_host"] = v } if v := getStr("base_dn"); v != "" { updates["base_dn"] = v } if v := getStr("admin_dn"); v != "" { updates["admin_dn"] = v } if v := getStr("search_filter"); v != "" { updates["search_filter"] = v } if userMappingStr != "" { updates["user_mapping"] = userMappingStr } if v := getStr("admin_password"); v != "" && v != "******" { updates["admin_password"] = v } if port := getInt("ldap_port"); port > 0 { updates["ldap_port"] = port } if b := getBool("use_ldaps"); b != nil { updates["use_ldaps"] = *b } if len(updates) > 0 { h.db.Model(&cfg).Updates(updates) } h.db.First(&cfg, cfg.ID) cfg.AdminPassword = "******" c.JSON(http.StatusOK, models.Response{Code: 200, Message: "LDAP配置已更新", Data: cfg}) } } // TestLDAPConnection 测试LDAP连接 // POST /api/v1/admin/ldap-config/test func (h *LDAPHandler) TestLDAPConnection(c *gin.Context) { var req struct { TestUser string `json:"test_user"` TestPassword string `json:"test_password"` } c.ShouldBindJSON(&req) var cfg models.LDAPConfig if err := h.db.Order("id").First(&cfg).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "message": "LDAP配置不存在,请先保存配置"}) return } if cfg.LDAPHost == "" { c.JSON(http.StatusOK, gin.H{"success": false, "message": "LDAP未配置"}) return } conn, err := dialLDAP(&cfg) if err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "message": fmt.Sprintf("连接失败: %s", err)}) return } defer conn.Close() if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "message": fmt.Sprintf("管理员绑定失败: %s", err)}) return } // If test_user provided, search and verify if req.TestUser != "" && req.TestPassword != "" { userFilter := "" baseFilter := "(objectClass=inetOrgPerson)" if cfg.SearchFilter != nil && *cfg.SearchFilter != "" { sf := *cfg.SearchFilter if strings.Contains(sf, "%s") { userFilter = strings.ReplaceAll(sf, "%s", ldap.EscapeFilter(req.TestUser)) } else { baseFilter = sf userFilter = fmt.Sprintf("(&%s(uid=%s))", baseFilter, ldap.EscapeFilter(req.TestUser)) } } else { userFilter = fmt.Sprintf("(&%s(uid=%s))", baseFilter, ldap.EscapeFilter(req.TestUser)) } sr, err := conn.Search(ldap.NewSearchRequest( cfg.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 10, false, userFilter, []string{"uid", "cn", "mail"}, nil, )) if err != nil || len(sr.Entries) == 0 { c.JSON(http.StatusOK, gin.H{"success": false, "message": fmt.Sprintf("用户 %s 不存在", req.TestUser)}) return } userDN := sr.Entries[0].DN if err := conn.Bind(userDN, req.TestPassword); err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "message": fmt.Sprintf("密码验证失败: %s", err)}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Data: gin.H{"success": true, "message": fmt.Sprintf("认证成功!用户DN: %s", userDN)}}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Data: gin.H{"success": true, "message": "LDAP连接成功,管理员认证通过"}}) } // ListLDAPUsers 列出LDAP用户 // POST /api/v1/admin/ldap-config/users func (h *LDAPHandler) ListLDAPUsers(c *gin.Context) { var req struct { OuDn string `json:"ou_dn"` } c.ShouldBindJSON(&req) var cfg models.LDAPConfig if err := h.db.Order("id").First(&cfg).Error; err != nil { c.JSON(http.StatusOK, []interface{}{}) return } conn, err := dialLDAP(&cfg) if err != nil { c.JSON(http.StatusOK, []interface{}{}) return } defer conn.Close() if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil { c.JSON(http.StatusOK, []interface{}{}) return } searchBase := cfg.BaseDN if req.OuDn != "" { searchBase = req.OuDn } baseFilter := "(objectClass=inetOrgPerson)" if cfg.SearchFilter != nil && *cfg.SearchFilter != "" { sf := *cfg.SearchFilter if strings.Contains(sf, "%s") { baseFilter = strings.ReplaceAll(sf, "%s", "*") } else { baseFilter = sf } } sr, err := conn.Search(ldap.NewSearchRequest( searchBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 200, 0, false, baseFilter, []string{"uid", "cn", "mail", "dn", "telephoneNumber", "title", "department"}, nil, )) if err != nil { c.JSON(http.StatusOK, []interface{}{}) return } type LdapUser struct { DN string `json:"dn"` UID string `json:"uid"` CN string `json:"cn"` Mail string `json:"mail"` Phone string `json:"phone"` Title string `json:"title"` Department string `json:"department"` } var users []LdapUser for _, e := range sr.Entries { users = append(users, LdapUser{ DN: e.DN, UID: e.GetAttributeValue("uid"), CN: e.GetAttributeValue("cn"), Mail: e.GetAttributeValue("mail"), Phone: e.GetAttributeValue("telephoneNumber"), Title: e.GetAttributeValue("title"), Department: e.GetAttributeValue("department"), }) } c.JSON(http.StatusOK, models.Response{Code: 200, Data: users}) } // BatchImportLDAP 批量导入LDAP用户 // POST /api/v1/admin/ldap-config/import func (h *LDAPHandler) BatchImportLDAP(c *gin.Context) { var req struct { Users []struct { UID string `json:"uid"` CN string `json:"cn"` Mail string `json:"mail"` Username string `json:"username"` Nickname string `json:"nickname"` Email string `json:"email"` } `json:"users"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) return } imported := 0 skipped := 0 for _, lu := range req.Users { uid := lu.UID if uid == "" { uid = lu.Username } cn := lu.CN if cn == "" { cn = lu.Nickname } mail := lu.Mail if mail == "" { mail = lu.Email } if uid == "" { skipped++ continue } // Check if already exists var existing models.User if err := h.db.Where("username = ?", uid).First(&existing).Error; err == nil { // Already exists, update info updates := map[string]interface{}{ "auth_source": "ldap", } if cn != "" { updates["real_name"] = cn } if mail != "" { updates["email"] = mail } h.db.Model(&existing).Updates(updates) imported++ continue } // Create new LDAP user user := models.User{ Username: uid, Password: "-", // LDAP users don't have local password RealName: cn, Email: mail, Status: 1, AuthSource: "ldap", } if err := h.db.Create(&user).Error; err != nil { skipped++ continue } imported++ } // bcrypt import to avoid unused import error _ = bcrypt.MinCost c.JSON(http.StatusOK, models.Response{ Code: 200, Message: fmt.Sprintf("导入完成: %d 个用户成功, %d 个跳过", imported, skipped), Data: gin.H{"imported": imported, "skipped": skipped}, }) } // ListLDAPOUs 列出 LDAP 组织单元 // POST /api/v1/admin/ldap-config/ou-list func (h *LDAPHandler) ListLDAPOUs(c *gin.Context) { var configs []models.LDAPConfig if err := h.db.Order("id").Find(&configs).Error; err != nil || len(configs) == 0 { c.JSON(http.StatusOK, models.Response{Code: 200, Data: []interface{}{}}) return } cfg := configs[0] port := 389 if cfg.LDAPPort != nil { port = *cfg.LDAPPort } conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%d", cfg.LDAPHost, port)) if err != nil { c.JSON(http.StatusOK, models.Response{Code: 200, Data: []interface{}{}}) return } defer conn.Close() conn.SetTimeout(5 * time.Second) if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil { c.JSON(http.StatusOK, models.Response{Code: 200, Data: []interface{}{}}) return } searchReq := ldap.NewSearchRequest( cfg.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=organizationalUnit)", []string{"ou", "description"}, nil, ) sr, err := conn.Search(searchReq) if err != nil { c.JSON(http.StatusOK, models.Response{Code: 200, Data: []interface{}{}}) return } type OU struct { DN string `json:"dn"` Name string `json:"name"` Description string `json:"description"` } var ous []OU for _, e := range sr.Entries { ous = append(ous, OU{ DN: e.DN, Name: e.GetAttributeValue("ou"), Description: e.GetAttributeValue("description"), }) } c.JSON(http.StatusOK, models.Response{Code: 200, Data: ous}) } // CreateLDAPOU 创建组织单元 // POST /api/v1/admin/ldap-config/create-ou func (h *LDAPHandler) CreateLDAPOU(c *gin.Context) { var req struct { OUName string `json:"ou_name"` ParentDN string `json:"parent_dn"` Description string `json:"description"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) return } var configs []models.LDAPConfig if err := h.db.Order("id").Find(&configs).Error; err != nil || len(configs) == 0 { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "LDAP未配置"}) return } cfg := configs[0] port := 389 if cfg.LDAPPort != nil { port = *cfg.LDAPPort } if req.ParentDN == "" { req.ParentDN = cfg.BaseDN } ouDN := fmt.Sprintf("ou=%s,%s", ldap.EscapeFilter(req.OUName), req.ParentDN) conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%d", cfg.LDAPHost, port)) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "LDAP连接失败: " + err.Error()}) return } defer conn.Close() conn.SetTimeout(5 * time.Second) if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "管理员认证失败: " + err.Error()}) return } addReq := ldap.NewAddRequest(ouDN, nil) addReq.Attribute("objectClass", []string{"organizationalUnit", "top"}) addReq.Attribute("ou", []string{req.OUName}) if req.Description != "" { addReq.Attribute("description", []string{req.Description}) } if err := conn.Add(addReq); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建OU失败: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "OU创建成功", Data: gin.H{"dn": ouDN}}) } // CreateLDAPUser 在LDAP中创建用户 // POST /api/v1/admin/ldap-config/create-user func (h *LDAPHandler) CreateLDAPUser(c *gin.Context) { var req struct { Username string `json:"username"` Password string `json:"password"` DisplayName string `json:"display_name"` Email string `json:"email"` OUDN string `json:"ou_dn"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: err.Error()}) return } if req.Username == "" || req.Password == "" { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "用户名和密码不能为空"}) return } var configs []models.LDAPConfig if err := h.db.Order("id").Find(&configs).Error; err != nil || len(configs) == 0 { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "LDAP未配置"}) return } cfg := configs[0] port := 389 if cfg.LDAPPort != nil { port = *cfg.LDAPPort } if req.OUDN == "" { req.OUDN = cfg.BaseDN } userDN := fmt.Sprintf("uid=%s,%s", ldap.EscapeFilter(req.Username), req.OUDN) // Parse user mapping mapping := map[string]string{"username": "uid", "email": "mail", "display_name": "cn"} if cfg.UserMapping != nil { _ = json.Unmarshal([]byte(*cfg.UserMapping), &mapping) } uidAttr := mapping["username"] if uidAttr == "" { uidAttr = "uid" } mailAttr := mapping["email"] if mailAttr == "" { mailAttr = "mail" } conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%d", cfg.LDAPHost, port)) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "LDAP连接失败: " + err.Error()}) return } defer conn.Close() conn.SetTimeout(5 * time.Second) if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "管理员认证失败: " + err.Error()}) return } addReq := ldap.NewAddRequest(userDN, nil) addReq.Attribute("objectClass", []string{"inetOrgPerson", "organizationalPerson", "person", "top"}) addReq.Attribute(uidAttr, []string{req.Username}) addReq.Attribute("cn", []string{req.DisplayName}) addReq.Attribute("sn", []string{req.DisplayName}) if req.Email != "" { addReq.Attribute(mailAttr, []string{req.Email}) } addReq.Attribute("userPassword", []string{req.Password}) if err := conn.Add(addReq); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建用户失败: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "用户创建成功", Data: gin.H{"dn": userDN}}) } // DeleteLDAPUser 删除LDAP条目(用户或OU) // POST /api/v1/admin/ldap-config/delete-user func (h *LDAPHandler) DeleteLDAPUser(c *gin.Context) { var req struct { DN string `json:"dn"` } if err := c.ShouldBindJSON(&req); err != nil || req.DN == "" { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "DN不能为空"}) return } var configs []models.LDAPConfig if err := h.db.Order("id").Find(&configs).Error; err != nil || len(configs) == 0 { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "LDAP未配置"}) return } cfg := configs[0] port := 389 if cfg.LDAPPort != nil { port = *cfg.LDAPPort } conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%d", cfg.LDAPHost, port)) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "LDAP连接失败: " + err.Error()}) return } defer conn.Close() conn.SetTimeout(5 * time.Second) if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "管理员认证失败: " + err.Error()}) return } delReq := ldap.NewDelRequest(req.DN, nil) if err := conn.Del(delReq); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "删除失败: " + err.Error()}) return } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "删除成功"}) } // UpdateLDAPUser 修改 LDAP 用户属性 // POST /api/v1/admin/ldap-config/update-user func (h *LDAPHandler) UpdateLDAPUser(c *gin.Context) { var req struct { DN string `json:"dn"` CN string `json:"cn"` Mail string `json:"mail"` Phone string `json:"phone"` Title string `json:"title"` } if err := c.ShouldBindJSON(&req); err != nil || req.DN == "" { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "DN不能为空"}) return } var configs []models.LDAPConfig if err := h.db.Order("id").Find(&configs).Error; err != nil || len(configs) == 0 { c.JSON(http.StatusBadRequest, models.Response{Code: 400, Message: "LDAP未配置"}) return } cfg := configs[0] port := 389 if cfg.LDAPPort != nil { port = *cfg.LDAPPort } conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:%d", cfg.LDAPHost, port)) if err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "LDAP连接失败: " + err.Error()}) return } defer conn.Close() conn.SetTimeout(5 * time.Second) if err := conn.Bind(cfg.AdminDN, cfg.AdminPassword); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "管理员认证失败: " + err.Error()}) return } // 先查当前值,做 diff sr, err := conn.Search(ldap.NewSearchRequest( req.DN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 1, 0, false, "(objectClass=*)", []string{"cn", "mail", "telephoneNumber", "title"}, nil, )) if err != nil || len(sr.Entries) == 0 { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "用户不存在"}) return } entry := sr.Entries[0] modReq := ldap.NewModifyRequest(req.DN, nil) // cn oldCN := entry.GetAttributeValue("cn") if req.CN != "" && req.CN != oldCN { modReq.Replace("cn", []string{req.CN}) modReq.Replace("sn", []string{req.CN}) // sn 同步更新 } // mail oldMail := entry.GetAttributeValue("mail") if req.Mail != oldMail { if req.Mail == "" { oldVals := entry.GetAttributeValues("mail") if len(oldVals) > 0 { modReq.Delete("mail", oldVals) } } else { modReq.Replace("mail", []string{req.Mail}) } } // telephoneNumber oldPhone := entry.GetAttributeValue("telephoneNumber") if req.Phone != oldPhone { if req.Phone == "" { oldVals := entry.GetAttributeValues("telephoneNumber") if len(oldVals) > 0 { modReq.Delete("telephoneNumber", oldVals) } } else { modReq.Replace("telephoneNumber", []string{req.Phone}) } } // title oldTitle := entry.GetAttributeValue("title") if req.Title != oldTitle { if req.Title == "" { oldVals := entry.GetAttributeValues("title") if len(oldVals) > 0 { modReq.Delete("title", oldVals) } } else { modReq.Replace("title", []string{req.Title}) } } if len(modReq.Changes) > 0 { if err := conn.Modify(modReq); err != nil { c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "修改失败: " + err.Error()}) return } } c.JSON(http.StatusOK, models.Response{Code: 200, Message: "修改成功"}) }