From 8b76f7cd1e784f18e3c7c1a20c4dfb725aa98e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Wed, 23 Oct 2024 09:50:11 +0000 Subject: [PATCH] feature/order_base (#1) Co-authored-by: daniel.w Reviewed-on: https://code.30cm.net/digimon/app-cloudep-order-server/pulls/1 --- Makefile | 2 +- .../mongodb/20241006000001_order.up.mongo | 13 + generate/protobuf/order.proto | 167 +++++-- go.mod | 20 + internal/config/config.go | 9 + internal/domain/errors.go | 45 ++ internal/domain/order.go | 22 + .../logic/orderservice/cancel_order_logic.go | 49 +- .../orderservice/cancel_order_logic_test.go | 110 +++++ .../logic/orderservice/create_order_logic.go | 235 ++++++++- .../orderservice/create_order_logic_test.go | 371 +++++++++++++++ internal/logic/orderservice/decimal_tools.go | 99 ++++ .../logic/orderservice/delete_order_logic.go | 30 +- .../orderservice/delete_order_logic_test.go | 97 ++++ .../logic/orderservice/get_order_logic.go | 76 ++- .../orderservice/get_order_logic_test.go | 131 ++++++ .../logic/orderservice/list_order_logic.go | 128 ++++- .../orderservice/list_order_logic_test.go | 444 ++++++++++++++++++ .../logic/orderservice/modify_order_logic.go | 31 -- .../orderservice/modify_order_status_logic.go | 50 +- .../modify_order_status_logic_test.go | 102 ++++ .../order_status_timeout_logic.go | 28 +- .../order_status_timeout_logic_test.go | 74 +++ internal/mock/lib/validate.go | 73 +++ internal/mock/model/order_model.go | 90 +++- internal/mock/model/order_model_gen.go | 14 +- internal/model/mongo/order_model.go | 217 ++++++++- internal/model/mongo/order_model_gen.go | 6 +- internal/model/mongo/order_types.go | 47 +- .../orderservice/order_service_server.go | 26 +- internal/svc/init_mongo.go | 23 + internal/svc/init_validate.go | 55 +++ internal/svc/service_context.go | 20 +- order.go | 5 +- 34 files changed, 2764 insertions(+), 145 deletions(-) create mode 100644 generate/database/mongodb/20241006000001_order.up.mongo create mode 100644 internal/domain/errors.go create mode 100644 internal/domain/order.go create mode 100644 internal/logic/orderservice/cancel_order_logic_test.go create mode 100644 internal/logic/orderservice/create_order_logic_test.go create mode 100644 internal/logic/orderservice/decimal_tools.go create mode 100644 internal/logic/orderservice/delete_order_logic_test.go create mode 100644 internal/logic/orderservice/get_order_logic_test.go create mode 100644 internal/logic/orderservice/list_order_logic_test.go delete mode 100644 internal/logic/orderservice/modify_order_logic.go create mode 100644 internal/logic/orderservice/modify_order_status_logic_test.go create mode 100644 internal/logic/orderservice/order_status_timeout_logic_test.go create mode 100644 internal/mock/lib/validate.go create mode 100644 internal/svc/init_mongo.go create mode 100644 internal/svc/init_validate.go diff --git a/Makefile b/Makefile index fbbfdf5..80e5221 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/generate/database/mongodb/20241006000001_order.up.mongo b/generate/database/mongodb/20241006000001_order.up.mongo new file mode 100644 index 0000000..e8510aa --- /dev/null +++ b/generate/database/mongodb/20241006000001_order.up.mongo @@ -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, +}) \ No newline at end of file diff --git a/generate/protobuf/order.proto b/generate/protobuf/order.proto index 7372d20..f70a722 100644 --- a/generate/protobuf/order.proto +++ b/generate/protobuf/order.proto @@ -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 -{ - int64 total = 1; // 總數量 - int64 size = 2; // 每頁數量 - int64 index = 3; // 當前頁碼 +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); } diff --git a/go.mod b/go.mod index 0a5d0f1..298332d 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/internal/config/config.go b/internal/config/config.go index c1f85b9..85679b2 100755 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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 + } } diff --git a/internal/domain/errors.go b/internal/domain/errors.go new file mode 100644 index 0000000..050f912 --- /dev/null +++ b/internal/domain/errors.go @@ -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, " ")) +} diff --git a/internal/domain/order.go b/internal/domain/order.go new file mode 100644 index 0000000..18233cb --- /dev/null +++ b/internal/domain/order.go @@ -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 // 交易超時 +) diff --git a/internal/logic/orderservice/cancel_order_logic.go b/internal/logic/orderservice/cancel_order_logic.go index 053b579..d2c8e56 100644 --- a/internal/logic/orderservice/cancel_order_logic.go +++ b/internal/logic/orderservice/cancel_order_logic.go @@ -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 } diff --git a/internal/logic/orderservice/cancel_order_logic_test.go b/internal/logic/orderservice/cancel_order_logic_test.go new file mode 100644 index 0000000..ce457bd --- /dev/null +++ b/internal/logic/orderservice/cancel_order_logic_test.go @@ -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) + } + }) + } +} diff --git a/internal/logic/orderservice/create_order_logic.go b/internal/logic/orderservice/create_order_logic.go index 9ae8e56..6da8404 100644 --- a/internal/logic/orderservice/create_order_logic.go +++ b/internal/logic/orderservice/create_order_logic.go @@ -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 } diff --git a/internal/logic/orderservice/create_order_logic_test.go b/internal/logic/orderservice/create_order_logic_test.go new file mode 100644 index 0000000..d2876a8 --- /dev/null +++ b/internal/logic/orderservice/create_order_logic_test.go @@ -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 +} diff --git a/internal/logic/orderservice/decimal_tools.go b/internal/logic/orderservice/decimal_tools.go new file mode 100644 index 0000000..80bb3a6 --- /dev/null +++ b/internal/logic/orderservice/decimal_tools.go @@ -0,0 +1,99 @@ +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 optionalDecimalToString(d *decimal.Decimal) *string { + if d != nil { + s := d.String() + + return &s + } + + 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 +} diff --git a/internal/logic/orderservice/delete_order_logic.go b/internal/logic/orderservice/delete_order_logic.go index f66b6cb..a678a02 100644 --- a/internal/logic/orderservice/delete_order_logic.go +++ b/internal/logic/orderservice/delete_order_logic.go @@ -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 } diff --git a/internal/logic/orderservice/delete_order_logic_test.go b/internal/logic/orderservice/delete_order_logic_test.go new file mode 100644 index 0000000..bbc7c9d --- /dev/null +++ b/internal/logic/orderservice/delete_order_logic_test.go @@ -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) + } + }) + } +} diff --git a/internal/logic/orderservice/get_order_logic.go b/internal/logic/orderservice/get_order_logic.go index 9260f0f..1d3030c 100644 --- a/internal/logic/orderservice/get_order_logic.go +++ b/internal/logic/orderservice/get_order_logic.go @@ -1,10 +1,13 @@ 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" + ers "code.30cm.net/digimon/library-go/errs" + "context" + "errors" + "github.com/zeromicro/go-zero/core/stores/mon" "github.com/zeromicro/go-zero/core/logx" ) @@ -23,9 +26,66 @@ 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.As(err, &mon.ErrNotFound) { + 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 } diff --git a/internal/logic/orderservice/get_order_logic_test.go b/internal/logic/orderservice/get_order_logic_test.go new file mode 100644 index 0000000..ddc83fa --- /dev/null +++ b/internal/logic/orderservice/get_order_logic_test.go @@ -0,0 +1,131 @@ +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" + "go.mongodb.org/mongo-driver/bson/primitive" + "testing" + + "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) + } + }) + } +} diff --git a/internal/logic/orderservice/list_order_logic.go b/internal/logic/orderservice/list_order_logic.go index 67e4cdb..c020dc7 100644 --- a/internal/logic/orderservice/list_order_logic.go +++ b/internal/logic/orderservice/list_order_logic.go @@ -1,10 +1,12 @@ 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 +25,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, + } } diff --git a/internal/logic/orderservice/list_order_logic_test.go b/internal/logic/orderservice/list_order_logic_test.go new file mode 100644 index 0000000..f864ca4 --- /dev/null +++ b/internal/logic/orderservice/list_order_logic_test.go @@ -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) +// } +// }) +// } +// } diff --git a/internal/logic/orderservice/modify_order_logic.go b/internal/logic/orderservice/modify_order_logic.go deleted file mode 100644 index a838866..0000000 --- a/internal/logic/orderservice/modify_order_logic.go +++ /dev/null @@ -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 -} diff --git a/internal/logic/orderservice/modify_order_status_logic.go b/internal/logic/orderservice/modify_order_status_logic.go index 4a2581b..5254a21 100644 --- a/internal/logic/orderservice/modify_order_status_logic.go +++ b/internal/logic/orderservice/modify_order_status_logic.go @@ -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" + ers "code.30cm.net/digimon/library-go/errs" "context" - "app-cloudep-order-server/gen_result/pb/tweeting" "app-cloudep-order-server/internal/svc" "github.com/zeromicro/go-zero/core/logx" @@ -23,9 +26,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 } diff --git a/internal/logic/orderservice/modify_order_status_logic_test.go b/internal/logic/orderservice/modify_order_status_logic_test.go new file mode 100644 index 0000000..069cad1 --- /dev/null +++ b/internal/logic/orderservice/modify_order_status_logic_test.go @@ -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) + } + }) + } +} diff --git a/internal/logic/orderservice/order_status_timeout_logic.go b/internal/logic/orderservice/order_status_timeout_logic.go index 67ce9c6..62d7fee 100644 --- a/internal/logic/orderservice/order_status_timeout_logic.go +++ b/internal/logic/orderservice/order_status_timeout_logic.go @@ -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 } diff --git a/internal/logic/orderservice/order_status_timeout_logic_test.go b/internal/logic/orderservice/order_status_timeout_logic_test.go new file mode 100644 index 0000000..a1a94da --- /dev/null +++ b/internal/logic/orderservice/order_status_timeout_logic_test.go @@ -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) + } + }) + } +} diff --git a/internal/mock/lib/validate.go b/internal/mock/lib/validate.go new file mode 100644 index 0000000..dd852f8 --- /dev/null +++ b/internal/mock/lib/validate.go @@ -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) +} diff --git a/internal/mock/model/order_model.go b/internal/mock/model/order_model.go index fb24d12..914b226 100644 --- a/internal/mock/model/order_model.go +++ b/internal/mock/model/order_model.go @@ -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) +} diff --git a/internal/mock/model/order_model_gen.go b/internal/mock/model/order_model_gen.go index f3fb4da..5052926 100644 --- a/internal/mock/model/order_model_gen.go +++ b/internal/mock/model/order_model_gen.go @@ -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 } diff --git a/internal/model/mongo/order_model.go b/internal/model/mongo/order_model.go index 73a14ba..9d760ac 100644 --- a/internal/model/mongo/order_model.go +++ b/internal/model/mongo/order_model.go @@ -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 +} diff --git a/internal/model/mongo/order_model_gen.go b/internal/model/mongo/order_model_gen.go index 0d9f599..ecd3914 100644 --- a/internal/model/mongo/order_model_gen.go +++ b/internal/model/mongo/order_model_gen.go @@ -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 diff --git a/internal/model/mongo/order_types.go b/internal/model/mongo/order_types.go index 30198e9..0f5af31 100644 --- a/internal/model/mongo/order_types.go +++ b/internal/model/mongo/order_types.go @@ -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"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,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" } diff --git a/internal/server/orderservice/order_service_server.go b/internal/server/orderservice/order_service_server.go index 7014e9d..971e351 100644 --- a/internal/server/orderservice/order_service_server.go +++ b/internal/server/orderservice/order_service_server.go @@ -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) } diff --git a/internal/svc/init_mongo.go b/internal/svc/init_mongo.go new file mode 100644 index 0000000..ef417ad --- /dev/null +++ b/internal/svc/init_mongo.go @@ -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()) +} diff --git a/internal/svc/init_validate.go b/internal/svc/init_validate.go new file mode 100644 index 0000000..f5104d5 --- /dev/null +++ b/internal/svc/init_validate.go @@ -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 + }, + } +} diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go index 53b091b..ee1c4d1 100644 --- a/internal/svc/service_context.go +++ b/internal/svc/service_context.go @@ -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 + Config config.Config + Validate vi.Validate + + OrderModel model.OrderModel } func NewServiceContext(c config.Config) *ServiceContext { + ers.Scope = code.CloudEPOrder + return &ServiceContext{ - Config: c, + Config: c, + Validate: vi.MustValidator(WithDecimalGt(), WithDecimalGte()), + OrderModel: MustOrderModel(c), } } diff --git a/order.go b/order.go index 0876820..815f0cd 100644 --- a/order.go +++ b/order.go @@ -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() }