package dao import ( "context" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "observer-server/biz/consts" "observer-server/biz/model/entity" "observer-server/common" ) // PaymentOrder 支付订单表 DAO:写频繁表不走查询缓存; // 所有写链路(含读改写事务)必须经 common.Serial() 串行执行。 type paymentOrderDao struct{} var PaymentOrder = &paymentOrderDao{} func init() { ctx := context.Background() common.DropLegacyTableIfHasColumn(ctx, consts.TablePaymentOrder, "device_id") _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS payment_order ( order_id TEXT PRIMARY KEY, phone_num TEXT NOT NULL, plan_id TEXT NOT NULL, channel TEXT NOT NULL, amount_cents INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'created', wx_trade_no TEXT UNIQUE, alipay_trade_no TEXT UNIQUE, created_at TEXT NOT NULL, paid_at TEXT )`) if err != nil { panic(err) } _, err = g.DB().Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_payment_order_phone ON payment_order(phone_num)") if err != nil { panic(err) } } // InsertOneInTx 事务内插入订单 func (d *paymentOrderDao) InsertOneInTx(ctx context.Context, tx gdb.TX, m *entity.PaymentOrder) error { _, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx).Insert(m) return err } // GetByPk 按订单号查询(普通读,不走缓存) func (d *paymentOrderDao) GetByPk(ctx context.Context, orderId string) (*entity.PaymentOrder, error) { return d.getByPk(ctx, nil, orderId) } // GetByPkInTx 事务内按订单号查询 func (d *paymentOrderDao) GetByPkInTx(ctx context.Context, tx gdb.TX, orderId string) (*entity.PaymentOrder, error) { return d.getByPk(ctx, tx, orderId) } func (d *paymentOrderDao) getByPk(ctx context.Context, tx gdb.TX, orderId string) (*entity.PaymentOrder, error) { m := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx) if tx != nil { m = m.TX(tx) } var e entity.PaymentOrder if err := m.Where("order_id", orderId).Scan(&e); err != nil { if common.IsNoRows(err) { return nil, nil } return nil, err } return &e, nil } // UpdatePaidWechatInTx 事务内标记微信订单已支付(状态机校验在 service 完成) func (d *paymentOrderDao) UpdatePaidWechatInTx(ctx context.Context, tx gdb.TX, orderId, wxTradeNo string) error { _, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx). Where("order_id", orderId). Data(gdb.Map{"status": consts.OrderStatusPaid, "wx_trade_no": wxTradeNo, "paid_at": gtime.Now()}). Update() return err } // UpdatePaidAlipayInTx 事务内标记支付宝订单已支付 func (d *paymentOrderDao) UpdatePaidAlipayInTx(ctx context.Context, tx gdb.TX, orderId, alipayTradeNo string) error { _, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx). Where("order_id", orderId). Data(gdb.Map{"status": consts.OrderStatusPaid, "alipay_trade_no": alipayTradeNo, "paid_at": gtime.Now()}). Update() return err } // UpdateClosedInTx 事务内关闭订单(渠道下单失败回滚 / 惰性回收) func (d *paymentOrderDao) UpdateClosedInTx(ctx context.Context, tx gdb.TX, orderId string) error { _, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx). Where("order_id", orderId). Data(gdb.Map{"status": consts.OrderStatusClosed}). Update() return err } // CloseStaleCreatedInTx 惰性关闭账号下超时未支付的 created 订单 func (d *paymentOrderDao) CloseStaleCreatedInTx(ctx context.Context, tx gdb.TX, phoneNum string, before *gtime.Time) error { _, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx). Where("phone_num", phoneNum). Where("status", consts.OrderStatusCreated). WhereLT("created_at", before). Data(gdb.Map{"status": consts.OrderStatusClosed}). Update() return err } // GetRecentCreatedInTx 事务内查账号在 since 之后创建的未支付订单(防重复扣款兜底,返回最早一条) func (d *paymentOrderDao) GetRecentCreatedInTx(ctx context.Context, tx gdb.TX, phoneNum string, since *gtime.Time) (*entity.PaymentOrder, error) { var e entity.PaymentOrder err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx). Where("phone_num", phoneNum). Where("status", consts.OrderStatusCreated). WhereGTE("created_at", since). OrderAsc("created_at"). Limit(1). Scan(&e) if err != nil { if common.IsNoRows(err) { return nil, nil } return nil, err } return &e, nil } // PageByFilter 管理端订单分页查询:按条件精确筛选(phoneNum/status 精确、时间区间含界), // 写频繁表不走缓存;返回同条件总数与当前页列表(按下单时间倒序)。 func (d *paymentOrderDao) PageByFilter(ctx context.Context, phoneNum, status string, startAt, endAt *gtime.Time, page, size int) ([]*entity.PaymentOrder, int64, error) { base := func() *gdb.Model { m := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx) if phoneNum != "" { m = m.Where("phone_num", phoneNum) } if status != "" { m = m.Where("status", status) } if startAt != nil { m = m.WhereGTE("created_at", startAt) } if endAt != nil { m = m.WhereLTE("created_at", endAt) } return m } total, err := base().Count() if err != nil { return nil, 0, err } var list []*entity.PaymentOrder err = base().OrderDesc("created_at").Limit((page-1)*size, size).Scan(&list) if err != nil { if common.IsNoRows(err) { return []*entity.PaymentOrder{}, int64(total), nil } return nil, 0, err } return list, int64(total), nil }