修复产品修改、删除
This commit is contained in:
@@ -9,4 +9,15 @@ type Config struct {
|
||||
zrpc.RpcServerConf
|
||||
DataSource string // 数据库连接的 DSN 字符串
|
||||
CacheRedis cache.CacheConf // 缓存配置,使用 go-zero 自带的缓存配置结构体
|
||||
Alipay AlipayConfig
|
||||
TopUp TopUpConfig
|
||||
}
|
||||
type AlipayConfig struct {
|
||||
AppID string
|
||||
PrivateKey string
|
||||
AlipayPublicKey string
|
||||
IsProduction bool
|
||||
}
|
||||
type TopUpConfig struct {
|
||||
MaxTopUpAmount int64
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ func NewDeleteProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Del
|
||||
}
|
||||
|
||||
func (l *DeleteProductLogic) DeleteProduct(in *sentinel.DeleteProductRequest) (*sentinel.Product, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
err := l.svcCtx.ProductsModel.Delete(l.ctx, in.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sentinel.Product{}, nil
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ package productlogic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tianyuan-api/apps/sentinel/internal/model"
|
||||
"tianyuan-api/apps/sentinel/internal/svc"
|
||||
"tianyuan-api/apps/sentinel/sentinel"
|
||||
"tianyuan-api/pkg/sqlutil"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
@@ -24,7 +25,17 @@ func NewUpdateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Upd
|
||||
}
|
||||
|
||||
func (l *UpdateProductLogic) UpdateProduct(in *sentinel.UpdateProductRequest) (*sentinel.Product, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
err := l.svcCtx.ProductsModel.Update(l.ctx, &model.Products{
|
||||
Id: in.Id,
|
||||
ProductName: in.ProductName,
|
||||
ProductCode: in.ProductCode,
|
||||
ProductDescription: sqlutil.StringToNullString(in.ProductDescription),
|
||||
ProductContent: sqlutil.StringToNullString(in.ProductContent),
|
||||
ProductPrice: in.ProductPrice,
|
||||
ProductGroup: in.ProductGroup,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sentinel.Product{}, nil
|
||||
}
|
||||
|
||||
65
apps/sentinel/internal/logic/topup/alitopuplogic.go
Normal file
65
apps/sentinel/internal/logic/topup/alitopuplogic.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package topuplogic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"tianyuan-api/apps/sentinel/internal/model"
|
||||
"tianyuan-api/apps/sentinel/internal/svc"
|
||||
"tianyuan-api/apps/sentinel/sentinel"
|
||||
"tianyuan-api/pkg/generate"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AliTopUpLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewAliTopUpLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AliTopUpLogic {
|
||||
return &AliTopUpLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AliTopUpLogic) AliTopUp(in *sentinel.AliTopUpRequest) (*sentinel.AliTopUpResponse, error) {
|
||||
if in.Amount > l.svcCtx.Config.TopUp.MaxTopUpAmount {
|
||||
return &sentinel.AliTopUpResponse{}, fmt.Errorf("充值金额最大不能超过%d元", l.svcCtx.Config.TopUp.MaxTopUpAmount)
|
||||
}
|
||||
// 使用从 svcCtx 中获取的支付宝客户端
|
||||
client := l.svcCtx.AlipayClient
|
||||
outTradeNo := generate.GenerateTransactionID()
|
||||
totalAmount := fmt.Sprintf("%.2f", float64(in.Amount))
|
||||
// 构造支付请求
|
||||
var p = alipay.TradePagePay{
|
||||
Trade: alipay.Trade{
|
||||
Subject: "天远数据API使用额度",
|
||||
OutTradeNo: outTradeNo,
|
||||
TotalAmount: totalAmount,
|
||||
ProductCode: "FAST_INSTANT_TRADE_PAY",
|
||||
NotifyURL: "https://console.tianyuanapi.com/api/console/", // 异步回调通知地址
|
||||
ReturnURL: "https://console.tianyuanapi.com/charge/recharge", // 支付成功后的跳转地址
|
||||
},
|
||||
}
|
||||
_, inserErr := l.svcCtx.PayOrderModel.Insert(l.ctx, &model.PayOrder{
|
||||
UserId: in.UserId,
|
||||
OutTradeNo: outTradeNo,
|
||||
Amount: float64(in.Amount),
|
||||
})
|
||||
if inserErr != nil {
|
||||
return nil, inserErr
|
||||
}
|
||||
// 生成支付链接
|
||||
url, err := client.TradePagePay(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sentinel.AliTopUpResponse{
|
||||
PayUrl: url.String(),
|
||||
}, nil
|
||||
}
|
||||
77
apps/sentinel/internal/logic/topup/alitopupnotifylogic.go
Normal file
77
apps/sentinel/internal/logic/topup/alitopupnotifylogic.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package topuplogic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"math"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"tianyuan-api/apps/sentinel/internal/svc"
|
||||
"tianyuan-api/apps/sentinel/sentinel"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AliTopUpNotifyLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewAliTopUpNotifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AliTopUpNotifyLogic {
|
||||
return &AliTopUpNotifyLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AliTopUpNotifyLogic) AliTopUpNotify(in *sentinel.AliTopUpNotifyRequest) (*sentinel.AliTopUpNotifyResponse, error) {
|
||||
globalErr := errors.New("error")
|
||||
// 初始化支付宝客户端
|
||||
client := l.svcCtx.AlipayClient
|
||||
|
||||
// 解析表单数据
|
||||
formData, err := url.ParseQuery(in.RawForm)
|
||||
if err != nil {
|
||||
l.Logger.Errorf("解析表单数据失败: %v", err)
|
||||
return nil, globalErr
|
||||
}
|
||||
|
||||
// 验证签名并解析通知
|
||||
notify, err := client.DecodeNotification(formData)
|
||||
if err != nil {
|
||||
l.Logger.Errorf("通知解析失败: %v", err)
|
||||
return nil, globalErr
|
||||
}
|
||||
|
||||
// 验证交易状态
|
||||
if notify.TradeStatus == alipay.TradeStatusSuccess {
|
||||
l.Logger.Infof("订单 %s 支付成功", notify.OutTradeNo)
|
||||
payOrder, findErr := l.svcCtx.PayOrderModel.FindOneByOutTradeNo(l.ctx, notify.OutTradeNo)
|
||||
if findErr != nil {
|
||||
return nil, globalErr
|
||||
}
|
||||
if payOrder.OutTradeNo == notify.OutTradeNo && payOrder.Status == 0 {
|
||||
notifyAmount, parseFloatErr := strconv.ParseFloat(notify.TotalAmount, 64)
|
||||
if parseFloatErr != nil {
|
||||
logx.Errorf("金额转换错误: %v", parseFloatErr)
|
||||
return nil, globalErr
|
||||
}
|
||||
logx.Infof("回调金额:%v,订单金额%v", notify.TotalAmount, payOrder.Amount)
|
||||
// 比较支付宝返回的金额与数据库存储的金额
|
||||
if math.Abs(notifyAmount-payOrder.Amount) < 1e-6 {
|
||||
// 金额匹配,继续处理
|
||||
} else {
|
||||
return nil, globalErr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
l.Logger.Infof("订单 %s 支付状态: %s", notify.OutTradeNo, notify.TradeStatus)
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
return &sentinel.AliTopUpNotifyResponse{Success: true}, nil
|
||||
}
|
||||
27
apps/sentinel/internal/model/payordermodel.go
Normal file
27
apps/sentinel/internal/model/payordermodel.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
var _ PayOrderModel = (*customPayOrderModel)(nil)
|
||||
|
||||
type (
|
||||
// PayOrderModel is an interface to be customized, add more methods here,
|
||||
// and implement the added methods in customPayOrderModel.
|
||||
PayOrderModel interface {
|
||||
payOrderModel
|
||||
}
|
||||
|
||||
customPayOrderModel struct {
|
||||
*defaultPayOrderModel
|
||||
}
|
||||
)
|
||||
|
||||
// NewPayOrderModel returns a model for the database table.
|
||||
func NewPayOrderModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) PayOrderModel {
|
||||
return &customPayOrderModel{
|
||||
defaultPayOrderModel: newPayOrderModel(conn, c, opts...),
|
||||
}
|
||||
}
|
||||
152
apps/sentinel/internal/model/payordermodel_gen.go
Normal file
152
apps/sentinel/internal/model/payordermodel_gen.go
Normal file
@@ -0,0 +1,152 @@
|
||||
// Code generated by goctl. DO NOT EDIT.
|
||||
// versions:
|
||||
// goctl version: 1.7.2
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/stores/builder"
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlc"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
var (
|
||||
payOrderFieldNames = builder.RawFieldNames(&PayOrder{})
|
||||
payOrderRows = strings.Join(payOrderFieldNames, ",")
|
||||
payOrderRowsExpectAutoSet = strings.Join(stringx.Remove(payOrderFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",")
|
||||
payOrderRowsWithPlaceHolder = strings.Join(stringx.Remove(payOrderFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?"
|
||||
|
||||
cachePayOrderIdPrefix = "cache:payOrder:id:"
|
||||
cachePayOrderOutTradeNoPrefix = "cache:payOrder:outTradeNo:"
|
||||
)
|
||||
|
||||
type (
|
||||
payOrderModel interface {
|
||||
Insert(ctx context.Context, data *PayOrder) (sql.Result, error)
|
||||
FindOne(ctx context.Context, id int64) (*PayOrder, error)
|
||||
FindOneByOutTradeNo(ctx context.Context, outTradeNo string) (*PayOrder, error)
|
||||
Update(ctx context.Context, data *PayOrder) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
defaultPayOrderModel struct {
|
||||
sqlc.CachedConn
|
||||
table string
|
||||
}
|
||||
|
||||
PayOrder struct {
|
||||
Id int64 `db:"id"` // 主键
|
||||
UserId int64 `db:"user_id"` // 用户ID
|
||||
OutTradeNo string `db:"out_trade_no"` // 商户订单号,唯一
|
||||
Amount float64 `db:"amount"` // 支付金额
|
||||
Status int64 `db:"status"` // 订单状态 0-待支付, 1-支付成功, 2-支付失败
|
||||
CreatedAt time.Time `db:"created_at"` // 创建时间
|
||||
UpdatedAt time.Time `db:"updated_at"` // 更新时间
|
||||
PaymentTime sql.NullTime `db:"payment_time"` // 支付完成时间
|
||||
}
|
||||
)
|
||||
|
||||
func newPayOrderModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultPayOrderModel {
|
||||
return &defaultPayOrderModel{
|
||||
CachedConn: sqlc.NewConn(conn, c, opts...),
|
||||
table: "`pay_order`",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultPayOrderModel) Delete(ctx context.Context, id int64) error {
|
||||
data, err := m.FindOne(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payOrderIdKey := fmt.Sprintf("%s%v", cachePayOrderIdPrefix, id)
|
||||
payOrderOutTradeNoKey := fmt.Sprintf("%s%v", cachePayOrderOutTradeNoPrefix, data.OutTradeNo)
|
||||
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
|
||||
return conn.ExecCtx(ctx, query, id)
|
||||
}, payOrderIdKey, payOrderOutTradeNoKey)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *defaultPayOrderModel) FindOne(ctx context.Context, id int64) (*PayOrder, error) {
|
||||
payOrderIdKey := fmt.Sprintf("%s%v", cachePayOrderIdPrefix, id)
|
||||
var resp PayOrder
|
||||
err := m.QueryRowCtx(ctx, &resp, payOrderIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
|
||||
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", payOrderRows, m.table)
|
||||
return conn.QueryRowCtx(ctx, v, query, id)
|
||||
})
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultPayOrderModel) FindOneByOutTradeNo(ctx context.Context, outTradeNo string) (*PayOrder, error) {
|
||||
payOrderOutTradeNoKey := fmt.Sprintf("%s%v", cachePayOrderOutTradeNoPrefix, outTradeNo)
|
||||
var resp PayOrder
|
||||
err := m.QueryRowIndexCtx(ctx, &resp, payOrderOutTradeNoKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
|
||||
query := fmt.Sprintf("select %s from %s where `out_trade_no` = ? limit 1", payOrderRows, m.table)
|
||||
if err := conn.QueryRowCtx(ctx, &resp, query, outTradeNo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Id, nil
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultPayOrderModel) Insert(ctx context.Context, data *PayOrder) (sql.Result, error) {
|
||||
payOrderIdKey := fmt.Sprintf("%s%v", cachePayOrderIdPrefix, data.Id)
|
||||
payOrderOutTradeNoKey := fmt.Sprintf("%s%v", cachePayOrderOutTradeNoPrefix, data.OutTradeNo)
|
||||
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?)", m.table, payOrderRowsExpectAutoSet)
|
||||
return conn.ExecCtx(ctx, query, data.UserId, data.OutTradeNo, data.Amount, data.Status, data.PaymentTime)
|
||||
}, payOrderIdKey, payOrderOutTradeNoKey)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (m *defaultPayOrderModel) Update(ctx context.Context, newData *PayOrder) error {
|
||||
data, err := m.FindOne(ctx, newData.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payOrderIdKey := fmt.Sprintf("%s%v", cachePayOrderIdPrefix, data.Id)
|
||||
payOrderOutTradeNoKey := fmt.Sprintf("%s%v", cachePayOrderOutTradeNoPrefix, data.OutTradeNo)
|
||||
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, payOrderRowsWithPlaceHolder)
|
||||
return conn.ExecCtx(ctx, query, newData.UserId, newData.OutTradeNo, newData.Amount, newData.Status, newData.PaymentTime, newData.Id)
|
||||
}, payOrderIdKey, payOrderOutTradeNoKey)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *defaultPayOrderModel) formatPrimary(primary any) string {
|
||||
return fmt.Sprintf("%s%v", cachePayOrderIdPrefix, primary)
|
||||
}
|
||||
|
||||
func (m *defaultPayOrderModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
|
||||
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", payOrderRows, m.table)
|
||||
return conn.QueryRowCtx(ctx, v, query, primary)
|
||||
}
|
||||
|
||||
func (m *defaultPayOrderModel) tableName() string {
|
||||
return m.table
|
||||
}
|
||||
34
apps/sentinel/internal/server/topup/topupserver.go
Normal file
34
apps/sentinel/internal/server/topup/topupserver.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Code generated by goctl. DO NOT EDIT.
|
||||
// goctl 1.7.2
|
||||
// Source: sentinel.proto
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tianyuan-api/apps/sentinel/internal/logic/topup"
|
||||
"tianyuan-api/apps/sentinel/internal/svc"
|
||||
"tianyuan-api/apps/sentinel/sentinel"
|
||||
)
|
||||
|
||||
type TopUpServer struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
sentinel.UnimplementedTopUpServer
|
||||
}
|
||||
|
||||
func NewTopUpServer(svcCtx *svc.ServiceContext) *TopUpServer {
|
||||
return &TopUpServer{
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TopUpServer) AliTopUp(ctx context.Context, in *sentinel.AliTopUpRequest) (*sentinel.AliTopUpResponse, error) {
|
||||
l := topuplogic.NewAliTopUpLogic(ctx, s.svcCtx)
|
||||
return l.AliTopUp(in)
|
||||
}
|
||||
|
||||
func (s *TopUpServer) AliTopUpNotify(ctx context.Context, in *sentinel.AliTopUpNotifyRequest) (*sentinel.AliTopUpNotifyResponse, error) {
|
||||
l := topuplogic.NewAliTopUpNotifyLogic(ctx, s.svcCtx)
|
||||
return l.AliTopUpNotify(in)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"tianyuan-api/apps/sentinel/internal/config"
|
||||
@@ -10,10 +12,12 @@ import (
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Redis *redis.Redis
|
||||
AlipayClient *alipay.Client
|
||||
WhitelistModel model.WhitelistModel
|
||||
SecretsModel model.SecretsModel
|
||||
ProductsModel model.ProductsModel
|
||||
UserProductsModel model.UserProductsModel
|
||||
PayOrderModel model.PayOrderModel
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
@@ -25,12 +29,26 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||
}
|
||||
// 使用 MustNewRedis 来初始化 Redis 客户端
|
||||
rds := redis.MustNewRedis(redisConf)
|
||||
|
||||
// 创建支付宝客户端
|
||||
client, err := alipay.New(c.Alipay.AppID, c.Alipay.PrivateKey, c.Alipay.IsProduction)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("创建支付宝客户端失败: %v", err))
|
||||
}
|
||||
|
||||
// 加载支付宝公钥
|
||||
err = client.LoadAliPayPublicKey(c.Alipay.AlipayPublicKey)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("加载支付宝公钥失败: %v", err))
|
||||
}
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Redis: rds,
|
||||
AlipayClient: client,
|
||||
WhitelistModel: model.NewWhitelistModel(rds, db, c.CacheRedis),
|
||||
SecretsModel: model.NewSecretsModel(db, c.CacheRedis),
|
||||
ProductsModel: model.NewProductsModel(db, c.CacheRedis),
|
||||
UserProductsModel: model.NewUserProductsModel(rds, db, c.CacheRedis),
|
||||
PayOrderModel: model.NewPayOrderModel(db, c.CacheRedis),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user