- 【笔记】云开发聚合实现分页,涉及跨表查询、逻辑计算、判断权限、数据格式化、限制输出
背景: 之前不会用聚合,因此把数据库结构分为了用户表、帖子表、喜欢表。小程序端请求一次列表,要根据帖子列表,循环查询用户表,并且还要做一系列的逻辑运算处理,计算当前帖子的权限、是否喜欢过、喜欢人数、是否有这个帖子管理权限等信息。 这样做有很多弊端: 处理速度慢,资源耗费严重,循环查询肯定慢且耗费资源,一个列表需要21次查询。需要写大量逻辑处理代码,如计算管理权限,喜欢数量、当前用户是否喜欢,格式处理等等。于是使用聚合进行了优化: 跨表查询数据格式化逻辑计算,权限判断、是否喜欢等数据统计,喜欢总人数权限判断,是否为管理员限制输出效果: 之前:上百行代码,多次查询,需要单独判断函数,处理时间在3000ms以上之后:几行代码,一次查询,直接查询时算出结果,处理时间在300ms以内 数据库结构 [图片] 代码实现: const { OPENID } = cloud.getWXContext(context) //构建查询条件 let query = null switch (Number(event.listType)) { case 0: query = db.collection('post').aggregate() .match({ //0我的 '_openid': OPENID }) .sort({ createTime: -1 }) .skip(20 * (event.pageNum - 1)) .limit(20) break; case 1: //1 随机 query = db.collection('post').aggregate() .match({ public: true, // feeling: _.gte(50) }) .sample({ size: 20 }) break; case 2: query = db.collection('post').aggregate() .match({ //2喜欢 likes: _.all([OPENID]) }) .sort({ createTime: -1 }) .skip(20 * (event.pageNum - 1)) .limit(20) break; case 4: query = db.collection('post').aggregate() .match({ //4指定 _id: event.id }) .sort({ createTime: -1 }) .skip(20 * (event.pageNum - 1)) .limit(20) break; } //使用聚合处理后续数据 let listData = await query .lookup({ from: "user", localField: "_openid", foreignField: "_id", as: "postList" })//联表查询用户表 .replaceRoot({ newRoot: $.mergeObjects([$.arrayElemAt(['$postList', 0]), '$$ROOT']) })//将用户表输出到根节点 .addFields({ day: $.dayOfMonth('$createTime'), month: $.month('$createTime'), year: $.year('$createTime'), isLike: $.in([OPENID, '$likes']), //是否喜欢 isLiked: $.in([OPENID, '$liked']), //是否喜欢过 isAdmin: $.eq([OPENID, 'oy0T-4yk7lCRFGDefpFC4Yvx_ppU']),//是否管理员 isAuthor: $.eq(['$_openid', OPENID]),//是否为作者 like: $.size('$likes'), //喜欢该帖子数 face: $.switch({ branches: [ { case: $.gte(['$feeling', 90]), then: 9 }, { case: $.gte(['$feeling', 80]), then: 8 }, { case: $.gte(['$feeling', 70]), then: 7 }, { case: $.gte(['$feeling', 60]), then: 6 }, { case: $.gte(['$feeling', 50]), then: 5 }, { case: $.gte(['$feeling', 40]), then: 4 }, { case: $.gte(['$feeling', 30]), then: 3 }, { case: $.gte(['$feeling', 20]), then: 2 }, { case: $.gte(['$feeling', 10]), then: 1 } ], default: 0 }) //根据心情值判断对应表情 }) .project({ postList: 0, userInfo: 0, liked: 0, likes: 0, city: 0, province: 0, country: 0, language: 0, nlp: 0, saveType: 0, }) //清楚掉不需要的数据 .end() return listData
2020-05-26 - 小程序canvas根据压感实现笔锋效果?
小程序canvas根据压感实现笔锋效果,现在已知x,y,还有压感数值 如何实现笔锋的效果呢?
2020-04-27 - (10)群聊能力
我们在后台收到很多朋友的反馈,希望更好的运用小程序群聊的能力。于是我们想写写群聊的故事。 微信群是小程序在微信这个社交工具下传播的重要途径,我们经常能通过群聊看见小程序的身影。我们希望开发者在实现小程序逻辑的时候,能理解每一个群聊,可实现小程序与各个群聊紧密相关的功能。 基于此,我们开放了群聊 ID(openGID)的功能,供开发者区分标识每个群聊。对于每个群聊而言,小程序所获取到的 openGID 是不变的。但对于同一个群,不同的小程序内获得的 openGID 是不一样的。这一特性类似于标识用户身份的 openID。 拥有了群聊 ID,开发者可以把用户的操作按照群聊 ID 来聚合、沉淀信息,实现群协作功能。此外,通过 openID+openGID 的方式,还可以实现群排行的功能。 例如“群影”小程序以群聊ID聚合用户上传的图片,实现群相册的功能。 [图片] (“群影”小程序) 01 如何获取群聊ID 开发者获取 openGID 要依托于用户转发到群聊的小程序卡片,具体步骤如下: 1 设置带 shareTicket 的分享 在小程序内,开发者调用接口wx.updateShareMenu 带参数withShareTicket:true ,设置当前页面分享到群聊时能获取 openGID。而shareTicket本身就是获取 openGID 的凭证。 [图片] 而 iOS/Android App 分享场景当中,微信SDK也支持把所分享的消息设置成带 shareTicket。 值得注意的是,带 shareTicket 的分享卡片会被固定在某个群聊的,也就是说分享卡片会变成不能被长按转发。 2 由启动参数获取 shareTicket 当用户从某个带 shareTicket 的卡片进入小程序时,开发者可以在App.onLaunch 或者App.onShow 获取 shareTicket,而在小游戏上开发者可以通过监听 wx.onShow 或者同步调用wx.getLaunchOptionsSync 获取shareTicket。 shareTicket 实际上是小程序启动时临时生成的变量,在小程序生命周期内仅作为调用接口的凭证。生命周期结束后 shareTicket 就没有意义了。 3 通过 shareTicket 获取 openGID 开发者调 wx.getShareInfo 接口以 shareTicket 换取 openGID 的加密数据包,这是为了保证开发者服务器收到的 openGID 是可信的,开发者需要把加密数据交由后台解密,拿到真实的 openGID。数据加密机制更多请参看[数据加密相关文档]。 注意事项 ▷▷ 由于2018年7月5日起,新提交发布的小程序版本将无法通过用户分享获得群ID,即开发者通过wx.onShareAppMessage获取群 ID 的方式将不再支持,后续仅支持通过启动参数获取群 ID。请开发者及时调整。 02 群聊名称组件 除了群聊 ID 以外,开发者还能使用群聊对应的名称。出于保护用户隐私的考虑,我们不会把真实的群聊名称暴露给开发者,而是通过 open-data 组件让开发者在小程序前端展示某个 openGID 对应的群名称。 [图片] 其中 openGID 就是小程序获取到的群聊 ID。 open-data 组件只展示那些用户所在群聊ID对应的名称。如果设置了非微信提供的群聊 ID,将无法展现群聊名称。 03 群聊功能 1 分享设置 wx.updateShareMenu : [查看文档] 2 小程序启动参数 App.onLaunch / App.onShow : [查看文档] 3 小游戏启动参数 wx.onShow : [查看文档] getLaunchOptionsSync : [查看文档] 4 获取 openGID wx.getShareInfo : [查看文档] 5 群名称组件 open-data>/open-data>: [查看文档]
2018-08-17 - 小程序关联公众号策略调整
各位开发者,大家好。 目前,小程序需要与公众号关联,才可被使用在公众号自定义菜单、模板消息、客服消息等场景中。而公众号关联小程序时,需要小程序管理员确认,该环节增加了开发者之间的沟通成本。 为了降低公众号与小程序间的合作门槛,我们将调整小程序关联公众号策略如下: 公众号关联小程序将无需小程序管理员确认。 取消“小程序最多关联500个公众号”的限制。 若希望小程序在被关联时保留管理员确认环节,可前往“小程序管理后台-设置-基本设置-关联公众号设置”修改设置项。 公众号文章中可直接使用小程序素材,无需关联小程序。 开发者可在“小程序管理后台-设置-关联设置”中管理已关联的公众号。 微信团队 2019.04.04
2019-04-08 - 小程序button去除边框问题
在网上搜了好多,但都是没有解决。所以来求助。 点击抽奖因为要获取用户手机号,所以用了button。但是修改了好多,发现一直存在黑色的边框。 上代码 html [代码]<[代码][代码]button[代码] [代码]class[代码][代码]=[代码][代码]"chou"[代码] [代码]style[代码][代码]=[代码][代码]'background-image:url(https://xxx.cn/playbtn.png);'[代码][代码]plain[代码][代码]=[代码][代码]'true'[代码] [代码]bindtap[代码][代码]=[代码][代码]"{{isclick}}"[代码] [代码]open-type[代码][代码]=[代码][代码]"start"[代码] [代码]bindgetphonenumber[代码][代码]=[代码][代码]"start"[代码] [代码]></[代码][代码]button[代码][代码]>[代码]css [代码].chou {[代码][代码] [代码][代码]position[代码][代码]: [代码][代码]absolute[代码][代码];[代码][代码] [代码][代码]top[代码][代码]: [代码][代码]50%[代码][代码];[代码][代码] [代码][代码]left[代码][代码]: [代码][代码]50%[代码][代码];[代码][代码] [代码][代码]transform: translate([代码][代码]-50%[代码][代码], [代码][代码]-50%[代码][代码]);[代码][代码] [代码][代码]width[代码][代码]: [代码][代码]180[代码][代码]rpx;[代码][代码] [代码][代码]height[代码][代码]: [代码][代码]180[代码][代码]rpx;[代码][代码] [代码][代码]z-index[代码][代码]: [代码][代码]10[代码][代码];[代码][代码] [代码][代码]margin[代码][代码]: [代码][代码]auto[代码][代码];[代码][代码] [代码][代码]padding[代码][代码]: [代码][代码]0[代码][代码];[代码][代码] [代码][代码]background-[代码][代码]size[代码][代码]: [代码][代码]180[代码][代码]rpx [代码][代码]180[代码][代码]rpx;[代码][代码] [代码][代码]background-repeat[代码][代码]: [代码][代码]no-repeat[代码][代码];[代码][代码] [代码][代码]border[代码][代码]: [代码][代码]none[代码][代码];[代码][代码]}[代码][代码]button::before {[代码][代码] [代码][代码]border[代码][代码]: [代码][代码]0[代码][代码];[代码][代码] [代码][代码]border[代码][代码]: [代码][代码]none[代码][代码];[代码][代码]}[代码][代码]button::after {[代码][代码] [代码][代码]border[代码][代码]: [代码][代码]0[代码][代码];[代码][代码] [代码][代码]border[代码][代码]: [代码][代码]none[代码][代码];[代码][代码]}[代码][图片]
2018-11-27 - 利用css圆锥渐变实现环形进度条
[图片] 锥形渐变的正式语法如下: conic-gradient( [ from <angle> ]? [ at <position> ]?, <angular-color-stop-list> ) 可以看出锥形渐变由3部分组成: 起始角度 中心位置 角渐变断点 其中起始角度和中心位置都是可以省略的 这里只用了角渐变断点 background-image: conic-gradient(green 80%,#fff 80% 100%); 主要实现就两步 1.利用conic-gradient画一个圆 [图片] 2.利用任意元素做个内圆遮挡 [图片] 最外层的背景色原来是白色的,这里为了便于识别改成了灰色,现在将灰色的背景还原为白色 [图片] 环形进度条完成,就这么简单 通过动态的设置green的值就可以改变进度条的值 补充:还可以实现渐变进度条效果 [图片] 代码片段: 利用css圆锥渐变实现环形进度条
2020-06-22 - weui内置扩展库使用步骤
更新最新的 nightly 版开发者工具 在app.json里新增 “useExtendedLib”: { “weui”: true } 在使用的页面json文件应用组件,比如在index.json里 { “navigationStyle”:“custom”, “usingComponents”: { “mp-navigation-bar”:“weui-miniprogram/navigation-bar/navigation-bar” } } wxml文件使用组件,比如在index.wxml里 <mp-navigation-bar title=“首页”></mp-navigation-bar> 验证有无生效。 示例代码片段:https://developers.weixin.qq.com/s/zB6mFrmc7elr 备注: 文档里说目前暂不支持在分包中引用,但据代码测试,分包也能使用。 各位觉得有用的话,给个赞呗。
2020-10-28 - (6)微信登录能力优化
小程序和小游戏内的用户登录,我们推荐使用以下两种方式获取用户信息: ▷ 按钮组件的登录方式,用户主动点击按钮可以拉起用户授权弹框,获取用户头像、昵称等信息; ▷ 在不获取用户信息的情况下,可展示用户头像昵称。 用户在没有任何操作的情况直接弹出授权的登录方式将逐渐不再支持,受影响的有 wx.getUserInfo 接口,以及 wx.authorize 接口传入 scope="scope.userInfo" 的情况。 1 为什么平台要做接口调整? 我们提供了 wx.login 和 wx.getUserInfo 接口,用于获取用户的 openID 和基本信息。 推出这两个接口的初衷是希望:当用户使用小程序时,只有访问到真正需要登录的页面,才需要授权并登录。 但在实际应用中,我们发现很多开发者在打开小程序时直接弹出授权框,如果用户点击拒绝授权,无法使用小程序。 在没有任何提示和背景的情况下,直接弹框想要获得用户信息授权,用户脑子里可能会闪过几个哲学问题: 你是谁? 我在哪里? 我为什么要同意? …… 相当一部分用户下意识会点击“拒绝“授权——这样不合理的登录流程既造成了用户的困扰,还使得用户流失。 用户通过小程序可以快速获取服务,因此在访问小程序的第一个页面非常重要。 对于一个互联网产品而言,第一个页面决定了用户对这个产品的认知,用户会选择是否继续使用这个产品。 一个优秀的互联网产品,能够给用户留下一个好的第一印象,用户可以快速了解你的产品,接收到你想要传递的服务信息,从而产生相应的操作行为。 一个优秀的小程序会吸引用户在小程序里进行探索,完成你期望他们去做的事,比如会员注册、商品购买等。 试想一下如果一个品牌的商品官网,一进入要求用户登录才能查看产品信息是什么感觉呢? 因此良好的用户登录体验非常重要。 2 如何设计登录流程? 用户打开小程序时,看第一眼的时候,开发者需要专注以下两个目标: ▷ 精准快速地传达产品理念,开发者要让用户能够快速了解自己的产品和服务; ▷ 将用户流量进行转化,让用户能方便操作或者交易。 一般而言,用户打开小程序后看到的第一个页面,先不要直接弹出授权框,第一个页面可以包含以下内容: ▷ 展示你的小程序功能(如产品、服务、活动等) ,让用户清晰地知道小程序是做什么用的,这些内容可以是你的精选内容; ▷ 激发用户的探索欲,通过描述或者图片吸引用户注意力; ▷ 按照自己的产品目标,给用户提供清晰明确的下一步操作(查看详情、购买等)。 如果某些特殊小程序在使用前一定需要用户登录,或者已经进行到需要用户登录的操作时,可以将 button 组件(其中 open-type 属性指定为 getUserInfo)放置到页面中,页面上可以大致说明以下要点: 为什么需要我授权? 需要我什么信息? 授权后我得到什么好处呢? 接下来在页面上放置一个明显的登录按钮, 建议这个页面上不要有额外的点击区域,以免分散用户注意力,让用户专注于登录这件事情。 3 简单的开发建议 1 当用户打开小程序时访问第一个页面时,先通过 wx.login,获取用户 openID 。这时无需弹框授权,开发者拿到 openID 可以建立自身的帐号 ID。 2 在第一步中,拿到 openID 后,判断是新用户还是老用户。如果是老用户,可以直接登录;如果是新用户,可先在小程序首页展示你的信息服务,让用户对这个小程序有大概的了解,再引导用户进行下一步的操作。 3 当需要获取用户头像昵称的时候,对用户展示一个登录页面,这个页面只有一个最重要的操作,引导用户进行登录。 小程序中,在页面中加入一个 button 按钮,并将 open-type 属性设置为 getUserInfo 。 以小程序为例: 微信登录 对于功能较简单的小程序或者小游戏而言,如果不是必须要获得用户的头像昵称,建议可先通过wx.login 拿到 openID 后,使用 open-data 方式或者开放数据域的方式展示用户信息,整个过程都无需用户授权。 Tips: 1、在用户登录后,开发者需要存储用户的 unionID,而且建议只把 unionID 作为互通的用户标识,不要直接使用 unionID 作为用户 ID。因为一旦小程序迁移到其他的开放平台下,unionID 是会改变的,而 openID 是不变的。 2、用 button 组件的方式获得用户授权后,调用 wx.getUserInfo 就可以直接获取用户信息。这个的意义在于获取过一次之后,用户有可能改昵称头像,因此为了及时同步,最好是定期获取用户信息。 这里两个小提示: ▷ 定期使用 wx.getUserInfo 获取并更新用户的信息; ▷ 如果用户授权过一次之后,又在设置中关掉了授权(或者本地删除了小程序),那这时再调用 wx.getUserInfo 也是不会成功的,需要重新获得授权。 相关开发文档参考: ▷ 小程序 1、小程序 wx.login 2、button 组件,并将 open-type 指定为 getUserInfo 类型,获取用户基本信息 3、open-data 展示用户基本信息 ▷ 小游戏 1、小游戏 wx.login 2、用户信息按钮 UserInfoButton 3、开放数据域下的展示用户信息
2018-08-17 - (19)文件系统能力
文件系统能力 文件系统能力可便于用户在客户端保存文件资源,并在下次启动客户端之后可以使用已保存的文件。 只要用户不主动删除小程序或小游戏,并保持一定的使用频率,文件都可以一直被保留。 合理的使用文件系统能力来缓存资源文件,可以给开发者更好的使用体验。 今天,我们来分享文件系统能力的小故事。 1 文件系统的演进历史 小程序在最早发布的版本中就已提供了最基础的文件存储和删除接口:wx.saveFile、 wx.removeSavedFile ; 对于绝大部分的小程序来说,这两个接口已经能够满足开发者的需求。但对于小游戏来说,需要更完整的能力来做支撑。 因此,发布小游戏的时候我们便提供了一套更完整的文件管理系统:FileSystemManager,其中主要包含了目录管理、文件内容读写等能力。 2 文件系统的设计背景 文件系统能力是应小游戏开发需求的迭代而逐步增强的。在小程序的场景下,很多时候只是需要把一个图片或视频资源缓存起来便可继续使用,文件内容与文件存储的目录结构都不是开发者所关心的。 但是在小游戏场景下情况则不同—— 一方面,小游戏除了有图片和视频文件、还有游戏引擎生成的配置文件,游戏需要能够去读取并理解配置文件的具体内容; 另一方面,游戏使用的资源文件会比普通小程序更多,若没有内容目录管理的功能,维护成本会变高。 除此之外,由于小游戏代码包大小限制只有4MB (加上分包最多8MB),对于一些偏重的游戏,资源甚至容易超100MB。 因此在此大背景下,我们给文件系统主要增加了目录管理、文件内容读写等两项接口—— 目录管理的需求场景是在使用游戏引擎时需要按目录来管理资源文件,文件内容读写的需求场景是在使用游戏引擎时需要读取配置文件;同时,我们对小游戏类目的本地存储容量的规范限制扩容到50MB。 开发者可能会疑惑,为什么在小程序的文件系统中会有一些功能相接近的接口?例如,想缓存一个文件,可以用 saveFile 或 copyFile ;再比如 removeSavedFile和 unlink 都可以用来删除一个文件。 上述情况的原因是我们在早期便提供了基础的文件存储接口 saveFile 和removeFile ,但不提供自定义目录相关的能力,开发者调用 saveFile 之后只能得到微信返回到的一个随机文件名。 小游戏应运而生的同时也增强了对文件系统能力扩展的需求,为了保证向后兼容,我们保留了这批基础接口,并在这个基础上增加了目录管理接口以及对应的文件操作接口。因此,便出现了上述一些相似接口的情况。 3 文件系统的优势—存储隔离 有不少开发者询问过关于文件存储的问题,他们担心文件内容被其他小程序读取到,也担心多个登录用户之间的文件内容会互相影响。为了保证用户的隐私安全,也为了保证小程序的数据安全,本地文件存储的一个重要规则便是保证隔离。 文件被存储到本地后,会以小程序账号和用户账号两个不同的维度来区分和隔离。即:同个微信用户使用不同小程序之间的文件存储会互相隔离;不同微信用户(在同一台手机中)使用同个小程序时,不同用户间的文件存储也会互相隔离。 [图片] 4 适当的存储容量 考虑到存储的问题,我们规范了小游戏文件存储的容量。普通小程序是10MB,小游戏则是50MB,当文件存储超出限制时,写入的文件会失败。 功能上线以后,我们曾收到过若干宝贵意见与反馈,希望能提高容量限制。但在经过反复论证与评估后,我们认为如果将文件存储的容量再往上提,就会有用户新增需要管理或清理手机存储空间的需求,小程序和小游戏将会变得不再“小”了。对于资源文件超过上述标准限制的小程序与小游戏,应该合理地管理本地文件,及时清理不常用的文件,这样在大多数情况下,手机存储空间便能保证顺畅。 更多关于小程序文件系统能力的信息,可查阅 接口文档 。
2018-08-21 - (7)小程序音频能力介绍
小程序支持播放和录制音频。小程序播放音频的方式有两种:内部音频和背景音频。 1.内部音频支持用户在使用小程序过程中播放音效; 2.背景音频支持在用户离开小程序后继续播放音效。 一、播放音频 (一)背景音频 播放背景音频 背景音频接口适用于音乐类小程序,如“音乐站”、“QQ 音乐小电台”。通过 wx.getBackgroundAudioManager() 接口可以获取全局唯一的背景音频管理器,所有关于背景音频的操作都由它来实现。 微信内只有一个背景音频,一个小程序开始播放背景音频之后,就持有背景音频播放器,只要当前小程序持有背景音频播放器,即使这个小程序进入后台(即用户离开小程序),也可以继续使用背景音频接口,且当前小程序不会被微信主动回收;一旦背景音频播放器被抢占(可能是其他小程序、微信内其他音乐、其他 App 的音乐),则小程序不再持有背景音频播放器。 [图片] [图片] (音乐站小程序) 在系统播放面板显示和控制 通过设置标题、专辑名、歌手名、封面图等属性,小程序音频接口支持在系统音乐播放面板显示出来。通过响应系统面板的点击事件([代码]onPrev[代码],[代码]onNext[代码]),可以实现列表播放。 [图片] (系统播放面板控制效果) (二)内部音频播放内部音频内部音频适用于所有小程序,尤其是游戏类目的小程序,如“跳一跳”。通过 wx.createInnerAudioContext() 接口可以创建一个音频实例。 [图片] 每个小程序可以同时持有和播放多个内部音频,但一旦小程序进入后台(onHide),所有内部音频都会被暂停,且在用户回到前台(即打开小程序)之前无法再被播放。 静音下也能播放在 iOS 系统中,内部音频默认遵循静音键设置。如果希望在静音时也能播放,可以设置 [代码]obeyMuteSwitch[代码] 为 [代码]false[代码]。 [图片] 安卓系统没有统一的静音开关,暂不支持此特性。 处理音频中断事件以游戏为例,在游戏中,经常有播放使用内部音频来播放游戏背景音乐的场景。音频中断事件指的是在游戏期间,音频被系统打断时触发的事件。音频中断事件分为中断开始和中断结束事件,分别使用 wx.onAudioInterruptionBegin() 和 wx.onAudioInterruptionEnd() 来监听。 以下事件会触发音频中断开始事件:接到电话、闹钟响起、系统提醒、收到微信好友的语音/视频通话请求。被中断之后,小游戏内所有音频会被暂停,并在中断结束之前都不能再播放成功。 中断结束之后,被暂停的音频不会自动继续播放,游戏可监听音频中断结束事件,并在收到中断结束事件之后调用背景音乐继续播放。 [图片] 如果游戏的逻辑强依赖音乐的播放(如音乐类游戏),需要在音频开始中断的时候暂停游戏 [图片] [图片] (跳一跳小游戏) 二、录制音频 通过 wx.getRecorderManager 接口,可以获取全局唯一的录音管理器。 [图片] 实现边录边传 默认情况下,录音结束后会生成一个本地文件,并通过回调返回本地文件的地址。对于实时性要求比较高的小程序(如“面对面翻译”),可以通过设置 [代码]frameSize[代码] 参数来设置一个帧的大小,这样每录制指定帧大小的内容后,会通过 [代码]onFrameRecorded[代码] 回调返回本次分片的数据。 [图片] 注意事项:不建议使用的历史接口上述接口可以满足所有音频相关的需求。除了上述接口,小程序内还有若干跟音频相关的接口(如 [代码]wx.startRecord[代码]、[代码]wx.playVoice[代码]、[代码]wx.playBackgroundAudio[代码] 等)。这些接口由于早期设计存在一些缺陷,我们不建议继续使用。
2018-08-17