前言(源码使用介绍在最后)

  

  一,微信小程序篇小程序下载(该源码为这节测试源代码)

  

  二.有多少人一直在期盼着小程序可以实现SmartConfig或者Airkiss的功能? 来吧!我的这种方式包您满意.

    注:APUConfig 是我自己取的名字(哈哈谁让这种方式,我是第一个在微信小程序上实现的),代表着 AP  UDP  Config

    绑定流程详细说明:

    APUConfig小程序端源码

  https://gitee.com/yang456/APUConfig.git

    

实现功能概要

  1.小程序使用APUConfig给Wi-Fi模块配网,并获取设备MAC等信息,然后通过MQTT控制绑定的Wi-Fi设备.

  2.演示视频:  https://www.bilibili.com/video/av74786875/

测试准备工作

一,下载单片机程序(请自行下载)

  

二,打开微信小程序软件,导入本节工程

  

  

三,把小程序安装到手机运行

  

四,调整波动开关位置,STM32和Wi-Fi 串口 通信

  

五,短接STM32的PB2和Wi-Fi模块的RST引脚(内部程序使用该引脚硬件复位Wi-Fi)

  

开始测试

一,点击小程序下方的添加设备按钮

  

二,选择添加Wi-Fi设备

  

三,输入路由器密码(注:Wi-Fi名称自动获取,也可自己填写)

四.长按PB5大约4S,等待指示灯快闪,松开PB5,Wi-Fi模块进入配网状态

  

五.点击小程序上的 "绑定设备"按钮,开始搜索设备,绑定成功,将自动跳转到主页面,显示绑定的Wi-Fi设备

  5.1 正在尝试连接Wi-Fi模块的热点

  

  5.2 连接上热点,正在和模块通信

  

  5.3 成功绑定设备

  

六.点击设备,进入控制页面,控制设备

  6.1 点击设备

  

  6.2 控制继电器吸合

      

  6.2 控制继电器断开

      

结语

希望能够为在用小程序做物联网开发的大家解决当前最大的烦心事!

知识是死的,人是活的!

希望大家不仅要有超强的学习能力,还需要有灵活运用知识的能力.

现在想想我挺感谢我的高中班主任 (朱兆同老师),是他让我开始了自学生涯,培养了这种能力.

师者,传道受业解惑者也.   传道:应该是首当其冲的吧!

绑定部分小程序端源码

https://gitee.com/yang456/APUConfig.git   (下载最新版本请在git下载)

小程序端APUConfig源码:

APUConfig.js

// pages/APUConfig/APUConfig.js
var util = require("../../utils/util.js"); var APUConfigStart = false;//是否在配网 var APUConfigconnectAPCount = ;//连接热点的次数 var APUConfigSendUDPDataIntervalNumber = ;//发送UDP数据的定时器编号 var APUConfigSendUDPDataCount = ;//发送UDP数据的次数 var udp; Page({ /**
* 页面的初始数据
*/
data: {
ssid: '',
password: ''
},
// 获取路由器名称
ssidInput: function (e) {
this.data.ssid = e.detail.value;
},
// 获取输入密码
passwordInput: function (e) {
this.data.password = e.detail.value;
},
/**
* 成功连接热点
*/
connectWifiSuccess: function(res)
{
var _this = this;
udp = wx.createUDPSocket()//启用UDP
udp.bind() wx.hideLoading();
wx.showLoading({
title: '正在绑定'
}) udp.onListening(function (res) {
console.log('监听中...')
console.log(res)
}) //定时1S发送一次UDP数据
try { clearInterval(APUConfigSendUDPDataIntervalNumber); } catch (e) { } APUConfigSendUDPDataIntervalNumber = setInterval(
function () {
udp.send
({
address: '192.168.4.1',
port: ,
message: "{\"ssid\":" + "\"" + _this.data.ssid + "\"" + "\"pwd\":" + "\"" + _this.data.password + "\"" + "}"
})
APUConfigSendUDPDataCount = APUConfigSendUDPDataCount + ;
console.log('发送数据: ' + "{\"ssid\":" + "\"" + _this.data.ssid + "\"" + "\"pwd\":" + "\"" + _this.data.password + "\"" + "}"); if (APUConfigSendUDPDataCount>)//发送了20次,还没绑定上
{
try { clearInterval(APUConfigSendUDPDataIntervalNumber); } catch (e) { }
APUConfigSendUDPDataCount = ;
APUConfigconnectAPCount = ;
APUConfigStart = false;//
udp.close();
wx.hideLoading();
wx.showModal({//弹出对话框
title: '绑定失败',
content: '请重新尝试'
})
}
},
,
"null");//启动定时器 //UDP接收到消息
udp.onMessage(function (res) {
console.log(res)
let str = util.newAb2Str(res.message);//接收消息
console.log('str===' + str) //{ "mac": "dc:4f:22:10:b0:ce", "ip": "192.168.0.101" } try { clearInterval(APUConfigSendUDPDataIntervalNumber); } catch (e) { }
try { udp.close(); } catch (e) { }//关闭UDP
APUConfigSendUDPDataCount = ;
APUConfigconnectAPCount = ;
APUConfigStart = false;//复位所有变量
wx.hideLoading();//关闭提示框 if (str!=null)
{
let json = JSON.parse(str);//解析JSON数据
if (json != null)
{
let mac = json.mac;
let ip = json.ip;
if (mac != null)
{
wx.reLaunch({
url: '../index/index?ClientID=' + mac + "&" + "IP=" + ip
})
}
}
}
}) },
/**
* 连接无线失败
*/
connectWifiFail: function (res)
{
var _this = this; if (APUConfigconnectAPCount<)//尝试连接热点的次数
{
APUConfigconnectAPCount = APUConfigconnectAPCount + ;
console.log('连接Wi-Fi: wifi_8266_bind');
wx.connectWifi//控制连接Wi-Fi无线信号
({
SSID: "wifi_8266_bind",
password: "",
success: _this.connectWifiSuccess,
fail: _this.connectWifiFail
})
}
else
{
APUConfigconnectAPCount = ;
APUConfigStart = false;//
wx.hideLoading();
wx.showModal({//弹出对话框
title: '绑定失败',
content: '请重新尝试'
})
} },
//点击绑定按钮
BindClick: function () { var _this = this; if (_this.data.ssid.length == || _this.data.password.length == ) {
wx.showModal({//弹出对话框
title: '提示',
content: 'Wi-Fi名称和密码不能为空'
})
}
else
{
APUConfigStart = true;//开始配网 //控制连接Wi-Fi无线信号
wx.connectWifi
({
SSID: "wifi_8266_bind",
password: "",
success: _this.connectWifiSuccess,
fail: _this.connectWifiFail
}) }
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var _this = this; _this.GetWiFiSSID();//显示当前连接的Wi-Fi名称 //启动网络状态监听
wx.onNetworkStatusChange(function (res)
{
console.log("绑定设备:网络改变" + res.isConnected + " " + res.networkType);
if (res.networkType == "wifi")//当前连接的网络类型是WIFI
{
console.log("绑定设备:当前连接的网络类型是WIFI");
if (!APUConfigStart)//没在配网状态
{
_this.GetWiFiSSID();
}
}
else//其它网络
{
if (!APUConfigStart)//没在配网状态
{
_this.setData({//清空显示的wifi名称
ssidValue: ""
})
}
}
})
},
/**
* 获取链接的WIFI名称
*/
GetWiFiSSID: function () {
var _this = this;
wx.startWifi({//启用WIFI功能
success(res) {
wx.getConnectedWifi//获取链接的Wi-Fi信息
({
success(res) //获取到信息
{
_this.data.ssid = res.wifi.SSID; console.log("绑定设备:连接的Wi-Fi名称 " + _this.data.ssid); _this.setData({
ssidValue: _this.data.ssid
})
},
fail(res) {
}
})
}
})
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
this.GetWiFiSSID();//显示当前连接的Wi-Fi名称
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
try { clearInterval(APUConfigSendUDPDataIntervalNumber); } catch (e) { }
try { udp.close(); } catch (e) { }
APUConfigSendUDPDataCount = ;
APUConfigconnectAPCount = ;
APUConfigStart = false;//
wx.hideLoading();
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () { },
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () { }, /**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () { }, /**
* 用户点击右上角分享
*/
onShareAppMessage: function () { }
})

APUConfig.json

{
"usingComponents": {}
}

APUConfig.wxml

<!--pages/APUConfig/APUConfig.wxml-->

<view class="container">
<!--提示--> <view class="hint">
<text style="color: #545454; font-size:35rpx;">绑定设备前请确定完成以下步骤</text> <view>
<text style="color:#09bb07; font-size:35rpx;">①</text>
<text style="font-size:35rpx;" space='nbsp'> 请先连接自家路由器热点</text>
</view> <view>
<text style="color:#09bb07; font-size:35rpx;">②</text>
<text style="font-size:35rpx;" space='nbsp'> 给设备上电</text>
</view> <view>
<text style="color:#09bb07; font-size:35rpx;">③</text>
<text style="font-size:35rpx;" space='nbsp'> 长按"配置按键"大约3S,直至指示灯快闪</text>
</view> <view>
<text style="color:#09bb07; font-size:35rpx;">④</text>
<text style="font-size:35rpx;" space='nbsp'> 输入路由器密码,点击“添加设备”按钮</text>
</view> <view>
<text style="color:#09bb07; font-size:35rpx;" >⑥</text>
<text style="font-size:35rpx;" space='nbsp'> 绑定成功后,软件自动跳转到设备页面</text>
</view> </view> <view class="login-from">
<!--WiFi名称-->
<view class="inputView">
<label class="loginLab">WiFi名称:</label>
<input class="inputText" placeholder="请输入路由器热点" bindinput="ssidInput" value="{{ssidValue}}"/>
</view>
<view class="line"></view> <!--WiFi密码-->
<view class="inputView">
<label class="loginLab">WiFi密码:</label>
<input class="inputText" placeholder="请输入密码" bindinput="passwordInput"/>
</view> <!--按钮-->
<view class="BindClickView">
<button class="BindClick" type="primary" bindtap="BindClick">绑定设备</button>
</view>
</view>
</view>

APUConfig.WXSS

/* pages/APUConfig/APUConfig.wxss */
page{
height: %;
} .container {
height: %;
display: flex;
flex-direction: column;
padding: ;
box-sizing: border-box;
background-color: #f2f2f2
} /*提示信息*/
.hint {
display: flex;
flex-direction: column;
margin-top: 10rpx
} /*表单内容*/
.login-from {
margin-top: 50px;
width: %;
flex: auto;
height:%;
} .inputView {
display: flex;
flex-direction: row;
background-color: #fff;
} /*Wi-Fi名称和密码两个字*/
.loginLab {
margin-left: 10px;
margin-top: 15px;
margin-bottom: 15px;
color: #;
font-size: 16px
} .inputText {
margin-left: 10px;
text-align: left;
margin-top: 15px;
color: black;
font-size: 16px
} .line {
width: %;
height: 1px;
background-color: #cccccc;
margin-top: 1px;
}
/*按钮*/
.BindClickView {
width: %;
height: auto;
background-color: #f2f2f2;
margin-top: 0px;
margin-bottom: 0px;
padding-bottom: 0px;
} .BindClick {
width: %;
margin-top: 35px;
}
util.js
const formatTime = date => {
const year = date.getFullYear()
const month = date.getMonth() +
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds() return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
} const formatNumber = n => {
n = n.toString()
return n[] ? n : '' + n
} // util.newAb2Str代码
var newAb2Str = function newAb2Str(arrayBuffer) {
let unit8Arr = new Uint8Array(arrayBuffer);
let encodedString = String.fromCharCode.apply(null, unit8Arr),
decodedString = decodeURIComponent(escape((encodedString)));//没有这一步中文会乱码
return decodedString;
} module.exports = {
formatTime: formatTime,
newAb2Str: newAb2Str
}

单片机端APUConfig,Wi-Fi配置流程

/**
******************************************************************************
* @author yang feng wu
* @version V1.0.0
* @date 2019/10/12
* @brief 配置8266
******************************************************************************
一,使用说明:指示灯(PC13)
1,把以下程序放在1ms定时器中断中
SmartConfigCnt++;
if(SmartConfigFlage)//配网状态,指示灯闪耀
{
Config8266LedDelay++;
if(Config8266LedDelay>100)
{
Config8266LedDelay=0;
SmartConfigPinOut = ~SmartConfigPinOut;
}
}
else
{
Config8266LedDelay=0;
} 2,调用使用,建议使用一个按钮控制 if(SmartConfig())//配网成功
{
//执行操作
}
******************************************************************************
APUConfig配网绑定流程
设备端
1.获取设备MAC XX:XX:XX:XX:XX:XX
2.控制WIFI发出固定无线网 名称:wifi_8266_bind 密码:11223344
3.UDP 监听固定端口5556
4.等待接收客户端的消息. 消息格式{"ssid":"qqqqq","pwd":"11223344"}
5.提取路由器名称和密码,连接路由器
6.获取链接路由器后分的的IP. 假设是192.168.10.2 以防后期实现局域网通信备用
7.UDP发送数据,{"mac":"XX:XX:XX:XX:XX:XX","ip":"192.168.10.2"} APP/小程序/上位机
1.提示用户连接自己的路由器,长按设备按钮使得设备进入UDP监听状态,提示用户输入路由器密码
2.用户点击绑定设备 , 控制手机尝试连接 名称为 wifi_8266_bind 的无线 (内部控制)
3.成功连接无线,往192.168.4.1:5556 UDP发送路由器信息,1S 1次
4.接收到 {"mac":"XX:XX:XX:XX:XX:XX","ip":"192.168.10.2"}
5.绑定成功
8.完
*/ #define CONFIG8266_C_
#include "include.h" char SmartConfigFlage = ;//是不是在配网
u32 SmartConfigCnt = ;//配网连接路由器延时使用
char SmartConfigSuccess = ;//是否配网成功
u32 Config8266Delay=;//执行Config8266函数内部所需延时
u32 Config8266LedDelay=;//配置8266指示灯闪耀 char ThisSSID[]="";//记录路由器名称
char ThisPWD[]="";//记录密码
char ThisMAC[]="";//记录设备MAC
char ThisIP[]="";//记录设备连接路由器分得的IP /**
* @brief 启用APUConfig 给WIFI配网
* @ warn None
* @param None
* @param None
* @param None
* @param None
* @retval 1:成功
* @example
**/
char APUConfig(void)
{
u32 delay=,Flage=;
SmartConfigPinOut = ;
SmartConfigSuccess = ;
Rst8266(); if(ConfigModuleBlock("+++","+++",NULL))//退出透传
{
if(ConfigModuleBlock("AT+RESTORE\r\n","ready",NULL))//恢复出厂设置
{
if(ConfigModuleBlock("AT+CWMODE_DEF=3\r\n","OK",NULL))//模式3
{
if(ConfigModuleBlock("AT+CIPSTAMAC_CUR?\r\n","MAC_CUR",NULL))//MAC
{
MainString = StrBetwString(Usart1ReadBuff,"MAC_CUR:\"","\"");//得到MAC if(strlen(MainString) ==)
{
memset(ThisMAC,,sizeof(ThisMAC));
memcpy(ThisMAC,MainString,);
}
else {goto end;}
cStringRestore(); if(ConfigModuleBlock("AT+CWSAP_DEF=\"wifi_8266_bind\",\"11223344\",11,4,4\r\n","OK",NULL))//配置发出的无线
{
if(ConfigModuleBlock("AT+CIPSTART=\"UDP\",\"192.168.4.2\",5555,5556,2\r\n","OK",NULL))//配置UDP
{
SmartConfigCnt = ;
while()
{
//{"ssid":"qqqqq","pwd":"11223344"}
//{"mac":"XX:XX:XX:XX:XX:XX","ip":"192.168.10.2"}
//*StrBetwString(char *Str,char *StrBegin,char *StrEnd)
IWDG_Feed();//喂狗
if(Usart1ReadFlage==)
{
Usart1ReadFlage=; MainString = StrBetwString(Usart1ReadBuff,"\"ssid\":\"","\"");//获取ssid
if(MainString!=NULL)
{
memset(ThisSSID,,sizeof(ThisSSID));
sprintf(ThisSSID,"%s",MainString);
cStringRestore(); MainString = StrBetwString(Usart1ReadBuff,"\"pwd\":\"","\"");//获取pwd
if(MainString!=NULL)
{
memset(ThisPWD,,sizeof(ThisPWD));
sprintf(ThisPWD,"%s",MainString);
cStringRestore();
break;
}
else {goto end;}
}
else {goto end;}
} if(SmartConfigCnt>) {goto end;}//60S超时
} if(ConfigModuleBlock("AT+CWAUTOCONN=1\r\n","OK",NULL))//自动连接路由器
{
memset(MainBuffer,,sizeof(MainBuffer));
sprintf(MainBuffer,"AT+CWJAP_DEF=\"%s\",\"%s\"\r\n",ThisSSID,ThisPWD);
if(ConfigModuleBlock(MainBuffer,"WIFI GOT IP",NULL))//设置连接的路由器
{
Flage = ;//配网成功
SmartConfigSuccess=; if(ConfigModuleBlock("AT+CIPSTA_CUR?\r\n","CIPSTA_CUR:ip",NULL))//获取路由器分得的IP
{
MainString = StrBetwString(Usart1ReadBuff,"CUR:ip:\"","\"");//得到路由器分得的IP
if(MainString != NULL)
{
memset(ThisIP,,sizeof(ThisIP));
memcpy(ThisIP,MainString,strlen(MainString)); split(MainString,".",NULL,&MainLen);//XXX.XXX.XXX.XXX if(MainLen == )
{
MainLen = sprintf(MainBuffer,"{\"mac\":\"%s\",\"ip\":\"%s\"}",ThisMAC,ThisIP);
MainLen = sprintf(MainBuffer,"AT+CIPSEND=%d\r\n",MainLen); if(ConfigModuleBlock(MainBuffer,">",NULL))//准备向UDP客户端发送消息
{
memset(MainBuffer,,sizeof(MainBuffer));
MainLen = sprintf(MainBuffer,"{\"mac\":\"%s\",\"ip\":\"%s\"}",ThisMAC,ThisIP);
printf("%s",MainBuffer); SmartConfigCnt = ;
while(SmartConfigCnt<)
{
IWDG_Feed();//喂狗
} }else {goto end;}
}else {goto end;}
}else {goto end;}
cStringRestore();
}
}
}
}
}
}
}
}
}
end:
if(ConfigModuleBlock("AT+CWMODE_DEF=1\r\n","OK",NULL))//模式1
{}
Rst8266();//复位
SmartConfigFlage = ;
return Flage;
}

1.把文件导入工程

    

2.请在util.js添加如下代码

  

使用
1.跳转到 APUConfig 函数,请在自己需要的地方自行填写

  

2.绑定完成,跳转到的页面(请根据自己的设置跳转路径)

  

3.在跳转的页面获取绑定的数据

  

ESA2GJK1DH1K微信小程序篇: 测试微信小程序APUConfig给WI-Fi模块配网并绑定设备,并通过MQTT控制设备的更多相关文章

  1. ESA2GJK1DH1K微信小程序篇: 测试微信小程序扫描Air202上面的二维码绑定设备,并通过MQTT控制设备

    前言 一,微信小程序篇小程序下载(该功能为小程序篇基础功能源码) 实现功能概要 微信小程序通过扫描GPRS上的二维码,绑定GPRS设备.然后使用小程序通过GPRS远程控制开发板上的继电器, 远程显示单 ...

  2. 5-(微信小程序篇)关于WiFi模块配网以后利用小程序绑定设备,绑定方式说明

    https://www.cnblogs.com/yangfengwu/p/11625189.html 众所周知:使用微信Airkiss 只能给设备配网,并不能够获取设备的MAC地址信息,但是我在 ht ...

  3. ESA2GJK1DH1K基础篇: 测试APP扫描Air202上面的二维码绑定通过MQTT控制设备(兼容SIM800)

    前言 此程序兼容SIM800 如果想绑定SIM800,请把其IMEI号,生成二维码,用手机APP扫描. 实现功能概要 APP通过扫描二维码获取GPRS设备的IMEI号,然后设置订阅的主题:device ...

  4. 微信小程序篇(微信小程序的支付)

    微信小程序的支付和微信公众号的支付是类似的,对比起来还比公众号支付简单了一些,我们只需要调用微信的统一下单接口获取prepay_id之后我们在调用微信的支付即可. 今天我们来封装一般node的支付接口 ...

  5. 10-网页,网站,微信公众号基础入门(使用微信自带配置选项实现Airkiss配网)

    https://www.cnblogs.com/yangfengwu/p/11066036.html 如果提交失败多提交两次,只要上一节可以,,这一节一定可以的 如果没有设备 这个是我的二维码 咱就测 ...

  6. ESP8266开发之旅 网络篇⑭ web配网

    1. 前言     目前,市面上流行多种配网方式: WIFI模块的智能配网(SmartConfig以及微信AirKiss配网) SmartConfig 配网方式 请参考博主之前的博文 ESP8266开 ...

  7. ESA2GJK1DH1K微信小程序篇: 安装Nginx,配置反向代理

    前言 一,为什么需要反向代理 小程序访问的是 443端口,咱需要把443端口的数据传给MQTT 这节为了避免大家配置出错,以下源码已经配置. 如果大家想自己配置,请参考 https://www.cnb ...

  8. 【转】微信小程序专项测试

    微信小程序专项测试 by 云层 原文地址: http://mp.weixin.qq.com/s?__biz=MzA4NDIzNTIzNA==&mid=2654370226&idx=1& ...

  9. WeTest+微信:小程序云端测试系统上线

    日前,微信新增小程序测试系统,可便于开发者检测小程序缺陷,评估小程序产品质量.在小程序发布之前,开发者可将小程序代码提交到测试系统,在不同型号的手机真机上运行,执行完毕后自动生成测试报告.小程序云端测 ...

随机推荐

  1. 《MySQL实战45讲》学习笔记2——MySQL的日志系统

    一.日志类型 逻辑日志:存储了逻辑SQL修改语句 物理日志:存储了数据被修改的值 二.binlog 1.定义 binlog 是 MySQL 的逻辑日志,也叫二进制日志.归档日志,由 MySQL Ser ...

  2. 分享大麦UWP版本开发历程-01.响应式轮播顶部焦点图

    话说有一天,临近下班无心工作,在网上看各种文章,阅读到了一篇名为<聊聊大麦网UWP版的首页顶部图片联动效果的实现方法>(传递:http://www.cnblogs.com/hippieZh ...

  3. CentOS 7.0 更改SSH 远程连接 端口号

    许多学习过redhat 7的同学们,在使用centos的时候总会遇到一些问题,因为centos在安装时会默认开启一些服务,今天我们就来更改下centos 7.0的SSH端口. 操作步骤: 远程登录到c ...

  4. Web应用调用.Net Core API

    Web应用调用.Net Core API 一.新建Web Application应用: 选择Web Application 新建好之后页面如下: 二.新建Model.新建Model文件夹并建立apiM ...

  5. BZOJ3209: 花神的数论题(数位DP)

    题目: 3209: 花神的数论题 解析: 二进制的数位DP 因为\([1,n]\)中每一个数对应的二进制数是唯一的,我们枚举\(1\)的个数\(k\),计算有多少个数的二进制中有\(k\)个\(1\) ...

  6. Android-----RadioButton单选使用(实现简单温度转换)

    废话少说,直接上代码: xml布局文件代码: <?xml version="1.0" encoding="utf-8"?> <LinearLa ...

  7. Spark高级函数应用【combineByKey、transform】

    一.combineByKey算子简介 功能:实现分组自定义求和及计数. 特点:用于处理(key,value)类型的数据. 实现步骤: 1.对要处理的数据进行初始化,以及一些转化操作 2.检测key是否 ...

  8. Shell中根据svn是否有待更新的版本去决定是执行maven打包

    1- svn 更新判断代码 本着学习的目的,这里使用了两种获取version的方法. localVersion=$(svn info -R | grep "Revision\:" ...

  9. 【MySQL】查看建表语句

    命令如下: SHOW CREATE TABLE tbl_name 例子: mysql> show create table m_zhbess_vehicle_report\G ********* ...

  10. Golang: 解析JSON数据之三

    前面我们介绍了 Marshal 和 Unmarshal 方法,今天再解一下另外两个 API:Encoder 和 Decoder. Encoder Encoder 主要负责将结构对象编码成 JSON 数 ...