// 功能:紧急联系人本人隔离、五人上限和幂等新增;版本:1.0.0。 package user import ( "errors" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common" "git.apinb.com/heqiapp/platforms/backend/api/internal/models" "github.com/gin-gonic/gin" "github.com/google/uuid" "gorm.io/gorm" "regexp" "strings" "unicode/utf8" ) // contactInput 仅接收联系人资料,不接收客户端伪造的设备或通知授权。 type contactInput struct { Name string `json:"name"` Phone string `json:"phone"` Relationship string `json:"relationship"` RequestNo string `json:"request_no"` } // 业务拒绝使用稳定错误码,区别于连接失败和校验失败。 var ( errContactLimit = errcode.NewError(2501, "最多可添加5位联系人,请先移除不再使用的联系人") errContactDuplicate = errcode.NewError(2502, "该手机号已在联系人列表中,请勿重复添加") errContactRequestChanged = errcode.NewError(2503, "该新增请求已处理,请关闭表单并刷新联系人列表") ) // validContact 约束输入和新增幂等标识;返回是否可保存。 func validContact(input *contactInput, creating bool) bool { input.Name = strings.TrimSpace(input.Name) input.Phone = strings.TrimSpace(input.Phone) input.Relationship = strings.TrimSpace(input.Relationship) if input.Name == "" || utf8.RuneCountInString(input.Name) > 64 || !regexp.MustCompile(`^1[3-9][0-9]{9}$`).MatchString(input.Phone) || utf8.RuneCountInString(input.Relationship) > 32 { return false } if creating { parsed, err := uuid.Parse(input.RequestNo) return err == nil && parsed != uuid.Nil && parsed.String() == input.RequestNo } return true } // contactView 白名单响应,不暴露内部归属或声明尚不存在的权限。 func contactView(value models.UserEmergencyContact) gin.H { return gin.H{"identity": value.Identity, "name": value.Name, "phone": value.Phone, "relationship": value.Relationship, "notification_available": false, "device_access_available": false, "control_available": false} } // ListEmergencyContacts 返回当前用户启用联系人,列表按创建顺序稳定排列。 func ListEmergencyContacts(ctx *gin.Context) { account, ok := common.UserAccount(ctx) if !ok { return } var records []models.UserEmergencyContact if err := impl.DBService.Where("user_account_id = ? AND status = ?", account.ID, common.StatusEnable).Order("created_at, identity").Limit(5).Find(&records).Error; err != nil { infra.Response.Error(ctx, err) return } items := make([]gin.H, 0, len(records)) for _, record := range records { items = append(items, contactView(record)) } infra.Response.Success(ctx, items) } // SaveEmergencyContact 新增或修改本人联系人;事务内锁账户保证并发上限。 func SaveEmergencyContact(ctx *gin.Context) { account, ok := common.UserAccount(ctx) if !ok { return } var input contactInput creating := ctx.Param("identity") == "" if ctx.ShouldBindJSON(&input) != nil || !validContact(&input, creating) { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } var record models.UserEmergencyContact err := impl.DBService.Transaction(func(tx *gorm.DB) error { if err := lockAddressOwner(tx, account.ID); err != nil { return err } if creating { err := tx.Where("user_account_id = ? AND request_no = ?", account.ID, input.RequestNo).First(&record).Error if err == nil { // 已删除的新增请求不能再次激活,同一请求号也不能改变原资料。 if record.Status != common.StatusEnable || record.Name != input.Name || record.Phone != input.Phone || record.Relationship != input.Relationship { return errContactRequestChanged } return nil } if !errors.Is(err, gorm.ErrRecordNotFound) { return err } var count int64 if err := tx.Model(&models.UserEmergencyContact{}).Where("user_account_id = ? AND status = ?", account.ID, common.StatusEnable).Count(&count).Error; err != nil { return err } if count >= 5 { return errContactLimit } record = models.UserEmergencyContact{Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable}, UserAccountID: account.ID, RequestNo: input.RequestNo} } else if err := tx.Where("identity = ? AND user_account_id = ? AND status = ?", ctx.Param("identity"), account.ID, common.StatusEnable).First(&record).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errcode.ErrRecordNotFound } return err } var duplicates int64 if err := tx.Model(&models.UserEmergencyContact{}).Where("user_account_id = ? AND status = ? AND phone = ? AND identity <> ?", account.ID, common.StatusEnable, input.Phone, record.Identity).Count(&duplicates).Error; err != nil { return err } if duplicates > 0 { return errContactDuplicate } record.Name, record.Phone, record.Relationship = input.Name, input.Phone, input.Relationship return tx.Save(&record).Error }) if err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, contactView(record)) } // DeleteEmergencyContact 归档本人记录,重复删除成功;不存在或跨账户统一拒绝。 func DeleteEmergencyContact(ctx *gin.Context) { account, ok := common.UserAccount(ctx) if !ok { return } err := impl.DBService.Transaction(func(tx *gorm.DB) error { if err := lockAddressOwner(tx, account.ID); err != nil { return err } var record models.UserEmergencyContact if err := tx.Where("identity = ? AND user_account_id = ?", ctx.Param("identity"), account.ID).First(&record).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errcode.ErrRecordNotFound } return err } if record.Status == common.StatusArchived { return nil } return tx.Model(&record).Update("status", common.StatusArchived).Error }) if err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"deleted": true}) }