恢复余额功能 & 管理员修改余额功能

This commit is contained in:
2026-03-30 14:59:35 +08:00
parent 22cb2d50d3
commit 4481c581e9
27 changed files with 1246 additions and 204 deletions

View File

@@ -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` 下定义了系统所有静态权限
新增系统权限需要在数据库中配套添加权限
前端也需要新增配套权限定义
## 业务逻辑
### 订单关闭的几种方式

View File

@@ -65,6 +65,7 @@ func main() {
m.Whitelist{},
m.Inquiry{},
m.ProductDiscount{},
m.BalanceActivity{},
)
g.Execute()
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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") // 读取优惠券列表

View File

@@ -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:

View File

@@ -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{

View File

@@ -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).

View File

@@ -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,
},

View File

@@ -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,

View File

@@ -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)

View File

@@ -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"`
}
// 绑定管理员

View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -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),

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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())
}

View File

@@ -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()
}
// 新增产品

View File

@@ -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) {

View File

@@ -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())
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}