feature/order_base #1
			
				
			
		
		
		
	
							
								
								
									
										2
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										2
									
								
								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 | ||||
|  |  | |||
|  | @ -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, | ||||
| }) | ||||
|  | @ -1,6 +1,6 @@ | |||
| syntax = "proto3"; | ||||
| package tweeting; | ||||
| option go_package = "./tweeting"; | ||||
| package order; | ||||
| option go_package = "./order"; | ||||
| 
 | ||||
| // ========== 基本回應 =========== | ||||
| message OKResp {} | ||||
|  | @ -9,46 +9,157 @@ message OKResp {} | |||
| message NoneReq {} | ||||
| 
 | ||||
| // 分頁信息 | ||||
| message Pager | ||||
| { | ||||
| message Pager { | ||||
|   int64 total = 1;  // 總數量 | ||||
|   int64 size = 2;   // 每頁數量 | ||||
|   int64 index = 3;  // 當前頁碼 | ||||
| } | ||||
| 
 | ||||
| message CreateOrderReq{} | ||||
| message CreateOrderResp{} | ||||
| message CreateOrderReq { | ||||
|   string business_id = 1;   // 訂單業務流水號 | ||||
|   int32 order_type = 2;     // 訂單類型 | ||||
|   int32 order_status = 3;   // 訂單狀態 | ||||
|   string brand = 4;         // 下單平台 | ||||
|   string order_uid = 5;     // 下單用戶 UID | ||||
|   string reference_id = 6;  // 訂單來源 | ||||
|   string count = 7;         // 訂單數量 (decimal to string) | ||||
|   string order_fee = 8;     // 訂單手續費 (decimal to string) | ||||
|   string amount = 9;        // 單價 (decimal to string) | ||||
| 
 | ||||
| message CancelOrderReq{} | ||||
| message ModifyOrderReq{} | ||||
| message ModifyOrderStatusReq{} | ||||
| message DeleteOrderReq{} | ||||
|   // 以下是可選字段(擴充用) | ||||
|   optional string reference_brand = 10;           // 訂單來源平台 | ||||
|   optional string reference_uid = 11;             // 訂單來源用戶 UID | ||||
|   optional int64 wallet_status = 12;              // 交易金額狀態 | ||||
|   optional int64 three_party_status = 13;         // 三方請求狀態 | ||||
|   optional int64 direction_type = 14;             // 交易方向 | ||||
|   optional string crypto_type = 15;               // 交易幣種 | ||||
|   optional string third_party_fee = 16;           // 第三方手續費 (decimal to string) | ||||
|   optional string crypto_to_usdt_rate = 17;       // 交易幣種對 USDT 匯率 (decimal to string) | ||||
|   optional string fiat_to_usd_rate = 18;          // 法幣對 USD 匯率 (decimal to string) | ||||
|   optional string fee_crypto_to_usdt_rate = 19;   // 手續費幣種對 USDT 匯率 (decimal to string) | ||||
|   optional string usdt_to_crypto_type_rate = 20;  // USDT 對交易幣種匯率 (decimal to string) | ||||
|   optional string payment_fiat = 21;              // 支付法幣 | ||||
|   optional string payment_unit_price = 22;        // crypto 單價 (decimal to string) | ||||
|   optional string payment_template_id = 23;       // 支付方式配置 ID | ||||
|   optional int64 order_arrival_time = 24;         // 訂單到帳時間 | ||||
|   optional int64 order_payment_time = 25;         // 訂單付款時間 | ||||
|   optional int64 unpaid_timeout_second = 26;      // 支付期限秒數 | ||||
|   optional string chain_type = 27;                // 主網類型 | ||||
|   optional string tx_hash = 28;                   // 交易哈希 | ||||
|   optional string from_address = 29;              // 來源地址 | ||||
|   optional string to_address = 30;                // 目標地址 | ||||
|   optional string chain_fee = 31;                 // 鏈上交易手續費 (decimal to string) | ||||
|   optional string chain_fee_crypto = 32;          // 鏈上手續費使用幣別 | ||||
|   optional string memo = 33;                      // 鏈上備註 | ||||
|   optional string order_note = 34;                // 訂單交易備註 | ||||
| } | ||||
| 
 | ||||
| message GetOrderReq{} | ||||
| message GetOrderResp{} | ||||
| message CancelOrderReq { | ||||
|   string business_id = 1; | ||||
|   int64 status = 2; | ||||
| } | ||||
| 
 | ||||
| message ListOrderReq{} | ||||
| message ListOrderResp{} | ||||
| message ModifyOrderStatusReq { | ||||
|   string business_id = 1; | ||||
|   int64 status = 2; | ||||
| } | ||||
| 
 | ||||
| message DeleteOrderReq { | ||||
|   string business_id = 1; | ||||
| } | ||||
| 
 | ||||
| message OrderStatusTimeoutReq{} | ||||
| message OrderStatusTimeoutReq {} | ||||
| 
 | ||||
| message GetOrderReq { | ||||
|   string business_id = 1;  // 訂單業務流水號 | ||||
| } | ||||
| message GetOrderResp { | ||||
|   string business_id = 1;   // 訂單業務流水號 | ||||
|   int32 order_type = 2;     // 訂單類型 | ||||
|   int32 order_status = 3;   // 訂單狀態 | ||||
|   string brand = 4;         // 下單平台 | ||||
|   string order_uid = 5;     // 下單用戶 UID | ||||
|   string reference_id = 6;  // 訂單來源 | ||||
|   string count = 7;         // 訂單數量 (decimal to string) | ||||
|   string order_fee = 8;     // 訂單手續費 (decimal to string) | ||||
|   string amount = 9;        // 單價 (decimal to string) | ||||
| 
 | ||||
|   // 以下是可選字段(擴充用) | ||||
|   optional string reference_brand = 10;           // 訂單來源平台 | ||||
|   optional string reference_uid = 11;             // 訂單來源用戶 UID | ||||
|   optional int64 wallet_status = 12;              // 交易金額狀態 | ||||
|   optional int64 three_party_status = 13;         // 三方請求狀態 | ||||
|   optional int64 direction_type = 14;             // 交易方向 | ||||
|   optional string crypto_type = 15;               // 交易幣種 | ||||
|   optional string third_party_fee = 16;           // 第三方手續費 (decimal to string) | ||||
|   optional string crypto_to_usdt_rate = 17;       // 交易幣種對 USDT 匯率 (decimal to string) | ||||
|   optional string fiat_to_usd_rate = 18;          // 法幣對 USD 匯率 (decimal to string) | ||||
|   optional string fee_crypto_to_usdt_rate = 19;   // 手續費幣種對 USDT 匯率 (decimal to string) | ||||
|   optional string usdt_to_crypto_type_rate = 20;  // USDT 對交易幣種匯率 (decimal to string) | ||||
|   optional string payment_fiat = 21;              // 支付法幣 | ||||
|   optional string payment_unit_price = 22;        // crypto 單價 (decimal to string) | ||||
|   optional string payment_template_id = 23;       // 支付方式配置 ID | ||||
|   optional int64 order_arrival_time = 24;         // 訂單到帳時間 | ||||
|   optional int64 order_payment_time = 25;         // 訂單付款時間 | ||||
|   optional int64 unpaid_timeout_second = 26;      // 支付期限秒數 | ||||
|   optional string chain_type = 27;                // 主網類型 | ||||
|   optional string tx_hash = 28;                   // 交易哈希 | ||||
|   optional string from_address = 29;              // 來源地址 | ||||
|   optional string to_address = 30;                // 目標地址 | ||||
|   optional string chain_fee = 31;                 // 鏈上交易手續費 (decimal to string) | ||||
|   optional string chain_fee_crypto = 32;          // 鏈上手續費使用幣別 | ||||
|   optional string memo = 33;                      // 鏈上備註 | ||||
|   optional string order_note = 34;                // 訂單交易備註 | ||||
|   int64 create_time = 35;                      // 建立時間 | ||||
|   int64 update_time = 36;                     // 更新時間 | ||||
| } | ||||
| 
 | ||||
| message ModifyOrderReq {} | ||||
| 
 | ||||
| message ListOrderReq { | ||||
|   int64 page_index = 1; // required | ||||
|   int64 page_size = 2;  // required | ||||
| 
 | ||||
|   string reference_id = 3; | ||||
|   string reference_uid = 4; | ||||
|   string business_id = 5; | ||||
|   string uid = 6; | ||||
|   int32 order_type = 7; | ||||
|   repeated int32 direction_type = 8; | ||||
|   repeated int32 order_status = 9; | ||||
| 
 | ||||
|   int64 start_create_time = 10; | ||||
|   int64 end_create_time = 11; | ||||
|   int64 start_update_time = 12; | ||||
|   int64 end_update_time = 13; | ||||
|   int64 start_order_arrival_time = 14; | ||||
|   int64 end_order_arrival_time = 15; | ||||
|   int64 start_order_payment_time = 16; | ||||
|   int64 end_order_payment_time = 17; | ||||
| 
 | ||||
|   string crypto_type = 18; | ||||
|   string tx_hash = 19; | ||||
| } | ||||
| 
 | ||||
| message ListOrderResp { | ||||
|   repeated GetOrderResp data = 1; // 訂單列表 | ||||
|   Pager page = 2; | ||||
| } | ||||
| 
 | ||||
| // OrderService 訂單服務(業務邏輯在外面組合) | ||||
| service OrderService{ | ||||
| service OrderService { | ||||
|   // CreateOrder 建立訂單 | ||||
|   rpc CreateOrder(CreateOrderReq)returns(CreateOrderResp); | ||||
|   rpc CreateOrder(CreateOrderReq) returns (OKResp); | ||||
|   // CancelOrder 取消訂單 | ||||
|   rpc CancelOrder(CancelOrderReq)returns(OKResp); | ||||
|   // ModifyOrder 修改訂單 | ||||
|   rpc ModifyOrder(ModifyOrderReq)returns(OKResp); | ||||
|   rpc CancelOrder(CancelOrderReq) returns (OKResp); | ||||
|   // ModifyOrderStatus 修改訂單狀態 | ||||
|   rpc ModifyOrderStatus(ModifyOrderStatusReq)returns(OKResp); | ||||
|   rpc ModifyOrderStatus(ModifyOrderStatusReq) returns (OKResp); | ||||
|   // DeleteOrder 刪除訂單(軟刪除) | ||||
|   rpc DeleteOrder(DeleteOrderReq)returns(OKResp); | ||||
|   rpc DeleteOrder(DeleteOrderReq) returns (OKResp); | ||||
|   // GetOrder 取得訂單詳情 | ||||
|   rpc GetOrder(GetOrderReq)returns(GetOrderResp); | ||||
|   rpc GetOrder(GetOrderReq) returns (GetOrderResp); | ||||
|   // ListOrder 取得訂單列表 | ||||
|   rpc ListOrder(ListOrderReq)returns(ListOrderResp); | ||||
|   rpc ListOrder(ListOrderReq) returns (ListOrderResp); | ||||
|   // OrderStatusTimeout 訂單超時任務/cron/order-status/timeout | ||||
|   rpc OrderStatusTimeout(OrderStatusTimeoutReq)returns(OKResp); | ||||
|   rpc OrderStatusTimeout(OrderStatusTimeoutReq) returns (OKResp); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										20
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										20
									
								
								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 | ||||
|  |  | |||
|  | @ -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 | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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, " ")) | ||||
| } | ||||
|  | @ -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 // 交易超時
 | ||||
| ) | ||||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -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, | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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)
 | ||||
| // 			}
 | ||||
| // 		})
 | ||||
| // 	}
 | ||||
| // }
 | ||||
|  | @ -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 | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -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) | ||||
| } | ||||
|  | @ -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) | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -1,14 +1,49 @@ | |||
| package model | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"go.mongodb.org/mongo-driver/bson/primitive" | ||||
| ) | ||||
| 
 | ||||
| type Order struct { | ||||
| 	ID                   primitive.ObjectID    `bson:"_id,omitempty" json:"id,omitempty"` | ||||
| 	// TODO: Fill your own fields
 | ||||
| 	UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"` | ||||
| 	CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"` | ||||
| 	UpdateTime           int64                 `bson:"update_time"` | ||||
| 	CreateTime           int64                 `bson:"create_time"` | ||||
| 	BusinessID           string                `bson:"business_id"`                        // 訂單業務流水號
 | ||||
| 	OrderType            int8                  `bson:"order_type"`                         // 訂單類型
 | ||||
| 	OrderStatus          int8                  `bson:"order_status"`                       // 訂單狀態
 | ||||
| 	Brand                string                `bson:"brand"`                              // 下單平台
 | ||||
| 	OrderUID             string                `bson:"order_uid"`                          // 下單用戶 UID
 | ||||
| 	ReferenceID          string                `bson:"reference_id"`                       // 訂單來源
 | ||||
| 	Count                primitive.Decimal128  `bson:"count"`                              // 訂單數量
 | ||||
| 	OrderFee             primitive.Decimal128  `bson:"order_fee"`                          // 訂單手續費
 | ||||
| 	Amount               primitive.Decimal128  `bson:"amount"`                             // 單價
 | ||||
| 	ReferenceBrand       *string               `bson:"reference_brand,omitempty"`          // 訂單來源平台
 | ||||
| 	ReferenceUID         *string               `bson:"reference_uid,omitempty"`            // 訂單來源用戶 UID
 | ||||
| 	WalletStatus         *int64                `bson:"wallet_status,omitempty"`            // 交易金額狀態
 | ||||
| 	ThreePartyStatus     *int64                `bson:"three_party_status,omitempty"`       // 三方請求狀態
 | ||||
| 	DirectionType        *int64                `bson:"direction_type,omitempty"`           // 交易方向
 | ||||
| 	CryptoType           *string               `bson:"crypto_type,omitempty"`              // 交易幣種
 | ||||
| 	ThirdPartyFee        *primitive.Decimal128 `bson:"third_party_fee,omitempty"`          // 第三方手續費
 | ||||
| 	CryptoToUSDTRate     *primitive.Decimal128 `bson:"crypto_to_usdt_rate,omitempty"`      // 加密貨幣對 USDT 匯率
 | ||||
| 	FiatToUSDRate        *primitive.Decimal128 `bson:"fiat_to_usd_rate,omitempty"`         // 法幣對 USD 匯率
 | ||||
| 	FeeCryptoToUSDTRate  *primitive.Decimal128 `bson:"fee_crypto_to_usdt_rate,omitempty"`  // 手續費加密貨幣對 USDT 匯率
 | ||||
| 	USDTToCryptoTypeRate *primitive.Decimal128 `bson:"usdt_to_crypto_type_rate,omitempty"` // USDT 對加密貨幣匯率
 | ||||
| 	PaymentFiat          *string               `bson:"payment_fiat,omitempty"`             // 支付法幣
 | ||||
| 	PaymentUnitPrice     *primitive.Decimal128 `bson:"payment_unit_price,omitempty"`       // 加密貨幣單價
 | ||||
| 	PaymentTemplateID    *string               `bson:"payment_template_id,omitempty"`      // 支付方式配置 ID
 | ||||
| 	OrderArrivalTime     *int64                `bson:"order_arrival_time,omitempty"`       // 訂單到帳時間
 | ||||
| 	OrderPaymentTime     *int64                `bson:"order_payment_time,omitempty"`       // 訂單付款時間
 | ||||
| 	UnpaidTimeoutSecond  *int64                `bson:"unpaid_timeout_second,omitempty"`    // 支付期限秒數
 | ||||
| 	ChainType            *string               `bson:"chain_type,omitempty"`               // 主網類型
 | ||||
| 	TxHash               *string               `bson:"tx_hash,omitempty"`                  // 交易哈希
 | ||||
| 	FromAddress          *string               `bson:"from_address,omitempty"`             // 來源地址
 | ||||
| 	ToAddress            *string               `bson:"to_address,omitempty"`               // 目標地址
 | ||||
| 	ChainFee             *primitive.Decimal128 `bson:"chain_fee,omitempty"`                // 鏈上交易手續費
 | ||||
| 	ChainFeeCrypto       *string               `bson:"chain_fee_crypto,omitempty"`         // 鏈上手續費使用幣別
 | ||||
| 	Memo                 *string               `bson:"memo,omitempty"`                     // 鏈上備註
 | ||||
| 	OrderNote            *string               `bson:"order_note,omitempty"`               // 訂單交易備註
 | ||||
| } | ||||
| 
 | ||||
| func (p *Order) CollectionName() string { | ||||
| 	return "order" | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| } | ||||
|  |  | |||
|  | @ -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()) | ||||
| } | ||||
|  | @ -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 | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | @ -1,13 +1,27 @@ | |||
| package svc | ||||
| 
 | ||||
| import "app-cloudep-order-server/internal/config" | ||||
| import ( | ||||
| 	"app-cloudep-order-server/internal/config" | ||||
| 	model "app-cloudep-order-server/internal/model/mongo" | ||||
| 
 | ||||
| 	ers "code.30cm.net/digimon/library-go/errs" | ||||
| 	"code.30cm.net/digimon/library-go/errs/code" | ||||
| 	vi "code.30cm.net/digimon/library-go/validator" | ||||
| ) | ||||
| 
 | ||||
| type ServiceContext struct { | ||||
| 	Config   config.Config | ||||
| 	Validate vi.Validate | ||||
| 
 | ||||
| 	OrderModel model.OrderModel | ||||
| } | ||||
| 
 | ||||
| func NewServiceContext(c config.Config) *ServiceContext { | ||||
| 	ers.Scope = code.CloudEPOrder | ||||
| 
 | ||||
| 	return &ServiceContext{ | ||||
| 		Config:     c, | ||||
| 		Validate:   vi.MustValidator(WithDecimalGt(), WithDecimalGte()), | ||||
| 		OrderModel: MustOrderModel(c), | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										5
									
								
								order.go
								
								
								
								
							
							
						
						
									
										5
									
								
								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() | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue