diff --git a/internal/logic/member.go b/internal/logic/member.go index 4b02563..80e7990 100644 --- a/internal/logic/member.go +++ b/internal/logic/member.go @@ -183,6 +183,18 @@ func prepareXpayOrder( for k, v := range orderFields { order[k] = v } + // plan_key / level_key 是会员专有列,但老表把它们建成了 NOT NULL 且无默认值 + // (次数包订单没有这两个语义)→ 不补值会报 + // `Error 1364: Field 'plan_key' doesn't have a default value`。 + // + // 这里统一兜底成空串,而不是去把库列改成 DEFAULT '': + // level_key 一旦被静默填成空串,发货时 extendMembership 会把用户等级写成空, + // 属于「无声降级」。保持列严格、由唯一的下单入口显式补齐,才是安全的做法。 + for _, k := range []string{"plan_key", "level_key"} { + if _, ok := order[k]; !ok { + order[k] = "" + } + } if _, err = g.Model(consts.TableMemberOrders).Data(order).Insert(); err != nil { return nil, err } @@ -423,6 +435,11 @@ func deliverOrder(ctx context.Context, outTradeNo, wxOrderId string) error { case consts.OrderTypeQuota: toolKey := row["tool_key"].String() times := row["times"].Int() + // 空 toolKey 会写出一条挂在空工具上的额度记录,times<=0 则是「发了等于没发」, + // 两者都属于数据异常,直接拒绝发货并告警,不要静默放过。 + if toolKey == "" || times <= 0 { + return gerror.Newf("订单 %s 次数包信息异常: toolKey=%q times=%d", outTradeNo, toolKey, times) + } expireAt, err := grantToolQuota(ctx, tx, userId, toolKey, times, days) if err != nil { return err @@ -431,6 +448,11 @@ func deliverOrder(ctx context.Context, outTradeNo, wxOrderId string) error { userId, outTradeNo, toolKey, times, expireAt.String()) default: levelKey := row["level_key"].String() + // 空等级会让 extendMembership 把 users.level_key 写成空串(等于无声降权), + // 这里拦住:宁可发货失败留一条告警日志,也不要污染用户数据。 + if levelKey == "" { + return gerror.Newf("订单 %s 缺少等级信息(level_key 为空),拒绝发货以免清空用户等级", outTradeNo) + } expireAt, finalLevel, err := extendMembership(ctx, tx, userId, levelKey, days) if err != nil { return err