Compare commits
3 Commits
4aab0bcbf2
...
716e9f5971
| Author | SHA1 | Date | |
|---|---|---|---|
| 716e9f5971 | |||
| 28ac58ea18 | |||
| 8e61cb1a54 |
@@ -1776,16 +1776,32 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
|
|||||||
nextResetAt = packagepkg.CalculateNextResetTime(pkg.DataResetCycle, pkg.CalendarType, now, activatedAt)
|
nextResetAt = packagepkg.CalculateNextResetTime(pkg.DataResetCycle, pkg.CalendarType, now, activatedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// REALNAME-02: 后台囤货场景三维决策(卡类型 + 购买路径 + expiry_base)
|
// REALNAME-02: 后台囤货场景三维决策(卡类型 + 购买路径 + expiry_base + 实名状态)
|
||||||
// 注意:仅在 else 分支(立即激活)时才需要判断是否切换为等实名
|
// 注意:仅在 else 分支(立即激活)时才需要判断是否切换为等实名
|
||||||
if !hasActiveMain {
|
if !hasActiveMain {
|
||||||
// 查询卡类型(行业卡永远直接激活,不等实名)
|
// 查询卡类型(行业卡永远直接激活,不等实名)
|
||||||
|
// 同时查询载体当前实名状态:已实名则跳过 pending,直接激活
|
||||||
var cardCategory string
|
var cardCategory string
|
||||||
|
var currentlyRealnamed bool
|
||||||
if carrierType == "iot_card" {
|
if carrierType == "iot_card" {
|
||||||
var card model.IotCard
|
var card model.IotCard
|
||||||
if err := tx.Select("card_category").First(&card, carrierID).Error; err == nil {
|
if err := tx.Select("card_category", "real_name_status").First(&card, carrierID).Error; err == nil {
|
||||||
cardCategory = card.CardCategory
|
cardCategory = card.CardCategory
|
||||||
|
currentlyRealnamed = card.RealNameStatus == constants.RealNameStatusVerified
|
||||||
}
|
}
|
||||||
|
// err != nil 时 currentlyRealnamed 保守默认 false,不阻断购买流程
|
||||||
|
} else if carrierType == "device" {
|
||||||
|
// 子查询:统计该设备绑定的已实名卡数量(GORM 自动注入 deleted_at IS NULL)
|
||||||
|
var count int64
|
||||||
|
subQuery := tx.Model(&model.DeviceSimBinding{}).
|
||||||
|
Select("iot_card_id").
|
||||||
|
Where("device_id = ? AND bind_status = 1", carrierID)
|
||||||
|
if err := tx.Model(&model.IotCard{}).
|
||||||
|
Where("id IN (?) AND real_name_status = 1", subQuery).
|
||||||
|
Count(&count).Error; err == nil {
|
||||||
|
currentlyRealnamed = count > 0
|
||||||
|
}
|
||||||
|
// err != nil 时保守默认 false,不阻断购买流程
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否 C 端购买(C 端购买前已做实名前置检查,直接激活)
|
// 判断是否 C 端购买(C 端购买前已做实名前置检查,直接激活)
|
||||||
@@ -1794,12 +1810,18 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
|
|||||||
if cardCategory != "industry" && !isCEndPurchase {
|
if cardCategory != "industry" && !isCEndPurchase {
|
||||||
// 后台囤货路径:按 expiry_base 决定是否等实名
|
// 后台囤货路径:按 expiry_base 决定是否等实名
|
||||||
if pkg.ExpiryBase != "from_purchase" {
|
if pkg.ExpiryBase != "from_purchase" {
|
||||||
// from_activation(默认):等实名后激活
|
if currentlyRealnamed {
|
||||||
status = constants.PackageUsageStatusPending
|
s.logger.Info("购买时载体已实名,直接激活套餐",
|
||||||
pendingRealnameActivation = true
|
zap.String("carrier_type", carrierType),
|
||||||
activatedAt = time.Time{}
|
zap.Uint("carrier_id", carrierID))
|
||||||
expiresAt = time.Time{}
|
} else {
|
||||||
nextResetAt = nil
|
// from_activation(默认):未实名,等实名后激活
|
||||||
|
status = constants.PackageUsageStatusPending
|
||||||
|
pendingRealnameActivation = true
|
||||||
|
activatedAt = time.Time{}
|
||||||
|
expiresAt = time.Time{}
|
||||||
|
nextResetAt = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// from_purchase:立即激活,status 已为 Active,保持不变
|
// from_purchase:立即激活,status 已为 Active,保持不变
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ import (
|
|||||||
// 职责:调 Gateway 查实名状态 → 写 DB → 触发首次实名激活任务
|
// 职责:调 Gateway 查实名状态 → 写 DB → 触发首次实名激活任务
|
||||||
// 不做:停复机决策(实名状态 0→1 时通过 EvaluateAndAct 触发 not_realname 复机)
|
// 不做:停复机决策(实名状态 0→1 时通过 EvaluateAndAct 触发 not_realname 复机)
|
||||||
type PollingRealnameHandler struct {
|
type PollingRealnameHandler struct {
|
||||||
base *PollingBase
|
base *PollingBase
|
||||||
gateway *gateway.Client
|
gateway *gateway.Client
|
||||||
iotCardStore *postgres.IotCardStore
|
iotCardStore *postgres.IotCardStore
|
||||||
asynqClient *asynq.Client
|
asynqClient *asynq.Client
|
||||||
stopResumeSvc iot_card_svc.StopResumeServiceInterface
|
stopResumeSvc iot_card_svc.StopResumeServiceInterface
|
||||||
|
deviceSimBindingStore *postgres.DeviceSimBindingStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPollingRealnameHandler 创建实名检查任务处理器
|
// NewPollingRealnameHandler 创建实名检查任务处理器
|
||||||
@@ -32,13 +33,15 @@ func NewPollingRealnameHandler(
|
|||||||
iotCardStore *postgres.IotCardStore,
|
iotCardStore *postgres.IotCardStore,
|
||||||
asynqClient *asynq.Client,
|
asynqClient *asynq.Client,
|
||||||
stopResumeSvc iot_card_svc.StopResumeServiceInterface,
|
stopResumeSvc iot_card_svc.StopResumeServiceInterface,
|
||||||
|
deviceSimBindingStore *postgres.DeviceSimBindingStore,
|
||||||
) *PollingRealnameHandler {
|
) *PollingRealnameHandler {
|
||||||
return &PollingRealnameHandler{
|
return &PollingRealnameHandler{
|
||||||
base: base,
|
base: base,
|
||||||
gateway: gw,
|
gateway: gw,
|
||||||
iotCardStore: iotCardStore,
|
iotCardStore: iotCardStore,
|
||||||
asynqClient: asynqClient,
|
asynqClient: asynqClient,
|
||||||
stopResumeSvc: stopResumeSvc,
|
stopResumeSvc: stopResumeSvc,
|
||||||
|
deviceSimBindingStore: deviceSimBindingStore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +141,7 @@ func (h *PollingRealnameHandler) Handle(ctx context.Context, t *asynq.Task) erro
|
|||||||
if isFirstRealname {
|
if isFirstRealname {
|
||||||
// 0→1:首次实名,触发套餐激活并立即复机评估(不等待下一个 carddata/package 周期)
|
// 0→1:首次实名,触发套餐激活并立即复机评估(不等待下一个 carddata/package 周期)
|
||||||
h.triggerFirstRealnameActivation(ctx, cardID)
|
h.triggerFirstRealnameActivation(ctx, cardID)
|
||||||
|
h.triggerDeviceRealnameActivation(ctx, cardID)
|
||||||
if h.stopResumeSvc != nil {
|
if h.stopResumeSvc != nil {
|
||||||
freshCard, loadErr := h.iotCardStore.GetByID(ctx, cardID)
|
freshCard, loadErr := h.iotCardStore.GetByID(ctx, cardID)
|
||||||
if loadErr == nil {
|
if loadErr == nil {
|
||||||
@@ -192,3 +196,42 @@ func (h *PollingRealnameHandler) triggerFirstRealnameActivation(ctx context.Cont
|
|||||||
h.base.logger.Warn("提交首次实名激活任务失败", zap.Uint("card_id", cardID), zap.Error(err))
|
h.base.logger.Warn("提交首次实名激活任务失败", zap.Uint("card_id", cardID), zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// triggerDeviceRealnameActivation 卡首次实名后,触发其所属设备的设备级套餐激活任务
|
||||||
|
// 若卡不属于任何设备(独立卡),静默跳过
|
||||||
|
func (h *PollingRealnameHandler) triggerDeviceRealnameActivation(ctx context.Context, cardID uint) {
|
||||||
|
if h.asynqClient == nil || h.deviceSimBindingStore == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding, err := h.deviceSimBindingStore.GetActiveBindingByCardID(ctx, cardID)
|
||||||
|
if err != nil {
|
||||||
|
if isNotFound(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.base.logger.Warn("查询卡绑定设备失败,跳过设备级套餐激活",
|
||||||
|
zap.Uint("card_id", cardID), zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
deviceID := binding.DeviceID
|
||||||
|
payload := map[string]any{
|
||||||
|
"carrier_type": "device",
|
||||||
|
"carrier_id": deviceID,
|
||||||
|
"activation_type": "realname",
|
||||||
|
"timestamp": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
payloadBytes, err := sonic.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
h.base.logger.Warn("序列化设备实名激活任务载荷失败",
|
||||||
|
zap.Uint("card_id", cardID), zap.Uint("device_id", deviceID), zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
task := asynq.NewTask(constants.TaskTypePackageFirstActivation, payloadBytes,
|
||||||
|
asynq.MaxRetry(3),
|
||||||
|
asynq.Timeout(30*time.Second),
|
||||||
|
asynq.Queue(constants.QueueDefault),
|
||||||
|
)
|
||||||
|
if _, err := h.asynqClient.EnqueueContext(ctx, task); err != nil {
|
||||||
|
h.base.logger.Warn("提交设备实名激活任务失败",
|
||||||
|
zap.Uint("card_id", cardID), zap.Uint("device_id", deviceID), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -104,44 +104,9 @@ ResumeCardIfStopped 不触发
|
|||||||
|
|
||||||
**存量问题修复**(代码部署后手动执行):
|
**存量问题修复**(代码部署后手动执行):
|
||||||
|
|
||||||
```sql
|
**当前生产环境确认**:仅 `id=1`(device_id=2,`natural_month` 12 个月,`data_reset_cycle=monthly`)受影响。
|
||||||
-- 找出所有已实名载体下仍处于 pending_realname_activation=true 的套餐
|
|
||||||
-- 设备级
|
|
||||||
UPDATE tb_package_usage pu
|
|
||||||
SET
|
|
||||||
status = 1,
|
|
||||||
pending_realname_activation = false,
|
|
||||||
activated_at = NOW(),
|
|
||||||
expires_at = NOW() + INTERVAL '12 months', -- 按实际套餐 duration 调整
|
|
||||||
updated_at = NOW()
|
|
||||||
WHERE pu.status = 0
|
|
||||||
AND pu.pending_realname_activation = true
|
|
||||||
AND pu.device_id > 0
|
|
||||||
AND pu.device_id IN (
|
|
||||||
SELECT DISTINCT dsb.device_id
|
|
||||||
FROM tb_device_sim_binding dsb
|
|
||||||
JOIN tb_iot_card ic ON ic.id = dsb.iot_card_id
|
|
||||||
WHERE dsb.bind_status = 1 AND dsb.deleted_at IS NULL
|
|
||||||
AND ic.real_name_status = 1 AND ic.deleted_at IS NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 卡级
|
**关键约束**:`expires_at` 必须匹配 Go 层 `CalculateExpiryTime` 的逻辑(自然月套餐 = 目标月末 23:59:59,不是简单的 `+N months`),同时需一并写入 `next_reset_at`。具体可执行 SQL 见 `tasks.md` 任务 4.2(三步操作:确认范围 → BEGIN/UPDATE/COMMIT → 验证)。
|
||||||
UPDATE tb_package_usage pu
|
|
||||||
SET
|
|
||||||
status = 1,
|
|
||||||
pending_realname_activation = false,
|
|
||||||
activated_at = NOW(),
|
|
||||||
expires_at = NOW() + INTERVAL '12 months', -- 按实际套餐 duration 调整
|
|
||||||
updated_at = NOW()
|
|
||||||
WHERE pu.status = 0
|
|
||||||
AND pu.pending_realname_activation = true
|
|
||||||
AND pu.iot_card_id > 0
|
|
||||||
AND pu.iot_card_id IN (
|
|
||||||
SELECT id FROM tb_iot_card WHERE real_name_status = 1 AND deleted_at IS NULL
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
> ⚠️ 以上 SQL 中 `expires_at` 使用了简化的固定值,生产执行前需根据 `CalculateExpiryTime` 逻辑按每条记录的套餐配置计算实际到期时间。
|
|
||||||
|
|
||||||
**回滚**:代码变更纯逻辑,无 schema 变更,直接回滚二进制即可。
|
**回滚**:代码变更纯逻辑,无 schema 变更,直接回滚二进制即可。
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,89 @@
|
|||||||
## 1. 修复购买时实名状态检查(order/service.go)
|
## 1. 修复购买时实名状态检查(order/service.go)
|
||||||
|
|
||||||
- [ ] 1.1 在 `activateMainPackage` 的 REALNAME-02 决策块中,扩展卡信息查询:将 `tx.Select("card_category")` 改为 `tx.Select("card_category", "real_name_status")`,并从结果读取 `currentlyRealnamed` 标志
|
- [x] 1.1 在 `activateMainPackage` 的 REALNAME-02 决策块中,扩展卡信息查询:将 `tx.Select("card_category")` 改为 `tx.Select("card_category", "real_name_status")`,并从结果读取 `currentlyRealnamed` 标志
|
||||||
- [ ] 1.2 在 REALNAME-02 决策块中,新增 `device` 载体的实名状态检查:当 `carrierType == "device"` 时,通过 `tb_device_sim_binding`(`bind_status=1, deleted_at IS NULL`)子查询统计绑定卡中 `real_name_status=1` 的数量,`count > 0` 则 `currentlyRealnamed=true`
|
- [x] 1.2 在 REALNAME-02 决策块中,新增 `device` 载体的实名状态检查:当 `carrierType == "device"` 时,通过 GORM 子查询统计绑定卡(`bind_status=1`)中 `real_name_status=1` 的数量,`count > 0` 则 `currentlyRealnamed=true`;注意:使用 GORM 查询时软删除过滤(`deleted_at IS NULL`)由 GORM 自动注入,**不需要**手动添加 `.Where("deleted_at IS NULL")`
|
||||||
- [ ] 1.3 修改 `expiry_base != "from_purchase"` 分支的决策逻辑:若 `currentlyRealnamed=true` 则保持 `status=Active`(不切换为 pending),记录 Info 日志"购买时载体已实名,直接激活套餐";若 `currentlyRealnamed=false` 则保持原有 `pending_realname_activation=true` 逻辑
|
- [x] 1.3 修改 `expiry_base != "from_purchase"` 分支的决策逻辑:若 `currentlyRealnamed=true` 则保持 `status=Active`(不切换为 pending),记录 Info 日志"购买时载体已实名,直接激活套餐";若 `currentlyRealnamed=false` 则保持原有 `pending_realname_activation=true` 逻辑;**边界情况**:若 1.1 的卡查询失败(`err != nil`)或 1.2 的设备子查询出错,`currentlyRealnamed` 应保守默认为 `false`(走 pending 路径),不应阻断整个购买流程
|
||||||
- [ ] 1.4 运行 `lsp_diagnostics` 确认 `internal/service/order/service.go` 无编译错误
|
- [x] 1.4 运行 `lsp_diagnostics` 确认 `internal/service/order/service.go` 无编译错误
|
||||||
|
|
||||||
## 2. 扩展 PollingRealnameHandler(polling_realname_handler.go)
|
## 2. 扩展 PollingRealnameHandler(polling_realname_handler.go)
|
||||||
|
|
||||||
- [ ] 2.1 在 `PollingRealnameHandler` 结构体中新增字段 `deviceSimBindingStore *postgres.DeviceSimBindingStore`
|
- [x] 2.1 在 `PollingRealnameHandler` 结构体中新增字段 `deviceSimBindingStore *postgres.DeviceSimBindingStore`
|
||||||
- [ ] 2.2 更新 `NewPollingRealnameHandler` 构造函数签名,在参数列表末尾新增 `deviceSimBindingStore *postgres.DeviceSimBindingStore`,并在函数体中赋值到结构体字段
|
- [x] 2.2 更新 `NewPollingRealnameHandler` 构造函数签名,在参数列表末尾新增 `deviceSimBindingStore *postgres.DeviceSimBindingStore`,并在函数体中赋值到结构体字段
|
||||||
- [ ] 2.3 新增 `triggerDeviceRealnameActivation(ctx context.Context, cardID uint)` 函数:调用 `h.deviceSimBindingStore.GetActiveBindingByCardID` 获取设备绑定;若无绑定(`ErrRecordNotFound`)静默返回;若有绑定则提交 `carrier_type="device", carrier_id=binding.DeviceID` 的 `TaskTypePackageFirstActivation` Asynq 任务(与卡级任务相同的 MaxRetry/Timeout/Queue 配置);失败记录 Warn 日志,不中断流程
|
- [x] 2.3 新增 `triggerDeviceRealnameActivation(ctx context.Context, cardID uint)` 函数:调用 `h.deviceSimBindingStore.GetActiveBindingByCardID` 获取设备绑定;判断无绑定使用同包内已有的 `isNotFound(err)` 工具函数(定义于 `internal/task/polling_utils.go`),命中则静默返回;若有绑定则提交 `carrier_type="device", carrier_id=binding.DeviceID` 的 `TaskTypePackageFirstActivation` Asynq 任务(与卡级任务相同的 MaxRetry/Timeout/Queue 配置,参照 `triggerFirstRealnameActivation` 实现);任务提交失败记录 Warn 日志,日志字段需同时包含 `card_id` 和 `device_id` 以便关联排查,不中断流程
|
||||||
- [ ] 2.4 在 `isFirstRealname` 分支中,在调用 `triggerFirstRealnameActivation` 之后追加调用 `triggerDeviceRealnameActivation(ctx, cardID)`
|
- [x] 2.4 在 `isFirstRealname` 分支中,在调用 `triggerFirstRealnameActivation` 之后追加调用 `triggerDeviceRealnameActivation(ctx, cardID)`
|
||||||
- [ ] 2.5 运行 `lsp_diagnostics` 确认 `internal/task/polling_realname_handler.go` 无编译错误
|
- [x] 2.5 运行 `lsp_diagnostics` 确认 `internal/task/polling_realname_handler.go` 无编译错误
|
||||||
|
|
||||||
## 3. 更新 Worker 构造调用(pkg/queue/handler.go)
|
## 3. 更新 Worker 构造调用(pkg/queue/handler.go)
|
||||||
|
|
||||||
- [ ] 3.1 在 `registerPollingHandlers` 中,将 `task.NewPollingRealnameHandler(...)` 调用末尾追加参数 `h.workerResult.Stores.DeviceSimBinding`
|
- [x] 3.1 在 `registerPollingHandlers` 中,将 `task.NewPollingRealnameHandler(...)` 调用末尾追加参数 `h.workerResult.Stores.DeviceSimBinding`
|
||||||
- [ ] 3.2 运行 `lsp_diagnostics` 确认 `pkg/queue/handler.go` 无编译错误
|
- [x] 3.2 运行 `lsp_diagnostics` 确认 `pkg/queue/handler.go` 无编译错误
|
||||||
|
|
||||||
## 4. 整体编译验证
|
## 4. 整体编译验证与存量修复
|
||||||
|
|
||||||
- [ ] 4.1 执行 `go build ./...` 确认整个项目编译通过,零错误零警告
|
- [x] 4.1 执行 `go build ./...` 确认整个项目编译通过,零错误零警告
|
||||||
- [ ] 4.2 使用 PostgreSQL MCP 执行存量修复 SQL:将 `tb_package_usage.id=1` 的套餐手动激活(`status=1, pending_realname_activation=false, activated_at=NOW(), expires_at` 按套餐配置计算),并验证更新结果
|
- [x] 4.2 通过 psql 或任意数据库客户端手动执行存量修复,分三步:
|
||||||
|
|
||||||
|
**步骤 0 — 确认受影响范围(只读,先跑)**
|
||||||
|
```sql
|
||||||
|
SELECT pu.id, pu.device_id, pu.iot_card_id,
|
||||||
|
p.calendar_type, p.duration_months, p.duration_days, p.data_reset_cycle
|
||||||
|
FROM tb_package_usage pu
|
||||||
|
JOIN tb_package p ON p.id = pu.package_id
|
||||||
|
WHERE pu.status = 0 AND pu.pending_realname_activation = true;
|
||||||
|
```
|
||||||
|
预期:仅返回 `id=1`(device_id=2,natural_month,12 个月,monthly 重置)。若有其他记录,核实后再执行步骤 1。
|
||||||
|
|
||||||
|
**步骤 1 — 批量修复(写操作,建议在事务中执行)**
|
||||||
|
```sql
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
UPDATE tb_package_usage pu
|
||||||
|
SET
|
||||||
|
status = 1,
|
||||||
|
pending_realname_activation = false,
|
||||||
|
activated_at = NOW(),
|
||||||
|
expires_at = CASE p.calendar_type
|
||||||
|
WHEN 'natural_month' THEN
|
||||||
|
DATE_TRUNC('month', NOW())
|
||||||
|
+ ((p.duration_months + 1)::text || ' months')::interval
|
||||||
|
- INTERVAL '1 second'
|
||||||
|
ELSE
|
||||||
|
DATE_TRUNC('day', NOW())
|
||||||
|
+ (p.duration_days::text || ' days')::interval
|
||||||
|
+ INTERVAL '86399 seconds'
|
||||||
|
END,
|
||||||
|
next_reset_at = CASE p.data_reset_cycle
|
||||||
|
WHEN 'monthly' THEN
|
||||||
|
CASE p.calendar_type
|
||||||
|
WHEN 'natural_month' THEN DATE_TRUNC('month', NOW()) + INTERVAL '1 month'
|
||||||
|
ELSE DATE_TRUNC('day', NOW()) + INTERVAL '30 days'
|
||||||
|
END
|
||||||
|
WHEN 'daily' THEN DATE_TRUNC('day', NOW()) + INTERVAL '1 day'
|
||||||
|
WHEN 'yearly' THEN DATE_TRUNC('year', NOW() + INTERVAL '1 year')
|
||||||
|
ELSE NULL
|
||||||
|
END,
|
||||||
|
updated_at = NOW()
|
||||||
|
FROM tb_package p
|
||||||
|
WHERE pu.package_id = p.id
|
||||||
|
AND pu.status = 0
|
||||||
|
AND pu.pending_realname_activation = true
|
||||||
|
AND pu.device_id > 0
|
||||||
|
AND pu.device_id IN (
|
||||||
|
SELECT DISTINCT dsb.device_id
|
||||||
|
FROM tb_device_sim_binding dsb
|
||||||
|
JOIN tb_iot_card ic ON ic.id = dsb.iot_card_id
|
||||||
|
WHERE dsb.bind_status = 1 AND dsb.deleted_at IS NULL
|
||||||
|
AND ic.real_name_status = 1 AND ic.deleted_at IS NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 确认影响行数为预期值后再 COMMIT,否则 ROLLBACK
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
**步骤 2 — 验证结果**
|
||||||
|
```sql
|
||||||
|
SELECT id, status, pending_realname_activation, activated_at, expires_at, next_reset_at
|
||||||
|
FROM tb_package_usage
|
||||||
|
WHERE id = 1;
|
||||||
|
```
|
||||||
|
预期:`status=1`,`pending_realname_activation=false`,`expires_at` 为当月 +12 个月月末 23:59:59,`next_reset_at` 为下月 1 日 00:00:00。
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ func (h *Handler) registerCommissionCalculationHandler() {
|
|||||||
func (h *Handler) registerPollingHandlers() {
|
func (h *Handler) registerPollingHandlers() {
|
||||||
realnameHandler := task.NewPollingRealnameHandler(
|
realnameHandler := task.NewPollingRealnameHandler(
|
||||||
h.pollingBase, h.gatewayClient, h.workerResult.Stores.IotCard,
|
h.pollingBase, h.gatewayClient, h.workerResult.Stores.IotCard,
|
||||||
h.asynqClient, h.pollingStopResumeSvc)
|
h.asynqClient, h.pollingStopResumeSvc, h.workerResult.Stores.DeviceSimBinding)
|
||||||
carrierStore := postgres.NewCarrierStore(h.db)
|
carrierStore := postgres.NewCarrierStore(h.db)
|
||||||
carddataHandler := task.NewPollingCarddataHandler(
|
carddataHandler := task.NewPollingCarddataHandler(
|
||||||
h.pollingBase, h.gatewayClient, h.workerResult.Stores.IotCard,
|
h.pollingBase, h.gatewayClient, h.workerResult.Stores.IotCard,
|
||||||
|
|||||||
Reference in New Issue
Block a user