diff --git a/README.md b/README.md index 75d77de..5942686 100644 --- a/README.md +++ b/README.md @@ -2,54 +2,19 @@ 用户请求需要检查数据权限 -管理页面查询统一加排序 - -后端默认用户名不能是完整手机号 - 前端需要 token 化改造,以避免每次 basic 认证流程中 bcrypt 对比导致的性能对比 -优化中间件,配置通用限速 - -observe 部署,蓝狐部署 - ---- - 用反射实现环境变量解析,以简化函数签名 分离 task 的客户端,支持多进程(prefork 必要!) -调整目录结构: - -``` -- /util 工具函数 - -- /models 模型 -- /queries 数据库层 -- /clients 三方依赖的客户端实例 - -- /services 服务层 -- /auth 认证相关,特化服务 - -- /app 应用相关,初始化日志,环境变量,错误类型等 -- /http 协议层,http 服务 -- /cmd 主函数 - -逐层向上依赖 -cmd 调用 app, http 的初始化函数 -http 调用 clients 的初始化函数 -``` +jsonb 类型转换问题,考虑一个高效的 any 到 struct 转换工具 --- -数据库转模型文件 - -jsonb 类型转换问题,考虑一个高效的 any 到 struct 转换工具 - 慢速请求底层调用埋点监控 -- redis -- gorm -- 三方接口 +数据库转模型文件 冷数据迁移方案 @@ -67,6 +32,14 @@ jsonb 类型转换问题,考虑一个高效的 any 到 struct 转换工具 3. 创建 model 文件,并将其添加到 gen 代码中 4. 生成查询文件 +### 权限管理 + +在 `web/core/scopes.go` 下定义了系统所有静态权限 + +新增系统权限需要在数据库中配套添加权限 + +前端也需要新增配套权限定义 + ## 业务逻辑 ### 订单关闭的几种方式 diff --git a/cmd/gen/main.go b/cmd/gen/main.go index fcd391c..b9c0743 100644 --- a/cmd/gen/main.go +++ b/cmd/gen/main.go @@ -65,6 +65,7 @@ func main() { m.Whitelist{}, m.Inquiry{}, m.ProductDiscount{}, + m.BalanceActivity{}, ) g.Execute() } diff --git a/pkg/u/u.go b/pkg/u/u.go index 2112c69..7b77672 100644 --- a/pkg/u/u.go +++ b/pkg/u/u.go @@ -17,14 +17,6 @@ func Else[T any](v *T, or T) T { } } -func ElseTo[A any, B any](a *A, f func(A) B) *B { - if a == nil { - return nil - } else { - return P(f(*a)) - } -} - // 三元表达式 func Ternary[T any](condition bool, trueValue T, falseValue T) T { if condition { diff --git a/scripts/sql/init.sql b/scripts/sql/init.sql index 96b3240..588227a 100644 --- a/scripts/sql/init.sql +++ b/scripts/sql/init.sql @@ -200,7 +200,7 @@ create table admin ( updated_at timestamptz default current_timestamp, deleted_at timestamptz ); -create unique index udx_admin_username on admin (username); +create unique index udx_admin_username on admin (username) where deleted_at is null; create index idx_admin_status on admin (status) where deleted_at is null; create index idx_admin_created_at on admin (created_at) where deleted_at is null; @@ -1018,6 +1018,36 @@ comment on column bill.created_at is '创建时间'; comment on column bill.updated_at is '更新时间'; comment on column bill.deleted_at is '删除时间'; +-- balance_activity 余额变动记录 +drop table if exists balance_activity cascade; +create table balance_activity ( + id int generated by default as identity primary key, + user_id int not null, + bill_id int, + admin_id int, + amount text not null, + balance_prev text not null, + balance_curr text not null, + remark text, + created_at timestamptz default current_timestamp +); +create index idx_balance_activity_user_id on balance_activity (user_id); +create index idx_balance_activity_bill_id on balance_activity (bill_id); +create index idx_balance_activity_admin_id on balance_activity (admin_id); +create index idx_balance_activity_created_at on balance_activity (created_at); + +-- balance_activity表字段注释 +comment on table balance_activity is '余额变动记录表'; +comment on column balance_activity.id is '记录ID'; +comment on column balance_activity.user_id is '用户ID'; +comment on column balance_activity.bill_id is '账单ID'; +comment on column balance_activity.admin_id is '管理员ID'; +comment on column balance_activity.amount is '变动金额'; +comment on column balance_activity.balance_prev is '变动前余额'; +comment on column balance_activity.balance_curr is '变动后余额'; +comment on column balance_activity.remark is '备注'; +comment on column balance_activity.created_at is '创建时间'; + -- coupon 优惠券 drop table if exists coupon cascade; create table coupon ( @@ -1175,4 +1205,10 @@ alter table product_sku_user alter table product_sku_user add constraint fk_product_sku_user_discount_id foreign key (discount_id) references product_discount (id) on delete restrict; +--balance_activity表外键 +alter table balance_activity + add constraint fk_balance_activity_user_id foreign key (user_id) references "user" (id) on delete cascade; +alter table balance_activity + add constraint fk_balance_activity_bill_id foreign key (bill_id) references bill (id) on delete set null; + -- endregion diff --git a/web/auth/account.go b/web/auth/account.go index 4781ea0..cdf84f7 100644 --- a/web/auth/account.go +++ b/web/auth/account.go @@ -155,22 +155,8 @@ func authAdminByPassword(tx *q.Query, username, password string) (*m.Admin, erro } func adminScopes(admin *m.Admin) ([]string, error) { - count, err := q.Admin. - LeftJoin(q.LinkAdminRole, q.LinkAdminRole.AdminID.EqCol(q.Admin.ID)). - LeftJoin(q.LinkAdminRolePermission, q.LinkAdminRolePermission.RoleID.EqCol(q.LinkAdminRole.RoleID)). - LeftJoin(q.Permission, q.Permission.ID.EqCol(q.LinkAdminRolePermission.PermissionID)). - Where(q.Admin.ID.Eq(admin.ID)). - Select(q.Permission.Name). - Count() - if err != nil { - return nil, err - } - if count == 0 { - return nil, nil - } - - scopes := make([]string, 0, count) - err = q.Admin. + var scopes []struct{ Name string } + err := q.Admin. LeftJoin(q.LinkAdminRole, q.LinkAdminRole.AdminID.EqCol(q.Admin.ID)). LeftJoin(q.LinkAdminRolePermission, q.LinkAdminRolePermission.RoleID.EqCol(q.LinkAdminRole.RoleID)). LeftJoin(q.Permission, q.Permission.ID.EqCol(q.LinkAdminRolePermission.PermissionID)). @@ -181,5 +167,9 @@ func adminScopes(admin *m.Admin) ([]string, error) { return nil, err } - return scopes, nil + scopeNames := make([]string, 0, len(scopes)) + for _, scope := range scopes { + scopeNames = append(scopeNames, scope.Name) + } + return scopeNames, nil } diff --git a/web/auth/endpoints.go b/web/auth/endpoints.go index 25a25d0..0501951 100644 --- a/web/auth/endpoints.go +++ b/web/auth/endpoints.go @@ -336,9 +336,8 @@ func authPassword(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m // 手机号首次登录的自动创建用户 user = &m.User{ - Phone: req.Username, - Username: u.P(req.Username), - Status: m.UserStatusEnabled, + Phone: req.Username, + Status: m.UserStatusEnabled, } } @@ -549,22 +548,30 @@ func introspectUser(ctx *fiber.Ctx, authCtx *AuthCtx) error { func introspectAdmin(ctx *fiber.Ctx, authCtx *AuthCtx) error { // 获取管理员信息 profile, err := q.Admin. + Preload(q.Admin.Roles, q.Admin.Roles.Permissions). Where(q.Admin.ID.Eq(authCtx.Admin.ID)). - Omit(q.Admin.DeletedAt). + Omit(q.Admin.DeletedAt, q.Admin.Password). Take() if err != nil { return err } - // 不返回密码 - profile.Password = "" - - // 掩码敏感信息 - if profile.Phone != nil && *profile.Phone != "" { - profile.Phone = u.P(maskPhone(*profile.Phone)) + // 整理权限列表 + scopes := make(map[string]struct{}, 0) + for _, role := range profile.Roles { + for _, permission := range role.Permissions { + scopes[permission.Name] = struct{}{} + } + } + list := make([]string, 0, len(scopes)) + for scope := range scopes { + list = append(list, scope) } - return ctx.JSON(profile) + return ctx.JSON(struct { + *m.Admin + Scopes []string `json:"scopes"` + }{profile, list}) } func maskPhone(phone string) string { diff --git a/web/core/scopes.go b/web/core/scopes.go index aabfeb7..f33fd0b 100644 --- a/web/core/scopes.go +++ b/web/core/scopes.go @@ -29,9 +29,12 @@ const ( ScopeResourceRead = string("resource:read") // 读取用户套餐列表 ScopeResourceWrite = string("resource:write") // 写入用户套餐 - ScopeUser = string("user") // 用户 - ScopeUserRead = string("user:read") // 读取用户列表 - ScopeUserWrite = string("user:write") // 写入用户 + ScopeUser = string("user") // 用户 + ScopeUserRead = string("user:read") // 读取用户列表 + ScopeUserReadOne = string("user:read:one") // 读取单个用户 + ScopeUserWrite = string("user:write") // 写入用户 + ScopeUserWriteBalance = string("user:write:balance") // 写入用户余额 + ScopeUserWriteBind = string("user:write:bind") // 用户认领 ScopeCoupon = string("coupon") // 优惠券 ScopeCouponRead = string("coupon:read") // 读取优惠券列表 diff --git a/web/error.go b/web/error.go index f58469a..d5a30e9 100644 --- a/web/error.go +++ b/web/error.go @@ -22,8 +22,9 @@ func ErrorHandler(c *fiber.Ctx, err error) error { var authErr auth.AuthErr var bizErr *core.BizErr var servErr *core.ServErr - var jsonErr *json.UnmarshalTypeError var timeErr *time.ParseError + var jsonErr *json.UnmarshalTypeError + var jsonSyntaxErr *json.SyntaxError switch { @@ -53,13 +54,17 @@ func ErrorHandler(c *fiber.Ctx, err error) error { code = fiber.StatusInternalServerError message = err.Error() + case errors.As(err, &timeErr): + code = fiber.StatusBadRequest + message = fmt.Sprintf("时间格式不正确,传入值为 %s,检查传参是否为时间类型", timeErr.Value) + case errors.As(err, &jsonErr): code = fiber.StatusBadRequest message = fmt.Sprintf("参数 %s 类型不正确,传入类型为 %s,正确类型应该为 %s", jsonErr.Field, jsonErr.Value, jsonErr.Type.Name()) - case errors.As(err, &timeErr): + case errors.As(err, &jsonSyntaxErr): code = fiber.StatusBadRequest - message = fmt.Sprintf("时间格式不正确,传入值为 %s,检查传参是否为时间类型", timeErr.Value) + message = "参数格式不正确,检查传参是否为 JSON 格式" // 所有未手动声明的错误类型 default: diff --git a/web/handlers/batch.go b/web/handlers/batch.go index 25a2783..5f510d0 100644 --- a/web/handlers/batch.go +++ b/web/handlers/batch.go @@ -106,6 +106,7 @@ func PageBatchByAdmin(c *fiber.Ctx) error { q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"), ). Where(do). + Order(q.LogsUserUsage.Time.Desc()). FindByPage(req.GetOffset(), req.GetLimit()) return c.JSON(core.PageResp{ diff --git a/web/handlers/bill.go b/web/handlers/bill.go index 81bacd8..705464f 100644 --- a/web/handlers/bill.go +++ b/web/handlers/bill.go @@ -57,7 +57,7 @@ func PageBillByAdmin(c *fiber.Ctx) error { } // 查询用户列表 - list, total, err := q.Bill.Debug(). + list, total, err := q.Bill. Joins( q.Bill.User, q.Bill.Resource, @@ -70,6 +70,7 @@ func PageBillByAdmin(c *fiber.Ctx) error { q.User.As("User").Phone.As("User__phone"), q.User.As("User").Name.As("User__name"), q.Trade.As("Trade").InnerNo.As("Trade__inner_no"), + q.Trade.As("Trade").Acquirer.As("Trade__acquirer"), q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"), ). Where(do). diff --git a/web/handlers/channel.go b/web/handlers/channel.go index 1e971fb..0bb3e62 100644 --- a/web/handlers/channel.go +++ b/web/handlers/channel.go @@ -72,6 +72,7 @@ func PageChannelByAdmin(c *fiber.Ctx) error { q.User.As("User").Name.As("User__name"), ). Where(do). + Order(q.Channel.CreatedAt.Desc()). FindByPage(req.GetOffset(), req.GetLimit()) if err != nil { return err @@ -190,6 +191,10 @@ func CreateChannel(c *fiber.Ctx) error { } // 创建通道 + var isp *m.EdgeISP + if req.Isp != nil { + isp = u.X(m.ToEdgeISP(*req.Isp)) + } result, err := s.Channel.CreateChannels( ip, req.ResourceId, @@ -197,7 +202,7 @@ func CreateChannel(c *fiber.Ctx) error { req.AuthType == s.ChannelAuthTypePass, req.Count, s.EdgeFilter{ - Isp: u.ElseTo(req.Isp, m.ToEdgeISP), + Isp: isp, Prov: req.Prov, City: req.City, }, diff --git a/web/handlers/resource.go b/web/handlers/resource.go index 0082feb..ec0a988 100644 --- a/web/handlers/resource.go +++ b/web/handlers/resource.go @@ -260,7 +260,7 @@ func PageResourceShortByAdmin(c *fiber.Ctx) error { } } - list, total, err := q.Resource.Debug(). + list, total, err := q.Resource. Joins(q.Resource.User, q.Resource.Short, q.Resource.Short.Sku). Select( q.Resource.ALL, @@ -352,7 +352,7 @@ func PageResourceLongByAdmin(c *fiber.Ctx) error { } } - list, total, err := q.Resource.Debug(). + list, total, err := q.Resource. Joins(q.Resource.User, q.Resource.Long, q.Resource.Long.Sku). Select( q.Resource.ALL, diff --git a/web/handlers/trade.go b/web/handlers/trade.go index ad2d33b..b343e2c 100644 --- a/web/handlers/trade.go +++ b/web/handlers/trade.go @@ -70,6 +70,7 @@ func PageTradeByAdmin(c *fiber.Ctx) error { q.User.As("User").Name.As("User__name"), ). Where(do). + Order(q.Trade.CreatedAt.Desc()). FindByPage(req.GetOffset(), req.GetLimit()) if err != nil { return err @@ -121,7 +122,13 @@ func TradeCreate(c *fiber.Ctx) error { } // 处理订单 - result, err := s.Trade.Create(authCtx.User, req.CreateTradeData, req.Resource) + var result *s.CreateTradeResult + switch req.Type { + case m.TradeTypePurchase: + result, err = s.Trade.Create(authCtx.User, req.CreateTradeData, req.Resource) + case m.TradeTypeRecharge: + result, err = s.Trade.Create(authCtx.User, req.CreateTradeData, req.Recharge) + } if err != nil { return core.NewServErr("处理购买产品信息失败", err) } @@ -193,11 +200,7 @@ type TradeCancelReq struct { // 检查订单 func TradeCheck(c *fiber.Ctx) error { - // 检查权限 - _, err := auth.GetAuthCtx(c).PermitUser() - if err != nil { - return err - } + // 检查权限:sse 接口暂时不检查权限 // 解析请求参数 req := new(TradeCheckReq) diff --git a/web/handlers/user.go b/web/handlers/user.go index 92669fa..5c3c38a 100644 --- a/web/handlers/user.go +++ b/web/handlers/user.go @@ -9,9 +9,157 @@ import ( s "platform/web/services" "github.com/gofiber/fiber/v2" + "github.com/shopspring/decimal" "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" ) +// 分页获取用户 +func PageUserByAdmin(c *fiber.Ctx) error { + // 检查权限 + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserRead) + if err != nil { + return err + } + + // 解析请求参数 + req := new(PageUserByAdminReq) + if err := g.Validator.ParseBody(c, req); err != nil { + return err + } + + // 构建查询条件 + do := q.User.Where() + if req.Account != nil { + do = do.Where(q.User.Where( + q.User.Username.Like("%" + *req.Account + "%"), + ).Or( + q.User.Phone.Like("%" + *req.Account + "%"), + ).Or( + q.User.Email.Like("%" + *req.Account + "%"), + )) + } + if req.Name != nil { + do = do.Where(q.User.Name.Eq(*req.Name)) + } + if req.Identified != nil { + if *req.Identified { + do = do.Where(q.User.IDType.Gt(0)) + } else { + do = do.Where(q.User.IDType.Eq(0)) + } + } + if req.Enabled != nil { + if *req.Enabled { + do = do.Where(q.User.Status.Eq(1)) + } else { + do = do.Where(q.User.Status.Eq(0)) + } + } + if req.Assigned != nil { + if *req.Assigned { + do = do.Where(q.User.AdminID.IsNotNull()) + } else { + do = do.Where(q.User.AdminID.IsNull()) + } + } + + // 查询用户列表 + users, total, err := q.User. + Preload(q.User.Admin, q.User.Discount). + Omit(q.User.Password). + Where(do). + Order(q.User.CreatedAt.Desc()). + FindByPage(req.GetOffset(), req.GetLimit()) + if err != nil { + return err + } + + for _, user := range users { + if user.Admin != nil { + user.Admin = &m.Admin{ + Name: user.Admin.Name, + } + } + } + + // 返回结果 + return c.JSON(core.PageResp{ + Total: int(total), + Page: req.GetPage(), + Size: req.GetSize(), + List: users, + }) +} + +type PageUserByAdminReq struct { + core.PageReq + Account *string `json:"account,omitempty"` + Name *string `json:"name,omitempty"` + Identified *bool `json:"identified,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Assigned *bool `json:"assigned,omitempty"` +} + +// 管理员获取单个用户 +func GetUserByAdmin(c *fiber.Ctx) error { + // 检查权限 + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserReadOne) + if err != nil { + return err + } + + // 解析请求参数 + var req GetUserByAdminReq + if err := g.Validator.ParseBody(c, &req); err != nil { + return err + } + + // 构建查询条件 + do := q.User.Where() + if req.Account != nil { + do = do.Where(q.User.Where( + q.User.Username.Like("%" + *req.Account + "%"), + ).Or( + q.User.Phone.Like("%" + *req.Account + "%"), + ).Or( + q.User.Email.Like("%" + *req.Account + "%"), + )) + } + if req.Name != nil { + do = do.Where(q.User.Name.Eq(*req.Name)) + } + + // 查询用户 + user, err := q.User. + Preload(q.User.Admin, q.User.Discount). + Omit(q.User.Password). + Where(do). + Order(q.User.CreatedAt.Desc()). + First() + if err == gorm.ErrRecordNotFound { + return core.NewBizErr("找不到用户") + } + if err != nil { + return err + } + + // 仅保留管理员名称 + if user.Admin != nil { + user.Admin = &m.Admin{ + Name: user.Admin.Name, + } + } + + // 返回结果 + return c.JSON(user) +} + +type GetUserByAdminReq struct { + Account *string `json:"account,omitempty"` + Name *string `json:"name,omitempty"` +} + // 管理员创建用户 func CreateUserByAdmin(c *fiber.Ctx) error { _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWrite) @@ -69,91 +217,36 @@ func RemoveUserByAdmin(c *fiber.Ctx) error { return c.JSON(nil) } -// 分页获取用户 -func PageUserByAdmin(c *fiber.Ctx) error { - // 检查权限 - _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserRead) +// 管理员更新用户余额 +func UpdateUserBalanceByAdmin(c *fiber.Ctx) error { + authCtx, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWriteBalance) if err != nil { return err } - // 解析请求参数 - req := new(PageUserByAdminReq) - if err := g.Validator.ParseBody(c, req); err != nil { + var req UpdateUserBalanceByAdminData + if err := g.Validator.ParseBody(c, &req); err != nil { return err } - // 构建查询条件 - do := q.User.Where() - if req.Account != nil { - do = do.Where(q.User.Where( - q.User.Username.Like("%" + *req.Account + "%"), - ).Or( - q.User.Phone.Like("%" + *req.Account + "%"), - ).Or( - q.User.Email.Like("%" + *req.Account + "%"), - )) - } - if req.Name != nil { - do = do.Where(q.User.Name.Eq(*req.Name)) - } - if req.Identified != nil { - if *req.Identified { - do = do.Where(q.User.IDType.Gt(0)) - } else { - do = do.Where(q.User.IDType.Eq(0)) - } - } - if req.Enabled != nil { - if *req.Enabled { - do = do.Where(q.User.Status.Eq(1)) - } else { - do = do.Where(q.User.Status.Eq(0)) - } - } - if req.Assigned != nil { - if *req.Assigned { - do = do.Where(q.User.AdminID.IsNotNull()) - } else { - do = do.Where(q.User.AdminID.IsNull()) - } - } - - // 查询用户列表 - users, total, err := q.User.Debug(). - Preload(q.User.Admin, q.User.Discount). - Omit(q.User.Password). - Where(do). - Order(q.User.CreatedAt). - FindByPage(req.GetOffset(), req.GetLimit()) + user, err := s.User.Get(q.Q, req.UserID) if err != nil { return err } - - for _, user := range users { - if user.Admin != nil { - user.Admin = &m.Admin{ - Name: user.Admin.Name, - } - } + balance, err := decimal.NewFromString(req.Balance) + if err != nil { + return err + } + if err := s.User.UpdateBalanceByAdmin(user, balance, &authCtx.Admin.ID); err != nil { + return err } - // 返回结果 - return c.JSON(core.PageResp{ - Total: int(total), - Page: req.GetPage(), - Size: req.GetSize(), - List: users, - }) + return c.JSON(nil) } -type PageUserByAdminReq struct { - core.PageReq - Account *string `json:"account,omitempty"` - Name *string `json:"name,omitempty"` - Identified *bool `json:"identified,omitempty"` - Enabled *bool `json:"enabled,omitempty"` - Assigned *bool `json:"assigned,omitempty"` +type UpdateUserBalanceByAdminData struct { + UserID int32 `json:"user_id" validate:"required"` + Balance string `json:"balance" validate:"required"` } // 绑定管理员 diff --git a/web/models/balance_activity.go b/web/models/balance_activity.go new file mode 100644 index 0000000..cde6956 --- /dev/null +++ b/web/models/balance_activity.go @@ -0,0 +1,22 @@ +package models + +import ( + "time" +) + +// BalanceActivity 余额变动记录表 +type BalanceActivity struct { + ID int32 `json:"id" gorm:"column:id;primaryKey"` // 记录ID + UserID int32 `json:"user_id" gorm:"column:user_id"` // 用户ID + BillID *int32 `json:"bill_id,omitempty" gorm:"column:bill_id"` // 账单ID + AdminID *int32 `json:"admin_id,omitempty" gorm:"column:admin_id"` // 管理员ID + Amount string `json:"amount" gorm:"column:amount"` // 变动金额 + BalancePrev string `json:"balance_prev" gorm:"column:balance_prev"` // 变动前余额 + BalanceCurr string `json:"balance_curr" gorm:"column:balance_curr"` // 变动后余额 + Remark *string `json:"remark,omitempty" gorm:"column:remark"` // 备注 + CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` // 创建时间 + + User *User `json:"user,omitempty" gorm:"foreignKey:UserID"` + Bill *Bill `json:"bill,omitempty" gorm:"foreignKey:BillID"` + Admin *User `json:"admin,omitempty" gorm:"foreignKey:AdminID"` +} diff --git a/web/queries/balance_activity.gen.go b/web/queries/balance_activity.gen.go new file mode 100644 index 0000000..1ebdad2 --- /dev/null +++ b/web/queries/balance_activity.gen.go @@ -0,0 +1,871 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package queries + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "platform/web/models" +) + +func newBalanceActivity(db *gorm.DB, opts ...gen.DOOption) balanceActivity { + _balanceActivity := balanceActivity{} + + _balanceActivity.balanceActivityDo.UseDB(db, opts...) + _balanceActivity.balanceActivityDo.UseModel(&models.BalanceActivity{}) + + tableName := _balanceActivity.balanceActivityDo.TableName() + _balanceActivity.ALL = field.NewAsterisk(tableName) + _balanceActivity.ID = field.NewInt32(tableName, "id") + _balanceActivity.UserID = field.NewInt32(tableName, "user_id") + _balanceActivity.BillID = field.NewInt32(tableName, "bill_id") + _balanceActivity.AdminID = field.NewInt32(tableName, "admin_id") + _balanceActivity.Amount = field.NewString(tableName, "amount") + _balanceActivity.BalancePrev = field.NewString(tableName, "balance_prev") + _balanceActivity.BalanceCurr = field.NewString(tableName, "balance_curr") + _balanceActivity.Remark = field.NewString(tableName, "remark") + _balanceActivity.CreatedAt = field.NewTime(tableName, "created_at") + _balanceActivity.Admin = balanceActivityHasOneAdmin{ + db: db.Session(&gorm.Session{}), + + RelationField: field.NewRelation("Admin", "models.User"), + Admin: struct { + field.RelationField + Roles struct { + field.RelationField + Permissions struct { + field.RelationField + Parent struct { + field.RelationField + } + Children struct { + field.RelationField + } + } + } + }{ + RelationField: field.NewRelation("Admin.Admin", "models.Admin"), + Roles: struct { + field.RelationField + Permissions struct { + field.RelationField + Parent struct { + field.RelationField + } + Children struct { + field.RelationField + } + } + }{ + RelationField: field.NewRelation("Admin.Admin.Roles", "models.AdminRole"), + Permissions: struct { + field.RelationField + Parent struct { + field.RelationField + } + Children struct { + field.RelationField + } + }{ + RelationField: field.NewRelation("Admin.Admin.Roles.Permissions", "models.Permission"), + Parent: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Admin.Admin.Roles.Permissions.Parent", "models.Permission"), + }, + Children: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Admin.Admin.Roles.Permissions.Children", "models.Permission"), + }, + }, + }, + }, + Discount: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Admin.Discount", "models.ProductDiscount"), + }, + Roles: struct { + field.RelationField + Permissions struct { + field.RelationField + } + }{ + RelationField: field.NewRelation("Admin.Roles", "models.UserRole"), + Permissions: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Admin.Roles.Permissions", "models.Permission"), + }, + }, + } + + _balanceActivity.User = balanceActivityBelongsToUser{ + db: db.Session(&gorm.Session{}), + + RelationField: field.NewRelation("User", "models.User"), + } + + _balanceActivity.Bill = balanceActivityBelongsToBill{ + db: db.Session(&gorm.Session{}), + + RelationField: field.NewRelation("Bill", "models.Bill"), + User: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Bill.User", "models.User"), + }, + Trade: struct { + field.RelationField + User struct { + field.RelationField + } + }{ + RelationField: field.NewRelation("Bill.Trade", "models.Trade"), + User: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Bill.Trade.User", "models.User"), + }, + }, + Resource: struct { + field.RelationField + User struct { + field.RelationField + } + Short struct { + field.RelationField + Sku struct { + field.RelationField + Product struct { + field.RelationField + } + Discount struct { + field.RelationField + } + } + } + Long struct { + field.RelationField + Sku struct { + field.RelationField + } + } + Product struct { + field.RelationField + } + }{ + RelationField: field.NewRelation("Bill.Resource", "models.Resource"), + User: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Bill.Resource.User", "models.User"), + }, + Short: struct { + field.RelationField + Sku struct { + field.RelationField + Product struct { + field.RelationField + } + Discount struct { + field.RelationField + } + } + }{ + RelationField: field.NewRelation("Bill.Resource.Short", "models.ResourceShort"), + Sku: struct { + field.RelationField + Product struct { + field.RelationField + } + Discount struct { + field.RelationField + } + }{ + RelationField: field.NewRelation("Bill.Resource.Short.Sku", "models.ProductSku"), + Product: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Bill.Resource.Short.Sku.Product", "models.Product"), + }, + Discount: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Bill.Resource.Short.Sku.Discount", "models.ProductDiscount"), + }, + }, + }, + Long: struct { + field.RelationField + Sku struct { + field.RelationField + } + }{ + RelationField: field.NewRelation("Bill.Resource.Long", "models.ResourceLong"), + Sku: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Bill.Resource.Long.Sku", "models.ProductSku"), + }, + }, + Product: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Bill.Resource.Product", "models.Product"), + }, + }, + Refund: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Bill.Refund", "models.Refund"), + }, + } + + _balanceActivity.fillFieldMap() + + return _balanceActivity +} + +type balanceActivity struct { + balanceActivityDo + + ALL field.Asterisk + ID field.Int32 + UserID field.Int32 + BillID field.Int32 + AdminID field.Int32 + Amount field.String + BalancePrev field.String + BalanceCurr field.String + Remark field.String + CreatedAt field.Time + Admin balanceActivityHasOneAdmin + + User balanceActivityBelongsToUser + + Bill balanceActivityBelongsToBill + + fieldMap map[string]field.Expr +} + +func (b balanceActivity) Table(newTableName string) *balanceActivity { + b.balanceActivityDo.UseTable(newTableName) + return b.updateTableName(newTableName) +} + +func (b balanceActivity) As(alias string) *balanceActivity { + b.balanceActivityDo.DO = *(b.balanceActivityDo.As(alias).(*gen.DO)) + return b.updateTableName(alias) +} + +func (b *balanceActivity) updateTableName(table string) *balanceActivity { + b.ALL = field.NewAsterisk(table) + b.ID = field.NewInt32(table, "id") + b.UserID = field.NewInt32(table, "user_id") + b.BillID = field.NewInt32(table, "bill_id") + b.AdminID = field.NewInt32(table, "admin_id") + b.Amount = field.NewString(table, "amount") + b.BalancePrev = field.NewString(table, "balance_prev") + b.BalanceCurr = field.NewString(table, "balance_curr") + b.Remark = field.NewString(table, "remark") + b.CreatedAt = field.NewTime(table, "created_at") + + b.fillFieldMap() + + return b +} + +func (b *balanceActivity) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := b.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (b *balanceActivity) fillFieldMap() { + b.fieldMap = make(map[string]field.Expr, 12) + b.fieldMap["id"] = b.ID + b.fieldMap["user_id"] = b.UserID + b.fieldMap["bill_id"] = b.BillID + b.fieldMap["admin_id"] = b.AdminID + b.fieldMap["amount"] = b.Amount + b.fieldMap["balance_prev"] = b.BalancePrev + b.fieldMap["balance_curr"] = b.BalanceCurr + b.fieldMap["remark"] = b.Remark + b.fieldMap["created_at"] = b.CreatedAt + +} + +func (b balanceActivity) clone(db *gorm.DB) balanceActivity { + b.balanceActivityDo.ReplaceConnPool(db.Statement.ConnPool) + b.Admin.db = db.Session(&gorm.Session{Initialized: true}) + b.Admin.db.Statement.ConnPool = db.Statement.ConnPool + b.User.db = db.Session(&gorm.Session{Initialized: true}) + b.User.db.Statement.ConnPool = db.Statement.ConnPool + b.Bill.db = db.Session(&gorm.Session{Initialized: true}) + b.Bill.db.Statement.ConnPool = db.Statement.ConnPool + return b +} + +func (b balanceActivity) replaceDB(db *gorm.DB) balanceActivity { + b.balanceActivityDo.ReplaceDB(db) + b.Admin.db = db.Session(&gorm.Session{}) + b.User.db = db.Session(&gorm.Session{}) + b.Bill.db = db.Session(&gorm.Session{}) + return b +} + +type balanceActivityHasOneAdmin struct { + db *gorm.DB + + field.RelationField + + Admin struct { + field.RelationField + Roles struct { + field.RelationField + Permissions struct { + field.RelationField + Parent struct { + field.RelationField + } + Children struct { + field.RelationField + } + } + } + } + Discount struct { + field.RelationField + } + Roles struct { + field.RelationField + Permissions struct { + field.RelationField + } + } +} + +func (a balanceActivityHasOneAdmin) Where(conds ...field.Expr) *balanceActivityHasOneAdmin { + if len(conds) == 0 { + return &a + } + + exprs := make([]clause.Expression, 0, len(conds)) + for _, cond := range conds { + exprs = append(exprs, cond.BeCond().(clause.Expression)) + } + a.db = a.db.Clauses(clause.Where{Exprs: exprs}) + return &a +} + +func (a balanceActivityHasOneAdmin) WithContext(ctx context.Context) *balanceActivityHasOneAdmin { + a.db = a.db.WithContext(ctx) + return &a +} + +func (a balanceActivityHasOneAdmin) Session(session *gorm.Session) *balanceActivityHasOneAdmin { + a.db = a.db.Session(session) + return &a +} + +func (a balanceActivityHasOneAdmin) Model(m *models.BalanceActivity) *balanceActivityHasOneAdminTx { + return &balanceActivityHasOneAdminTx{a.db.Model(m).Association(a.Name())} +} + +func (a balanceActivityHasOneAdmin) Unscoped() *balanceActivityHasOneAdmin { + a.db = a.db.Unscoped() + return &a +} + +type balanceActivityHasOneAdminTx struct{ tx *gorm.Association } + +func (a balanceActivityHasOneAdminTx) Find() (result *models.User, err error) { + return result, a.tx.Find(&result) +} + +func (a balanceActivityHasOneAdminTx) Append(values ...*models.User) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Append(targetValues...) +} + +func (a balanceActivityHasOneAdminTx) Replace(values ...*models.User) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Replace(targetValues...) +} + +func (a balanceActivityHasOneAdminTx) Delete(values ...*models.User) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Delete(targetValues...) +} + +func (a balanceActivityHasOneAdminTx) Clear() error { + return a.tx.Clear() +} + +func (a balanceActivityHasOneAdminTx) Count() int64 { + return a.tx.Count() +} + +func (a balanceActivityHasOneAdminTx) Unscoped() *balanceActivityHasOneAdminTx { + a.tx = a.tx.Unscoped() + return &a +} + +type balanceActivityBelongsToUser struct { + db *gorm.DB + + field.RelationField +} + +func (a balanceActivityBelongsToUser) Where(conds ...field.Expr) *balanceActivityBelongsToUser { + if len(conds) == 0 { + return &a + } + + exprs := make([]clause.Expression, 0, len(conds)) + for _, cond := range conds { + exprs = append(exprs, cond.BeCond().(clause.Expression)) + } + a.db = a.db.Clauses(clause.Where{Exprs: exprs}) + return &a +} + +func (a balanceActivityBelongsToUser) WithContext(ctx context.Context) *balanceActivityBelongsToUser { + a.db = a.db.WithContext(ctx) + return &a +} + +func (a balanceActivityBelongsToUser) Session(session *gorm.Session) *balanceActivityBelongsToUser { + a.db = a.db.Session(session) + return &a +} + +func (a balanceActivityBelongsToUser) Model(m *models.BalanceActivity) *balanceActivityBelongsToUserTx { + return &balanceActivityBelongsToUserTx{a.db.Model(m).Association(a.Name())} +} + +func (a balanceActivityBelongsToUser) Unscoped() *balanceActivityBelongsToUser { + a.db = a.db.Unscoped() + return &a +} + +type balanceActivityBelongsToUserTx struct{ tx *gorm.Association } + +func (a balanceActivityBelongsToUserTx) Find() (result *models.User, err error) { + return result, a.tx.Find(&result) +} + +func (a balanceActivityBelongsToUserTx) Append(values ...*models.User) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Append(targetValues...) +} + +func (a balanceActivityBelongsToUserTx) Replace(values ...*models.User) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Replace(targetValues...) +} + +func (a balanceActivityBelongsToUserTx) Delete(values ...*models.User) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Delete(targetValues...) +} + +func (a balanceActivityBelongsToUserTx) Clear() error { + return a.tx.Clear() +} + +func (a balanceActivityBelongsToUserTx) Count() int64 { + return a.tx.Count() +} + +func (a balanceActivityBelongsToUserTx) Unscoped() *balanceActivityBelongsToUserTx { + a.tx = a.tx.Unscoped() + return &a +} + +type balanceActivityBelongsToBill struct { + db *gorm.DB + + field.RelationField + + User struct { + field.RelationField + } + Trade struct { + field.RelationField + User struct { + field.RelationField + } + } + Resource struct { + field.RelationField + User struct { + field.RelationField + } + Short struct { + field.RelationField + Sku struct { + field.RelationField + Product struct { + field.RelationField + } + Discount struct { + field.RelationField + } + } + } + Long struct { + field.RelationField + Sku struct { + field.RelationField + } + } + Product struct { + field.RelationField + } + } + Refund struct { + field.RelationField + } +} + +func (a balanceActivityBelongsToBill) Where(conds ...field.Expr) *balanceActivityBelongsToBill { + if len(conds) == 0 { + return &a + } + + exprs := make([]clause.Expression, 0, len(conds)) + for _, cond := range conds { + exprs = append(exprs, cond.BeCond().(clause.Expression)) + } + a.db = a.db.Clauses(clause.Where{Exprs: exprs}) + return &a +} + +func (a balanceActivityBelongsToBill) WithContext(ctx context.Context) *balanceActivityBelongsToBill { + a.db = a.db.WithContext(ctx) + return &a +} + +func (a balanceActivityBelongsToBill) Session(session *gorm.Session) *balanceActivityBelongsToBill { + a.db = a.db.Session(session) + return &a +} + +func (a balanceActivityBelongsToBill) Model(m *models.BalanceActivity) *balanceActivityBelongsToBillTx { + return &balanceActivityBelongsToBillTx{a.db.Model(m).Association(a.Name())} +} + +func (a balanceActivityBelongsToBill) Unscoped() *balanceActivityBelongsToBill { + a.db = a.db.Unscoped() + return &a +} + +type balanceActivityBelongsToBillTx struct{ tx *gorm.Association } + +func (a balanceActivityBelongsToBillTx) Find() (result *models.Bill, err error) { + return result, a.tx.Find(&result) +} + +func (a balanceActivityBelongsToBillTx) Append(values ...*models.Bill) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Append(targetValues...) +} + +func (a balanceActivityBelongsToBillTx) Replace(values ...*models.Bill) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Replace(targetValues...) +} + +func (a balanceActivityBelongsToBillTx) Delete(values ...*models.Bill) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Delete(targetValues...) +} + +func (a balanceActivityBelongsToBillTx) Clear() error { + return a.tx.Clear() +} + +func (a balanceActivityBelongsToBillTx) Count() int64 { + return a.tx.Count() +} + +func (a balanceActivityBelongsToBillTx) Unscoped() *balanceActivityBelongsToBillTx { + a.tx = a.tx.Unscoped() + return &a +} + +type balanceActivityDo struct{ gen.DO } + +func (b balanceActivityDo) Debug() *balanceActivityDo { + return b.withDO(b.DO.Debug()) +} + +func (b balanceActivityDo) WithContext(ctx context.Context) *balanceActivityDo { + return b.withDO(b.DO.WithContext(ctx)) +} + +func (b balanceActivityDo) ReadDB() *balanceActivityDo { + return b.Clauses(dbresolver.Read) +} + +func (b balanceActivityDo) WriteDB() *balanceActivityDo { + return b.Clauses(dbresolver.Write) +} + +func (b balanceActivityDo) Session(config *gorm.Session) *balanceActivityDo { + return b.withDO(b.DO.Session(config)) +} + +func (b balanceActivityDo) Clauses(conds ...clause.Expression) *balanceActivityDo { + return b.withDO(b.DO.Clauses(conds...)) +} + +func (b balanceActivityDo) Returning(value interface{}, columns ...string) *balanceActivityDo { + return b.withDO(b.DO.Returning(value, columns...)) +} + +func (b balanceActivityDo) Not(conds ...gen.Condition) *balanceActivityDo { + return b.withDO(b.DO.Not(conds...)) +} + +func (b balanceActivityDo) Or(conds ...gen.Condition) *balanceActivityDo { + return b.withDO(b.DO.Or(conds...)) +} + +func (b balanceActivityDo) Select(conds ...field.Expr) *balanceActivityDo { + return b.withDO(b.DO.Select(conds...)) +} + +func (b balanceActivityDo) Where(conds ...gen.Condition) *balanceActivityDo { + return b.withDO(b.DO.Where(conds...)) +} + +func (b balanceActivityDo) Order(conds ...field.Expr) *balanceActivityDo { + return b.withDO(b.DO.Order(conds...)) +} + +func (b balanceActivityDo) Distinct(cols ...field.Expr) *balanceActivityDo { + return b.withDO(b.DO.Distinct(cols...)) +} + +func (b balanceActivityDo) Omit(cols ...field.Expr) *balanceActivityDo { + return b.withDO(b.DO.Omit(cols...)) +} + +func (b balanceActivityDo) Join(table schema.Tabler, on ...field.Expr) *balanceActivityDo { + return b.withDO(b.DO.Join(table, on...)) +} + +func (b balanceActivityDo) LeftJoin(table schema.Tabler, on ...field.Expr) *balanceActivityDo { + return b.withDO(b.DO.LeftJoin(table, on...)) +} + +func (b balanceActivityDo) RightJoin(table schema.Tabler, on ...field.Expr) *balanceActivityDo { + return b.withDO(b.DO.RightJoin(table, on...)) +} + +func (b balanceActivityDo) Group(cols ...field.Expr) *balanceActivityDo { + return b.withDO(b.DO.Group(cols...)) +} + +func (b balanceActivityDo) Having(conds ...gen.Condition) *balanceActivityDo { + return b.withDO(b.DO.Having(conds...)) +} + +func (b balanceActivityDo) Limit(limit int) *balanceActivityDo { + return b.withDO(b.DO.Limit(limit)) +} + +func (b balanceActivityDo) Offset(offset int) *balanceActivityDo { + return b.withDO(b.DO.Offset(offset)) +} + +func (b balanceActivityDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *balanceActivityDo { + return b.withDO(b.DO.Scopes(funcs...)) +} + +func (b balanceActivityDo) Unscoped() *balanceActivityDo { + return b.withDO(b.DO.Unscoped()) +} + +func (b balanceActivityDo) Create(values ...*models.BalanceActivity) error { + if len(values) == 0 { + return nil + } + return b.DO.Create(values) +} + +func (b balanceActivityDo) CreateInBatches(values []*models.BalanceActivity, batchSize int) error { + return b.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (b balanceActivityDo) Save(values ...*models.BalanceActivity) error { + if len(values) == 0 { + return nil + } + return b.DO.Save(values) +} + +func (b balanceActivityDo) First() (*models.BalanceActivity, error) { + if result, err := b.DO.First(); err != nil { + return nil, err + } else { + return result.(*models.BalanceActivity), nil + } +} + +func (b balanceActivityDo) Take() (*models.BalanceActivity, error) { + if result, err := b.DO.Take(); err != nil { + return nil, err + } else { + return result.(*models.BalanceActivity), nil + } +} + +func (b balanceActivityDo) Last() (*models.BalanceActivity, error) { + if result, err := b.DO.Last(); err != nil { + return nil, err + } else { + return result.(*models.BalanceActivity), nil + } +} + +func (b balanceActivityDo) Find() ([]*models.BalanceActivity, error) { + result, err := b.DO.Find() + return result.([]*models.BalanceActivity), err +} + +func (b balanceActivityDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.BalanceActivity, err error) { + buf := make([]*models.BalanceActivity, 0, batchSize) + err = b.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (b balanceActivityDo) FindInBatches(result *[]*models.BalanceActivity, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return b.DO.FindInBatches(result, batchSize, fc) +} + +func (b balanceActivityDo) Attrs(attrs ...field.AssignExpr) *balanceActivityDo { + return b.withDO(b.DO.Attrs(attrs...)) +} + +func (b balanceActivityDo) Assign(attrs ...field.AssignExpr) *balanceActivityDo { + return b.withDO(b.DO.Assign(attrs...)) +} + +func (b balanceActivityDo) Joins(fields ...field.RelationField) *balanceActivityDo { + for _, _f := range fields { + b = *b.withDO(b.DO.Joins(_f)) + } + return &b +} + +func (b balanceActivityDo) Preload(fields ...field.RelationField) *balanceActivityDo { + for _, _f := range fields { + b = *b.withDO(b.DO.Preload(_f)) + } + return &b +} + +func (b balanceActivityDo) FirstOrInit() (*models.BalanceActivity, error) { + if result, err := b.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*models.BalanceActivity), nil + } +} + +func (b balanceActivityDo) FirstOrCreate() (*models.BalanceActivity, error) { + if result, err := b.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*models.BalanceActivity), nil + } +} + +func (b balanceActivityDo) FindByPage(offset int, limit int) (result []*models.BalanceActivity, count int64, err error) { + result, err = b.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = b.Offset(-1).Limit(-1).Count() + return +} + +func (b balanceActivityDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = b.Count() + if err != nil { + return + } + + err = b.Offset(offset).Limit(limit).Scan(result) + return +} + +func (b balanceActivityDo) Scan(result interface{}) (err error) { + return b.DO.Scan(result) +} + +func (b balanceActivityDo) Delete(models ...*models.BalanceActivity) (result gen.ResultInfo, err error) { + return b.DO.Delete(models) +} + +func (b *balanceActivityDo) withDO(do gen.Dao) *balanceActivityDo { + b.DO = *do.(*gen.DO) + return b +} diff --git a/web/queries/gen.go b/web/queries/gen.go index 66cbdf6..7e25714 100644 --- a/web/queries/gen.go +++ b/web/queries/gen.go @@ -20,6 +20,7 @@ var ( Admin *admin AdminRole *adminRole Announcement *announcement + BalanceActivity *balanceActivity Bill *bill Channel *channel Client *client @@ -57,6 +58,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) { Admin = &Q.Admin AdminRole = &Q.AdminRole Announcement = &Q.Announcement + BalanceActivity = &Q.BalanceActivity Bill = &Q.Bill Channel = &Q.Channel Client = &Q.Client @@ -95,6 +97,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query { Admin: newAdmin(db, opts...), AdminRole: newAdminRole(db, opts...), Announcement: newAnnouncement(db, opts...), + BalanceActivity: newBalanceActivity(db, opts...), Bill: newBill(db, opts...), Channel: newChannel(db, opts...), Client: newClient(db, opts...), @@ -134,6 +137,7 @@ type Query struct { Admin admin AdminRole adminRole Announcement announcement + BalanceActivity balanceActivity Bill bill Channel channel Client client @@ -174,6 +178,7 @@ func (q *Query) clone(db *gorm.DB) *Query { Admin: q.Admin.clone(db), AdminRole: q.AdminRole.clone(db), Announcement: q.Announcement.clone(db), + BalanceActivity: q.BalanceActivity.clone(db), Bill: q.Bill.clone(db), Channel: q.Channel.clone(db), Client: q.Client.clone(db), @@ -221,6 +226,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query { Admin: q.Admin.replaceDB(db), AdminRole: q.AdminRole.replaceDB(db), Announcement: q.Announcement.replaceDB(db), + BalanceActivity: q.BalanceActivity.replaceDB(db), Bill: q.Bill.replaceDB(db), Channel: q.Channel.replaceDB(db), Client: q.Client.replaceDB(db), @@ -258,6 +264,7 @@ type queryCtx struct { Admin *adminDo AdminRole *adminRoleDo Announcement *announcementDo + BalanceActivity *balanceActivityDo Bill *billDo Channel *channelDo Client *clientDo @@ -295,6 +302,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx { Admin: q.Admin.WithContext(ctx), AdminRole: q.AdminRole.WithContext(ctx), Announcement: q.Announcement.WithContext(ctx), + BalanceActivity: q.BalanceActivity.WithContext(ctx), Bill: q.Bill.WithContext(ctx), Channel: q.Channel.WithContext(ctx), Client: q.Client.WithContext(ctx), diff --git a/web/routes.go b/web/routes.go index 160763a..0d76089 100644 --- a/web/routes.go +++ b/web/routes.go @@ -116,7 +116,7 @@ func clientRouter(api fiber.Router) { client := api // 验证短信令牌 - client.Post("/sms/verify", handlers.SmsCode) + client.Post("/verify/sms", handlers.SmsCode) // 套餐定价查询 resource := client.Group("/resource") @@ -159,11 +159,13 @@ func adminRouter(api fiber.Router) { // user 用户 var user = api.Group("/user") user.Post("/page", handlers.PageUserByAdmin) + user.Post("/get", handlers.GetUserByAdmin) user.Post("/create", handlers.CreateUserByAdmin) user.Post("/update", handlers.UpdateUserByAdmin) user.Post("/remove", handlers.RemoveUserByAdmin) user.Post("/bind", handlers.BindAdmin) + user.Post("/update/balance", handlers.UpdateUserBalanceByAdmin) // resource 套餐 var resource = api.Group("/resource") diff --git a/web/services/admin.go b/web/services/admin.go index 6f9cad7..3c2530b 100644 --- a/web/services/admin.go +++ b/web/services/admin.go @@ -122,7 +122,7 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error { // 更新管理员基本信息 if len(simples) > 0 { _, err := tx.Admin. - Where(tx.Admin.ID.Eq(update.Id)). + Where(tx.Admin.ID.Eq(update.Id), tx.Admin.Username.Neq("admin")). UpdateSimple(simples...) if err != nil { return err @@ -154,6 +154,6 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error { } func (s *adminService) RemoveAdmin(id int32) error { - _, err := q.Admin.Where(q.Admin.ID.Eq(id)).UpdateColumn(q.Admin.DeletedAt, time.Now()) + _, err := q.Admin.Where(q.Admin.ID.Eq(id), q.Admin.Username.Neq("admin")).UpdateColumn(q.Admin.DeletedAt, time.Now()) return err } diff --git a/web/services/coupon.go b/web/services/coupon.go index f7c041d..6e29198 100644 --- a/web/services/coupon.go +++ b/web/services/coupon.go @@ -18,11 +18,11 @@ var Coupon = &couponService{} type couponService struct{} func (s *couponService) All() (result []*m.Coupon, err error) { - return q.Coupon.Find() + return q.Coupon.Order(q.Coupon.CreatedAt.Desc()).Find() } func (s *couponService) Page(req *core.PageReq) (result []*m.Coupon, count int64, err error) { - return q.Coupon.FindByPage(req.GetOffset(), req.GetLimit()) + return q.Coupon.Order(q.Coupon.CreatedAt.Desc()).FindByPage(req.GetOffset(), req.GetLimit()) } func (s *couponService) Create(data CreateCouponData) error { diff --git a/web/services/permission.go b/web/services/permission.go index eeb4df1..53ba3bf 100644 --- a/web/services/permission.go +++ b/web/services/permission.go @@ -15,5 +15,5 @@ func (r *permissionService) ListPermissions() (result []*m.Permission, err error } func (p *permissionService) PagePermissions(req core.PageReq) (result []*m.Permission, count int64, err error) { - return q.Permission.FindByPage(req.GetOffset(), req.GetLimit()) + return q.Permission.Order(q.Permission.Sort).FindByPage(req.GetOffset(), req.GetLimit()) } diff --git a/web/services/product.go b/web/services/product.go index 3aa1913..9ec66c3 100644 --- a/web/services/product.go +++ b/web/services/product.go @@ -20,7 +20,9 @@ func (s *productService) GetPrice(code string) { // 获取所有产品 func (s *productService) AllProducts() ([]*m.Product, error) { - return q.Product.Find() + return q.Product. + Order(q.Product.Sort.Asc(), q.Product.CreatedAt.Desc()). + Find() } // 新增产品 diff --git a/web/services/product_discount.go b/web/services/product_discount.go index a65aa04..3b25976 100644 --- a/web/services/product_discount.go +++ b/web/services/product_discount.go @@ -14,11 +14,11 @@ var ProductDiscount = &productDiscountService{} type productDiscountService struct{} func (s *productDiscountService) All() (result []*m.ProductDiscount, err error) { - return q.ProductDiscount.Find() + return q.ProductDiscount.Order(q.ProductDiscount.CreatedAt.Desc()).Find() } func (s *productDiscountService) Page(req *core.PageReq) (result []*m.ProductDiscount, count int64, err error) { - return q.ProductDiscount.FindByPage(req.GetOffset(), req.GetLimit()) + return q.ProductDiscount.Order(q.ProductDiscount.CreatedAt.Desc()).FindByPage(req.GetOffset(), req.GetLimit()) } func (s *productDiscountService) Create(data CreateProductDiscountData) (err error) { diff --git a/web/services/product_sku.go b/web/services/product_sku.go index 93372ff..d65cf54 100644 --- a/web/services/product_sku.go +++ b/web/services/product_sku.go @@ -20,6 +20,7 @@ func (s *productSkuService) All(product_code string) (result []*m.ProductSku, er Joins(q.ProductSku.Product). Where(q.Product.As("Product").Code.Eq(product_code)). Select(q.ProductSku.ALL). + Order(q.ProductSku.CreatedAt.Desc()). Find() } @@ -31,6 +32,7 @@ func (s *productSkuService) Page(req *core.PageReq, productId *int32) (result [] return q.ProductSku. Joins(q.ProductSku.Discount). Where(do...). + Order(q.ProductSku.CreatedAt.Desc()). FindByPage(req.GetOffset(), req.GetLimit()) } diff --git a/web/services/resource.go b/web/services/resource.go index 39f22f2..1d929dc 100644 --- a/web/services/resource.go +++ b/web/services/resource.go @@ -26,21 +26,10 @@ func (s *resourceService) CreateResourceByBalance(user *m.User, data *CreateReso return core.NewServErr("获取产品支付信息失败", err) } - newBalance := user.Balance.Sub(detail.Actual) - if newBalance.IsNegative() { - return ErrBalanceNotEnough - } - return q.Q.Transaction(func(q *q.Query) error { // 更新用户余额 - _, err = q.User. - Where( - q.User.ID.Eq(user.ID), - q.User.Balance.Eq(user.Balance), - ). - UpdateSimple(q.User.Balance.Value(newBalance)) - if err != nil { + if err := User.UpdateBalance(q, user, detail.Actual.Neg(), "余额购买产品", nil); err != nil { return core.NewServErr("更新用户余额失败", err) } @@ -273,13 +262,21 @@ func (data *CreateResourceData) TradeDetail(user *m.User) (*TradeDetail, error) return nil, err } + var discountId *int32 = nil + if discount != nil { + discountId = &discount.ID + } + var couponId *int32 = nil + if coupon != nil { + couponId = &coupon.ID + } return &TradeDetail{ data, m.TradeTypePurchase, sku.Name, amount, actual, - &discount.ID, discount, - &coupon.ID, coupon, + discountId, discount, + couponId, coupon, }, nil } diff --git a/web/services/trade.go b/web/services/trade.go index b79cf4c..bdc3295 100644 --- a/web/services/trade.go +++ b/web/services/trade.go @@ -1,8 +1,9 @@ package services import ( + "bytes" "context" - "encoding/json" + "encoding/gob" "errors" "fmt" "io" @@ -26,13 +27,18 @@ import ( "github.com/wechatpay-apiv3/wechatpay-go/services/payments/native" ) +func init() { + gob.Register(&CreateResourceData{}) + gob.Register(&UpdateBalanceData{}) +} + var Trade = &tradeService{} type tradeService struct { } // 创建交易 -func (s *tradeService) Create(user *m.User, tradeData *CreateTradeData, productData *CreateResourceData) (*CreateTradeResult, error) { +func (s *tradeService) Create(user *m.User, tradeData *CreateTradeData, productData ProductData) (*CreateTradeResult, error) { if user == nil { return nil, core.NewBizErr("用户未登录") } @@ -196,15 +202,12 @@ func (s *tradeService) Create(user *m.User, tradeData *CreateTradeData, productD } // 缓存产品数据 - serialized, err := json.Marshal(detail) - if err != nil { - return nil, core.NewServErr("序列化产品信息失败", err) - } - + w := bytes.Buffer{} + gob.NewEncoder(&w).Encode(detail) err = g.Redis.Set( context.Background(), tradeProductKey(tradeNo), - serialized, + w.Bytes(), expireIn, ).Err() if err != nil { @@ -267,14 +270,15 @@ func (s *tradeService) OnCompleteTrade(user *m.User, interNo string, outerNo str case m.TradeStatusPending: } - // 恢复购买信息 - detailStr, err := g.Redis.Get(context.Background(), tradeProductKey(interNo)).Result() + // 恢复购买信息;如果反序列化失败,检查开头 init 函数中是否注册了对应的 struct 类型 + detailBytes, err := g.Redis.Get(context.Background(), tradeProductKey(interNo)).Bytes() if err != nil { return core.NewServErr("恢复购买信息失败", err) } var detail TradeDetail - if err := json.Unmarshal([]byte(detailStr), &detail); err != nil { + r := bytes.NewReader(detailBytes) + if err := gob.NewDecoder(r).Decode(&detail); err != nil { return core.NewServErr("解析购买信息失败", err) } @@ -299,7 +303,7 @@ func (s *tradeService) OnCompleteTrade(user *m.User, interNo string, outerNo str switch trade.Type { case m.TradeTypeRecharge: // 更新用户余额 - if err := User.UpdateBalance(q, user, detail.Actual); err != nil { + if err := User.UpdateBalance(q, user, detail.Actual, "充值余额", nil); err != nil { return err } @@ -605,12 +609,12 @@ type OnTradeCompletedData struct { *TradeSuccessResult } -type ProductInfo interface { +type ProductData interface { TradeDetail(user *m.User) (*TradeDetail, error) } type TradeDetail struct { - Product ProductInfo `json:"product"` + Product ProductData `json:"product"` Type m.TradeType `json:"type"` Subject string `json:"subject"` Amount decimal.Decimal `json:"amount"` @@ -621,11 +625,6 @@ type TradeDetail struct { Coupon *m.Coupon `json:"-"` // 不应缓存 } -type CompleteEvent interface { - Check(t m.TradeType) (ProductInfo, bool) - OnTradeComplete(info ProductInfo, trade *m.Trade) error -} - type TradeErr string func (e TradeErr) Error() string { diff --git a/web/services/user.go b/web/services/user.go index eb128a1..871d70d 100644 --- a/web/services/user.go +++ b/web/services/user.go @@ -28,12 +28,28 @@ func (s *userService) Get(q *q.Query, uid int32) (*m.User, error) { return user, nil } -func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Decimal) error { +func (s *userService) UpdateBalanceByAdmin(user *m.User, newBalance decimal.Decimal, adminId *int32) error { + if user == nil { + return core.NewServErr("用户不存在") + } + + amount := newBalance.Sub(user.Balance) + if amount.IsZero() { + return nil + } + + return q.Q.Transaction(func(q *q.Query) error { + return s.UpdateBalance(q, user, amount, "管理员修改余额", adminId) + }) +} + +func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Decimal, remark string, adminId *int32) error { balance := user.Balance.Add(amount) if balance.IsNegative() { return core.NewServErr("用户余额不足") } + // 更新余额 _, err := q.User. Where( q.User.ID.Eq(user.ID), @@ -46,6 +62,19 @@ func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Dec return core.NewServErr("更新用户余额失败", err) } + // 新增动账记录 + err = q.BalanceActivity.Create(&m.BalanceActivity{ + UserID: user.ID, + AdminID: adminId, + Amount: amount.StringFixed(2), + BalancePrev: user.Balance.StringFixed(2), + BalanceCurr: balance.StringFixed(2), + Remark: &remark, + }) + if err != nil { + return core.NewServErr("新增动账记录失败", err) + } + return nil }