news 2026/9/11 4:12:47

多商户酒店预订小程序升级:权限隔离、微信支付v3及前端体验优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多商户酒店预订小程序升级:权限隔离、微信支付v3及前端体验优化

先交代一下背景。我们团队手上原本跑着一套单体酒店预订小程序,只服务自营的几家门店,房源录入、排房、订单、收款都在同一个后台里。上线两年后,加盟和托管业主越来越多,业务方提出每个商户要有独立的房源管理、独立的订单视图、独立的结算对账,“不要动不动就来平台后台翻数据”。于是我们花了大概四个月时间,把整个系统从“单店工具”改造成了“多商户酒店预订小程序系统”。这轮全新升级完成后,“一站式订房平台”不再是一句宣传语,而是一个真正跑通了的业务形态。

这篇文章不聊空泛的架构概念,直接把我在这轮升级里踩过的坑、验证过的方案、以及那些热搜词背后对应的真实问题(微信支付v3对接、动态设置标题、顶部导航栏高度、软键盘遮挡查询内容、小程序反编译排查异常、导出Excel)一一拆开讲。做小程序开发的、做酒店SaaS产品的、或者正准备把单体业务平台化的朋友,应该都能从这里找到参照。

1. 为什么非要从单体小程序拆成多商户平台

1.1 单体架构撑不住“多业主”时,第一波阵痛出现在哪里

业务没有变复杂之前,单体架构是最舒服的。一个后台管所有门店,订单全汇总,财务看总数就行。但多商户入驻后,问题一下就暴露了。

最痛的是数据边界。业主A只能看自己家的订单,业主B不能看到A的房价策略和入住率。原来所有表都挂在同一套门店ID下,商户一多,查询条件里到处要拼“当前商户ID = ?”,漏掉一个条件就是越权。其次是结算。每个商户的抽佣比例、结算周期、提现方式都不一样,如果还是统一走一个财务通道,月底对账基本靠Excel人工拉数据,时间全部耗在沟通上。

所以这次升级的核心目标,不是把界面换好看一点,而是把系统从一个“能用的后台”变成一个“能让商户自主经营、平台统一管控”的多商户生态。

1.2 升级后整体变成了三端结构

使用对象核心职责技术形态
用户端C端住客搜索、比价、预订、支付、入住、评价uni-app微信小程序
商户端各酒店/民宿业主房源管理、房价库存、订单处理、结算对账uni-app小程序 + H5
平台运营端平台管理员商户审核、类目配置、抽佣设置、平台级报表Vue3管理后台

这三端共用同一套后端接口,但权限模型完全隔离。用户端小程序只暴露“浏览-下单-支付-售后”链路,商户端小程序只能操作自己名下的门店和订单,平台端则是全局视角。这样拆分以后,每一端的迭代都可以独立发版,而不会互相踩脚。

这里有一个取舍想提醒大家:商户端不要一开始就做成App,太重了。小程序很合适,商户不需要下载安装,微信扫码就能进入。我们后来把商户端也打包成了H5版,方便商户在电脑上操作房价日历和大批量订单导出,体验比在手机上舒服很多。

2. 多商户体系的设计核心:权限边界、数据归属与审核流转

2.1 账号体系别再等了,先建RBAC权限模型

单体系统时代,我们直接用了“用户表里加一个role字段”的简化做法,但多商户场景下这远远不够。这次升级,我第一步就是把权限模型落成标准的五表结构:用户表、角色表、权限表、商户表以及用户-商户关联表。

这里商户一定是独立一张表,不能只作为用户的一个属性存在。一个自然人可能同时在两个不同商户上挂职,比如既是A酒店店长,又管理B民宿的渠道。用户与商户之间是“用户在某个商户下拥有某个角色”,所以关系表里必须同时记录user_id、merchant_id、role_id,并且查询时永远带着merchant_id这个条件。

RBAC模型落地后还有一个额外好处:平台运营端的菜单权限可以直接复用同一套模型。平台超管、审核员、财务、客服,各自的按钮级权限都可以精确控制,省掉了后面还要单独为后台管理员做权限系统的成本。

2.2 数据归属:所有业务表必须带 merchant_id,且不允许越权拼接

多商户系统的“命门”就是数据归属。酒店行业的数据链路比普通电商更长:商户 -> 门店 -> 房型 -> 具体房间 -> 房价计划 -> 库存日历 -> 订单 -> 支付流水。我们给这条链路上的每一张业务表都加上了merchant_id字段。

但加了字段只是第一步,真正要命的是查询必须规范化。拿订单查询举例,商户端接口的SQL绝不能是“根据订单号查订单”,然后由前端传过来的userId来决定能不能看。正确的是:先从token里解出user_id,再查用户与商户的关联关系拿到merchant_id,SQL永远带上merchant_id作为第一过滤条件。接口层面再做一次参数校验,防止商户传别人的merchantId。

这条规则我踩过很深的坑。有一次商户反馈“我看到了别人的订单”,排查下来是导出接口里用了两个不同的DAO查询,一个带了商户过滤,一个没带,结果那个没带的被报表模块复用,数据就穿了。所以我们现在有两条硬性规范:第一,所有查询入口必须统一走一个带商户上下文的Service基类;第二,代码Review时第一眼看的就是有没有用当前登录商户的上下文,而不是盲目信任前端参数。

2.3 房源上架审核与状态流转

多商户平台必须有审核机制,否则平台对房源质量完全失控。我们把房源状态设计成了五个状态组成的闭环:草稿、待审核、已上架、已驳回、已下架。

商户提交新房源后进入“待审核”,平台运营端审核通过后变“已上架”,审核不过打回“已驳回”并附原因。已上架的房源如果被平台风控或用户投诉下架,则进入“已下架”状态,商户需要修改后重新提交审核。

状态变更全部走一个状态机Service,不能直接在代码里乱改status字段。比如“已驳回”只能由“待审核”流转过来,不能从“已上架”直接改成“已驳回”。状态机还天然形成操作日志,谁在什么时间把房源从什么状态改成了什么状态,全部留痕。多商户系统最怕扯皮,日志就是解决扯皮的最好工具。

3. 微信支付v3对接实践:从商户号配置到多商户分账

3.1 为什么这次升级选择了V3而不是继续延用V2

旧系统用的是微信支付v2,APIv2的签名方式是MD5验签,密钥是32位,回调通知是明文XML,调试倒是比较方便,但安全性确实一般。而且v2接口对新商户号、新应用的审核限制越来越多,一旦涉及退款、分账等高级功能,基本都会被引导到v3。

V3最大的变化是:API密钥变成APIv3密钥,签名改用商户私钥加SHA256-RSA2048,回调通知用AES-256-GCM加密。初看模板很啰嗦,但安全性高了一个量级。多商户平台涉及资金流水,我强烈建议直接用V3,别在这个问题上给自己留旧债。

3.2 对接前需要预备的四样东西

  • 商户号(mchid)
  • APIv3密钥(32位,用于回调报文解密)
  • 商户API证书(apiclient_cert.p12/apiclient_key.pem)
  • 证书序列号(用于请求头Authorization里的serial_no)

这里是最容易乱的地方。APIv3密钥和商户API证书的私钥是两回事。前者是回调里解密resource字段用的对称密钥;后者是请求签名用的非对称私钥。当时团队里新来的同学把这两个混在一起,导致下单签名成功但回调解密一直失败,定位了很久才发现是解密的Key搞错了。建议在配置文件里把这两个值分开命名,注释写清楚各自用途。

3.3 下单、回调验签与解密的关键代码逻辑

统一下单接口是/v3/pay/transactions/jsapi,核心参数就是appid、mchid、description、out_trade_no、notify_url、amount。这里的out_trade_no必须保证唯一,我们用的是“商户ID + 日期 + 随机数”的拼接规则,既保证唯一,又方便反查商户。

回调处理是整个支付环节最容易出问题的地方。V3回调的Header里有Wechatpay-Signature和Wechatpay-Nonce,Body里是加密的resource,格式大致如下:

{ "id": "回调通知ID", "event_type": "TRANSACTION.SUCCESS", "resource_type": "encrypt-resource", "resource": { "ciphertext": "加密密文", "nonce": "加密随机串", "associated_data": "附加数据" } }

处理顺序一定不能错:先验签,再解密,再更新订单状态,最后返回200状态码。验签时要从微信支付平台证书拿公钥,用SHA256withRSA校验签名;验签通过后,再用APIv3密钥对ciphertext做AES-256-GCM解密。解密后拿到的明文JSON里有out_trade_no和trade_state,这时才允许把本地订单状态改成已支付。

为什么一定要先验签?因为回调地址如果泄露,攻击者可以伪造一个“支付成功”通知打到你服务器上,如果不验签就直接改订单状态,那系统就形同虚设。验签代码网上有很多现成封装,但每家的异常处理质量参差不齐。我们最终把验签失败、解密失败、订单状态更新失败都做了独立的日志埋点和告警,因为这几类错误是支付对账排查时最重要的线索。

3.4 多商户分账:平台单商户号 + 分账接口

多商户系统不可能每个商户都申请一个微信支付商户号,这不现实,尤其是平台初期。我们采用的是“平台一个商户号,钱先进平台,再通过分账接口把属于商户的那部分结算出去”。

微信支付V3的分账接口是/v3/profitsharing/orders,请求分账前需要在商户平台配置分账接收方,接收方类型通常是MERCHANT_ID或PERSON_OPENID。需要说明的是,分账比例有上限,默认单笔订单分账给接收方的总额不能超过30%,如果需要更高比例,要额外申请调整。

实际操作中,我们不建议在支付回调里即时分账。更稳的方案是:支付完成后先冻结分账,等用户实际入住、没有发起退款后再触发自动分账,这样的资金流更安全。由于分账是资金操作,建议做一个单独的分账Job,每天定时跑一次,把前一天的订单统一处理,并生成分账流水供财务核对。当时我们把分账逻辑和订单列表耦合在一起,结果报表数据老是对不上,拆成独立任务后瞬间清爽了。

4. 前端体验升级:自定义导航栏、动态标题与软键盘遮挡

4.1 自定义导航栏高度计算,适配不同机型不再拍脑袋

多商户系统和C端自营商城不一样,顶部的导航栏要给商户或住客呈现不同的操作入口。自带的navigationBar样式太受限,按钮位置、颜色、字重都很难完全贴合UI稿,所以我们很多页面使用了自定义导航栏,即pages.json里配"navigationStyle": "custom"

但自定义导航栏的最大坑是高度适配。不同手机的状态栏高度不一样,iPhone X系列有刘海,刘海高度还分好几代,安卓机型更是五花八门。计算公式其实业内已经有公认答案:

const systemInfo = uni.getSystemInfoSync() const statusBarHeight = systemInfo.statusBarHeight // 状态栏高度 const menuButton = uni.getMenuButtonBoundingClientRect() // 胶囊按钮信息(仅小程序端有效) const navBarHeight = (menuButton.top - statusBarHeight) * 2 + menuButton.height

简单解释一下:胶囊按钮的上边界与状态栏底部的距离乘以2,再加胶囊按钮自身高度,就是导航栏的总高度。这样算出来之后,不管什么机型,都能保证导航栏内容垂直居中,并且和胶囊按钮在一条水平线上。这个公式在H5端不适用,所以代码里要判断当前环境,H5端直接用固定高度44px加安全距离就行。

4.2 页面动态设置标题的两种实现路径

热搜词里有“小程序动态设置标题”,这是我们开发里几乎天天碰到的需求。多商户场景下,标题往往要展示商户名或房型名,不可能全部静态写在pages.json里。

第一种方式最简单,直接调用uni.setNavigationBarTitle({ title: 'xxx' }),适合页面加载后根据接口数据改标题的场景。比如酒店详情页,拿到酒店名称后再设置“某某酒店”这个标题。

第二种方式适合自定义导航栏,这时没有原生标题可用,只能在页面组件里动态渲染。要注意的是自定义导航栏标题如果太长,会跟右侧的胶囊按钮重叠。我们专门封装了一个TitleBar组件,对标题做了自动截断处理,超过一定宽度就在CSS层面截断加省略号。这个细节不高大上,但真正用起来能避免大量“标题打架”的视觉问题。

4.3 input组件被软键盘遮挡的修复方案

这个热搜词“uniapp 微信小程序 手机软键盘会遮挡住查询内容”几乎每天都有开发者求助。我一开始也以为是input的focus状态没处理好,后来反复测试才发现,问题出在页面的滚动容器和键盘弹起后的视口变化上。

小程序默认情况下,键盘弹起会把页面整体上推(adjust-position为true),但在某些自定义导航栏或fixed定位的页面里,键盘弹起后上推效果不生效,或者内容被键盘盖住。我们的处理方案是:在pages.json对应页面里设置"adjustPosition": false,然后在input的focus事件里手动滚动到目标区域,比如让搜索结果的第一个item滚动到可视区顶部。

还有一种更简单的兜底方案:把查询按钮和input放在页面上方固定区域,结果列表用scroll-view包裹,并设置scroll-into-view指向当前高亮项。这样键盘弹起时,用户仍然可以通过滚动scroll-view看到内容,不会被键盘挡住。我们在酒店搜索页就是用这个方案解决的,实测在iOS和安卓上表现都稳定。

5. 首屏加载页优化与站点信息的动态化

5.1 默认启动页升级:用好第一印象,而不是简单替换一张图

很多团队对小程序启动页面的理解就是“换一张广告图”,这其实是不够的。小程序启动链路分两层:第一层是微信客户端的启动图(就是微信展示你的小程序图标和名称的页面),这一层我们控制不了样式;第二层是我们自己小程序的第一个页面加载过程,这一层完全由代码控制。

之前我们的首屏是一个纯白页面,然后数据回来再渲染,用户打开小程序的前一秒基本是白屏。这次升级我们做了两件事:第一,把第一个页面改成启动页(splash),上面有品牌Logo和一句Slogan,用CSS动画做淡入;第二,启动页内部并行发起几个关键请求,比如首页banner、城市列表、默认定位,等数据回来后再跳转到首页或让用户进入主页。

注意:启动页不要一上来就uni.switchTab跳走,那样有时候会闪一下tabBar。更平滑的方式是先把这个启动页的根组件做好过渡,比如透明渐变或者上滑效果,用户感知不到页面切换。这一点对酒店预订这种用户可能反复打开小程序的场景尤其重要,每次打开都像第一次一样流畅,信任感会强很多。

5.2 动态设置标题与站点信息统一从配置中心读取

多商户平台涉及很多“站点级”配置:平台名称、客服电话、用户协议版本、支付方式开关、帮助中心URL等。这些内容不能散落在代码里,否则每次改动都要重新发版,运营体验非常差。

我们建了一张platform_config表,用key-value方式存储配置项,后端提供/api/config/list接口。小程序启动时拉取一次配置,存放在全局状态管理里,后续页面通过getter读取。动态标题、首页banner文案、客服电话、甚至是主题色,都可以通过配置中心下发。

这里有一个细节:配置接口的缓存策略要设计好。我们给配置接口设置了5分钟本地缓存,后端变更配置后,用户最多5分钟后能看到新值。成本极低,但改起来非常方便,运营再也不必因为一个电话号码变动而催着前端发版了。

5.3 骨架屏:比loading菊花更高级的加载占位

酒店详情页有房型列表、酒店设施、评价、地图等内容,接口在弱网环境下可能要两秒才能全部返回。这个时候整页loading并不好看,我们改用骨架屏占位——页面上先渲染出几个灰色的矩形块,形状和真实内容的排版一致,比如顶部轮播图位置、标题位置、几个按钮位置,让用户一进页面就知道这里会有什么内容。

骨架屏的实现不需要自己从头画。如果UI稿简单,用CSS动画模拟块状脉冲即可;如果复杂,可以给每个区块写一个.skeleton类,统一灰色背景加呼吸闪动效果。注意骨架屏不要全屏用同一个动画速度,块和块之间稍微错开一点动画延迟,视觉效果会自然很多。

6. 升级过程中的真实问题排查:从请求分析到线上产物比对

6.1 接口请求分析:定位“订单状态不对”最快的方式

开发多商户系统时,我遇到最多的问题是“明明支付成功了,小程序里却显示未支付”。这类问题靠肉眼读代码很难定位,必须抓真实请求链路。手机端小程序走的是HTTPS加密流量,要在可控环境下做请求分析,通常需要在电脑上配置代理,并安装对应的CA证书,让手机的HTTPS流量能够被本地工具解密查看。这一步对排查回调通知、请求参数、响应体特别有用。

比如有一次,商户端小程序提交房价计划后,前端一直报500。我通过代理工具看到请求体里传的startDate是“2025-03-01”,而后端接口接收参数名是start_date,下划线命名对不上,数据丢失导致校验不过。这类问题在社区里被反复问到,其实从请求数据角度一眼就能定位。

强调一下,这种手段用在自己的小程序和自建服务端上是完全正当的调试行为,不要拿来扒别人家的接口数据,风险和法律问题都不值得。

6.2 线上产物与本地代码不一致,反编译工具的正确打开方式

小程序在线上跑的是微信后台编译上传的产物包(wxapkg),如果“开发者工具里跑得好好的,手机上线上版就是不对”,大概率是上传的包不对或缓存没更新。有一次酒店详情页面显示的价格和后台配置完全不一致,我们本地代码改来改去都没用,最后把手机上的小程序缓存清掉重新进,结果一切正常。这说明问题出在微信客户端的本地缓存。

但如果清缓存、重新进还是不对,那就需要用小程序反编译工具把线上wxapkg包解开,看看当前线上跑的到底是哪一版代码。反编译工具主要用来解开wxml、wxss、js文件,然后对照本地打包产物,确认线上包是否包含了最新修改。这个操作本质上是“现场取证”,能快速判断是发布流程的锅还是代码逻辑的锅。

再多说一句,我们后来规范了发布流程:每次发版都记录“本地git commit号”和“微信开发者工具上传时的版本号”,并把这个版本号透传到小程序端的某个接口日志里。之后再遇到线上问题,先在日志里查版本号,就能立刻判断是否最新代码,不用动不动就反编译。

6.3 支付功能被限制的处理预案

热搜词里有一条“由于小程序违规,支付功能暂时无法使用”,多商户平台最怕这个。微信对小程序的支付权限有严格的风控,一旦某个主体或某个小程序被判定违规,支付功能可能被暂时限制,整个平台就停摆了。

我们的预案是:第一,所有涉及支付能力的调用都做统一的封装,万一某天支付被限制,可以快速切换到其他支付通道或临时调整业务流程;第二,平台运营端需要有专门的“支付健康检查”入口,定期调用一次小额下单查询API,确认支付链路通畅;第三,一旦收到违规通知,第一时间评估影响范围,该申诉的马上申诉,并准备整改方案。

多商户系统还有一个特殊性:C端用户的小程序支付被限制,影响的是所有商户的订单量。所以我们在平台和商户之间会有个公告机制,支付异常时第一时间通知商户,避免商户自己发现后产生恐慌。

7. 运营提效:房态数据导出Excel与多商户报表体系

7.1 商户为什么那么需要Excel导出

酒店行业的商户运营者,很多并不习惯在后台看图表,他们更习惯“把今天的订单、明天的预订、下周的房价拉出来,放到Excel里面自己算”。所以这次升级我们把Excel导出作为商户端一个非常重要的功能来做,而不是“锦上添花”。

商户端常见导出场景有三种:订单明细导出(按日期范围、入住状态、渠道来源筛选)、房价库存导出(按酒店/房型查看每天的房源可售数和价格)、对账单导出(一个结算周期内的订单金额、平台服务费、应结算金额)。

技术实现上,我们是在后端生成真正的Excel文件,而不是返回JSON让前端组装。后端用Java的EasyExcel,几万条数据导出也稳定;生成的文件上传到对象存储,返回一个临时下载链接给前端。小程序前端拿到链接后用uni.downloadFile下载,再用uni.openDocument打开预览。这个方案比前端生成Excel稳定得多,小程序端对文件处理能力有限,不要勉强。

7.2 导出接口的性能防线:异步任务不能省

第一次做导出功能时,我图省事直接用同步接口查询并返回,结果一个商户导三个月的订单,几万条数据查了十几秒,请求直接超时。后来改成异步任务模式:商户点击导出,后端创建一个导出任务,任务在后台线程池里执行,完成后生成文件并通知。商户再通过任务列表页面下载结果文件。

这样做有两个好处:一是接口响应时间始终在1秒以内,体验不会卡;二是大文件导出不会占用请求线程,避免了把应用拖垮。给做一个参考的简单流程:创建任务 -> 任务执行 -> 结果写入OSS -> 前端轮询任务状态 -> 下载成功。状态用枚举管理:待执行、执行中、已完成、失败。

7.3 多商户报表维度:不止是GMV

报表体系方面,我们最终确定了四个核心维度:

  • 经营概览:今日GMV、订单量、入住率、客单价
  • 房态分析:各房型未来7天可售、满房预警
  • 渠道分析:直连订单/平台引流/线下散客各自占比
  • 评价分析:好评率、差评标签云、回复时效

这些报表都能按商户维度隔离,平台端有汇总版。做报表时一个比较重要的心得是:多商户的报表查询条件一定要预计算汇总表。如果直接在原始订单表上做Group By,商户一多,性能明显下降。我们每天凌晨跑离线任务,把每个商户每天的经营指标汇总到一张日汇总表里,前端展示时直接查汇总表,速度能提升一个数量级。

8. 升级后的四点沉淀:如果重新做一遍,我会怎么优化

多商户酒店预订小程序这轮升级,整体算是达到了预期。但如果再让我重新做一遍,有几个设计我会在一开始就注意,而不是中途返工。

第一,商户端的菜单和操作权限不要在数据库里写死,尽量做成运营端可配置。我们一开始是按代码常量写死的,后来商户分类一多,A类商户和B类商户功能完全不一样,只能加班改代码。如果一开始就直接做动态配置,后面会轻松很多。

第二,所有涉及支付分账和结算的逻辑,从第一天起就做成独立模块。旧代码里支付和订单逻辑耦合比较深,升级时拆了好几个晚上。资金链路的代码应该是全项目中最清晰、注释最完整的部分,没有例外。

第三,小程序前端的请求层要做统一封装,尤其是token过期、登录态刷新、支付状态检查这些跨端逻辑。多商户端和用户端共用同一套请求框架,但各自的error handler不一样,否则商户端报错会弹成用户端的提示文案。

第四,日志体系一定要从项目第一天就配上,而且要有请求ID贯穿全链路。排查支付问题、权限问题、数据不一致问题的时候,一个requestId能省掉大量“贴代码截图”的沟通时间。

最后分享一个我们上线后稳定运行的小技巧:每天凌晨定时跑一次支付订单状态对账任务,把“平台侧订单状态”和“微信支付平台侧订单状态”做全量比对,发现不一致的自动告警。多商户平台资金链路复杂,这笔“自动巡检”的投入,比事后人工对账划算太多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 4:12:15

树莓派Pico ADC深度解析:校准、驱动与硬件定时实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 4:09:46

软件生命周期与过程模型:系统分析师视角的实战拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 4:09:15

C#上位机高并发数据采集与UI解耦实战

1. 这不是“又一个C#教学视频”,而是上位机开发者的实战生存指南 你点开过多少个标着“C#上位机.NET教学视频”的链接?前3分钟讲Hello World,中间20分钟拖控件、改颜色、加按钮,最后5分钟说“完整源码已打包,评论区领取…

作者头像 李华
网站建设 2026/9/11 4:07:30

200 个机器人实时仿真,MuJoCo 分布式并行怎么搭

200 个机器人实时仿真,MuJoCo 分布式并行怎么搭 【免费下载链接】mujoco Multi-Joint dynamics with Contact. A general purpose physics simulator. 项目地址: https://gitcode.com/GitHub_Trending/mu/mujoco 跑 MuJoCo 的时候,你有没有过这种…

作者头像 李华