修复产品修改、删除

This commit is contained in:
2024-10-15 00:23:07 +08:00
parent e6467b7004
commit 8f903b457f
31 changed files with 1292 additions and 111 deletions

View File

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

View File

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

View File

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

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

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

View 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...),
}
}

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

View 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)
}

View File

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