package user import ( "context" "database/sql" "errors" "fmt" "os" "time" "bdrp-server/app/main/api/internal/svc" "bdrp-server/app/main/api/internal/types" "bdrp-server/app/main/model" "bdrp-server/common/ctxdata" "bdrp-server/common/xerr" "bdrp-server/pkg/lzkit/crypto" pkgerrors "github.com/pkg/errors" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/sqlx" ) type CancelOutLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewCancelOutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CancelOutLogic { return &CancelOutLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } // cancelledMobilePlain 生成唯一占位明文手机号(11 位),用于释放真实手机号唯一约束 func cancelledMobilePlain(userID int64) string { return fmt.Sprintf("199%09d", userID%1000000000) } func (l *CancelOutLogic) CancelOut(req *types.CancelOutReq) error { userID, err := ctxdata.GetUidFromCtx(l.ctx) if err != nil { return pkgerrors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err) } user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) if err != nil { if errors.Is(err, model.ErrNotFound) { return pkgerrors.Wrapf(xerr.NewErrCode(xerr.USER_NOT_FOUND), "用户不存在") } return pkgerrors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户失败: %v", err) } if user.Disable == 1 { return pkgerrors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") } if user.CancelledAt.Valid { return pkgerrors.Wrapf(xerr.NewErrCode(xerr.USER_CANCELLED), "账号已注销") } if !user.Mobile.Valid || user.Mobile.String == "" { return pkgerrors.Wrapf(xerr.NewErrMsg("请先绑定手机号后再注销账号"), "注销账号, 未绑定手机号 userId=%d", userID) } secretKey := l.svcCtx.Config.Encrypt.SecretKey encryptedMobile := user.Mobile.String if os.Getenv("ENV") != "development" { codeKey := fmt.Sprintf("%s:%s", "cancelAccount", encryptedMobile) cacheCode, err := l.svcCtx.Redis.Get(codeKey) if err != nil { if errors.Is(err, redis.Nil) { return pkgerrors.Wrapf(xerr.NewErrMsg("验证码已过期"), "注销账号, 验证码过期") } return pkgerrors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "注销账号, 读取验证码失败: %v", err) } if cacheCode != req.Code { return pkgerrors.Wrapf(xerr.NewErrMsg("验证码不正确"), "注销账号, 验证码不正确") } } placeholderPlain := cancelledMobilePlain(userID) placeholderEnc, err := crypto.EncryptMobile(placeholderPlain, secretKey) if err != nil { return pkgerrors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "注销账号, 生成占位手机号失败: %v", err) } agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) if err != nil && !errors.Is(err, model.ErrNotFound) { return pkgerrors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err) } err = l.svcCtx.UserModel.Trans(l.ctx, func(tranCtx context.Context, session sqlx.Session) error { dbUser, err := l.svcCtx.UserModel.FindOne(tranCtx, userID) if err != nil { return err } if dbUser.CancelledAt.Valid { return xerr.NewErrCode(xerr.USER_CANCELLED) } dbUser.CancelledAt = sql.NullTime{Time: time.Now(), Valid: true} dbUser.Mobile = sql.NullString{String: placeholderEnc, Valid: true} dbUser.Nickname = sql.NullString{String: "已注销用户", Valid: true} dbUser.Password = sql.NullString{Valid: false} dbUser.Info = "" if err := l.svcCtx.UserModel.UpdateWithVersion(tranCtx, session, dbUser); err != nil { return pkgerrors.Wrapf(err, "更新用户注销状态失败") } authBuilder := l.svcCtx.UserAuthModel.SelectBuilder().Where("user_id = ?", userID) userAuths, err := l.svcCtx.UserAuthModel.FindAll(tranCtx, authBuilder, "") if err != nil && !errors.Is(err, model.ErrNotFound) { return pkgerrors.Wrapf(err, "查询用户授权失败") } for _, ua := range userAuths { if err := l.svcCtx.UserAuthModel.Delete(tranCtx, session, ua.Id); err != nil { return pkgerrors.Wrapf(err, "删除用户授权失败 id=%d", ua.Id) } } queryBuilder := l.svcCtx.QueryModel.SelectBuilder().Where("user_id = ?", userID) queries, err := l.svcCtx.QueryModel.FindAll(tranCtx, queryBuilder, "") if err != nil && !errors.Is(err, model.ErrNotFound) { return pkgerrors.Wrapf(err, "查询用户查询记录失败") } for _, q := range queries { if err := l.svcCtx.QueryModel.Delete(tranCtx, session, q.Id); err != nil { return pkgerrors.Wrapf(err, "删除查询记录失败 id=%d", q.Id) } } if agentModel != nil { ag, err := l.svcCtx.AgentModel.FindOne(tranCtx, agentModel.Id) if err != nil { return err } ag.Mobile = placeholderEnc ag.WechatId = sql.NullString{Valid: false} if err := l.svcCtx.AgentModel.UpdateWithVersion(tranCtx, session, ag); err != nil { return pkgerrors.Wrapf(err, "更新代理占位信息失败") } } return nil }) if err != nil { var ce *xerr.CodeError if errors.As(err, &ce) { return ce } return pkgerrors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户注销失败: %v", err) } return nil }