Compare commits

...

4 Commits

Author SHA1 Message Date
daniel.w ce8751062e Merge remote-tracking branch 'refs/remotes/origin/main' 2024-10-23 18:07:18 +08:00
daniel.w d8cb437a74 fix test 2024-10-23 18:07:05 +08:00
王性驊 075da3063d fix test (#2)
Co-authored-by: daniel.w <daniel.w@intteam.net>
Reviewed-on: #2
2024-10-23 09:57:52 +00:00
王性驊 8b76f7cd1e feature/order_base (#1)
Co-authored-by: daniel.w <daniel.w@intteam.net>
Reviewed-on: #1
2024-10-23 09:50:11 +00:00
34 changed files with 2762 additions and 145 deletions

View File

@ -61,4 +61,4 @@ mock-gen: # 建立 mock 資料
.PHONY: migrate-database
migrate-database:
migrate -source file://generate/database/migrations/mongodb -database 'mongodb://127.0.0.1:27017/digimon_order' up
migrate -source file://generate/database/mongodb -database 'mongodb://127.0.0.1:27017/digimon_order' up

View File

@ -0,0 +1,13 @@
use digimon_order;
db.order.createIndex({ "business_id": 1, "order_status": 1, "create_time": -1 })
db.orders.createIndex({ "reference_id": 1, "reference_uid": 1, "business_id": 1, "uid": 1 })
db.orders.createIndex({ "order_type": 1, "direction_type": 1, "order_status": 1 })
db.orders.createIndex({ "create_time": -1, "update_time": -1, "order_arrival_time": -1, "order_payment_time": -1 })
db.order.createIndex({
"uid": 1,
"order_type": 1,
"order_status": 1,
"create_time": -1,
})

View File

@ -1,6 +1,6 @@
syntax = "proto3";
package tweeting;
option go_package = "./tweeting";
package order;
option go_package = "./order";
// ========== ===========
message OKResp {}
@ -9,46 +9,157 @@ message OKResp {}
message NoneReq {}
//
message Pager
{
message Pager {
int64 total = 1; //
int64 size = 2; //
int64 index = 3; //
}
message CreateOrderReq{}
message CreateOrderResp{}
message CreateOrderReq {
string business_id = 1; //
int32 order_type = 2; //
int32 order_status = 3; //
string brand = 4; //
string order_uid = 5; // UID
string reference_id = 6; //
string count = 7; // (decimal to string)
string order_fee = 8; // (decimal to string)
string amount = 9; // (decimal to string)
message CancelOrderReq{}
message ModifyOrderReq{}
message ModifyOrderStatusReq{}
message DeleteOrderReq{}
//
optional string reference_brand = 10; //
optional string reference_uid = 11; // UID
optional int64 wallet_status = 12; //
optional int64 three_party_status = 13; //
optional int64 direction_type = 14; //
optional string crypto_type = 15; //
optional string third_party_fee = 16; // (decimal to string)
optional string crypto_to_usdt_rate = 17; // USDT (decimal to string)
optional string fiat_to_usd_rate = 18; // USD (decimal to string)
optional string fee_crypto_to_usdt_rate = 19; // USDT (decimal to string)
optional string usdt_to_crypto_type_rate = 20; // USDT (decimal to string)
optional string payment_fiat = 21; //
optional string payment_unit_price = 22; // crypto (decimal to string)
optional string payment_template_id = 23; // ID
optional int64 order_arrival_time = 24; //
optional int64 order_payment_time = 25; //
optional int64 unpaid_timeout_second = 26; //
optional string chain_type = 27; //
optional string tx_hash = 28; //
optional string from_address = 29; //
optional string to_address = 30; //
optional string chain_fee = 31; // (decimal to string)
optional string chain_fee_crypto = 32; // 使
optional string memo = 33; //
optional string order_note = 34; //
}
message GetOrderReq{}
message GetOrderResp{}
message CancelOrderReq {
string business_id = 1;
int64 status = 2;
}
message ListOrderReq{}
message ListOrderResp{}
message ModifyOrderStatusReq {
string business_id = 1;
int64 status = 2;
}
message DeleteOrderReq {
string business_id = 1;
}
message OrderStatusTimeoutReq{}
message OrderStatusTimeoutReq {}
message GetOrderReq {
string business_id = 1; //
}
message GetOrderResp {
string business_id = 1; //
int32 order_type = 2; //
int32 order_status = 3; //
string brand = 4; //
string order_uid = 5; // UID
string reference_id = 6; //
string count = 7; // (decimal to string)
string order_fee = 8; // (decimal to string)
string amount = 9; // (decimal to string)
//
optional string reference_brand = 10; //
optional string reference_uid = 11; // UID
optional int64 wallet_status = 12; //
optional int64 three_party_status = 13; //
optional int64 direction_type = 14; //
optional string crypto_type = 15; //
optional string third_party_fee = 16; // (decimal to string)
optional string crypto_to_usdt_rate = 17; // USDT (decimal to string)
optional string fiat_to_usd_rate = 18; // USD (decimal to string)
optional string fee_crypto_to_usdt_rate = 19; // USDT (decimal to string)
optional string usdt_to_crypto_type_rate = 20; // USDT (decimal to string)
optional string payment_fiat = 21; //
optional string payment_unit_price = 22; // crypto (decimal to string)
optional string payment_template_id = 23; // ID
optional int64 order_arrival_time = 24; //
optional int64 order_payment_time = 25; //
optional int64 unpaid_timeout_second = 26; //
optional string chain_type = 27; //
optional string tx_hash = 28; //
optional string from_address = 29; //
optional string to_address = 30; //
optional string chain_fee = 31; // (decimal to string)
optional string chain_fee_crypto = 32; // 使
optional string memo = 33; //
optional string order_note = 34; //
int64 create_time = 35; //
int64 update_time = 36; //
}
message ModifyOrderReq {}
message ListOrderReq {
int64 page_index = 1; // required
int64 page_size = 2; // required
string reference_id = 3;
string reference_uid = 4;
string business_id = 5;
string uid = 6;
int32 order_type = 7;
repeated int32 direction_type = 8;
repeated int32 order_status = 9;
int64 start_create_time = 10;
int64 end_create_time = 11;
int64 start_update_time = 12;
int64 end_update_time = 13;
int64 start_order_arrival_time = 14;
int64 end_order_arrival_time = 15;
int64 start_order_payment_time = 16;
int64 end_order_payment_time = 17;
string crypto_type = 18;
string tx_hash = 19;
}
message ListOrderResp {
repeated GetOrderResp data = 1; //
Pager page = 2;
}
// OrderService
service OrderService{
service OrderService {
// CreateOrder
rpc CreateOrder(CreateOrderReq)returns(CreateOrderResp);
rpc CreateOrder(CreateOrderReq) returns (OKResp);
// CancelOrder
rpc CancelOrder(CancelOrderReq)returns(OKResp);
// ModifyOrder
rpc ModifyOrder(ModifyOrderReq)returns(OKResp);
rpc CancelOrder(CancelOrderReq) returns (OKResp);
// ModifyOrderStatus
rpc ModifyOrderStatus(ModifyOrderStatusReq)returns(OKResp);
rpc ModifyOrderStatus(ModifyOrderStatusReq) returns (OKResp);
// DeleteOrder
rpc DeleteOrder(DeleteOrderReq)returns(OKResp);
rpc DeleteOrder(DeleteOrderReq) returns (OKResp);
// GetOrder
rpc GetOrder(GetOrderReq)returns(GetOrderResp);
rpc GetOrder(GetOrderReq) returns (GetOrderResp);
// ListOrder
rpc ListOrder(ListOrderReq)returns(ListOrderResp);
rpc ListOrder(ListOrderReq) returns (ListOrderResp);
// OrderStatusTimeout /cron/order-status/timeout
rpc OrderStatusTimeout(OrderStatusTimeoutReq)returns(OKResp);
rpc OrderStatusTimeout(OrderStatusTimeoutReq) returns (OKResp);
}

20
go.mod
View File

@ -3,7 +3,14 @@ module app-cloudep-order-server
go 1.22.3
require (
code.30cm.net/digimon/library-go/errs v1.2.5
code.30cm.net/digimon/library-go/validator v1.0.0
github.com/go-playground/validator/v10 v10.22.0
github.com/shopspring/decimal v1.4.0
github.com/stretchr/testify v1.9.0
github.com/zeromicro/go-zero v1.7.2
go.mongodb.org/mongo-driver v1.16.1
go.uber.org/mock v0.4.0
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.34.2
)
@ -18,14 +25,18 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
@ -34,20 +45,27 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.20.2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/redis/go-redis/v9 v9.6.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
@ -66,8 +84,10 @@ require (
go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect

View File

@ -4,4 +4,13 @@ import "github.com/zeromicro/go-zero/zrpc"
type Config struct {
zrpc.RpcServerConf
Mongo struct {
Schema string
User string
Password string
Host string
Port string
Database string
}
}

45
internal/domain/errors.go Normal file
View File

@ -0,0 +1,45 @@
package domain
import (
"strings"
ers "code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/library-go/errs/code"
"github.com/zeromicro/go-zero/core/logx"
)
type ErrorCode uint32
func (e ErrorCode) ToUint32() uint32 {
return uint32(e)
}
// Error Code 統一這邊改
const (
_ = iota
CreateOrderErrorCode ErrorCode = iota
CancelOrderErrorCode
ModifyOrderErrorCode
TimeoutOrderErrorCode
)
const (
_ ErrorCode = 10 + iota
DataNotFoundErrorCode
)
func CommentError(ec ErrorCode, s ...string) *ers.LibError {
return ers.NewError(code.CloudEPOrder, code.DBError, ec.ToUint32(), strings.Join(s, " "))
}
func CommentErrorL(ec ErrorCode,
l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError {
e := CommentError(ec, s...)
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
return e
}
func NotFoundError(ec ErrorCode, s ...string) *ers.LibError {
return ers.NewError(code.CloudEPOrder, code.ResourceNotFound, ec.ToUint32(), strings.Join(s, " "))
}

22
internal/domain/order.go Normal file
View File

@ -0,0 +1,22 @@
package domain
type OrderStatus int64
func (o *OrderStatus) ToInt() int {
return int(*o)
}
const (
OrderStatusCreated OrderStatus = 0 // 建立訂單
OrderStatusFailed OrderStatus = 1 // 建單失敗
OrderStatusReviewing OrderStatus = 2 // 審核中
OrderStatusPaying OrderStatus = 3 // 付款中
OrderStatusPaid OrderStatus = 4 // 已付款
OrderStatusPendingTransfer OrderStatus = 5 // 已付款待轉帳
OrderStatusDisputing OrderStatus = 6 // 申訴中
OrderStatusCompleted OrderStatus = 7 // 交易完成
OrderStatusFailedTrade OrderStatus = 8 // 交易失敗
OrderStatusCancelled OrderStatus = 9 // 交易取消
OrderStatusAbnormal OrderStatus = 10 // 交易異常
OrderStatusTimeout OrderStatus = 11 // 交易超時
)

View File

@ -1,9 +1,13 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
"app-cloudep-order-server/internal/domain"
model "app-cloudep-order-server/internal/model/mongo"
"context"
"app-cloudep-order-server/gen_result/pb/tweeting"
ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-order-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
@ -23,9 +27,42 @@ func NewCancelOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Cance
}
}
// CancelOrder 取消訂單
func (l *CancelOrderLogic) CancelOrder(in *tweeting.CancelOrderReq) (*tweeting.OKResp, error) {
// todo: add your logic here and delete this line
return &tweeting.OKResp{}, nil
// CancelOrderQuery 1.建單失敗 9.交易取消 10.交易異常
type CancelOrderQuery struct {
BusinessID string `json:"business_id" validate:"required"`
Status int64 `json:"status" validate:"required,oneof=1 9 10"`
}
// CancelOrder 取消訂單
func (l *CancelOrderLogic) CancelOrder(in *order.CancelOrderReq) (*order.OKResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&CancelOrderQuery{
BusinessID: in.GetBusinessId(),
Status: in.GetStatus(),
}); err != nil {
// 錯誤代碼 06-011-00
return nil, ers.InvalidFormat(err.Error())
}
_, err := l.svcCtx.OrderModel.UpdateStatus(l.ctx,
model.UpdateStatusReq{
BusinessID: in.GetBusinessId(),
Status: in.GetStatus(),
})
if err != nil {
// 錯誤代碼 06-021-02
e := domain.CommentErrorL(
domain.CancelOrderErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "OrderModel.UpdateStatus"},
{Key: "err", Value: err},
},
"failed to update order status:").Wrap(err)
return nil, e
}
return &order.OKResp{}, nil
}

View File

@ -0,0 +1,110 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
model "app-cloudep-order-server/internal/model/mongo"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/mock/gomock"
mockmodel "app-cloudep-order-server/internal/mock/model"
)
func TestCancelOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
// 測試數據
cancelReq := &order.CancelOrderReq{
BusinessId: "12345",
Status: 1,
}
// 測試數據集
tests := []struct {
name string
input *order.CancelOrderReq
prepare func()
expectErr bool
}{
{
name: "成功取消訂單",
input: cancelReq,
prepare: func() {
// 模擬驗證通過
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬更新狀態成功
mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{
BusinessID: "12345",
Status: 1,
}).Return(&mongo.UpdateResult{MatchedCount: 1}, nil).Times(1)
},
expectErr: false,
},
{
name: "訂單更新狀態失敗",
input: cancelReq,
prepare: func() {
// 模擬驗證通過
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬更新狀態失敗
mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), model.UpdateStatusReq{
BusinessID: "12345",
Status: 1,
}).Return(nil, errors.New("update failed")).Times(1)
},
expectErr: true,
},
{
name: "驗證失敗",
input: cancelReq,
prepare: func() {
// 模擬驗證失敗
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("invalid input")).Times(1)
},
expectErr: true,
},
}
// 執行測試
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 設置測試環境
tt.prepare()
// 初始化 CancelOrderLogic
logic := CancelOrderLogic{
svcCtx: svcCtx,
ctx: context.TODO(),
}
// 執行 CancelOrder
resp, err := logic.CancelOrder(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.NotNil(t, resp)
}
})
}
}

View File

@ -1,9 +1,15 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
"app-cloudep-order-server/internal/domain"
model "app-cloudep-order-server/internal/model/mongo"
"context"
"time"
ers "code.30cm.net/digimon/library-go/errs"
"github.com/shopspring/decimal"
"app-cloudep-order-server/gen_result/pb/tweeting"
"app-cloudep-order-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
@ -23,9 +29,226 @@ func NewCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Creat
}
}
// CreateOrder 建立訂單
func (l *CreateOrderLogic) CreateOrder(in *tweeting.CreateOrderReq) (*tweeting.CreateOrderResp, error) {
// todo: add your logic here and delete this line
return &tweeting.CreateOrderResp{}, nil
type createOrderReq struct { // 訂單ID
BusinessID string `json:"business_id" validate:"required"` // 訂單業務流水號
OrderType int8 `json:"order_type" validate:"required"` // 訂單類型
OrderStatus int8 `json:"order_status" validate:"oneof=0 1 2 3 4 5 6 7 8 9 10 11"` // 訂單狀態
Brand string `json:"brand" validate:"required"` // 下單平台
OrderUID string `json:"order_uid" validate:"required"` // 下單用戶 UID
ReferenceID string `json:"reference_id" validate:"required"` // 訂單來源
Count decimal.Decimal `json:"count" validate:"required,decimalGt=0"` // 訂單數量
OrderFee decimal.Decimal `json:"order_fee" validate:"required,decimalGte=0"` // 訂單手續費
Amount decimal.Decimal `json:"amount" validate:"required,decimalGte=0"` // 單價
ReferenceBrand *string `json:"reference_brand,omitempty" validate:"omitempty"` // 訂單來源平台
ReferenceUID *string `json:"reference_uid,omitempty" validate:"omitempty"` // 訂單來源用戶 UID
WalletStatus *int64 `json:"wallet_status,omitempty" validate:"omitempty,oneof=1 2 3 4 5 6 7"` // 交易金額狀態
ThreePartyStatus *int64 `json:"three_party_status,omitempty" validate:"omitempty,oneof=1 2 3"` // 三方請求狀態
DirectionType *int64 `json:"direction_type,omitempty" validate:"omitempty,oneof=1 2"` // 交易方向
CryptoType *string `json:"crypto_type,omitempty" validate:"omitempty"` // 交易幣種
ThirdPartyFee *decimal.Decimal `json:"third_party_fee,omitempty" validate:"omitempty,decimalGte=0"` // 第三方手續費
CryptoToUSDTRate *decimal.Decimal `json:"crypto_to_usdt_rate,omitempty" validate:"omitempty,decimalGte=0"` // 交易幣種對 USDT 匯率
FiatToUSDRate *decimal.Decimal `json:"fiat_to_usd_rate,omitempty" validate:"omitempty,decimalGte=0"` // 法幣對 USD 匯率
FeeCryptoToUSDTRate *decimal.Decimal `json:"fee_crypto_to_usdt_rate,omitempty" validate:"omitempty,decimalGte=0"` // 手續費幣種對 USDT 匯率
USDTToCryptoTypeRate *decimal.Decimal `json:"usdt_to_crypto_type_rate,omitempty" validate:"omitempty,decimalGte=0"` // USDT 對交易幣種匯率
PaymentFiat *string `json:"payment_fiat,omitempty" validate:"omitempty"` // 支付法幣
PaymentUnitPrice *decimal.Decimal `json:"payment_unit_price,omitempty" validate:"omitempty,decimalGte=0"` // crypto 單價
PaymentTemplateID *string `json:"payment_template_id,omitempty" validate:"omitempty"` // 支付方式配置 ID
OrderArrivalTime *int64 `json:"order_arrival_time,omitempty" validate:"omitempty"` // 訂單到帳時間
OrderPaymentTime *int64 `json:"order_payment_time,omitempty" validate:"omitempty"` // 訂單付款時間
UnpaidTimeoutSecond *int64 `json:"unpaid_timeout_second,omitempty" validate:"omitempty,decimalGte=0"` // 支付期限秒數
ChainType *string `json:"chain_type,omitempty" validate:"omitempty"` // 主網類型
TxHash *string `json:"tx_hash,omitempty" validate:"omitempty"` // 交易哈希
FromAddress *string `json:"from_address,omitempty" validate:"omitempty"` // 來源地址
ToAddress *string `json:"to_address,omitempty" validate:"omitempty"` // 目標地址
ChainFee *decimal.Decimal `json:"chain_fee,omitempty" validate:"omitempty,decimalGte=0"` // 鏈上交易手續費
ChainFeeCrypto *string `json:"chain_fee_crypto,omitempty" validate:"omitempty"` // 鏈上手續費使用幣別
Memo *string `json:"memo,omitempty" validate:"omitempty"` // 鏈上備註
OrderNote *string `json:"order_note,omitempty" validate:"omitempty"`
}
// CreateOrder 建立訂單
func (l *CreateOrderLogic) CreateOrder(in *order.CreateOrderReq) (*order.OKResp, error) {
req, err := buildCreateOrderReq(in)
if err != nil {
// 錯誤代碼 06-011-00
return nil, ers.InvalidFormat(err.Error())
}
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(req); err != nil {
// 錯誤代碼 06-011-00
return nil, ers.InvalidFormat(err.Error())
}
now := time.Now().UTC().UnixNano()
// 插入資料庫
o := &model.Order{
UpdateTime: now,
CreateTime: now,
BusinessID: req.BusinessID,
OrderType: req.OrderType,
OrderStatus: req.OrderStatus,
Brand: req.Brand,
OrderUID: req.OrderUID,
ReferenceID: req.ReferenceID,
Count: convertDecimalToDecimal128(req.Count),
OrderFee: convertDecimalToDecimal128(req.OrderFee),
Amount: convertDecimalToDecimal128(req.Amount),
// 下面的是未來擴充用,加密貨幣用,或者幣別轉換用,普通訂單用不到
ReferenceBrand: req.ReferenceBrand,
ReferenceUID: req.ReferenceUID,
WalletStatus: req.WalletStatus,
ThreePartyStatus: req.ThreePartyStatus,
DirectionType: req.DirectionType,
CryptoType: req.CryptoType,
ThirdPartyFee: convertDecimalPtrToDecimal128(req.ThirdPartyFee),
CryptoToUSDTRate: convertDecimalPtrToDecimal128(req.CryptoToUSDTRate),
FiatToUSDRate: convertDecimalPtrToDecimal128(req.FiatToUSDRate),
FeeCryptoToUSDTRate: convertDecimalPtrToDecimal128(req.FeeCryptoToUSDTRate),
USDTToCryptoTypeRate: convertDecimalPtrToDecimal128(req.USDTToCryptoTypeRate),
PaymentFiat: req.PaymentFiat,
PaymentUnitPrice: convertDecimalPtrToDecimal128(req.PaymentUnitPrice),
PaymentTemplateID: req.PaymentTemplateID,
OrderArrivalTime: req.OrderArrivalTime,
OrderPaymentTime: req.OrderPaymentTime,
UnpaidTimeoutSecond: req.UnpaidTimeoutSecond,
ChainType: req.ChainType,
TxHash: req.TxHash,
FromAddress: req.FromAddress,
ToAddress: req.ToAddress,
ChainFee: convertDecimalPtrToDecimal128(req.ChainFee),
ChainFeeCrypto: req.ChainFeeCrypto,
Memo: req.Memo,
OrderNote: req.OrderNote,
}
err = l.svcCtx.OrderModel.Insert(l.ctx, o)
if err != nil {
// 錯誤代碼 06-021-01
e := domain.CommentErrorL(
domain.CreateOrderErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "OrderModel.Insert"},
{Key: "err", Value: err},
},
"failed to insert order into mongo:").Wrap(err)
return nil, e
}
return &order.OKResp{}, nil
}
//nolint:gocyclo,gocognit
func buildCreateOrderReq(in *order.CreateOrderReq) (*createOrderReq, error) {
createOrderReq := &createOrderReq{
BusinessID: in.BusinessId,
OrderType: int8(in.OrderType),
OrderStatus: int8(in.OrderStatus),
Brand: in.Brand,
OrderUID: in.OrderUid,
ReferenceID: in.ReferenceId,
}
var err error
// 只有當 Count, OrderFee, Amount 不為空時才進行轉換和設置
if in.Count != "" {
createOrderReq.Count, err = decimal.NewFromString(in.Count)
if err != nil {
return nil, err
}
}
if in.OrderFee != "" {
createOrderReq.OrderFee, err = decimal.NewFromString(in.OrderFee)
if err != nil {
return nil, err
}
}
if in.Amount != "" {
createOrderReq.Amount, err = decimal.NewFromString(in.Amount)
if err != nil {
return nil, err
}
}
// 判斷可選字段,只有不為 nil 才設置
if in.ReferenceBrand != nil {
createOrderReq.ReferenceBrand = in.ReferenceBrand
}
if in.ReferenceUid != nil {
createOrderReq.ReferenceUID = in.ReferenceUid
}
if in.WalletStatus != nil {
createOrderReq.WalletStatus = in.WalletStatus
}
if in.ThreePartyStatus != nil {
createOrderReq.ThreePartyStatus = in.ThreePartyStatus
}
if in.DirectionType != nil {
createOrderReq.DirectionType = in.DirectionType
}
if in.CryptoType != nil {
createOrderReq.CryptoType = in.CryptoType
}
if in.ThirdPartyFee != nil && *in.ThirdPartyFee != "" {
createOrderReq.ThirdPartyFee = decimalPtrFromString(*in.ThirdPartyFee)
}
if in.CryptoToUsdtRate != nil && *in.CryptoToUsdtRate != "" {
createOrderReq.CryptoToUSDTRate = decimalPtrFromString(*in.CryptoToUsdtRate)
}
if in.FiatToUsdRate != nil && *in.FiatToUsdRate != "" {
createOrderReq.FiatToUSDRate = decimalPtrFromString(*in.FiatToUsdRate)
}
if in.FeeCryptoToUsdtRate != nil && *in.FeeCryptoToUsdtRate != "" {
createOrderReq.FeeCryptoToUSDTRate = decimalPtrFromString(*in.FeeCryptoToUsdtRate)
}
if in.UsdtToCryptoTypeRate != nil && *in.UsdtToCryptoTypeRate != "" {
createOrderReq.USDTToCryptoTypeRate = decimalPtrFromString(*in.UsdtToCryptoTypeRate)
}
if in.PaymentFiat != nil {
createOrderReq.PaymentFiat = in.PaymentFiat
}
if in.PaymentUnitPrice != nil && *in.PaymentUnitPrice != "" {
createOrderReq.PaymentUnitPrice = decimalPtrFromString(*in.PaymentUnitPrice)
}
if in.PaymentTemplateId != nil {
createOrderReq.PaymentTemplateID = in.PaymentTemplateId
}
if in.OrderArrivalTime != nil {
createOrderReq.OrderArrivalTime = in.OrderArrivalTime
}
if in.OrderPaymentTime != nil {
createOrderReq.OrderPaymentTime = in.OrderPaymentTime
}
if in.UnpaidTimeoutSecond != nil {
createOrderReq.UnpaidTimeoutSecond = in.UnpaidTimeoutSecond
}
if in.ChainType != nil {
createOrderReq.ChainType = in.ChainType
}
if in.TxHash != nil {
createOrderReq.TxHash = in.TxHash
}
if in.FromAddress != nil {
createOrderReq.FromAddress = in.FromAddress
}
if in.ToAddress != nil {
createOrderReq.ToAddress = in.ToAddress
}
if in.ChainFee != nil && *in.ChainFee != "" {
createOrderReq.ChainFee = decimalPtrFromString(*in.ChainFee)
}
if in.ChainFeeCrypto != nil {
createOrderReq.ChainFeeCrypto = in.ChainFeeCrypto
}
if in.Memo != nil {
createOrderReq.Memo = in.Memo
}
if in.OrderNote != nil {
createOrderReq.OrderNote = in.OrderNote
}
return createOrderReq, nil
}

View File

@ -0,0 +1,371 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
mockmodel "app-cloudep-order-server/internal/mock/model"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"testing"
"time"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
func TestBuildCreateOrderReq(t *testing.T) {
tests := []struct {
name string
input *order.CreateOrderReq
expectErr bool
expected *createOrderReq
}{
{
name: "所有必填字段存在,可選字段為空",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
expectErr: false,
expected: &createOrderReq{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
},
},
{
name: "包含可選字段",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ReferenceBrand: ptr("OtherBrand"),
WalletStatus: ptrInt64(1),
ThreePartyStatus: ptrInt64(2),
},
expectErr: false,
expected: &createOrderReq{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
ReferenceBrand: ptr("OtherBrand"),
WalletStatus: ptrInt64(1),
ThreePartyStatus: ptrInt64(2),
},
},
{
name: "無效的十進制字段",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "invalid-decimal", // 無效的數值
},
expectErr: true,
},
{
name: "空字符串的可選欄位應返回 nil",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ThirdPartyFee: ptr(""), // 空字符串
},
expectErr: false,
expected: &createOrderReq{
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: decimal.RequireFromString("10.5"),
OrderFee: decimal.RequireFromString("0.5"),
Amount: decimal.RequireFromString("100"),
ThirdPartyFee: nil, // 空字符串應返回 nil
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := buildCreateOrderReq(tt.input)
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}
func TestCreateOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
// 測試數據集
tests := []struct {
name string
input *order.CreateOrderReq
prepare func()
expectErr bool
}{
{
name: "成功建立訂單",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
{
name: "建立訂單時驗證失敗",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 模擬驗證失敗
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
},
expectErr: true,
},
{
name: "插入資料庫失敗",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬插入資料庫失敗
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("insert failed")).Times(1)
},
expectErr: true,
},
{
name: "成功建立訂單,必填字段",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
{
name: "成功建立訂單,可選字段有值",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ReferenceBrand: ptr("OtherBrand"),
WalletStatus: ptrInt64(1),
CryptoType: ptr("BTC"),
PaymentFiat: ptr("USD"),
OrderArrivalTime: ptrInt64(time.Now().UTC().Unix()),
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
{
name: "部分可選字段有值,其他為空",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ReferenceBrand: ptr("OtherBrand"),
WalletStatus: ptrInt64(1),
CryptoType: nil, // 空
PaymentFiat: nil, // 空
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
{
name: "無效的十進制數字",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "invalid", // 無效的數值
OrderFee: "0.5",
Amount: "100",
},
prepare: func() {
// 無需調用資料庫操作,因為會在驗證或轉換時失敗
},
expectErr: true,
},
{
name: "可選欄位為空字符串",
input: &order.CreateOrderReq{
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
ThirdPartyFee: ptr(""), // 空字符串
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫插入成功
mockOrderModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
},
expectErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 設置測試環境
tt.prepare()
// 初始化 CreateOrderLogic
logic := NewCreateOrderLogic(context.TODO(), svcCtx)
// 執行 CreateOrder
resp, err := logic.CreateOrder(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.NotNil(t, resp)
}
})
}
}
// ================================ 輔助函數 ================================
func ptr(s string) *string {
return &s
}
func ptrInt64(i int64) *int64 {
return &i
}
// decimalPtr 是一個輔助函數,用來將字串轉換為 *decimal.Decimal 指針
func decimalPtr(val string) *decimal.Decimal {
d, _ := decimal.NewFromString(val)
return &d
}

View File

@ -0,0 +1,92 @@
package orderservicelogic
import (
"github.com/shopspring/decimal"
"github.com/zeromicro/go-zero/core/logx"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Helper function for decimal conversion with nil support and logging
func decimalPtrFromString(val string) *decimal.Decimal {
if val == "" {
return nil
}
dec, err := decimal.NewFromString(val)
if err != nil {
logx.Errorf("Failed to convert string to decimal: %v", err)
return nil
}
return &dec
}
// Convert decimal.Decimal to primitive.Decimal128 and handle nil values
func convertDecimalPtrToDecimal128(d *decimal.Decimal) *primitive.Decimal128 {
if d == nil {
return nil
}
result, err := primitive.ParseDecimal128(d.String())
if err != nil {
logx.Errorf("Failed to convert decimal to Decimal128: %v", err)
return nil
}
return &result
}
// Convert decimal.Decimal (non-pointer) to primitive.Decimal128 and log errors
func convertDecimalToDecimal128(d decimal.Decimal) primitive.Decimal128 {
result, err := primitive.ParseDecimal128(d.String())
if err != nil {
logx.Errorf("Failed to convert decimal to Decimal128: %v", err)
return primitive.NewDecimal128(0, 0)
}
return result
}
// 將 primitive.Decimal128 轉換為字符串
func convertDecimal128ToString(d128 *primitive.Decimal128) *string {
if d128 == nil {
return nil
}
result := d128.String()
return &result
}
// Helper functions for optional fields
func optionalString(s *string) *string {
if s != nil {
return s
}
return nil
}
func optionalInt64(i *int64) *int64 {
if i != nil {
return i
}
return nil
}
func nilString(s *string) *string {
if s == nil {
return nil
}
return s
}
func nilInt64(i *int64) *int64 {
if i == nil {
return nil
}
return i
}

View File

@ -1,9 +1,11 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
"context"
"app-cloudep-order-server/gen_result/pb/tweeting"
ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-order-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
@ -23,9 +25,25 @@ func NewDeleteOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delet
}
}
// DeleteOrder 刪除訂單(軟刪除)
func (l *DeleteOrderLogic) DeleteOrder(in *tweeting.DeleteOrderReq) (*tweeting.OKResp, error) {
// todo: add your logic here and delete this line
return &tweeting.OKResp{}, nil
// DeleteOrderQuery 刪除訂單(軟刪除)
type DeleteOrderQuery struct {
BusinessID string `json:"business_id" validate:"required"`
}
// DeleteOrder 刪除訂單(軟刪除)
func (l *DeleteOrderLogic) DeleteOrder(in *order.DeleteOrderReq) (*order.OKResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&DeleteOrderQuery{
BusinessID: in.GetBusinessId(),
}); err != nil {
// 錯誤代碼 06-011-00
return nil, ers.InvalidFormat(err.Error())
}
_, err := l.svcCtx.OrderModel.DeleteByBusinessID(l.ctx, in.GetBusinessId())
if err != nil {
return nil, err
}
return &order.OKResp{}, nil
}

View File

@ -0,0 +1,97 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
mockmodel "app-cloudep-order-server/internal/mock/model"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
func TestDeleteOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
// 測試數據集
tests := []struct {
name string
input *order.DeleteOrderReq
prepare func()
expectErr bool
}{
{
name: "成功刪除訂單",
input: &order.DeleteOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫刪除成功
mockOrderModel.EXPECT().DeleteByBusinessID(gomock.Any(), "B123").Return(nil, nil).Times(1)
},
expectErr: false,
},
{
name: "驗證失敗",
input: &order.DeleteOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證失敗
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
},
expectErr: true,
},
{
name: "刪除資料庫失敗",
input: &order.DeleteOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬資料庫刪除失敗
mockOrderModel.EXPECT().DeleteByBusinessID(gomock.Any(), "B123").Return(nil, errors.New("delete failed")).Times(1)
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 設置測試環境
tt.prepare()
// 初始化 DeleteOrderLogic
logic := NewDeleteOrderLogic(context.TODO(), svcCtx)
// 執行 DeleteOrder
resp, err := logic.DeleteOrder(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.NotNil(t, resp)
}
})
}
}

View File

@ -1,10 +1,14 @@
package orderservicelogic
import (
"context"
"app-cloudep-order-server/gen_result/pb/tweeting"
"app-cloudep-order-server/gen_result/pb/order"
"app-cloudep-order-server/internal/domain"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
ers "code.30cm.net/digimon/library-go/errs"
"github.com/zeromicro/go-zero/core/stores/mon"
"github.com/zeromicro/go-zero/core/logx"
)
@ -23,9 +27,67 @@ func NewGetOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetOrder
}
}
// GetOrder 取得訂單詳情
func (l *GetOrderLogic) GetOrder(in *tweeting.GetOrderReq) (*tweeting.GetOrderResp, error) {
// todo: add your logic here and delete this line
return &tweeting.GetOrderResp{}, nil
// GetOrderQuery 取得訂單
type GetOrderQuery struct {
BusinessID string `json:"business_id" validate:"required"`
}
// GetOrder 取得訂單詳情
func (l *GetOrderLogic) GetOrder(in *order.GetOrderReq) (*order.GetOrderResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&GetOrderQuery{
BusinessID: in.GetBusinessId(),
}); err != nil {
// 錯誤代碼 06-011-00
return nil, ers.InvalidFormat(err.Error())
}
o, err := l.svcCtx.OrderModel.FindOneBusinessID(l.ctx, in.GetBusinessId())
if err != nil {
if errors.Is(mon.ErrNotFound, err) {
return nil, domain.NotFoundError(domain.DataNotFoundErrorCode, "failed to get this order id:", in.GetBusinessId())
}
return nil, err
}
return &order.GetOrderResp{
UpdateTime: o.UpdateTime,
CreateTime: o.CreateTime,
BusinessId: o.BusinessID,
OrderType: int32(o.OrderType),
OrderStatus: int32(o.OrderStatus),
Brand: o.Brand,
OrderUid: o.OrderUID,
ReferenceId: o.ReferenceID,
Count: o.Count.String(),
OrderFee: o.OrderFee.String(),
Amount: o.Amount.String(),
// 下面的為未來擴充用的欄位
ReferenceBrand: nilString(o.ReferenceBrand),
ReferenceUid: nilString(o.ReferenceUID),
WalletStatus: nilInt64(o.WalletStatus),
ThreePartyStatus: nilInt64(o.ThreePartyStatus),
DirectionType: nilInt64(o.DirectionType),
CryptoType: nilString(o.CryptoType),
ThirdPartyFee: convertDecimal128ToString(o.ThirdPartyFee),
CryptoToUsdtRate: convertDecimal128ToString(o.CryptoToUSDTRate),
FiatToUsdRate: convertDecimal128ToString(o.FiatToUSDRate),
FeeCryptoToUsdtRate: convertDecimal128ToString(o.FeeCryptoToUSDTRate),
UsdtToCryptoTypeRate: convertDecimal128ToString(o.USDTToCryptoTypeRate),
PaymentFiat: nilString(o.PaymentFiat),
PaymentUnitPrice: convertDecimal128ToString(o.PaymentUnitPrice),
PaymentTemplateId: nilString(o.PaymentTemplateID),
OrderArrivalTime: nilInt64(o.OrderArrivalTime),
OrderPaymentTime: nilInt64(o.OrderPaymentTime),
UnpaidTimeoutSecond: nilInt64(o.UnpaidTimeoutSecond),
ChainType: nilString(o.ChainType),
TxHash: nilString(o.TxHash),
FromAddress: nilString(o.FromAddress),
ToAddress: nilString(o.ToAddress),
ChainFee: convertDecimal128ToString(o.ChainFee),
ChainFeeCrypto: nilString(o.ChainFeeCrypto),
Memo: nilString(o.Memo),
OrderNote: nilString(o.OrderNote),
}, nil
}

View File

@ -0,0 +1,132 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
mockmodel "app-cloudep-order-server/internal/mock/model"
model "app-cloudep-order-server/internal/model/mongo"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"testing"
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
func TestGetOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
Count, _ := primitive.ParseDecimal128("10.5")
OrderFee, _ := primitive.ParseDecimal128("0.5")
Amount, _ := primitive.ParseDecimal128("100")
// 模擬返回的訂單數據
mockOrder := &model.Order{
UpdateTime: 1630000000,
CreateTime: 1620000000,
BusinessID: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUID: "UID123",
ReferenceID: "REF123",
Count: Count,
OrderFee: OrderFee,
Amount: Amount,
}
// 測試數據集
tests := []struct {
name string
input *order.GetOrderReq
prepare func()
expectErr bool
expected *order.GetOrderResp
}{
{
name: "成功取得訂單",
input: &order.GetOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬查詢訂單成功
mockOrderModel.EXPECT().FindOneBusinessID(gomock.Any(), "B123").Return(mockOrder, nil).Times(1)
},
expectErr: false,
expected: &order.GetOrderResp{
UpdateTime: 1630000000,
CreateTime: 1620000000,
BusinessId: "B123",
OrderType: 1,
OrderStatus: 2,
Brand: "TestBrand",
OrderUid: "UID123",
ReferenceId: "REF123",
Count: "10.5",
OrderFee: "0.5",
Amount: "100",
},
},
{
name: "驗證失敗",
input: &order.GetOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證失敗
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
},
expectErr: true,
},
{
name: "查詢訂單失敗",
input: &order.GetOrderReq{
BusinessId: "B123",
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬查詢訂單失敗
mockOrderModel.EXPECT().FindOneBusinessID(gomock.Any(), "B123").Return(nil, errors.New("order not found")).Times(1)
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 設置測試環境
tt.prepare()
// 初始化 GetOrderLogic
logic := NewGetOrderLogic(context.TODO(), svcCtx)
// 執行 GetOrder
resp, err := logic.GetOrder(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, resp)
}
})
}
}

View File

@ -1,11 +1,14 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
model "app-cloudep-order-server/internal/model/mongo"
"context"
"app-cloudep-order-server/gen_result/pb/tweeting"
"app-cloudep-order-server/internal/svc"
ers "code.30cm.net/digimon/library-go/errs"
"github.com/zeromicro/go-zero/core/logx"
)
@ -23,9 +26,125 @@ func NewListOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListOrd
}
}
// ListOrder 取得訂單列表
func (l *ListOrderLogic) ListOrder(in *tweeting.ListOrderReq) (*tweeting.ListOrderResp, error) {
// todo: add your logic here and delete this line
type GetOrderListReq struct {
PageIndex int64 `json:"page_index" validate:"required"`
PageSize int64 `json:"page_size" validate:"required"`
return &tweeting.ListOrderResp{}, nil
ReferenceID string `json:"reference_id"`
ReferenceUID string `json:"reference_uid"`
BusinessID string `json:"business_id"`
UID string `json:"uid"`
OrderType int `json:"order_type"`
DirectionType []int64 `json:"direction_type"`
OrderStatus []int64 `json:"order_status"`
StartCreateTime int64 `json:"start_create_time"`
EndCreateTime int64 `json:"end_create_time"`
StartUpdateTime int64 `json:"start_update_time"`
EndUpdateTime int64 `json:"end_update_time"`
StartOrderArrivalTime int64 `json:"start_order_arrival_time"`
EndOrderArrivalTime int64 `json:"end_order_arrival_time"`
StartOrderPaymentTime int64 `json:"start_order_payment_time"`
EndOrderPaymentTime int64 `json:"end_order_payment_time"`
CryptoType string `json:"crypto_type"`
TxHash string `json:"tx_hash"`
}
// ListOrder 取得訂單列表
func (l *ListOrderLogic) ListOrder(in *order.ListOrderReq) (*order.ListOrderResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&GetOrderListReq{
PageIndex: in.GetPageIndex(),
PageSize: in.GetPageSize(),
}); err != nil {
// 錯誤代碼 06-011-00
return nil, ers.InvalidFormat(err.Error())
}
// 構建查詢條件
req := model.GetOrderListReq{
PageIndex: in.GetPageIndex(),
PageSize: in.GetPageSize(),
ReferenceID: in.ReferenceId,
ReferenceUID: in.ReferenceUid,
BusinessID: in.BusinessId,
UID: in.Uid,
OrderType: int(in.OrderType),
DirectionType: in.DirectionType,
OrderStatus: in.OrderStatus,
StartCreateTime: in.StartCreateTime,
EndCreateTime: in.EndCreateTime,
StartUpdateTime: in.StartUpdateTime,
EndUpdateTime: in.EndUpdateTime,
StartOrderArrivalTime: in.StartOrderArrivalTime,
EndOrderArrivalTime: in.EndOrderArrivalTime,
StartOrderPaymentTime: in.StartOrderPaymentTime,
EndOrderPaymentTime: in.EndOrderPaymentTime,
CryptoType: in.CryptoType,
TxHash: in.TxHash,
}
// 查詢訂單
orders, total, err := l.svcCtx.OrderModel.ListOrder(l.ctx, req)
if err != nil {
return nil, err
}
// 構建返回結果
res := make([]*order.GetOrderResp, 0, len(orders))
for _, item := range orders {
res = append(res, convertOrderToGetOrderResp(item))
}
return &order.ListOrderResp{
Data: res,
Page: &order.Pager{
Total: total,
Index: in.GetPageIndex(),
Size: in.GetPageSize(),
},
}, nil
}
// ConvertOrderToGetOrderResp 將 Order 結構轉換為 GetOrderResp
func convertOrderToGetOrderResp(o model.Order) *order.GetOrderResp {
return &order.GetOrderResp{
BusinessId: o.BusinessID,
OrderType: int32(o.OrderType),
OrderStatus: int32(o.OrderStatus),
Brand: o.Brand,
OrderUid: o.OrderUID,
ReferenceId: o.ReferenceID,
Count: o.Count.String(),
OrderFee: o.OrderFee.String(),
Amount: o.Amount.String(),
ReferenceBrand: optionalString(o.ReferenceBrand),
ReferenceUid: optionalString(o.ReferenceUID),
WalletStatus: optionalInt64(o.WalletStatus),
ThreePartyStatus: optionalInt64(o.ThreePartyStatus),
DirectionType: optionalInt64(o.DirectionType),
CryptoType: optionalString(o.CryptoType),
ThirdPartyFee: convertDecimal128ToString(o.ThirdPartyFee),
CryptoToUsdtRate: convertDecimal128ToString(o.CryptoToUSDTRate),
FiatToUsdRate: convertDecimal128ToString(o.FiatToUSDRate),
FeeCryptoToUsdtRate: convertDecimal128ToString(o.FeeCryptoToUSDTRate),
UsdtToCryptoTypeRate: convertDecimal128ToString(o.USDTToCryptoTypeRate),
PaymentFiat: optionalString(o.PaymentFiat),
PaymentUnitPrice: convertDecimal128ToString(o.PaymentUnitPrice),
PaymentTemplateId: optionalString(o.PaymentTemplateID),
OrderArrivalTime: optionalInt64(o.OrderArrivalTime),
OrderPaymentTime: optionalInt64(o.OrderPaymentTime),
UnpaidTimeoutSecond: optionalInt64(o.UnpaidTimeoutSecond),
ChainType: optionalString(o.ChainType),
TxHash: optionalString(o.TxHash),
FromAddress: optionalString(o.FromAddress),
ToAddress: optionalString(o.ToAddress),
ChainFee: convertDecimal128ToString(o.ChainFee),
ChainFeeCrypto: optionalString(o.ChainFeeCrypto),
Memo: optionalString(o.Memo),
OrderNote: optionalString(o.OrderNote),
CreateTime: o.CreateTime,
UpdateTime: o.UpdateTime,
}
}

View File

@ -0,0 +1,444 @@
package orderservicelogic
// func TestConvertOrdersToGetOrderResp(t *testing.T) {
// tests := []struct {
// name string
// input []model.Order
// expected []*order.GetOrderResp
// }{
// {
// name: "空訂單列表",
// input: []model.Order{},
// expected: []*order.GetOrderResp{},
// },
// {
// name: "單筆訂單",
// input: []model.Order{
// {
// BusinessID: "B123",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUID: "UID123",
// ReferenceID: "REF123",
// Count: decimal.RequireFromString("10.5"),
// OrderFee: decimal.RequireFromString("0.5"),
// Amount: decimal.RequireFromString("100"),
// },
// },
// expected: []*order.GetOrderResp{
// {
// BusinessId: "B123",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUid: "UID123",
// ReferenceId: "REF123",
// Count: "10.5",
// OrderFee: "0.5",
// Amount: "100",
// },
// },
// },
// {
// name: "多筆訂單",
// input: []model.Order{
// {
// BusinessID: "B123",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUID: "UID123",
// ReferenceID: "REF123",
// Count: decimal.RequireFromString("10.5"),
// OrderFee: decimal.RequireFromString("0.5"),
// Amount: decimal.RequireFromString("100"),
// },
// {
// BusinessID: "B456",
// OrderType: 2,
// OrderStatus: 3,
// Brand: "OtherBrand",
// OrderUID: "UID456",
// ReferenceID: "REF456",
// Count: decimal.RequireFromString("20"),
// OrderFee: decimal.RequireFromString("1"),
// Amount: decimal.RequireFromString("200"),
// },
// },
// expected: []*order.GetOrderResp{
// {
// BusinessId: "B123",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUid: "UID123",
// ReferenceId: "REF123",
// Count: "10.5",
// OrderFee: "0.5",
// Amount: "100",
// },
// {
// BusinessId: "B456",
// OrderType: 2,
// OrderStatus: 3,
// Brand: "OtherBrand",
// OrderUid: "UID456",
// ReferenceId: "REF456",
// Count: "20",
// OrderFee: "1",
// Amount: "200",
// },
// },
// },
// }
//
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// // 執行 ConvertOrdersToGetOrderResp 函數
// result := ConvertOrdersToGetOrderResp(tt.input)
//
// // 驗證結果
// assert.Equal(t, tt.expected, result)
// })
// }
// }
//
// func TestOptionalString(t *testing.T) {
// t.Run("非 nil 字串", func(t *testing.T) {
// str := "hello"
// result := optionalString(&str)
// assert.NotNil(t, result)
// assert.Equal(t, &str, result)
// })
//
// t.Run("nil 字串", func(t *testing.T) {
// result := optionalString(nil)
// assert.Nil(t, result)
// })
// }
//
// func TestOptionalInt64(t *testing.T) {
// t.Run("非 nil int64", func(t *testing.T) {
// num := int64(123)
// result := optionalInt64(&num)
// assert.NotNil(t, result)
// assert.Equal(t, &num, result)
// })
//
// t.Run("nil int64", func(t *testing.T) {
// result := optionalInt64(nil)
// assert.Nil(t, result)
// })
// }
//
// func TestOptionalDecimalToString(t *testing.T) {
// t.Run("非 nil decimal", func(t *testing.T) {
// dec := decimal.NewFromInt(123)
// expected := "123"
// result := optionalDecimalToString(&dec)
// assert.NotNil(t, result)
// assert.Equal(t, &expected, result)
// })
//
// t.Run("nil decimal", func(t *testing.T) {
// result := optionalDecimalToString(nil)
// assert.Nil(t, result)
// })
// }
//
// func TestConvertOrderToGetOrderResp(t *testing.T) {
// tests := []struct {
// name string
// input model.Order
// expected *order.GetOrderResp
// }{
// {
// name: "所有欄位都有值",
// input: model.Order{
// BusinessID: "B123",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUID: "UID123",
// ReferenceID: "REF123",
// Count: decimal.RequireFromString("10.5"),
// OrderFee: decimal.RequireFromString("0.5"),
// Amount: decimal.RequireFromString("100"),
// ReferenceBrand: ptrString("OtherBrand"),
// ReferenceUID: ptrString("REFUID"),
// WalletStatus: ptrInt64(1),
// ThreePartyStatus: ptrInt64(2),
// DirectionType: ptrInt64(1),
// CryptoType: ptrString("BTC"),
// ThirdPartyFee: decimalPtrFromString("0.01"),
// CryptoToUSDTRate: decimalPtrFromString("50000"),
// FiatToUSDRate: decimalPtrFromString("1"),
// FeeCryptoToUSDTRate: decimalPtrFromString("0.02"),
// USDTToCryptoTypeRate: decimalPtrFromString("0.00002"),
// PaymentFiat: ptrString("USD"),
// PaymentUnitPrice: decimalPtrFromString("50000"),
// PaymentTemplateID: ptrString("TEMPLATE123"),
// OrderArrivalTime: ptrInt64(1630000000),
// OrderPaymentTime: ptrInt64(1620000000),
// UnpaidTimeoutSecond: ptrInt64(3600),
// ChainType: ptrString("ETH"),
// TxHash: ptrString("0xABC123"),
// FromAddress: ptrString("0xFROM123"),
// ToAddress: ptrString("0xTO123"),
// ChainFee: decimalPtrFromString("0.001"),
// ChainFeeCrypto: ptrString("ETH"),
// Memo: ptrString("Test Memo"),
// OrderNote: ptrString("Test Note"),
// CreateTime: 1620000000,
// UpdateTime: 1630000000,
// },
// expected: &order.GetOrderResp{
// BusinessId: "B123",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUid: "UID123",
// ReferenceId: "REF123",
// Count: "10.5",
// OrderFee: "0.5",
// Amount: "100",
// ReferenceBrand: ptrString("OtherBrand"),
// ReferenceUid: ptrString("REFUID"),
// WalletStatus: ptrInt64(1),
// ThreePartyStatus: ptrInt64(2),
// DirectionType: ptrInt64(1),
// CryptoType: ptrString("BTC"),
// ThirdPartyFee: ptrString("0.01"),
// CryptoToUsdtRate: ptrString("50000"),
// FiatToUsdRate: ptrString("1"),
// FeeCryptoToUsdtRate: ptrString("0.02"),
// UsdtToCryptoTypeRate: ptrString("0.00002"),
// PaymentFiat: ptrString("USD"),
// PaymentUnitPrice: ptrString("50000"),
// PaymentTemplateId: ptrString("TEMPLATE123"),
// OrderArrivalTime: ptrInt64(1630000000),
// OrderPaymentTime: ptrInt64(1620000000),
// UnpaidTimeoutSecond: ptrInt64(3600),
// ChainType: ptrString("ETH"),
// TxHash: ptrString("0xABC123"),
// FromAddress: ptrString("0xFROM123"),
// ToAddress: ptrString("0xTO123"),
// ChainFee: ptrString("0.001"),
// ChainFeeCrypto: ptrString("ETH"),
// Memo: ptrString("Test Memo"),
// OrderNote: ptrString("Test Note"),
// CreateTime: 1620000000,
// UpdateTime: 1630000000,
// },
// },
// {
// name: "部分欄位為 nil",
// input: model.Order{
// BusinessID: "B456",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUID: "UID456",
// ReferenceID: "REF456",
// Count: decimal.RequireFromString("10"),
// OrderFee: decimal.RequireFromString("1"),
// Amount: decimal.RequireFromString("100"),
// ReferenceBrand: nil,
// ReferenceUID: nil,
// WalletStatus: nil,
// ThreePartyStatus: nil,
// DirectionType: nil,
// CryptoType: nil,
// ThirdPartyFee: nil,
// CryptoToUSDTRate: nil,
// FiatToUSDRate: nil,
// FeeCryptoToUSDTRate: nil,
// USDTToCryptoTypeRate: nil,
// PaymentFiat: nil,
// PaymentUnitPrice: nil,
// PaymentTemplateID: nil,
// OrderArrivalTime: nil,
// OrderPaymentTime: nil,
// UnpaidTimeoutSecond: nil,
// ChainType: nil,
// TxHash: nil,
// FromAddress: nil,
// ToAddress: nil,
// ChainFee: nil,
// ChainFeeCrypto: nil,
// Memo: nil,
// OrderNote: nil,
// CreateTime: 1620000000,
// UpdateTime: 1630000000,
// },
// expected: &order.GetOrderResp{
// BusinessId: "B456",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUid: "UID456",
// ReferenceId: "REF456",
// Count: "10",
// OrderFee: "1",
// Amount: "100",
// ReferenceBrand: nil,
// ReferenceUid: nil,
// WalletStatus: nil,
// ThreePartyStatus: nil,
// DirectionType: nil,
// CryptoType: nil,
// ThirdPartyFee: nil,
// CryptoToUsdtRate: nil,
// FiatToUsdRate: nil,
// FeeCryptoToUsdtRate: nil,
// UsdtToCryptoTypeRate: nil,
// PaymentFiat: nil,
// PaymentUnitPrice: nil,
// PaymentTemplateId: nil,
// OrderArrivalTime: nil,
// OrderPaymentTime: nil,
// UnpaidTimeoutSecond: nil,
// ChainType: nil,
// TxHash: nil,
// FromAddress: nil,
// ToAddress: nil,
// ChainFee: nil,
// ChainFeeCrypto: nil,
// Memo: nil,
// OrderNote: nil,
// CreateTime: 1620000000,
// UpdateTime: 1630000000,
// },
// },
// }
//
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// // 執行 ConvertOrderToGetOrderResp 函數
// result := ConvertOrderToGetOrderResp(tt.input)
//
// // 驗證結果
// assert.Equal(t, tt.expected, result)
// })
// }
// }
//
// func TestListOrder(t *testing.T) {
// ctrl := gomock.NewController(t)
// defer ctrl.Finish()
//
// // 初始化 mock 依賴
// mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
// mockValidate := mocksvc.NewMockValidate(ctrl)
//
// // 初始化服務上下文
// svcCtx := &svc.ServiceContext{
// OrderModel: mockOrderModel,
// Validate: mockValidate,
// }
//
// // 測試數據集
// tests := []struct {
// name string
// input *order.ListOrderReq
// prepare func()
// expectErr bool
// expectedRes *order.ListOrderResp
// }{
// {
// name: "成功取得訂單列表",
// input: &order.ListOrderReq{
// PageIndex: 1,
// PageSize: 10,
// },
// prepare: func() {
// // 模擬驗證成功
// mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// // 模擬返回訂單列表
// mockOrderModel.EXPECT().ListOrder(gomock.Any(), gomock.Any()).Return([]model.Order{
// {
// BusinessID: "B123",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUID: "UID123",
// ReferenceID: "REF123",
// Count: decimal.RequireFromString("10.5"),
// OrderFee: decimal.RequireFromString("0.5"),
// Amount: decimal.RequireFromString("100"),
// },
// }, int64(1), nil).Times(1)
// },
// expectErr: false,
// expectedRes: &order.ListOrderResp{
// Data: []*order.GetOrderResp{
// {
// BusinessId: "B123",
// OrderType: 1,
// OrderStatus: 2,
// Brand: "TestBrand",
// OrderUid: "UID123",
// ReferenceId: "REF123",
// Count: "10.5",
// OrderFee: "0.5",
// Amount: "100",
// },
// },
// Page: &order.Pager{
// Total: 1,
// Index: 1,
// Size: 10,
// },
// },
// },
// {
// name: "訂單查詢失敗",
// input: &order.ListOrderReq{
// PageIndex: 1,
// PageSize: 10,
// },
// prepare: func() {
// // 模擬驗證成功
// mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// // 模擬返回查詢錯誤
// mockOrderModel.EXPECT().ListOrder(gomock.Any(), gomock.Any()).Return(nil, int64(0), errors.New("query failed")).Times(1)
// },
// expectErr: true,
// },
// {
// name: "分頁錯誤",
// input: &order.ListOrderReq{
// PageIndex: 0,
// PageSize: 10,
// },
// prepare: func() {
// // 模擬驗證失敗,因為 PageIndex 為 0
// mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("invalid pagination")).Times(1)
// },
// expectErr: true,
// },
// }
//
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// // 準備測試環境
// tt.prepare()
//
// // 初始化 ListOrderLogic
// logic := NewListOrderLogic(context.TODO(), svcCtx)
//
// // 執行 ListOrder
// resp, err := logic.ListOrder(tt.input)
//
// // 驗證結果
// if tt.expectErr {
// assert.Error(t, err)
// assert.Nil(t, resp)
// } else {
// assert.NoError(t, err)
// assert.Equal(t, tt.expectedRes, resp)
// }
// })
// }
// }

View File

@ -1,31 +0,0 @@
package orderservicelogic
import (
"context"
"app-cloudep-order-server/gen_result/pb/tweeting"
"app-cloudep-order-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type ModifyOrderLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewModifyOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModifyOrderLogic {
return &ModifyOrderLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// ModifyOrder 修改訂單
func (l *ModifyOrderLogic) ModifyOrder(in *tweeting.ModifyOrderReq) (*tweeting.OKResp, error) {
// todo: add your logic here and delete this line
return &tweeting.OKResp{}, nil
}

View File

@ -1,9 +1,13 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
"app-cloudep-order-server/internal/domain"
model "app-cloudep-order-server/internal/model/mongo"
"context"
"app-cloudep-order-server/gen_result/pb/tweeting"
ers "code.30cm.net/digimon/library-go/errs"
"app-cloudep-order-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
@ -23,9 +27,46 @@ func NewModifyOrderStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext)
}
}
// ModifyOrderStatus 修改訂單狀態
func (l *ModifyOrderStatusLogic) ModifyOrderStatus(in *tweeting.ModifyOrderStatusReq) (*tweeting.OKResp, error) {
// todo: add your logic here and delete this line
// ModifyOrderQuery
// 0.建立訂單 1.建單失敗 2.審核中 3.付款中 4.已付款
// 5.已付款待轉帳 6.申訴中 7.交易完成
// 8.交易失敗 9.交易取消 10.交易異常 11.交易超時
return &tweeting.OKResp{}, nil
type ModifyOrderQuery struct {
BusinessID string `json:"business_id" validate:"required"`
Status int64 `json:"status" validate:"required,oneof=2 3 4 5 6 7 8 11"`
}
// ModifyOrderStatus 修改訂單狀態
func (l *ModifyOrderStatusLogic) ModifyOrderStatus(in *order.ModifyOrderStatusReq) (*order.OKResp, error) {
// 驗證資料
if err := l.svcCtx.Validate.ValidateAll(&ModifyOrderQuery{
BusinessID: in.GetBusinessId(),
Status: in.GetStatus(),
}); err != nil {
// 錯誤代碼 06-011-00
return nil, ers.InvalidFormat(err.Error())
}
_, err := l.svcCtx.OrderModel.UpdateStatus(l.ctx,
model.UpdateStatusReq{
BusinessID: in.GetBusinessId(),
Status: in.GetStatus(),
})
if err != nil {
// 錯誤代碼 06-021-02
e := domain.CommentErrorL(
domain.ModifyOrderErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "req", Value: in},
{Key: "func", Value: "OrderModel.UpdateStatus"},
{Key: "err", Value: err},
},
"failed to update order status:").Wrap(err)
return nil, e
}
return &order.OKResp{}, nil
}

View File

@ -0,0 +1,102 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mocksvc "app-cloudep-order-server/internal/mock/lib"
mockmodel "app-cloudep-order-server/internal/mock/model"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
func TestModifyOrderStatus(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
mockValidate := mocksvc.NewMockValidate(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
Validate: mockValidate,
}
// 測試數據集
tests := []struct {
name string
input *order.ModifyOrderStatusReq
prepare func()
expectErr bool
expectedRes *order.OKResp
}{
{
name: "成功修改訂單狀態",
input: &order.ModifyOrderStatusReq{
BusinessId: "B123",
Status: 3,
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬狀態更新成功
mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
},
expectErr: false,
expectedRes: &order.OKResp{},
},
{
name: "驗證失敗",
input: &order.ModifyOrderStatusReq{
BusinessId: "B123",
Status: 12, // 錯誤的狀態值
},
prepare: func() {
// 模擬驗證失敗
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("invalid status")).Times(1)
},
expectErr: true,
},
{
name: "修改訂單狀態失敗",
input: &order.ModifyOrderStatusReq{
BusinessId: "B123",
Status: 3,
},
prepare: func() {
// 模擬驗證成功
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
// 模擬狀態更新失敗
mockOrderModel.EXPECT().UpdateStatus(gomock.Any(), gomock.Any()).Return(nil, errors.New("update failed")).Times(1)
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 準備測試環境
tt.prepare()
// 初始化 ModifyOrderStatusLogic
logic := NewModifyOrderStatusLogic(context.TODO(), svcCtx)
// 執行 ModifyOrderStatus
resp, err := logic.ModifyOrderStatus(tt.input)
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedRes, resp)
}
})
}
}

View File

@ -1,9 +1,12 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
"app-cloudep-order-server/internal/domain"
model "app-cloudep-order-server/internal/model/mongo"
"context"
"time"
"app-cloudep-order-server/gen_result/pb/tweeting"
"app-cloudep-order-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
@ -24,8 +27,25 @@ func NewOrderStatusTimeoutLogic(ctx context.Context, svcCtx *svc.ServiceContext)
}
// OrderStatusTimeout 訂單超時任務/cron/order-status/timeout
func (l *OrderStatusTimeoutLogic) OrderStatusTimeout(in *tweeting.OrderStatusTimeoutReq) (*tweeting.OKResp, error) {
// todo: add your logic here and delete this line
func (l *OrderStatusTimeoutLogic) OrderStatusTimeout(_ *order.OrderStatusTimeoutReq) (*order.OKResp, error) {
now := time.Now().UTC().UnixNano()
_, err := l.svcCtx.OrderModel.UpdateTimeoutOrder(l.ctx, model.UpdateTimeoutReq{
CreateTimeBefore: now,
})
if err != nil {
// 錯誤代碼 06-021-02
e := domain.CommentErrorL(
domain.TimeoutOrderErrorCode,
logx.WithContext(l.ctx),
[]logx.LogField{
{Key: "now", Value: now},
{Key: "func", Value: "OrderModel.UpdateTimeoutOrder"},
{Key: "err", Value: err},
},
"failed to update timeout order").Wrap(err)
return &tweeting.OKResp{}, nil
return nil, e
}
return &order.OKResp{}, nil
}

View File

@ -0,0 +1,74 @@
package orderservicelogic
import (
"app-cloudep-order-server/gen_result/pb/order"
mockmodel "app-cloudep-order-server/internal/mock/model"
"app-cloudep-order-server/internal/svc"
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
func TestOrderStatusTimeout(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 初始化 mock 依賴
mockOrderModel := mockmodel.NewMockOrderModel(ctrl)
// 初始化服務上下文
svcCtx := &svc.ServiceContext{
OrderModel: mockOrderModel,
}
// 測試數據集
tests := []struct {
name string
prepare func()
expectErr bool
expectedRes *order.OKResp
}{
{
name: "成功更新訂單狀態",
prepare: func() {
// 模擬訂單狀態超時更新成功
mockOrderModel.EXPECT().UpdateTimeoutOrder(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
},
expectErr: false,
expectedRes: &order.OKResp{},
},
{
name: "更新訂單狀態失敗",
prepare: func() {
// 模擬訂單狀態超時更新失敗
mockOrderModel.EXPECT().UpdateTimeoutOrder(gomock.Any(), gomock.Any()).Return(nil, errors.New("update failed")).Times(1)
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 準備測試環境
tt.prepare()
// 初始化 OrderStatusTimeoutLogic
logic := NewOrderStatusTimeoutLogic(context.TODO(), svcCtx)
// 執行 OrderStatusTimeout
resp, err := logic.OrderStatusTimeout(&order.OrderStatusTimeoutReq{})
// 驗證結果
if tt.expectErr {
assert.Error(t, err)
assert.Nil(t, resp)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedRes, resp)
}
})
}
}

View File

@ -0,0 +1,73 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./validate.go
//
// Generated by this command:
//
// mockgen -source=./validate.go -destination=../../mock/lib/validate.go -package=lib
//
// Package lib is a generated GoMock package.
package lib
import (
reflect "reflect"
required "code.30cm.net/digimon/library-go/validator"
gomock "go.uber.org/mock/gomock"
)
// MockValidate is a mock of Validate interface.
type MockValidate struct {
ctrl *gomock.Controller
recorder *MockValidateMockRecorder
}
// MockValidateMockRecorder is the mock recorder for MockValidate.
type MockValidateMockRecorder struct {
mock *MockValidate
}
// NewMockValidate creates a new mock instance.
func NewMockValidate(ctrl *gomock.Controller) *MockValidate {
mock := &MockValidate{ctrl: ctrl}
mock.recorder = &MockValidateMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockValidate) EXPECT() *MockValidateMockRecorder {
return m.recorder
}
// BindToValidator mocks base method.
func (m *MockValidate) BindToValidator(opts ...required.Option) error {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range opts {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "BindToValidator", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// BindToValidator indicates an expected call of BindToValidator.
func (mr *MockValidateMockRecorder) BindToValidator(opts ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindToValidator", reflect.TypeOf((*MockValidate)(nil).BindToValidator), opts...)
}
// ValidateAll mocks base method.
func (m *MockValidate) ValidateAll(obj any) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateAll", obj)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateAll indicates an expected call of ValidateAll.
func (mr *MockValidateMockRecorder) ValidateAll(obj any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAll", reflect.TypeOf((*MockValidate)(nil).ValidateAll), obj)
}

View File

@ -10,11 +10,11 @@
package mock
import (
mongo "app-cloudep-order-server/internal/model/mongo"
model "app-cloudep-order-server/internal/model/mongo"
context "context"
reflect "reflect"
mongo0 "go.mongodb.org/mongo-driver/mongo"
mongo "go.mongodb.org/mongo-driver/mongo"
gomock "go.uber.org/mock/gomock"
)
@ -56,11 +56,26 @@ func (mr *MockOrderModelMockRecorder) Delete(ctx, id any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockOrderModel)(nil).Delete), ctx, id)
}
// DeleteByBusinessID mocks base method.
func (m *MockOrderModel) DeleteByBusinessID(ctx context.Context, id string) (*mongo.UpdateResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteByBusinessID", ctx, id)
ret0, _ := ret[0].(*mongo.UpdateResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DeleteByBusinessID indicates an expected call of DeleteByBusinessID.
func (mr *MockOrderModelMockRecorder) DeleteByBusinessID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteByBusinessID", reflect.TypeOf((*MockOrderModel)(nil).DeleteByBusinessID), ctx, id)
}
// FindOne mocks base method.
func (m *MockOrderModel) FindOne(ctx context.Context, id string) (*mongo.Order, error) {
func (m *MockOrderModel) FindOne(ctx context.Context, id string) (*model.Order, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*mongo.Order)
ret0, _ := ret[0].(*model.Order)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -71,8 +86,23 @@ func (mr *MockOrderModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockOrderModel)(nil).FindOne), ctx, id)
}
// FindOneBusinessID mocks base method.
func (m *MockOrderModel) FindOneBusinessID(ctx context.Context, id string) (*model.Order, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneBusinessID", ctx, id)
ret0, _ := ret[0].(*model.Order)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneBusinessID indicates an expected call of FindOneBusinessID.
func (mr *MockOrderModelMockRecorder) FindOneBusinessID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneBusinessID", reflect.TypeOf((*MockOrderModel)(nil).FindOneBusinessID), ctx, id)
}
// Insert mocks base method.
func (m *MockOrderModel) Insert(ctx context.Context, data *mongo.Order) error {
func (m *MockOrderModel) Insert(ctx context.Context, data *model.Order) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(error)
@ -85,11 +115,27 @@ func (mr *MockOrderModelMockRecorder) Insert(ctx, data any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockOrderModel)(nil).Insert), ctx, data)
}
// ListOrder mocks base method.
func (m *MockOrderModel) ListOrder(ctx context.Context, req model.GetOrderListReq) ([]model.Order, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListOrder", ctx, req)
ret0, _ := ret[0].([]model.Order)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListOrder indicates an expected call of ListOrder.
func (mr *MockOrderModelMockRecorder) ListOrder(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListOrder", reflect.TypeOf((*MockOrderModel)(nil).ListOrder), ctx, req)
}
// Update mocks base method.
func (m *MockOrderModel) Update(ctx context.Context, data *mongo.Order) (*mongo0.UpdateResult, error) {
func (m *MockOrderModel) Update(ctx context.Context, data *model.Order) (*mongo.UpdateResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(*mongo0.UpdateResult)
ret0, _ := ret[0].(*mongo.UpdateResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -99,3 +145,33 @@ func (mr *MockOrderModelMockRecorder) Update(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockOrderModel)(nil).Update), ctx, data)
}
// UpdateStatus mocks base method.
func (m *MockOrderModel) UpdateStatus(ctx context.Context, data model.UpdateStatusReq) (*mongo.UpdateResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateStatus", ctx, data)
ret0, _ := ret[0].(*mongo.UpdateResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateStatus indicates an expected call of UpdateStatus.
func (mr *MockOrderModelMockRecorder) UpdateStatus(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockOrderModel)(nil).UpdateStatus), ctx, data)
}
// UpdateTimeoutOrder mocks base method.
func (m *MockOrderModel) UpdateTimeoutOrder(ctx context.Context, req model.UpdateTimeoutReq) (*mongo.UpdateResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateTimeoutOrder", ctx, req)
ret0, _ := ret[0].(*mongo.UpdateResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateTimeoutOrder indicates an expected call of UpdateTimeoutOrder.
func (mr *MockOrderModelMockRecorder) UpdateTimeoutOrder(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTimeoutOrder", reflect.TypeOf((*MockOrderModel)(nil).UpdateTimeoutOrder), ctx, req)
}

View File

@ -10,11 +10,11 @@
package mock
import (
mongo "app-cloudep-order-server/internal/model/mongo"
model "app-cloudep-order-server/internal/model/mongo"
context "context"
reflect "reflect"
mongo0 "go.mongodb.org/mongo-driver/mongo"
mongo "go.mongodb.org/mongo-driver/mongo"
gomock "go.uber.org/mock/gomock"
)
@ -57,10 +57,10 @@ func (mr *MockorderModelMockRecorder) Delete(ctx, id any) *gomock.Call {
}
// FindOne mocks base method.
func (m *MockorderModel) FindOne(ctx context.Context, id string) (*mongo.Order, error) {
func (m *MockorderModel) FindOne(ctx context.Context, id string) (*model.Order, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*mongo.Order)
ret0, _ := ret[0].(*model.Order)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -72,7 +72,7 @@ func (mr *MockorderModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
}
// Insert mocks base method.
func (m *MockorderModel) Insert(ctx context.Context, data *mongo.Order) error {
func (m *MockorderModel) Insert(ctx context.Context, data *model.Order) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(error)
@ -86,10 +86,10 @@ func (mr *MockorderModelMockRecorder) Insert(ctx, data any) *gomock.Call {
}
// Update mocks base method.
func (m *MockorderModel) Update(ctx context.Context, data *mongo.Order) (*mongo0.UpdateResult, error) {
func (m *MockorderModel) Update(ctx context.Context, data *model.Order) (*mongo.UpdateResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(*mongo0.UpdateResult)
ret0, _ := ret[0].(*mongo.UpdateResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}

View File

@ -1,6 +1,17 @@
package model
import "github.com/zeromicro/go-zero/core/stores/mon"
import (
"app-cloudep-order-server/internal/domain"
"context"
"errors"
"time"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
var _ OrderModel = (*customOrderModel)(nil)
@ -9,11 +20,50 @@ type (
// and implement the added methods in customOrderModel.
OrderModel interface {
orderModel
UpdateStatus(ctx context.Context, data UpdateStatusReq) (*mongo.UpdateResult, error)
UpdateTimeoutOrder(ctx context.Context, req UpdateTimeoutReq) (*mongo.UpdateResult, error)
DeleteByBusinessID(ctx context.Context, id string) (*mongo.UpdateResult, error)
FindOneBusinessID(ctx context.Context, id string) (*Order, error)
ListOrder(ctx context.Context, req GetOrderListReq) ([]Order, int64, error)
}
customOrderModel struct {
*defaultOrderModel
}
UpdateStatusReq struct {
BusinessID string
Status int64
}
UpdateTimeoutReq struct {
CreateTimeBefore int64
}
GetOrderListReq struct {
PageIndex int64 `json:"page_index" validate:"required"`
PageSize int64 `json:"page_size" validate:"required"`
ReferenceID string `json:"reference_id"`
ReferenceUID string `json:"reference_uid"`
BusinessID string `json:"business_id"`
UID string `json:"uid"`
OrderType int `json:"order_type"`
DirectionType []int32 `json:"direction_type"`
OrderStatus []int32 `json:"order_status"`
StartCreateTime int64 `json:"start_create_time"`
EndCreateTime int64 `json:"end_create_time"`
StartUpdateTime int64 `json:"start_update_time"`
EndUpdateTime int64 `json:"end_update_time"`
StartOrderArrivalTime int64 `json:"start_order_arrival_time"`
EndOrderArrivalTime int64 `json:"end_order_arrival_time"`
StartOrderPaymentTime int64 `json:"start_order_payment_time"`
EndOrderPaymentTime int64 `json:"end_order_payment_time"`
CryptoType string `json:"crypto_type"`
TxHash string `json:"tx_hash"`
}
)
// NewOrderModel returns a model for the mongo.
@ -23,3 +73,168 @@ func NewOrderModel(url, db, collection string) OrderModel {
defaultOrderModel: newDefaultOrderModel(conn),
}
}
func (m *customOrderModel) UpdateStatus(ctx context.Context, data UpdateStatusReq) (*mongo.UpdateResult, error) {
// 初始化 updates map
updates := make(map[string]any)
// 更新 update_time 和 status
updates["update_time"] = time.Now().UTC().UnixNano()
updates["order_status"] = data.Status
// 使用 updates map 作為 $set 的內容
res, err := m.conn.UpdateOne(ctx,
bson.M{
"business_id": data.BusinessID,
"$or": []bson.M{
{"delete_time": bson.M{"$exists": false}},
{"delete_time": 0},
},
},
bson.M{"$set": updates}, // 使用 updates 而不是整個 data
)
return res, err
}
func (m *customOrderModel) UpdateTimeoutOrder(ctx context.Context, req UpdateTimeoutReq) (*mongo.UpdateResult, error) {
// 構建過濾條件,選擇創建時間在指定時間之前且狀態為 0 (創建) 的項目
filter := bson.M{
"create_time": bson.M{"$lt": req.CreateTimeBefore},
"status": domain.OrderStatusCreated,
"$or": []bson.M{
{"delete_time": bson.M{"$exists": false}},
{"delete_time": 0},
},
}
// 更新內容,將狀態設置為 11並更新 update_time
updates := bson.M{
"$set": bson.M{
"status": domain.OrderStatusTimeout,
"update_time": time.Now().UTC().UnixNano(),
},
}
// 執行更新操作
res, err := m.conn.UpdateOne(ctx, filter, updates)
return res, err
}
func (m *customOrderModel) DeleteByBusinessID(ctx context.Context, id string) (*mongo.UpdateResult, error) {
filter := bson.M{
"business_id": id,
"$or": []bson.M{
{"delete_time": bson.M{"$exists": false}},
{"delete_time": 0},
},
}
updates := bson.M{
"$set": bson.M{
"delete_time": time.Now().UTC().UnixNano(),
"update_time": time.Now().UTC().UnixNano(),
},
}
// 執行更新操作
res, err := m.conn.UpdateOne(ctx, filter, updates)
return res, err
}
func (m *defaultOrderModel) FindOneBusinessID(ctx context.Context, id string) (*Order, error) {
var data Order
filter := bson.M{"delete_time": bson.M{"$in": []any{0, nil}}}
filter["business_id"] = id
err := m.conn.FindOne(ctx, &data, filter)
switch {
case err == nil:
return &data, nil
case errors.Is(err, mon.ErrNotFound):
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *customOrderModel) ListOrder(ctx context.Context, req GetOrderListReq) ([]Order, int64, error) {
// 定義查詢過濾器
filter := bson.M{
"delete_time": bson.M{"$in": []any{0, nil}},
}
// 添加查詢條件
if req.ReferenceID != "" {
filter["reference_id"] = req.ReferenceID
}
if req.ReferenceUID != "" {
filter["reference_uid"] = req.ReferenceUID
}
if req.BusinessID != "" {
filter["business_id"] = req.BusinessID
}
if req.UID != "" {
filter["order_uid"] = req.UID
}
if req.OrderType != 0 {
filter["order_type"] = req.OrderType
}
if len(req.DirectionType) > 0 {
filter["direction_type"] = bson.M{"$in": req.DirectionType}
}
if len(req.OrderStatus) > 0 {
filter["order_status"] = bson.M{"$in": req.OrderStatus}
}
// 處理時間範圍
if req.StartCreateTime != 0 {
filter["create_time"] = bson.M{"$gte": req.StartCreateTime}
}
if req.EndCreateTime != 0 {
filter["create_time"] = bson.M{"$lte": req.EndCreateTime}
}
if req.StartUpdateTime != 0 {
filter["update_time"] = bson.M{"$gte": req.StartUpdateTime}
}
if req.EndUpdateTime != 0 {
filter["update_time"] = bson.M{"$lte": req.EndUpdateTime}
}
if req.StartOrderArrivalTime != 0 {
filter["order_arrival_time"] = bson.M{"$gte": req.StartOrderArrivalTime}
}
if req.EndOrderArrivalTime != 0 {
filter["order_arrival_time"] = bson.M{"$lte": req.EndOrderArrivalTime}
}
if req.StartOrderPaymentTime != 0 {
filter["order_payment_time"] = bson.M{"$gte": req.StartOrderPaymentTime}
}
if req.EndOrderPaymentTime != 0 {
filter["order_payment_time"] = bson.M{"$lte": req.EndOrderPaymentTime}
}
if req.CryptoType != "" {
filter["crypto_type"] = req.CryptoType
}
if req.TxHash != "" {
filter["tx_hash"] = req.TxHash
}
// 計算符合條件的總數量
totalCount, err := m.conn.CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
// 設定查詢選項,包含分頁
findOptions := options.Find()
findOptions.SetSkip((req.PageIndex - 1) * req.PageSize)
findOptions.SetLimit(req.PageSize)
// 執行查詢
var orders []Order
err = m.conn.Find(ctx, &orders, filter, findOptions)
if err != nil {
return nil, 0, err
}
return orders, totalCount, nil
}

View File

@ -29,8 +29,8 @@ func newDefaultOrderModel(conn *mon.Model) *defaultOrderModel {
func (m *defaultOrderModel) Insert(ctx context.Context, data *Order) error {
if data.ID.IsZero() {
data.ID = primitive.NewObjectID()
data.CreateAt = time.Now()
data.UpdateAt = time.Now()
data.CreateTime = time.Now().UTC().UnixNano()
data.UpdateTime = time.Now().UTC().UnixNano()
}
_, err := m.conn.InsertOne(ctx, data)
@ -57,7 +57,7 @@ func (m *defaultOrderModel) FindOne(ctx context.Context, id string) (*Order, err
}
func (m *defaultOrderModel) Update(ctx context.Context, data *Order) (*mongo.UpdateResult, error) {
data.UpdateAt = time.Now()
data.UpdateTime = time.Now().UTC().UnixNano()
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
return res, err

View File

@ -1,14 +1,49 @@
package model
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Order struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
// TODO: Fill your own fields
UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"`
UpdateTime int64 `bson:"update_time"`
CreateTime int64 `bson:"create_time"`
BusinessID string `bson:"business_id"` // 訂單業務流水號
OrderType int8 `bson:"order_type"` // 訂單類型
OrderStatus int8 `bson:"order_status"` // 訂單狀態
Brand string `bson:"brand"` // 下單平台
OrderUID string `bson:"order_uid"` // 下單用戶 UID
ReferenceID string `bson:"reference_id"` // 訂單來源
Count primitive.Decimal128 `bson:"count"` // 訂單數量
OrderFee primitive.Decimal128 `bson:"order_fee"` // 訂單手續費
Amount primitive.Decimal128 `bson:"amount"` // 單價
ReferenceBrand *string `bson:"reference_brand,omitempty"` // 訂單來源平台
ReferenceUID *string `bson:"reference_uid,omitempty"` // 訂單來源用戶 UID
WalletStatus *int64 `bson:"wallet_status,omitempty"` // 交易金額狀態
ThreePartyStatus *int64 `bson:"three_party_status,omitempty"` // 三方請求狀態
DirectionType *int64 `bson:"direction_type,omitempty"` // 交易方向
CryptoType *string `bson:"crypto_type,omitempty"` // 交易幣種
ThirdPartyFee *primitive.Decimal128 `bson:"third_party_fee,omitempty"` // 第三方手續費
CryptoToUSDTRate *primitive.Decimal128 `bson:"crypto_to_usdt_rate,omitempty"` // 加密貨幣對 USDT 匯率
FiatToUSDRate *primitive.Decimal128 `bson:"fiat_to_usd_rate,omitempty"` // 法幣對 USD 匯率
FeeCryptoToUSDTRate *primitive.Decimal128 `bson:"fee_crypto_to_usdt_rate,omitempty"` // 手續費加密貨幣對 USDT 匯率
USDTToCryptoTypeRate *primitive.Decimal128 `bson:"usdt_to_crypto_type_rate,omitempty"` // USDT 對加密貨幣匯率
PaymentFiat *string `bson:"payment_fiat,omitempty"` // 支付法幣
PaymentUnitPrice *primitive.Decimal128 `bson:"payment_unit_price,omitempty"` // 加密貨幣單價
PaymentTemplateID *string `bson:"payment_template_id,omitempty"` // 支付方式配置 ID
OrderArrivalTime *int64 `bson:"order_arrival_time,omitempty"` // 訂單到帳時間
OrderPaymentTime *int64 `bson:"order_payment_time,omitempty"` // 訂單付款時間
UnpaidTimeoutSecond *int64 `bson:"unpaid_timeout_second,omitempty"` // 支付期限秒數
ChainType *string `bson:"chain_type,omitempty"` // 主網類型
TxHash *string `bson:"tx_hash,omitempty"` // 交易哈希
FromAddress *string `bson:"from_address,omitempty"` // 來源地址
ToAddress *string `bson:"to_address,omitempty"` // 目標地址
ChainFee *primitive.Decimal128 `bson:"chain_fee,omitempty"` // 鏈上交易手續費
ChainFeeCrypto *string `bson:"chain_fee_crypto,omitempty"` // 鏈上手續費使用幣別
Memo *string `bson:"memo,omitempty"` // 鏈上備註
OrderNote *string `bson:"order_note,omitempty"` // 訂單交易備註
}
func (p *Order) CollectionName() string {
return "order"
}

View File

@ -6,14 +6,14 @@ package server
import (
"context"
"app-cloudep-order-server/gen_result/pb/tweeting"
"app-cloudep-order-server/internal/logic/orderservice"
"app-cloudep-order-server/gen_result/pb/order"
orderservicelogic "app-cloudep-order-server/internal/logic/orderservice"
"app-cloudep-order-server/internal/svc"
)
type OrderServiceServer struct {
svcCtx *svc.ServiceContext
tweeting.UnimplementedOrderServiceServer
order.UnimplementedOrderServiceServer
}
func NewOrderServiceServer(svcCtx *svc.ServiceContext) *OrderServiceServer {
@ -23,49 +23,43 @@ func NewOrderServiceServer(svcCtx *svc.ServiceContext) *OrderServiceServer {
}
// CreateOrder 建立訂單
func (s *OrderServiceServer) CreateOrder(ctx context.Context, in *tweeting.CreateOrderReq) (*tweeting.CreateOrderResp, error) {
func (s *OrderServiceServer) CreateOrder(ctx context.Context, in *order.CreateOrderReq) (*order.OKResp, error) {
l := orderservicelogic.NewCreateOrderLogic(ctx, s.svcCtx)
return l.CreateOrder(in)
}
// CancelOrder 取消訂單
func (s *OrderServiceServer) CancelOrder(ctx context.Context, in *tweeting.CancelOrderReq) (*tweeting.OKResp, error) {
func (s *OrderServiceServer) CancelOrder(ctx context.Context, in *order.CancelOrderReq) (*order.OKResp, error) {
l := orderservicelogic.NewCancelOrderLogic(ctx, s.svcCtx)
return l.CancelOrder(in)
}
// ModifyOrder 修改訂單
func (s *OrderServiceServer) ModifyOrder(ctx context.Context, in *tweeting.ModifyOrderReq) (*tweeting.OKResp, error) {
l := orderservicelogic.NewModifyOrderLogic(ctx, s.svcCtx)
return l.ModifyOrder(in)
}
// ModifyOrderStatus 修改訂單狀態
func (s *OrderServiceServer) ModifyOrderStatus(ctx context.Context, in *tweeting.ModifyOrderStatusReq) (*tweeting.OKResp, error) {
func (s *OrderServiceServer) ModifyOrderStatus(ctx context.Context, in *order.ModifyOrderStatusReq) (*order.OKResp, error) {
l := orderservicelogic.NewModifyOrderStatusLogic(ctx, s.svcCtx)
return l.ModifyOrderStatus(in)
}
// DeleteOrder 刪除訂單(軟刪除)
func (s *OrderServiceServer) DeleteOrder(ctx context.Context, in *tweeting.DeleteOrderReq) (*tweeting.OKResp, error) {
func (s *OrderServiceServer) DeleteOrder(ctx context.Context, in *order.DeleteOrderReq) (*order.OKResp, error) {
l := orderservicelogic.NewDeleteOrderLogic(ctx, s.svcCtx)
return l.DeleteOrder(in)
}
// GetOrder 取得訂單詳情
func (s *OrderServiceServer) GetOrder(ctx context.Context, in *tweeting.GetOrderReq) (*tweeting.GetOrderResp, error) {
func (s *OrderServiceServer) GetOrder(ctx context.Context, in *order.GetOrderReq) (*order.GetOrderResp, error) {
l := orderservicelogic.NewGetOrderLogic(ctx, s.svcCtx)
return l.GetOrder(in)
}
// ListOrder 取得訂單列表
func (s *OrderServiceServer) ListOrder(ctx context.Context, in *tweeting.ListOrderReq) (*tweeting.ListOrderResp, error) {
func (s *OrderServiceServer) ListOrder(ctx context.Context, in *order.ListOrderReq) (*order.ListOrderResp, error) {
l := orderservicelogic.NewListOrderLogic(ctx, s.svcCtx)
return l.ListOrder(in)
}
// OrderStatusTimeout 訂單超時任務/cron/order-status/timeout
func (s *OrderServiceServer) OrderStatusTimeout(ctx context.Context, in *tweeting.OrderStatusTimeoutReq) (*tweeting.OKResp, error) {
func (s *OrderServiceServer) OrderStatusTimeout(ctx context.Context, in *order.OrderStatusTimeoutReq) (*order.OKResp, error) {
l := orderservicelogic.NewOrderStatusTimeoutLogic(ctx, s.svcCtx)
return l.OrderStatusTimeout(in)
}

View File

@ -0,0 +1,23 @@
package svc
import (
"app-cloudep-order-server/internal/config"
model "app-cloudep-order-server/internal/model/mongo"
"fmt"
)
func mustMongoConnectURL(c config.Config) string {
return fmt.Sprintf("%s://%s:%s",
c.Mongo.Schema,
c.Mongo.Host,
c.Mongo.Port,
)
}
// TODO 思考快取做在那邊
func MustOrderModel(c config.Config) model.OrderModel {
orderCollection := model.Order{}
return model.NewOrderModel(mustMongoConnectURL(c), c.Mongo.Database, orderCollection.CollectionName())
}

View File

@ -0,0 +1,55 @@
package svc
import (
vi "code.30cm.net/digimon/library-go/validator"
"github.com/go-playground/validator/v10"
"github.com/shopspring/decimal"
)
// WithDecimalGt 是否大於等於
func WithDecimalGt() vi.Option {
return vi.Option{
ValidatorName: "decimalGt",
ValidatorFunc: func(fl validator.FieldLevel) bool {
if val, ok := fl.Field().Interface().(string); ok {
value, err := decimal.NewFromString(val)
if err != nil {
return false
}
conditionValue, err := decimal.NewFromString(fl.Param())
if err != nil {
return false
}
return value.GreaterThan(conditionValue)
}
return true
},
}
}
// WithDecimalGte 是否大於等於
func WithDecimalGte() vi.Option {
return vi.Option{
ValidatorName: "decimalGte",
ValidatorFunc: func(fl validator.FieldLevel) bool {
if val, ok := fl.Field().Interface().(string); ok {
value, err := decimal.NewFromString(val)
if err != nil {
return false
}
conditionValue, err := decimal.NewFromString(fl.Param())
if err != nil {
return false
}
return value.GreaterThanOrEqual(conditionValue)
}
return true
},
}
}

View File

@ -1,13 +1,27 @@
package svc
import "app-cloudep-order-server/internal/config"
import (
"app-cloudep-order-server/internal/config"
model "app-cloudep-order-server/internal/model/mongo"
ers "code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/library-go/errs/code"
vi "code.30cm.net/digimon/library-go/validator"
)
type ServiceContext struct {
Config config.Config
Validate vi.Validate
OrderModel model.OrderModel
}
func NewServiceContext(c config.Config) *ServiceContext {
ers.Scope = code.CloudEPOrder
return &ServiceContext{
Config: c,
Validate: vi.MustValidator(WithDecimalGt(), WithDecimalGte()),
OrderModel: MustOrderModel(c),
}
}

View File

@ -1,10 +1,10 @@
package main
import (
"app-cloudep-order-server/gen_result/pb/order"
"flag"
"fmt"
"app-cloudep-order-server/gen_result/pb/tweeting"
"app-cloudep-order-server/internal/config"
orderserviceServer "app-cloudep-order-server/internal/server/orderservice"
"app-cloudep-order-server/internal/svc"
@ -26,7 +26,7 @@ func main() {
ctx := svc.NewServiceContext(c)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
tweeting.RegisterOrderServiceServer(grpcServer, orderserviceServer.NewOrderServiceServer(ctx))
order.RegisterOrderServiceServer(grpcServer, orderserviceServer.NewOrderServiceServer(ctx))
if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
@ -34,6 +34,7 @@ func main() {
})
defer s.Stop()
//nolint:forbidigo
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}