feat(account): 添加删除所有过期无效账号接口

- 新增DeleteExpiredAccountsReq和DeleteExpiredAccountsRes结构体
- 实现DeleteAllExpiredAccounts函数,通过状态删除过期账号
- 在service接口中加入DeleteAllExpiredAccounts方法声明
- 将定时任务注册中添加CronExpiredTokensCode和CronCardBindingTask任务调用
- 删除了原有的Token自动登录及验证码发送相关复杂逻辑,改为简单清理状态的实现
- 修正Token删除逻辑为直接删除数据库记录
- 调整Token状态校验逻辑,允许验证码已发送和验证失败状态输入验证码
- 修正CreateTokenReq中手机号验证规则为phone格式验证
This commit is contained in:
danial
2025-12-09 20:13:03 +08:00
parent e3956cbe35
commit 8112cf92f4
7 changed files with 39 additions and 170 deletions

View File

@@ -110,3 +110,13 @@ type AccountStatisticsRes struct {
OrderCount int `json:"orderCount" description:"订单数"`
} `json:"recentTrend" description:"近期订单趋势最近7天"`
}
// DeleteExpiredAccountsReq 删除所有过期无效账号请求
type DeleteExpiredAccountsReq struct {
g.Meta `path:"/jd-v2/account/delete-expired" tags:"JD V2 Account" method:"delete" summary:"删除所有过期无效账号"`
}
type DeleteExpiredAccountsRes struct {
Message string `json:"message" description:"操作结果信息"`
DeletedCount int64 `json:"deletedCount" description:"删除的账号数量"`
}

View File

@@ -17,7 +17,7 @@ import (
type CreateTokenReq struct {
g.Meta `path:"/token/create" tags:"JD V2 Token Management" method:"post" summary:"创建 Token"`
Name string `json:"name" v:"required" description:"Token名称"`
Phone string `json:"phone" v:"required" description:"绑定的手机号"`
Phone string `json:"phone" v:"phone" description:"绑定的手机号"`
Remark string `json:"remark" description:"备注"`
RechargeLimitAmount float64 `json:"rechargeLimitAmount" v:"required" description:"充值金额限制"`
RechargeLimitCount int `json:"rechargeLimitCount" v:"required" description:"充值次数限制"`

View File

@@ -112,6 +112,19 @@ func (s *sCamelOil) DeleteAccount(ctx context.Context, accountId int64) (err err
return nil
}
// DeleteAllExpiredAccounts 删除所有过期无效的账号
func (s *sCamelOil) DeleteAllExpiredAccounts(ctx context.Context) (deletedCount int64, err error) {
m := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1())
// 查询符合条件的过期账号
result, err := m.Where(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusInvalid).Delete()
if err != nil {
return 0, gerror.Wrap(err, "删除过期账号失败")
}
deletedCount, _ = result.RowsAffected()
return deletedCount, nil
}
// ListAccounts 获取账号列表
func (s *sCamelOil) ListAccounts(ctx context.Context, status int, current, pageSize int) (accounts []*entity.V1CamelOilAccount, total int, err error) {
m := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1())

View File

@@ -412,160 +412,10 @@ func (s *sCamelOil) CronCleanExpiredPrefetchOrders(ctx context.Context) (cleaned
return cleanedCount, nil
}
// CronTokenLoginTask Token 自动登录定时任务 - 由cron调度器定期调用
// 流程:检查需要发送验证码或重新登录的 Token自动处理登录流程
func (s *sCamelOil) CronTokenLoginTask(ctx context.Context) error {
glog.Info(ctx, "开始执行 Token 自动登录任务")
// 1. 处理需要发送验证码的 Token登录状态为 3验证码待获取
needCodeTokens, err := s.GetTokensNeedingCode(ctx)
if err != nil {
glog.Errorf(ctx, "查询需要验证码的 Token 失败: %v", err)
return err
}
if len(needCodeTokens) > 0 {
glog.Infof(ctx, "查询到 %d 个需要验证码的 Token", len(needCodeTokens))
codeSuccessCount := 0
codeFailCount := 0
for _, token := range needCodeTokens {
// 检查是否需要重试(避免频繁请求)
if token.UpdatedAt != nil && gtime.Now().Sub(token.UpdatedAt) < time.Minute*5 {
continue
}
err := s.ResendVerificationCode(ctx, &model.CamelOilTokenResendCodeInput{
TokenId: token.Id,
})
if err != nil {
glog.Errorf(ctx, "发送验证码失败Token ID: %d, 错误: %v", token.Id, err)
codeFailCount++
} else {
codeSuccessCount++
}
}
glog.Infof(ctx, "验证码发送完成: 成功=%d, 失败=%d", codeSuccessCount, codeFailCount)
}
// 2. 处理登录失败的 Token登录状态为 2登录失败
failedTokens, err := s.GetFailedLoginTokens(ctx)
if err != nil {
glog.Errorf(ctx, "查询登录失败的 Token 失败: %v", err)
return err
}
if len(failedTokens) > 0 {
glog.Infof(ctx, "查询到 %d 个登录失败的 Token", len(failedTokens))
retrySuccessCount := 0
retryFailCount := 0
for _, token := range failedTokens {
// 检查是否需要重试避免频繁重试间隔至少30分钟
if token.UpdatedAt != nil && gtime.Now().Sub(token.UpdatedAt) < time.Minute*30 {
continue
}
// 重置状态并发送验证码
err := s.ResendVerificationCode(ctx, &model.CamelOilTokenResendCodeInput{
TokenId: token.Id,
})
if err != nil {
glog.Errorf(ctx, "重试发送验证码失败Token ID: %d, 错误: %v", token.Id, err)
retryFailCount++
} else {
retrySuccessCount++
}
}
glog.Infof(ctx, "登录失败 Token 重试完成: 成功=%d, 失败=%d", retrySuccessCount, retryFailCount)
}
// 3. 清理过期的 Token登录 token 过期的)
expiredCount, err := s.CleanExpiredLoginTokens(ctx)
if err != nil {
glog.Errorf(ctx, "清理过期登录 Token 失败: %v", err)
} else if expiredCount > 0 {
glog.Infof(ctx, "清理过期登录 Token 完成,清理数量: %d", expiredCount)
}
glog.Info(ctx, "Token 自动登录任务完成")
return nil
}
// GetTokensNeedingCode 获取需要发送验证码的 Token状态为待验证码
func (s *sCamelOil) GetTokensNeedingCode(ctx context.Context) ([]*entity.V1CamelOilToken, error) {
m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1())
var tokens []*entity.V1CamelOilToken
// 获取状态为待验证码的 Token
err := m.Where(dao.V1CamelOilToken.Columns().Status, int(consts.CamelOilTokenStatusPendingVerification)).
Scan(&tokens)
if err != nil {
return nil, gerror.Wrap(err, "查询需要验证码的 Token 失败")
}
return tokens, nil
}
// GetFailedLoginTokens 获取登录失败的 Token状态为登录失败
func (s *sCamelOil) GetFailedLoginTokens(ctx context.Context) ([]*entity.V1CamelOilToken, error) {
m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1())
var tokens []*entity.V1CamelOilToken
// 获取状态为登录失败的 Token
err := m.Where(dao.V1CamelOilToken.Columns().Status, int(consts.CamelOilTokenStatusLoginFailed)).
Scan(&tokens)
if err != nil {
return nil, gerror.Wrap(err, "查询登录失败的 Token 失败")
}
return tokens, nil
}
// CleanExpiredLoginTokens 清理登录 token 过期的 Token
func (s *sCamelOil) CleanExpiredLoginTokens(ctx context.Context) (cleanedCount int, err error) {
m := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1())
// 查询登录 token 已过期但状态仍为可用的 Token
var expiredTokens []*entity.V1CamelOilToken
err = m.Where(dao.V1CamelOilToken.Columns().Status, int(consts.CamelOilTokenStatusAvailable)).
WhereNotNull(dao.V1CamelOilToken.Columns().LoginTokenExpiresAt).
WhereLT(dao.V1CamelOilToken.Columns().LoginTokenExpiresAt, gtime.Now()).
Scan(&expiredTokens)
if err != nil {
return 0, gerror.Wrap(err, "查询过期登录 Token 失败")
}
if len(expiredTokens) == 0 {
return 0, nil
}
// 将这些 Token 标记为需要重新登录
for _, token := range expiredTokens {
_, err = m.Where(dao.V1CamelOilToken.Columns().Id, token.Id).
Update(&do.V1CamelOilToken{
Status: int(consts.CamelOilTokenStatusPendingVerification), // 待验证码状态
LoginToken: "", // 清空登录 token
LoginTokenExpiresAt: nil, // 清空过期时间
UpdatedAt: gtime.Now(),
})
if err != nil {
glog.Warningf(ctx, "标记过期 Token 为待验证码失败ID=%d: %v", token.Id, err)
continue
}
cleanedCount++
}
return cleanedCount, nil
// CronExpiredTokensCode 获取需要发送验证码的 Token状态为待验证码
func (s *sCamelOil) CronExpiredTokensCode(ctx context.Context) error {
_, err := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()).Where(dao.V1CamelOilToken.Columns().Status, consts.CamelOilTokenStatusCodeSent).
WhereLT(dao.V1CamelOilToken.Columns().UpdatedAt, gtime.Now().Add(-gtime.M*5)).
Update(do.V1CamelOilToken{Status: consts.CamelOilTokenStatusVerificationFailed})
return err
}

View File

@@ -66,8 +66,6 @@ func (s *sCamelOil) CreateToken(ctx context.Context, req *model.CamelOilTokenCre
}
tokenId, _ = result.LastInsertId()
glog.Infof(ctx, "Token创建成功: tokenId=%d, tokenName=%s, phone=%s, 充值金额限制=%.2f, 充值次数限制=%d", tokenId, req.Name, req.Phone, req.RechargeLimitAmount, req.RechargeLimitCount)
return tokenId, nil
}
@@ -187,7 +185,6 @@ func (s *sCamelOil) UpdateTokenInfo(ctx context.Context, req *model.CamelOilToke
Remark: req.Remark,
RechargeLimitAmount: rechargeLimitAmountDecimal,
RechargeLimitCount: req.RechargeLimitCount,
UpdatedAt: gtime.Now(),
})
if err != nil {
@@ -223,10 +220,7 @@ func (s *sCamelOil) DeleteToken(ctx context.Context, req *model.CamelOilTokenDel
return gerror.NewCode(gcode.CodeNotAuthorized, "无权删除此Token")
}
_, err = m.Where(dao.V1CamelOilToken.Columns().Id, req.TokenId).
Update(&do.V1CamelOilToken{
DeletedAt: gtime.Now(),
})
_, err = m.Where(dao.V1CamelOilToken.Columns().Id, req.TokenId).Delete()
if err != nil {
return gerror.Wrap(err, "删除Token失败")
@@ -485,8 +479,8 @@ func (s *sCamelOil) InputVerificationCode(ctx context.Context, req *model.CamelO
return "", gerror.New("Token不存在")
}
// 检查 Token 状态,只有验证码已发送状态的 Token 才能输入验证码
if token.Status != int(consts.CamelOilTokenStatusCodeSent) || token.Status != int(consts.CamelOilTokenStatusVerificationFailed) {
// 检查 Token 状态,只有验证码已发送或验证失败的 Token 才能输入验证码
if token.Status != int(consts.CamelOilTokenStatusCodeSent) && token.Status != int(consts.CamelOilTokenStatusVerificationFailed) {
return "", gerror.New("Token状态不允许输入验证码")
}

View File

@@ -25,6 +25,8 @@ type (
UpdateAccount(ctx context.Context, accountId int64, remark string) (err error)
// DeleteAccount 删除账号(软删除)
DeleteAccount(ctx context.Context, accountId int64) (err error)
// DeleteAllExpiredAccounts 删除所有过期无效的账号
DeleteAllExpiredAccounts(ctx context.Context) (deletedCount int64, err error)
// ListAccounts 获取账号列表
ListAccounts(ctx context.Context, status int, current int, pageSize int) (accounts []*entity.V1CamelOilAccount, total int, err error)
// ListAccount 查询账号列表API版本
@@ -72,6 +74,8 @@ type (
CronCardBindingTask(ctx context.Context) error
// CronCleanExpiredPrefetchOrders 清理过期的预拉取订单
CronCleanExpiredPrefetchOrders(ctx context.Context) (cleanedCount int, err error)
// CronExpiredTokensCode 获取需要发送验证码的 Token状态为待验证码
CronExpiredTokensCode(ctx context.Context) error
// UpdateOrderStatus 更新订单状态并记录历史
UpdateOrderStatus(ctx context.Context, orderId int64, newStatus consts.CamelOilOrderStatus, operationType consts.CamelOilOrderChangeType, rawData string, description string) (err error)
// SubmitOrder 提交订单并返回支付宝支付链接

View File

@@ -92,16 +92,14 @@ func registerCamelOilTasks(ctx context.Context) {
_ = service.CamelOil().CronOrderPaymentCheckTask(ctx)
_ = service.CamelOil().ProcessPendingCallbacks(ctx)
_, _ = service.CamelOil().CronCleanExpiredPrefetchOrders(ctx)
_ = service.CamelOil().CronCardBindingTask(ctx)
_ = service.CamelOil().CronExpiredTokensCode(ctx)
}, "CamelOilAccountPrefetch")
_, _ = gcron.AddSingleton(ctx, "0 1 0 * * ?", func(ctx context.Context) {
_ = service.CamelOil().CronAccountDailyResetTask(ctx)
}, "CamelOilAccountDailyReset")
//_, _ = gcron.AddSingleton(ctx, "@every 60m", func(ctx context.Context) {
// _, _ = service.CamelOil().CronCleanExpiredPrefetchOrders(ctx)
//}, "CamelOilCleanExpiredPrefetchOrders")
_, _ = gcron.AddSingleton(ctx, "@every 1s", func(ctx context.Context) {
_ = service.CamelOil().CronPrefetchOrderSupplementTask(ctx)
}, "CamelOilPrefetchOrderSupplementOrders")