demo
This commit is contained in:
65
internal/service/errors.go
Normal file
65
internal/service/errors.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package service
|
||||
|
||||
import "errors"
|
||||
|
||||
type Error struct {
|
||||
Status int
|
||||
Message string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func newError(status int, message string, err error) error {
|
||||
return &Error{
|
||||
Status: status,
|
||||
Message: message,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func badRequest(message string) error {
|
||||
return newError(400, message, nil)
|
||||
}
|
||||
|
||||
func NewValidationError(message string) error {
|
||||
return badRequest(message)
|
||||
}
|
||||
|
||||
func notFound(message string) error {
|
||||
return newError(404, message, nil)
|
||||
}
|
||||
|
||||
func internalError(message string, err error) error {
|
||||
return newError(500, message, err)
|
||||
}
|
||||
|
||||
func StatusCode(err error) int {
|
||||
if serviceErr, ok := errors.AsType[*Error](err); ok {
|
||||
return serviceErr.Status
|
||||
}
|
||||
|
||||
return 500
|
||||
}
|
||||
|
||||
func Message(err error) string {
|
||||
if serviceErr, ok := errors.AsType[*Error](err); ok {
|
||||
return serviceErr.Message
|
||||
}
|
||||
|
||||
return "internal server error"
|
||||
}
|
||||
1020
internal/service/service.go
Normal file
1020
internal/service/service.go
Normal file
File diff suppressed because it is too large
Load Diff
84
internal/service/service_test.go
Normal file
84
internal/service/service_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.starryskymeow.cn/B309/datamarket/internal/repository"
|
||||
)
|
||||
|
||||
func TestNormalizePage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
limit int32
|
||||
offset int32
|
||||
wantLimit int32
|
||||
wantOffset int32
|
||||
}{
|
||||
{name: "default values", limit: 0, offset: -1, wantLimit: defaultLimit, wantOffset: 0},
|
||||
{name: "cap max limit", limit: 999, offset: 3, wantLimit: maxLimit, wantOffset: 3},
|
||||
{name: "keep valid page", limit: 15, offset: 8, wantLimit: 15, wantOffset: 8},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := normalizePage(tt.limit, tt.offset)
|
||||
if got.Limit != tt.wantLimit || got.Offset != tt.wantOffset {
|
||||
t.Fatalf("normalizePage() = %+v, want limit=%d offset=%d", got, tt.wantLimit, tt.wantOffset)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAssetDerivedFields(t *testing.T) {
|
||||
input := AssetCreateInput{
|
||||
AssetName: "机械臂抓取演示数据集A",
|
||||
AssetType: "视频+轨迹",
|
||||
Domain: "机器人",
|
||||
DataDescription: "demo",
|
||||
DataScale: "1200条轨迹",
|
||||
CollectionMethod: "真实采集",
|
||||
LabelingStatus: new("已完整标注"),
|
||||
PrivacyLevel: "中",
|
||||
PermissionMode: "授权访问",
|
||||
SupportsValidation: true,
|
||||
SellerExpectedPriceMin: new(float64(20000)),
|
||||
SellerExpectedPriceMax: new(float64(50000)),
|
||||
}
|
||||
|
||||
derived, err := buildAssetDerivedFields(input)
|
||||
if err != nil {
|
||||
t.Fatalf("buildAssetDerivedFields() error = %v", err)
|
||||
}
|
||||
|
||||
if derived.QualityLevel != "高" {
|
||||
t.Fatalf("unexpected quality level: %s", derived.QualityLevel)
|
||||
}
|
||||
if derived.ScarcityLevel != "高" {
|
||||
t.Fatalf("unexpected scarcity level: %s", derived.ScarcityLevel)
|
||||
}
|
||||
if derived.BaseValueScore <= 0 {
|
||||
t.Fatalf("unexpected base value score: %v", derived.BaseValueScore)
|
||||
}
|
||||
if derived.BasePriceMin >= derived.BasePriceMax {
|
||||
t.Fatalf("unexpected base price range: %v - %v", derived.BasePriceMin, derived.BasePriceMax)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildNegotiationRange(t *testing.T) {
|
||||
pricing := repository.PricingResult{
|
||||
ScenarioPriceMin: numericValue(30000),
|
||||
ScenarioPriceMax: numericValue(42000),
|
||||
}
|
||||
|
||||
min, max, err := buildNegotiationRange(pricing, 38000)
|
||||
if err != nil {
|
||||
t.Fatalf("buildNegotiationRange() error = %v", err)
|
||||
}
|
||||
|
||||
if min != 34960 {
|
||||
t.Fatalf("unexpected negotiation min: %v", min)
|
||||
}
|
||||
if max != 41040 {
|
||||
t.Fatalf("unexpected negotiation max: %v", max)
|
||||
}
|
||||
}
|
||||
197
internal/service/types.go
Normal file
197
internal/service/types.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ListResult[T any] struct {
|
||||
List []T `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
type Asset struct {
|
||||
ID string `json:"id"`
|
||||
AssetName string `json:"asset_name"`
|
||||
AssetType string `json:"asset_type"`
|
||||
Domain string `json:"domain"`
|
||||
ApplicationScene *string `json:"application_scene,omitempty"`
|
||||
DataDescription string `json:"data_description"`
|
||||
DataScale string `json:"data_scale"`
|
||||
CollectionMethod string `json:"collection_method"`
|
||||
LabelingStatus *string `json:"labeling_status,omitempty"`
|
||||
UpdateFrequency *string `json:"update_frequency,omitempty"`
|
||||
PrivacyLevel string `json:"privacy_level"`
|
||||
PermissionMode string `json:"permission_mode"`
|
||||
SupportsValidation bool `json:"supports_validation"`
|
||||
SellerExpectedPriceMin *float64 `json:"seller_expected_price_min,omitempty"`
|
||||
SellerExpectedPriceMax *float64 `json:"seller_expected_price_max,omitempty"`
|
||||
QualityLevel *string `json:"quality_level,omitempty"`
|
||||
ScarcityLevel *string `json:"scarcity_level,omitempty"`
|
||||
BaseValueScore *float64 `json:"base_value_score,omitempty"`
|
||||
BasePriceMin *float64 `json:"base_price_min,omitempty"`
|
||||
BasePriceMax *float64 `json:"base_price_max,omitempty"`
|
||||
AssetStatus string `json:"asset_status"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
type AssetCreateInput struct {
|
||||
AssetName string
|
||||
AssetType string
|
||||
Domain string
|
||||
ApplicationScene *string
|
||||
DataDescription string
|
||||
DataScale string
|
||||
CollectionMethod string
|
||||
LabelingStatus *string
|
||||
UpdateFrequency *string
|
||||
PrivacyLevel string
|
||||
PermissionMode string
|
||||
SupportsValidation bool
|
||||
SellerExpectedPriceMin *float64
|
||||
SellerExpectedPriceMax *float64
|
||||
}
|
||||
|
||||
type AssetListInput struct {
|
||||
Limit int32
|
||||
Offset int32
|
||||
Keyword *string
|
||||
AssetType *string
|
||||
Domain *string
|
||||
PrivacyLevel *string
|
||||
SupportsValidation *bool
|
||||
}
|
||||
|
||||
type StatusUpdate struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
type AssetStatusResult struct {
|
||||
AssetID string `json:"asset_id"`
|
||||
AssetStatus string `json:"asset_status"`
|
||||
}
|
||||
|
||||
type Pricing struct {
|
||||
ID string `json:"pricing_id"`
|
||||
AssetID string `json:"asset_id"`
|
||||
RequestID string `json:"request_id"`
|
||||
ScenarioValueScore *float64 `json:"scenario_value_score,omitempty"`
|
||||
ScenarioPriceMin *float64 `json:"scenario_price_min,omitempty"`
|
||||
ScenarioPriceMax *float64 `json:"scenario_price_max,omitempty"`
|
||||
SuggestedPrice *float64 `json:"suggested_price,omitempty"`
|
||||
SuccessProbability *float64 `json:"success_probability,omitempty"`
|
||||
PricingReason1 *string `json:"pricing_reason_1,omitempty"`
|
||||
PricingReason2 *string `json:"pricing_reason_2,omitempty"`
|
||||
PricingReason3 *string `json:"pricing_reason_3,omitempty"`
|
||||
VerificationSuggestion *string `json:"verification_suggestion,omitempty"`
|
||||
PricingStatus string `json:"pricing_status"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
type PricingCreateInput struct {
|
||||
AssetID string
|
||||
TaskType string
|
||||
ModelType string
|
||||
BuyerBudgetMin *float64
|
||||
BuyerBudgetMax *float64
|
||||
PrivacyRequirement *string
|
||||
UsagePurpose *string
|
||||
RequestNote *string
|
||||
}
|
||||
|
||||
type Validation struct {
|
||||
ID string `json:"validation_id"`
|
||||
AssetID string `json:"asset_id"`
|
||||
RequestID string `json:"request_id"`
|
||||
ValidationType *string `json:"validation_type,omitempty"`
|
||||
ValidationRequested bool `json:"validation_requested"`
|
||||
ValidationStatus string `json:"validation_status"`
|
||||
ValidationSignal *string `json:"validation_signal,omitempty"`
|
||||
ValidationScore *float64 `json:"validation_score,omitempty"`
|
||||
RiskWarning *string `json:"risk_warning,omitempty"`
|
||||
ContinueRecommendation *string `json:"continue_recommendation,omitempty"`
|
||||
ValidationCreatedAt *time.Time `json:"validation_created_at,omitempty"`
|
||||
ValidationFinishedAt *time.Time `json:"validation_finished_at,omitempty"`
|
||||
}
|
||||
|
||||
type ValidationCreateInput struct {
|
||||
AssetID string
|
||||
RequestID string
|
||||
ValidationType string
|
||||
}
|
||||
|
||||
type ValidationCreateResult struct {
|
||||
ValidationID string `json:"validation_id"`
|
||||
ValidationStatus string `json:"validation_status"`
|
||||
}
|
||||
|
||||
type Order struct {
|
||||
ID string `json:"order_id"`
|
||||
AssetID string `json:"asset_id"`
|
||||
RequestID string `json:"request_id"`
|
||||
PricingID string `json:"pricing_id"`
|
||||
ValidationID string `json:"validation_id,omitempty"`
|
||||
AssetName string `json:"asset_name"`
|
||||
CurrentPrice *float64 `json:"current_price,omitempty"`
|
||||
NegotiationMin *float64 `json:"negotiation_min,omitempty"`
|
||||
NegotiationMax *float64 `json:"negotiation_max,omitempty"`
|
||||
ValidationUsed bool `json:"validation_used"`
|
||||
DeliveryMode string `json:"delivery_mode"`
|
||||
OrderStatus string `json:"order_status"`
|
||||
OrderCreatedAt *time.Time `json:"order_created_at,omitempty"`
|
||||
OrderUpdatedAt *time.Time `json:"order_updated_at,omitempty"`
|
||||
}
|
||||
|
||||
type OrderCreateInput struct {
|
||||
AssetID string
|
||||
RequestID string
|
||||
PricingID string
|
||||
ValidationID *string
|
||||
CurrentPrice *float64
|
||||
DeliveryMode string
|
||||
}
|
||||
|
||||
type OrderCreateResult struct {
|
||||
OrderID string `json:"order_id"`
|
||||
OrderStatus string `json:"order_status"`
|
||||
}
|
||||
|
||||
type OrderListInput struct {
|
||||
Limit int32
|
||||
Offset int32
|
||||
OrderStatus string
|
||||
}
|
||||
|
||||
type ValidationListInput struct {
|
||||
Limit int32
|
||||
Offset int32
|
||||
}
|
||||
|
||||
type AssetService interface {
|
||||
CreateAsset(context.Context, AssetCreateInput) (AssetStatusResult, error)
|
||||
GetAsset(context.Context, string) (Asset, error)
|
||||
ListAssets(context.Context, AssetListInput) (ListResult[Asset], error)
|
||||
UpdateAssetStatus(context.Context, string, StatusUpdate) (AssetStatusResult, error)
|
||||
}
|
||||
|
||||
type PricingService interface {
|
||||
CreatePricing(context.Context, PricingCreateInput) (Pricing, error)
|
||||
GetPricing(context.Context, string) (Pricing, error)
|
||||
}
|
||||
|
||||
type ValidationService interface {
|
||||
CreateValidation(context.Context, ValidationCreateInput) (ValidationCreateResult, error)
|
||||
GetValidation(context.Context, string) (Validation, error)
|
||||
ListValidations(context.Context, ValidationListInput) (ListResult[Validation], error)
|
||||
}
|
||||
|
||||
type OrderService interface {
|
||||
CreateOrder(context.Context, OrderCreateInput) (OrderCreateResult, error)
|
||||
GetOrder(context.Context, string) (Order, error)
|
||||
ListOrders(context.Context, OrderListInput) (ListResult[Order], error)
|
||||
UpdateOrderStatus(context.Context, string, StatusUpdate) (OrderCreateResult, error)
|
||||
}
|
||||
Reference in New Issue
Block a user