修复账单与交易接口问题 & 管理员权限的身份限制
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
|
交易信息持久化
|
||||||
|
|
||||||
用户请求需要检查数据权限
|
用户请求需要检查数据权限
|
||||||
|
|
||||||
用反射实现环境变量解析,以简化函数签名
|
用反射实现环境变量解析,以简化函数签名
|
||||||
|
|||||||
@@ -169,6 +169,9 @@ func adminScopes(admin *m.Admin) ([]string, error) {
|
|||||||
|
|
||||||
scopeNames := make([]string, 0, len(scopes))
|
scopeNames := make([]string, 0, len(scopes))
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
|
if scope.Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
scopeNames = append(scopeNames, scope.Name)
|
scopeNames = append(scopeNames, scope.Name)
|
||||||
}
|
}
|
||||||
return scopeNames, nil
|
return scopeNames, nil
|
||||||
|
|||||||
@@ -356,6 +356,11 @@ func authPassword(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 非锁定管理员,不允许为空权限
|
||||||
|
if !admin.Lock && (len(scopes) == 0) {
|
||||||
|
return nil, ErrAuthorizeInvalidScope // 没有配置权限
|
||||||
|
}
|
||||||
|
|
||||||
// 更新管理员登录时间
|
// 更新管理员登录时间
|
||||||
admin.LastLogin = u.P(time.Now())
|
admin.LastLogin = u.P(time.Now())
|
||||||
admin.LastLoginIP = ip
|
admin.LastLoginIP = ip
|
||||||
|
|||||||
@@ -62,10 +62,11 @@ const (
|
|||||||
ScopeChannelReadOfUser = string("channel:read:of_user") // 读取指定用户的 IP 列表
|
ScopeChannelReadOfUser = string("channel:read:of_user") // 读取指定用户的 IP 列表
|
||||||
ScopeChannelWrite = string("channel:write") // 写入 IP
|
ScopeChannelWrite = string("channel:write") // 写入 IP
|
||||||
|
|
||||||
ScopeTrade = string("trade") // 交易
|
ScopeTrade = string("trade") // 交易
|
||||||
ScopeTradeRead = string("trade:read") // 读取交易列表
|
ScopeTradeRead = string("trade:read") // 读取交易列表
|
||||||
ScopeTradeReadOfUser = string("trade:read:of_user") // 读取指定用户的交易列表
|
ScopeTradeReadOfUser = string("trade:read:of_user") // 读取指定用户的交易列表
|
||||||
ScopeTradeWrite = string("trade:write") // 写入交易
|
ScopeTradeWrite = string("trade:write") // 写入交易
|
||||||
|
ScopeTradeWriteComplete = string("trade:write:complete") // 完成交易
|
||||||
|
|
||||||
ScopeBill = string("bill") // 账单
|
ScopeBill = string("bill") // 账单
|
||||||
ScopeBillRead = string("bill:read") // 读取账单列表
|
ScopeBillRead = string("bill:read") // 读取账单列表
|
||||||
|
|||||||
@@ -173,6 +173,8 @@ type PageTradeOfUserByAdminReq struct {
|
|||||||
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
|
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
// 创建订单
|
// 创建订单
|
||||||
func TradeCreate(c *fiber.Ctx) error {
|
func TradeCreate(c *fiber.Ctx) error {
|
||||||
// 检查权限
|
// 检查权限
|
||||||
@@ -219,6 +221,8 @@ type TradeCreateReq struct {
|
|||||||
Recharge *s.UpdateBalanceData `json:"recharge,omitempty"`
|
Recharge *s.UpdateBalanceData `json:"recharge,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
// 完成订单
|
// 完成订单
|
||||||
func TradeComplete(c *fiber.Ctx) error {
|
func TradeComplete(c *fiber.Ctx) error {
|
||||||
// 检查权限
|
// 检查权限
|
||||||
@@ -228,13 +232,13 @@ func TradeComplete(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解析请求参数
|
// 解析请求参数
|
||||||
req := new(TradeCompleteReq)
|
var req s.TradeRef
|
||||||
if err := g.Validator.ParseBody(c, req); err != nil {
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查订单状态
|
// 检查订单状态
|
||||||
err = s.Trade.CompleteTrade(authCtx.User, &req.TradeRef)
|
err = s.Trade.CompleteTrade(authCtx.User, &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -242,10 +246,40 @@ func TradeComplete(c *fiber.Ctx) error {
|
|||||||
return c.SendStatus(fiber.StatusNoContent)
|
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 {
|
func TradeCancel(c *fiber.Ctx) error {
|
||||||
// 检查权限
|
// 检查权限
|
||||||
@@ -274,6 +308,8 @@ type TradeCancelReq struct {
|
|||||||
s.TradeRef
|
s.TradeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
// 检查订单
|
// 检查订单
|
||||||
func TradeCheck(c *fiber.Ctx) error {
|
func TradeCheck(c *fiber.Ctx) error {
|
||||||
// 检查权限:sse 接口暂时不检查权限
|
// 检查权限:sse 接口暂时不检查权限
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"platform/web/auth"
|
"platform/web/auth"
|
||||||
"platform/web/core"
|
"platform/web/core"
|
||||||
g "platform/web/globals"
|
g "platform/web/globals"
|
||||||
@@ -76,6 +77,12 @@ func PageUserByAdmin(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range users {
|
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 {
|
if user.Admin != nil {
|
||||||
user.Admin = &m.Admin{
|
user.Admin = &m.Admin{
|
||||||
Name: user.Admin.Name,
|
Name: user.Admin.Name,
|
||||||
@@ -306,6 +313,9 @@ func UpdateUser(c *fiber.Ctx) error {
|
|||||||
ContactQQ: &req.ContactQQ,
|
ContactQQ: &req.ContactQQ,
|
||||||
ContactWechat: &req.ContactWechat,
|
ContactWechat: &req.ContactWechat,
|
||||||
})
|
})
|
||||||
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return core.NewBizErr("用户名或邮箱已被占用")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"platform/pkg/env"
|
"platform/pkg/env"
|
||||||
"platform/pkg/u"
|
"platform/pkg/u"
|
||||||
"platform/web/auth"
|
"platform/web/auth"
|
||||||
@@ -92,7 +93,7 @@ func CreateWhitelist(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
ip, err := secureAddr(req.Host)
|
ip, err := secureAddr(req.Host)
|
||||||
if err != nil {
|
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)
|
ip, err := secureAddr(req.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return core.NewBizErr("IP 地址无效", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新白名单
|
// 更新白名单
|
||||||
@@ -201,7 +202,7 @@ func secureAddr(str string) (*orm.Inet, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !ip.IsGlobalUnicast() && env.RunMode != env.RunModeDev {
|
if !ip.IsGlobalUnicast() && env.RunMode != env.RunModeDev {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "IP 地址不可用")
|
return nil, errors.New("IP 地址不可用")
|
||||||
}
|
}
|
||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ func adminRouter(api fiber.Router) {
|
|||||||
var trade = api.Group("/trade")
|
var trade = api.Group("/trade")
|
||||||
trade.Post("/page", handlers.PageTradeByAdmin)
|
trade.Post("/page", handlers.PageTradeByAdmin)
|
||||||
trade.Post("/page/of-user", handlers.PageTradeOfUserByAdmin)
|
trade.Post("/page/of-user", handlers.PageTradeOfUserByAdmin)
|
||||||
|
trade.Post("/complete", handlers.TradeCompleteByAdmin)
|
||||||
|
|
||||||
// bill 账单
|
// bill 账单
|
||||||
var bill = api.Group("/bill")
|
var bill = api.Group("/bill")
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ var Bill = &billService{}
|
|||||||
|
|
||||||
type billService struct{}
|
type billService struct{}
|
||||||
|
|
||||||
func (s *billService) CreateForBalance(q *q.Query, uid, tradeId int32, detail *TradeDetail) error {
|
func (s *billService) CreateForBalance(q *q.Query, uid, tradeId int32, detail *TradeDetail) (*m.Bill, error) {
|
||||||
return q.Bill.Create(&m.Bill{
|
bill := &m.Bill{
|
||||||
UserID: uid,
|
UserID: uid,
|
||||||
BillNo: ID.GenReadable("bil"),
|
BillNo: ID.GenReadable("bil"),
|
||||||
TradeID: &tradeId,
|
TradeID: &tradeId,
|
||||||
@@ -18,11 +18,18 @@ func (s *billService) CreateForBalance(q *q.Query, uid, tradeId int32, detail *T
|
|||||||
Info: &detail.Subject,
|
Info: &detail.Subject,
|
||||||
Amount: detail.Amount,
|
Amount: detail.Amount,
|
||||||
Actual: detail.Actual,
|
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 {
|
func (s *billService) CreateForResource(q *q.Query, uid, resourceId int32, tradeId *int32, detail *TradeDetail) (*m.Bill, error) {
|
||||||
return q.Bill.Create(&m.Bill{
|
bill := &m.Bill{
|
||||||
UserID: uid,
|
UserID: uid,
|
||||||
BillNo: ID.GenReadable("bil"),
|
BillNo: ID.GenReadable("bil"),
|
||||||
ResourceID: &resourceId,
|
ResourceID: &resourceId,
|
||||||
@@ -32,5 +39,12 @@ func (s *billService) CreateForResource(q *q.Query, uid, resourceId int32, trade
|
|||||||
Info: &detail.Subject,
|
Info: &detail.Subject,
|
||||||
Amount: detail.Amount,
|
Amount: detail.Amount,
|
||||||
Actual: detail.Actual,
|
Actual: detail.Actual,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
err := q.Bill.Create(bill)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bill, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ func (s *resourceService) CreateResourceByBalance(user *m.User, data *CreateReso
|
|||||||
|
|
||||||
return q.Q.Transaction(func(q *q.Query) error {
|
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)
|
resource, err := s.Create(q, user.ID, now, data)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return core.NewServErr("生成账单失败", err)
|
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 {
|
if detail.CouponUserId != nil {
|
||||||
err = Coupon.UseCoupon(q, *detail.CouponUserId)
|
err = Coupon.UseCoupon(q, *detail.CouponUserId)
|
||||||
|
|||||||
@@ -302,17 +302,18 @@ func (s *tradeService) OnCompleteTrade(user *m.User, interNo string, outerNo str
|
|||||||
|
|
||||||
switch trade.Type {
|
switch trade.Type {
|
||||||
case m.TradeTypeRecharge:
|
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 {
|
if err != nil {
|
||||||
return core.NewServErr("生成账单失败", err)
|
return core.NewServErr("生成账单失败", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新用户余额
|
||||||
|
if err := User.UpdateBalance(q, user, detail.Actual, "充值余额", nil, &bill.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
case m.TradeTypePurchase:
|
case m.TradeTypePurchase:
|
||||||
data, ok := detail.Product.(*CreateResourceData)
|
data, ok := detail.Product.(*CreateResourceData)
|
||||||
if !ok {
|
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 {
|
if err != nil {
|
||||||
return core.NewServErr("生成账单失败", err)
|
return core.NewServErr("生成账单失败", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ func (s *userService) UpdateBalanceByAdmin(user *m.User, newBalance decimal.Deci
|
|||||||
}
|
}
|
||||||
|
|
||||||
return q.Q.Transaction(func(q *q.Query) error {
|
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)
|
balance := user.Balance.Add(amount)
|
||||||
if balance.IsNegative() {
|
if balance.IsNegative() {
|
||||||
return core.NewServErr("用户余额不足")
|
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{
|
err = q.BalanceActivity.Create(&m.BalanceActivity{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
AdminID: adminId,
|
AdminID: adminId,
|
||||||
|
BillID: billId,
|
||||||
Amount: amount.StringFixed(2),
|
Amount: amount.StringFixed(2),
|
||||||
BalancePrev: user.Balance.StringFixed(2),
|
BalancePrev: user.Balance.StringFixed(2),
|
||||||
BalanceCurr: balance.StringFixed(2),
|
BalanceCurr: balance.StringFixed(2),
|
||||||
|
|||||||
Reference in New Issue
Block a user