开发经验心得

业务配置

这部分主要是针对SDK的云端控制、业务隔离等的一些思考。通过全局限频、配置管理等模块一方面可以降低业务之间的相互影响,也更有利于SDK一些简单问题的修复。

后台控制

这部分主要是对于后台接口的调用的控制。包括权限控制、和全局限频等。权限控制用于控制应用对于接口访问的合法性,全局限频主要是针对每个APP,对于调用量应该给出限制。防止单个业务被攻击或者流量突然升高引起其余业务接口调用的不稳定。
这里额外补充两点点关于全局限频的。

  • 全局限频怎么做
    最简单的就是有一个地方记录调用额度,定时刷新额度,在接口调用时,每调用一次去减一。这种对于单台服务器比较容易,当分布部署以后就比较复杂,需要考虑更多的细节。
    因此一般的分布部署都是通过另一种方法:分布部署必然会有负载均衡,因此根据机器的数量将应用调用总量均分到每台机器上,当对应机器的限额用完,就提示超频。
  • 全局限频怎么限
    首先所有的应用会有一个预先的配额,作为预警配额,这个配额是根据应用情况预估的;其次除了预警配额还会有一个阈值的配合

客户端配置

云端配置是指客户端的一些开关、关键是配置值可以通过后台来修改。这样做可以:

  • 当有业务配置错误的时候,我们可以通过云端控制修改配置,而无需业务修改版本。
  • 当某写功能出现异常的时候,可以通过云端修改配置,临时关闭相关功能。

数据上报

关键日志

关键日志上报同样是为了SDK的开发方便问题定位。主要用在以下场景:

  • 当游戏客户端遇到问题的时候,打开指定用户的日志控制开关,他就可以把本地日志远程上传。协助开发者分析错误,定位问题。
  • 对于业务逻辑下的某些异常逻辑增加关键日志自动上报,就可以协助开发者提前发现和定位解决问题,不再被动。

数据上报

数据上报的意义更加重大了,尤其是当数据上报和关键日志、云端配置配合,就是客户端问题定位的神器。这个地方的数据上报不包括一些接口调用数据等正常数据的上报。
通过异常数据的实时上报,我们可以:

  • 建立一套客户端的监控和告警机制,实时了解客户端版本情况。当某一接口异常的时候,提前开启关键日志分析问题。
  • 除了解决问题,客户端的异常上报如果包含了后台接口的返回异常,就可以同时监控后台接口的稳定性,尤其是当后台接口的监控和告警不可信的时候,可以及时通知后台。(别不信,现实中这种情况太多太多)
    安全性
    网络请求、配置、DB数据存储等,采用更高效、更安全的处理措施。具体的例如:
  • 网络请求
    可以使用https,既可以提高安全性,还可以防止国内运营商常见的DNS劫持等问题。
  • 配置、DB数据存储
  • SDK的配置、SDK相关的数据以及用户数据都是SDK的核心内容,一定要使用有效的加密方案来保存。对于加密以后带来的性能问题可以通过尽可能少的加解密次数以及不同级别的加密算法等来解决。
  • 加解密的密钥以及加解密的算法的本身的安全性也要保证,建议是直接放在SO,然后再通过SO的加固增强安全性。
  • 所有的加密行为最终使用的加密密钥最好时和设备相关,防止因为用户手机被恶意程序攻击远程拷走应用私有目录的信息以后带来问题。而因为imei和mac地址的不唯一,建议加密时不要简单的使用imei或者mac地址。

SDK的数据有哪些

接口调用数据

接口调用数据,包括接口调用的成功率,失败率,调用次数等。这部分主要用来监控接口的稳定性,及时的发现问题。另一方面这些数据也是SDK的品牌数据,用于进行数据推广等。

  • 开发者、接入应用相关数据。
    开发者和应用相关的数据包括,开发者的地域分布、接入应用的类型、单开发者接入应用的数量等等。这部分数据有助于了解当前开发者的活跃地带,为商务提供支持,了解目前的接入应用的分布,可以了解应用市场的走向。

SDK的使用习惯数据。

SDK的使用习惯数据包括某个功能开发者一般用来做什么等,例如SDK面向用户的公告,开发者一般用来发公告还是活动,一般同一时间内有效的会有几条,发送的频率一般是多久等。这部分数据可以协助新的开发者更有经验的使用SDK的相关功能,同时将SDK的功能最大化。

SDK的用户数据。

用户数据内容比较多,再细分一下,包括用户个人数据,用户设备数据,用户使用习惯数据、用户和应用的相关性数据。

  • 用户个人数据,包括用户的地域分布、性别、职业等。这部分数据用于制作用户画像,让运营更加了解用户的实际情况。
  • 用户设备数据,包括用户设备的机型、系统、系统版本、内存、CPU、网络、分辨率、DPI、传感器等数据。这部分可以让开发者们更了解当前使用者的设备情况,更好的做兼容和后续方向的规划。
  • 用户的使用习惯数据,包括使用时段、使用时长、打开次数、打开频率、留存、活跃等数据。通过这部分数据,可以让运营更加了解用户的使用习惯,更容易掌握推广的时机。
  • 用户应用的相关性数据。包括单一用户手机上同类型APP的安装数量,每天的使用APP的数量等数据。这部分数据对于市场的开拓会有很大的效果。
    设计原则

接口名称、参数名称要足够清晰

一个牛逼的接口名称可以替代无数的注释

一个接口只做一件事

  • 一个接口只做一件事。如果有两个比较接近的功能,但是用一个接口实现有点麻烦,那就用两个接口,不要为了减少接口而生硬的把两个接口合为一个。

接口参数要尽可能少

  • 接口调用的参数要尽可能少,SDK能自身获取的就不要让开发者继续传递,尽可能少的在一个接口中使用同一数据类型的参数,如果确实很多,建议封装为Object作为参数。
    接口参数要一定要校验、需要转义或者转换的一定要尽可能早的处理

所有接口参数必须要做合法性校验。不要让别的接口去保证调用你接口的参数一定是合法的。

  • 所有接口做的第一件事就应该是对参数做合法性校验。不要等到逻辑跑完大半了再告诉参数不合法,调用失败。Are u kidding me?
  • 对于需要转义、需要类型转换等的参数,一定要处理,而且尽量尽早的去处理,虽然客户端没有XSS或者SQL注入什么的,不代表你就可以不用考虑

通用的名称要统一

  • 即使再小的系统,也会有一些通用名词,对于一些通用名词或者模块的叫法、写法一定要统一。忽然发现在这一系列文档中,有很多东西自己叫的都不统一,又打脸了。

关于同步和异步接口

  • 可以同步的接口,一定不要异步
  • 能不用全局回调就一定不要用全局的回调
  • 一定要用全局回调最好按照模块分开,一个模块一个回调。开发者只需实现他关心模块的回调即可。无关模块的回调设置与否对SDK的正常使用没有影响;另外每一个回调里最好又能区分回调属于哪次调用的字段。
  • 同一个回调里面的接口尽可能的少,可以合并的尽量合并。

关于多线程

关于多线程,其实本来和SDK关系不大,但是个人觉的有必要专门说明一下。

关于UI线程

  • SDK除非必须,不要使用应用的主线程,就算使用也只能是简单操作,不能长时间占用。
  • SDK应该有一个专门的线程来处理SDK相关的操作。
    这样做最主要的目的就是尽可能的减少SDK对应用本身的影响,尽可能减少SDK引起的ANR等问题。具体案例就不说了,太废话了。上面的结论就带来了下面的问题:既然UI进程的影响要尽可能小,那就带来一个进程间怎么通信的问题,Handle就是解决神器。

怎么使用handle

  • 所有耗时、异步操作都通过handle扔给SDK的线程去处理。处理结束以后再把结果通过handle发给主线程。
  • 任何时候主线程只做一件事,UI调整。所有的耗时操作:读取文件、读取DB、网络数据读取、网络请求发起等全部都要不要用UI线程去处理。
    这里就不讲案例了,之前博客已经讲过一些因为主线程处理耗时操作引起的血淋淋的案例了。这里就说下目前个人比较常用的一个可能有耗时操作的函数的处理流程吧。
    1.调用方调用接口
    2.接口参数校验,校验合法,发给SDK的线程
    3.SDK的线程收到消息开始处理内部逻辑,处理结束发起回调
    4.对返回结果进行二次处理,发给UI线程
    5.UI线程通过回调接口回调游戏

关于第三方平台的常量

  • 对于第三方平台的常量例如错误码等,最好是自己封装一层提供给开发者。不要直接将第三方平台暴漏给开发者

关于第三方平台的配置

  • 对于第三方平台的各种配置,比如appid等,最好是仅仅用于第三方平台的逻辑中,不要为了省事把第三方平台的配置用于自己的业务逻辑

关于配置

这里主要说一些关于模块开关,平台配置相关的内容。主要涉及到配置文件的处理和下发。这部分内容主要探讨一下吧,还没有很好的结果。

配置通过什么形式下发

不管是模块的开关还是接口的权限,都应该可以后台控制。当然前台最好也要有配置文件,可以减少一些无用的请求。而且在后台不能控制的时候,前台的开关还是很有必要的。
在同时有前后台的开关或者配置的时候,记得优先使用后台的配置,不然你搞个后台开关有毛用(不多说,亲身经历。)

配置放在什么位置

目前我们项目的配置文件放在assert目录下,目前遇到的问题是我们云端下发配置文件的时候比较麻烦。这里就出现另一个问题,云端下发是下发配置文件还是下发配置开关。如果下发配置开关,那就放在什么位置都可以,如果下发配置文件,配置文件下发在什么位置就很重要。

配置使用什么格式

  • 所有的配置文件用key-value的方式来保存
  • 所有的配置项的Key建议增加统一的前缀,例如MSDK_。

文档应该包含的内容

流程相关

商业的接入流程:例如怎么签订合约、怎么申请或者获取APPID、对于有权限的接口怎么申请权限等流程。

SDK介绍相关

SDK介绍

介绍SDK的能力、包括的模块、名词解释、SDK下载地址、版本历史等内容

接入指引

主要介绍开发者从下载完SDK到将SDK合入自己工程的工作。包括SDK包内容介绍、SDK的架构的简单介绍、开发者接入SDK、更新SDK的操作指引、打包的混淆规则等内容。

API文档

按照模块区分介绍对应模块API的使用方法。建议包含模块介绍、模块接入需要修改的配置、每个API的说明(包括使用场景、接口声明、调用事例、异常处理、注意事项等)、模块接入的FAQ以及一些常见问题的定位方法。其中需要有一个模块是提供一些SDK的基础方法,例如获取SDK的版本号等功能。(在实际中我们发现游戏有时候还是不够熟悉整个模块的接入方法,因此对于具体的模块,我们还会提供整个模块的推荐用法。)

性能说明

主要是介绍SDK的一些测试指标,例如SDK包的大小,运行时对内存、CPU的占用等性能数据。

版本号

字符版本号

目前的主流版本号都是分三段:主版本、特性版本、修正版本。例如:2.0.1这种。主版本号主要用于大版本的发布,特性版本主要用于更新迭代,修正版本号主要用于bug修复。

数字版本号

关于数字版本号,可以自己来直接维护,也可以通过一定的规则来生成。如果是自己维护,像SDK存在多个分支同时维护的情况下,维护起来就会很复杂,问题很多。这里给两个个人觉得可行的方法吧。

  • 最完美的方案还是直接使用git的提交次数,或者buildNo 作为版本号,这样可以完美做到版本号全局唯一
  • 如果数字版本号一定要由字符版本号生成,同时建议字符版本号中的修正版本号用两位位来围护,例如2.0.00;或者2.0.01这种。对应的转化出的数字版本号为20000和20001。,或者在版本比较的时候逐级转化为浮点数去比对

版本号放在哪里

这里还是直接写经验吧:
首先:

  • 代码里面手动写一个。这个用于开发标示和了解当前的版本号。为了防止开发发版本忘了改版本号,建议增加到版本提测的checkList点击查看
  • 自动构建打一个。
    我们在自动构建时,会通过构建脚本生成一个版本配置相关的文件,在这个文件里面,我们会打上一些对于我们后续定位问题有需要的内容。其中就包括版本号。具体的配置文件事例如下:
  VERSION=1.3.1
  SVN_REVISION=55384
  BASE_LINE=Tag_AGSDK_1.3.1.304_55384
  DATETIME=2015-04-29 12:23:56.279
  • 应用启动时,进行版本对比,二次确认。虽然我们不想,但是总会有可能出现版本号的错误或者不一致。所以我们在应用启动的时候会比对上面的两个版本号。当版本号不一致时,我们会生成对应的错误日志。最终通过多重方式确保生成一个安全的,可用的版本号。

测试

关于提测流程

在关于版本的总结中,已经说了对于一个版本提测,要有一个专门的提测流程。对于一些重点项还该有具体的说明(结合自身实际制定了一个提测前的版本checkList)。让版本提测有一个标准化的提测流程,只有流程化了才能减少出错的机会,提高版本质量,降低开发和测试的工作量。
当然,对于我们来说,这个相对比较简单,内部有专门的系统去管理版本的提测,因此就已经有了一定的流程。我们的重点就变成了一些重点项的检查。目前的提测流程主要包括三部:

  • 出自测版本包,根据版本功能完成功能自测和一些基础的自动化测试。
    这可以理解为先消除一些最基本的问题,用一些自动化工具先检查一边。
  • 检查版本提测前重点检查项并记录结果。这是提测前最重要的一环。
    这也是保证版本出现低级错误的关键,我们根据一次次爬坑的经验总结了一系列的检查项(后面专门说明)。开发者可以根据自身需要制定自己的流程。
  • 准备版本更新说明:这里要详细列举这次版本变更的内容以及对应的测试方法。
    有时候有些功测试虽然知道需求,但是她并不知道怎么测试,写清楚详细的步骤有助于他们初步了解这个功能的具体实现。
  • 出包、确定最终的版本代码tag,提交测试。
    一般前两步确认没有问题以后,这一步几乎花不了多少时间。
    推荐做法之新版本、回归版本提测:
提测前版本相关重点检查项目: 

    对比TAPD需求单和BUG, 确定是否完成所有需求以及所有BUG修复
    自己根据TAPD需求单上的需求跑过一遍自测
    检查文档是否对应新增的添加了功能接入说明
    检查VERSION.md是否说明了所有更新内容
    检查MSDK/assets/ 下面的msdkconfig.test.ini  msdkconfig.debug.ini 是否正确设置。可选模块是否有开关完全关闭
    检查版本号配置是否正确
    检查第三方sdk的版本是否正确
    保证自动化测试所有接口通过
    版本通过金刚审计
    按照冒烟测试测试用例完成冒烟测试
    
提测前代码Review重点检查项目: 

    发布前跑一遍FindBugs, 解决工具发现的所有BUG, 确认无需解决的BUG提示必须注释说明
    检查资源文件使用方式, 必须使用反射方式调用
    检查Logcat打印的内容, 避免在Log中打印关键信息
    检查RDM打包版本号配置是否正确
    检查避免使用硬编码值和魔法数字,如果有,必须说明        确认所有TODO标签已经完成, 没有遗漏, 确定要遗留的问题必须注释写明原因        检查相关的so是否都已经提交到SVN

推荐做法之紧急版本提测:

 提测前版本相关重点检查项目:
 
    检查文档是否对应新增的添加了功能接入说明
    检查VERSION.txt是否说明了所有更新内容
    检查代码中版本号配置是否正确
    检查RDM打包版本号配置是否正确
    保证自动化测试所有接口通过
    对比TAPD需求单和BUG, 确定是否完成所有需求以及所有BUG 已经修复
    确认所有TODO标签已经完成, 没有遗漏, 确定要遗留的问题必须注释写明原因
    检查相关的so是否都已经提交到SVN
    按照冒烟测试测试用例完成冒烟测试

紧急版本一般都是bug修复,也不会有功能更新,也不会大的修改,因此只是把一些容易忽视的地方确认一次就可以了。
增加几点说明吧:

  1. 由于版本会有提测版本,回归版本或者紧急版本,因此我们对不同的版本也会有不同的ckecklist
  2. checkList是我们为了提高版本质量而增加的,因此我们都会仔细核对,而不该是走个过场。
  3. 我们也会根据实际情况和测试协商对checkList做一些调整(这个list也是测试和我们根据经验具体总结的)

接下来我会对新版本提测的list做一个介绍:
提测前版本相关重点检查项目:

  • 对比TAPD需求单和BUG单, 确定是否完成所有需求以及所有BUG修复。
    TAPD是内部一个管理需求和bug的系统,这里也就是说确认下这个版本计划的内容是否都已经完成。并根据实际修改需求或者bug单的状态。
  • 自己根据TAPD需求单上的需求跑过一遍自测
    第一步是确认所有的需求是否已经处理,而这里就是验证功能是否符合需求。这里我们会把一开始流程中提到的第三步在这里完成。边验证功能,边整理提供给测试的测试方法。一般这一步由对应模块的开发整理,然后汇总给版本负责人。
  • 检查文档是否对应新增的添加了功能接入说明
    我们的文档使用wiki,而且我们一般会再版本提测时提前告知新版本的内容,方便游戏提前了解。以前我们出现过版本发了,但是有一个模块的文档一直忘了写上去,那时候还不是wiki,文档是在版本包,为此还不得不再走一次打包提测的流程。所以直接增加进来。
  • 检查VERSION.md是否说明了所有更新内容
    初衷和原因与上面的文档一致
  • 检查MSDK/assets/ 下面的msdkconfig.test.ini msdkconfig.debug.ini 是否正确设置。可选模块是否有开关完全关闭
    这个可以理解为对外提供的配置是否正确。我们的SDK给测试和最终发布的包的配置并不一样,所以需要确认不同环境对外发布包的推荐配置是否正确,之前也遇到过某个默认关闭的开关再配置中写为打开。结果有游戏没有留意直接拷贝过去,导致功能被打开,但是游戏并没有接口权限,导致游戏接口被告警。
  • 检查版本号配置是否正确
    这里主要是检查代码中的版本号是否正确,我们为了确保版本号正确,有几个保护机制。这个在关于版本号的SDK设计心得之版本号(点击查看)会重点说明。
  • 检查第三方sdk的版本是否正确
    由于我们的SDK还接入不少SDK,因此需要确认第三方的版本是否正确。同时也是为后面游戏来咨询做准备。
  • 保证自动化测试所有接口通过
    这里是我们对于所有提供给游戏使用的外部接口的一个JUnit test单元测试。只是简单的合法参数调用测试,没有详细的边界和异常测试(这部分专业测试会做)。
  • 版本通过金刚审计
    金刚是一个内部漏洞扫描平台,我们会把demo应用提交平台来确认代码没有漏洞,这也是对外发布的其中一环。
  • 按照冒烟测试测试用例完成冒烟测试
    这个是指一些通用功能的测试,为了提高版本质量,测试会要求我们先对一些通过功能的正常逻辑进行验证(目前这部分我们已经通过自动化测试实现,测得时候盯着看,然后再看一遍日志就OK)
    提测版本前代码Review重点检查项目:
  • 发布前跑一遍FindBugs, 解决工具发现的所有BUG, 确认无需解决的BUG提示必须注释说明
    主要是为了提高版本质量,不过目前这部分用的比较少,有时候改动不是很大,版本比较着急就不会用(不过测试还是会基于jar扫描)
  • 检查资源文件使用方式, 必须使用反射方式调用
    这个是我们项目特有的,由于我们项目的代码框架(这个我会在SDK设计心得之目录结构和资源文件(点击查看)里面说明)的特殊性,我们的SDK代码如果调用资源,只能通过反射获取,所以我们要确认是否正确。
  • 检查Logcat打印的内容, 避免在Log中打印关键信息
    我们的logcat有一次直接把网络请求加密前的内容打印了……你懂的
  • 检查RDM打包版本号配置是否正确
    RDM是一个内部自动打包系统,我们SDK的最终版本会对比这个系统配置的版本和我们代码中的版本,通过两个版本的一致性来确保版本的正确。
  • 检查避免使用硬编码值和魔法数字,如果有,必须说明
    关于硬编码和魔法数字举个例子。例如代码中有个定时器,默认是半个小时执行一次。我们开发中为了测试效果可能会调整到1分钟一次。如果版本周期比较长,可能最后就忘了修改这个数,一旦版本发布以后就更麻烦了。
    好吧,我还是不回避这个问题了,就举自己的案例吧。当时我们某个版本忘了因为什么,访问后台的域名不但配置文件有,代码也有,而且优先使用代码中的,悲剧就这样开始了。开发完测试的时候代码用了测试环境的域名,功能正常。提测以后测试配置了测试环境的域名功能正常,也没发现,然后正好那个版本比较着急用就没灰度。发布后第三天好几个游戏来问,无论怎么配置都是测试环境……瞬间噶屁了。修改很简单,但是因为没有灰度,已经有比较多游戏在更新了,只能一个个去通知更换,然后被屌犯这么脑残的错误。
    这个被坑过几次,感触颇深,不过这个可以配合todo标签来避免。
  • 确认所有TODO标签已经完成, 没有遗漏, 确定要遗留的问题必须注释写明原因
    关于TODO我会在SDK那些事之SDK开发中的一些开发经验(点击查看)专门说,一定要看,是干货。TODO标签的合理使用可以有效的解决很多问题。
  • 检查相关的so是否都已经提交到SVN
    so文件在win下(mac默认ignore好像也有)坑爹的SVN不会提交,有一次忘了就给漏了!!给漏了!!!漏了!!!

版本应该怎么测试

这里主要是列举一些我们目前使用到的测试方法:

白盒

这里主要是指基于接口的测试,包括合法、非法参数、边界值的测试,这部分内容是确定的,因此我们都通过自动化测试实现。还有一个是版本代码比对。我们的测试会比对当前版本与上个版本代码的差异(当然有些是看不懂的,但是不影响理解整个流程)。其实让测试看代码我觉得是一件很好的事情,如果遇到有些紧急版本,一对比代码就知道是怎么改的,一眼胜千言。

黑盒

黑盒主要是指demo,我们会为游戏提供一套我们的接口调用的demo(我会在SDK开发经验之Demo和文档(点击查看)中描述demo的价值)。测试可以通过demo验证一些具体的功能表现,目前我们通用的功能的基于UI的测试也都已经是通过自动化测试。

兼容性

不用多说,Android的兼容性和web前端的兼容性没啥区别。

性能测试

这个最好有,尤其是作为SDK,游戏经常来问的最多的就是你们的包多大,加了以后对性能(CPU和内存)的占用量是多少等。这些有了才更专业啊。

版本说明

做SDK一直以来,除了之前提过的文档的问题,另一个比较多的抱怨就是关于版本更新。由于

  • SDK版本比较多,而且迭代较快,
  • 同时提供的版本历史太过简单。
    当游戏接入新功能的时候,很容易遇到的问题就是需要替换版本时,他们从老版本升级到当前版本要做什么,他们无法知道对应的功能更新在什么地方可以看到,或者他们需要做什么工作。
    为了解决这个问题,SDK的版本做了几次更新,最后形成了一个还在试验中,但是感觉已经相对会明晰很多的版本历史记录方法,作为参考。

最早的版本说明

最早期的版本文档,只是简单记录了版本变更的概要,例如:
XX版本变更内容

1. 增加了XX功能
2. 修复了XX引起的一个bug
3. ……

里面只是简单列出变更的内容,开发商无法了解到变更的内容,以及更新需要做的工作,尤其是由于SDK内容较多,导致文档内容很多。开发商每次都要彻底过一遍文档,然而大部分内容没有变更,导致开发商极易遗漏一些小的变更点。初期曾尝试在文档中做变更标记,后来发现随着版本增多,文档可读性越来越低。最后还是采用版本历史来描述版本变更内容,在版本历史中同时增加SDK和文档变更的内容。
##更新以后的版本说明
这次调整以后,版本更新概要变成如下的样子:

XX版本变更内容

代码变更:
    1.XX功能变动
        【修改】 调整XXX接口
    2.修复XXbug
        【删除】 删除XX代码的某处理逻辑,解决XXX引起的问题
    3.……       
文档调整:
    【删除】 删除第XXX部分对于某功能的说明
    【修改】 增加对XX接口调用事例的说明

这次调整以后明显有了改观,但是游戏还是有意见,他们认为没有虽然很清晰,但是其实里面有一些内容他们并不需要关注,但是罗列在一起还是相对繁杂。另外上面的模式,不方便使用markdown编辑,根据意见最终修改为下面的格式:

当前的版本说明

目前SDK使用的就是下面的版本更新说明,当然这也并不最好的模式,版本更新文档变的越来越大。我们也在尝试更好的方式。

### XX版本变更内容

#### 代码变更:

- 新增功能:
    1. XX功能
        XX功能主要提供……
    - 增加API XXX
        通过XXAPI,可以轻松实现…………
- 功能调整:
    1. XX API调整
        将XX API的参数从int转为枚举……
- BUG修复
    1. 修复XX逻辑引起的XX问题
    
#### 文档调整:

- 增加:
    1. XX模块增加关于XX功能的描述(Wiki链接)
- 修改:
    1. 修改XX模块中关于XX功能的权限声明(Wiki链接)
    - ……
  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值