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