修复账单与交易接口问题 & 管理员权限的身份限制

This commit is contained in:
2026-04-11 14:10:44 +08:00
parent 624a5ff2c0
commit cfbe751af7
12 changed files with 107 additions and 32 deletions

View File

@@ -169,6 +169,9 @@ func adminScopes(admin *m.Admin) ([]string, error) {
scopeNames := make([]string, 0, len(scopes))
for _, scope := range scopes {
if scope.Name == "" {
continue
}
scopeNames = append(scopeNames, scope.Name)
}
return scopeNames, nil

View File

@@ -356,6 +356,11 @@ func authPassword(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m
return nil, err
}
// 非锁定管理员,不允许为空权限
if !admin.Lock && (len(scopes) == 0) {
return nil, ErrAuthorizeInvalidScope // 没有配置权限
}
// 更新管理员登录时间
admin.LastLogin = u.P(time.Now())
admin.LastLoginIP = ip

View File

@@ -62,10 +62,11 @@ const (
ScopeChannelReadOfUser = string("channel:read:of_user") // 读取指定用户的 IP 列表
ScopeChannelWrite = string("channel:write") // 写入 IP
ScopeTrade = string("trade") // 交易
ScopeTradeRead = string("trade:read") // 读取交易列表
ScopeTradeReadOfUser = string("trade:read:of_user") // 读取指定用户的交易列表
ScopeTradeWrite = string("trade:write") // 写入交易
ScopeTrade = string("trade") // 交易
ScopeTradeRead = string("trade:read") // 读取交易列表
ScopeTradeReadOfUser = string("trade:read:of_user") // 读取指定用户的交易列表
ScopeTradeWrite = string("trade:write") // 写入交易
ScopeTradeWriteComplete = string("trade:write:complete") // 完成交易
ScopeBill = string("bill") // 账单
ScopeBillRead = string("bill:read") // 读取账单列表

View File

@@ -173,6 +173,8 @@ type PageTradeOfUserByAdminReq struct {
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
}
// ============================================================
// 创建订单
func TradeCreate(c *fiber.Ctx) error {
// 检查权限
@@ -219,6 +221,8 @@ type TradeCreateReq struct {
Recharge *s.UpdateBalanceData `json:"recharge,omitempty"`
}
// ============================================================
// 完成订单
func TradeComplete(c *fiber.Ctx) error {
// 检查权限
@@ -228,13 +232,13 @@ func TradeComplete(c *fiber.Ctx) error {
}
// 解析请求参数
req := new(TradeCompleteReq)
if err := g.Validator.ParseBody(c, req); err != nil {
var req s.TradeRef
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
// 检查订单状态
err = s.Trade.CompleteTrade(authCtx.User, &req.TradeRef)
err = s.Trade.CompleteTrade(authCtx.User, &req)
if err != nil {
return err
}
@@ -242,10 +246,40 @@ func TradeComplete(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent)
}
type TradeCompleteReq struct {
s.TradeRef
// 管理员完成订单
func TradeCompleteByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeTradeWriteComplete)
if err != nil {
return err
}
// 解析请求参数
var req struct {
s.TradeRef
UserID int32 `json:"user_id" validate:"required"`
}
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
// 获取用户信息
user, err := s.User.Get(q.Q, req.UserID)
if err != nil {
return err
}
// 完成订单
err = s.Trade.CompleteTrade(user, &req.TradeRef)
if err != nil {
return err
}
return c.SendStatus(fiber.StatusNoContent)
}
// ============================================================
// 取消订单
func TradeCancel(c *fiber.Ctx) error {
// 检查权限
@@ -274,6 +308,8 @@ type TradeCancelReq struct {
s.TradeRef
}
// ============================================================
// 检查订单
func TradeCheck(c *fiber.Ctx) error {
// 检查权限sse 接口暂时不检查权限

View File

@@ -1,6 +1,7 @@
package handlers
import (
"errors"
"platform/web/auth"
"platform/web/core"
g "platform/web/globals"
@@ -76,6 +77,12 @@ func PageUserByAdmin(c *fiber.Ctx) error {
}
for _, user := range users {
if user.IDNo != nil && len(*user.IDNo) == 18 {
var str = *user.IDNo
*user.IDNo = str[:6] + "****" + str[len(str)-2:]
}
if user.Admin != nil {
user.Admin = &m.Admin{
Name: user.Admin.Name,
@@ -306,6 +313,9 @@ func UpdateUser(c *fiber.Ctx) error {
ContactQQ: &req.ContactQQ,
ContactWechat: &req.ContactWechat,
})
if errors.Is(err, gorm.ErrDuplicatedKey) {
return core.NewBizErr("用户名或邮箱已被占用")
}
if err != nil {
return err
}

View File

@@ -1,6 +1,7 @@
package handlers
import (
"errors"
"platform/pkg/env"
"platform/pkg/u"
"platform/web/auth"
@@ -92,7 +93,7 @@ func CreateWhitelist(c *fiber.Ctx) error {
ip, err := secureAddr(req.Host)
if err != nil {
return err
return core.NewBizErr("IP 地址无效", err)
}
// 创建白名单
@@ -132,7 +133,7 @@ func UpdateWhitelist(c *fiber.Ctx) error {
ip, err := secureAddr(req.Host)
if err != nil {
return err
return core.NewBizErr("IP 地址无效", err)
}
// 更新白名单
@@ -201,7 +202,7 @@ func secureAddr(str string) (*orm.Inet, error) {
return nil, err
}
if !ip.IsGlobalUnicast() && env.RunMode != env.RunModeDev {
return nil, fiber.NewError(fiber.StatusBadRequest, "IP 地址不可用")
return nil, errors.New("IP 地址不可用")
}
return ip, nil
}

View File

@@ -192,6 +192,7 @@ func adminRouter(api fiber.Router) {
var trade = api.Group("/trade")
trade.Post("/page", handlers.PageTradeByAdmin)
trade.Post("/page/of-user", handlers.PageTradeOfUserByAdmin)
trade.Post("/complete", handlers.TradeCompleteByAdmin)
// bill 账单
var bill = api.Group("/bill")

View File

@@ -9,8 +9,8 @@ var Bill = &billService{}
type billService struct{}
func (s *billService) CreateForBalance(q *q.Query, uid, tradeId int32, detail *TradeDetail) error {
return q.Bill.Create(&m.Bill{
func (s *billService) CreateForBalance(q *q.Query, uid, tradeId int32, detail *TradeDetail) (*m.Bill, error) {
bill := &m.Bill{
UserID: uid,
BillNo: ID.GenReadable("bil"),
TradeID: &tradeId,
@@ -18,11 +18,18 @@ func (s *billService) CreateForBalance(q *q.Query, uid, tradeId int32, detail *T
Info: &detail.Subject,
Amount: detail.Amount,
Actual: detail.Actual,
})
}
err := q.Bill.Create(bill)
if err != nil {
return nil, err
}
return bill, nil
}
func (s *billService) CreateForResource(q *q.Query, uid, resourceId int32, tradeId *int32, detail *TradeDetail) error {
return q.Bill.Create(&m.Bill{
func (s *billService) CreateForResource(q *q.Query, uid, resourceId int32, tradeId *int32, detail *TradeDetail) (*m.Bill, error) {
bill := &m.Bill{
UserID: uid,
BillNo: ID.GenReadable("bil"),
ResourceID: &resourceId,
@@ -32,5 +39,12 @@ func (s *billService) CreateForResource(q *q.Query, uid, resourceId int32, trade
Info: &detail.Subject,
Amount: detail.Amount,
Actual: detail.Actual,
})
}
err := q.Bill.Create(bill)
if err != nil {
return nil, err
}
return bill, nil
}

View File

@@ -28,11 +28,6 @@ func (s *resourceService) CreateResourceByBalance(user *m.User, data *CreateReso
return q.Q.Transaction(func(q *q.Query) error {
// 更新用户余额
if err := User.UpdateBalance(q, user, detail.Actual.Neg(), "余额购买产品", nil); err != nil {
return core.NewServErr("更新用户余额失败", err)
}
// 保存套餐
resource, err := s.Create(q, user.ID, now, data)
if err != nil {
@@ -40,11 +35,16 @@ func (s *resourceService) CreateResourceByBalance(user *m.User, data *CreateReso
}
// 生成账单
err = Bill.CreateForResource(q, user.ID, resource.ID, nil, detail)
bill, err := Bill.CreateForResource(q, user.ID, resource.ID, nil, detail)
if err != nil {
return core.NewServErr("生成账单失败", err)
}
// 更新用户余额
if err := User.UpdateBalance(q, user, detail.Actual.Neg(), "余额购买产品", nil, &bill.ID); err != nil {
return core.NewServErr("更新用户余额失败", err)
}
// 核销优惠券
if detail.CouponUserId != nil {
err = Coupon.UseCoupon(q, *detail.CouponUserId)

View File

@@ -302,17 +302,18 @@ 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, "充值余额", nil); err != nil {
return err
}
// 生成账单
err = Bill.CreateForBalance(q, user.ID, trade.ID, &detail)
bill, err := Bill.CreateForBalance(q, user.ID, trade.ID, &detail)
if err != nil {
return core.NewServErr("生成账单失败", err)
}
// 更新用户余额
if err := User.UpdateBalance(q, user, detail.Actual, "充值余额", nil, &bill.ID); err != nil {
return err
}
case m.TradeTypePurchase:
data, ok := detail.Product.(*CreateResourceData)
if !ok {
@@ -326,7 +327,7 @@ func (s *tradeService) OnCompleteTrade(user *m.User, interNo string, outerNo str
}
// 生成账单
err = Bill.CreateForResource(q, user.ID, resource.ID, &trade.ID, &detail)
_, err = Bill.CreateForResource(q, user.ID, resource.ID, &trade.ID, &detail)
if err != nil {
return core.NewServErr("生成账单失败", err)
}

View File

@@ -39,11 +39,11 @@ func (s *userService) UpdateBalanceByAdmin(user *m.User, newBalance decimal.Deci
}
return q.Q.Transaction(func(q *q.Query) error {
return s.UpdateBalance(q, user, amount, "管理员修改余额", adminId)
return s.UpdateBalance(q, user, amount, "管理员修改余额", adminId, nil)
})
}
func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Decimal, remark string, adminId *int32) error {
func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Decimal, remark string, adminId *int32, billId *int32) error {
balance := user.Balance.Add(amount)
if balance.IsNegative() {
return core.NewServErr("用户余额不足")
@@ -66,6 +66,7 @@ func (s *userService) UpdateBalance(q *q.Query, user *m.User, amount decimal.Dec
err = q.BalanceActivity.Create(&m.BalanceActivity{
UserID: user.ID,
AdminID: adminId,
BillID: billId,
Amount: amount.StringFixed(2),
BalancePrev: user.Balance.StringFixed(2),
BalanceCurr: balance.StringFixed(2),