package logic import ( "context" "fmt" "strconv" "lone-services/pkg/modelbase" "lone-services/pkg/utils" "lone-services/pkg/validate" user "lone-services/rpc/user/pb" "lone-services/services/user/internal/dao" "lone-services/services/user/internal/model" "lone-services/services/user/internal/svc" "lone-services/services/user/validator" "github.com/zeromicro/go-zero/core/logx" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "gorm.io/gorm" ) type InternalUpdateLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewInternalUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *InternalUpdateLogic { return &InternalUpdateLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *InternalUpdateLogic) InternalUpdate(in *user.InternalUpdateReq) (*user.InternalUpdateData, error) { var req validator.InternalUpdateValidator if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { return nil, status.Error(codes.InvalidArgument, msg) } userId, findErr := findUserIdByBiz(l.svcCtx.DB, req.BizId, req.SubjectType) if findErr != nil { l.Errorf("internal update find by biz: %v", findErr) return nil, status.Error(codes.Internal, utils.Fail.Msg) } if userId < utils.NumberOne { return nil, status.Error(codes.NotFound, utils.ErrorNotFund.Msg) } userModel := model.UserModel{}.Init() var row dao.UserRow if err := userModel.GetOne(modelbase.Params{ Eq: map[string]string{"id": strconv.FormatInt(userId, utils.NumberTen)}, }, &row); err != nil { l.Errorf("internal update get user: %v", err) return nil, status.Error(codes.Internal, utils.Fail.Msg) } if row.Id < utils.NumberOne { return nil, status.Error(codes.NotFound, utils.ErrorNotFund.Msg) } if row.Status == dao.StatusDisabled { return nil, status.Error(codes.FailedPrecondition, "用户已禁用") } encryptMobile, encErr := utils.EncryptPhone(req.Mobile) if encErr != nil { l.Errorf("internal update encrypt: %v", encErr) return nil, status.Error(codes.Internal, utils.ErrorEncryptAesError.Msg) } if encryptMobile != row.Mobile { var occupied dao.UserRow if err := userModel.GetOne(modelbase.Params{ Eq: map[string]string{"mobile": encryptMobile}, }, &occupied); err != nil { l.Errorf("internal update unique check: %v", err) return nil, status.Error(codes.Internal, utils.Fail.Msg) } if occupied.Id > utils.NumberZero && occupied.Id != row.Id { return nil, status.Error(codes.AlreadyExists, utils.ErrorDataIsExist.Msg) } } clientCodes := uniqueNonEmpty(req.ClientCode) clientModel := model.ClientModel{}.Init() for _, code := range clientCodes { var client dao.Client if err := clientModel.GetOne(modelbase.Params{ Eq: map[string]string{ "code": code, "status": strconv.Itoa(int(dao.StatusEnabled)), }, }, &client); err != nil { l.Errorf("internal update client: %v", err) return nil, status.Error(codes.Internal, utils.Fail.Msg) } if client.Id < utils.NumberOne { return nil, status.Error(codes.InvalidArgument, "端配置不存在或已禁用: "+code) } if client.SubjectType != req.SubjectType { return nil, status.Error(codes.InvalidArgument, "端主体类型与业务主体不一致: "+code) } } updates := map[string]interface{}{} if encryptMobile != row.Mobile { updates["mobile"] = encryptMobile } if req.Name != utils.StringEmpty { updates["name"] = req.Name } if req.Avatar != utils.StringEmpty { updates["avatar"] = req.Avatar } if req.Gender > utils.NumberZero { updates["gender"] = uint8(req.Gender) } if req.Birthday != utils.StringEmpty { birthday, bErr := utils.ParseDateOnly(req.Birthday) if bErr != nil { return nil, status.Error(codes.InvalidArgument, "生日格式不正确") } // 用 YYYY-MM-DD 字符串写入,避免 Edit 的 JSON 往返把 CustomTime 编成带 00:00:00 的 datetime updates["birthday"] = birthday.DateString() } oldMobile := row.Mobile credentialTypes := uniqueNonEmpty(req.CredentialType) txErr := l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error { um := model.UserModel{}.Init() um.Base = um.Base.WithTX(tx) if len(updates) > utils.NumberZero { if _, err := um.Edit(modelbase.Params{ Eq: map[string]string{"id": strconv.FormatInt(row.Id, utils.NumberTen)}, }, updates); err != nil { return err } } if encryptMobile != oldMobile { credModel := model.UserCredentialModel{}.Init() credModel.Base = credModel.Base.WithTX(tx) if _, err := credModel.Edit(modelbase.Params{ Eq: map[string]string{ "user_id": strconv.FormatInt(row.Id, utils.NumberTen), "identifier": oldMobile, "subject_type": req.SubjectType, }, }, map[string]interface{}{"identifier": encryptMobile}); err != nil { return err } } for _, credType := range credentialTypes { if err := ensureCredential(tx, row.Id, credType, req.Identifier, req.Secret, encryptMobile, req.SubjectType); err != nil { return err } } for _, code := range clientCodes { if err := ensureUserClient(tx, row.Id, code, req.BizId); err != nil { return err } } return nil }) if txErr != nil { if st, ok := status.FromError(txErr); ok { return nil, st.Err() } l.Errorf("internal update tx: %v", txErr) return nil, status.Error(codes.Internal, utils.Fail.Msg) } name := row.Name if req.Name != utils.StringEmpty { name = req.Name } return &user.InternalUpdateData{ UserId: row.Id, Mobile: encryptMobile, Name: name, }, nil } func findUserIdByBiz(db *gorm.DB, bizId int64, subjectType string) (int64, error) { prefix := modelbase.Prefix() var userId int64 err := db.Table(fmt.Sprintf("%suser_client AS uc", prefix)). Select("uc.user_id"). Joins(fmt.Sprintf("INNER JOIN %sclient AS c ON c.code = uc.client_code", prefix)). Where("uc.biz_id = ? AND c.subject_type = ? AND uc.status = ?", bizId, subjectType, dao.StatusEnabled). Limit(utils.NumberOne). Scan(&userId).Error return userId, err }