diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..1bd167c --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,28 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write|Bash", + "hooks": [ + { + "type": "command", + "command": "git rev-parse --git-dir >/dev/null 2>&1 && code-review-graph update --skip-flows --repo \"/Users/liulujian/Documents/code/TopFansByGithub\" || true", + "timeout": 30 + } + ] + } + ], + "SessionStart": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": "git rev-parse --git-dir >/dev/null 2>&1 && code-review-graph status --repo \"/Users/liulujian/Documents/code/TopFansByGithub\" || echo 'Not a git repo, skipping'", + "timeout": 10 + } + ] + } + ] + } +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 929b633..67d237e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,7 +4,12 @@ "Skill(superpowers:subagent-driven-development)", "Skill(superpowers:subagent-driven-development:*)", "Bash(go build:*)", - "Bash(go vet:*)" + "Bash(go vet:*)", + "mcp__code-review-graph__semantic_search_nodes_tool" ] - } + }, + "enableAllProjectMcpServers": true, + "enabledMcpjsonServers": [ + "code-review-graph" + ] } diff --git a/.claude/skills/debug-issue/skill.md b/.claude/skills/debug-issue/skill.md new file mode 100644 index 0000000..ef1b38a --- /dev/null +++ b/.claude/skills/debug-issue/skill.md @@ -0,0 +1,27 @@ +--- +name: Debug Issue +description: Systematically debug issues using graph-powered code navigation +--- + +## Debug Issue + +Use the knowledge graph to systematically trace and debug issues. + +### Steps + +1. Use `semantic_search_nodes` to find code related to the issue. +2. Use `query_graph` with `callers_of` and `callees_of` to trace call chains. +3. Use `get_flow` to see full execution paths through suspected areas. +4. Run `detect_changes` to check if recent changes caused the issue. +5. Use `get_impact_radius` on suspected files to see what else is affected. + +### Tips + +- Check both callers and callees to understand the full context. +- Look at affected flows to find the entry point that triggers the bug. +- Recent changes are the most common source of new issues. + +## Token Efficiency Rules +- ALWAYS start with `get_minimal_context(task="")` before any other graph tool. +- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient. +- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens. diff --git a/.claude/skills/explore-codebase/skill.md b/.claude/skills/explore-codebase/skill.md new file mode 100644 index 0000000..dc7ad10 --- /dev/null +++ b/.claude/skills/explore-codebase/skill.md @@ -0,0 +1,28 @@ +--- +name: Explore Codebase +description: Navigate and understand codebase structure using the knowledge graph +--- + +## Explore Codebase + +Use the code-review-graph MCP tools to explore and understand the codebase. + +### Steps + +1. Run `list_graph_stats` to see overall codebase metrics. +2. Run `get_architecture_overview` for high-level community structure. +3. Use `list_communities` to find major modules, then `get_community` for details. +4. Use `semantic_search_nodes` to find specific functions or classes. +5. Use `query_graph` with patterns like `callers_of`, `callees_of`, `imports_of` to trace relationships. +6. Use `list_flows` and `get_flow` to understand execution paths. + +### Tips + +- Start broad (stats, architecture) then narrow down to specific areas. +- Use `children_of` on a file to see all its functions and classes. +- Use `find_large_functions` to identify complex code. + +## Token Efficiency Rules +- ALWAYS start with `get_minimal_context(task="")` before any other graph tool. +- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient. +- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens. diff --git a/.claude/skills/refactor-safely/skill.md b/.claude/skills/refactor-safely/skill.md new file mode 100644 index 0000000..cf84420 --- /dev/null +++ b/.claude/skills/refactor-safely/skill.md @@ -0,0 +1,28 @@ +--- +name: Refactor Safely +description: Plan and execute safe refactoring using dependency analysis +--- + +## Refactor Safely + +Use the knowledge graph to plan and execute refactoring with confidence. + +### Steps + +1. Use `refactor_tool` with mode="suggest" for community-driven refactoring suggestions. +2. Use `refactor_tool` with mode="dead_code" to find unreferenced code. +3. For renames, use `refactor_tool` with mode="rename" to preview all affected locations. +4. Use `apply_refactor_tool` with the refactor_id to apply renames. +5. After changes, run `detect_changes` to verify the refactoring impact. + +### Safety Checks + +- Always preview before applying (rename mode gives you an edit list). +- Check `get_impact_radius` before major refactors. +- Use `get_affected_flows` to ensure no critical paths are broken. +- Run `find_large_functions` to identify decomposition targets. + +## Token Efficiency Rules +- ALWAYS start with `get_minimal_context(task="")` before any other graph tool. +- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient. +- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens. diff --git a/.claude/skills/review-changes/skill.md b/.claude/skills/review-changes/skill.md new file mode 100644 index 0000000..6bb3558 --- /dev/null +++ b/.claude/skills/review-changes/skill.md @@ -0,0 +1,29 @@ +--- +name: Review Changes +description: Perform a structured code review using change detection and impact +--- + +## Review Changes + +Perform a thorough, risk-aware code review using the knowledge graph. + +### Steps + +1. Run `detect_changes` to get risk-scored change analysis. +2. Run `get_affected_flows` to find impacted execution paths. +3. For each high-risk function, run `query_graph` with pattern="tests_for" to check test coverage. +4. Run `get_impact_radius` to understand the blast radius. +5. For any untested changes, suggest specific test cases. + +### Output Format + +Provide findings grouped by risk level (high/medium/low) with: +- What changed and why it matters +- Test coverage status +- Suggested improvements +- Overall merge recommendation + +## Token Efficiency Rules +- ALWAYS start with `get_minimal_context(task="")` before any other graph tool. +- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient. +- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens. diff --git a/.gitignore b/.gitignore index 186ef7b..d866917 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ frontend/.hbuilderx/launch.json .idea node_modules -package-lock.json \ No newline at end of file +package-lock.json +# Added by code-review-graph +.code-review-graph/ diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..c3391d2 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,13 @@ +{ + "mcpServers": { + "code-review-graph": { + "command": "uvx", + "args": [ + "code-review-graph", + "serve" + ], + "cwd": "/Users/liulujian/Documents/code/TopFansByGithub", + "type": "stdio" + } + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d3dc9b4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,38 @@ + +## MCP Tools: code-review-graph + +**IMPORTANT: This project has a knowledge graph. ALWAYS use the +code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore +the codebase.** The graph is faster, cheaper (fewer tokens), and gives +you structural context (callers, dependents, test coverage) that file +scanning cannot. + +### When to use graph tools FIRST + +- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep +- **Understanding impact**: `get_impact_radius` instead of manually tracing imports +- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files +- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for +- **Architecture questions**: `get_architecture_overview` + `list_communities` + +Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. + +### Key Tools + +| Tool | Use when | +| ------ | ---------- | +| `detect_changes` | Reviewing code changes — gives risk-scored analysis | +| `get_review_context` | Need source snippets for review — token-efficient | +| `get_impact_radius` | Understanding blast radius of a change | +| `get_affected_flows` | Finding which execution paths are impacted | +| `query_graph` | Tracing callers, callees, imports, tests, dependencies | +| `semantic_search_nodes` | Finding functions/classes by name or keyword | +| `get_architecture_overview` | Understanding high-level codebase structure | +| `refactor_tool` | Planning renames, finding dead code | + +### Workflow + +1. The graph auto-updates on file changes (via hooks). +2. Use `detect_changes` for code review. +3. Use `get_affected_flows` to understand impact. +4. Use `query_graph` pattern="tests_for" to check coverage. diff --git a/backend/dev.sh b/backend/dev.sh index 7e88636..92b8bca 100755 --- a/backend/dev.sh +++ b/backend/dev.sh @@ -219,20 +219,26 @@ restart_service() { } # 启动文件监听器 -# 用法: start_watcher name dir binary port use_db +# 用法: start_watcher name dir1:dir2:dir3 binary port use_db +# 注意: 多个目录用冒号分隔 start_watcher() { local name=$1 - local dir=$2 + local dirs=$2 local binary=$3 local port=$4 local use_db=$5 - local watch_path="$SCRIPT_DIR/$dir" + local watch_paths=() + local watch_path="" local restart_marker="/tmp/dev_sh_${name}_restart" - if [ ! -d "$watch_path" ]; then - echo -e "${RED}❌ 监听目录不存在: $watch_path${NC}" - return 1 - fi + # 分割多个目录 + IFS=':' read -ra watch_paths <<< "$dirs" + for d in "${watch_paths[@]}"; do + if [ ! -d "$SCRIPT_DIR/$d" ]; then + echo -e "${RED}❌ 监听目录不存在: $SCRIPT_DIR/$d${NC}" + return 1 + fi + done # 清理可能遗留的重启标记 rm -f "$restart_marker" @@ -240,20 +246,31 @@ start_watcher() { ( if [[ "$(uname)" == "Darwin" ]]; then # 排除: .git目录, 测试文件, 二进制文件(无扩展名), .exe, bin/目录 - fswatch -r "$watch_path" \ - --exclude='\.git' \ - --exclude='_test\.go$' \ - --exclude='\.exe$' \ - --exclude='gateway$' \ - --exclude='userService$' \ - --exclude='assetService$' \ - --exclude='socialService$' \ - --exclude='galleryService$' \ - --exclude='activityService$' \ - --exclude='taskService$' \ - --exclude='starbookService$' + local exclude_args="" + for d in "${watch_paths[@]}"; do + if [[ "$d" == "pkg/proto"* ]] || [[ "$d" == "proto"* ]]; then + # proto 目录监听所有 proto 文件变化 + fswatch -r "$SCRIPT_DIR/$d" \ + --exclude='\.git' \ + --exclude='_test\.go$' \ + --exclude='\.exe$' & + else + fswatch -r "$SCRIPT_DIR/$d" \ + --exclude='\.git' \ + --exclude='_test\.go$' \ + --exclude='\.exe$' \ + --exclude='gateway$' \ + --exclude='userService$' \ + --exclude='assetService$' \ + --exclude='socialService$' \ + --exclude='galleryService$' \ + --exclude='activityService$' \ + --exclude='taskService$' \ + --exclude='starbookService$' & + fi + done else - inotifywait -r -m -e modify,create,write "$watch_path" \ + inotifywait -r -m -e modify,create,write "${watch_paths[@]}" \ --exclude='\.git' \ --exclude='_test\.go$' \ --exclude='\.exe$' \ @@ -264,16 +281,9 @@ start_watcher() { --exclude='galleryService$' \ --exclude='activityService$' \ --exclude='taskService$' \ - --exclude='starbookService$' - fi | while read event; do - # 时间戳防抖:每次事件更新标记文件 - # Darwin/Windows 不支持 date +%s%N,使用 python 获取纳秒时间戳 - if [[ "$(uname)" == "Darwin" ]] || [[ "$(uname)" =~ ^MINGW ]] || [[ "$(uname)" =~ ^MSYS ]] || [[ "$(uname)" =~ ^CYGWIN ]]; then - python3 -c 'import time; print(int(time.time()*1e9))' > "$restart_marker" - else - date +%s%N > "$restart_marker" - fi - done + --exclude='starbookService$' & + fi + wait ) & local watcher_pid=$! echo "$watcher_pid" >> /tmp/dev_sh_watchers.tmp @@ -297,7 +307,25 @@ start_watcher() { if (( elapsed >= 500000000 )); then # 距上次事件已过 300ms,执行重启 rm -f "$restart_marker" - restart_service "$name" "$dir" "$binary" "$port" "$use_db" + + # 检查是否包含 proto 目录变化,包含则先重新编译 proto + local proto_changed=false + for d in "${watch_paths[@]}"; do + if [[ "$d" == "pkg/proto"* ]] || [[ "$d" == "proto"* ]]; then + proto_changed=true + break + fi + done + + if [ "$proto_changed" = true ]; then + echo -e "${YELLOW}🔄 [Proto 文件变化] 重新编译 Proto...${NC}" + build_proto + # proto 变化时重启 gateway(gateway 是 proto 客户端的调用方) + echo -e "${YELLOW}🔄 [Proto 变化] 重启 gateway...${NC}" + restart_service "gateway" "gateway" "gateway/gateway" "8080" "0" + fi + + restart_service "$name" "${watch_paths[0]}" "$binary" "$port" "$use_db" fi done ) & @@ -328,6 +356,21 @@ for service in gateway userService socialService assetService galleryService act done sleep 1 +# 编译 proto 文件 +build_proto() { + echo -e "${YELLOW}📦 编译 Proto 文件...${NC}" + if [ -f "$SCRIPT_DIR/scripts/compile-proto.sh" ]; then + bash "$SCRIPT_DIR/scripts/compile-proto.sh" + else + echo -e "${RED}❌ compile-proto.sh 不存在,跳过${NC}" + fi +} + +# 先编译 proto 文件 +echo "" +echo -e "${YELLOW}🔨 预编译 Proto 文件...${NC}" +build_proto + # 先构建所有服务 echo "" echo -e "${YELLOW}🔨 预编译所有服务...${NC}" @@ -361,13 +404,13 @@ start_service "gateway" "gateway/gateway" 8080 # 启动所有文件监听器 echo "" echo -e "${YELLOW}👁️ 启动所有文件监听器...${NC}" -start_watcher "gateway" "gateway" "gateway/gateway" 8080 0 -start_watcher "userService" "services/userService" "services/userService/userService" 20000 1 -start_watcher "assetService" "services/assetService" "services/assetService/assetService" 20003 1 -start_watcher "socialService" "services/socialService" "services/socialService/socialService" 20002 1 -start_watcher "activityService" "services/activityService" "services/activityService/activityService" 20005 1 -start_watcher "taskService" "services/taskService" "services/taskService/taskService" 20006 1 -start_watcher "starbookService" "services/starbookService" "services/starbookService/starbookService" 20007 1 +start_watcher "gateway" "gateway:pkg/proto" "gateway/gateway" 8080 0 +start_watcher "userService" "services/userService" "services/userService/userService" 20000 1 +start_watcher "assetService" "services/assetService:pkg/proto/asset" "services/assetService/assetService" 20003 1 +start_watcher "socialService" "services/socialService" "services/socialService/socialService" 20002 1 +start_watcher "activityService" "services/activityService" "services/activityService/activityService" 20005 1 +start_watcher "taskService" "services/taskService" "services/taskService/taskService" 20006 1 +start_watcher "starbookService" "services/starbookService" "services/starbookService/starbookService" 20007 1 echo "" echo -e "${GREEN}========================================${NC}" diff --git a/backend/gateway/controller/asset_controller.go b/backend/gateway/controller/asset_controller.go index 4327917..247adca 100644 --- a/backend/gateway/controller/asset_controller.go +++ b/backend/gateway/controller/asset_controller.go @@ -228,6 +228,51 @@ func (ctrl *AssetController) PreCreateMintOrder(c *gin.Context) { response.Success(c, data) } +// EstimateMintCost 估算铸造费用 +// @Summary 估算铸造费用 +// @Description 在用户确认铸造前,显示本次铸造消耗水晶数和当前余额 +// @Tags assets +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} response.Response +// @Router /api/v1/assets/mints/cost-estimate [post] +func (ctrl *AssetController) EstimateMintCost(c *gin.Context) { + userID, exists := c.Get("user_id") + if !exists { + response.Error(c, http.StatusUnauthorized, "未授权") + return + } + starID, exists := c.Get("star_id") + if !exists { + response.Error(c, http.StatusUnauthorized, "未授权") + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ + "user_id": strconv.FormatInt(userID.(int64), 10), + "star_id": strconv.FormatInt(starID.(int64), 10), + }) + + resp, err := ctrl.assetService.EstimateMintCost(ctx, &pbAsset.EstimateMintCostRequest{}) + if err != nil { + code, msg := parseRPCError(err) + response.ErrorWithCode(c, code, msg) + return + } + + data := map[string]interface{}{ + "cost_crystal": resp.CostCrystal, + "current_balance": resp.CurrentBalance, + "balance_after": resp.BalanceAfter, + "mint_count": resp.MintCount, + "next_tier_hint": resp.NextTierHint, + } + response.Success(c, data) +} + // CreateMintOrder 创建铸造订单 // @Summary 创建铸造订单 // @Description 创建一个新的数字藏品铸造订单 @@ -1619,9 +1664,18 @@ func (ctrl *AssetController) GetAssetMaterials(c *gin.Context) { return } + userID, _ := c.Get("user_id") + starID, _ := c.Get("star_id") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + // 设置 Dubbo attachments + ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ + "user_id": strconv.FormatInt(userID.(int64), 10), + "star_id": strconv.FormatInt(starID.(int64), 10), + }) + resp, err := ctrl.assetService.GetAssetMaterials(ctx, &pbAsset.GetAssetMaterialsRequest{ AssetId: assetID, }) diff --git a/backend/gateway/router/router.go b/backend/gateway/router/router.go index eab337c..b8deaba 100644 --- a/backend/gateway/router/router.go +++ b/backend/gateway/router/router.go @@ -187,6 +187,7 @@ func SetupRouter(userClient *client.Client, socialClient *client.Client, assetCl assets.GET("/oss/batch-presigned-urls", assetCtrl.GetOSSBatchPresignedURLs) // 批量获取 OSS 预签名URL(用于读取目录下的图片) assets.POST("/upload", assetCtrl.UploadImage) // 上传图片 assets.POST("/mints/precreate", assetCtrl.PreCreateMintOrder) // 阶段一:预创建铸造订单(返回 order_id) + assets.POST("/mints/cost-estimate", assetCtrl.EstimateMintCost) // 估算铸造费用(显示消耗和余额) assets.POST("/mints", assetCtrl.CreateMintOrder) // 创建铸造订单 assets.GET("/mints/:order_id", assetCtrl.GetMintOrder) // 查询铸造订单状态 assets.DELETE("/mints/:order_id", assetCtrl.CancelMintOrder) // 取消铸造订单 diff --git a/backend/pkg/proto/asset/asset.pb.go b/backend/pkg/proto/asset/asset.pb.go index 2d5e699..a8c994c 100644 --- a/backend/pkg/proto/asset/asset.pb.go +++ b/backend/pkg/proto/asset/asset.pb.go @@ -817,8 +817,10 @@ func (x *PreCreateMintOrderResponse) GetOrder() *MintOrder { type CreateMintOrderResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` - Order *MintOrder `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` // 创建的订单信息 - Asset *Asset `protobuf:"bytes,3,opt,name=asset,proto3" json:"asset,omitempty"` // 创建的资产信息 + Order *MintOrder `protobuf:"bytes,2,opt,name=order,proto3" json:"order,omitempty"` // 创建的订单信息 + Asset *Asset `protobuf:"bytes,3,opt,name=asset,proto3" json:"asset,omitempty"` // 创建的资产信息 + CostCrystal int64 `protobuf:"varint,4,opt,name=cost_crystal,json=costCrystal,proto3" json:"cost_crystal,omitempty"` // 本次铸造消耗水晶 + BalanceAfter int64 `protobuf:"varint,5,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"` // 铸造后余额 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -874,6 +876,150 @@ func (x *CreateMintOrderResponse) GetAsset() *Asset { return nil } +func (x *CreateMintOrderResponse) GetCostCrystal() int64 { + if x != nil { + return x.CostCrystal + } + return 0 +} + +func (x *CreateMintOrderResponse) GetBalanceAfter() int64 { + if x != nil { + return x.BalanceAfter + } + return 0 +} + +// 估算铸造费用请求 +type EstimateMintCostRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + TraceId string `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` // 追踪ID(可选,用于调试) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EstimateMintCostRequest) Reset() { + *x = EstimateMintCostRequest{} + mi := &file_asset_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EstimateMintCostRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EstimateMintCostRequest) ProtoMessage() {} + +func (x *EstimateMintCostRequest) ProtoReflect() protoreflect.Message { + mi := &file_asset_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EstimateMintCostRequest.ProtoReflect.Descriptor instead. +func (*EstimateMintCostRequest) Descriptor() ([]byte, []int) { + return file_asset_proto_rawDescGZIP(), []int{9} +} + +func (x *EstimateMintCostRequest) GetTraceId() string { + if x != nil { + return x.TraceId + } + return "" +} + +// 估算铸造费用响应 +type EstimateMintCostResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + CostCrystal int64 `protobuf:"varint,2,opt,name=cost_crystal,json=costCrystal,proto3" json:"cost_crystal,omitempty"` // 本次铸造消耗水晶 + CurrentBalance int64 `protobuf:"varint,3,opt,name=current_balance,json=currentBalance,proto3" json:"current_balance,omitempty"` // 当前余额(铸造前) + BalanceAfter int64 `protobuf:"varint,4,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"` // 铸造后余额 + MintCount int32 `protobuf:"varint,5,opt,name=mint_count,json=mintCount,proto3" json:"mint_count,omitempty"` // 本次是第几次铸造 + NextTierHint string `protobuf:"bytes,6,opt,name=next_tier_hint,json=nextTierHint,proto3" json:"next_tier_hint,omitempty"` // 下一阶梯提示 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EstimateMintCostResponse) Reset() { + *x = EstimateMintCostResponse{} + mi := &file_asset_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EstimateMintCostResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EstimateMintCostResponse) ProtoMessage() {} + +func (x *EstimateMintCostResponse) ProtoReflect() protoreflect.Message { + mi := &file_asset_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EstimateMintCostResponse.ProtoReflect.Descriptor instead. +func (*EstimateMintCostResponse) Descriptor() ([]byte, []int) { + return file_asset_proto_rawDescGZIP(), []int{10} +} + +func (x *EstimateMintCostResponse) GetBase() *common.BaseResponse { + if x != nil { + return x.Base + } + return nil +} + +func (x *EstimateMintCostResponse) GetCostCrystal() int64 { + if x != nil { + return x.CostCrystal + } + return 0 +} + +func (x *EstimateMintCostResponse) GetCurrentBalance() int64 { + if x != nil { + return x.CurrentBalance + } + return 0 +} + +func (x *EstimateMintCostResponse) GetBalanceAfter() int64 { + if x != nil { + return x.BalanceAfter + } + return 0 +} + +func (x *EstimateMintCostResponse) GetMintCount() int32 { + if x != nil { + return x.MintCount + } + return 0 +} + +func (x *EstimateMintCostResponse) GetNextTierHint() string { + if x != nil { + return x.NextTierHint + } + return "" +} + // 获取我的藏品列表请求 type GetMyAssetsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -888,7 +1034,7 @@ type GetMyAssetsRequest struct { func (x *GetMyAssetsRequest) Reset() { *x = GetMyAssetsRequest{} - mi := &file_asset_proto_msgTypes[9] + mi := &file_asset_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -900,7 +1046,7 @@ func (x *GetMyAssetsRequest) String() string { func (*GetMyAssetsRequest) ProtoMessage() {} func (x *GetMyAssetsRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[9] + mi := &file_asset_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -913,7 +1059,7 @@ func (x *GetMyAssetsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyAssetsRequest.ProtoReflect.Descriptor instead. func (*GetMyAssetsRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{9} + return file_asset_proto_rawDescGZIP(), []int{11} } func (x *GetMyAssetsRequest) GetPage() int32 { @@ -962,7 +1108,7 @@ type GetMyAssetsResponse struct { func (x *GetMyAssetsResponse) Reset() { *x = GetMyAssetsResponse{} - mi := &file_asset_proto_msgTypes[10] + mi := &file_asset_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -974,7 +1120,7 @@ func (x *GetMyAssetsResponse) String() string { func (*GetMyAssetsResponse) ProtoMessage() {} func (x *GetMyAssetsResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[10] + mi := &file_asset_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -987,7 +1133,7 @@ func (x *GetMyAssetsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMyAssetsResponse.ProtoReflect.Descriptor instead. func (*GetMyAssetsResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{10} + return file_asset_proto_rawDescGZIP(), []int{12} } func (x *GetMyAssetsResponse) GetBase() *common.BaseResponse { @@ -1018,7 +1164,7 @@ type AssetListData struct { func (x *AssetListData) Reset() { *x = AssetListData{} - mi := &file_asset_proto_msgTypes[11] + mi := &file_asset_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1030,7 +1176,7 @@ func (x *AssetListData) String() string { func (*AssetListData) ProtoMessage() {} func (x *AssetListData) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[11] + mi := &file_asset_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1043,7 +1189,7 @@ func (x *AssetListData) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetListData.ProtoReflect.Descriptor instead. func (*AssetListData) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{11} + return file_asset_proto_rawDescGZIP(), []int{13} } func (x *AssetListData) GetGroups() []*AssetGroup { @@ -1097,7 +1243,7 @@ type AssetGroup struct { func (x *AssetGroup) Reset() { *x = AssetGroup{} - mi := &file_asset_proto_msgTypes[12] + mi := &file_asset_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1109,7 +1255,7 @@ func (x *AssetGroup) String() string { func (*AssetGroup) ProtoMessage() {} func (x *AssetGroup) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[12] + mi := &file_asset_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1122,7 +1268,7 @@ func (x *AssetGroup) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetGroup.ProtoReflect.Descriptor instead. func (*AssetGroup) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{12} + return file_asset_proto_rawDescGZIP(), []int{14} } func (x *AssetGroup) GetType() string { @@ -1187,7 +1333,7 @@ type GradeSection struct { func (x *GradeSection) Reset() { *x = GradeSection{} - mi := &file_asset_proto_msgTypes[13] + mi := &file_asset_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1199,7 +1345,7 @@ func (x *GradeSection) String() string { func (*GradeSection) ProtoMessage() {} func (x *GradeSection) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[13] + mi := &file_asset_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1212,7 +1358,7 @@ func (x *GradeSection) ProtoReflect() protoreflect.Message { // Deprecated: Use GradeSection.ProtoReflect.Descriptor instead. func (*GradeSection) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{13} + return file_asset_proto_rawDescGZIP(), []int{15} } func (x *GradeSection) GetGrade() int32 { @@ -1260,7 +1406,7 @@ type AssetItem struct { func (x *AssetItem) Reset() { *x = AssetItem{} - mi := &file_asset_proto_msgTypes[14] + mi := &file_asset_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1272,7 +1418,7 @@ func (x *AssetItem) String() string { func (*AssetItem) ProtoMessage() {} func (x *AssetItem) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[14] + mi := &file_asset_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1285,7 +1431,7 @@ func (x *AssetItem) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetItem.ProtoReflect.Descriptor instead. func (*AssetItem) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{14} + return file_asset_proto_rawDescGZIP(), []int{16} } func (x *AssetItem) GetAssetId() int64 { @@ -1361,7 +1507,7 @@ type AssetListItem struct { func (x *AssetListItem) Reset() { *x = AssetListItem{} - mi := &file_asset_proto_msgTypes[15] + mi := &file_asset_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1373,7 +1519,7 @@ func (x *AssetListItem) String() string { func (*AssetListItem) ProtoMessage() {} func (x *AssetListItem) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[15] + mi := &file_asset_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1386,7 +1532,7 @@ func (x *AssetListItem) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetListItem.ProtoReflect.Descriptor instead. func (*AssetListItem) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{15} + return file_asset_proto_rawDescGZIP(), []int{17} } func (x *AssetListItem) GetAssetId() int64 { @@ -1455,7 +1601,7 @@ type GetAssetRequest struct { func (x *GetAssetRequest) Reset() { *x = GetAssetRequest{} - mi := &file_asset_proto_msgTypes[16] + mi := &file_asset_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1467,7 +1613,7 @@ func (x *GetAssetRequest) String() string { func (*GetAssetRequest) ProtoMessage() {} func (x *GetAssetRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[16] + mi := &file_asset_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1480,7 +1626,7 @@ func (x *GetAssetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetRequest.ProtoReflect.Descriptor instead. func (*GetAssetRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{16} + return file_asset_proto_rawDescGZIP(), []int{18} } func (x *GetAssetRequest) GetAssetId() int64 { @@ -1501,7 +1647,7 @@ type GetAssetResponse struct { func (x *GetAssetResponse) Reset() { *x = GetAssetResponse{} - mi := &file_asset_proto_msgTypes[17] + mi := &file_asset_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1513,7 +1659,7 @@ func (x *GetAssetResponse) String() string { func (*GetAssetResponse) ProtoMessage() {} func (x *GetAssetResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[17] + mi := &file_asset_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1526,7 +1672,7 @@ func (x *GetAssetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetResponse.ProtoReflect.Descriptor instead. func (*GetAssetResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{17} + return file_asset_proto_rawDescGZIP(), []int{19} } func (x *GetAssetResponse) GetBase() *common.BaseResponse { @@ -1553,7 +1699,7 @@ type GetAssetStatusRequest struct { func (x *GetAssetStatusRequest) Reset() { *x = GetAssetStatusRequest{} - mi := &file_asset_proto_msgTypes[18] + mi := &file_asset_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1565,7 +1711,7 @@ func (x *GetAssetStatusRequest) String() string { func (*GetAssetStatusRequest) ProtoMessage() {} func (x *GetAssetStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[18] + mi := &file_asset_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1578,7 +1724,7 @@ func (x *GetAssetStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetStatusRequest.ProtoReflect.Descriptor instead. func (*GetAssetStatusRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{18} + return file_asset_proto_rawDescGZIP(), []int{20} } func (x *GetAssetStatusRequest) GetAssetId() int64 { @@ -1604,7 +1750,7 @@ type GetAssetStatusResponse struct { func (x *GetAssetStatusResponse) Reset() { *x = GetAssetStatusResponse{} - mi := &file_asset_proto_msgTypes[19] + mi := &file_asset_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1616,7 +1762,7 @@ func (x *GetAssetStatusResponse) String() string { func (*GetAssetStatusResponse) ProtoMessage() {} func (x *GetAssetStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[19] + mi := &file_asset_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1629,7 +1775,7 @@ func (x *GetAssetStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetStatusResponse.ProtoReflect.Descriptor instead. func (*GetAssetStatusResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{19} + return file_asset_proto_rawDescGZIP(), []int{21} } func (x *GetAssetStatusResponse) GetBase() *common.BaseResponse { @@ -1691,7 +1837,7 @@ type GetMintOrderRequest struct { func (x *GetMintOrderRequest) Reset() { *x = GetMintOrderRequest{} - mi := &file_asset_proto_msgTypes[20] + mi := &file_asset_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1703,7 +1849,7 @@ func (x *GetMintOrderRequest) String() string { func (*GetMintOrderRequest) ProtoMessage() {} func (x *GetMintOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[20] + mi := &file_asset_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1716,7 +1862,7 @@ func (x *GetMintOrderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMintOrderRequest.ProtoReflect.Descriptor instead. func (*GetMintOrderRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{20} + return file_asset_proto_rawDescGZIP(), []int{22} } func (x *GetMintOrderRequest) GetOrderId() string { @@ -1738,7 +1884,7 @@ type GetMintOrderResponse struct { func (x *GetMintOrderResponse) Reset() { *x = GetMintOrderResponse{} - mi := &file_asset_proto_msgTypes[21] + mi := &file_asset_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1750,7 +1896,7 @@ func (x *GetMintOrderResponse) String() string { func (*GetMintOrderResponse) ProtoMessage() {} func (x *GetMintOrderResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[21] + mi := &file_asset_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1763,7 +1909,7 @@ func (x *GetMintOrderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMintOrderResponse.ProtoReflect.Descriptor instead. func (*GetMintOrderResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{21} + return file_asset_proto_rawDescGZIP(), []int{23} } func (x *GetMintOrderResponse) GetBase() *common.BaseResponse { @@ -1797,7 +1943,7 @@ type CancelMintOrderRequest struct { func (x *CancelMintOrderRequest) Reset() { *x = CancelMintOrderRequest{} - mi := &file_asset_proto_msgTypes[22] + mi := &file_asset_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1809,7 +1955,7 @@ func (x *CancelMintOrderRequest) String() string { func (*CancelMintOrderRequest) ProtoMessage() {} func (x *CancelMintOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[22] + mi := &file_asset_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1822,7 +1968,7 @@ func (x *CancelMintOrderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelMintOrderRequest.ProtoReflect.Descriptor instead. func (*CancelMintOrderRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{22} + return file_asset_proto_rawDescGZIP(), []int{24} } func (x *CancelMintOrderRequest) GetOrderId() string { @@ -1844,7 +1990,7 @@ type CancelMintOrderResponse struct { func (x *CancelMintOrderResponse) Reset() { *x = CancelMintOrderResponse{} - mi := &file_asset_proto_msgTypes[23] + mi := &file_asset_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1856,7 +2002,7 @@ func (x *CancelMintOrderResponse) String() string { func (*CancelMintOrderResponse) ProtoMessage() {} func (x *CancelMintOrderResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[23] + mi := &file_asset_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1869,7 +2015,7 @@ func (x *CancelMintOrderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelMintOrderResponse.ProtoReflect.Descriptor instead. func (*CancelMintOrderResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{23} + return file_asset_proto_rawDescGZIP(), []int{25} } func (x *CancelMintOrderResponse) GetBase() *common.BaseResponse { @@ -1903,7 +2049,7 @@ type LikeAssetRequest struct { func (x *LikeAssetRequest) Reset() { *x = LikeAssetRequest{} - mi := &file_asset_proto_msgTypes[24] + mi := &file_asset_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1915,7 +2061,7 @@ func (x *LikeAssetRequest) String() string { func (*LikeAssetRequest) ProtoMessage() {} func (x *LikeAssetRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[24] + mi := &file_asset_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1928,7 +2074,7 @@ func (x *LikeAssetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LikeAssetRequest.ProtoReflect.Descriptor instead. func (*LikeAssetRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{24} + return file_asset_proto_rawDescGZIP(), []int{26} } func (x *LikeAssetRequest) GetAssetId() int64 { @@ -1949,7 +2095,7 @@ type LikeAssetResponse struct { func (x *LikeAssetResponse) Reset() { *x = LikeAssetResponse{} - mi := &file_asset_proto_msgTypes[25] + mi := &file_asset_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1961,7 +2107,7 @@ func (x *LikeAssetResponse) String() string { func (*LikeAssetResponse) ProtoMessage() {} func (x *LikeAssetResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[25] + mi := &file_asset_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1974,7 +2120,7 @@ func (x *LikeAssetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LikeAssetResponse.ProtoReflect.Descriptor instead. func (*LikeAssetResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{25} + return file_asset_proto_rawDescGZIP(), []int{27} } func (x *LikeAssetResponse) GetBase() *common.BaseResponse { @@ -2001,7 +2147,7 @@ type UnlikeAssetRequest struct { func (x *UnlikeAssetRequest) Reset() { *x = UnlikeAssetRequest{} - mi := &file_asset_proto_msgTypes[26] + mi := &file_asset_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2013,7 +2159,7 @@ func (x *UnlikeAssetRequest) String() string { func (*UnlikeAssetRequest) ProtoMessage() {} func (x *UnlikeAssetRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[26] + mi := &file_asset_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2026,7 +2172,7 @@ func (x *UnlikeAssetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnlikeAssetRequest.ProtoReflect.Descriptor instead. func (*UnlikeAssetRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{26} + return file_asset_proto_rawDescGZIP(), []int{28} } func (x *UnlikeAssetRequest) GetAssetId() int64 { @@ -2047,7 +2193,7 @@ type UnlikeAssetResponse struct { func (x *UnlikeAssetResponse) Reset() { *x = UnlikeAssetResponse{} - mi := &file_asset_proto_msgTypes[27] + mi := &file_asset_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2059,7 +2205,7 @@ func (x *UnlikeAssetResponse) String() string { func (*UnlikeAssetResponse) ProtoMessage() {} func (x *UnlikeAssetResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[27] + mi := &file_asset_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2072,7 +2218,7 @@ func (x *UnlikeAssetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnlikeAssetResponse.ProtoReflect.Descriptor instead. func (*UnlikeAssetResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{27} + return file_asset_proto_rawDescGZIP(), []int{29} } func (x *UnlikeAssetResponse) GetBase() *common.BaseResponse { @@ -2099,7 +2245,7 @@ type CheckAssetLikeRequest struct { func (x *CheckAssetLikeRequest) Reset() { *x = CheckAssetLikeRequest{} - mi := &file_asset_proto_msgTypes[28] + mi := &file_asset_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2111,7 +2257,7 @@ func (x *CheckAssetLikeRequest) String() string { func (*CheckAssetLikeRequest) ProtoMessage() {} func (x *CheckAssetLikeRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[28] + mi := &file_asset_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2124,7 +2270,7 @@ func (x *CheckAssetLikeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckAssetLikeRequest.ProtoReflect.Descriptor instead. func (*CheckAssetLikeRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{28} + return file_asset_proto_rawDescGZIP(), []int{30} } func (x *CheckAssetLikeRequest) GetAssetId() int64 { @@ -2145,7 +2291,7 @@ type CheckAssetLikeResponse struct { func (x *CheckAssetLikeResponse) Reset() { *x = CheckAssetLikeResponse{} - mi := &file_asset_proto_msgTypes[29] + mi := &file_asset_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2157,7 +2303,7 @@ func (x *CheckAssetLikeResponse) String() string { func (*CheckAssetLikeResponse) ProtoMessage() {} func (x *CheckAssetLikeResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[29] + mi := &file_asset_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2170,7 +2316,7 @@ func (x *CheckAssetLikeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckAssetLikeResponse.ProtoReflect.Descriptor instead. func (*CheckAssetLikeResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{29} + return file_asset_proto_rawDescGZIP(), []int{31} } func (x *CheckAssetLikeResponse) GetBase() *common.BaseResponse { @@ -2201,7 +2347,7 @@ type AssetLike struct { func (x *AssetLike) Reset() { *x = AssetLike{} - mi := &file_asset_proto_msgTypes[30] + mi := &file_asset_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2213,7 +2359,7 @@ func (x *AssetLike) String() string { func (*AssetLike) ProtoMessage() {} func (x *AssetLike) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[30] + mi := &file_asset_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2226,7 +2372,7 @@ func (x *AssetLike) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetLike.ProtoReflect.Descriptor instead. func (*AssetLike) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{30} + return file_asset_proto_rawDescGZIP(), []int{32} } func (x *AssetLike) GetId() int64 { @@ -2276,7 +2422,7 @@ type GetAssetLikesRequest struct { func (x *GetAssetLikesRequest) Reset() { *x = GetAssetLikesRequest{} - mi := &file_asset_proto_msgTypes[31] + mi := &file_asset_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2288,7 +2434,7 @@ func (x *GetAssetLikesRequest) String() string { func (*GetAssetLikesRequest) ProtoMessage() {} func (x *GetAssetLikesRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[31] + mi := &file_asset_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2301,7 +2447,7 @@ func (x *GetAssetLikesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetLikesRequest.ProtoReflect.Descriptor instead. func (*GetAssetLikesRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{31} + return file_asset_proto_rawDescGZIP(), []int{33} } func (x *GetAssetLikesRequest) GetAssetId() int64 { @@ -2340,7 +2486,7 @@ type GetAssetLikesResponse struct { func (x *GetAssetLikesResponse) Reset() { *x = GetAssetLikesResponse{} - mi := &file_asset_proto_msgTypes[32] + mi := &file_asset_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2352,7 +2498,7 @@ func (x *GetAssetLikesResponse) String() string { func (*GetAssetLikesResponse) ProtoMessage() {} func (x *GetAssetLikesResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[32] + mi := &file_asset_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2365,7 +2511,7 @@ func (x *GetAssetLikesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetLikesResponse.ProtoReflect.Descriptor instead. func (*GetAssetLikesResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{32} + return file_asset_proto_rawDescGZIP(), []int{34} } func (x *GetAssetLikesResponse) GetBase() *common.BaseResponse { @@ -2420,7 +2566,7 @@ type GetAssetForRPCRequest struct { func (x *GetAssetForRPCRequest) Reset() { *x = GetAssetForRPCRequest{} - mi := &file_asset_proto_msgTypes[33] + mi := &file_asset_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2432,7 +2578,7 @@ func (x *GetAssetForRPCRequest) String() string { func (*GetAssetForRPCRequest) ProtoMessage() {} func (x *GetAssetForRPCRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[33] + mi := &file_asset_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2445,7 +2591,7 @@ func (x *GetAssetForRPCRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetForRPCRequest.ProtoReflect.Descriptor instead. func (*GetAssetForRPCRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{33} + return file_asset_proto_rawDescGZIP(), []int{35} } func (x *GetAssetForRPCRequest) GetAssetId() int64 { @@ -2470,7 +2616,7 @@ type GetAssetForRPCResponse struct { func (x *GetAssetForRPCResponse) Reset() { *x = GetAssetForRPCResponse{} - mi := &file_asset_proto_msgTypes[34] + mi := &file_asset_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2482,7 +2628,7 @@ func (x *GetAssetForRPCResponse) String() string { func (*GetAssetForRPCResponse) ProtoMessage() {} func (x *GetAssetForRPCResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[34] + mi := &file_asset_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2495,7 +2641,7 @@ func (x *GetAssetForRPCResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetForRPCResponse.ProtoReflect.Descriptor instead. func (*GetAssetForRPCResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{34} + return file_asset_proto_rawDescGZIP(), []int{36} } func (x *GetAssetForRPCResponse) GetBase() *common.BaseResponse { @@ -2560,7 +2706,7 @@ type Material struct { func (x *Material) Reset() { *x = Material{} - mi := &file_asset_proto_msgTypes[35] + mi := &file_asset_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2572,7 +2718,7 @@ func (x *Material) String() string { func (*Material) ProtoMessage() {} func (x *Material) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[35] + mi := &file_asset_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2585,7 +2731,7 @@ func (x *Material) ProtoReflect() protoreflect.Message { // Deprecated: Use Material.ProtoReflect.Descriptor instead. func (*Material) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{35} + return file_asset_proto_rawDescGZIP(), []int{37} } func (x *Material) GetMaterialId() int64 { @@ -2686,7 +2832,7 @@ type AssetMaterialRelation struct { func (x *AssetMaterialRelation) Reset() { *x = AssetMaterialRelation{} - mi := &file_asset_proto_msgTypes[36] + mi := &file_asset_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2698,7 +2844,7 @@ func (x *AssetMaterialRelation) String() string { func (*AssetMaterialRelation) ProtoMessage() {} func (x *AssetMaterialRelation) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[36] + mi := &file_asset_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2711,7 +2857,7 @@ func (x *AssetMaterialRelation) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetMaterialRelation.ProtoReflect.Descriptor instead. func (*AssetMaterialRelation) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{36} + return file_asset_proto_rawDescGZIP(), []int{38} } func (x *AssetMaterialRelation) GetRelationId() int64 { @@ -2815,7 +2961,7 @@ type UploadMaterialRequest struct { func (x *UploadMaterialRequest) Reset() { *x = UploadMaterialRequest{} - mi := &file_asset_proto_msgTypes[37] + mi := &file_asset_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2827,7 +2973,7 @@ func (x *UploadMaterialRequest) String() string { func (*UploadMaterialRequest) ProtoMessage() {} func (x *UploadMaterialRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[37] + mi := &file_asset_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2840,7 +2986,7 @@ func (x *UploadMaterialRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UploadMaterialRequest.ProtoReflect.Descriptor instead. func (*UploadMaterialRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{37} + return file_asset_proto_rawDescGZIP(), []int{39} } func (x *UploadMaterialRequest) GetOssKey() string { @@ -2909,7 +3055,7 @@ type UploadMaterialResponse struct { func (x *UploadMaterialResponse) Reset() { *x = UploadMaterialResponse{} - mi := &file_asset_proto_msgTypes[38] + mi := &file_asset_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2921,7 +3067,7 @@ func (x *UploadMaterialResponse) String() string { func (*UploadMaterialResponse) ProtoMessage() {} func (x *UploadMaterialResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[38] + mi := &file_asset_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2934,7 +3080,7 @@ func (x *UploadMaterialResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UploadMaterialResponse.ProtoReflect.Descriptor instead. func (*UploadMaterialResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{38} + return file_asset_proto_rawDescGZIP(), []int{40} } func (x *UploadMaterialResponse) GetBase() *common.BaseResponse { @@ -2962,7 +3108,7 @@ type BindAssetMaterialsRequest struct { func (x *BindAssetMaterialsRequest) Reset() { *x = BindAssetMaterialsRequest{} - mi := &file_asset_proto_msgTypes[39] + mi := &file_asset_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2974,7 +3120,7 @@ func (x *BindAssetMaterialsRequest) String() string { func (*BindAssetMaterialsRequest) ProtoMessage() {} func (x *BindAssetMaterialsRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[39] + mi := &file_asset_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2987,7 +3133,7 @@ func (x *BindAssetMaterialsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BindAssetMaterialsRequest.ProtoReflect.Descriptor instead. func (*BindAssetMaterialsRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{39} + return file_asset_proto_rawDescGZIP(), []int{41} } func (x *BindAssetMaterialsRequest) GetAssetId() int64 { @@ -3013,7 +3159,7 @@ type BindAssetMaterialsResponse struct { func (x *BindAssetMaterialsResponse) Reset() { *x = BindAssetMaterialsResponse{} - mi := &file_asset_proto_msgTypes[40] + mi := &file_asset_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3025,7 +3171,7 @@ func (x *BindAssetMaterialsResponse) String() string { func (*BindAssetMaterialsResponse) ProtoMessage() {} func (x *BindAssetMaterialsResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[40] + mi := &file_asset_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3038,7 +3184,7 @@ func (x *BindAssetMaterialsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BindAssetMaterialsResponse.ProtoReflect.Descriptor instead. func (*BindAssetMaterialsResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{40} + return file_asset_proto_rawDescGZIP(), []int{42} } func (x *BindAssetMaterialsResponse) GetBase() *common.BaseResponse { @@ -3058,7 +3204,7 @@ type GetAssetMaterialsRequest struct { func (x *GetAssetMaterialsRequest) Reset() { *x = GetAssetMaterialsRequest{} - mi := &file_asset_proto_msgTypes[41] + mi := &file_asset_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3070,7 +3216,7 @@ func (x *GetAssetMaterialsRequest) String() string { func (*GetAssetMaterialsRequest) ProtoMessage() {} func (x *GetAssetMaterialsRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[41] + mi := &file_asset_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3083,7 +3229,7 @@ func (x *GetAssetMaterialsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetMaterialsRequest.ProtoReflect.Descriptor instead. func (*GetAssetMaterialsRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{41} + return file_asset_proto_rawDescGZIP(), []int{43} } func (x *GetAssetMaterialsRequest) GetAssetId() int64 { @@ -3103,7 +3249,7 @@ type GetAssetMaterialsResponse struct { func (x *GetAssetMaterialsResponse) Reset() { *x = GetAssetMaterialsResponse{} - mi := &file_asset_proto_msgTypes[42] + mi := &file_asset_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3115,7 +3261,7 @@ func (x *GetAssetMaterialsResponse) String() string { func (*GetAssetMaterialsResponse) ProtoMessage() {} func (x *GetAssetMaterialsResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[42] + mi := &file_asset_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3128,7 +3274,7 @@ func (x *GetAssetMaterialsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAssetMaterialsResponse.ProtoReflect.Descriptor instead. func (*GetAssetMaterialsResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{42} + return file_asset_proto_rawDescGZIP(), []int{44} } func (x *GetAssetMaterialsResponse) GetBase() *common.BaseResponse { @@ -3156,7 +3302,7 @@ type UpdateMaterialLayerOrderRequest struct { func (x *UpdateMaterialLayerOrderRequest) Reset() { *x = UpdateMaterialLayerOrderRequest{} - mi := &file_asset_proto_msgTypes[43] + mi := &file_asset_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3168,7 +3314,7 @@ func (x *UpdateMaterialLayerOrderRequest) String() string { func (*UpdateMaterialLayerOrderRequest) ProtoMessage() {} func (x *UpdateMaterialLayerOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[43] + mi := &file_asset_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3181,7 +3327,7 @@ func (x *UpdateMaterialLayerOrderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateMaterialLayerOrderRequest.ProtoReflect.Descriptor instead. func (*UpdateMaterialLayerOrderRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{43} + return file_asset_proto_rawDescGZIP(), []int{45} } func (x *UpdateMaterialLayerOrderRequest) GetAssetId() int64 { @@ -3208,7 +3354,7 @@ type MaterialLayerOrderItem struct { func (x *MaterialLayerOrderItem) Reset() { *x = MaterialLayerOrderItem{} - mi := &file_asset_proto_msgTypes[44] + mi := &file_asset_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3220,7 +3366,7 @@ func (x *MaterialLayerOrderItem) String() string { func (*MaterialLayerOrderItem) ProtoMessage() {} func (x *MaterialLayerOrderItem) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[44] + mi := &file_asset_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3233,7 +3379,7 @@ func (x *MaterialLayerOrderItem) ProtoReflect() protoreflect.Message { // Deprecated: Use MaterialLayerOrderItem.ProtoReflect.Descriptor instead. func (*MaterialLayerOrderItem) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{44} + return file_asset_proto_rawDescGZIP(), []int{46} } func (x *MaterialLayerOrderItem) GetRelationId() int64 { @@ -3259,7 +3405,7 @@ type UpdateMaterialLayerOrderResponse struct { func (x *UpdateMaterialLayerOrderResponse) Reset() { *x = UpdateMaterialLayerOrderResponse{} - mi := &file_asset_proto_msgTypes[45] + mi := &file_asset_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3271,7 +3417,7 @@ func (x *UpdateMaterialLayerOrderResponse) String() string { func (*UpdateMaterialLayerOrderResponse) ProtoMessage() {} func (x *UpdateMaterialLayerOrderResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[45] + mi := &file_asset_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3284,7 +3430,7 @@ func (x *UpdateMaterialLayerOrderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateMaterialLayerOrderResponse.ProtoReflect.Descriptor instead. func (*UpdateMaterialLayerOrderResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{45} + return file_asset_proto_rawDescGZIP(), []int{47} } func (x *UpdateMaterialLayerOrderResponse) GetBase() *common.BaseResponse { @@ -3304,7 +3450,7 @@ type UnbindAssetMaterialRequest struct { func (x *UnbindAssetMaterialRequest) Reset() { *x = UnbindAssetMaterialRequest{} - mi := &file_asset_proto_msgTypes[46] + mi := &file_asset_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3316,7 +3462,7 @@ func (x *UnbindAssetMaterialRequest) String() string { func (*UnbindAssetMaterialRequest) ProtoMessage() {} func (x *UnbindAssetMaterialRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[46] + mi := &file_asset_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3329,7 +3475,7 @@ func (x *UnbindAssetMaterialRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnbindAssetMaterialRequest.ProtoReflect.Descriptor instead. func (*UnbindAssetMaterialRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{46} + return file_asset_proto_rawDescGZIP(), []int{48} } func (x *UnbindAssetMaterialRequest) GetRelationId() int64 { @@ -3348,7 +3494,7 @@ type UnbindAssetMaterialResponse struct { func (x *UnbindAssetMaterialResponse) Reset() { *x = UnbindAssetMaterialResponse{} - mi := &file_asset_proto_msgTypes[47] + mi := &file_asset_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3360,7 +3506,7 @@ func (x *UnbindAssetMaterialResponse) String() string { func (*UnbindAssetMaterialResponse) ProtoMessage() {} func (x *UnbindAssetMaterialResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[47] + mi := &file_asset_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3373,7 +3519,7 @@ func (x *UnbindAssetMaterialResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnbindAssetMaterialResponse.ProtoReflect.Descriptor instead. func (*UnbindAssetMaterialResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{47} + return file_asset_proto_rawDescGZIP(), []int{49} } func (x *UnbindAssetMaterialResponse) GetBase() *common.BaseResponse { @@ -3393,7 +3539,7 @@ type ClearAssetLikeRecordsRequest struct { func (x *ClearAssetLikeRecordsRequest) Reset() { *x = ClearAssetLikeRecordsRequest{} - mi := &file_asset_proto_msgTypes[48] + mi := &file_asset_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3405,7 +3551,7 @@ func (x *ClearAssetLikeRecordsRequest) String() string { func (*ClearAssetLikeRecordsRequest) ProtoMessage() {} func (x *ClearAssetLikeRecordsRequest) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[48] + mi := &file_asset_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3418,7 +3564,7 @@ func (x *ClearAssetLikeRecordsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearAssetLikeRecordsRequest.ProtoReflect.Descriptor instead. func (*ClearAssetLikeRecordsRequest) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{48} + return file_asset_proto_rawDescGZIP(), []int{50} } func (x *ClearAssetLikeRecordsRequest) GetAssetId() int64 { @@ -3438,7 +3584,7 @@ type ClearAssetLikeRecordsResponse struct { func (x *ClearAssetLikeRecordsResponse) Reset() { *x = ClearAssetLikeRecordsResponse{} - mi := &file_asset_proto_msgTypes[49] + mi := &file_asset_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3450,7 +3596,7 @@ func (x *ClearAssetLikeRecordsResponse) String() string { func (*ClearAssetLikeRecordsResponse) ProtoMessage() {} func (x *ClearAssetLikeRecordsResponse) ProtoReflect() protoreflect.Message { - mi := &file_asset_proto_msgTypes[49] + mi := &file_asset_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3463,7 +3609,7 @@ func (x *ClearAssetLikeRecordsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClearAssetLikeRecordsResponse.ProtoReflect.Descriptor instead. func (*ClearAssetLikeRecordsResponse) Descriptor() ([]byte, []int) { - return file_asset_proto_rawDescGZIP(), []int{49} + return file_asset_proto_rawDescGZIP(), []int{51} } func (x *ClearAssetLikeRecordsResponse) GetBase() *common.BaseResponse { @@ -3557,11 +3703,23 @@ const file_asset_proto_rawDesc = "" + "\x05event\x18\x05 \x01(\tR\x05event\"~\n" + "\x1aPreCreateMintOrderResponse\x120\n" + "\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12.\n" + - "\x05order\x18\x02 \x01(\v2\x18.topfans.asset.MintOrderR\x05order\"\xa7\x01\n" + + "\x05order\x18\x02 \x01(\v2\x18.topfans.asset.MintOrderR\x05order\"\xef\x01\n" + "\x17CreateMintOrderResponse\x120\n" + "\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12.\n" + "\x05order\x18\x02 \x01(\v2\x18.topfans.asset.MintOrderR\x05order\x12*\n" + - "\x05asset\x18\x03 \x01(\v2\x14.topfans.asset.AssetR\x05asset\"\x8b\x01\n" + + "\x05asset\x18\x03 \x01(\v2\x14.topfans.asset.AssetR\x05asset\x12!\n" + + "\fcost_crystal\x18\x04 \x01(\x03R\vcostCrystal\x12#\n" + + "\rbalance_after\x18\x05 \x01(\x03R\fbalanceAfter\"4\n" + + "\x17EstimateMintCostRequest\x12\x19\n" + + "\btrace_id\x18\x01 \x01(\tR\atraceId\"\x82\x02\n" + + "\x18EstimateMintCostResponse\x120\n" + + "\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x12!\n" + + "\fcost_crystal\x18\x02 \x01(\x03R\vcostCrystal\x12'\n" + + "\x0fcurrent_balance\x18\x03 \x01(\x03R\x0ecurrentBalance\x12#\n" + + "\rbalance_after\x18\x04 \x01(\x03R\fbalanceAfter\x12\x1d\n" + + "\n" + + "mint_count\x18\x05 \x01(\x05R\tmintCount\x12$\n" + + "\x0enext_tier_hint\x18\x06 \x01(\tR\fnextTierHint\"\x8b\x01\n" + "\x12GetMyAssetsRequest\x12\x12\n" + "\x04page\x18\x01 \x01(\x05R\x04page\x12\x1b\n" + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x16\n" + @@ -3759,11 +3917,12 @@ const file_asset_proto_rawDesc = "" + "\x1cClearAssetLikeRecordsRequest\x12\x19\n" + "\basset_id\x18\x01 \x01(\x03R\aassetId\"Q\n" + "\x1dClearAssetLikeRecordsResponse\x120\n" + - "\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base2\xd0\x10\n" + + "\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base2\xe5\x11\n" + "\fAssetService\x12Z\n" + "\rInitMintOrder\x12#.topfans.asset.InitMintOrderRequest\x1a$.topfans.asset.InitMintOrderResponse\x12\x94\x01\n" + "\x12PreCreateMintOrder\x12(.topfans.asset.PreCreateMintOrderRequest\x1a).topfans.asset.PreCreateMintOrderResponse\")\x82\xd3\xe4\x93\x02#:\x01*\"\x1e/api/v1/assets/mints/precreate\x12\x81\x01\n" + - "\x0fCreateMintOrder\x12%.topfans.asset.CreateMintOrderRequest\x1a&.topfans.asset.CreateMintOrderResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/api/v1/assets/mints\x12o\n" + + "\x0fCreateMintOrder\x12%.topfans.asset.CreateMintOrderRequest\x1a&.topfans.asset.CreateMintOrderResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/api/v1/assets/mints\x12\x92\x01\n" + + "\x10EstimateMintCost\x12&.topfans.asset.EstimateMintCostRequest\x1a'.topfans.asset.EstimateMintCostResponse\"-\x82\xd3\xe4\x93\x02':\x01*\"\"/api/v1/assets/mints/cost-estimate\x12o\n" + "\vGetMyAssets\x12!.topfans.asset.GetMyAssetsRequest\x1a\".topfans.asset.GetMyAssetsResponse\"\x19\x82\xd3\xe4\x93\x02\x13\x12\x11/api/v1/assets/me\x12n\n" + "\bGetAsset\x12\x1e.topfans.asset.GetAssetRequest\x1a\x1f.topfans.asset.GetAssetResponse\"!\x82\xd3\xe4\x93\x02\x1b\x12\x19/api/v1/assets/{asset_id}\x12\x87\x01\n" + "\x0eGetAssetStatus\x12$.topfans.asset.GetAssetStatusRequest\x1a%.topfans.asset.GetAssetStatusResponse\"(\x82\xd3\xe4\x93\x02\"\x12 /api/v1/assets/{asset_id}/status\x12\x80\x01\n" + @@ -3793,7 +3952,7 @@ func file_asset_proto_rawDescGZIP() []byte { return file_asset_proto_rawDescData } -var file_asset_proto_msgTypes = make([]protoimpl.MessageInfo, 50) +var file_asset_proto_msgTypes = make([]protoimpl.MessageInfo, 52) var file_asset_proto_goTypes = []any{ (*Asset)(nil), // 0: topfans.asset.Asset (*OwnerInfo)(nil), // 1: topfans.asset.OwnerInfo @@ -3804,130 +3963,135 @@ var file_asset_proto_goTypes = []any{ (*PreCreateMintOrderRequest)(nil), // 6: topfans.asset.PreCreateMintOrderRequest (*PreCreateMintOrderResponse)(nil), // 7: topfans.asset.PreCreateMintOrderResponse (*CreateMintOrderResponse)(nil), // 8: topfans.asset.CreateMintOrderResponse - (*GetMyAssetsRequest)(nil), // 9: topfans.asset.GetMyAssetsRequest - (*GetMyAssetsResponse)(nil), // 10: topfans.asset.GetMyAssetsResponse - (*AssetListData)(nil), // 11: topfans.asset.AssetListData - (*AssetGroup)(nil), // 12: topfans.asset.AssetGroup - (*GradeSection)(nil), // 13: topfans.asset.GradeSection - (*AssetItem)(nil), // 14: topfans.asset.AssetItem - (*AssetListItem)(nil), // 15: topfans.asset.AssetListItem - (*GetAssetRequest)(nil), // 16: topfans.asset.GetAssetRequest - (*GetAssetResponse)(nil), // 17: topfans.asset.GetAssetResponse - (*GetAssetStatusRequest)(nil), // 18: topfans.asset.GetAssetStatusRequest - (*GetAssetStatusResponse)(nil), // 19: topfans.asset.GetAssetStatusResponse - (*GetMintOrderRequest)(nil), // 20: topfans.asset.GetMintOrderRequest - (*GetMintOrderResponse)(nil), // 21: topfans.asset.GetMintOrderResponse - (*CancelMintOrderRequest)(nil), // 22: topfans.asset.CancelMintOrderRequest - (*CancelMintOrderResponse)(nil), // 23: topfans.asset.CancelMintOrderResponse - (*LikeAssetRequest)(nil), // 24: topfans.asset.LikeAssetRequest - (*LikeAssetResponse)(nil), // 25: topfans.asset.LikeAssetResponse - (*UnlikeAssetRequest)(nil), // 26: topfans.asset.UnlikeAssetRequest - (*UnlikeAssetResponse)(nil), // 27: topfans.asset.UnlikeAssetResponse - (*CheckAssetLikeRequest)(nil), // 28: topfans.asset.CheckAssetLikeRequest - (*CheckAssetLikeResponse)(nil), // 29: topfans.asset.CheckAssetLikeResponse - (*AssetLike)(nil), // 30: topfans.asset.AssetLike - (*GetAssetLikesRequest)(nil), // 31: topfans.asset.GetAssetLikesRequest - (*GetAssetLikesResponse)(nil), // 32: topfans.asset.GetAssetLikesResponse - (*GetAssetForRPCRequest)(nil), // 33: topfans.asset.GetAssetForRPCRequest - (*GetAssetForRPCResponse)(nil), // 34: topfans.asset.GetAssetForRPCResponse - (*Material)(nil), // 35: topfans.asset.Material - (*AssetMaterialRelation)(nil), // 36: topfans.asset.AssetMaterialRelation - (*UploadMaterialRequest)(nil), // 37: topfans.asset.UploadMaterialRequest - (*UploadMaterialResponse)(nil), // 38: topfans.asset.UploadMaterialResponse - (*BindAssetMaterialsRequest)(nil), // 39: topfans.asset.BindAssetMaterialsRequest - (*BindAssetMaterialsResponse)(nil), // 40: topfans.asset.BindAssetMaterialsResponse - (*GetAssetMaterialsRequest)(nil), // 41: topfans.asset.GetAssetMaterialsRequest - (*GetAssetMaterialsResponse)(nil), // 42: topfans.asset.GetAssetMaterialsResponse - (*UpdateMaterialLayerOrderRequest)(nil), // 43: topfans.asset.UpdateMaterialLayerOrderRequest - (*MaterialLayerOrderItem)(nil), // 44: topfans.asset.MaterialLayerOrderItem - (*UpdateMaterialLayerOrderResponse)(nil), // 45: topfans.asset.UpdateMaterialLayerOrderResponse - (*UnbindAssetMaterialRequest)(nil), // 46: topfans.asset.UnbindAssetMaterialRequest - (*UnbindAssetMaterialResponse)(nil), // 47: topfans.asset.UnbindAssetMaterialResponse - (*ClearAssetLikeRecordsRequest)(nil), // 48: topfans.asset.ClearAssetLikeRecordsRequest - (*ClearAssetLikeRecordsResponse)(nil), // 49: topfans.asset.ClearAssetLikeRecordsResponse - (*common.BaseResponse)(nil), // 50: topfans.common.BaseResponse + (*EstimateMintCostRequest)(nil), // 9: topfans.asset.EstimateMintCostRequest + (*EstimateMintCostResponse)(nil), // 10: topfans.asset.EstimateMintCostResponse + (*GetMyAssetsRequest)(nil), // 11: topfans.asset.GetMyAssetsRequest + (*GetMyAssetsResponse)(nil), // 12: topfans.asset.GetMyAssetsResponse + (*AssetListData)(nil), // 13: topfans.asset.AssetListData + (*AssetGroup)(nil), // 14: topfans.asset.AssetGroup + (*GradeSection)(nil), // 15: topfans.asset.GradeSection + (*AssetItem)(nil), // 16: topfans.asset.AssetItem + (*AssetListItem)(nil), // 17: topfans.asset.AssetListItem + (*GetAssetRequest)(nil), // 18: topfans.asset.GetAssetRequest + (*GetAssetResponse)(nil), // 19: topfans.asset.GetAssetResponse + (*GetAssetStatusRequest)(nil), // 20: topfans.asset.GetAssetStatusRequest + (*GetAssetStatusResponse)(nil), // 21: topfans.asset.GetAssetStatusResponse + (*GetMintOrderRequest)(nil), // 22: topfans.asset.GetMintOrderRequest + (*GetMintOrderResponse)(nil), // 23: topfans.asset.GetMintOrderResponse + (*CancelMintOrderRequest)(nil), // 24: topfans.asset.CancelMintOrderRequest + (*CancelMintOrderResponse)(nil), // 25: topfans.asset.CancelMintOrderResponse + (*LikeAssetRequest)(nil), // 26: topfans.asset.LikeAssetRequest + (*LikeAssetResponse)(nil), // 27: topfans.asset.LikeAssetResponse + (*UnlikeAssetRequest)(nil), // 28: topfans.asset.UnlikeAssetRequest + (*UnlikeAssetResponse)(nil), // 29: topfans.asset.UnlikeAssetResponse + (*CheckAssetLikeRequest)(nil), // 30: topfans.asset.CheckAssetLikeRequest + (*CheckAssetLikeResponse)(nil), // 31: topfans.asset.CheckAssetLikeResponse + (*AssetLike)(nil), // 32: topfans.asset.AssetLike + (*GetAssetLikesRequest)(nil), // 33: topfans.asset.GetAssetLikesRequest + (*GetAssetLikesResponse)(nil), // 34: topfans.asset.GetAssetLikesResponse + (*GetAssetForRPCRequest)(nil), // 35: topfans.asset.GetAssetForRPCRequest + (*GetAssetForRPCResponse)(nil), // 36: topfans.asset.GetAssetForRPCResponse + (*Material)(nil), // 37: topfans.asset.Material + (*AssetMaterialRelation)(nil), // 38: topfans.asset.AssetMaterialRelation + (*UploadMaterialRequest)(nil), // 39: topfans.asset.UploadMaterialRequest + (*UploadMaterialResponse)(nil), // 40: topfans.asset.UploadMaterialResponse + (*BindAssetMaterialsRequest)(nil), // 41: topfans.asset.BindAssetMaterialsRequest + (*BindAssetMaterialsResponse)(nil), // 42: topfans.asset.BindAssetMaterialsResponse + (*GetAssetMaterialsRequest)(nil), // 43: topfans.asset.GetAssetMaterialsRequest + (*GetAssetMaterialsResponse)(nil), // 44: topfans.asset.GetAssetMaterialsResponse + (*UpdateMaterialLayerOrderRequest)(nil), // 45: topfans.asset.UpdateMaterialLayerOrderRequest + (*MaterialLayerOrderItem)(nil), // 46: topfans.asset.MaterialLayerOrderItem + (*UpdateMaterialLayerOrderResponse)(nil), // 47: topfans.asset.UpdateMaterialLayerOrderResponse + (*UnbindAssetMaterialRequest)(nil), // 48: topfans.asset.UnbindAssetMaterialRequest + (*UnbindAssetMaterialResponse)(nil), // 49: topfans.asset.UnbindAssetMaterialResponse + (*ClearAssetLikeRecordsRequest)(nil), // 50: topfans.asset.ClearAssetLikeRecordsRequest + (*ClearAssetLikeRecordsResponse)(nil), // 51: topfans.asset.ClearAssetLikeRecordsResponse + (*common.BaseResponse)(nil), // 52: topfans.common.BaseResponse } var file_asset_proto_depIdxs = []int32{ 1, // 0: topfans.asset.Asset.owner:type_name -> topfans.asset.OwnerInfo - 50, // 1: topfans.asset.InitMintOrderResponse.base:type_name -> topfans.common.BaseResponse + 52, // 1: topfans.asset.InitMintOrderResponse.base:type_name -> topfans.common.BaseResponse 2, // 2: topfans.asset.InitMintOrderResponse.order:type_name -> topfans.asset.MintOrder - 50, // 3: topfans.asset.PreCreateMintOrderResponse.base:type_name -> topfans.common.BaseResponse + 52, // 3: topfans.asset.PreCreateMintOrderResponse.base:type_name -> topfans.common.BaseResponse 2, // 4: topfans.asset.PreCreateMintOrderResponse.order:type_name -> topfans.asset.MintOrder - 50, // 5: topfans.asset.CreateMintOrderResponse.base:type_name -> topfans.common.BaseResponse + 52, // 5: topfans.asset.CreateMintOrderResponse.base:type_name -> topfans.common.BaseResponse 2, // 6: topfans.asset.CreateMintOrderResponse.order:type_name -> topfans.asset.MintOrder 0, // 7: topfans.asset.CreateMintOrderResponse.asset:type_name -> topfans.asset.Asset - 50, // 8: topfans.asset.GetMyAssetsResponse.base:type_name -> topfans.common.BaseResponse - 11, // 9: topfans.asset.GetMyAssetsResponse.data:type_name -> topfans.asset.AssetListData - 12, // 10: topfans.asset.AssetListData.groups:type_name -> topfans.asset.AssetGroup - 13, // 11: topfans.asset.AssetGroup.grades:type_name -> topfans.asset.GradeSection - 14, // 12: topfans.asset.AssetGroup.items:type_name -> topfans.asset.AssetItem - 14, // 13: topfans.asset.GradeSection.items:type_name -> topfans.asset.AssetItem - 50, // 14: topfans.asset.GetAssetResponse.base:type_name -> topfans.common.BaseResponse - 0, // 15: topfans.asset.GetAssetResponse.asset:type_name -> topfans.asset.Asset - 50, // 16: topfans.asset.GetAssetStatusResponse.base:type_name -> topfans.common.BaseResponse - 50, // 17: topfans.asset.GetMintOrderResponse.base:type_name -> topfans.common.BaseResponse - 2, // 18: topfans.asset.GetMintOrderResponse.order:type_name -> topfans.asset.MintOrder - 0, // 19: topfans.asset.GetMintOrderResponse.asset:type_name -> topfans.asset.Asset - 50, // 20: topfans.asset.CancelMintOrderResponse.base:type_name -> topfans.common.BaseResponse - 50, // 21: topfans.asset.LikeAssetResponse.base:type_name -> topfans.common.BaseResponse - 50, // 22: topfans.asset.UnlikeAssetResponse.base:type_name -> topfans.common.BaseResponse - 50, // 23: topfans.asset.CheckAssetLikeResponse.base:type_name -> topfans.common.BaseResponse - 50, // 24: topfans.asset.GetAssetLikesResponse.base:type_name -> topfans.common.BaseResponse - 30, // 25: topfans.asset.GetAssetLikesResponse.likes:type_name -> topfans.asset.AssetLike - 50, // 26: topfans.asset.GetAssetForRPCResponse.base:type_name -> topfans.common.BaseResponse - 50, // 27: topfans.asset.UploadMaterialResponse.base:type_name -> topfans.common.BaseResponse - 35, // 28: topfans.asset.UploadMaterialResponse.material:type_name -> topfans.asset.Material - 36, // 29: topfans.asset.BindAssetMaterialsRequest.materials:type_name -> topfans.asset.AssetMaterialRelation - 50, // 30: topfans.asset.BindAssetMaterialsResponse.base:type_name -> topfans.common.BaseResponse - 50, // 31: topfans.asset.GetAssetMaterialsResponse.base:type_name -> topfans.common.BaseResponse - 36, // 32: topfans.asset.GetAssetMaterialsResponse.materials:type_name -> topfans.asset.AssetMaterialRelation - 44, // 33: topfans.asset.UpdateMaterialLayerOrderRequest.orders:type_name -> topfans.asset.MaterialLayerOrderItem - 50, // 34: topfans.asset.UpdateMaterialLayerOrderResponse.base:type_name -> topfans.common.BaseResponse - 50, // 35: topfans.asset.UnbindAssetMaterialResponse.base:type_name -> topfans.common.BaseResponse - 50, // 36: topfans.asset.ClearAssetLikeRecordsResponse.base:type_name -> topfans.common.BaseResponse - 3, // 37: topfans.asset.AssetService.InitMintOrder:input_type -> topfans.asset.InitMintOrderRequest - 6, // 38: topfans.asset.AssetService.PreCreateMintOrder:input_type -> topfans.asset.PreCreateMintOrderRequest - 5, // 39: topfans.asset.AssetService.CreateMintOrder:input_type -> topfans.asset.CreateMintOrderRequest - 9, // 40: topfans.asset.AssetService.GetMyAssets:input_type -> topfans.asset.GetMyAssetsRequest - 16, // 41: topfans.asset.AssetService.GetAsset:input_type -> topfans.asset.GetAssetRequest - 18, // 42: topfans.asset.AssetService.GetAssetStatus:input_type -> topfans.asset.GetAssetStatusRequest - 20, // 43: topfans.asset.AssetService.GetMintOrder:input_type -> topfans.asset.GetMintOrderRequest - 22, // 44: topfans.asset.AssetService.CancelMintOrder:input_type -> topfans.asset.CancelMintOrderRequest - 33, // 45: topfans.asset.AssetService.GetAssetForRPC:input_type -> topfans.asset.GetAssetForRPCRequest - 24, // 46: topfans.asset.AssetService.LikeAsset:input_type -> topfans.asset.LikeAssetRequest - 26, // 47: topfans.asset.AssetService.UnlikeAsset:input_type -> topfans.asset.UnlikeAssetRequest - 28, // 48: topfans.asset.AssetService.CheckAssetLike:input_type -> topfans.asset.CheckAssetLikeRequest - 31, // 49: topfans.asset.AssetService.GetAssetLikes:input_type -> topfans.asset.GetAssetLikesRequest - 48, // 50: topfans.asset.AssetService.ClearAssetLikeRecords:input_type -> topfans.asset.ClearAssetLikeRecordsRequest - 37, // 51: topfans.asset.AssetService.UploadMaterial:input_type -> topfans.asset.UploadMaterialRequest - 39, // 52: topfans.asset.AssetService.BindAssetMaterials:input_type -> topfans.asset.BindAssetMaterialsRequest - 41, // 53: topfans.asset.AssetService.GetAssetMaterials:input_type -> topfans.asset.GetAssetMaterialsRequest - 43, // 54: topfans.asset.AssetService.UpdateMaterialLayerOrder:input_type -> topfans.asset.UpdateMaterialLayerOrderRequest - 46, // 55: topfans.asset.AssetService.UnbindAssetMaterial:input_type -> topfans.asset.UnbindAssetMaterialRequest - 4, // 56: topfans.asset.AssetService.InitMintOrder:output_type -> topfans.asset.InitMintOrderResponse - 7, // 57: topfans.asset.AssetService.PreCreateMintOrder:output_type -> topfans.asset.PreCreateMintOrderResponse - 8, // 58: topfans.asset.AssetService.CreateMintOrder:output_type -> topfans.asset.CreateMintOrderResponse - 10, // 59: topfans.asset.AssetService.GetMyAssets:output_type -> topfans.asset.GetMyAssetsResponse - 17, // 60: topfans.asset.AssetService.GetAsset:output_type -> topfans.asset.GetAssetResponse - 19, // 61: topfans.asset.AssetService.GetAssetStatus:output_type -> topfans.asset.GetAssetStatusResponse - 21, // 62: topfans.asset.AssetService.GetMintOrder:output_type -> topfans.asset.GetMintOrderResponse - 23, // 63: topfans.asset.AssetService.CancelMintOrder:output_type -> topfans.asset.CancelMintOrderResponse - 34, // 64: topfans.asset.AssetService.GetAssetForRPC:output_type -> topfans.asset.GetAssetForRPCResponse - 25, // 65: topfans.asset.AssetService.LikeAsset:output_type -> topfans.asset.LikeAssetResponse - 27, // 66: topfans.asset.AssetService.UnlikeAsset:output_type -> topfans.asset.UnlikeAssetResponse - 29, // 67: topfans.asset.AssetService.CheckAssetLike:output_type -> topfans.asset.CheckAssetLikeResponse - 32, // 68: topfans.asset.AssetService.GetAssetLikes:output_type -> topfans.asset.GetAssetLikesResponse - 49, // 69: topfans.asset.AssetService.ClearAssetLikeRecords:output_type -> topfans.asset.ClearAssetLikeRecordsResponse - 38, // 70: topfans.asset.AssetService.UploadMaterial:output_type -> topfans.asset.UploadMaterialResponse - 40, // 71: topfans.asset.AssetService.BindAssetMaterials:output_type -> topfans.asset.BindAssetMaterialsResponse - 42, // 72: topfans.asset.AssetService.GetAssetMaterials:output_type -> topfans.asset.GetAssetMaterialsResponse - 45, // 73: topfans.asset.AssetService.UpdateMaterialLayerOrder:output_type -> topfans.asset.UpdateMaterialLayerOrderResponse - 47, // 74: topfans.asset.AssetService.UnbindAssetMaterial:output_type -> topfans.asset.UnbindAssetMaterialResponse - 56, // [56:75] is the sub-list for method output_type - 37, // [37:56] is the sub-list for method input_type - 37, // [37:37] is the sub-list for extension type_name - 37, // [37:37] is the sub-list for extension extendee - 0, // [0:37] is the sub-list for field type_name + 52, // 8: topfans.asset.EstimateMintCostResponse.base:type_name -> topfans.common.BaseResponse + 52, // 9: topfans.asset.GetMyAssetsResponse.base:type_name -> topfans.common.BaseResponse + 13, // 10: topfans.asset.GetMyAssetsResponse.data:type_name -> topfans.asset.AssetListData + 14, // 11: topfans.asset.AssetListData.groups:type_name -> topfans.asset.AssetGroup + 15, // 12: topfans.asset.AssetGroup.grades:type_name -> topfans.asset.GradeSection + 16, // 13: topfans.asset.AssetGroup.items:type_name -> topfans.asset.AssetItem + 16, // 14: topfans.asset.GradeSection.items:type_name -> topfans.asset.AssetItem + 52, // 15: topfans.asset.GetAssetResponse.base:type_name -> topfans.common.BaseResponse + 0, // 16: topfans.asset.GetAssetResponse.asset:type_name -> topfans.asset.Asset + 52, // 17: topfans.asset.GetAssetStatusResponse.base:type_name -> topfans.common.BaseResponse + 52, // 18: topfans.asset.GetMintOrderResponse.base:type_name -> topfans.common.BaseResponse + 2, // 19: topfans.asset.GetMintOrderResponse.order:type_name -> topfans.asset.MintOrder + 0, // 20: topfans.asset.GetMintOrderResponse.asset:type_name -> topfans.asset.Asset + 52, // 21: topfans.asset.CancelMintOrderResponse.base:type_name -> topfans.common.BaseResponse + 52, // 22: topfans.asset.LikeAssetResponse.base:type_name -> topfans.common.BaseResponse + 52, // 23: topfans.asset.UnlikeAssetResponse.base:type_name -> topfans.common.BaseResponse + 52, // 24: topfans.asset.CheckAssetLikeResponse.base:type_name -> topfans.common.BaseResponse + 52, // 25: topfans.asset.GetAssetLikesResponse.base:type_name -> topfans.common.BaseResponse + 32, // 26: topfans.asset.GetAssetLikesResponse.likes:type_name -> topfans.asset.AssetLike + 52, // 27: topfans.asset.GetAssetForRPCResponse.base:type_name -> topfans.common.BaseResponse + 52, // 28: topfans.asset.UploadMaterialResponse.base:type_name -> topfans.common.BaseResponse + 37, // 29: topfans.asset.UploadMaterialResponse.material:type_name -> topfans.asset.Material + 38, // 30: topfans.asset.BindAssetMaterialsRequest.materials:type_name -> topfans.asset.AssetMaterialRelation + 52, // 31: topfans.asset.BindAssetMaterialsResponse.base:type_name -> topfans.common.BaseResponse + 52, // 32: topfans.asset.GetAssetMaterialsResponse.base:type_name -> topfans.common.BaseResponse + 38, // 33: topfans.asset.GetAssetMaterialsResponse.materials:type_name -> topfans.asset.AssetMaterialRelation + 46, // 34: topfans.asset.UpdateMaterialLayerOrderRequest.orders:type_name -> topfans.asset.MaterialLayerOrderItem + 52, // 35: topfans.asset.UpdateMaterialLayerOrderResponse.base:type_name -> topfans.common.BaseResponse + 52, // 36: topfans.asset.UnbindAssetMaterialResponse.base:type_name -> topfans.common.BaseResponse + 52, // 37: topfans.asset.ClearAssetLikeRecordsResponse.base:type_name -> topfans.common.BaseResponse + 3, // 38: topfans.asset.AssetService.InitMintOrder:input_type -> topfans.asset.InitMintOrderRequest + 6, // 39: topfans.asset.AssetService.PreCreateMintOrder:input_type -> topfans.asset.PreCreateMintOrderRequest + 5, // 40: topfans.asset.AssetService.CreateMintOrder:input_type -> topfans.asset.CreateMintOrderRequest + 9, // 41: topfans.asset.AssetService.EstimateMintCost:input_type -> topfans.asset.EstimateMintCostRequest + 11, // 42: topfans.asset.AssetService.GetMyAssets:input_type -> topfans.asset.GetMyAssetsRequest + 18, // 43: topfans.asset.AssetService.GetAsset:input_type -> topfans.asset.GetAssetRequest + 20, // 44: topfans.asset.AssetService.GetAssetStatus:input_type -> topfans.asset.GetAssetStatusRequest + 22, // 45: topfans.asset.AssetService.GetMintOrder:input_type -> topfans.asset.GetMintOrderRequest + 24, // 46: topfans.asset.AssetService.CancelMintOrder:input_type -> topfans.asset.CancelMintOrderRequest + 35, // 47: topfans.asset.AssetService.GetAssetForRPC:input_type -> topfans.asset.GetAssetForRPCRequest + 26, // 48: topfans.asset.AssetService.LikeAsset:input_type -> topfans.asset.LikeAssetRequest + 28, // 49: topfans.asset.AssetService.UnlikeAsset:input_type -> topfans.asset.UnlikeAssetRequest + 30, // 50: topfans.asset.AssetService.CheckAssetLike:input_type -> topfans.asset.CheckAssetLikeRequest + 33, // 51: topfans.asset.AssetService.GetAssetLikes:input_type -> topfans.asset.GetAssetLikesRequest + 50, // 52: topfans.asset.AssetService.ClearAssetLikeRecords:input_type -> topfans.asset.ClearAssetLikeRecordsRequest + 39, // 53: topfans.asset.AssetService.UploadMaterial:input_type -> topfans.asset.UploadMaterialRequest + 41, // 54: topfans.asset.AssetService.BindAssetMaterials:input_type -> topfans.asset.BindAssetMaterialsRequest + 43, // 55: topfans.asset.AssetService.GetAssetMaterials:input_type -> topfans.asset.GetAssetMaterialsRequest + 45, // 56: topfans.asset.AssetService.UpdateMaterialLayerOrder:input_type -> topfans.asset.UpdateMaterialLayerOrderRequest + 48, // 57: topfans.asset.AssetService.UnbindAssetMaterial:input_type -> topfans.asset.UnbindAssetMaterialRequest + 4, // 58: topfans.asset.AssetService.InitMintOrder:output_type -> topfans.asset.InitMintOrderResponse + 7, // 59: topfans.asset.AssetService.PreCreateMintOrder:output_type -> topfans.asset.PreCreateMintOrderResponse + 8, // 60: topfans.asset.AssetService.CreateMintOrder:output_type -> topfans.asset.CreateMintOrderResponse + 10, // 61: topfans.asset.AssetService.EstimateMintCost:output_type -> topfans.asset.EstimateMintCostResponse + 12, // 62: topfans.asset.AssetService.GetMyAssets:output_type -> topfans.asset.GetMyAssetsResponse + 19, // 63: topfans.asset.AssetService.GetAsset:output_type -> topfans.asset.GetAssetResponse + 21, // 64: topfans.asset.AssetService.GetAssetStatus:output_type -> topfans.asset.GetAssetStatusResponse + 23, // 65: topfans.asset.AssetService.GetMintOrder:output_type -> topfans.asset.GetMintOrderResponse + 25, // 66: topfans.asset.AssetService.CancelMintOrder:output_type -> topfans.asset.CancelMintOrderResponse + 36, // 67: topfans.asset.AssetService.GetAssetForRPC:output_type -> topfans.asset.GetAssetForRPCResponse + 27, // 68: topfans.asset.AssetService.LikeAsset:output_type -> topfans.asset.LikeAssetResponse + 29, // 69: topfans.asset.AssetService.UnlikeAsset:output_type -> topfans.asset.UnlikeAssetResponse + 31, // 70: topfans.asset.AssetService.CheckAssetLike:output_type -> topfans.asset.CheckAssetLikeResponse + 34, // 71: topfans.asset.AssetService.GetAssetLikes:output_type -> topfans.asset.GetAssetLikesResponse + 51, // 72: topfans.asset.AssetService.ClearAssetLikeRecords:output_type -> topfans.asset.ClearAssetLikeRecordsResponse + 40, // 73: topfans.asset.AssetService.UploadMaterial:output_type -> topfans.asset.UploadMaterialResponse + 42, // 74: topfans.asset.AssetService.BindAssetMaterials:output_type -> topfans.asset.BindAssetMaterialsResponse + 44, // 75: topfans.asset.AssetService.GetAssetMaterials:output_type -> topfans.asset.GetAssetMaterialsResponse + 47, // 76: topfans.asset.AssetService.UpdateMaterialLayerOrder:output_type -> topfans.asset.UpdateMaterialLayerOrderResponse + 49, // 77: topfans.asset.AssetService.UnbindAssetMaterial:output_type -> topfans.asset.UnbindAssetMaterialResponse + 58, // [58:78] is the sub-list for method output_type + 38, // [38:58] is the sub-list for method input_type + 38, // [38:38] is the sub-list for extension type_name + 38, // [38:38] is the sub-list for extension extendee + 0, // [0:38] is the sub-list for field type_name } func init() { file_asset_proto_init() } @@ -3941,7 +4105,7 @@ func file_asset_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_asset_proto_rawDesc), len(file_asset_proto_rawDesc)), NumEnums: 0, - NumMessages: 50, + NumMessages: 52, NumExtensions: 0, NumServices: 1, }, diff --git a/backend/pkg/proto/asset/asset.triple.go b/backend/pkg/proto/asset/asset.triple.go index a2f18fd..249ea37 100644 --- a/backend/pkg/proto/asset/asset.triple.go +++ b/backend/pkg/proto/asset/asset.triple.go @@ -91,6 +91,7 @@ type AssetService interface { GetAssetMaterials(ctx context.Context, req *GetAssetMaterialsRequest, opts ...client.CallOption) (*GetAssetMaterialsResponse, error) UpdateMaterialLayerOrder(ctx context.Context, req *UpdateMaterialLayerOrderRequest, opts ...client.CallOption) (*UpdateMaterialLayerOrderResponse, error) UnbindAssetMaterial(ctx context.Context, req *UnbindAssetMaterialRequest, opts ...client.CallOption) (*UnbindAssetMaterialResponse, error) + EstimateMintCost(ctx context.Context, req *EstimateMintCostRequest, opts ...client.CallOption) (*EstimateMintCostResponse, error) } // NewAssetService constructs a client for the asset.AssetService service. @@ -265,9 +266,17 @@ func (c *AssetServiceImpl) UnbindAssetMaterial(ctx context.Context, req *UnbindA return resp, nil } +func (c *AssetServiceImpl) EstimateMintCost(ctx context.Context, req *EstimateMintCostRequest, opts ...client.CallOption) (*EstimateMintCostResponse, error) { + resp := new(EstimateMintCostResponse) + if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "EstimateMintCost", opts...); err != nil { + return nil, err + } + return resp, nil +} + var AssetService_ClientInfo = client.ClientInfo{ InterfaceName: "topfans.asset.AssetService", - MethodNames: []string{"InitMintOrder", "PreCreateMintOrder", "CreateMintOrder", "GetMyAssets", "GetAsset", "GetAssetStatus", "GetMintOrder", "CancelMintOrder", "GetAssetForRPC", "LikeAsset", "UnlikeAsset", "CheckAssetLike", "GetAssetLikes", "ClearAssetLikeRecords", "UploadMaterial", "BindAssetMaterials", "GetAssetMaterials", "UpdateMaterialLayerOrder", "UnbindAssetMaterial"}, + MethodNames: []string{"InitMintOrder", "PreCreateMintOrder", "CreateMintOrder", "GetMyAssets", "GetAsset", "GetAssetStatus", "GetMintOrder", "CancelMintOrder", "GetAssetForRPC", "LikeAsset", "UnlikeAsset", "CheckAssetLike", "GetAssetLikes", "ClearAssetLikeRecords", "UploadMaterial", "BindAssetMaterials", "GetAssetMaterials", "UpdateMaterialLayerOrder", "UnbindAssetMaterial", "EstimateMintCost"}, ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) { dubboCli := dubboCliRaw.(*AssetServiceImpl) dubboCli.conn = conn @@ -295,6 +304,7 @@ type AssetServiceHandler interface { GetAssetMaterials(context.Context, *GetAssetMaterialsRequest) (*GetAssetMaterialsResponse, error) UpdateMaterialLayerOrder(context.Context, *UpdateMaterialLayerOrderRequest) (*UpdateMaterialLayerOrderResponse, error) UnbindAssetMaterial(context.Context, *UnbindAssetMaterialRequest) (*UnbindAssetMaterialResponse, error) + EstimateMintCost(context.Context, *EstimateMintCostRequest) (*EstimateMintCostResponse, error) } func RegisterAssetServiceHandler(srv *server.Server, hdlr AssetServiceHandler, opts ...server.ServiceOption) error { @@ -594,5 +604,20 @@ var AssetService_ServiceInfo = server.ServiceInfo{ return triple_protocol.NewResponse(res), nil }, }, + { + Name: "EstimateMintCost", + Type: constant.CallUnary, + ReqInitFunc: func() interface{} { + return new(EstimateMintCostRequest) + }, + MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) { + req := args[0].(*EstimateMintCostRequest) + res, err := handler.(AssetServiceHandler).EstimateMintCost(ctx, req) + if err != nil { + return nil, err + } + return triple_protocol.NewResponse(res), nil + }, + }, }, } diff --git a/backend/proto/asset.proto b/backend/proto/asset.proto index fcdb16d..0d50599 100644 --- a/backend/proto/asset.proto +++ b/backend/proto/asset.proto @@ -112,6 +112,23 @@ message CreateMintOrderResponse { topfans.common.BaseResponse base = 1; MintOrder order = 2; // 创建的订单信息 Asset asset = 3; // 创建的资产信息 + int64 cost_crystal = 4; // 本次铸造消耗水晶 + int64 balance_after = 5; // 铸造后余额 +} + +// 估算铸造费用请求 +message EstimateMintCostRequest { + string trace_id = 1; // 追踪ID(可选,用于调试) +} + +// 估算铸造费用响应 +message EstimateMintCostResponse { + topfans.common.BaseResponse base = 1; + int64 cost_crystal = 2; // 本次铸造消耗水晶 + int64 current_balance = 3; // 当前余额(铸造前) + int64 balance_after = 4; // 铸造后余额 + int32 mint_count = 5; // 本次是第几次铸造 + string next_tier_hint = 6; // 下一阶梯提示 } // ==================== 资产查询相关消息 ==================== @@ -428,6 +445,14 @@ service AssetService { }; } + // 估算铸造费用(在确认铸造前显示消耗和余额) + rpc EstimateMintCost(EstimateMintCostRequest) returns (EstimateMintCostResponse) { + option (google.api.http) = { + post: "/api/v1/assets/mints/cost-estimate" + body: "*" + }; + } + // 获取我的藏品列表 rpc GetMyAssets(GetMyAssetsRequest) returns (GetMyAssetsResponse) { option (google.api.http) = { diff --git a/backend/services/assetService/provider/asset_provider.go b/backend/services/assetService/provider/asset_provider.go index 80da8c3..c2d5e1b 100644 --- a/backend/services/assetService/provider/asset_provider.go +++ b/backend/services/assetService/provider/asset_provider.go @@ -816,3 +816,43 @@ func (p *AssetProvider) CancelMintOrder(ctx context.Context, req *pb.CancelMintO return resp, nil } + +// EstimateMintCost 估算铸造费用 +func (p *AssetProvider) EstimateMintCost(ctx context.Context, req *pb.EstimateMintCostRequest) (*pb.EstimateMintCostResponse, error) { + userID, starID, err := extractUserInfoFromDubboAttachments(ctx) + if err != nil { + return &pb.EstimateMintCostResponse{ + Base: &pbCommon.BaseResponse{ + Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, + Message: "user authentication required", + }, + }, err + } + + estimate, err := p.mintService.EstimateMintCost(userID, starID) + if err != nil { + logger.Logger.Error("EstimateMintCost failed", + zap.Int64("user_id", userID), + zap.Int64("star_id", starID), + zap.Error(err), + ) + return &pb.EstimateMintCostResponse{ + Base: &pbCommon.BaseResponse{ + Code: appErrors.ToStatusCode(err), + Message: err.Error(), + }, + }, err + } + + return &pb.EstimateMintCostResponse{ + Base: &pbCommon.BaseResponse{ + Code: pbCommon.StatusCode_STATUS_OK, + Message: "ok", + }, + CostCrystal: estimate.CostCrystal, + CurrentBalance: estimate.CurrentBalance, + BalanceAfter: estimate.AfterBalance, + MintCount: estimate.MintCount, + NextTierHint: estimate.NextTierHint, + }, nil +} diff --git a/backend/services/assetService/provider/material_provider.go b/backend/services/assetService/provider/material_provider.go index aebc116..2c3f174 100644 --- a/backend/services/assetService/provider/material_provider.go +++ b/backend/services/assetService/provider/material_provider.go @@ -2,7 +2,13 @@ package provider import ( "context" + "fmt" + "net/url" + "os" + "strings" + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/aliyun/credentials-go/credentials" pb "github.com/topfans/backend/pkg/proto/asset" pbCommon "github.com/topfans/backend/pkg/proto/common" "github.com/topfans/backend/pkg/models" @@ -121,6 +127,13 @@ func (p *AssetProvider) GetAssetMaterials(ctx context.Context, req *pb.GetAssetM MaterialType: rel.MaterialType, LayerOrder: int32(rel.LayerOrder), } + // 如果关联的 Material 有 OssKey,生成预签名 URL + if rel.Material.OssKey != "" { + signedURL, err := generatePresignedURL(rel.Material.OssKey, 3600) + if err == nil { + pbRel.MaterialUrlSigned = signedURL + } + } if rel.PosX != nil { pbRel.PosX = *rel.PosX } @@ -197,3 +210,75 @@ func doublePtr(v float64) *float64 { } return &v } + +// generatePresignedURL 生成预签名 URL +func generatePresignedURL(filePath string, expiresInSeconds int64) (string, error) { + region := os.Getenv("OSS_REGION") + bucketName := os.Getenv("OSS_BUCKET_NAME") + roleArn := os.Getenv("OSS_STS_ROLE_ARN") + accessKeyID := os.Getenv("OSS_ACCESS_KEY_ID") + accessKeySecret := os.Getenv("OSS_ACCESS_KEY_SECRET") + + if region == "" || bucketName == "" || roleArn == "" || accessKeyID == "" || accessKeySecret == "" { + return "", fmt.Errorf("OSS 配置不完整") + } + + credConfig := new(credentials.Config). + SetType("ram_role_arn"). + SetAccessKeyId(accessKeyID). + SetAccessKeySecret(accessKeySecret). + SetRoleArn(roleArn). + SetRoleSessionName("topfans-download-session"). + SetPolicy(""). + SetRoleSessionExpiration(int(expiresInSeconds)) + + provider, err := credentials.NewCredential(credConfig) + if err != nil { + return "", fmt.Errorf("创建凭证提供器失败: %w", err) + } + + cred, err := provider.GetCredential() + if err != nil { + return "", fmt.Errorf("获取临时凭证失败: %w", err) + } + + endpoint := fmt.Sprintf("https://oss-%s.aliyuncs.com", region) + client, err := oss.New(endpoint, *cred.AccessKeyId, *cred.AccessKeySecret, + oss.SecurityToken(*cred.SecurityToken)) + if err != nil { + return "", fmt.Errorf("创建OSS客户端失败: %w", err) + } + + bucket, err := client.Bucket(bucketName) + if err != nil { + return "", fmt.Errorf("获取Bucket失败: %w", err) + } + + ossKey := filePath + if strings.HasPrefix(filePath, "https://") { + parts := strings.SplitN(filePath, ".oss-", 2) + if len(parts) == 2 { + keyParts := strings.SplitN(parts[1], "/", 2) + if len(keyParts) == 2 { + ossKey = keyParts[1] + } + } + } + + signedURL, err := bucket.SignURL(ossKey, oss.HTTPGet, expiresInSeconds) + if err != nil { + return "", fmt.Errorf("生成预签名URL失败: %w", err) + } + + if idx := strings.Index(signedURL, "?"); idx >= 0 { + signedURL = strings.ReplaceAll(signedURL[:idx], "%2F", "/") + signedURL[idx:] + } else { + signedURL = strings.ReplaceAll(signedURL, "%2F", "/") + } + + if !strings.Contains(signedURL, "security-token") && cred.SecurityToken != nil && *cred.SecurityToken != "" { + signedURL = signedURL + "&security-token=" + url.QueryEscape(*cred.SecurityToken) + } + + return signedURL, nil +} diff --git a/backend/services/assetService/service/mint_service.go b/backend/services/assetService/service/mint_service.go index f9764a3..4fa94ed 100644 --- a/backend/services/assetService/service/mint_service.go +++ b/backend/services/assetService/service/mint_service.go @@ -50,6 +50,9 @@ type MintService interface { // GetUserMintCount 获取用户累计铸爱次数 GetUserMintCount(userID, starID int64) (int32, error) + // EstimateMintCost 估算铸造费用(不实际扣款) + EstimateMintCost(userID, starID int64) (*MintCostEstimate, error) + // UpdateMintCountAndBoost 更新铸爱次数和收益提升 UpdateMintCountAndBoost(ctx context.Context, tx *gorm.DB, userID, starID int64, boostBps int32) error } @@ -62,7 +65,7 @@ type mintService struct { db *gorm.DB config *config.AssetConfig registryRepo starbookRepo.AssetRegistryRepository // 资产索引仓库(用于星册体系) - mintCostRepo repository.MintCostRepository // 铸造消耗配置仓库 + localMintCostRepo repository.MintCostRepository // 铸造消耗配置仓库 userMintCountRepo repository.UserMintCountRepository // 用户铸爱累计仓库 } @@ -74,7 +77,7 @@ func NewMintService( db *gorm.DB, cfg *config.AssetConfig, registryRepo starbookRepo.AssetRegistryRepository, - mintCostRepo repository.MintCostRepository, + localMintCostRepo repository.MintCostRepository, userMintCountRepo repository.UserMintCountRepository, ) MintService { return &mintService{ @@ -84,7 +87,7 @@ func NewMintService( db: db, config: cfg, registryRepo: registryRepo, - mintCostRepo: mintCostRepo, + localMintCostRepo: localMintCostRepo, userMintCountRepo: userMintCountRepo, } } @@ -230,7 +233,12 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st // 3. 使用事务创建铸造订单(或将阶段一订单推进到 PROCESSING) var mintOrder *models.MintOrder var asset *models.Asset + var newBalance int64 + var capturedCostCrystal int64 // 捕获铸造消耗 err = s.db.Transaction(func(tx *gorm.DB) error { + // 用局部变量捕获事务内获取的 newBalance + var capturedBalance int64 + defer func() { newBalance = capturedBalance }() // 3.0 取出阶段一订单,并校验状态/所有者 logger.Logger.Info("[MintOrder] Step 3.0: 获取订单", zap.String("order_id", req.OrderId), zap.Int64("user_id", userID), zap.Int64("star_id", starID)) existing, err := s.mintOrderRepo.GetByOrderIDAndUser(req.OrderId, userID, starID) @@ -281,19 +289,21 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st // 3.1 获取铸造消耗配置(阶梯计价) // 本次铸造是第 currentMintCount+1 次 logger.Logger.Info("[MintOrder] Step 3.1: 获取铸造配置", zap.Int32("current_mint_count", currentMintCount)) - mintCost, err := s.GetMintCost(currentMintCount + 1) + var localMintCost *models.MintCostConfig + localMintCost, err = s.GetMintCost(currentMintCount + 1) if err != nil { logger.Logger.Error("[MintOrder] Step 3.1 失败", zap.Error(err)) return fmt.Errorf("获取铸造消耗配置失败: %w", err) } - logger.Logger.Info("[MintOrder] Step 3.1 完成", zap.Int64("cost_crystal", mintCost.CostCrystal)) + capturedCostCrystal = localMintCost.CostCrystal // 捕获铸造消耗 + logger.Logger.Info("[MintOrder] Step 3.1 完成", zap.Int64("cost_crystal", localMintCost.CostCrystal)) // 3.2 扣除水晶余额(调用 User Service RPC) - logger.Logger.Info("[MintOrder] Step 3.2: 扣除水晶", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int64("delta", -mintCost.CostCrystal)) + logger.Logger.Info("[MintOrder] Step 3.2: 扣除水晶", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int64("delta", -localMintCost.CostCrystal)) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - newBalance, err := s.userClient.UpdateCrystalBalance(ctx, userID, starID, -mintCost.CostCrystal, + capturedBalance, err = s.userClient.UpdateCrystalBalance(ctx, userID, starID, -localMintCost.CostCrystal, "mint_cost", req.OrderId, fmt.Sprintf("铸造藏品 #%s", req.OrderId)) if err != nil { logger.Logger.Error("[MintOrder] Step 3.2 失败: 扣除水晶错误", zap.Error(err)) @@ -304,17 +314,17 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st // 3.3 检查是否触发保底(概率触发) var boostBps int32 = 0 - if mintCost.Probability > 0 && mintCost.RewardValue > 0 { + if localMintCost.Probability > 0 && localMintCost.RewardValue > 0 { // 随机判断是否触发 randomValue := time.Now().UnixNano() % 100 - if randomValue < mintCost.Probability { - boostBps = int32(mintCost.RewardValue) // reward_value 单位是 bps + if randomValue < localMintCost.Probability { + boostBps = int32(localMintCost.RewardValue) // reward_value 单位是 bps logger.Logger.Info("Mint guarantee triggered", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int32("mint_count", currentMintCount+1), zap.Int32("boost_bps", boostBps), - zap.Int64("probability", mintCost.Probability)) + zap.Int64("probability", localMintCost.Probability)) } } @@ -397,7 +407,7 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st updates := map[string]interface{}{ "asset_id": asset.ID, "status": models.MintOrderStatusSuccess, // 直接设为成功,无需异步处理 - "cost_crystal": mintCost.CostCrystal, + "cost_crystal": localMintCost.CostCrystal, "error_message": nil, "material_url": getStringValue(mintOrder.MaterialURL), "name": getStringValue(mintOrder.Name), @@ -415,7 +425,7 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st assetID := asset.ID mintOrder.AssetID = &assetID mintOrder.Status = models.MintOrderStatusSuccess - mintOrder.CostCrystal = mintCost.CostCrystal + mintOrder.CostCrystal = localMintCost.CostCrystal logger.Logger.Info("Mint order created", zap.String("order_id", mintOrder.OrderID), @@ -446,20 +456,25 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st } // 6. 构建响应 + // 获取本次铸造消耗(从事务内捕获的值) response := &pb.CreateMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "", Timestamp: time.Now().UnixMilli(), }, - Order: ModelToProtoMintOrder(mintOrder), - Asset: ModelToProtoAssetDetail(asset, ownerNickname, false, 0, 0, 0), // 新创建的资产,is_liked 为 false,display_status 默认为 0,earnings 和 exhibitionExpireAt 为 0 + Order: ModelToProtoMintOrder(mintOrder), + Asset: ModelToProtoAssetDetail(asset, ownerNickname, false, 0, 0, 0), // 新创建的资产,is_liked 为 false,display_status 默认为 0,earnings 和 exhibitionExpireAt 为 0 + CostCrystal: capturedCostCrystal, + BalanceAfter: newBalance, } logger.Logger.Info("Create mint order successful", zap.String("order_id", mintOrder.OrderID), zap.Int64("asset_id", asset.ID), zap.Int64("user_id", userID), + zap.Int64("cost_crystal", capturedCostCrystal), + zap.Int64("balance_after", newBalance), ) return response, nil @@ -785,7 +800,7 @@ func (s *mintService) GetMintCost(mintCount int32) (*models.MintCostConfig, erro mintCount = 10 } - config, err := s.mintCostRepo.GetByMintCount(mintCount) + config, err := s.localMintCostRepo.GetByMintCount(mintCount) if err != nil { logger.Logger.Error("Failed to get mint cost config", zap.Int32("mint_count", mintCount), @@ -805,6 +820,70 @@ func (s *mintService) GetUserMintCount(userID, starID int64) (int32, error) { return record.MintCount, nil } +// EstimateMintCost 估算铸造费用(不实际扣款) +func (s *mintService) EstimateMintCost(userID, starID int64) (*MintCostEstimate, error) { + // 获取当前累计铸爱次数 + currentMintCount, err := s.GetUserMintCount(userID, starID) + if err != nil { + logger.Logger.Warn("Failed to get user mint count, using 0", + zap.Int64("user_id", userID), + zap.Int64("star_id", starID), + zap.Error(err)) + currentMintCount = 0 + } + + // 获取铸造消耗配置(本次铸造是第 currentMintCount+1 次) + localMintCost, err := s.GetMintCost(currentMintCount + 1) + if err != nil { + return nil, fmt.Errorf("获取铸造消耗配置失败: %w", err) + } + + // 获取当前水晶余额 + profile, err := s.userClient.GetFanProfile(context.Background(), userID, starID) + var currentBalance int64 = 0 + if err != nil { + logger.Logger.Warn("Failed to get fan profile, balance will be 0", + zap.Int64("user_id", userID), + zap.Int64("star_id", starID), + zap.Error(err)) + } else { + currentBalance = profile.CrystalBalance + } + + // 计算铸造后余额 + afterBalance := currentBalance - localMintCost.CostCrystal + if afterBalance < 0 { + afterBalance = 0 + } + + // 下一阶梯费用提示 + var nextTierHint string + nextMintCount := currentMintCount + 2 // 下一次铸造的次数 + if nextMintCount <= 10 { + nextCost, err := s.GetMintCost(nextMintCount) + if err == nil { + nextTierHint = fmt.Sprintf("下一阶梯 %d 水晶", nextCost.CostCrystal) + } + } + + return &MintCostEstimate{ + CostCrystal: localMintCost.CostCrystal, + CurrentBalance: currentBalance, + AfterBalance: afterBalance, + MintCount: currentMintCount + 1, // 本次将是第几次铸造 + NextTierHint: nextTierHint, + }, nil +} + +// MintCostEstimate 铸造费用估算结果 +type MintCostEstimate struct { + CostCrystal int64 // 本次铸造消耗水晶 + CurrentBalance int64 // 当前余额(铸造前) + AfterBalance int64 // 铸造后余额 + MintCount int32 // 本次铸造是第几次 + NextTierHint string // 下一阶梯提示 +} + // UpdateMintCountAndBoost 更新铸爱次数和收益提升 // 在事务内调用,tx 为nil时会创建新事务 func (s *mintService) UpdateMintCountAndBoost(ctx context.Context, tx *gorm.DB, userID, starID int64, boostBps int32) error { diff --git a/docs/superpowers/plans/2026-05-16-lenticular-card-mint-asset-registry.md b/docs/superpowers/plans/2026-05-16-lenticular-card-mint-asset-registry.md new file mode 100644 index 0000000..81edc9a --- /dev/null +++ b/docs/superpowers/plans/2026-05-16-lenticular-card-mint-asset-registry.md @@ -0,0 +1,315 @@ +# 光栅卡铸造后写入 asset_registry 方案 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 确保光栅卡铸造完成后:1) 两张图片(main 主图 + bg 背景图)都注册到 `materials` 表;2) `asset_registry` 有记录;3) `asset_material_relations` 正确绑定两组图片 + +**Architecture:** +- 前端在 `lenticular-result.vue` 的 `selectAsset` 中调用 `submitCraftMintFromPath` 完成上传和铸造 +- `submitCraftMintFromPath` 已经支持:上传主图(main) + 背景图(bg)、注册素材、创建订单、绑定多素材 +- 问题:`lenticular-result.vue` 引用了未定义的 `isCraftLenticular` 和 `craftCoverUrl`,导致铸造流程断裂 + +**Tech Stack:** Vue3 + uni-app 前端,Go 后端,Supabase/PostgreSQL + +--- + +## 数据库设计解析 + +### 核心表关系 + +``` +asset_registry ← 资产索引表(用户有哪些藏品) + │ asset_id + ▼ + assets ← 资产表(名称、封面、描述) + │ id + ▼ +asset_material_relations ← 资产-素材关联表(同一资产有哪些图片) + │ asset_id, material_id + ▼ + materials ← 素材表(OSS key、MIME、尺寸) +``` + +### `asset_registry` 表结构(不存图片) + +```sql +CREATE TABLE public.asset_registry ( + id bigint, + asset_id bigint, -- 关联 assets.ID + asset_type varchar(20), -- 'regular'|'collection'|'activity' + owner_uid bigint, + star_id bigint, + grade integer, + status integer, -- 0=待处理, 1=已激活 + like_count integer, + display_status integer, + created_at bigint, + updated_at bigint +); +``` + +**注意:** `asset_registry` 只存储资产索引和元数据,**不存储任何图片 URL**。 + +### 图片通过 `asset_material_relations` 关联 + +光栅卡有**两张图片**(main 主图 + bg 背景图),通过 `asset_material_relations` 表实现多对一关联: + +```sql +-- 同一个 asset_id = 123 的光栅卡,有两条素材记录 +INSERT INTO asset_material_relations (asset_id, material_id, material_type, layer_order) VALUES +(123, 456, 'main', 0), -- 主图(人物层) +(123, 789, 'bg', 1); -- 背景图 + +-- materials 表存储实际图片信息 +INSERT INTO materials (id, oss_key, original_name, file_size, mime_type, hash, ...) VALUES +(456, 'asset/7/88/main/xxx.jpg', 'subject.jpg', 102400, 'image/jpeg', 'abc123', ...), +(789, 'asset/7/88/bg/xxx.jpg', 'bg.png', 204800, 'image/png', 'def456', ...); +``` + +### 封面图来源 + +铸造时,前端传入的 `material_url`(main 素材的 oss_key)会被设为 `assets.CoverURL`,用于列表页展示: + +``` +craftMintSubmit.js → createMintOrderApi({ material_url: main_oss_key }) + ↓ +mint_service.go:339 assets.CoverURL = materialURLValue + ↓ +前端列表页通过 assets.CoverURL 获取封面图 +``` + +### 完整数据流 + +``` +submitCraftMintFromPath({ imagePath, bgImagePath, formData }) + │ + ├─ uploadImageAndRegisterMaterial(imagePath, 'main') → materials.id=456 + ├─ uploadImageAndRegisterMaterial(bgImagePath, 'bg') → materials.id=789 + │ + ├─ createMintOrderApi({ material_url: main_oss_key }) + │ ↓ + │ mint_service.go + │ ├── tx.Create(asset) → CoverURL = main_oss_key + │ ├── tx.Create(registry) → asset_registry (status=1) + │ └── 更新订单状态为 SUCCESS + │ + └─ bindAssetMaterialsApi(asset_id, [ + { material_id: 456, material_type: 'main', layer_order: 0 }, + { material_id: 789, material_type: 'bg', layer_order: 1 } + ]) + ↓ + asset_material_relations 写入两条记录 +``` + +--- + +## 问题分析 + +### 当前代码问题(`lenticular-result.vue` 第 596-623 行) + +```javascript +const selectAsset = async () => { + // line 596-625 是完整代码块 + const imagePath = + isCraftLenticular.value // ❌ isCraftLenticular 未定义 + ? lenticularLayers.value.find((l) => l.id === 'mid')?.src || craftCoverUrl.value + : craftCoverUrl.value; // ❌ craftCoverUrl 未定义 + if (!imagePath) { ... } + + const bgImagePath = isCraftLenticular.value // ❌ isCraftLenticular 未定义 + ? lenticularLayers.value.find((l) => l.id === 'base')?.src || '' + : undefined; + + try { + await submitCraftMintFromPath({ imagePath, bgImagePath, formData: craftFormData.value }); + uni.navigateTo({ url: '/pages/castlove/success' }); + return; // line 623 return 后不会执行 + } catch (e) { ... } + + // 以下代码 dead code(return 后的逻辑) + if (!isLenticularDisplay.value && selectedIndex.value === -1) { ... } + if (isUploading.value) { ... } + // ... 上传到 OSS 并创建订单的旧代码 ... +}; +``` + +### 现有正确流程(`craftMintSubmit.js` 的 `submitCraftMintFromPath`) + +```javascript +// 第 187-306 行 +export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData }) { + // 1. 获取 OSS 签名 + // 2. 上传主图并注册为 'main' 素材 + // 3. 如果是光栅卡,上传背景图并注册为 'bg' 素材 + // 4. 创建铸造订单(后端自动写入 asset_registry) + // 5. 绑定多素材到资产(main + bg) + // 6. 返回 +} +``` + +铸造流程本身是正确的,问题在前端调用方。 + +--- + +## 文件结构 + +| 文件 | 修改类型 | 职责 | +|------|----------|------| +| `frontend/pages/castlove/lenticular/lenticular-result.vue` | 修改 | 修复 `selectAsset` 函数,添加缺失的 computed | +| `frontend/utils/craftMintSubmit.js` | 已有 | 铸造流程(已正确,无需修改) | + +--- + +## Task 1: 修复 `lenticular-result.vue` 的 `selectAsset` 函数 + +**Files:** +- Modify: `frontend/pages/castlove/lenticular/lenticular-result.vue:595-625` + +- [ ] **Step 1: 确认缺失的 import 并添加** + +在 ` + + \ No newline at end of file diff --git a/frontend/pages/castlove/lenticular/lenticular-result.vue b/frontend/pages/castlove/lenticular/lenticular-result.vue new file mode 100644 index 0000000..71d1933 --- /dev/null +++ b/frontend/pages/castlove/lenticular/lenticular-result.vue @@ -0,0 +1,1294 @@ + + + + + diff --git a/frontend/pages/castlove/lenticular/lenticular-thinking.vue b/frontend/pages/castlove/lenticular/lenticular-thinking.vue new file mode 100644 index 0000000..5ddae4e --- /dev/null +++ b/frontend/pages/castlove/lenticular/lenticular-thinking.vue @@ -0,0 +1,361 @@ + + + + + diff --git a/frontend/pages/castlove/success.vue b/frontend/pages/castlove/success.vue index d09c0d5..c7926a7 100644 --- a/frontend/pages/castlove/success.vue +++ b/frontend/pages/castlove/success.vue @@ -7,7 +7,7 @@ -
+ @@ -18,12 +18,31 @@ - + + + + + + + + + + + @@ -48,6 +67,9 @@ import { ref, onMounted } from 'vue'; import { onUnload } from '@dcloudio/uni-app'; import Header from '../components/Header.vue'; import NftCard from '../components/NftCard.vue'; +import LenticularCard from '@/components/lenticular/LenticularCard.vue'; +import { useLenticularCraftTiltPreview } from '@/composables/useLenticularCraftTiltPreview.js'; +import { buildLenticularLayersTwo } from '@/utils/castloveMintForm.js'; // 藏品数据 const nftData = ref({ @@ -55,9 +77,24 @@ const nftData = ref({ name: '', event: '', remark: '', - materialType: '' + materialType: '', + is_lenticular: false, + bg_image: '', }); +// 是否显示为光栅卡 +const isLenticular = ref(false) + +// 光栅卡图层 +const lenticularLayers = ref([]) +const { + layerTransforms, + simulate, + gyroSourceLabel, + scheduleTiltStart, + stopTiltPreview, +} = useLenticularCraftTiltPreview(lenticularLayers); + // NftCard 样式 const nftCardStyle = { position: 'relative', @@ -77,8 +114,16 @@ onMounted(() => { name: data.name || '未命名藏品', event: data.event || '', remark: data.remark || '', - materialType: data.materialType || '' + materialType: data.materialType || '', + is_lenticular: data.is_lenticular || false, + bg_image: data.bg_image || '', }; + // 如果是光栅卡,构建图层 + if (data.is_lenticular && data.image && data.bg_image) { + isLenticular.value = true + lenticularLayers.value = buildLenticularLayersTwo(data.bg_image, data.image) + scheduleTiltStart() + } } } catch (e) { console.error('读取藏品数据失败:', e); @@ -89,6 +134,15 @@ onMounted(() => { } }); +onUnload(() => { + stopTiltPreview() + try { + uni.removeStorageSync('temp_nft_data'); + } catch (e) { + /* noop */ + } +}); + // 查看详情(保留 temp_nft_data,避免第二次点击时 order_id 丢失) const handleViewDetails = () => { let orderId = ''; @@ -212,6 +266,20 @@ onUnload(() => { animation: zoomIn 0.8s ease-out 0.2s both; } +/* 光栅卡容器 */ +.lenticular-card-wrap { + width: 420rpx; + height: 560rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.lenticular-preview { + width: 420rpx; + height: 560rpx; +} + @keyframes zoomIn { from { opacity: 0; @@ -296,4 +364,54 @@ onUnload(() => { opacity: 0.9; transform: scale(0.98); } + +/* 光栅卡样式 */ +.lenticular-result-wrap { + display: flex; + justify-content: center; + z-index: 8; +} + +.lenticular-result-card { + display: flex; + flex-direction: column; + align-items: center; +} + +.lenticular-result-card .card-wrapper { + position: relative; + width: 352rpx; + height: 520rpx; +} + +.lenticular-result-card .craft-card-wrapper { + margin-bottom: 32rpx; +} + +.lenticular-result-card .card-frame { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; + transform: rotate(-10deg); +} + +.lenticular-result-card .craft-lenticular-slot { + position: absolute; + left: 50%; + top: 50%; + width: 87%; + height: 96%; + border-radius: 48rpx; + transform: translate(-50%, -50%) rotate(-10deg); + z-index: 2; + overflow: hidden; +} + +.lenticular-result-card .craft-lenticular-card { + width: 100%; + height: 100%; +} diff --git a/frontend/utils/api.js b/frontend/utils/api.js index a0a373e..6e64f7e 100644 --- a/frontend/utils/api.js +++ b/frontend/utils/api.js @@ -558,6 +558,14 @@ export function getMintOrderDetailApi(orderId) { }) } +// 估算铸造费用(显示消耗和余额) +export function estimateMintCostApi() { + return request({ + url: '/api/v1/assets/mints/cost-estimate', + method: 'POST' + }) +} + // 上传素材(返回 material_id) export function uploadMaterialApi(data) { return request({ @@ -576,6 +584,14 @@ export function bindAssetMaterialsApi(assetId, materials) { }) } +// 获取资产素材列表 +export function getAssetMaterialsApi(assetId) { + return request({ + url: `/api/v1/assets/${assetId}/materials`, + method: 'GET' + }) +} + // 图生图 export function imageGenerationApi(params) { return request({ diff --git a/frontend/utils/assetImageHelper.js b/frontend/utils/assetImageHelper.js index 8d050cc..0552867 100644 --- a/frontend/utils/assetImageHelper.js +++ b/frontend/utils/assetImageHelper.js @@ -1,3 +1,5 @@ +import { getOssPresignedUrlApi } from '@/utils/api.js' + /** * 从URL提取文件名 * @param {String} url - URL字符串 @@ -64,8 +66,8 @@ export function extractOssObjectPath(url) { } /** - * 获取藏品封面的真实URL - 直接使用后端返回的URL - * @param {String} coverUrl - 后端返回的cover_url + * 获取藏品封面的真实URL - 使用 OSS 预签名 URL + * @param {String} coverUrl - 后端返回的cover_url(相对路径或完整URL) * @returns {Promise} 真实可访问的URL */ export async function getAssetCoverRealUrl(coverUrl) { @@ -77,13 +79,27 @@ export async function getAssetCoverRealUrl(coverUrl) { return coverUrl || DEFAULT_IMAGE; } - // 直接使用后端返回的 URL - return coverUrl; + // 如果是完整的绝对URL(包含 ://),直接返回 + if (coverUrl.includes('://')) { + return coverUrl; + } + + // 相对路径:调用预签名接口获取可访问的URL + try { + const res = await getOssPresignedUrlApi(coverUrl, 3600, 'asset'); + if (res && res.data && res.data.url) { + return res.data.url; + } + return coverUrl; + } catch (e) { + console.error('[assetImageHelper] get presigned url failed:', e); + return coverUrl; + } } /** - * 获取好友头像的真实URL - 直接使用后端返回的URL - * @param {String} avatarUrl - 后端返回的avatar_url(完整URL) + * 获取好友头像的真实URL - 使用 OSS 预签名 URL + * @param {String} avatarUrl - 后端返回的avatar_url * @returns {Promise} 真实可访问的URL,失败返回空字符串 */ export async function getFriendAvatarRealUrl(avatarUrl) { @@ -92,6 +108,21 @@ export async function getFriendAvatarRealUrl(avatarUrl) { return avatarUrl || ''; } - // 直接使用后端返回的 URL - return avatarUrl; -} + // 如果是完整的绝对URL(包含 :://),直接返回 + if (avatarUrl.includes('://')) { + return avatarUrl; + } + + // 相对路径:调用预签名接口获取可访问的URL + try { + const res = await getOssPresignedUrlApi(avatarUrl, 3600, 'avatar'); + if (res && res.data && res.data.url) { + return res.data.url; + } + // 预签名失败,返回原始路径 + return avatarUrl; + } catch (e) { + console.error('[assetImageHelper] get avatar presigned url failed:', e); + return avatarUrl; + } +} \ No newline at end of file diff --git a/frontend/utils/castloveGenerationFlow.js b/frontend/utils/castloveGenerationFlow.js index e558873..2e0a874 100644 --- a/frontend/utils/castloveGenerationFlow.js +++ b/frontend/utils/castloveGenerationFlow.js @@ -1,5 +1,8 @@ /** * 铸爱 — 统一「Thinking → 选择 → 详情确认 → 铸造」路由与 Storage + * + * 本模块管理铸爱(castlove)生成流程的页面跳转、数据持久化和状态管理。 + * 支持四种模式:API生成、预填充选择、光栅工作室、镭射工作室 */ import { @@ -7,29 +10,61 @@ import { CASTLOVE_LASER_ENTRY_KEY, } from '@/utils/castloveMintForm.js' +// ========== Storage Keys ========== +/** 生成流程的完整Payload(包含mode、images等) */ export const GENERATION_FLOW_KEY = 'generation_flow_payload' +/** AI生成请求的数据(prompt、model、aspect_ratio等) */ export const GENERATION_REQUEST_KEY = 'generation_request_data' +/** 已生成的图片列表(预填充或AI生成的结果) */ export const GENERATED_IMAGES_KEY = 'generated_images' +/** 生成结果的元数据(displayMode、imageCount) */ export const GENERATION_RESULT_META_KEY = 'generation_result_meta' +/** 铸爱表单数据(craft_name、typeName等) */ export const CASTLOVE_FORM_KEY = 'castlove_form_data' +/** 用户在选择界面选中的图片 */ export const CRAFT_SELECTED_IMAGE_KEY = 'craft_selected_image' +/** 用户在选择界面选中的图片索引 */ export const CRAFT_SELECTED_INDEX_KEY = 'craft_selected_index' +// ========== Flow Modes ========== +/** 模式:API生成(AI生成四图) */ export const FLOW_MODE_API = 'api' +/** 模式:预填充(用户已有所需图片,直接进入选择) */ export const FLOW_MODE_PREFILLED = 'prefilled' +/** 模式:光栅工作室(工作台生成,不走AI四图) */ export const FLOW_MODE_LENTICULAR = 'lenticular' +/** 模式:镭射工作室(工作台生成,不走AI四图) */ export const FLOW_MODE_LASER = 'laser' +// ========== After Select Actions ========== +/** 选择后的动作:直接进入铸造 */ export const AFTER_SELECT_MINT = 'mint' +/** 选择后的动作:进入详情确认 */ export const AFTER_SELECT_DETAIL = 'detail' +// ========== Studio Types ========== +/** 工作室类型:光栅 */ export const STUDIO_LENTICULAR = 'lenticular' +/** 工作室类型:镭射 */ export const STUDIO_LASER = 'laser' +// ========== Page URLs ========== const LOADING_URL = '/pages/discover/generation-loading' const RESULT_URL = '/pages/discover/generation-result' -const ASSET_DETAIL_URL = '/pages/asset-detail/asset-detail' +const LENTICULAR_THINKING_URL = '/pages/castlove/lenticular/lenticular-thinking' +// const ASSET_DETAIL_URL = '/pages/asset-detail/asset-detail' +// ========== Core Functions ========== + +/** + * 将图片数组补足到最少指定数量 + * - 如果图片数量已达到最低要求,直接返回前minCount张 + * - 否则循环追加图片直到达到最低数量(用于保持UI四宫格布局) + * + * @param {Array} images - 图片数组 + * @param {number} minCount - 最少图片数量,默认4 + * @returns {Array} 补足后的图片数组 + */ export function padImagesForSelection(images, minCount = 4) { if (!Array.isArray(images) || images.length === 0) { return [] @@ -39,17 +74,55 @@ export function padImagesForSelection(images, minCount = 4) { } const out = [...images] while (out.length < minCount) { + // 循环追加:用已有图片填充空白位置 out.push(images[out.length % images.length]) } return out } +/** + * 将表单数据持久化到Storage + * 用于在页面跳转过程中保存用户填写的表单信息 + * + * @param {Object|null} formData - 表单数据对象 + */ function persistFormData(formData) { if (formData != null) { - uni.setStorageSync(CASTLOVE_FORM_KEY, JSON.stringify(formData)) + try { + uni.setStorageSync(CASTLOVE_FORM_KEY, JSON.stringify(formData)) + } catch (e) { + console.error('persistFormData failed:', e) + if (e.name === 'QuotaExceededError' || e.message?.includes('quota')) { + return { + success: false, + error: { + title: '存储空间不足', + content: '图片数据过大,请尝试压缩图片后重新提交' + } + } + } else { + return { + success: false, + error: { + title: '存储失败', + content: '表单数据保存失败,请重试' + } + } + } + } } + return { success: true } } +/** + * enrich(丰富)表单数据,添加流程控制字段 + * - generation_after: 选择后的动作(mint/detail) + * - studio_kind: 工作室类型(lenticular/laser) + * + * @param {Object|null} formData - 原始表单数据 + * @param {Object} options - { afterSelect, studioKind } + * @returns {Object} 丰富后的表单数据 + */ function enrichFormData(formData, { afterSelect, studioKind } = {}) { const next = { ...(formData || {}) } if (afterSelect) { @@ -61,14 +134,26 @@ function enrichFormData(formData, { afterSelect, studioKind } = {}) { return next } +/** + * 将图片引用转换为可用的本地路径或Base64 + * 支持三种形式: + * 1. data:URL格式 → 直接返回base64 + * 2. http/https URL → 下载到本地临时文件 + * 3. 本地路径 → 直接返回path + * + * @param {string} src - 图片引用(URL、路径或base64) + * @returns {Promise<{path: string, base64: string}>} + */ export function materializeImageRef(src) { const s = String(src || '').trim() if (!s) { return Promise.resolve({ path: '', base64: '' }) } + // data:URL 格式(如 data:image/png;base64,XXXX) if (s.startsWith('data:')) { return Promise.resolve({ path: '', base64: s }) } + // 网络URL,需要下载 if (s.startsWith('http://') || s.startsWith('https://')) { return new Promise((resolve, reject) => { uni.downloadFile({ @@ -84,13 +169,33 @@ export function materializeImageRef(src) { }) }) } + // 本地路径 return Promise.resolve({ path: s, base64: '' }) } +/** + * 导航到加载页面(生成中动画) + */ function navigateToLoading() { uni.navigateTo({ url: LOADING_URL }) } +/** + * 启动AI图片生成流程 + * 1. 构建生成请求数据(prompt、model、aspect_ratio等) + * 2. 保存表单数据和请求数据到Storage + * 3. 设置流程模式为API模式 + * 4. 跳转到加载页面 + * + * @param {Object} options + * @param {string} options.prompt - 生成提示词 + * @param {Object} options.formData - 表单数据(craft_name等) + * @param {number} options.n - 生成数量,默认4 + * @param {string} options.model - 模型,默认'image-01' + * @param {string} options.aspectRatio - 宽高比,默认'16:9' + * @param {string} options.afterSelect - 选择后动作,默认mint + * @param {string} options.studioKind - 工作室类型 + */ export function startAiImageGenerationFlow({ prompt, formData, @@ -122,14 +227,36 @@ export function startAiImageGenerationFlow({ navigateToLoading() } -/** 光栅 / 镭射:工作台生成预览,不走通用 AI 四图 */ +/** + * 导航到对应类型的加载页面(生成中动画) + * @param {string} studioKind - 工作室类型(lenticular/laser) + */ +function navigateToStudioLoading(studioKind) { + if (studioKind === STUDIO_LENTICULAR) { + uni.navigateTo({ url: LENTICULAR_THINKING_URL }) + } else { + uni.navigateTo({ url: LOADING_URL }) + } +} + +/** + * 启动工作台生成流程(光栅/镭射) + * 与AI生成流程不同,工作台生成不走通用AI四图,而是直接在工作台生成预览 + * + * @param {Object} options + * @param {Object} options.formData - 表单数据 + * @param {string} options.studioKind - 工作室类型(lenticular/laser) + */ export function startCraftGenerationFlow({ formData, studioKind }) { const merged = enrichFormData(formData, { - afterSelect: AFTER_SELECT_DETAIL, + afterSelect: AFTER_SELECT_DETAIL, // 工作台模式默认选择后进入详情 studioKind, }) - persistFormData(merged) - uni.removeStorageSync(GENERATION_REQUEST_KEY) + const result = persistFormData(merged) + if (!result.success) { + return result // { success: false, error: { title, content } } + } + uni.removeStorageSync(GENERATION_REQUEST_KEY) // 不需要AI请求数据 const mode = studioKind === STUDIO_LENTICULAR ? FLOW_MODE_LENTICULAR : FLOW_MODE_LASER uni.setStorageSync( @@ -139,12 +266,24 @@ export function startCraftGenerationFlow({ formData, studioKind }) { studioKind, afterSelect: AFTER_SELECT_DETAIL, craft: merged?.craft_name || merged?.typeName || '', - minDurationMs: 1600, + minDurationMs: 1600, // 至少展示1600ms的加载动画 }) ) - navigateToLoading() + navigateToStudioLoading(studioKind) } +/** + * 启动预填充选择流程(用户已有所需图片) + * 用户从相册或历史记录选择图片,直接进入选择界面 + * + * @param {Object} options + * @param {Array} options.images - 图片数组 + * @param {Object} options.formData - 表单数据 + * @param {number} options.minDurationMs - 加载动画最少展示时间,默认1400ms + * @param {boolean} options.padToFour - 是否补足到4张,默认true + * @param {string} options.afterSelect - 选择后动作,默认mint + * @param {string} options.studioKind - 工作室类型 + */ export function startPrefilledSelectionFlow({ images, formData, @@ -158,7 +297,10 @@ export function startPrefilledSelectionFlow({ throw new Error('startPrefilledSelectionFlow: images 不能为空') } const merged = enrichFormData(formData, { afterSelect, studioKind }) - persistFormData(merged) + const result = persistFormData(merged) + if (!result.success) { + return result + } uni.removeStorageSync(GENERATION_REQUEST_KEY) uni.setStorageSync( GENERATION_FLOW_KEY, @@ -174,6 +316,12 @@ export function startPrefilledSelectionFlow({ navigateToLoading() } +/** + * 持久化光栅工作室预览元数据 + * 在进入光栅结果页面前调用,保存背景图、主体图、材质等信息 + * + * @param {Object} formData - 包含lenticularBgImage、lenticularSubjectImage等字段 + */ export function persistLenticularPreviewMeta(formData) { uni.setStorageSync( LENTICULAR_STUDIO_STORAGE_KEY, @@ -194,6 +342,12 @@ export function persistLenticularPreviewMeta(formData) { uni.setStorageSync(GENERATED_IMAGES_KEY, JSON.stringify([{ type: 'lenticular' }])) } +/** + * 持久化镭射工作室预览图片 + * 在进入镭射结果页面前调用,保存生成的图片路径列表 + * + * @param {Array} paths - 镭射生成的图片路径数组 + */ export function persistLaserPreviewImages(paths) { uni.setStorageSync( GENERATION_RESULT_META_KEY, @@ -202,6 +356,18 @@ export function persistLaserPreviewImages(paths) { uni.setStorageSync(GENERATED_IMAGES_KEY, JSON.stringify(paths)) } +/** + * 完成图片选择并跳转到详情页 + * 1. 将选中图片转换为可用的本地引用 + * 2. 保存选中图片和索引到Storage + * 3. 根据工作室类型保存特定的元数据 + * 4. 跳转到资产详情页 + * + * @param {Object} options + * @param {string} options.selectedImage - 用户选中的图片 + * @param {number} options.selectedIndex - 用户选中的图片索引 + * @param {Object} options.formData - 表单数据(包含studio_kind等) + */ export async function completeSelectionAndOpenDetail({ selectedImage, selectedIndex, @@ -212,6 +378,7 @@ export async function completeSelectionAndOpenDetail({ uni.setStorageSync(CRAFT_SELECTED_IMAGE_KEY, storedImage) uni.setStorageSync(CRAFT_SELECTED_INDEX_KEY, String(selectedIndex ?? 0)) + // 根据工作室类型保存特定的预览元数据 if (formData?.studio_kind === STUDIO_LENTICULAR) { const subjectRef = storedImage uni.setStorageSync( @@ -241,23 +408,48 @@ export async function completeSelectionAndOpenDetail({ } const kind = formData?.studio_kind || '' - uni.navigateTo({ - url: `${ASSET_DETAIL_URL}?from=craft_confirm&studio_kind=${encodeURIComponent(kind)}`, - }) + // uni.navigateTo({ + // url: `${ASSET_DETAIL_URL}?from=craft_confirm&studio_kind=${encodeURIComponent(kind)}`, + // }) } +// ========== Query Helpers ========== + +/** + * 判断选择后的动作是否为"详情确认" + * @param {Object} formData - 表单数据 + * @returns {boolean} + */ export function isDetailAfterSelect(formData) { return formData?.generation_after === AFTER_SELECT_DETAIL } +/** + * 判断是否为光栅工作室类型 + * @param {Object} formData - 表单数据 + * @returns {boolean} + */ export function isLenticularKind(formData) { return formData?.studio_kind === STUDIO_LENTICULAR } +/** + * 判断是否为镭射工作室类型 + * @param {Object} formData - 表单数据 + * @returns {boolean} + */ export function isLaserKind(formData) { return formData?.studio_kind === STUDIO_LASER } +// ========== Payload Consumer ========== + +/** + * 消费(读取并清除)生成流程的Payload + * 在目标页面读取后自动清除,保证一次性使用 + * + * @returns {Object|null} 流程Payload,包含mode、images、craft等 + */ export function consumeGenerationFlowPayload() { try { const raw = uni.getStorageSync(GENERATION_FLOW_KEY) @@ -272,4 +464,5 @@ export function consumeGenerationFlowPayload() { } } -export { RESULT_URL } +// ========== Exports ========== +export { RESULT_URL } \ No newline at end of file diff --git a/frontend/utils/craftMintSubmit.js b/frontend/utils/craftMintSubmit.js index 9bdac33..133b4ad 100644 --- a/frontend/utils/craftMintSubmit.js +++ b/frontend/utils/craftMintSubmit.js @@ -32,13 +32,14 @@ function uploadFileToOss(tempFilePath, ossData) { } /** - * 上传文件到 OSS 并返回文件大小(用于注册素材) + * 上传文件到 OSS 并返回文件大小和 hash(用于注册素材) */ function uploadFileToOssWithInfo(tempFilePath, ossData) { return new Promise((resolve, reject) => { + console.log('[craftMintSubmit] uploadFileToOssWithInfo tempFilePath:', tempFilePath) const fileName = `${Date.now()}.jpg` uni.uploadFile({ - url: ossData.host, + url: resolveH5OssPostUrl(ossData.host), filePath: tempFilePath, name: 'file', formData: { @@ -52,6 +53,7 @@ function uploadFileToOssWithInfo(tempFilePath, ossData) { 'x-oss-signature-version': ossData.x_oss_signature_version, }, success: async (res) => { + console.log('[craftMintSubmit] uploadFileToOssWithInfo statusCode:', res.statusCode) if (res.statusCode === 200 || res.statusCode === 204) { const url = `${ossData.host}/${ossData.dir}${fileName}` // 获取文件大小 @@ -68,30 +70,264 @@ function uploadFileToOssWithInfo(tempFilePath, ossData) { } catch (e) { console.warn('[craftMintSubmit] getFileInfo failed', e) } - resolve({ url, size, hash: '' }) + // 计算文件 hash + const hash = await computeHashFromPath(tempFilePath) + console.log('[craftMintSubmit] uploadFileToOssWithInfo hash:', hash ? hash.substring(0, 10) + '...' : 'empty') + resolve({ url, size, hash }) } else { reject(new Error(`上传失败 ${res.statusCode}`)) } }, - fail: reject, + fail: (err) => { + console.error('[craftMintSubmit] uploadFileToOssWithInfo fail:', err) + reject(err) + }, }) }) } +/** + * 纯 JavaScript SHA256 实现(用于 App 等不支持 crypto.subtle 的环境) + */ +function sha256Sync(str) { + // SHA256 实现 + const rotateRight = (n, s) => (n >>> s) | ((n << (32 - s)) >>> 0) + const choice = (x, y, z) => (x & y) ^ (~x & z) + const majority = (x, y, z) => (x & y) ^ (x & z) ^ (y & z) + const maj = (x, y, z) => (x & y) ^ (x & z) ^ (y & z) + const ch = (x, y, z) => (x & y) ^ (~x & z) + + const K = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0x49b40821, 0xfef9e05c, 0x4fd9c4a7, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, + 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ] + + // 转换为 bytes + const msgBytes = [] + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i) + if (code < 0x80) { + msgBytes.push(code) + } else if (code < 0x800) { + msgBytes.push(0xc0 | (code >> 6)) + msgBytes.push(0x80 | (code & 0x3f)) + } else if (code < 0xd800 || code >= 0xe000) { + msgBytes.push(0xe0 | (code >> 12)) + msgBytes.push(0x80 | ((code >> 6) & 0x3f)) + msgBytes.push(0x80 | (code & 0x3f)) + } else { + const cp = 0x10000 + ((code - 0xd800) << 10) | (str.charCodeAt(++i) - 0xdc00) + msgBytes.push(0xf0 | (cp >> 18)) + msgBytes.push(0x80 | ((cp >> 12) & 0x3f)) + msgBytes.push(0x80 | ((cp >> 6) & 0x3f)) + msgBytes.push(0x80 | (cp & 0x3f)) + } + } + + // Padding + const msgLen = msgBytes.length + const bitLen = msgLen * 8 + msgBytes.push(0x80) + while ((msgBytes.length % 64) !== 56) msgBytes.push(0) + for (let i = 7; i >= 0; i--) msgBytes.push((bitLen >>> (i * 8)) & 0xff) + + // Initialize hash values + let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a + let h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19 + + // Process blocks + for (let chunk = 0; chunk < msgBytes.length / 64; chunk++) { + const w = new Array(64) + for (let j = 0; j < 16; j++) { + w[j] = (msgBytes[chunk * 64 + j * 4] << 24) | (msgBytes[chunk * 64 + j * 4 + 1] << 16) | + (msgBytes[chunk * 64 + j * 4 + 2] << 8) | msgBytes[chunk * 64 + j * 4 + 3] + } + for (let j = 16; j < 64; j++) { + const s0 = rotateRight(w[j - 15], 7) ^ rotateRight(w[j - 15], 18) ^ (w[j - 15] >>> 3) + const s1 = rotateRight(w[j - 2], 17) ^ rotateRight(w[j - 2], 19) ^ (w[j - 2] >>> 10) + w[j] = (w[j - 16] + s0 + w[j - 7] + s1) >>> 0 + } + let [ah0, ah1, ah2, ah3, ah4, ah5, ah6, ah7] = [h0, h1, h2, h3, h4, h5, h6, h7] + for (let j = 0; j < 64; j++) { + const S1 = rotateRight(ah6, 6) ^ rotateRight(ah6, 11) ^ rotateRight(ah6, 25) + const ch2 = ch(ah6, ah5, ah4) + const temp1 = (ah7 + S1 + ch2 + K[j] + w[j]) >>> 0 + const S0 = rotateRight(ah0, 2) ^ rotateRight(ah0, 13) ^ rotateRight(ah0, 22) + const maj2 = maj(ah0, ah1, ah2) + const temp2 = (S0 + maj2) >>> 0 + ah7 = (ah6 + temp1) >>> 0 + ah6 = (ah5 + temp1) >>> 0 + ah5 = (ah4 + temp1) >>> 0 + ah4 = (ah3 + temp1) >>> 0 + ah3 = (ah2 + temp1) >>> 0 + ah2 = (ah1 + temp1) >>> 0 + ah1 = (ah0 + temp1) >>> 0 + ah0 = (temp1 + temp2) >>> 0 + } + h0 = (h0 + ah0) >>> 0; h1 = (h1 + ah1) >>> 0; h2 = (h2 + ah2) >>> 0; h3 = (h3 + ah3) >>> 0 + h4 = (h4 + ah4) >>> 0; h5 = (h5 + ah5) >>> 0; h6 = (h6 + ah6) >>> 0; h7 = (h7 + ah7) >>> 0 + } + + const toHex = (n) => n.toString(16).padStart(8, '0') + return toHex(h0) + toHex(h1) + toHex(h2) + toHex(h3) + toHex(h4) + toHex(h5) + toHex(h6) + toHex(h7) +} + /** * 计算文件 SHA256 哈希(使用 Web Crypto API) */ async function computeFileHash(file) { - if (typeof crypto !== 'undefined' && crypto.subtle) { - const buffer = await file.arrayBuffer() - const hashBuffer = await crypto.subtle.digest('SHA-256', buffer) - const hashArray = Array.from(new Uint8Array(hashBuffer)) - return hashArray.map(b => b.toString(16).padStart(2, '0')).join('') + console.log('[craftMintSubmit] computeFileHash start, file type:', typeof file, file instanceof Blob ? `Blob(size=${file.size})` : '') + try { + if (typeof crypto !== 'undefined' && crypto.subtle) { + const buffer = await file.arrayBuffer() + console.log('[craftMintSubmit] computeFileHash arrayBuffer length:', buffer.byteLength) + const hashBuffer = await crypto.subtle.digest('SHA-256', buffer) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + const hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') + console.log('[craftMintSubmit] computeFileHash result:', hash.substring(0, 20) + '...') + return hash + } else { + console.warn('[craftMintSubmit] computeFileHash: crypto.subtle not available') + } + } catch (e) { + console.error('[craftMintSubmit] computeFileHash failed:', e) } // Fallback: 返回空字符串,后端会重新计算 return '' } +/** + * 从文件路径读取内容并计算 hash(支持各端) + */ +async function computeHashFromPath(filePath) { + console.log('[craftMintSubmit] computeHashFromPath start, path:', filePath ? filePath.substring(0, 80) : 'empty') + try { + // 处理 data:URL 格式 + if (filePath && filePath.startsWith('data:')) { + console.log('[craftMintSubmit] computeHashFromPath data:URL format') + try { + const base64Data = filePath.split(',')[1] || '' + const binaryStr = atob(base64Data) + // 优先用 crypto.subtle,失败则用纯JS + if (typeof crypto !== 'undefined' && crypto.subtle) { + const bytes = new Uint8Array(binaryStr.length) + for (let i = 0; i < binaryStr.length; i++) { + bytes[i] = binaryStr.charCodeAt(i) + } + const hashBuffer = await crypto.subtle.digest('SHA-256', bytes) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + const hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') + console.log('[craftMintSubmit] computeHashFromPath data:URL hash:', hash.substring(0, 20) + '...') + return hash + } else { + // Fallback 纯JS SHA256 + const hash = sha256Sync(binaryStr) + console.log('[craftMintSubmit] computeHashFromPath data:URL sha256Sync hash:', hash.substring(0, 20) + '...') + return hash + } + } catch (e) { + console.error('[craftMintSubmit] data:URL hash failed:', e) + } + return '' + } + + let content = '' + + // #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ + console.log('[craftMintSubmit] computeHashFromPath in miniprogram') + try { + const fs = uni.getFileSystemManager() + content = await new Promise((resolve, reject) => { + fs.readFile({ + filePath: filePath, + encoding: 'base64', + success: (res) => { + console.log('[craftMintSubmit] fs.readFile success, data length:', res.data?.length) + resolve(res.data) + }, + fail: (err) => { + console.error('[craftMintSubmit] fs.readFile fail:', err) + reject(err) + } + }) + }) + } catch (e) { + console.error('[craftMintSubmit] 小程序读取文件失败:', e) + } + // #endif + + // #ifdef APP-PLUS + if (!content) { + console.log('[craftMintSubmit] computeHashFromPath in app-plus') + try { + content = await new Promise((resolve, reject) => { + plus.io.resolveLocalFileSystemURL(filePath, (entry) => { + entry.file((file) => { + const reader = new plus.io.FileReader() + reader.onloadend = (e) => { + console.log('[craftMintSubmit] FileReader onloadend, result length:', e.target.result?.length) + resolve(e.target.result.split(',')[1] || '') + } + reader.onerror = (e) => { + console.error('[craftMintSubmit] FileReader error:', e) + reject(e) + } + reader.readAsDataURL(file) + }, (err) => { + console.error('[craftMintSubmit] getFile error:', err) + reject(err) + }) + }, (err) => { + console.error('[craftMintSubmit] resolveLocalFileSystemURL error:', err) + reject(err) + }) + }) + } catch (e) { + console.error('[craftMintSubmit] App读取文件失败:', e) + } + } + // #endif + + console.log('[craftMintSubmit] computeHashFromPath content length:', content?.length || 0) + console.log('[craftMintSubmit] computeHashFromPath crypto:', typeof crypto, crypto ? 'exists' : 'null', ', subtle:', crypto?.subtle ? 'exists' : 'null') + if (content && typeof crypto !== 'undefined' && crypto && crypto.subtle) { + try { + // 将 base64 转换为 ArrayBuffer 并计算 hash + const binary = atob(content) + const bytes = new Uint8Array(binary.length) + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i) + } + const hashBuffer = await crypto.subtle.digest('SHA-256', bytes) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + const hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') + console.log('[craftMintSubmit] computeHashFromPath hash:', hash.substring(0, 20) + '...') + return hash + } catch (e) { + console.error('[craftMintSubmit] hash计算失败:', e) + } + } else { + // Fallback: 使用纯JS SHA256 实现 (App 环境 crypto.subtle 可能不可用) + try { + console.log('[craftMintSubmit] computeHashFromPath 使用纯JS SHA256 fallback, content length:', content.length) + // content 是 base64 字符串,需要先解码为 binary string + const binaryStr = atob(content) + const hash = sha256Sync(binaryStr) + console.log('[craftMintSubmit] computeHashFromPath sha256Sync hash:', hash.substring(0, 20) + '...') + return hash + } catch (e) { + console.error('[craftMintSubmit] SHA256 fallback failed:', e) + } + } + } catch (e) { + console.error('[craftMintSubmit] computeHashFromPath failed:', e) + } + console.log('[craftMintSubmit] computeHashFromPath return empty') + return '' +} + /** * 获取文件 MIME 类型 */ @@ -119,9 +355,17 @@ async function uploadImageAndRegisterMaterial(imagePath, ossData, originalName, // #ifdef H5 if (imagePath.startsWith('data:')) { console.log('[craftMintSubmit] H5 base64 upload') - const blob = await fetch(imagePath).then((r) => r.blob()) + // H5: data:URL 需要手动转换 为 Blob + const base64Data = imagePath.split(',')[1] || '' + const binaryStr = atob(base64Data) + const bytes = new Uint8Array(binaryStr.length) + for (let i = 0; i < binaryStr.length; i++) { + bytes[i] = binaryStr.charCodeAt(i) + } + const blob = new Blob([bytes], { type: 'image/jpeg' }) uploadFileSize = blob.size uploadHash = await computeFileHash(blob) + console.log('[craftMintSubmit] H5 base64 hash computed:', uploadHash.substring(0, 20) + '...') const fd = new FormData() const fileName = `${Date.now()}.jpg` fd.append('key', ossData.dir + fileName) @@ -139,6 +383,7 @@ async function uploadImageAndRegisterMaterial(imagePath, ossData, originalName, throw new Error('上传失败') } imageUrl = `${ossData.host}/${ossData.dir}${fileName}` + console.log('[craftMintSubmit] H5 base64 upload complete, hash:', uploadHash.substring(0, 20) + '...') } else { console.log('[craftMintSubmit] 小程序/非H5上传') const uploadResult = await uploadFileToOssWithInfo(imagePath, ossData) @@ -163,6 +408,7 @@ async function uploadImageAndRegisterMaterial(imagePath, ossData, originalName, const mimeType = getFileMimeType(imagePath) // 4. 注册素材到后端 + console.log('[craftMintSubmit] uploadMaterialApi ossKey:', ossKey, 'mimeType:', mimeType, 'fileSize:', uploadFileSize) const materialRes = await uploadMaterialApi({ oss_key: ossKey, original_name: originalName || `${materialType}.jpg`, @@ -171,6 +417,7 @@ async function uploadImageAndRegisterMaterial(imagePath, ossData, originalName, hash: uploadHash, material_type: materialType, }) + console.log('[craftMintSubmit] uploadMaterialApi response:', materialRes) if (!materialRes || materialRes.code !== 200 || !materialRes.data) { throw new Error(materialRes?.message || '素材注册失败') @@ -287,7 +534,7 @@ export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData // 7. 构建 NFT 数据并跳转 const nftData = { - image: mainResult.ossKey, // oss_key + image: `${ossData.host}/${mainResult.ossKey}`, // 完整OSS URL name: snap.name, description: snap.description || '', material_type: snap.material_type, @@ -296,7 +543,11 @@ export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData asset_id: assetId, info: snap.info, event: snap.info, - ...(bgMaterialId ? { bg_material_id: bgMaterialId } : {}), + } + // 如果是光栅卡,存储背景图路径供 success 页面展示 + if (isLenticular && bgImagePath) { + nftData.bg_image = bgImagePath + nftData.is_lenticular = true } uni.setStorageSync('temp_nft_data', JSON.stringify(nftData)) uni.removeStorageSync('castlove_form_data')