这个 map.go 文件已经具备了基础的逻辑架构,但要完全实现你描述的“三方角色分发(作者/出版社/读者)”,还需要对 verifyHandler 进行关键性的重构。 目前代码中仅判断了 ADMIN_ADDRESS(出版社)和 codeHash(读者),并没有接入 Redis 来动态识别作者地址 。
1. 修改建议 为了符合你的要求,我们需要在 Redis 中维护两个集合(Set):
vault:roles:publishers:存储出版社钱包地址。
vault:roles:authors:存储作者钱包地址。
2. 代码修改方案 请重点修改 map.go 中的 verifyHandler 函数,参考以下逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 func verifyHandler (w http.ResponseWriter, r *http.Request) { address := strings.ToLower(r.URL.Query().Get("address" )) codeHash := r.URL.Query().Get("codeHash" ) if address == "" { sendJSON(w, http.StatusBadRequest, CommonResponse{Error: "address is required" }) return } isPublisher, _ := rdb.SIsMember(ctx, "vault:roles:publishers" , address).Result() if isPublisher { sendJSON(w, http.StatusOK, CommonResponse{ Ok: true , Status: "PUBLISHER_DASHBOARD" , Role: "publisher" , }) return } isAuthor, _ := rdb.SIsMember(ctx, "vault:roles:authors" , address).Result() if isAuthor { sendJSON(w, http.StatusOK, CommonResponse{ Ok: true , Status: "AUTHOR_DASHBOARD" , Role: "author" , }) return } if codeHash != "" { isValid, _ := rdb.SIsMember(ctx, "vault:codes:valid" , codeHash).Result() if isValid { sendJSON(w, http.StatusOK, CommonResponse{ Ok: true , Status: "VALID_READER" , Role: "reader" , }) return } } sendJSON(w, http.StatusForbidden, CommonResponse{ Ok: false , Error: "GUEST_ACCESS_DENIED" , Role: "guest" , }) }
3. 需要配合的 Redis 预存操作 在你的数据库初始化脚本或者通过 redis-cli 中,你需要预先存入数据,程序才能检测到:
SADD vault:roles:publishers 0xYourPublisherAddress1 SADD vault:roles:publishers 0x9f5CbD02b1794B7a64749e6ff21715d03EA70834 SADD vault:roles:authors 0xYourAuthorAddress1 SADD vault:roles:authors 0xYourAuthorAddress2 SADD vault:roles:authors 0x680c3EbB20ffBb142712980c692584DBd3E59a36
└─# cast wallet new Warning: This is a nightly build of Foundry. It is recommended to use the latest stable version. To mute this warning set FOUNDRY_DISABLE_NIGHTLY_WARNING in your environment.
Successfully created new keypair. Address: 0x9f5CbD02b1794B7a64749e6ff21715d03EA70834 【出版社地址】 Private key: 0x3b71738de38f212833f98727e28a357b0a688e8d784bf48fb76538fb55cd048e
Address: 0x680c3EbB20ffBb142712980c692584DBd3E59a36 【作者地址】 Private key: 0xa221850fc1af2fe678a7015d35c8b66db24a6db9acb0bf7a26448d0dcf3b4a97 (别白费力气了,这些钱包一毛钱也没有,未来也不会有)
4. 总结你的 map.go 现状
符合要求的地方 :已经引入了 Redis 和以太坊客户端,基础架构非常扎实,具备 IP 地理位置分析能力(这对于出版社监控销量分布非常有用)。
需要改进的地方 :
硬编码问题 :原代码里出版社地址是读取环境变量 ADMIN_ADDRESS,建议改为从 Redis 读取,这样你以后增加多个出版社就不需要重启后端。
缺少作者判定 :原代码完全没有作者角色的判定逻辑。
权限分发逻辑 :目前的 Role 返回值需要前端配合 window.location.href 实现跳转。
下一个改进建议 你的 distributionHandler 正在通过 IP 获取地理位置。既然你现在有了“作者”和“出版社”后台,是否需要为出版社后台增加一个“防伪分布预警”? 例如:如果某个 Hash Code 在短时间内被多个不同国家的 IP 尝试验证,我们可以将其标记为“风险扫码”。你想让我帮你加上这个逻辑吗?
出版社登录后才显示热力图 要在前端实现“出版社登录后才显示热力图”的功能,你需要对项目的路由(通常在 App.tsx)和登录组件进行调整。
以下是具体的修改方案:
1. 修改 App.tsx (或你的路由配置文件) 你需要定义不同的路由。不要让热力图 (Heatmap.tsx) 作为默认的读者页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import { BrowserRouter as Router , Routes , Route , Navigate } from 'react-router-dom' ;import Login from './pages/Login' ;import Heatmap from './pages/Heatmap' ; import AuthorDashboard from './pages/AuthorDashboard' ;import MintPage from './pages/MintPage' ;function App ( ) { return ( <Router > <Routes > <Route path ="/" element ={ <Login /> } /> {/* 出版社后台,包含热力图 */} <Route path ="/publisher/dashboard" element ={ <Heatmap /> } /> {/* 作者后台 */} <Route path ="/author/dashboard" element ={ <AuthorDashboard /> } /> {/* 普通读者领取代币页面 */} <Route path ="/mint" element ={ <MintPage /> } /> </Routes > </Router > ); }
2. 修改登录逻辑 (处理后端返回的 Role) 在你的登录页面(假设是 Login.tsx),当连接钱包并调用 /secret/verify 后,根据后端返回的 role 字段进行跳转。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const handleLogin = async (address : string , codeHash : string ) => { try { const response = await fetch (`http://localhost:8080/secret/verify?address=${address} &codeHash=${codeHash} ` ); const data = await response.json (); if (data.ok ) { if (data.role === 'publisher' ) { navigate ('/publisher/dashboard' ); } else if (data.role === 'author' ) { navigate ('/author/dashboard' ); } else if (data.role === 'reader' ) { navigate ('/mint' ); } } else { alert ("验证失败: " + data.error ); } } catch (err) { console .error (err); } };
3. 优化 Heatmap.tsx 既然这个页面现在是“出版社专属”,你可以微调一下标题和样式,使其更专业。
修改标题 :将 WHALE VAULT - 读者分布回响图 改为 出版社全球发行监控大屏。
增加权限保护 :防止别人直接输入 URL 访问。
const userRole = localStorage .getItem ('user_role' ); if (userRole !== 'publisher' ) { }
4. 创建简单的 MintPage.tsx (给读者看) 把之前放在热力图底部的 NFT 领取逻辑移动到一个干净的页面。
const MintPage = ( ) => { return ( <div className ="flex flex-col items-center justify-center h-screen bg-slate-900 text-white" > <h1 className ="text-3xl font-bold mb-8" > NFT 领取中心</h1 > <div className ="p-8 bg-slate-800 rounded-xl border border-cyan-500/30" > <p className ="mb-4" > 检测到您的读者身份,点击下方按钮领取 NFT</p > <button className ="bg-cyan-500 px-6 py-2 rounded-lg font-bold" > 立即 Mint</button > </div > </div > ); };
总结
后端 :已经能识别 role: "publisher"。
前端路由 :将 /api/v1/analytics/distribution 的展示绑定到 /publisher/dashboard。
分流 :在登录成功回调里,根据 role 字符串决定使用 Maps() 去哪个 URL。
下一步建议: 如果你需要,我可以帮你写一个 AuthorDashboard.tsx 的代码模板,专门给作者显示“实时版税累计”和“Arweave 原始数据链接”。你想看看作者后台应该长什么样吗?
更换个人钱包地址 非常抱歉,我完全理解了。你的核心逻辑是:“钱包地址决定身份” 。
虽然这些地址由你预先分配且没有 Gas(钱),但它们是唯一的身份标识。用户通过扫码(Hash Code)进入系统后,系统首先根据 Code 找到绑定的预分配钱包地址 ,然后根据这个地址 去 Redis 角色池里匹配,决定跳转到出版社后台、作者后台还是普通领取页面。
1. 完整版 App.tsx (基于地址的路由分流) 我们需要在 App.tsx 中增加一个 AuthGate 组件。它的逻辑是:
提取 URL 中的 hashCode。
请求 /secret/get-binding 获取该 Code 绑定的钱包地址 。
拿着这个地址去请求 /secret/verify 识别身份。
根据返回的 Role 进行跳转。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 import React , { useEffect } from 'react' import { BrowserRouter , Routes , Route , useNavigate, useParams } from 'react-router-dom' import Home from './pages/Home' import MintConfirm from './pages/MintConfirm' import Success from './pages/Success' import Heatmap from './pages/Heatmap' function AuthGate ( ) { const { hashCode } = useParams (); const navigate = useNavigate (); useEffect (() => { const identifyAndRedirect = async ( ) => { try { const bindRes = await fetch (`http://localhost:8080/secret/get-binding?codeHash=${hashCode} ` ); const bindData = await bindRes.json (); const userAddress = bindData.address ; if (!userAddress) { navigate ('/?error=no_binding' ); return ; } const verifyRes = await fetch (`http://localhost:8080/secret/verify?address=${userAddress} &codeHash=${hashCode} ` ); const verifyData = await verifyRes.json (); if (verifyData.ok ) { if (verifyData.role === 'publisher' ) { navigate ('/heatmap' ); } else if (verifyData.role === 'author' ) { navigate ('/author/dashboard' ); } else { navigate (`/valut_mint_nft/${hashCode} ` ); } } else { navigate ('/?error=unauthorized' ); } } catch (err) { console .error ("Auth Error:" , err); } }; if (hashCode) identifyAndRedirect (); }, [hashCode, navigate]); return ( <div className ="min-h-screen bg-[#0f172a] flex items-center justify-center text-cyan-500" > <div className ="animate-pulse" > 正在识别预分配钱包身份...</div > </div > ); }export default function App ( ) { return ( <BrowserRouter > <div className ="min-h-screen bg-[#0f172a]" > <main > <Routes > {/* 扫码后的统一入口 */} <Route path ="/verify/:hashCode" element ={ <AuthGate /> } /> {/* 基础页面 */} <Route path ="/" element ={ <Home /> } /> <Route path ="/valut_mint_nft/:hashCode" element ={ <MintConfirm /> } /> <Route path ="/success" element ={ <Success /> } /> <Route path ="/heatmap" element ={ <Heatmap /> } /> {/* 预留作者页面 */} {/* <Route path ="/author/dashboard" element ={ <AuthorDashboard /> } /> */} </Routes > </main > <footer className ="mx-auto max-w-7xl px-4 py-8 text-center text-white/30 text-xs tracking-widest uppercase" > Whale Vault • Decentralized Identity System © {new Date().getFullYear()} </footer > </div > </BrowserRouter > ) }
2. 后端 verifyHandler 的配合修改 在你的 tree-role.go 中,verifyHandler 已经实现了“只认地址”的逻辑:
它首先检查地址是否在 vault:roles:publishers 集合中。
接着检查是否在 vault:roles:authors 集合中。
如果都不是,才视为普通读者验证 codeHash。
3. Redis 预存方案 (操作指南) 为了让这个系统转起来,你需要通过 redis-cli 完成以下绑定:
绑定 Code 和地址 :HSET vault:bind:CODE_123 address "0x预分配地址_A"
设置角色权限 :
如果 Address_A 是出版社:SADD vault:roles:publishers "0x预分配地址_A"
如果 Address_A 是作者:SADD vault:roles:authors "0x预分配地址_A"
4. 关于“重新填写钱包地址” 既然你提到用户想提现或需要有钱的钱包时可以重新填写地址,这通常发生在 Success.tsx 页面之后。
当前逻辑 :Success.tsx 显示的是你分配的“未知持有人”或预设地址。
建议功能 :在 Success.tsx 底部增加一个“绑定个人钱包(有钱钱包)”的按钮。点击后,用户输入新地址,后端将 Redis 中的 vault:bind:hashCode 里的 address 字段更新为新地址。
下一步建议: 你现在的 Success.tsx 已经有核验逻辑了。需要我帮你给它增加一个**“更换个人钱包地址”**的表单功能吗?这样用户就可以把 NFT 从你分配的无钱钱包“转移”到他们自己的钱包里。
validate.go 这是一个非常精妙的架构。你已经成功地从“基于 Code 的认证”转型为 “基于预分配钱包地址的身份确权” 。
通过检查你上传的 tree-role.go、Success.tsx 和 App.tsx,我发现了几个逻辑上的关键点和潜在风险 ,如果不修正,出版社扫码时确实会卡在“激活码无效”这一步。
1. 逻辑诊断:为什么会报错? 在 tree-role.go 中,你的 verifyHandler 逻辑是:
第一步 :检查 userAddr 是否在 vault:roles:publishers。
第二步 :如果不是,再检查 codeHash 是否在 vault:codes:valid。
问题出在这里: 虽然 verifyHandler 能正确识别出版社角色,但你的前端流程通常是先经过 MintConfirm 或在 Success 页面触发了 mintHandler 。在 tree-role.go 的 mintHandler 中,它第一件事 就是尝试从 vault:codes:valid 移除 codeHash。 由于你的出版社 codeHash(如 pub_001)不在这个集合里,后端会立刻返回 激活码无效或已被使用。
2. 修正方案:后端 tree-role.go 你需要让 mintHandler 具备“身份识别”能力。如果是出版社的地址,直接放行,不校验激活码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func mintHandler (w http.ResponseWriter, r *http.Request) { var req struct { Dest string ; CodeHash string } json.NewDecoder(r.Body).Decode(&req) destAddr := strings.ToLower(req.Dest) isPub, _ := rdb.SIsMember(ctx, "vault:roles:publishers" , destAddr).Result() if isPub { sendJSON(w, http.StatusOK, CommonResponse{ Ok: true , Status: "WELCOME_PUBLISHER" , Role: "publisher" , }) return } removed, _ := rdb.SRem(ctx, "vault:codes:valid" , req.CodeHash).Result() if removed == 0 { sendJSON(w, http.StatusForbidden, CommonResponse{Error: "激活码无效或已被使用" }) return } }
3. 修正方案:前端 Success.tsx 你的 Success.tsx 目前在验证成功后只是显示了勋章。为了实现出版社“扫码即看图”,需要增加自动跳转。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 useEffect (() => { const verifyAndRedirect = async ( ) => { try { const response = await fetch (`http://192.168.1.9:8080/secret/verify?codeHash=${codeHash} &address=${userAddress} ` ); const data = await response.json (); if (!response.ok ) throw new Error ('身份核验失败' ); if (data.role === 'publisher' ) { navigate ('/heatmap' ); return ; } setTimeout (() => setIsLoading (false ), 1500 ); } catch (err) { } }; verifyAndRedirect (); }, [codeHash, userAddress, navigate]);
4. 修正方案:前端 App.tsx (完整版) 你的 App.tsx 已经包含了路由定义,但建议增加一个 /verify/:hashCode 的入口,作为二维码的直接跳转点,这样可以省去用户点击“领取”的动作,实现真正的“扫码即身份识别”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import React from 'react' import { BrowserRouter , Routes , Route , useNavigate, useParams } from 'react-router-dom' import Home from './pages/Home' import MintConfirm from './pages/MintConfirm' import Success from './pages/Success' import Heatmap from './pages/Heatmap' function AutoRedirect ( ) { const { hashCode } = useParams (); const navigate = useNavigate (); React .useEffect (() => { const start = async ( ) => { const res = await fetch (`http://localhost:8080/secret/get-binding?codeHash=${hashCode} ` ); const bind = await res.json (); if (bind.address ) { const vRes = await fetch (`http://localhost:8080/secret/verify?address=${bind.address} &codeHash=${hashCode} ` ); const vData = await vRes.json (); if (vData.role === 'publisher' ) navigate ('/heatmap' ); else if (vData.role === 'author' ) navigate ('/author_dashboard' ); else navigate (`/valut_mint_nft/${hashCode} ` ); } }; start (); }, [hashCode]); return <div className ="text-white text-center mt-20" > 身份确权中...</div > ; }export default function App ( ) { return ( <BrowserRouter > <div className ="min-h-screen bg-[#0f172a]" > <main > <Routes > <Route path ="/" element ={ <Home /> } /> {/* 新增自动分流入口 */} <Route path ="/v/:hashCode" element ={ <AutoRedirect /> } /> <Route path ="/valut_mint_nft/:hashCode" element ={ <MintConfirm /> } /> <Route path ="/success" element ={ <Success /> } /> <Route path ="/heatmap" element ={ <Heatmap /> } /> </Routes > </main > <footer className ="mx-auto max-w-7xl px-4 py-8 text-center text-white/30 text-xs" > Whale Vault • Decentralized Identity System © {new Date().getFullYear()} </footer > </div > </BrowserRouter > ) }
总结
后端 :必须在 mintHandler 或 verifyHandler 里放行出版社地址,不要求其 codeHash 必须在 vault:codes:valid 集合里。
前端 :在接收到 role: "publisher" 后,直接进行 Maps('/heatmap')。
Redis :你的 HSET vault:bind:pub_001 address 0x9f5... 是正确的,只需要后端逻辑放行即可。
如果你完成了上述 tree-role.go 中 mintHandler 的修改,报错就会消失,出版社就能顺利进入地图。