2026-04-01 15:43:01 +08:00
|
|
|
|
package user
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
2026-04-20 11:34:35 +08:00
|
|
|
|
"database/sql"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"bdrp-server/app/main/api/internal/svc"
|
|
|
|
|
|
"bdrp-server/app/main/api/internal/types"
|
2026-04-01 15:43:01 +08:00
|
|
|
|
"bdrp-server/app/main/model"
|
|
|
|
|
|
"bdrp-server/common/ctxdata"
|
|
|
|
|
|
"bdrp-server/common/xerr"
|
2026-04-20 11:34:35 +08:00
|
|
|
|
"bdrp-server/pkg/lzkit/crypto"
|
2026-04-01 15:43:01 +08:00
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
pkgerrors "github.com/pkg/errors"
|
2026-04-01 15:43:01 +08:00
|
|
|
|
"github.com/zeromicro/go-zero/core/logx"
|
2026-04-20 11:34:35 +08:00
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
2026-04-01 15:43:01 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
// cancelledMobilePlain 生成唯一占位明文手机号(11 位),用于释放真实手机号唯一约束
|
|
|
|
|
|
func cancelledMobilePlain(userID int64) string {
|
|
|
|
|
|
return fmt.Sprintf("199%09d", userID%1000000000)
|
|
|
|
|
|
}
|
2026-04-01 15:43:01 +08:00
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
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)
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
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), "用户不存在")
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
2026-04-20 11:34:35 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
2026-04-01 15:43:01 +08:00
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
|
|
|
|
|
encryptedMobile := user.Mobile.String
|
2026-04-01 15:43:01 +08:00
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
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("验证码已过期"), "注销账号, 验证码过期")
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
2026-04-20 11:34:35 +08:00
|
|
|
|
return pkgerrors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "注销账号, 读取验证码失败: %v", err)
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
2026-04-20 11:34:35 +08:00
|
|
|
|
if cacheCode != req.Code {
|
|
|
|
|
|
return pkgerrors.Wrapf(xerr.NewErrMsg("验证码不正确"), "注销账号, 验证码不正确")
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
2026-04-20 11:34:35 +08:00
|
|
|
|
}
|
2026-04-01 15:43:01 +08:00
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
placeholderPlain := cancelledMobilePlain(userID)
|
|
|
|
|
|
placeholderEnc, err := crypto.EncryptMobile(placeholderPlain, secretKey)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return pkgerrors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "注销账号, 生成占位手机号失败: %v", err)
|
|
|
|
|
|
}
|
2026-04-01 15:43:01 +08:00
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
2026-04-01 15:43:01 +08:00
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
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)
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
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, "更新用户注销状态失败")
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
authBuilder := l.svcCtx.UserAuthModel.SelectBuilder().Where("user_id = ?", userID)
|
|
|
|
|
|
userAuths, err := l.svcCtx.UserAuthModel.FindAll(tranCtx, authBuilder, "")
|
2026-04-01 15:43:01 +08:00
|
|
|
|
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
2026-04-20 11:34:35 +08:00
|
|
|
|
return pkgerrors.Wrapf(err, "查询用户授权失败")
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
2026-04-20 11:34:35 +08:00
|
|
|
|
for _, ua := range userAuths {
|
|
|
|
|
|
if err := l.svcCtx.UserAuthModel.Delete(tranCtx, session, ua.Id); err != nil {
|
|
|
|
|
|
return pkgerrors.Wrapf(err, "删除用户授权失败 id=%d", ua.Id)
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
2026-04-20 11:34:35 +08:00
|
|
|
|
return pkgerrors.Wrapf(err, "查询用户查询记录失败")
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
2026-04-20 11:34:35 +08:00
|
|
|
|
for _, q := range queries {
|
|
|
|
|
|
if err := l.svcCtx.QueryModel.Delete(tranCtx, session, q.Id); err != nil {
|
|
|
|
|
|
return pkgerrors.Wrapf(err, "删除查询记录失败 id=%d", q.Id)
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 11:34:35 +08:00
|
|
|
|
if agentModel != nil {
|
|
|
|
|
|
ag, err := l.svcCtx.AgentModel.FindOne(tranCtx, agentModel.Id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
2026-04-20 11:34:35 +08:00
|
|
|
|
ag.Mobile = placeholderEnc
|
|
|
|
|
|
ag.WechatId = sql.NullString{Valid: false}
|
|
|
|
|
|
if err := l.svcCtx.AgentModel.UpdateWithVersion(tranCtx, session, ag); err != nil {
|
|
|
|
|
|
return pkgerrors.Wrapf(err, "更新代理占位信息失败")
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
2026-04-20 11:34:35 +08:00
|
|
|
|
var ce *xerr.CodeError
|
|
|
|
|
|
if errors.As(err, &ce) {
|
|
|
|
|
|
return ce
|
|
|
|
|
|
}
|
|
|
|
|
|
return pkgerrors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户注销失败: %v", err)
|
2026-04-01 15:43:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|