阿里云
一、简介
阿里云物联网平台(Alibaba Cloud IoT Platform)是阿里巴巴集团旗下的一个物联网平台,该平台是一个集成了设备接入、设备管理、数据安全通信、消息订阅、消息转发和数据服务(存储、分析、过滤、解析、集成等)等能力的一体化平台。向下支持连接海量设备,采集设备数据上云;向上提供云端 API,服务端可通过云端 SDK 调用云端 API 将指令下发至设备端,实现远程控制。
二、演示功能概述
本教程教你如何用 Air724 开发板,接入阿里云,以及如何数据通信、远程升级等。
三、准备硬件环境
3.1 开发板准备
使用 EVB_Air724 开发板,如下图所示:
淘宝购买链接:Air724UG-NFM 开发板淘宝购买链接 ;
此开发板的详细使用说明参考:Air724UG 产品手册 中的《EVB_Air724UG_AXX 开发板使用说明》,写这篇文章时最新版本的使用说明为:《EVB_Air724UG_A14 开发板使用说明》;开发板使用过程中遇到任何问题,可以直接参考这份使用说明文档。
api:https://doc.openluat.com/wiki/21?wiki_page_id=2068
3.2 数据通信线
USB 数据线一根(micro USB)。
3.3 PC 电脑
WIN7 以及以上版本的 WINDOWS 系统。
3.4 SIM 卡
中国大陆环境下,可以上网的 SIM 卡。一般来说,使用移动,电信,联通的物联网卡或者手机卡都行。
3.5 组装硬件环境
USB 数据线插入 USB 口,另一端与电脑相连,拨码开关全部拨到 ON,串口切换开关选择 UART1,USB 供电的 4V 对应开关拨至 ON 档,SIM 卡放到 SIM 卡槽中锁紧,如下图所示。
四、准备软件环境
4.1 下载调试工具
使用说明参考:Luatools 下载和详细使用
4.2 源码及固件
1.底层 core 下载
下载底层固件,并解压
链接:https://docs.openluat.com/air724ug/luatos/firmware/
如下图所示,红框的是我们要使用到的。
2.本教程使用的 demo:
4.3 下载固件和脚本到开发板中
打开 Luatools,开发板上电开机,如开机成功 Luatools 会打印如下信息。
点击项目管理测试选项。
进入管理界面,如下图所示。
- 点击选择文件,选择底层固件,我的文件放在 D:\luatOS\Air724 路径中
- 点击增加脚本或资源文件,选择程序源码,如下图所示。
- - 点击下载底层和脚本,下载完成如下图所示。
五、代码示例介绍
5.1 API 说明
目前的产品节点类型仅支持“设备”,设备认证方式支持“一机一密和“一型一密”
5.1.1 aLiYun.sleep()
断开阿里云物联网套件的连接,并且不再重连
- 参数
无
- 返回值
nil
- 例子
aLiYun.sleep()
5.1.2 aLiYun.wakeup()
重新打开阿里云物联网套件的连接
- 参数
无
- 返回值
nil
- 例子
aLiYun.wakeup()
5.1.3 aLiYun.sleepStatus()
查看打开阿里云物联网套件的是否允许连接状态
- 参数
无
- 返回值
bool 是否允许连接阿里云
- 例子
local ar = aLiYun.sleepStatus()
5.1.4 aLiYun.Authsleep()
断开阿里云物联网套件的鉴权连接,并且不再重连
- 参数
无
- 返回值
nil
- 例子
aLiYun.Authsleep()
5.1.5 aLiYun.Authwakeup()
重新打开阿里云物联网套件的鉴权连接
- 参数
无
- 返回值
nil
- 例子
aLiYun.Authwakeup()
5.1.6 aLiYun.AuthSleepStatus()
查看打开阿里云物联网套件的是否允许鉴权状态
- 参数
无
- 返回值
bool 是否允许连接阿里云
- 例子
local ar = aLiYun.AuthSleepStatus()
5.1.7 aLiYun.setup(productKey, productSecret, getDeviceNameFnc, getDeviceSecretFnc, setDeviceSecretFnc)
配置阿里云物联网套件的产品信息和设备信息
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
productKey | string | 产品标识 |
productSecret | string | 可选参数,默认为 nil 产品密钥一机一密认证方案时,此参数传入 nil 一型一密认证方案时,此参数传入真实的产品密钥 |
getDeviceNameFnc | function | 获取设备名称的函数 |
getDeviceSecretFnc | function | 获取设备密钥的函数 |
setDeviceSecretFnc | function | 可选参数,默认为 nil 设置设备密钥的函数,一型一密认证方案才需要此参数 |
> |
- 返回值
nil
- 例子
aLiYun.setup("b0FMK1Ga5cp",nil,getDeviceNameFnc,getDeviceSecretFnc)
aLiYun.setup("a1AoVqkCIbG","7eCdPyR6fYPntFcM",getDeviceNameFnc,getDeviceSecretFnc,setDeviceSecretFnc)
5.1.8 aLiYun.setMqtt(cleanSession, will, keepAlive)
设置 MQTT 数据通道的参数
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
cleanSession | number | 可选参数,默认为 1 1/0 |
will | table | 可选参数,默认为 nil 遗嘱参数,格式为{qos=,retain=,topic=,payload=} |
keepAlive | number | 可选参数,默认为 240 单位秒 |
- 返回值
nil
- 例子
aLiYun.setMqtt(0)
aLiYun.setMqtt(1,{qos=0,retain=1,topic="/willTopic",payload="will payload"})
aLiYun.setMqtt(1,{qos=0,retain=1,topic="/willTopic",payload="will payload"},120)
5.1.9 aLiYun.setRegion(region)
设置地域 region id
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
region | string | 地域 id 字符串,参考:https://help.aliyun.com/document_detail/40654.html?spm=a2c4g.11186623.2.16.c0a63f82Z7qCtA#concept-h4v-j5k-xdb |
- 返回值
nil
- 例子
_-- 设置华北1:aLiYun.setRegion("cn-qingdao")-- 设置华东1:aLiYun.setRegion("cn-hangzhou")-- 设置华南1:aLiYun.setRegion("cn-shenzhen")_
5.1.10 aLiYun.setConnectMode(mode, host, port, getClientIdFnc, getUserNameFnc, getPasswordFnc)
设置连接方式
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
mode | string | 连接方式,支持如下几种方式:"direct"表示 MQTT-TCP 直连 |
host | string | 服务器地址 |
port | number | 服务器端口 |
getClientIdFnc | function | 获取 mqtt,client,id 的函数 |
getUserNameFnc | function | 获取 mqtt,client,userName 的函数 |
getPasswordFnc | function | 获取 mqtt,client,password 的函数 |
- 返回值
nil
- 例子
_-- 设置为MQTT-TCP直连:aLiYun.setConnectMode("direct")_
5.1.11 aLiYun.subscribe(topic, qos)
订阅主题
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
topic | param | string 或者 table 类型,一个主题时为 string 类型,多个主题时为 table 类型,主题内容为 UTF8 编码 |
qos | param | number 或者 nil,topic 为一个主题时,qos 为 number 类型(0/1,默认 0);topic 为多个主题时,qos 为 nil |
- 返回值
nil
- 例子
aLiYun.subscribe("/b0FMK1Ga5cp/862991234567890/get", 0)
aLiYun.subscribe({["/b0FMK1Ga5cp/862991234567890/get"] = 0, ["/b0FMK1Ga5cp/862991234567890/get"] = 1})
5.1.12 aLiYun.publish(topic, payload, qos, cbFnc, cbPara)
发布一条消息
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
topic | string | UTF8 编码的主题 |
payload | string | 负载 |
qos | number | 可选参数,默认为 0 质量等级,0/1,默认 0 |
cbFnc | function | 可选参数,默认为 nil 消息发布结果的回调函数回调函数的调用形式为:cbFnc(result,cbPara)。result 为 true 表示发布成功,false 或者 nil 表示订阅失败;cbPara 为本接口中的第 5 个参数 |
cbPara | param | 可选参数,默认为 nil 消息发布结果回调函数的回调参数 |
- 返回值
nil
- 例子
aLiYun.publish("/b0FMK1Ga5cp/862991234567890/update","test",0)
aLiYun.publish("/b0FMK1Ga5cp/862991234567890/update","test",1,cbFnc,"cbFncPara")
5.1.13 aLiYun.on(evt, cbFnc)
注册事件的处理函数
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
evt | string | 事件"auth"表示鉴权服务器认证结果事件"connect"表示接入服务器连接结果事件"reconnect"表示重连事件"receive"表示接收到接入服务器的消息事件 |
cbFnc | function | 事件的处理函数当 evt 为"auth"时,cbFnc 的调用形式为:cbFnc(result),result 为 true 表示认证成功,false 或者 nil 表示认证失败当 evt 为"connect"时,cbFnc 的调用形式为:cbFnc(result),result 为 true 表示连接成功,false 或者 nil 表示连接失败当 evt 为"receive"时,cbFnc 的调用形式为:cbFnc(topic,qos,payload),topic 为 UTF8 编码的主题(string 类型),qos 为质量等级(number 类型),payload 为原始编码的负载(string 类型)当 evt 为"reconnect"时,cbFnc 的调用形式为:cbFnc(),表示 lib 中在自动重连阿里云服务器 |
- 返回值
nil
- 例子
aLiYun.on("connect",cbFnc)
5.1.14 aLiYun.setErrHandle(cbFnc, tmout)
设置阿里云 task 连续一段时间工作异常的处理程序
- 参数
名称 | 传入值类型 | 释义 |
---|---|---|
cbFnc | function | 异常处理函数,cbFnc 的调用形式为:cbFnc() |
tmout | number | 可选参数,默认为 150 连续工作异常的时间,当连续异常到达这个时间之后,会调用 cbFnc() |
- 返回值
nil
- 例子
aLiYun.setErrHandle(function() sys.restart("ALIYUN_TASK_INACTIVE") end, 300)
5.2 testALiYun.lua 代码
本文件主要功能是,连接到阿里云,订阅消息主题。定时发布消息。
实时接收来自阿里云的消息。
注意三元组信息,根据实际值自行修改,否则无法连接上阿里云。 local PRODUCT_KEY = "yourProductKey" local DEVICE_NAME = "862991234567890" local DEVICE_SECRET = "c85d8dd8d28fbf527b2a4a36a67b7541"
--- 模块功能:阿里云功能测试.
-- 支持数据传输和OTA功能
-- @author openLuat
-- @module aLiYun.testALiYun
-- @license MIT
-- @copyright openLuat
-- @release 2018.04.14
module(...,package.seeall)
require"aLiYun"
require"misc"
require"pm"
--地域和可用区,详情参考:https://help.aliyun.com/document_detail/40654.html?spm=a2c4g.11186623.2.22.797d7c80uIGAZ7
--根据自己的产品所在地域修改
local REGION_ID = "cn-shanghai"
--三元组信息,根据实际值自行修改
--注意:这里默认的三元组无法连接上阿里云
local PRODUCT_KEY = "yourProductKey"
local DEVICE_NAME = "862991234567890"
local DEVICE_SECRET = "c85d8dd8d28fbf527b2a4a36a67b7541"
--[[
函数名:getDeviceName
功能 :获取设备名称
参数 :无
返回值:设备名称
]]
local function getDeviceName()
return DEVICE_NAME
end
--[[
函数名:getDeviceSecret
功能 :获取设备密钥
参数 :无
返回值:设备密钥
]]
local function getDeviceSecret()
return DEVICE_SECRET
end
--阿里云客户端是否处于连接状态
local sConnected
local publishCnt = 1
--[[
函数名:publishTestCb
功能 :发布1条qos为1的消息后收到PUBACK的回调函数
参数 :
usertag:调用mqttclient:publish时传入的usertag
result:true表示发布成功,false或者nil表示失败
返回值:无
]]
local function publishTestCb(result,para)
log.info("testALiYun.publishTestCb",result,para)
sys.timerStart(publishTest,20000)
publishCnt = publishCnt+1
end
--发布一条QOS为1的消息
function publishTest()
if sConnected then
--注意:在此处自己去控制payload的内容编码,aLiYun库中不会对payload的内容做任何编码转换
aLiYun.publish("/"..PRODUCT_KEY.."/"..getDeviceName().."/update","qos1data",1,publishTestCb,"publishTest_"..publishCnt)
end
end
---数据接收的处理函数
-- @string topic,UTF8编码的消息主题
-- @number qos,消息质量等级
-- @string payload,原始编码的消息负载
local function rcvCbFnc(topic,qos,payload)
log.info("testALiYun.rcvCbFnc",topic,qos,payload)
end
--- 连接结果的处理函数
-- @bool result,连接结果,true表示连接成功,false或者nil表示连接失败
local function connectCbFnc(result)
log.info("testALiYun.connectCbFnc",result)
sConnected = result
if result then
--订阅主题,不需要考虑订阅结果,如果订阅失败,aLiYun库中会自动重连
--根据自己的项目需要订阅主题,下面注释掉的一行代码中的主题是非法的,所以不能打开,一旦打开,会导致订阅失败
--aLiYun.subscribe({["/"..PRODUCT_KEY.."/"..getDeviceName().."/get"]=0, ["/"..PRODUCT_KEY.."/"..getDeviceName().."/get"]=1})
--注册数据接收的处理函数
aLiYun.on("receive",rcvCbFnc)
--PUBLISH消息测试
publishTest()
end
end
--rrpcUseCustomTopic接口不是必须的,在设置rrpc自定义topic时调用
--aLiyun.rrpcUseCustomTopic(true)
aLiYun.on("connect",connectCbFnc)
--setMqtt接口不是必须的,aLiYun.lua中有这个接口设置的参数默认值,如果默认值满足不了需求,参考下面注释掉的代码,去设置参数
--aLiYun.setMqtt(0)
aLiYun.setRegion(REGION_ID)
aLiYun.setConnectMode("direct",PRODUCT_KEY..".iot-as-mqtt."..REGION_ID..".aliyuncs.com",1883)
aLiYun.setup(PRODUCT_KEY,nil,getDeviceName,getDeviceSecret)
--要使用阿里云OTA功能,必须参考本文件124或者126行aLiYun.setup去配置参数
--然后加载阿里云OTA功能模块(打开下面的代码注释)
require"aLiYunOta"
--如果利用阿里云OTA功能去下载升级合宙模块的新固件,默认的固件版本号格式为:_G.PROJECT.."_".._G.VERSION.."_"..sys.getcorever(),下载结束后,直接重启,则到此为止,不需要再看下文说明
--如果下载升级合宙模块的新固件,下载结束后,自己控制是否重启
--如果利用阿里云OTA功能去下载其他升级包,例如模块外接的MCU升级包,则根据实际情况,打开下面的代码注释,调用设置接口进行配置和处理
--设置MCU当前运行的固件版本号
--aLiYunOta.setVer("MCU_VERSION_1.0.0")
--设置新固件下载后保存的文件名
--aLiYunOta.setName("MCU_FIRMWARE.bin")
--[[
函数名:otaCb
功能 :新固件文件下载结束后的回调函数
通过uart1(115200,8,uart.PAR_NONE,uart.STOP_1)把下载成功的文件,发送到MCU,发送成功后,删除此文件
参数 :
result:下载结果,true为成功,false为失败
filePath:新固件文件保存的完整路径,只有result为true时,此参数才有意义
返回值:无
]]
local function otaCb(result,filePath)
log.info("testALiYun.otaCb",result,filePath)
if result then
local uartID = 1
sys.taskInit(
function()
local fileHandle = io.open(filePath,"rb")
if not fileHandle then
log.error("testALiYun.otaCb open file error")
if filePath then os.remove(filePath) end
return
end
pm.wake("UART_SENT2MCU")
uart.on(uartID,"sent",function() sys.publish("UART_SENT2MCU_OK") end)
uart.setup(uartID,115200,8,uart.PAR_NONE,uart.STOP_1,nil,1)
while true do
local data = fileHandle:read(1460)
if not data then break end
uart.write(uartID,data)
sys.waitUntil("UART_SENT2MCU_OK")
end
--此处上报新固件版本号(仅供测试使用)
--用户开发自己的程序时,根据下载下来的新固件,执行升级动作
--升级成功后,调用aLiYunOta.setVer上报新固件版本号
--如果升级失败,调用aLiYunOta.setVer上报旧固件版本号
aLiYunOta.setVer("MCU_VERSION_1.0.1")
uart.close(uartID)
pm.sleep("UART_SENT2MCU")
fileHandle:close()
if filePath then os.remove(filePath) end
end
)
else
--文件使用完之后,如果以后不再需求,需要自行删除
if filePath then os.remove(filePath) end
end
end
--设置新固件下载结果的回调函数
--aLiYunOta.setCb(otaCb)
5.3 main.lua 代码
本代码为主程序脚本,系统启动后首先会对 4G 网络进行配置,等待网络连接成功,然后加载测试模块。
六、阿里云操作
6.1 产品操作
打开阿里云找到物联网平台,开通业务后进入控制台。
点开设备管理的产品页面,点击新建产品。根据需求和图示说明创建产品。
设备管理界面
新建产品输入产品名称,所属品类选择自定义,节点类型选择直连设备,如下图所示。
点击确认,产品建立完成,如图所示。
6.2 设备操作
创建产品完成后就可以进入设备页面添加设备,在对应产品页面进入设备管理,按照提示添加设备
点击添加设备,输入产品名称和 devicename,如图所示。
(在做正式产品时建议使用 imei 为 devicename,方便后期维护)
三元组如图所示
6.3 一机一密 LuatOS 方式连接
一机一密需要提前按照文档阿里云操作章节先建好产品并添加设备,获取三元组供代码使用。
找到所使用的脚本版本进入 demo 目录找到 aliyun 文件夹打开 testALiYun.lua
首先修改 PRODUCT_KEY 为自己项目的 PRODUCT_KEY
local PRODUCT_KEY = "b0FMK1Ga5cp"
再找到 getDeviceName 这个函数,如果是按前文的操作直接使用的 imei 作为 devicename 那么就不需要需改,如果是其他 devicename 就需要注释掉第一个 return 删除后面的 return 的注释,填上自己的 devicename,类型是字符串。
函数名:getDeviceName
功能 :获取设备名称
参数 :无
返回值:设备名称
local function getDeviceName()_--默认使用设备的IMEI作为设备名称,用户可以根据项目需求自行修改 _return misc.getImei()
_--用户单体测试时,可以在此处直接返回阿里云的iot控制台上注册的设备名称,例如return "862991419835241"--return "862991419835241"_end
下一步找到 getDeviceSecret 注释掉第一个 return,去掉第二个 return 的注释,把阿里云上的 DeviceSecret 替换上。
函数名:getDeviceSecret
功能 :获取设备密钥
参数 :无
返回值:设备密钥
local function getDeviceSecret()_--默认使用设备的SN作为设备密钥,用户可以根据项目需求自行修改_return misc.getSn() _--用户单体测试时,可以在此处直接返回阿里云的iot控制台上生成的设备密钥,例如return"y7MTCG6Gk33Ux26bbWSpANl4OaI0bg5Q"--return "y7MTCG6Gk33Ux26bbWSpANl4OaI0bg5Q"_end
修改以上三个参数后保存代码下载到设备就可以连接阿里云了。
6.4 一型一密 LuatOS 方式连接
除了需要添加产品和设备还需要在阿里云打开动态注册开关
找到 ProductSecret,复制 ProductSecret 使用
首先修改 PRODUCT_KEY 为自己项目的 PRODUCT_KEY
local PRODUCT_KEY = "b0FMK1Ga5cp"
找到 demo 的这个地方,把 PRODUCE_SECRET 的注释去掉然后替换成自己的
_--采用一型一密认证方案时:_
_--PRODUCT_KEY和PRODUCE_SECRET为阿里云华东2站点上创建的产品的ProductKey和ProductSecret,用户根据实际值自行修改_
_--local PRODUCT_KEY = "b1KCi45LcCP"_
_--local PRODUCE_SECRET = "VWll9fiYWKiwraBk"_
_--除了上面的PRODUCT_KEY和PRODUCE_SECRET外,还需要提供获取DeviceName的函数、获取DeviceSecret的函数、设置DeviceSecret的函数_
_--设备第一次在某个product下使用时,会先去云端动态注册,获取到DeviceSecret后,调用设置DeviceSecret的函数保存DeviceSecret_
getDeviceName()这个地方前面阿里云操作时建议使用 imei 作为 devicename 就是为了此处使用方便。直接使用 demo 的这个写法即可,切记不可使用固定值。如果需要自己定义 devicename 请通过其他逻辑实现获取。
local function getDeviceName()
_--默认使用设备的IMEI作为设备名称,用户可以根据项目需求自行修改 _
return misc.getImei()
_--用户单体测试时,可以在此处直接返回阿里云的iot控制台上注册的设备名称,例如return "862991419835241"_
_--return "862991419835241"_
end
getDeviceSecret 的地方也不需要修改,默认会将参数记录到 sn 区域。如果程序有其他地方使用到 sn 可修改为记录到 nvm,请根据需要自行实现。
最后找到如下部分,将一机一密的代码注释,将一型一密的代码打开
_--采用一机一密认证方案时:_
_--配置:ProductKey、获取DeviceName的函数、获取DeviceSecret的函数;其中aLiYun.setup中的第二个参数必须传入nil_
_--aLiYun.setup(PRODUCT_KEY,nil,getDeviceName,getDeviceSecret)_
_--采用一型一密认证方案时:_
_--配置:ProductKey、ProductSecret、获取DeviceName的函数、获取DeviceSecret的函数、设置DeviceSecret的函数_
aLiYun.setup(PRODUCT_KEY,PRODUCE_SECRET,getDeviceName,getDeviceSecret,setDeviceSecret)
保存代码下载到设备就可以连接阿里云了。
6.5 其他说明
阿里云文档中有 SDK 接入云接入等多种方式,合宙的模块接入方式不属于 SDK 接入也不属于云接入。目前采用文档中说明的开放协议的 MQTT 协议接入。模块的 AT 模式只提供设备接入,数据收发,LuatOS 模式增加了 OTA,其他阿里云物联提供的功能暂不支持。开发者可根据文档与 API 自行开发。
七、开机调试
7.1 开发板开机
连接好硬件并下载固件后,启动 Luatools 软件,系统运行信息将显示在界面中。红框中为开发板连接到 PC 机后正常打印的信息,如下图所示。
7.2 功能调试
连接阿里云成功开始发送数据
7.3 阿里云上查看服务器日志信息
八、 远程升级
此时我们已经成功连接到阿里云,下面进行远程升级的详解。
8.1 在进行远程升级前我们首先要准备好脚本资源文件。
8.2 将设备中脚本 VERSION 修改为 1.0.0,如下所示。
1. PROJECT = “ALIYUN” 2. VERSION = “1.0.0”
此时在阿里云端可以看到下图。
8.3 此时打开 Luatools去生成基础版本的量产文件如下图所示。
8.4 生成后修改目标脚本中的版本号,以便区分。
PROJECT = "ALIYUN"
VERSION = "2.0.0"
. 使用 Luatools生成目标版本的量产文件,如无需升级底层 core 则不用勾选升级文件包含 core 。
8.5 将两份远程升级文件去进行差分
生成的量产文件
http://doc.openluat.com/chafen 点进链接上传两份远程升级文件。
8.6 将生成的差分文件重命名,进入到阿里云-> 监控运维->OTA 升级-> 添加升级包
8.7 在阿里云端确认升级成功。
- 主动下发升级 URL:
在阿里云控制台,确认设备的状态并获取其唯一标识。
通过控制台向设备下发升级指令,包含升级的 URL 和版本信息。
- 设备接收指令:
确保设备能够正确接收到下发的升级指令和 URL。
- 下载升级包:
设备根据下发的 URL 下载新的固件或应用程序包。
验证下载的文件完整性(例如,校验和)。
- 重启设备:
在完成下载后,设备需要重启以应用新的固件或更新。如下图所示。
升级日志:
九、常见问题
9.1 如何向模块批量写入 DeviceSecret?
http://doc.openluat.com/article/421/0
9.2 固件升级不支持跨功能升级
例如非 float 固件升级到 float 固件
给读者的话
本篇文章由
杨超
开发;本篇文章描述的内容,如果有错误、细节缺失、细节不清晰或者其他任何问题,总之就是无法解决您遇到的问题;
请登录合宙技术交流论坛,点击文档找错赢奖金-Air724UG-LuatOS-软件指南-公有云对接-阿里云;
用截图标注+文字描述的方式跟帖回复,记录清楚您发现的问题;
我们会迅速核实并且修改文档;
同时也会为您累计找错积分,您还可能赢取月度找错奖金!