package controllers import ( "encoding/json" "fmt" "strconv" "strings" "time" "server/models" "server/pkg/jwtutil" "github.com/beego/beego/v2/client/orm" beego "github.com/beego/beego/v2/server/web" ) // BackendErpContactController 通讯录管理接口 type BackendErpContactController struct { beego.Controller } // contactClaims 从请求头解析登录态 JWT,返回后端用户 Claims func (c *BackendErpContactController) contactClaims() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, fmt.Errorf("未登录") } parts := strings.SplitN(auth, " ", 2) if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { return nil, fmt.Errorf("认证信息格式错误") } claims, err := jwtutil.ParseToken(parts[1]) if err != nil { return nil, fmt.Errorf("无效的token") } if claims.UserType != "backend" { return nil, fmt.Errorf("无权访问") } return claims, nil } // contactTenantID 获取当前请求的租户ID:优先使用登录态 JWT 中的租户, // 仅当显式传入 tid 参数时才以参数为准(兼容管理端等显式指定场景)。 func (c *BackendErpContactController) contactTenantID() (uint64, error) { claims, err := c.contactClaims() if err != nil { return 0, err } tid := uint64(claims.TenantId) if v, e := c.GetInt64("tid"); e == nil && v > 0 { tid = uint64(v) } return tid, nil } type erpContactDTO struct { ID uint64 `json:"id"` Tid uint64 `json:"tid"` EmployeeID uint64 `json:"employee_id"` OrgID uint64 `json:"org_id"` OrgName string `json:"org_name"` ContactType int8 `json:"contact_type"` ContactName string `json:"contact_name"` Gender int8 `json:"gender"` Phone string `json:"phone"` WorkPhone string `json:"work_phone"` Email string `json:"email"` Wechat string `json:"wechat"` QQ string `json:"qq"` Avatar string `json:"avatar"` CompanyName string `json:"company_name"` DeptName string `json:"dept_name"` PositionID uint64 `json:"position_id"` PositionTitle string `json:"position_title"` Address string `json:"address"` Remark string `json:"remark"` IsStarred int8 `json:"is_starred"` Sort uint `json:"sort"` Status int8 `json:"status"` CreateTime string `json:"create_time"` } // List 获取通讯录列表(支持分页、搜索、按组织筛选) // GET /backend/erp/contact/list func (c *BackendErpContactController) List() { tid, err := c.contactTenantID() if err != nil { c.contactJsonError(401, "未登录或无权访问") return } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("page_size", 20) keyword := strings.TrimSpace(c.GetString("keyword")) orgID, _ := c.GetUint64("org_id") contactType, _ := c.GetInt("contact_type") if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } qs := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("delete_time__isnull", true). Exclude("status", 0). Filter("tid", tid) if orgID > 0 { qs = qs.Filter("org_id", orgID) } if contactType > 0 { qs = qs.Filter("contact_type", contactType) } if keyword != "" { cond := orm.NewCondition() cond = cond.Or("contact_name__contains", keyword) cond = cond.Or("phone__contains", keyword) cond = cond.Or("email__contains", keyword) cond = cond.Or("wechat__contains", keyword) cond = cond.Or("qq__contains", keyword) qs = qs.SetCond(cond) } total, err := qs.Count() if err != nil { c.contactJsonError(500, "查询通讯录失败: "+err.Error()) return } var rows []models.BackendErpContact _, err = qs.OrderBy("-is_starred", "contact_name", "-id"). Limit(pageSize, (page-1)*pageSize). All(&rows) if err != nil { c.contactJsonError(500, "查询通讯录失败: "+err.Error()) return } list := make([]erpContactDTO, 0, len(rows)) for _, row := range rows { list = append(list, c.contactDTO(row)) } c.contactJsonOK(map[string]interface{}{ "list": list, "total": total, "page": page, "page_size": pageSize, }) } // Detail 获取通讯录详情 // GET /backend/erp/contact/detail/:id func (c *BackendErpContactController) Detail() { id, ok := c.contactPathUint64(":id") if !ok { c.contactJsonError(400, "无效ID") return } var row models.BackendErpContact err := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("id", id). Filter("delete_time__isnull", true). One(&row) if err != nil { c.contactJsonError(404, "联系人不存在") return } c.contactJsonOK(c.contactDTO(row)) } // Create 创建联系人(手动添加外部联系人) // POST /backend/erp/contact/create func (c *BackendErpContactController) Create() { body := c.contactParseJSONBody() contactName, _ := c.contactGetStringValue(body, "contact_name", "name") contactName = strings.TrimSpace(contactName) if contactName == "" { c.contactJsonError(400, "联系人姓名不能为空") return } tid, _ := c.contactGetUint64Value(body, "tid", "tenant_id") orgID, _ := c.contactGetUint64Value(body, "org_id") contactType, _ := c.contactGetIntValue(body, "contact_type") gender, _ := c.contactGetIntValue(body, "gender") phone, _ := c.contactGetStringValue(body, "phone") workPhone, _ := c.contactGetStringValue(body, "work_phone") email, _ := c.contactGetStringValue(body, "email") wechat, _ := c.contactGetStringValue(body, "wechat") qq, _ := c.contactGetStringValue(body, "qq") avatar, _ := c.contactGetStringValue(body, "avatar") companyName, _ := c.contactGetStringValue(body, "company_name") deptName, _ := c.contactGetStringValue(body, "dept_name") positionID, _ := c.contactGetUint64Value(body, "position_id") positionTitle, _ := c.contactGetStringValue(body, "position_title") address, _ := c.contactGetStringValue(body, "address") remark, _ := c.contactGetStringValue(body, "remark") sortVal, _ := c.contactGetUintValue(body, "sort") if contactType == 0 { contactType = 2 // 默认外部联系人 } row := models.BackendErpContact{ Tid: tid, ContactType: int8(contactType), ContactName: contactName, Gender: int8(gender), Phone: contactStrPtr(phone), WorkPhone: contactStrPtr(workPhone), Email: contactStrPtr(email), Wechat: contactStrPtr(wechat), QQ: contactStrPtr(qq), Avatar: contactStrPtr(avatar), CompanyName: contactStrPtr(companyName), DeptName: contactStrPtr(deptName), PositionTitle: contactStrPtr(positionTitle), Address: contactStrPtr(address), Remark: contactStrPtr(remark), Status: 1, Sort: sortVal, } if orgID > 0 { row.OrgID = &orgID } if positionID > 0 { row.PositionID = &positionID } id, err := models.Orm.Insert(&row) if err != nil { c.contactJsonError(500, "创建联系人失败: "+err.Error()) return } c.contactJsonOK(map[string]interface{}{"id": id}) } // Update 更新联系人 // POST /backend/erp/contact/update/:id func (c *BackendErpContactController) Update() { id, ok := c.contactPathUint64(":id") if !ok { c.contactJsonError(400, "无效ID") return } body := c.contactParseJSONBody() update := orm.Params{} if v, has := c.contactGetStringValue(body, "contact_name", "name"); has { v = strings.TrimSpace(v) if v == "" { c.contactJsonError(400, "联系人姓名不能为空") return } update["contact_name"] = v } if v, has := c.contactGetUint64Value(body, "org_id"); has { if v == 0 { update["org_id"] = nil } else { update["org_id"] = v } } if v, has := c.contactGetIntValue(body, "contact_type"); has { update["contact_type"] = int8(v) } if v, has := c.contactGetIntValue(body, "gender"); has { update["gender"] = int8(v) } if v, has := c.contactGetStringValue(body, "phone"); has { update["phone"] = contactNullableString(v) } if v, has := c.contactGetStringValue(body, "work_phone"); has { update["work_phone"] = contactNullableString(v) } if v, has := c.contactGetStringValue(body, "email"); has { update["email"] = contactNullableString(v) } if v, has := c.contactGetStringValue(body, "wechat"); has { update["wechat"] = contactNullableString(v) } if v, has := c.contactGetStringValue(body, "qq"); has { update["qq"] = contactNullableString(v) } if v, has := c.contactGetStringValue(body, "avatar"); has { update["avatar"] = contactNullableString(v) } if v, has := c.contactGetStringValue(body, "company_name"); has { update["company_name"] = contactNullableString(v) } if v, has := c.contactGetStringValue(body, "dept_name"); has { update["dept_name"] = contactNullableString(v) } if v, has := c.contactGetUint64Value(body, "position_id"); has { if v == 0 { update["position_id"] = nil } else { update["position_id"] = v } } if v, has := c.contactGetStringValue(body, "position_title"); has { update["position_title"] = contactNullableString(v) } if v, has := c.contactGetStringValue(body, "address"); has { update["address"] = contactNullableString(v) } if v, has := c.contactGetStringValue(body, "remark"); has { update["remark"] = contactNullableString(v) } if v, has := c.contactGetIntValue(body, "is_starred"); has { update["is_starred"] = int8(v) } if v, has := c.contactGetUintValue(body, "sort"); has { update["sort"] = v } if v, has := c.contactGetIntValue(body, "status"); has { update["status"] = int8(v) } if len(update) == 0 { c.contactJsonError(400, "无更新字段") return } num, err := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("id", id). Filter("delete_time__isnull", true). Update(update) if err != nil { c.contactJsonError(500, "更新联系人失败: "+err.Error()) return } if num == 0 { c.contactJsonError(404, "联系人不存在") return } c.contactJsonOK(nil) } // Delete 删除联系人(软删除) // DELETE /backend/erp/contact/delete/:id func (c *BackendErpContactController) Delete() { id, ok := c.contactPathUint64(":id") if !ok { c.contactJsonError(400, "无效ID") return } now := time.Now().Format("2006-01-02 15:04:05") num, err := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("id", id). Filter("delete_time__isnull", true). Update(orm.Params{"delete_time": now, "status": 0}) if err != nil { c.contactJsonError(500, "删除联系人失败: "+err.Error()) return } if num == 0 { c.contactJsonError(404, "联系人不存在") return } c.contactJsonOK(nil) } // Star 收藏/取消收藏联系人 // POST /backend/erp/contact/star/:id func (c *BackendErpContactController) Star() { id, ok := c.contactPathUint64(":id") if !ok { c.contactJsonError(400, "无效ID") return } body := c.contactParseJSONBody() starred, _ := c.contactGetIntValue(body, "is_starred", "starred") num, err := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("id", id). Filter("delete_time__isnull", true). Update(orm.Params{"is_starred": int8(starred)}) if err != nil { c.contactJsonError(500, "操作失败: "+err.Error()) return } if num == 0 { c.contactJsonError(404, "联系人不存在") return } c.contactJsonOK(nil) } // SyncAllContacts 全量同步:将所有员工同步到通讯录 // POST /backend/erp/contact/syncAll func (c *BackendErpContactController) SyncAllContacts() { tid, _ := c.GetInt64("tid") qs := models.Orm.QueryTable(new(models.BackendEmployee)). Filter("delete_time__isnull", true). Exclude("account_status", 2) if tid > 0 { qs = qs.Filter("tid", tid) } var employees []models.BackendEmployee _, err := qs.All(&employees) if err != nil { c.contactJsonError(500, "查询员工失败: "+err.Error()) return } created := 0 updated := 0 for _, emp := range employees { tidVal := uint64(0) if emp.Tid != nil { tidVal = uint64(*emp.Tid) } empID := uint64(emp.ID) result := syncContactFromEmployee(tidVal, empID, &emp) if result == "created" { created++ } else if result == "updated" { updated++ } } c.contactJsonOK(map[string]interface{}{ "total": len(employees), "created": created, "updated": updated, }) } // GetContactOrgTree 获取通讯录组织树(带各部门联系人数量) // GET /backend/erp/contact/orgTree func (c *BackendErpContactController) GetContactOrgTree() { tid, err := c.contactTenantID() if err != nil { c.contactJsonError(401, "未登录或无权访问") return } qs := models.Orm.QueryTable(new(models.BackendOrganization)). Filter("delete_time__isnull", true). Exclude("status", 0). Filter("tid", tid) var orgs []models.BackendOrganization _, err = qs.OrderBy("sort", "id").All(&orgs) if err != nil { c.contactJsonError(500, "查询组织架构失败: "+err.Error()) return } orgContactCounts := make(map[uint64]int64) cqs := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("delete_time__isnull", true). Exclude("status", 0). Filter("contact_type", 1). Filter("tid", tid) type orgCount struct { OrgID uint64 `orm:"column(org_id)"` Count int64 `orm:"column(cnt)"` } var counts []orgCount _, err = cqs.GroupBy("org_id").All(&counts, "org_id") if err == nil { // Beego ORM GroupBy doesn't support raw count in All, so count manually } // Fallback: count per org manually for _, org := range orgs { cnt, _ := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("org_id", org.ID). Filter("tid", tid). Filter("delete_time__isnull", true). Exclude("status", 0). Filter("contact_type", 1). Count() orgContactCounts[org.ID] = cnt } // Also get total internal contacts count for root totalInternal, _ := cqs.Count() // Build tree tree := c.buildContactOrgTree(orgs, orgContactCounts, uint64(totalInternal)) c.contactJsonOK(tree) } // buildContactOrgTree 构建通讯录组织树 func (c *BackendErpContactController) buildContactOrgTree( orgs []models.BackendOrganization, counts map[uint64]int64, totalInternal uint64, ) []map[string]interface{} { nodeMap := make(map[uint64]map[string]interface{}) tree := make([]map[string]interface{}, 0) for _, org := range orgs { node := map[string]interface{}{ "id": org.ID, "org_name": org.OrgName, "parent_id": org.ParentID, "is_company": org.IsCompany, "status": org.Status, "contact_count": counts[org.ID], "children": make([]map[string]interface{}, 0), } nodeMap[org.ID] = node } for _, org := range orgs { node := nodeMap[org.ID] if org.ParentID == 0 { node["contact_count"] = int64(totalInternal) tree = append(tree, node) } else { if parent, exists := nodeMap[org.ParentID]; exists { parent["children"] = append(parent["children"].([]map[string]interface{}), node) } } } return tree } // --- DTO builder --- func (c *BackendErpContactController) contactDTO(row models.BackendErpContact) erpContactDTO { orgName := "" if row.OrgID != nil && *row.OrgID > 0 { var org models.BackendOrganization if err := models.Orm.QueryTable(new(models.BackendOrganization)). Filter("id", *row.OrgID). One(&org); err == nil { orgName = org.OrgName } } return erpContactDTO{ ID: row.ID, Tid: row.Tid, EmployeeID: contactDerefUint64(row.EmployeeID), OrgID: contactDerefUint64(row.OrgID), OrgName: orgName, ContactType: row.ContactType, ContactName: row.ContactName, Gender: row.Gender, Phone: contactDerefString(row.Phone), WorkPhone: contactDerefString(row.WorkPhone), Email: contactDerefString(row.Email), Wechat: contactDerefString(row.Wechat), QQ: contactDerefString(row.QQ), Avatar: contactDerefString(row.Avatar), CompanyName: contactDerefString(row.CompanyName), DeptName: contactDerefString(row.DeptName), PositionID: contactDerefUint64(row.PositionID), PositionTitle: contactDerefString(row.PositionTitle), Address: contactDerefString(row.Address), Remark: contactDerefString(row.Remark), IsStarred: row.IsStarred, Sort: row.Sort, Status: row.Status, CreateTime: row.CreateTime.Format("2006-01-02 15:04:05"), } } // --- Sync from Employee --- // SyncContactOnEmployeeCreate 员工创建后同步到通讯录(由组织架构控制器调用) func SyncContactOnEmployeeCreate(tid uint64, employeeID uint64, emp *models.BackendEmployee) { syncContactFromEmployee(tid, employeeID, emp) } // SyncContactOnEmployeeRefresh 员工信息变更后重新全量同步通讯录(由组织架构控制器调用)。 // 走的是与建档同步同一套逻辑:先取员工基础字段,再用人事档案字段覆盖(工作邮箱、家庭地址优先)。 func SyncContactOnEmployeeRefresh(tid uint64, employeeID uint64) { var emp models.BackendEmployee if err := models.Orm.QueryTable(new(models.BackendEmployee)). Filter("id", employeeID). Filter("delete_time__isnull", true). One(&emp); err != nil { return } syncContactFromEmployee(tid, employeeID, &emp) } // SyncContactOnEmployeeFileChange 人事档案建档/变更后同步通讯录(由 OA 档案控制器调用)。 // 通讯录以 employee_id 关联员工,档案里的字段(工作邮箱、家庭地址)优先级高于员工自身字段。 // 只做字段同步:不会新增/删除员工,也不会清理通讯录记录。 func SyncContactOnEmployeeFileChange(tid uint64, employeeID uint64) { SyncContactOnEmployeeRefresh(tid, employeeID) } // SyncContactOnEmployeeUpdate 员工更新后同步到通讯录(由 ERP 控制器调用) func SyncContactOnEmployeeUpdate(employeeID uint64, update orm.Params) { var contact models.BackendErpContact err := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("employee_id", employeeID). Filter("delete_time__isnull", true). One(&contact) if err != nil { return } contactUpdate := orm.Params{} if v, ok := update["name"]; ok { contactUpdate["contact_name"] = v } if v, ok := update["phone"]; ok { contactUpdate["phone"] = v } if v, ok := update["email"]; ok { contactUpdate["email"] = v } if v, ok := update["wechat"]; ok { contactUpdate["wechat"] = v } if v, ok := update["gender"]; ok { contactUpdate["gender"] = v } if v, ok := update["department"]; ok { if v == nil { contactUpdate["org_id"] = nil } else { switch dept := v.(type) { case string: orgID, err := strconv.ParseUint(strings.TrimSpace(dept), 10, 64) if err == nil && orgID > 0 { contactUpdate["org_id"] = orgID } else { contactUpdate["org_id"] = nil } case uint64: if dept > 0 { contactUpdate["org_id"] = dept } else { contactUpdate["org_id"] = nil } case uint: if dept > 0 { contactUpdate["org_id"] = uint64(dept) } else { contactUpdate["org_id"] = nil } default: contactUpdate["org_id"] = v } } } if v, ok := update["position"]; ok { contactUpdate["position_title"] = v } if v, ok := update["home_address"]; ok { contactUpdate["address"] = v } // 员工账号状态(启用/禁用/离职)不同步到通讯录: // 通讯录是企业数据,员工离职或账号被删除都不代表联系人应当停用,是否停用由通讯录模块自行维护。 if len(contactUpdate) > 0 { models.Orm.QueryTable(new(models.BackendErpContact)). Filter("id", contact.ID). Update(contactUpdate) } } // SyncContactOnEmployeeDelete 清理员工关联的通讯录记录。 // 注意:员工离职/禁用(deleteEmployee)与后台账号删除(deleteUser)都不得调用本函数—— // 通讯录是企业数据,账号或状态变化不等于联系人消失。仅在员工数据被真正物理清理时使用。 func SyncContactOnEmployeeDelete(employeeID uint64) { now := time.Now().Format("2006-01-02 15:04:05") models.Orm.QueryTable(new(models.BackendErpContact)). Filter("employee_id", employeeID). Filter("delete_time__isnull", true). Update(orm.Params{"delete_time": now, "status": 0}) } // syncContactFromEmployee 内部同步函数 // 从员工信息和人事档案同步数据到通讯录 func syncContactFromEmployee(tid uint64, employeeID uint64, emp *models.BackendEmployee) string { var existing models.BackendErpContact err := models.Orm.QueryTable(new(models.BackendErpContact)). Filter("employee_id", employeeID). Filter("delete_time__isnull", true). One(&existing) // 获取人事档案数据 fileData := getEmployeeFileForContact(employeeID) // 通讯录字段优先级:人事档案 > 员工(档案字段为空时回退员工自身字段) email := contactFirstNonEmptyPtr(&fileData.WorkEmail, emp.Email) address := contactFirstNonEmptyPtr(&fileData.HouseholdAddress, &fileData.CurrentAddress, emp.HomeAddress) if err != nil { // Not found - create new contact var orgID *uint64 if emp.Department != nil { if oid, err := strconv.ParseUint(strings.TrimSpace(*emp.Department), 10, 64); err == nil && oid > 0 { orgID = &oid } } contact := models.BackendErpContact{ Tid: tid, EmployeeID: &employeeID, OrgID: orgID, ContactType: 1, ContactName: emp.Name, Gender: emp.Gender, Phone: emp.Phone, Email: email, Wechat: emp.Wechat, PositionTitle: emp.Position, Address: address, Status: 1, } models.Orm.Insert(&contact) return "created" } // Found - update update := orm.Params{ "contact_name": emp.Name, "gender": emp.Gender, "phone": emp.Phone, "email": email, "wechat": emp.Wechat, "position_title": emp.Position, "address": address, } if emp.Department != nil { if oid, err := strconv.ParseUint(strings.TrimSpace(*emp.Department), 10, 64); err == nil && oid > 0 { update["org_id"] = oid } else { update["org_id"] = nil } } else { update["org_id"] = nil } models.Orm.QueryTable(new(models.BackendErpContact)). Filter("id", existing.ID). Update(update) return "updated" } // getEmployeeFileForContact 获取员工人事档案数据(用于通讯录同步) // 返回档案中与通讯录相关的字段 func getEmployeeFileForContact(employeeID uint64) struct { WorkEmail string HouseholdAddress string CurrentAddress string EmergencyContact string EmergencyPhone string EmergencyRelation string EmploymentStatus int8 } { var file models.BackendEmployeeFile err := models.Orm.QueryTable(new(models.BackendEmployeeFile)). Filter("employee_id", employeeID). Filter("is_deleted", 0). One(&file) if err != nil { // 档案不存在,返回空值 return struct { WorkEmail string HouseholdAddress string CurrentAddress string EmergencyContact string EmergencyPhone string EmergencyRelation string EmploymentStatus int8 }{} } return struct { WorkEmail string HouseholdAddress string CurrentAddress string EmergencyContact string EmergencyPhone string EmergencyRelation string EmploymentStatus int8 }{ WorkEmail: file.WorkEmail, HouseholdAddress: file.HouseholdAddress, CurrentAddress: file.CurrentAddress, EmergencyContact: file.EmergencyContact, EmergencyPhone: file.EmergencyPhone, EmergencyRelation: file.EmergencyRelation, EmploymentStatus: file.EmploymentStatus, } } // --- Helper functions (package-level, shared with ERP controller) --- func contactDerefString(v *string) string { if v == nil { return "" } return *v } func contactDerefUint64(v *uint64) uint64 { if v == nil { return 0 } return *v } func contactStrPtr(v string) *string { v = strings.TrimSpace(v) if v == "" { return nil } return &v } func contactNullableString(v string) interface{} { v = strings.TrimSpace(v) if v == "" { return nil } return v } // --- Controller-level helpers --- func (c *BackendErpContactController) contactParseJSONBody() map[string]interface{} { body := map[string]interface{}{} contentType := strings.ToLower(c.Ctx.Input.Header("Content-Type")) if !strings.Contains(contentType, "json") { return body } if len(c.Ctx.Input.RequestBody) == 0 { return body } _ = json.Unmarshal(c.Ctx.Input.RequestBody, &body) return body } func (c *BackendErpContactController) contactGetStringValue(body map[string]interface{}, keys ...string) (string, bool) { for _, key := range keys { if v, ok := body[key]; ok { switch val := v.(type) { case string: return val, true case float64: return strconv.FormatFloat(val, 'f', -1, 64), true case bool: return strconv.FormatBool(val), true default: b, _ := json.Marshal(v) return strings.TrimSpace(strings.Trim(strings.ReplaceAll(strings.ReplaceAll(string(b), "\n", ""), "\r", ""), "\"")), true } } if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil { _ = c.Ctx.Request.ParseMultipartForm(32 << 20) } if val := c.GetString(key); val != "" { return val, true } } return "", false } func (c *BackendErpContactController) contactGetIntValue(body map[string]interface{}, keys ...string) (int, bool) { for _, key := range keys { if v, ok := body[key]; ok { switch val := v.(type) { case float64: return int(val), true case int: return val, true case string: if strings.TrimSpace(val) == "" { return 0, true } parsed, err := strconv.Atoi(strings.TrimSpace(val)) return parsed, err == nil } } if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil { _ = c.Ctx.Request.ParseMultipartForm(32 << 20) } if val := c.GetString(key); val != "" { parsed, err := strconv.Atoi(strings.TrimSpace(val)) return parsed, err == nil } } return 0, false } func (c *BackendErpContactController) contactGetUintValue(body map[string]interface{}, keys ...string) (uint, bool) { v, ok := c.contactGetIntValue(body, keys...) if !ok || v < 0 { return 0, ok } return uint(v), true } func (c *BackendErpContactController) contactGetUint64Value(body map[string]interface{}, keys ...string) (uint64, bool) { for _, key := range keys { if v, ok := body[key]; ok { switch val := v.(type) { case float64: if val < 0 { return 0, false } return uint64(val), true case int: if val < 0 { return 0, false } return uint64(val), true case string: if strings.TrimSpace(val) == "" { return 0, true } parsed, err := strconv.ParseUint(strings.TrimSpace(val), 10, 64) return parsed, err == nil } } if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil { _ = c.Ctx.Request.ParseMultipartForm(32 << 20) } if val := c.GetString(key); val != "" { parsed, err := strconv.ParseUint(strings.TrimSpace(val), 10, 64) return parsed, err == nil } } return 0, false } func (c *BackendErpContactController) contactPathUint64(name string) (uint64, bool) { id, err := strconv.ParseUint(c.Ctx.Input.Param(name), 10, 64) return id, err == nil && id > 0 } func (c *BackendErpContactController) contactJsonOK(data interface{}) { resp := map[string]interface{}{"code": 200, "msg": "success"} if data != nil { resp["data"] = data } c.Data["json"] = resp _ = c.ServeJSON() } func (c *BackendErpContactController) contactJsonError(code int, msg string) { c.Data["json"] = map[string]interface{}{"code": code, "msg": msg} _ = c.ServeJSON() } // contactFirstNonEmptyPtr 按优先级取第一个非空字符串(去空格后判断),全部为空时返回 nil。 // 用于通讯录"档案字段 > 员工字段"的取值优先级,避免用空字符串覆盖已有数据。 func contactFirstNonEmptyPtr(values ...*string) *string { for _, v := range values { if v == nil { continue } if s := strings.TrimSpace(*v); s != "" { return &s } } return nil }