This commit is contained in:
2026-04-20 11:34:35 +08:00
parent 957976db31
commit bdbd6ae7e9
22 changed files with 761 additions and 238 deletions

View File

@@ -161,13 +161,14 @@ type (
List []AgentSubordinateList `json:"list"` // 查询列表 List []AgentSubordinateList `json:"list"` // 查询列表
} }
AgentSubordinateList { AgentSubordinateList {
ID int64 `json:"id"` ID int64 `json:"id"`
Mobile string `json:"mobile"` Mobile string `json:"mobile"`
CreateTime string `json:"create_time"` CreateTime string `json:"create_time"`
LevelName string `json:"level_name"` LevelName string `json:"level_name"`
TotalOrders int64 `json:"total_orders"` // 总单量 TotalOrders int64 `json:"total_orders"` // 总单量
TotalEarnings float64 `json:"total_earnings"` // 总金额 TotalEarnings float64 `json:"total_earnings"` // 总金额
TotalContribution float64 `json:"total_contribution"` // 总贡献 TotalContribution float64 `json:"total_contribution"` // 总贡献
AccountCancelled bool `json:"account_cancelled"` // 账号是否已注销
} }
GetAgentSubordinateContributionDetailReq { GetAgentSubordinateContributionDetailReq {
Page int64 `form:"page"` // 页码 Page int64 `form:"page"` // 页码

View File

@@ -110,10 +110,13 @@ service main {
post /user/getToken returns (MobileCodeLoginResp) post /user/getToken returns (MobileCodeLoginResp)
@handler cancelOut @handler cancelOut
post /user/cancelOut post /user/cancelOut (CancelOutReq)
} }
type ( type (
CancelOutReq {
Code string `json:"code" validate:"required"`
}
UserInfoResp { UserInfoResp {
UserInfo User `json:"userInfo"` UserInfo User `json:"userInfo"`
} }
@@ -143,7 +146,7 @@ type (
sendSmsReq { sendSmsReq {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
CaptchaVerifyParam string `json:"captchaVerifyParam,optional,omitempty"` CaptchaVerifyParam string `json:"captchaVerifyParam,optional,omitempty"`
ActionType string `json:"actionType,optional" validate:"oneof=login register query agentApply realName bindMobile"` ActionType string `json:"actionType,optional" validate:"oneof=login register query agentApply realName bindMobile cancelAccount"`
} }
) )

View File

@@ -5,13 +5,26 @@ import (
"bdrp-server/app/main/api/internal/logic/user" "bdrp-server/app/main/api/internal/logic/user"
"bdrp-server/app/main/api/internal/svc" "bdrp-server/app/main/api/internal/svc"
"bdrp-server/app/main/api/internal/types"
"bdrp-server/common/result" "bdrp-server/common/result"
"bdrp-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/rest/httpx"
) )
func CancelOutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func CancelOutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var req types.CancelOutReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := user.NewCancelOutLogic(r.Context(), svcCtx) l := user.NewCancelOutLogic(r.Context(), svcCtx)
err := l.CancelOut() err := l.CancelOut(&req)
result.HttpResult(r, w, nil, err) result.HttpResult(r, w, nil, err)
} }
} }

View File

@@ -83,6 +83,9 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
if user.Disable == 1 { if user.Disable == 1 {
return errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") return errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁")
} }
if user.CancelledAt.Valid {
return errors.Wrapf(xerr.NewErrCode(xerr.USER_CANCELLED), "账号已注销")
}
if claims != nil && claims.UserType == model.UserTypeTemp { if claims != nil && claims.UserType == model.UserTypeTemp {
// 临时用户,转为正式用户 // 临时用户,转为正式用户
err = l.svcCtx.UserService.TempUserBindUser(l.ctx, session, user.Id) err = l.svcCtx.UserService.TempUserBindUser(l.ctx, session, user.Id)

View File

@@ -161,11 +161,21 @@ func (l *GetAgentSubordinateContributionDetailLogic) GetAgentSubordinateContribu
} }
} }
// 解密手机号 // 解密手机号(账号已注销时展示占位文案)
secretKey := l.svcCtx.Config.Encrypt.SecretKey secretKey := l.svcCtx.Config.Encrypt.SecretKey
mobile, err := crypto.DecryptMobile(subordinateAgent.Mobile, secretKey) subUser, err := l.svcCtx.UserModel.FindOne(l.ctx, subordinateAgent.UserId)
if err != nil { if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级贡献详情, 解密手机号失败: %v", err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理下级贡献详情, 查询用户失败: %v", err)
}
var mobileMasked string
if subUser.CancelledAt.Valid {
mobileMasked = "已注销用户"
} else {
mobile, err := crypto.DecryptMobile(subordinateAgent.Mobile, secretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理下级贡献详情, 解密手机号失败: %v", err)
}
mobileMasked = maskPhone(mobile)
} }
// 获取合并后的分页列表 // 获取合并后的分页列表
@@ -187,7 +197,7 @@ func (l *GetAgentSubordinateContributionDetailLogic) GetAgentSubordinateContribu
} }
return &types.GetAgentSubordinateContributionDetailResp{ return &types.GetAgentSubordinateContributionDetailResp{
Mobile: maskPhone(mobile), Mobile: mobileMasked,
Total: total, Total: total,
CreateTime: closure.CreateTime.Format("2006-01-02 15:04:05"), CreateTime: closure.CreateTime.Format("2006-01-02 15:04:05"),
TotalEarnings: totalEarnings, TotalEarnings: totalEarnings,

View File

@@ -124,6 +124,20 @@ func (l *GetAgentSubordinateListLogic) GetAgentSubordinateList(req *types.GetAge
orderCountMap[v.AgentId]++ orderCountMap[v.AgentId]++
} }
// 下级对应用户是否已注销
accountCancelled := make(map[int64]bool)
for _, id := range descendantIDs {
ag := agentMap[id]
if ag == nil {
continue
}
u, err := l.svcCtx.UserModel.FindOne(l.ctx, ag.UserId)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取下级用户信息失败: %v", err)
}
accountCancelled[id] = u.CancelledAt.Valid
}
// 构建返回结果 // 构建返回结果
secretKey := l.svcCtx.Config.Encrypt.SecretKey secretKey := l.svcCtx.Config.Encrypt.SecretKey
descendantList = make([]types.AgentSubordinateList, 0, len(descendantIDs)) descendantList = make([]types.AgentSubordinateList, 0, len(descendantIDs))
@@ -133,19 +147,26 @@ func (l *GetAgentSubordinateListLogic) GetAgentSubordinateList(req *types.GetAge
continue continue
} }
mobile, err := crypto.DecryptMobile(agent.Mobile, secretKey) var mobileDisplay string
if err != nil { if accountCancelled[id] {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, 解密手机号失败: %v", err) mobileDisplay = "已注销用户"
} else {
mobile, err := crypto.DecryptMobile(agent.Mobile, secretKey)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, 解密手机号失败: %v", err)
}
mobileDisplay = maskPhone(mobile)
} }
subordinate := types.AgentSubordinateList{ subordinate := types.AgentSubordinateList{
ID: id, ID: id,
Mobile: maskPhone(mobile), Mobile: mobileDisplay,
LevelName: agent.LevelName, LevelName: agent.LevelName,
CreateTime: createTimeMap[id].Format("2006-01-02 15:04:05"), CreateTime: createTimeMap[id].Format("2006-01-02 15:04:05"),
TotalContribution: deductionMap[id] + rewardsMap[id], TotalContribution: deductionMap[id] + rewardsMap[id],
TotalEarnings: commissionMap[id], TotalEarnings: commissionMap[id],
TotalOrders: orderCountMap[id], TotalOrders: orderCountMap[id],
AccountCancelled: accountCancelled[id],
} }
descendantList = append(descendantList, subordinate) descendantList = append(descendantList, subordinate)
} }

View File

@@ -3,9 +3,14 @@ package auth
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"math/rand" "math/rand"
"strings"
"time" "time"
"bdrp-server/app/main/model"
"bdrp-server/common/xerr" "bdrp-server/common/xerr"
jwtx "bdrp-server/common/jwt"
"bdrp-server/pkg/lzkit/crypto" "bdrp-server/pkg/lzkit/crypto"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -53,6 +58,11 @@ func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
if action == "" { if action == "" {
action = "login" action = "login"
} }
if action == "cancelAccount" {
if err := l.verifyCancelAccountSmsCaller(req); err != nil {
return err
}
}
secretKey := l.svcCtx.Config.Encrypt.SecretKey secretKey := l.svcCtx.Config.Encrypt.SecretKey
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
if err != nil { if err != nil {
@@ -121,3 +131,49 @@ func (l *SendSmsLogic) sendSmsRequest(mobile, code string) (*dysmsapi.SendSmsRes
runtime := &service.RuntimeOptions{} runtime := &service.RuntimeOptions{}
return cli.SendSmsWithOptions(request, runtime) return cli.SendSmsWithOptions(request, runtime)
} }
// verifyCancelAccountSmsCaller 注销验证码:须携带与 Jwt 一致的登录态,且手机号与当前账号绑定一致
func (l *SendSmsLogic) verifyCancelAccountSmsCaller(req *types.SendSmsReq) error {
var authz string
if hr, ok := l.ctx.Value(captcha.HTTPRequestContextKey).(*http.Request); ok && hr != nil {
authz = strings.TrimSpace(hr.Header.Get("Authorization"))
}
if authz == "" {
return errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "请先登录后再获取注销验证码")
}
tokenStr := strings.TrimSpace(authz)
if len(tokenStr) > 7 && strings.EqualFold(tokenStr[:7], "Bearer ") {
tokenStr = strings.TrimSpace(tokenStr[7:])
}
claims, err := jwtx.ParseJwtToken(tokenStr, l.svcCtx.Config.JwtAuth.AccessSecret)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "登录已失效,请重新登录")
}
if claims.UserType != model.UserTypeNormal {
return errors.Wrapf(xerr.NewErrMsg("当前账号类型不支持注销验证码"), "cancelAccount sms")
}
user, err := l.svcCtx.UserModel.FindOne(l.ctx, claims.UserId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.USER_NOT_FOUND), "用户不存在")
}
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户失败: %v", err)
}
if user.CancelledAt.Valid {
return errors.Wrapf(xerr.NewErrCode(xerr.USER_CANCELLED), "账号已注销")
}
if user.Disable == 1 {
return errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁")
}
if !user.Mobile.Valid || user.Mobile.String == "" {
return errors.Wrapf(xerr.NewErrMsg("请先绑定手机号后再注销账号"), "cancelAccount sms, 未绑定手机")
}
enc, err := crypto.EncryptMobile(req.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
}
if enc != user.Mobile.String {
return errors.Wrapf(xerr.NewErrMsg("手机号与当前登录账号不一致"), "cancelAccount sms mobile mismatch")
}
return nil
}

View File

@@ -68,6 +68,9 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
if user.Disable == 1 { if user.Disable == 1 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁")
} }
if user.CancelledAt.Valid {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_CANCELLED), "账号已注销")
}
// 进行平台绑定 // 进行平台绑定
if claims != nil { if claims != nil {
if claims.UserType == model.UserTypeTemp { if claims.UserType == model.UserTypeTemp {

View File

@@ -2,18 +2,23 @@ package user
import ( import (
"context" "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/app/main/model"
"bdrp-server/common/ctxdata" "bdrp-server/common/ctxdata"
"bdrp-server/common/xerr" "bdrp-server/common/xerr"
"bdrp-server/pkg/lzkit/crypto"
"github.com/zeromicro/go-zero/core/mr" pkgerrors "github.com/pkg/errors"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"bdrp-server/app/main/api/internal/svc"
"github.com/zeromicro/go-zero/core/logx" "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 { type CancelOutLogic struct {
@@ -30,222 +35,122 @@ func NewCancelOutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CancelO
} }
} }
func (l *CancelOutLogic) CancelOut() error { // cancelledMobilePlain 生成唯一占位明文手机号11 位),用于释放真实手机号唯一约束
userID, getUserIdErr := ctxdata.GetUidFromCtx(l.ctx) func cancelledMobilePlain(userID int64) string {
if getUserIdErr != nil { return fmt.Sprintf("199%09d", userID%1000000000)
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", getUserIdErr) }
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)
} }
// 1. 先检查用户是否是代理
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil && !errors.Is(err, model.ErrNotFound) { if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查询代理信息失败, userId: %d", userID) return pkgerrors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
} }
// 如果用户是代理,进行额外检查
if agentModel != nil {
// 1.1 检查代理等级是否为VIP或SVIP
if agentModel.LevelName == model.AgentLeveNameVIP || agentModel.LevelName == model.AgentLeveNameSVIP {
return errors.Wrapf(xerr.NewErrMsg("您是"+agentModel.LevelName+"会员,请联系客服进行注销"), "用户是代理会员,不能注销")
}
// 1.2 检查代理钱包是否有余额或冻结金额
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理钱包失败, agentId: %d", agentModel.Id)
}
if wallet != nil && (wallet.Balance > 0 || wallet.FrozenBalance > 0) {
if wallet.Balance > 0 {
return errors.Wrapf(xerr.NewErrMsg("您的钱包还有余额%.2f元,请先提现后再注销账号"), "用户钱包有余额,不能注销", wallet.Balance)
}
if wallet.FrozenBalance > 0 {
return errors.Wrapf(xerr.NewErrMsg("您的钱包还有冻结金额%.2f元,请等待解冻后再注销账号"), "用户钱包有冻结金额,不能注销", wallet.FrozenBalance)
}
}
}
// 在事务中处理用户注销相关操作
err = l.svcCtx.UserModel.Trans(l.ctx, func(tranCtx context.Context, session sqlx.Session) error { err = l.svcCtx.UserModel.Trans(l.ctx, func(tranCtx context.Context, session sqlx.Session) error {
// 1. 删除用户基本信息 dbUser, err := l.svcCtx.UserModel.FindOne(tranCtx, userID)
if err := l.svcCtx.UserModel.Delete(tranCtx, session, userID); err != nil { if err != nil {
return errors.Wrapf(err, "删除用户基本信息失败, userId: %d", userID) return err
}
if dbUser.CancelledAt.Valid {
return xerr.NewErrCode(xerr.USER_CANCELLED)
} }
// 2. 查询并删除用户授权信息 dbUser.CancelledAt = sql.NullTime{Time: time.Now(), Valid: true}
UserAuthModelBuilder := l.svcCtx.UserAuthModel.SelectBuilder().Where("user_id = ?", userID) dbUser.Mobile = sql.NullString{String: placeholderEnc, Valid: true}
userAuths, err := l.svcCtx.UserAuthModel.FindAll(tranCtx, UserAuthModelBuilder, "") 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) { if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查询用户授权信息失败, userId: %d", userID) return pkgerrors.Wrapf(err, "查询用户授权失败")
} }
for _, ua := range userAuths {
// 并发删除用户授权信息 if err := l.svcCtx.UserAuthModel.Delete(tranCtx, session, ua.Id); err != nil {
if len(userAuths) > 0 { return pkgerrors.Wrapf(err, "删除用户授权失败 id=%d", ua.Id)
funcs := make([]func() error, len(userAuths))
for i, userAuth := range userAuths {
authID := userAuth.Id
funcs[i] = func() error {
return l.svcCtx.UserAuthModel.Delete(tranCtx, session, authID)
}
}
if err := mr.Finish(funcs...); err != nil {
return errors.Wrapf(err, "删除用户授权信息失败")
} }
} }
// 3. 处理代理相关信息
if agentModel != nil {
// 3.1 删除代理信息
if err := l.svcCtx.AgentModel.Delete(tranCtx, session, agentModel.Id); err != nil {
return errors.Wrapf(err, "删除代理信息失败, agentId: %d", agentModel.Id)
}
// 3.2 查询并删除代理会员配置
configBuilder := l.svcCtx.AgentMembershipUserConfigModel.SelectBuilder().Where("agent_id = ?", agentModel.Id)
configs, err := l.svcCtx.AgentMembershipUserConfigModel.FindAll(tranCtx, configBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查询代理会员配置失败, agentId: %d", agentModel.Id)
}
// 并发删除代理会员配置
if len(configs) > 0 {
configFuncs := make([]func() error, len(configs))
for i, config := range configs {
configId := config.Id
configFuncs[i] = func() error {
return l.svcCtx.AgentMembershipUserConfigModel.Delete(tranCtx, session, configId)
}
}
if err := mr.Finish(configFuncs...); err != nil {
return errors.Wrapf(err, "删除代理会员配置失败")
}
}
// 3.3 删除代理钱包信息
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(tranCtx, agentModel.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查询代理钱包信息失败, agentId: %d", agentModel.Id)
}
if wallet != nil {
if err := l.svcCtx.AgentWalletModel.Delete(tranCtx, session, wallet.Id); err != nil {
return errors.Wrapf(err, "删除代理钱包信息失败, walletId: %d", wallet.Id)
}
}
// 3.4 删除代理关系信息
closureBuilder := l.svcCtx.AgentClosureModel.SelectBuilder().Where("ancestor_id = ? AND depth = ?", agentModel.Id, 1)
closures, err := l.svcCtx.AgentClosureModel.FindAll(tranCtx, closureBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查询代理关系信息失败, agentId: %d", agentModel.Id)
}
if len(closures) > 0 {
closureFuncs := make([]func() error, len(closures))
for i, closure := range closures {
closureId := closure.Id
closureFuncs[i] = func() error {
return l.svcCtx.AgentClosureModel.Delete(tranCtx, session, closureId)
}
}
if err := mr.Finish(closureFuncs...); err != nil {
return errors.Wrapf(err, "删除代理关系信息失败")
}
}
}
// 4. 查询并删除代理审核信息
auditBuilder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID)
audits, err := l.svcCtx.AgentAuditModel.FindAll(tranCtx, auditBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查询代理审核信息失败, userId: %d", userID)
}
// 并发删除代理审核信息
if len(audits) > 0 {
auditFuncs := make([]func() error, len(audits))
for i, audit := range audits {
auditId := audit.Id
auditFuncs[i] = func() error {
return l.svcCtx.AgentAuditModel.Delete(tranCtx, session, auditId)
}
}
if err := mr.Finish(auditFuncs...); err != nil {
return errors.Wrapf(err, "删除代理审核信息失败")
}
}
// 5. 删除用户查询记录
queryBuilder := l.svcCtx.QueryModel.SelectBuilder().Where("user_id = ?", userID) queryBuilder := l.svcCtx.QueryModel.SelectBuilder().Where("user_id = ?", userID)
queries, err := l.svcCtx.QueryModel.FindAll(tranCtx, queryBuilder, "") queries, err := l.svcCtx.QueryModel.FindAll(tranCtx, queryBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) { if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查询用户查询记录失败, userId: %d", userID) return pkgerrors.Wrapf(err, "查询用户查询记录失败")
} }
for _, q := range queries {
if len(queries) > 0 { if err := l.svcCtx.QueryModel.Delete(tranCtx, session, q.Id); err != nil {
queryFuncs := make([]func() error, len(queries)) return pkgerrors.Wrapf(err, "删除查询记录失败 id=%d", q.Id)
for i, query := range queries {
queryId := query.Id
queryFuncs[i] = func() error {
return l.svcCtx.QueryModel.Delete(tranCtx, session, queryId)
}
}
if err := mr.Finish(queryFuncs...); err != nil {
return errors.Wrapf(err, "删除用户查询记录失败")
} }
} }
// 6. 删除用户订单记录 if agentModel != nil {
orderBuilder := l.svcCtx.OrderModel.SelectBuilder().Where("user_id = ?", userID) ag, err := l.svcCtx.AgentModel.FindOne(tranCtx, agentModel.Id)
orders, err := l.svcCtx.OrderModel.FindAll(tranCtx, orderBuilder, "") if err != nil {
if err != nil && !errors.Is(err, model.ErrNotFound) { return err
return errors.Wrapf(err, "查询用户订单记录失败, userId: %d", userID)
}
if len(orders) > 0 {
orderFuncs := make([]func() error, len(orders))
for i, order := range orders {
orderId := order.Id
orderFuncs[i] = func() error {
return l.svcCtx.OrderModel.Delete(tranCtx, session, orderId)
}
} }
ag.Mobile = placeholderEnc
if err := mr.Finish(orderFuncs...); err != nil { ag.WechatId = sql.NullString{Valid: false}
return errors.Wrapf(err, "删除用户订单记录失败") if err := l.svcCtx.AgentModel.UpdateWithVersion(tranCtx, session, ag); err != nil {
return pkgerrors.Wrapf(err, "更新代理占位信息失败")
} }
} }
// 7. 删除代理订单信息
agentOrderBuilder := l.svcCtx.AgentOrderModel.SelectBuilder().Where("agent_id = ?", agentModel.Id)
agentOrders, err := l.svcCtx.AgentOrderModel.FindAll(tranCtx, agentOrderBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查询代理订单信息失败, agentId: %d, err: %v", agentModel.Id, err)
}
if len(agentOrders) > 0 {
agentOrderFuncs := make([]func() error, len(agentOrders))
for i, agentOrder := range agentOrders {
agentOrderId := agentOrder.Id
agentOrderFuncs[i] = func() error {
return l.svcCtx.AgentOrderModel.Delete(tranCtx, session, agentOrderId)
}
}
if err := mr.Finish(agentOrderFuncs...); err != nil {
return errors.Wrapf(err, "删除代理订单信息失败")
}
}
return nil return nil
}) })
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户注销失败%v", err) var ce *xerr.CodeError
if errors.As(err, &ce) {
return ce
}
return pkgerrors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户注销失败: %v", err)
} }
return nil return nil

View File

@@ -50,6 +50,9 @@ func (l *GetTokenLogic) GetToken() (resp *types.MobileCodeLoginResp, err error)
if user.Disable == 1 { if user.Disable == 1 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁")
} }
if user.CancelledAt.Valid {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_CANCELLED), "账号已注销")
}
} }
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, claims.UserId, claims.UserType) token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, claims.UserId, claims.UserType)
if err != nil { if err != nil {

View File

@@ -68,6 +68,9 @@ func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (r
if user.Disable == 1 { if user.Disable == 1 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁")
} }
if user.CancelledAt.Valid {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_CANCELLED), "账号已注销")
}
userID = user.Id userID = user.Id
} }
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)

View File

@@ -57,6 +57,9 @@ func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthRe
if user.Disable == 1 { if user.Disable == 1 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁")
} }
if user.CancelledAt.Valid {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_CANCELLED), "账号已注销")
}
userID = userAuth.UserId userID = userAuth.UserId
userType = model.UserTypeNormal userType = model.UserTypeNormal
} else { } else {

View File

@@ -56,6 +56,9 @@ func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMi
if user.Disable == 1 { if user.Disable == 1 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁") return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁")
} }
if user.CancelledAt.Valid {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_CANCELLED), "账号已注销")
}
userID = userAuth.UserId userID = userAuth.UserId
userType = model.UserTypeNormal userType = model.UserTypeNormal
} else { } else {

View File

@@ -44,6 +44,10 @@ func (m *UserAuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.Handl
m.writeErrorResponse(w, http.StatusForbidden, xerr.NewErrCode(xerr.USER_DISABLED)) m.writeErrorResponse(w, http.StatusForbidden, xerr.NewErrCode(xerr.USER_DISABLED))
return return
} }
if user.CancelledAt.Valid {
m.writeErrorResponse(w, http.StatusForbidden, xerr.NewErrCode(xerr.USER_CANCELLED))
return
}
next(w, r) next(w, r)
} }
} }

View File

@@ -2,10 +2,16 @@ package service
import ( import (
"context" "context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
"time" "time"
"bdrp-server/app/main/api/internal/config" "bdrp-server/app/main/api/internal/config"
"bdrp-server/app/main/model" "bdrp-server/app/main/model"
"bdrp-server/common/ctxdata" "bdrp-server/common/ctxdata"
@@ -151,11 +157,23 @@ func newWechatPayServiceWithWxPayPubKey(c config.Config, userAuthModel model.Use
} }
} }
// CreateWechatAppOrder 创建微信APP支付订单 // randomNonceWechat 生成微信调起支付用的随机串
func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount float64, description string, outTradeNo string) (string, error) { func randomNonceWechat(n int) (string, error) {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
return "", err
}
for i := range b {
b[i] = letters[int(b[i])%len(letters)]
}
return string(b), nil
}
// CreateWechatAppOrder 创建微信 APP 支付:统一下单后按 V3「APP 调起支付」规则签名,供原生 SDK / uni.requestPayment(wxpay) 使用
func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount float64, description string, outTradeNo string) (map[string]string, error) {
totalAmount := lzUtils.ToWechatAmount(amount) totalAmount := lzUtils.ToWechatAmount(amount)
// 构建支付请求参数
payRequest := app.PrepayRequest{ payRequest := app.PrepayRequest{
Appid: core.String(w.config.Wxpay.AppID), Appid: core.String(w.config.Wxpay.AppID),
Mchid: core.String(w.config.Wxpay.MchID), Mchid: core.String(w.config.Wxpay.MchID),
@@ -167,17 +185,41 @@ func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount floa
}, },
} }
// 初始化 AppApiService
svc := app.AppApiService{Client: w.wechatClient} svc := app.AppApiService{Client: w.wechatClient}
// 发起预支付请求
resp, result, err := svc.Prepay(ctx, payRequest) resp, result, err := svc.Prepay(ctx, payRequest)
if err != nil { if err != nil {
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) return nil, fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
} }
// 返回预支付交易会话标识 prepayID := *resp.PrepayId
return *resp.PrepayId, nil mchPrivateKey, err := utils.LoadPrivateKeyWithPath(w.config.Wxpay.MchPrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("加载商户私钥失败: %w", err)
}
ts := strconv.FormatInt(time.Now().Unix(), 10)
nonceStr, err := randomNonceWechat(32)
if err != nil {
return nil, err
}
appID := w.config.Wxpay.AppID
msg := fmt.Sprintf("%s\n%s\n%s\n%s\n", appID, ts, nonceStr, prepayID)
sum := sha256.Sum256([]byte(msg))
sig, err := rsa.SignPKCS1v15(rand.Reader, mchPrivateKey, crypto.SHA256, sum[:])
if err != nil {
return nil, fmt.Errorf("APP 调起支付签名失败: %w", err)
}
return map[string]string{
"appid": appID,
"partnerid": w.config.Wxpay.MchID,
"prepayid": prepayID,
"package": "Sign=WXPay",
"noncestr": nonceStr,
"timestamp": ts,
"sign": base64.StdEncoding.EncodeToString(sig),
}, nil
} }
// CreateWechatMiniProgramOrder 创建微信小程序支付订单 // CreateWechatMiniProgramOrder 创建微信小程序支付订单

View File

@@ -1282,6 +1282,7 @@ type AgentSubordinateList struct {
TotalOrders int64 `json:"total_orders"` // 总单量 TotalOrders int64 `json:"total_orders"` // 总单量
TotalEarnings float64 `json:"total_earnings"` // 总金额 TotalEarnings float64 `json:"total_earnings"` // 总金额
TotalContribution float64 `json:"total_contribution"` // 总贡献 TotalContribution float64 `json:"total_contribution"` // 总贡献
AccountCancelled bool `json:"account_cancelled"` // 账号是否已注销
} }
type AgentWalletTransactionListItem struct { type AgentWalletTransactionListItem struct {
@@ -1346,6 +1347,10 @@ type BindMobileResp struct {
RefreshAfter int64 `json:"refreshAfter"` RefreshAfter int64 `json:"refreshAfter"`
} }
type CancelOutReq struct {
Code string `json:"code" validate:"required"`
}
type Commission struct { type Commission struct {
OrderId string `json:"order_id"` // 订单号 OrderId string `json:"order_id"` // 订单号
ProductName string `json:"product_name"` ProductName string `json:"product_name"`
@@ -2231,5 +2236,5 @@ type GetAppVersionResp struct {
type SendSmsReq struct { type SendSmsReq struct {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
CaptchaVerifyParam string `json:"captchaVerifyParam,optional,omitempty"` CaptchaVerifyParam string `json:"captchaVerifyParam,optional,omitempty"`
ActionType string `json:"actionType,optional" validate:"oneof=login register query agentApply realName bindMobile"` ActionType string `json:"actionType,optional" validate:"oneof=login register query agentApply realName bindMobile cancelAccount"`
} }

View File

@@ -67,8 +67,9 @@ type (
Password sql.NullString `db:"password"` Password sql.NullString `db:"password"`
Nickname sql.NullString `db:"nickname"` Nickname sql.NullString `db:"nickname"`
Info string `db:"info"` Info string `db:"info"`
Inside int64 `db:"inside"` Inside int64 `db:"inside"`
Disable int64 `db:"disable"` // 0可用 1禁用 Disable int64 `db:"disable"` // 0可用 1禁用
CancelledAt sql.NullTime `db:"cancelled_at"` // 账号注销时间,未注销为 NULL
} }
) )
@@ -84,11 +85,11 @@ func (m *defaultUserModel) Insert(ctx context.Context, session sqlx.Session, dat
bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, data.Id) bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, data.Id)
bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile) bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile)
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, userRowsExpectAutoSet) query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, userRowsExpectAutoSet)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable) return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable, data.CancelledAt)
} }
return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable) return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable, data.CancelledAt)
}, bdrpUserIdKey, bdrpUserMobileKey) }, bdrpUserIdKey, bdrpUserMobileKey)
} }
@@ -139,9 +140,9 @@ func (m *defaultUserModel) Update(ctx context.Context, session sqlx.Session, new
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id) return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.CancelledAt, newData.Id)
} }
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id) return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.CancelledAt, newData.Id)
}, bdrpUserIdKey, bdrpUserMobileKey) }, bdrpUserIdKey, bdrpUserMobileKey)
} }
@@ -162,9 +163,9 @@ func (m *defaultUserModel) UpdateWithVersion(ctx context.Context, session sqlx.S
sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, userRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, userRowsWithPlaceHolder)
if session != nil { if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id, oldVersion) return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.CancelledAt, newData.Id, oldVersion)
} }
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.Id, oldVersion) return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.CancelledAt, newData.Id, oldVersion)
}, bdrpUserIdKey, bdrpUserMobileKey) }, bdrpUserIdKey, bdrpUserMobileKey)
if err != nil { if err != nil {
return err return err

View File

@@ -18,6 +18,7 @@ const USER_NOT_FOUND uint32 = 100009
const USER_NEED_BIND_MOBILE uint32 = 100010 const USER_NEED_BIND_MOBILE uint32 = 100010
const USER_DISABLED uint32 = 100011 // 账号已被封禁 const USER_DISABLED uint32 = 100011 // 账号已被封禁
const USER_TEMP_INVALID uint32 = 100012 const USER_TEMP_INVALID uint32 = 100012
const USER_CANCELLED uint32 = 100013 // 账号已注销
const LOGIN_FAILED uint32 = 200001 const LOGIN_FAILED uint32 = 200001
const LOGIC_QUERY_WAIT uint32 = 200002 const LOGIC_QUERY_WAIT uint32 = 200002

View File

@@ -15,6 +15,7 @@ func init() {
message[USER_NEED_BIND_MOBILE] = "请先绑定手机号" message[USER_NEED_BIND_MOBILE] = "请先绑定手机号"
message[USER_DISABLED] = "账号已被封禁" message[USER_DISABLED] = "账号已被封禁"
message[USER_TEMP_INVALID] = "用户状态异常" message[USER_TEMP_INVALID] = "用户状态异常"
message[USER_CANCELLED] = "账号已注销"
} }
func MapErrMsg(errcode uint32) string { func MapErrMsg(errcode uint32) string {

View File

@@ -0,0 +1,27 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ UserModel = (*customUserModel)(nil)
type (
// UserModel is an interface to be customized, add more methods here,
// and implement the added methods in customUserModel.
UserModel interface {
userModel
}
customUserModel struct {
*defaultUserModel
}
)
// NewUserModel returns a model for the database table.
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) UserModel {
return &customUserModel{
defaultUserModel: newUserModel(conn, c),
}
}

View File

@@ -0,0 +1,412 @@
// Code generated by goctl. DO NOT EDIT!
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"bdrp-server/common/globalkey"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
userFieldNames = builder.RawFieldNames(&User{})
userRows = strings.Join(userFieldNames, ",")
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), ",")
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"
cacheBdrpUserIdPrefix = "cache:bdrp:user:id:"
cacheBdrpUserMobilePrefix = "cache:bdrp:user:mobile:"
)
type (
userModel interface {
Insert(ctx context.Context, session sqlx.Session, data *User) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*User, error)
FindOneByMobile(ctx context.Context, mobile sql.NullString) (*User, error)
Update(ctx context.Context, session sqlx.Session, data *User) (sql.Result, error)
UpdateWithVersion(ctx context.Context, session sqlx.Session, data *User) error
Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error
SelectBuilder() squirrel.SelectBuilder
DeleteSoft(ctx context.Context, session sqlx.Session, data *User) error
FindSum(ctx context.Context, sumBuilder squirrel.SelectBuilder, field string) (float64, error)
FindCount(ctx context.Context, countBuilder squirrel.SelectBuilder, field string) (int64, error)
FindAll(ctx context.Context, rowBuilder squirrel.SelectBuilder, orderBy string) ([]*User, error)
FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, error)
FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, int64, error)
FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*User, error)
FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*User, error)
Delete(ctx context.Context, session sqlx.Session, id int64) error
}
defaultUserModel struct {
sqlc.CachedConn
table string
}
User struct {
Id int64 `db:"id"`
CreateTime time.Time `db:"create_time"`
UpdateTime time.Time `db:"update_time"`
DeleteTime sql.NullTime `db:"delete_time"` // 删除时间
DelState int64 `db:"del_state"`
Version int64 `db:"version"` // 版本号
Mobile sql.NullString `db:"mobile"`
Password sql.NullString `db:"password"`
Nickname sql.NullString `db:"nickname"`
Info string `db:"info"`
Inside int64 `db:"inside"`
Disable int64 `db:"disable"` // 0可用 1禁用
CancelledAt sql.NullTime `db:"cancelled_at"` // 账号注销时间
}
)
func newUserModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultUserModel {
return &defaultUserModel{
CachedConn: sqlc.NewConn(conn, c),
table: "`user`",
}
}
func (m *defaultUserModel) Insert(ctx context.Context, session sqlx.Session, data *User) (sql.Result, error) {
data.DelState = globalkey.DelStateNo
bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, data.Id)
bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile)
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, userRowsExpectAutoSet)
if session != nil {
return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable, data.CancelledAt)
}
return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info, data.Inside, data.Disable, data.CancelledAt)
}, bdrpUserIdKey, bdrpUserMobileKey)
}
func (m *defaultUserModel) FindOne(ctx context.Context, id int64) (*User, error) {
bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, id)
var resp User
err := m.QueryRowCtx(ctx, &resp, bdrpUserIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", userRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id, globalkey.DelStateNo)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserModel) FindOneByMobile(ctx context.Context, mobile sql.NullString) (*User, error) {
bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, mobile)
var resp User
err := m.QueryRowIndexCtx(ctx, &resp, bdrpUserMobileKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := fmt.Sprintf("select %s from %s where `mobile` = ? and del_state = ? limit 1", userRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, mobile, globalkey.DelStateNo); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserModel) Update(ctx context.Context, session sqlx.Session, newData *User) (sql.Result, error) {
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return nil, err
}
bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, data.Id)
bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile)
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRowsWithPlaceHolder)
if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.CancelledAt, newData.Id)
}
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.CancelledAt, newData.Id)
}, bdrpUserIdKey, bdrpUserMobileKey)
}
func (m *defaultUserModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *User) error {
oldVersion := newData.Version
newData.Version += 1
var sqlResult sql.Result
var err error
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return err
}
bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, data.Id)
bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile)
sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, userRowsWithPlaceHolder)
if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.CancelledAt, newData.Id, oldVersion)
}
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Inside, newData.Disable, newData.CancelledAt, newData.Id, oldVersion)
}, bdrpUserIdKey, bdrpUserMobileKey)
if err != nil {
return err
}
updateCount, err := sqlResult.RowsAffected()
if err != nil {
return err
}
if updateCount == 0 {
return ErrNoRowsUpdate
}
return nil
}
func (m *defaultUserModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *User) error {
data.DelState = globalkey.DelStateYes
data.DeleteTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := m.UpdateWithVersion(ctx, session, data); err != nil {
return errors.Wrapf(errors.New("delete soft failed "), "UserModel delete err : %+v", err)
}
return nil
}
func (m *defaultUserModel) FindSum(ctx context.Context, builder squirrel.SelectBuilder, field string) (float64, error) {
if len(field) == 0 {
return 0, errors.Wrapf(errors.New("FindSum Least One Field"), "FindSum Least One Field")
}
builder = builder.Columns("IFNULL(SUM(" + field + "),0)")
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
if err != nil {
return 0, err
}
var resp float64
err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return 0, err
}
}
func (m *defaultUserModel) FindCount(ctx context.Context, builder squirrel.SelectBuilder, field string) (int64, error) {
if len(field) == 0 {
return 0, errors.Wrapf(errors.New("FindCount Least One Field"), "FindCount Least One Field")
}
builder = builder.Columns("COUNT(" + field + ")")
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
if err != nil {
return 0, err
}
var resp int64
err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return 0, err
}
}
func (m *defaultUserModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*User, error) {
builder = builder.Columns(userRows)
if orderBy == "" {
builder = builder.OrderBy("id DESC")
} else {
builder = builder.OrderBy(orderBy)
}
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
if err != nil {
return nil, err
}
var resp []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, error) {
builder = builder.Columns(userRows)
if orderBy == "" {
builder = builder.OrderBy("id DESC")
} else {
builder = builder.OrderBy(orderBy)
}
if page < 1 {
page = 1
}
offset := (page - 1) * pageSize
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql()
if err != nil {
return nil, err
}
var resp []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, int64, error) {
total, err := m.FindCount(ctx, builder, "id")
if err != nil {
return nil, 0, err
}
builder = builder.Columns(userRows)
if orderBy == "" {
builder = builder.OrderBy("id DESC")
} else {
builder = builder.OrderBy(orderBy)
}
if page < 1 {
page = 1
}
offset := (page - 1) * pageSize
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql()
if err != nil {
return nil, total, err
}
var resp []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, total, nil
default:
return nil, total, err
}
}
func (m *defaultUserModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*User, error) {
builder = builder.Columns(userRows)
if preMinId > 0 {
builder = builder.Where(" id < ? ", preMinId)
}
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id DESC").Limit(uint64(pageSize)).ToSql()
if err != nil {
return nil, err
}
var resp []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*User, error) {
builder = builder.Columns(userRows)
if preMaxId > 0 {
builder = builder.Where(" id > ? ", preMaxId)
}
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id ASC").Limit(uint64(pageSize)).ToSql()
if err != nil {
return nil, err
}
var resp []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserModel) Trans(ctx context.Context, fn func(ctx context.Context, session sqlx.Session) error) error {
return m.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
return fn(ctx, session)
})
}
func (m *defaultUserModel) SelectBuilder() squirrel.SelectBuilder {
return squirrel.Select().From(m.table)
}
func (m *defaultUserModel) Delete(ctx context.Context, session sqlx.Session, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
bdrpUserIdKey := fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, id)
bdrpUserMobileKey := fmt.Sprintf("%s%v", cacheBdrpUserMobilePrefix, data.Mobile)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
if session != nil {
return session.ExecCtx(ctx, query, id)
}
return conn.ExecCtx(ctx, query, id)
}, bdrpUserIdKey, bdrpUserMobileKey)
return err
}
func (m *defaultUserModel) formatPrimary(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheBdrpUserIdPrefix, primary)
}
func (m *defaultUserModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", userRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo)
}
func (m *defaultUserModel) tableName() string {
return m.table
}

View File

@@ -0,0 +1,3 @@
-- 账号注销标记注销时间NULL 表示未注销)
ALTER TABLE `user`
ADD COLUMN `cancelled_at` DATETIME NULL DEFAULT NULL COMMENT '账号注销时间' AFTER `disable`;