From 3fb3a8026f5b6142b88d2e8c4769e515ffe45fa7 Mon Sep 17 00:00:00 2001 From: luorijun Date: Wed, 8 Apr 2026 13:38:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8C=87=E5=AE=9A=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=20&=20=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=9D=83=E9=99=90=E7=BB=86=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/sql/fill.sql | 148 ++++++++++++++++++++++++++++++++++++--- scripts/sql/init.sql | 3 +- web/core/scopes.go | 51 ++++++++------ web/handlers/admin.go | 8 +-- web/handlers/batch.go | 72 +++++++++++++++++++ web/handlers/bill.go | 74 ++++++++++++++++++++ web/handlers/channel.go | 73 +++++++++++++++++++ web/handlers/resource.go | 145 +++++++++++++++++++++++++++++++++++++- web/handlers/trade.go | 76 ++++++++++++++++++++ web/handlers/user.go | 115 ++++++++++++++++++++++++++++++ web/models/admin.go | 1 + web/queries/admin.gen.go | 6 +- web/routes.go | 11 ++- web/services/admin.go | 64 +++++++++-------- web/services/user.go | 2 - 15 files changed, 780 insertions(+), 69 deletions(-) diff --git a/scripts/sql/fill.sql b/scripts/sql/fill.sql index d2a39a7..3b3a20e 100644 --- a/scripts/sql/fill.sql +++ b/scripts/sql/fill.sql @@ -9,13 +9,145 @@ insert into product (code, name, description) values ('short', '短效动态', ' insert into product (code, name, description) values ('long', '长效动态', '长效动态'); insert into product (code, name, description) values ('static', '长效静态', '长效静态'); +-- ==================== +-- region 权限 +-- ==================== + delete from permission where true; -insert into permission - (name, description) -values - ('permission:read', '读取权限列表'), - ('permission:write', '写入权限'), - ('admin-role:read', '读取管理员角色列表'), - ('admin-role:write', '写入管理员角色') -; + +-- -------------------------- +-- level 1 +-- -------------------------- +insert into permission (name, description, sort) values + ('permission', '权限', 1), + ('admin_role', '管理员角色', 2), + ('admin', '管理员', 3), + ('product', '产品', 4), + ('product_sku', '产品套餐', 5), + ('discount', '折扣', 6), + ('resource', '用户套餐', 7), + ('user', '用户', 8), + ('coupon', '优惠券', 9), + ('batch', '批次', 10), + ('channel', 'IP', 11), + ('trade', '交易', 12), + ('bill', '账单', 13); + +-- -------------------------- +-- level 2 +-- -------------------------- + +-- permission 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'permission' and deleted_at is null), 'permission:read', '读取权限列表', 1), + ((select id from permission where name = 'permission' and deleted_at is null), 'permission:write', '写入权限', 2); + +-- admin_role 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'admin_role' and deleted_at is null), 'admin_role:read', '读取管理员角色列表', 1), + ((select id from permission where name = 'admin_role' and deleted_at is null), 'admin_role:write', '写入管理员角色', 2); + +-- admin 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'admin' and deleted_at is null), 'admin:read', '读取管理员列表', 1), + ((select id from permission where name = 'admin' and deleted_at is null), 'admin:write', '写入管理员', 2); + +-- product 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'product' and deleted_at is null), 'product:read', '读取产品列表', 1), + ((select id from permission where name = 'product' and deleted_at is null), 'product:write', '写入产品', 2); + +-- product_sku 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'product_sku' and deleted_at is null), 'product_sku:read', '读取产品套餐列表', 1), + ((select id from permission where name = 'product_sku' and deleted_at is null), 'product_sku:write', '写入产品套餐', 2); + +-- discount 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'discount' and deleted_at is null), 'discount:read', '读取折扣列表', 1), + ((select id from permission where name = 'discount' and deleted_at is null), 'discount:write', '写入折扣', 2); + +-- resource 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'resource' and deleted_at is null), 'resource:read', '读取用户套餐列表', 1), + ((select id from permission where name = 'resource' and deleted_at is null), 'resource:write', '写入用户套餐', 2); + +-- user 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'user' and deleted_at is null), 'user:read', '读取用户列表', 1), + ((select id from permission where name = 'user' and deleted_at is null), 'user:write', '写入用户', 2); + +-- coupon 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'coupon' and deleted_at is null), 'coupon:read', '读取优惠券列表', 1), + ((select id from permission where name = 'coupon' and deleted_at is null), 'coupon:write', '写入优惠券', 2); + +-- batch 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'batch' and deleted_at is null), 'batch:read', '读取批次列表', 1), + ((select id from permission where name = 'batch' and deleted_at is null), 'batch:write', '写入批次', 2); + +-- channel 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'channel' and deleted_at is null), 'channel:read', '读取 IP 列表', 1), + ((select id from permission where name = 'channel' and deleted_at is null), 'channel:write', '写入 IP', 2); + +-- trade 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'trade' and deleted_at is null), 'trade:read', '读取交易列表', 1), + ((select id from permission where name = 'trade' and deleted_at is null), 'trade:write', '写入交易', 2); + +-- bill 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'bill' and deleted_at is null), 'bill:read', '读取账单列表', 1), + ((select id from permission where name = 'bill' and deleted_at is null), 'bill:write', '写入账单', 2); + +-- -------------------------- +-- level 3 +-- -------------------------- + +-- product_sku:write 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'product_sku:write' and deleted_at is null), 'product_sku:write:status', '更改产品套餐状态', 1); + +-- resource:read 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'resource:read' and deleted_at is null), 'resource:read:short:of-user', '读取指定用户的短效套餐列表', 1), + ((select id from permission where name = 'resource:read' and deleted_at is null), 'resource:read:long:of-user', '读取指定用户的长效套餐列表', 2); + +-- user:read 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'user:read' and deleted_at is null), 'user:read:one', '读取单个用户', 1), + ((select id from permission where name = 'user:read' and deleted_at is null), 'user:read:not-bind', '读取未绑定管理员的用户列表', 2); + +-- user:write 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'user:write' and deleted_at is null), 'user:write:balance', '写入用户余额', 1), + ((select id from permission where name = 'user:write' and deleted_at is null), 'user:write:bind', '用户认领', 2); + +-- batch:read 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'batch:read' and deleted_at is null), 'batch:read:of-user', '读取指定用户的批次列表', 1); + +-- channel:read 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'channel:read' and deleted_at is null), 'channel:read:of-user', '读取指定用户的 IP 列表', 1); + +-- trade:read 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'trade:read' and deleted_at is null), 'trade:read:of-user', '读取指定用户的交易列表', 1); + +-- bill:read 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'bill:read' and deleted_at is null), 'bill:read:of-user', '读取指定用户的账单列表', 1); + +-- -------------------------- +-- level 4 +-- -------------------------- + +-- user:write:balance 子权限 +insert into permission (parent_id, name, description, sort) values + ((select id from permission where name = 'user:write:balance' and deleted_at is null), 'user:write:balance:inc', '增加用户余额', 1), + ((select id from permission where name = 'user:write:balance' and deleted_at is null), 'user:write:balance:dec', '减少用户余额', 2); + -- endregion diff --git a/scripts/sql/init.sql b/scripts/sql/init.sql index a7910fc..b82d2cc 100644 --- a/scripts/sql/init.sql +++ b/scripts/sql/init.sql @@ -196,6 +196,7 @@ create table admin ( last_login timestamptz, last_login_ip inet, last_login_ua text, + lock bool not null default false, created_at timestamptz default current_timestamp, updated_at timestamptz default current_timestamp, deleted_at timestamptz @@ -217,6 +218,7 @@ comment on column admin.status is '状态:0-禁用,1-正常'; comment on column admin.last_login is '最后登录时间'; comment on column admin.last_login_ip is '最后登录地址'; comment on column admin.last_login_ua is '最后登录代理'; +comment on column admin.lock is '是否锁定编辑'; comment on column admin.created_at is '创建时间'; comment on column admin.updated_at is '更新时间'; comment on column admin.deleted_at is '删除时间'; @@ -775,7 +777,6 @@ comment on column product_sku.discount_id is '折扣ID'; comment on column product_sku.code is 'SKU 代码:格式为 key=value,key=value,...,其中,key:value 是 SKU 的属性,多个属性用逗号分隔'; comment on column product_sku.name is 'SKU 可读名称'; comment on column product_sku.price_min is '最低价格'; -comment on column product_sku.min is '最小购买量'; comment on column product_sku.status is 'SKU状态:0-禁用,1-正常'; comment on column product_sku.created_at is '创建时间'; comment on column product_sku.updated_at is '更新时间'; diff --git a/web/core/scopes.go b/web/core/scopes.go index d96e144..d032d28 100644 --- a/web/core/scopes.go +++ b/web/core/scopes.go @@ -26,34 +26,43 @@ const ( ScopeDiscountRead = string("discount:read") // 读取折扣列表 ScopeDiscountWrite = string("discount:write") // 写入折扣 - ScopeResource = string("resource") // 用户套餐 - ScopeResourceRead = string("resource:read") // 读取用户套餐列表 - ScopeResourceWrite = string("resource:write") // 写入用户套餐 + ScopeResource = string("resource") // 用户套餐 + ScopeResourceRead = string("resource:read") // 读取用户套餐列表 + ScopeResourceReadShortOfUser = string("resource:read:short:of-user") // 读取指定用户的短效套餐列表 + ScopeResourceReadLongOfUser = string("resource:read:long:of-user") // 读取指定用户的长效套餐列表 + ScopeResourceWrite = string("resource: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") // 用户认领 + ScopeUser = string("user") // 用户 + ScopeUserRead = string("user:read") // 读取用户列表 + ScopeUserReadOne = string("user:read:one") // 读取单个用户 + ScopeUserReadNotBind = string("user:read:not-bind") // 读取未绑定管理员的用户列表 + ScopeUserWrite = string("user:write") // 写入用户 + ScopeUserWriteBalance = string("user:write:balance") // 写入用户余额 + ScopeUserWriteBalanceInc = string("user:write:balance:inc") // 增加用户余额 + ScopeUserWriteBalanceDec = string("user:write:balance:dec") // 减少用户余额 + ScopeUserWriteBind = string("user:write:bind") // 用户认领 ScopeCoupon = string("coupon") // 优惠券 ScopeCouponRead = string("coupon:read") // 读取优惠券列表 ScopeCouponWrite = string("coupon:write") // 写入优惠券 - ScopeBatch = string("batch") // 批次 - ScopeBatchRead = string("batch:read") // 读取批次列表 - ScopeBatchWrite = string("batch:write") // 写入批次 + ScopeBatch = string("batch") // 批次 + ScopeBatchRead = string("batch:read") // 读取批次列表 + ScopeBatchReadOfUser = string("batch:read:of-user") // 读取指定用户的批次列表 + ScopeBatchWrite = string("batch:write") // 写入批次 - ScopeChannel = string("channel") // IP - ScopeChannelRead = string("channel:read") // 读取 IP 列表 - ScopeChannelWrite = string("channel:write") // 写入 IP + ScopeChannel = string("channel") // IP + ScopeChannelRead = string("channel:read") // 读取 IP 列表 + ScopeChannelReadOfUser = string("channel:read:of-user") // 读取指定用户的 IP 列表 + ScopeChannelWrite = string("channel:write") // 写入 IP - ScopeTrade = string("trade") // 交易 - ScopeTradeRead = string("trade:read") // 读取交易列表 - ScopeTradeWrite = string("trade:write") // 写入交易 + ScopeTrade = string("trade") // 交易 + ScopeTradeRead = string("trade:read") // 读取交易列表 + ScopeTradeReadOfUser = string("trade:read:of-user") // 读取指定用户的交易列表 + ScopeTradeWrite = string("trade:write") // 写入交易 - ScopeBill = string("bill") // 账单 - ScopeBillRead = string("bill:read") // 读取账单列表 - ScopeBillWrite = string("bill:write") // 写入账单 + ScopeBill = string("bill") // 账单 + ScopeBillRead = string("bill:read") // 读取账单列表 + ScopeBillReadOfUser = string("bill:read:of-user") // 读取指定用户的账单列表 + ScopeBillWrite = string("bill:write") // 写入账单 ) diff --git a/web/handlers/admin.go b/web/handlers/admin.go index 3e11bba..a1e1753 100644 --- a/web/handlers/admin.go +++ b/web/handlers/admin.go @@ -20,7 +20,7 @@ func PageAdminByAdmin(c *fiber.Ctx) error { return err } - list, total, err := s.Admin.PageAdmins(req.PageReq) + list, total, err := s.Admin.Page(req.PageReq) if err != nil { return err } @@ -62,7 +62,7 @@ func CreateAdmin(c *fiber.Ctx) error { return err } - if err := s.Admin.CreateAdmin(&req); err != nil { + if err := s.Admin.Create(&req); err != nil { return err } @@ -80,7 +80,7 @@ func UpdateAdmin(c *fiber.Ctx) error { return err } - if err := s.Admin.UpdateAdmin(&req); err != nil { + if err := s.Admin.Update(&req); err != nil { return err } @@ -98,7 +98,7 @@ func RemoveAdmin(c *fiber.Ctx) error { return err } - if err := s.Admin.RemoveAdmin(req.Id); err != nil { + if err := s.Admin.Remove(req.Id); err != nil { return err } diff --git a/web/handlers/batch.go b/web/handlers/batch.go index 5f510d0..b24b2fb 100644 --- a/web/handlers/batch.go +++ b/web/handlers/batch.go @@ -128,3 +128,75 @@ type PageBatchByAdminReq struct { CreatedAtStart *time.Time `json:"created_at_start"` CreatedAtEnd *time.Time `json:"created_at_end"` } + +// PageBatchOfUserByAdmin 分页查询指定用户的提取记录 +func PageBatchOfUserByAdmin(ctx *fiber.Ctx) error { + _, err := auth.GetAuthCtx(ctx).PermitAdmin(core.ScopeBatchReadOfUser) + if err != nil { + return err + } + + var req PageBatchOfUserByAdminReq + if err = g.Validator.ParseBody(ctx, &req); err != nil { + return err + } + + do := q.LogsUserUsage.Where(q.LogsUserUsage.UserID.Eq(req.UserID)) + if req.ResourceNo != nil { + do = do.Where(q.Resource.ResourceNo.Eq(*req.ResourceNo)) + } + if req.BatchNo != nil { + do = do.Where(q.LogsUserUsage.BatchNo.Eq(*req.BatchNo)) + } + if req.Prov != nil { + do = do.Where(q.LogsUserUsage.Prov.Eq(*req.Prov)) + } + if req.City != nil { + do = do.Where(q.LogsUserUsage.City.Eq(*req.City)) + } + if req.Isp != nil { + do = do.Where(q.LogsUserUsage.ISP.Eq(*req.Isp)) + } + if req.CreatedAtStart != nil { + t := u.DateHead(*req.CreatedAtStart) + do = do.Where(q.LogsUserUsage.Time.Gte(t)) + } + if req.CreatedAtEnd != nil { + t := u.DateTail(*req.CreatedAtEnd) + do = do.Where(q.LogsUserUsage.Time.Lte(t)) + } + + list, total, err := q.LogsUserUsage. + Joins(q.LogsUserUsage.User, q.LogsUserUsage.Resource). + Select( + q.LogsUserUsage.ALL, + q.User.As("User").Phone.As("User__phone"), + q.User.As("User").Name.As("User__name"), + q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"), + ). + Where(do). + Order(q.LogsUserUsage.Time.Desc()). + FindByPage(req.GetOffset(), req.GetLimit()) + if err != nil { + return ctx.JSON(core.NewBizErr("获取数据失败", err)) + } + + return ctx.JSON(c.PageResp{ + List: list, + Total: int(total), + Page: req.GetPage(), + Size: req.GetSize(), + }) +} + +type PageBatchOfUserByAdminReq struct { + c.PageReq + UserID int32 `json:"user_id" validate:"required"` + ResourceNo *string `json:"resource_no"` + BatchNo *string `json:"batch_no"` + Prov *string `json:"prov"` + City *string `json:"city"` + Isp *string `json:"isp"` + CreatedAtStart *time.Time `json:"created_at_start"` + CreatedAtEnd *time.Time `json:"created_at_end"` +} diff --git a/web/handlers/bill.go b/web/handlers/bill.go index 705464f..033367a 100644 --- a/web/handlers/bill.go +++ b/web/handlers/bill.go @@ -101,6 +101,80 @@ type PageBillByAdminReq struct { SkuCode *string `json:"sku_code,omitempty"` } +// PageBillOfUserByAdmin 分页查询指定用户账单 +func PageBillOfUserByAdmin(c *fiber.Ctx) error { + // 检查权限 + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeBillReadOfUser) + if err != nil { + return err + } + + // 解析请求参数 + req := new(PageBillOfUserByAdminReq) + if err := g.Validator.ParseBody(c, req); err != nil { + return err + } + + // 构造查询条件 + do := q.Bill.Where(q.Bill.UserID.Eq(req.UserID)) + if req.TradeInnerNo != nil { + do = do.Where(q.Trade.As("Trade").InnerNo.Eq(*req.TradeInnerNo)) + } + if req.ResourceNo != nil { + do = do.Where(q.Resource.As("Resource").ResourceNo.Eq(*req.ResourceNo)) + } + if req.BillNo != nil { + do = do.Where(q.Bill.BillNo.Eq(*req.BillNo)) + } + if req.CreatedAtStart != nil { + time := u.DateHead(*req.CreatedAtStart) + do = do.Where(q.Bill.CreatedAt.Gte(time)) + } + if req.CreatedAtEnd != nil { + time := u.DateHead(*req.CreatedAtEnd) + do = do.Where(q.Bill.CreatedAt.Lte(time)) + } + + // 查询账单列表 + list, total, err := q.Bill. + Joins( + q.Bill.Resource, + q.Bill.Trade, + q.Bill.Resource.Short, + q.Bill.Resource.Long, + ). + Select( + q.Bill.ALL, + 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). + Order(q.Bill.CreatedAt.Desc()). + FindByPage(req.GetOffset(), req.GetLimit()) + if err != nil { + return err + } + + // 返回结果 + return c.JSON(core.PageResp{ + List: list, + Total: int(total), + Page: req.GetPage(), + Size: req.GetSize(), + }) +} + +type PageBillOfUserByAdminReq struct { + core.PageReq + UserID int32 `json:"user_id" validate:"required"` + TradeInnerNo *string `json:"trade_inner_no,omitempty"` + ResourceNo *string `json:"resource_no,omitempty"` + BillNo *string `json:"bill_no,omitempty"` + CreatedAtStart *time.Time `json:"created_at_start,omitempty"` + CreatedAtEnd *time.Time `json:"created_at_end,omitempty"` +} + // ListBill 获取账单列表 func ListBill(c *fiber.Ctx) error { // 检查权限 diff --git a/web/handlers/channel.go b/web/handlers/channel.go index 0bb3e62..f8d7d56 100644 --- a/web/handlers/channel.go +++ b/web/handlers/channel.go @@ -271,3 +271,76 @@ func RemoveChannels(c *fiber.Ctx) error { type RemoveChannelsReq struct { Batch string `json:"batch" validate:"required"` } + +// PageChannelOfUserByAdmin 分页查询指定用户的通道 +func PageChannelOfUserByAdmin(c *fiber.Ctx) error { + // 检查权限 + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeChannelReadOfUser) + if err != nil { + return err + } + + // 解析请求参数 + var req PageChannelOfUserByAdminReq + if err := g.Validator.ParseBody(c, &req); err != nil { + return err + } + + // 构建查询条件 + do := q.Channel.Where(q.Channel.UserID.Eq(req.UserID)) + if req.ResourceNo != nil { + do = do.Where(q.Resource.As("Resource").ResourceNo.Eq(*req.ResourceNo)) + } + if req.BatchNo != nil { + do = do.Where(q.Channel.BatchNo.Eq(*req.BatchNo)) + } + if req.ProxyHost != nil { + do = do.Where(q.Channel.Host.Eq(*req.ProxyHost)) + } + if req.ProxyPort != nil { + do = do.Where(q.Channel.Port.Eq(*req.ProxyPort)) + } + if req.ExpiredAtStart != nil { + t := u.DateHead(*req.ExpiredAtStart) + do = do.Where(q.Channel.ExpiredAt.Gte(t)) + } + if req.ExpiredAtEnd != nil { + t := u.DateHead(*req.ExpiredAtEnd) + do = do.Where(q.Channel.ExpiredAt.Lte(t)) + } + + // 查询通道列表 + list, total, err := q.Channel. + Joins(q.Channel.User, q.Channel.Resource). + Select( + q.Channel.ALL, + q.Resource.As("Resource").ResourceNo.As("Resource__resource_no"), + q.User.As("User").Phone.As("User__phone"), + 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 + } + + // 返回结果 + return c.JSON(core.PageResp{ + List: list, + Total: int(total), + Page: req.GetPage(), + Size: req.GetSize(), + }) +} + +type PageChannelOfUserByAdminReq struct { + core.PageReq + UserID int32 `json:"user_id" validate:"required"` + ResourceNo *string `json:"resource_no"` + BatchNo *string `json:"batch_no"` + ProxyHost *string `json:"proxy_host"` + ProxyPort *uint16 `json:"proxy_port"` + ExpiredAtStart *time.Time `json:"expired_at_start"` + ExpiredAtEnd *time.Time `json:"expired_at_end"` +} diff --git a/web/handlers/resource.go b/web/handlers/resource.go index ec0a988..8febacf 100644 --- a/web/handlers/resource.go +++ b/web/handlers/resource.go @@ -393,6 +393,148 @@ type PageResourceLongByAdminReq struct { Expired *bool `json:"expired" form:"expired"` } +// PageResourceShortOfUserByAdmin 分页查询指定用户的短效套餐 +func PageResourceShortOfUserByAdmin(c *fiber.Ctx) error { + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceReadShortOfUser) + if err != nil { + return err + } + + var req PageResourceShortOfUserByAdminReq + if err = g.Validator.ParseBody(c, &req); err != nil { + return err + } + + do := q.Resource.Where(q.Resource.UserID.Eq(req.UserID)) + if req.ResourceNo != nil { + do = do.Where(q.Resource.ResourceNo.Eq(*req.ResourceNo)) + } + if req.Active != nil { + do = do.Where(q.Resource.Active.Is(*req.Active)) + } + if req.Mode != nil { + do = do.Where(q.ResourceShort.As("Short").Type.Eq(int(*req.Mode))) + } + if req.CreatedAtStart != nil { + t := u.DateHead(*req.CreatedAtStart) + do = do.Where(q.Resource.CreatedAt.Gte(t)) + } + if req.CreatedAtEnd != nil { + t := u.DateTail(*req.CreatedAtEnd) + do = do.Where(q.Resource.CreatedAt.Lte(t)) + } + + list, total, err := q.Resource. + Joins(q.Resource.User, q.Resource.Short, q.Resource.Short.Sku). + Select( + q.Resource.ALL, + q.User.As("User").Phone.As("User__phone"), + q.User.As("User").Name.As("User__name"), + q.ResourceShort.As("Short").Type.As("Short__type"), + q.ResourceShort.As("Short").Live.As("Short__live"), + q.ResourceShort.As("Short").Quota.As("Short__quota"), + q.ResourceShort.As("Short").Used.As("Short__used"), + q.ResourceShort.As("Short").Daily.As("Short__daily"), + q.ResourceShort.As("Short").LastAt.As("Short__last_at"), + q.ResourceShort.As("Short").ExpireAt.As("Short__expire_at"), + q.ProductSku.As("Short__Sku").Name.As("Short__Sku__name"), + ). + Where(q.Resource.Type.Eq(int(m.ResourceTypeShort)), do). + Order(q.Resource.CreatedAt.Desc()). + FindByPage(req.GetOffset(), req.GetLimit()) + if err != nil { + return err + } + + return c.JSON(core.PageResp{ + List: list, + Total: int(total), + Page: req.GetPage(), + Size: req.GetSize(), + }) +} + +type PageResourceShortOfUserByAdminReq struct { + core.PageReq + UserID int32 `json:"user_id" validate:"required"` + ResourceNo *string `json:"resource_no"` + Active *bool `json:"active"` + Mode *int `json:"mode"` + CreatedAtStart *time.Time `json:"created_at_start"` + CreatedAtEnd *time.Time `json:"created_at_end"` +} + +// PageResourceLongOfUserByAdmin 分页查询指定用户的长效套餐 +func PageResourceLongOfUserByAdmin(c *fiber.Ctx) error { + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceReadLongOfUser) + if err != nil { + return err + } + + var req PageResourceLongOfUserByAdminReq + if err = g.Validator.ParseBody(c, &req); err != nil { + return err + } + + do := q.Resource.Where(q.Resource.UserID.Eq(req.UserID)) + if req.ResourceNo != nil { + do = do.Where(q.Resource.ResourceNo.Eq(*req.ResourceNo)) + } + if req.Active != nil { + do = do.Where(q.Resource.Active.Is(*req.Active)) + } + if req.Mode != nil { + do = do.Where(q.ResourceLong.As("Long").Type.Eq(*req.Mode)) + } + if req.CreatedAtStart != nil { + t := u.DateHead(*req.CreatedAtStart) + do = do.Where(q.Resource.CreatedAt.Gte(t)) + } + if req.CreatedAtEnd != nil { + t := u.DateTail(*req.CreatedAtEnd) + do = do.Where(q.Resource.CreatedAt.Lte(t)) + } + + list, total, err := q.Resource. + Joins(q.Resource.User, q.Resource.Long, q.Resource.Long.Sku). + Select( + q.Resource.ALL, + q.User.As("User").Phone.As("User__phone"), + q.User.As("User").Name.As("User__name"), + q.ResourceLong.As("Long").Type.As("Long__type"), + q.ResourceLong.As("Long").Live.As("Long__live"), + q.ResourceLong.As("Long").Quota.As("Long__quota"), + q.ResourceLong.As("Long").Used.As("Long__used"), + q.ResourceLong.As("Long").Daily.As("Long__daily"), + q.ResourceLong.As("Long").LastAt.As("Long__last_at"), + q.ResourceLong.As("Long").ExpireAt.As("Long__expire_at"), + q.ProductSku.As("Long__Sku").Name.As("Long__Sku__name"), + ). + Where(q.Resource.Type.Eq(int(m.ResourceTypeLong)), do). + Order(q.Resource.CreatedAt.Desc()). + FindByPage(req.GetOffset(), req.GetLimit()) + if err != nil { + return err + } + + return c.JSON(core.PageResp{ + List: list, + Total: int(total), + Page: req.GetPage(), + Size: req.GetSize(), + }) +} + +type PageResourceLongOfUserByAdminReq struct { + core.PageReq + UserID int32 `json:"user_id" validate:"required"` + ResourceNo *string `json:"resource_no"` + Active *bool `json:"active"` + Mode *int `json:"mode"` + CreatedAtStart *time.Time `json:"created_at_start"` + CreatedAtEnd *time.Time `json:"created_at_end"` +} + // AllActiveResource 所有可用套餐 func AllActiveResource(c *fiber.Ctx) error { // 检查权限 @@ -449,9 +591,6 @@ func AllActiveResource(c *fiber.Ctx) error { return c.JSON(resources) } -type AllResourceReq struct { -} - func UpdateResourceByAdmin(c *fiber.Ctx) error { _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeResourceWrite) if err != nil { diff --git a/web/handlers/trade.go b/web/handlers/trade.go index b343e2c..d4a87e0 100644 --- a/web/handlers/trade.go +++ b/web/handlers/trade.go @@ -97,6 +97,82 @@ type PageTradeByAdminReq struct { CreatedAtEnd *time.Time `json:"created_at_end,omitempty"` } +// PageTradeOfUserByAdmin 分页查询指定用户的订单 +func PageTradeOfUserByAdmin(c *fiber.Ctx) error { + // 检查权限 + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeTradeReadOfUser) + if err != nil { + return err + } + + // 解析请求参数 + req := new(PageTradeOfUserByAdminReq) + if err := g.Validator.ParseBody(c, req); err != nil { + return err + } + + // 构建查询语句 + do := q.Trade.Where(q.Trade.UserID.Eq(req.UserID)) + if req.InnerNo != nil { + do = do.Where(q.Trade.InnerNo.Eq(*req.InnerNo)) + } + if req.OuterNo != nil { + do = do.Where(q.Trade.OuterNo.Eq(*req.OuterNo)) + } + if req.Method != nil { + do = do.Where(q.Trade.Method.Eq(*req.Method)) + } + if req.Platform != nil { + do = do.Where(q.Trade.Platform.Eq(*req.Platform)) + } + if req.Status != nil { + do = do.Where(q.Trade.Status.Eq(*req.Status)) + } + if req.CreatedAtStart != nil { + time := u.DateHead(*req.CreatedAtStart) + do = do.Where(q.Trade.CreatedAt.Gte(time)) + } + if req.CreatedAtEnd != nil { + time := u.DateTail(*req.CreatedAtEnd) + do = do.Where(q.Trade.CreatedAt.Lte(time)) + } + + // 查询订单列表 + list, total, err := q.Trade. + Joins(q.Trade.User). + Select( + q.Trade.ALL, + q.User.As("User").Phone.As("User__phone"), + 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 + } + + // 返回结果 + return c.JSON(core.PageResp{ + List: list, + Total: int(total), + Page: req.GetPage(), + Size: req.GetSize(), + }) +} + +type PageTradeOfUserByAdminReq struct { + core.PageReq + UserID int32 `json:"user_id" validate:"required"` + InnerNo *string `json:"inner_no,omitempty"` + OuterNo *string `json:"outer_no,omitempty"` + Method *int `json:"method,omitempty"` + Platform *int `json:"platform,omitempty"` + Status *int `json:"status,omitempty"` + CreatedAtStart *time.Time `json:"created_at_start,omitempty"` + CreatedAtEnd *time.Time `json:"created_at_end,omitempty"` +} + // 创建订单 func TradeCreate(c *fiber.Ctx) error { // 检查权限 diff --git a/web/handlers/user.go b/web/handlers/user.go index 5c3c38a..bbe41a4 100644 --- a/web/handlers/user.go +++ b/web/handlers/user.go @@ -405,3 +405,118 @@ type UpdatePasswordReq struct { Code string `json:"code"` Password string `json:"password"` } + +// PageUserNotBindByAdmin 分页获取未绑定管理员的用户 +func PageUserNotBindByAdmin(c *fiber.Ctx) error { + // 检查权限 + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserReadNotBind) + if err != nil { + return err + } + + // 解析请求参数 + req := new(PageUserNotBindByAdminReq) + if err := g.Validator.ParseBody(c, req); err != nil { + return err + } + + // 构建查询条件(强制过滤未绑定管理员的用户) + do := q.User.Where(q.User.AdminID.IsNull()) + if req.Phone != nil { + do = do.Where(q.User.Phone.Eq(*req.Phone)) + } + + // 查询用户列表 + users, total, err := q.User. + Omit(q.User.Password, q.User.IDNo). + Where(do). + Order(q.User.CreatedAt.Desc()). + FindByPage(req.GetOffset(), req.GetLimit()) + if err != nil { + return err + } + + // 返回结果 + return c.JSON(core.PageResp{ + Total: int(total), + Page: req.GetPage(), + Size: req.GetSize(), + List: users, + }) +} + +type PageUserNotBindByAdminReq struct { + core.PageReq + Phone *string `json:"phone,omitempty"` +} + +// UpdateUserBalanceIncByAdmin 管理员增加用户余额 +func UpdateUserBalanceIncByAdmin(c *fiber.Ctx) error { + authCtx, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWriteBalance) + if err != nil { + return err + } + + var req UpdateUserBalanceChangeByAdminData + if err := g.Validator.ParseBody(c, &req); err != nil { + return err + } + + amount, err := decimal.NewFromString(req.Amount) + if err != nil { + return err + } + if !amount.IsPositive() { + return core.NewBizErr("金额必须为正数") + } + + user, err := s.User.Get(q.Q, req.UserID) + if err != nil { + return err + } + + newBalance := user.Balance.Add(amount) + if err := s.User.UpdateBalanceByAdmin(user, newBalance, &authCtx.Admin.ID); err != nil { + return err + } + + return c.JSON(nil) +} + +// UpdateUserBalanceDecByAdmin 管理员减少用户余额 +func UpdateUserBalanceDecByAdmin(c *fiber.Ctx) error { + authCtx, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWriteBalance) + if err != nil { + return err + } + + var req UpdateUserBalanceChangeByAdminData + if err := g.Validator.ParseBody(c, &req); err != nil { + return err + } + + amount, err := decimal.NewFromString(req.Amount) + if err != nil { + return err + } + if !amount.IsPositive() { + return core.NewBizErr("金额必须为正数") + } + + user, err := s.User.Get(q.Q, req.UserID) + if err != nil { + return err + } + + newBalance := user.Balance.Sub(amount) + if err := s.User.UpdateBalanceByAdmin(user, newBalance, &authCtx.Admin.ID); err != nil { + return err + } + + return c.JSON(nil) +} + +type UpdateUserBalanceChangeByAdminData struct { + UserID int32 `json:"user_id" validate:"required"` + Amount string `json:"amount" validate:"required"` +} diff --git a/web/models/admin.go b/web/models/admin.go index 5662d5a..134b943 100644 --- a/web/models/admin.go +++ b/web/models/admin.go @@ -20,6 +20,7 @@ type Admin struct { LastLogin *time.Time `json:"last_login,omitempty" gorm:"column:last_login"` // 最后登录时间 LastLoginIP *orm.Inet `json:"last_login_ip,omitempty" gorm:"column:last_login_ip"` // 最后登录地址 LastLoginUA *string `json:"last_login_ua,omitempty" gorm:"column:last_login_ua"` // 最后登录代理 + Lock bool `json:"lock" gorm:"column:lock"` // 是否锁定编辑 Roles []*AdminRole `json:"roles" gorm:"many2many:link_admin_role"` } diff --git a/web/queries/admin.gen.go b/web/queries/admin.gen.go index 21a1be2..2360338 100644 --- a/web/queries/admin.gen.go +++ b/web/queries/admin.gen.go @@ -41,6 +41,7 @@ func newAdmin(db *gorm.DB, opts ...gen.DOOption) admin { _admin.LastLogin = field.NewTime(tableName, "last_login") _admin.LastLoginIP = field.NewField(tableName, "last_login_ip") _admin.LastLoginUA = field.NewString(tableName, "last_login_ua") + _admin.Lock = field.NewBool(tableName, "lock") _admin.Roles = adminManyToManyRoles{ db: db.Session(&gorm.Session{}), @@ -91,6 +92,7 @@ type admin struct { LastLogin field.Time LastLoginIP field.Field LastLoginUA field.String + Lock field.Bool Roles adminManyToManyRoles fieldMap map[string]field.Expr @@ -122,6 +124,7 @@ func (a *admin) updateTableName(table string) *admin { a.LastLogin = field.NewTime(table, "last_login") a.LastLoginIP = field.NewField(table, "last_login_ip") a.LastLoginUA = field.NewString(table, "last_login_ua") + a.Lock = field.NewBool(table, "lock") a.fillFieldMap() @@ -138,7 +141,7 @@ func (a *admin) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (a *admin) fillFieldMap() { - a.fieldMap = make(map[string]field.Expr, 15) + a.fieldMap = make(map[string]field.Expr, 16) a.fieldMap["id"] = a.ID a.fieldMap["created_at"] = a.CreatedAt a.fieldMap["updated_at"] = a.UpdatedAt @@ -153,6 +156,7 @@ func (a *admin) fillFieldMap() { a.fieldMap["last_login"] = a.LastLogin a.fieldMap["last_login_ip"] = a.LastLoginIP a.fieldMap["last_login_ua"] = a.LastLoginUA + a.fieldMap["lock"] = a.Lock } diff --git a/web/routes.go b/web/routes.go index b21fe86..e287ef3 100644 --- a/web/routes.go +++ b/web/routes.go @@ -159,35 +159,44 @@ func adminRouter(api fiber.Router) { // user 用户 var user = api.Group("/user") user.Post("/page", handlers.PageUserByAdmin) + user.Post("/page/not-bind", handlers.PageUserNotBindByAdmin) 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/bind", handlers.BindAdmin) user.Post("/update/balance", handlers.UpdateUserBalanceByAdmin) + user.Post("/update/balance-inc", handlers.UpdateUserBalanceIncByAdmin) + user.Post("/update/balance-dec", handlers.UpdateUserBalanceDecByAdmin) // resource 套餐 var resource = api.Group("/resource") resource.Post("/short/page", handlers.PageResourceShortByAdmin) + resource.Post("/short/page/of-user", handlers.PageResourceShortOfUserByAdmin) resource.Post("/long/page", handlers.PageResourceLongByAdmin) + resource.Post("/long/page/of-user", handlers.PageResourceLongOfUserByAdmin) resource.Post("/update", handlers.UpdateResourceByAdmin) // batch 批次 var batch = api.Group("/batch") batch.Post("/page", handlers.PageBatchByAdmin) + batch.Post("/page/of-user", handlers.PageBatchOfUserByAdmin) // channel 通道 var channel = api.Group("/channel") channel.Post("/page", handlers.PageChannelByAdmin) + channel.Post("/page/of-user", handlers.PageChannelOfUserByAdmin) // trade 交易 var trade = api.Group("/trade") trade.Post("/page", handlers.PageTradeByAdmin) + trade.Post("/page/of-user", handlers.PageTradeOfUserByAdmin) // bill 账单 var bill = api.Group("/bill") bill.Post("/page", handlers.PageBillByAdmin) + bill.Post("/page/of-user", handlers.PageBillOfUserByAdmin) // product 产品 var product = api.Group("/product") diff --git a/web/services/admin.go b/web/services/admin.go index 3c2530b..2dc5bc4 100644 --- a/web/services/admin.go +++ b/web/services/admin.go @@ -15,7 +15,7 @@ var Admin = &adminService{} type adminService struct{} -func (s *adminService) PageAdmins(req core.PageReq) (result []*m.Admin, count int64, err error) { +func (s *adminService) Page(req core.PageReq) (result []*m.Admin, count int64, err error) { return q.Admin. Preload(q.Admin.Roles). Omit(q.Admin.Password). @@ -30,25 +30,14 @@ func (s *adminService) All() (result []*m.Admin, err error) { Find() } -type CreateAdmin struct { - Username string `json:"username" validate:"required,min=3,max=50"` - Password string `json:"password" validate:"required,min=6,max=50"` - Name *string `json:"name"` - Avatar *string `json:"avatar"` - Phone *string `json:"phone"` - Email *string `json:"email"` - Status *m.AdminStatus `json:"status"` - Roles []int32 `json:"roles"` -} - -func (s *adminService) CreateAdmin(create *CreateAdmin) error { +func (s *adminService) Create(create *CreateAdmin) error { // 哈希密码 hash, err := bcrypt.GenerateFromPassword([]byte(create.Password), bcrypt.DefaultCost) if err != nil { return core.NewServErr("密码加密失败", err) } - return q.Q.Transaction(func(tx *q.Query) error { + return q.Q.Transaction(func(q *q.Query) error { // 创建管理员 admin := &m.Admin{ Username: create.Username, @@ -59,7 +48,7 @@ func (s *adminService) CreateAdmin(create *CreateAdmin) error { Email: create.Email, Status: u.Else(create.Status, m.AdminStatusEnabled), } - if err := tx.Admin.Create(admin); err != nil { + if err := q.Admin.Create(admin); err != nil { return err } @@ -72,7 +61,7 @@ func (s *adminService) CreateAdmin(create *CreateAdmin) error { RoleID: roleID, } } - if err := tx.LinkAdminRole.CreateInBatches(links, 1000); err != nil { + if err := q.LinkAdminRole.CreateInBatches(links, 1000); err != nil { return err } } @@ -81,18 +70,18 @@ func (s *adminService) CreateAdmin(create *CreateAdmin) error { }) } -type UpdateAdmin struct { - Id int32 `json:"id" validate:"required"` - Password *string `json:"password"` +type CreateAdmin struct { + Username string `json:"username" validate:"required,min=3,max=50"` + Password string `json:"password" validate:"required,min=6,max=50"` Name *string `json:"name"` Avatar *string `json:"avatar"` Phone *string `json:"phone"` Email *string `json:"email"` Status *m.AdminStatus `json:"status"` - Roles *[]int32 `json:"roles"` + Roles []int32 `json:"roles"` } -func (s *adminService) UpdateAdmin(update *UpdateAdmin) error { +func (s *adminService) Update(update *UpdateAdmin) error { simples := make([]field.AssignExpr, 0) if update.Password != nil { @@ -118,11 +107,14 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error { simples = append(simples, q.Admin.Status.Value(int(*update.Status))) } - return q.Q.Transaction(func(tx *q.Query) error { + return q.Q.Transaction(func(q *q.Query) error { // 更新管理员基本信息 if len(simples) > 0 { - _, err := tx.Admin. - Where(tx.Admin.ID.Eq(update.Id), tx.Admin.Username.Neq("admin")). + _, err := q.Admin. + Where( + q.Admin.ID.Eq(update.Id), + q.Admin.Lock.Is(false), + ). UpdateSimple(simples...) if err != nil { return err @@ -132,7 +124,7 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error { // 更新角色关联 if update.Roles != nil { roles := *update.Roles - if _, err := tx.LinkAdminRole.Where(tx.LinkAdminRole.AdminID.Eq(update.Id)).Delete(); err != nil { + if _, err := q.LinkAdminRole.Where(q.LinkAdminRole.AdminID.Eq(update.Id)).Delete(); err != nil { return err } if len(roles) > 0 { @@ -143,7 +135,7 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error { RoleID: roleID, } } - if err := tx.LinkAdminRole.CreateInBatches(links, 1000); err != nil { + if err := q.LinkAdminRole.CreateInBatches(links, 1000); err != nil { return err } } @@ -153,7 +145,23 @@ func (s *adminService) UpdateAdmin(update *UpdateAdmin) error { }) } -func (s *adminService) RemoveAdmin(id int32) error { - _, err := q.Admin.Where(q.Admin.ID.Eq(id), q.Admin.Username.Neq("admin")).UpdateColumn(q.Admin.DeletedAt, time.Now()) +type UpdateAdmin struct { + Id int32 `json:"id" validate:"required"` + Password *string `json:"password"` + Name *string `json:"name"` + Avatar *string `json:"avatar"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Status *m.AdminStatus `json:"status"` + Roles *[]int32 `json:"roles"` +} + +func (s *adminService) Remove(id int32) error { + _, err := q.Admin. + Where( + q.Admin.ID.Eq(id), + q.Admin.Lock.Is(false), + ). + UpdateColumn(q.Admin.DeletedAt, time.Now()) return err } diff --git a/web/services/user.go b/web/services/user.go index 871d70d..1a94770 100644 --- a/web/services/user.go +++ b/web/services/user.go @@ -118,7 +118,6 @@ func (s *userService) CreateByAdmin(data CreateUserByAdminData) error { Email: data.Email, Password: hashedPwd, Source: &source, - Name: data.Name, Avatar: data.Avatar, Status: u.Else(data.Status, m.UserStatusEnabled), ContactQQ: data.ContactQQ, @@ -141,7 +140,6 @@ type CreateUserByAdminData struct { Username *string `json:"username"` Email *string `json:"email"` Password *string `json:"password"` - Name *string `json:"name"` Avatar *string `json:"avatar"` Status *m.UserStatus `json:"status"` ContactQQ *string `json:"contact_qq"`