微信公众号开发之"你问我答"demo
微信开发
微信的开发主要是两个平台:微信公众平台和微信开放平台;基于这两个不同的平台可以开发不同的需求
-
微信公众平台 开发服务号、订阅号、小程序、企业微信
-
微信开放平台 开发移动应用、网站应用、公众账号、第三方平台
两个服务平台的区别:
- 微信公众平台面向的是普通的用户,比如自媒体和媒体,企业官方微信公众账号运营人员使用,当然你所在的团队或者公司有实力去开发一些内容,也可以调用公众平台里面的接口,比如自定义菜单,自动回复,查询功能。目前大多数微信在认证之后,都在做这个事情。
- 开放平台面向的是开发者和第三方独立软件开发商。开发者或软件开发商通过微信开放提供的平台和接口,可以开发适合企业的电子商务网站,比如微信登录,微信支付等。
公众号开发实战
准备工具
内网穿透
问题一:什么是内网穿透?
内网穿透简单来说就是将内网外网通过natapp隧道打通,让内网的数据让外网可以获取。比如常用的办公室软件等,一般在办公室或家里,通过拨号上网,这样办公软件只有在本地的局域网之内才能访问,那么问题来了,如果是手机上,或者公司外地的办公人员,如何访问到办公软件呢?这就需要natapp内网穿透工具了。运行natapp隧道之后,natapp会分配一个专属域名/端口,办公软件就已经在公网上了,在外地的办公人员可以在任何地方愉快的访问办公软件了~~
问题二:能干什么?
-
上文举例的办公软件
-
放在家里的树莓派,服务器等,需要远程ssh管理,这样打通服务器的22端口即可远程通过ssh操作服务器了.
-
微信/支付宝等本地开发.现在微信/支付宝等应用,需要服务器接收微信/支付宝发送的回调信息,然而在本地开发程序的话,还得实时上传到服务器,以便支持微信/支付宝的回调信息,如果使用了natapp内网穿透软件,将回调地址设置成natapp提供的地址,回调数据立即传递回本地,,这样很方便的在本地就可以实时调试程序,无须再不断上传服务器等繁琐且无意义的步骤.
-
一些企业内部数据库,由于安全等原因,不愿意放到云服务器上,可以将数据库放到办公室本地,然后通过natapp的tcp隧道映射,这样既保证安全,又保证公网可以正常访问.
-
一些开发板做的监控等信息,每台设备运行一条隧道,可以方便的管理监控各个设备的运行情况.
-
一些本地运行的游戏,想和好基友一起联网玩,一条命令运行natapp即可实现联网游戏.
-
群辉上运行natapp之后,随时随地在任何地方可以访问到群辉上应用
问题三:如何使用?
- 注册登录natapp网站,先下载natapp,放到本地
- 在网站上创建自己的免费隧道,并将端口映射到本地的80端口上(微信平台的要求)
- 记录下自己的 authtoken
- 在本地创建一个bat脚本,内容是natapp的启动命令
natapp -authtoken=ad6fa1b675****
- 查看映射的域名,值得注意的是这个域名会隔一段时间变化一次
本地开发:
步骤一:创建SpringBoot项目
该项目依赖于SpringBoot的2.0.6.RELEASE版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
导入依赖,其中注释了的一个依赖是关于Spring配置文件的,可加可不加
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-configuration-processor</artifactId>-->
<!--<optional>true</optional>-->
<!--</dependency>-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.xfire</groupId>
<artifactId>xfire-core</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.60</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
</dependencies>
配置application.yml文件(其中的token是自己定义的;appid和sceret需要在公众号平台查看;tokenUrl是公众号平台提供的获取access-token的接口),暂时先把选项添加上
server:
port: 80
spring:
application:
name: hello
data:
wechat:
token: admin
appid: wx9f1c0f01*****
sceret: 53275ff4638a0e32d4436bd9e*****
tokenUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${data.wechat.appid}&secret=${data.wechat.sceret}
创建配置类
创建controller类,来和公众号平台获取连接,值得注意的是,本地开发提供一个Get请求接口
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private WeChatUtils weChatUtils;
@GetMapping("/check")
public String checkLink(@RequestParam String signature,
@RequestParam String timestamp,
@RequestParam String nonce,
@RequestParam String echostr
){
return weChatUtils.checkLink(signature, timestamp, nonce, echostr);
}
}
WeChatUtils工具类中提供一个checkLink方法
public String checkLink(String signature,String timestamp,String nonce,String echostr){
//1)将token、timestamp、nonce三个参数进行字典序排序
String[] arr = {weChatConfig.getToken(),timestamp,nonce};
Arrays.sort(arr);
//2)将三个参数字符串拼接成一个字符串进行sha1加密
StringBuilder stringBuilder = new StringBuilder();
for (String s:arr) {
stringBuilder.append(s);
}
String sha1 = getSha1(stringBuilder.toString());
//3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if (signature.equals(sha1)) {
return echostr;
}else {
return "";
}
}
步骤二:订阅号配置
- 登录公众号开发平台,点击跳转至链接
- 获取appid及secret;并将这两个数据填入application.yml
- 设置本机IP白名单( 在IP白名单内的IP来源,获取access_token接口才可调用成功)
- 启动本地SpringBoot项目,并且natapp保持开启
- 在左侧<开发>菜单项下的<基本配置>中设置好内网穿透的URL(能够提供checkLink校验的)、Token(本地项目设置的)等;点击提交,提示通过就可继续开发。如果没有校验通过,看看自己的natapp的域名是否发生变化;还有就是后台接口是否正确。
- 至此算是本地服务器与微信服务器建立了连接
步骤三:自定义消息回复
在本地服务中校验连接的controller中,添加一个支持Post请求的接口,并且该接口的url和校验的一致;其功能就是能够解析微信服务器传过来的一个xml数据,并根据msgType类型进行响应
@PostMapping(value = "/check")
public String messageMonitor(HttpServletRequest request) {
return service.message(request);
}
开发MessageService,下面给出代码片段
@Override
public String message(HttpServletRequest request){
Map<String, String> map = null;
try {
map = utils.xmlToMap(request);
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(TO_USER_NAME,map.get(FROM_USER_NAME));
hashMap.put(FROM_USER_NAME,map.get(TO_USER_NAME));
hashMap.put(CREATETIME,String.valueOf(System.currentTimeMillis()));
String msgType = map.get(MSGTYPE);
String message = "success";
String content = "";
String mediaId = "";
switch (msgType) {
case "text" :
content = map.get(CONTENT);
if (content.contains("笑话")) {
hashMap.put(MSGTYPE,"voice");
hashMap.put("MediaId",luoSmartReply.voiceReply(content));
}else if (content.contains("图片")) {
hashMap.put(MSGTYPE,"image");
hashMap.put("MediaId",luoSmartReply.pictureReply(content));
}else {
hashMap.put(MSGTYPE,msgType);
hashMap.put(CONTENT,luoSmartReply.textReply(content));
}
try {
LOGGER.info("{} success",content);
message = utils.mapToXml(hashMap);
break;
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
case "image":
hashMap.put(MSGTYPE,msgType);
mediaId = map.get("MediaId");
hashMap.put("MediaId",mediaId);
try {
message = utils.mapToXml(hashMap);
break;
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
case "voice":
hashMap.put(MSGTYPE,msgType);
mediaId = map.get("MediaId");
hashMap.put("MediaId",mediaId);
try {
message = utils.mapToXml(hashMap);
break;
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
default:
break;
}
return message;
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
return "success";
}
除此之外还需要提供能够获取access_token的接口,该接口需要向微信提供的接口发送Get请求,下面是一个get请求方法。当我们能够获取的access_token的时候,就可以进行下一步操作了。
public static Object get(String uri) {
HttpGet httpGet = new HttpGet(uri);
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
try {
CloseableHttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
String res = EntityUtils.toString(entity,DEFAULT_ENCODING);
return JSONObject.parse(res);
} catch (IOException e) {
LOGGER.error(e.getMessage());
return null;
} finally {
try {
httpClient.close();
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
}
}
在微信的debug平台,进行本地服务的api测试,后台是否能够支持;根据返回值判断
当本地服务写好启动之后,还需要在微信平台启动该服务配置
demo代码:码云