Files
platforms/backend/api/internal/logic/client/user/emergency_contacts.go

161 lines
6.0 KiB
Go
Raw Normal View History

// 功能:紧急联系人本人隔离、五人上限和幂等新增;版本: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})
}