微信公众号开发
运行效果
微信公众号简介
微信公众号分为服务号、订阅号、企业号,订阅号可以个人申请,服务号和企业号要有企业资质才可以。
我们所说的微信公众号开发指的是订阅号和服务号。关于订阅号和服务器的区别,官方是这样解释的
- 服务号:主要偏向于服务交互(功能类似12315,114,银行,提供绑定信息,服务交互),每月可群发4条消息;服务号**适用人群:媒体、企业、政府或其他组织。
- 订阅号:主要偏向于为用户传达资讯,(功能类似报纸杂志,为用户提供新闻信息或娱乐趣事),每天可群发1条消息;订阅号**适用人群:个人、媒体、企业、政府或其他组织。
注册微信公众号
进入微信公众号注册页面https://mp.weixin.qq.com/点击公众号右上方的注册按钮,进入注册界面,填写基本信息,选择订阅号, 完成身份认证, 即可。
注册测试公众号
个人订阅号有一些接口是没有权限的,也就是说个人订阅号无法调用一些高级的权限接口,如生成二维码、网页授权、自定义菜单、微信支付这样的接口权限个人订阅号是没有调用权限的, 幸运的是,微信公众平台提供了测试公众账号,测试公众号有很多个人订阅号不具备的权限, 测试公众号的注册地址为:
http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
用微信扫描页面中的二维码进行登录,登录成功后,就可以看到腾讯分配给我们的测试公众号的信息了,如下图所示, 接下来我们就可以搭建环境,进行开发测试了
测试公众号的所拥有的接口权限如下:
搭建微信本地调试环境
开发基于微信公众号的应用最大的痛苦之处就是调试问题,每次实现一个功能后都需要部署到一个公网服务器进行测试,因为微信用户每次向公众号发起请求时,微信服务器会先接收到用户的请求,然后再转发到我们的服务器上,也就是说,微信服务器是要和我们的服务器进行网络交互,所以我们必须保证我们的服务器外网可以访问到,这种部署到公网服务器进行测试的做法对于我们开发者来说简直是噩梦。所以我们要想一个办法可以做到本地部署,本地调试代码,而要做到这一点,那么我们要解决的问题就是将内网的部署服务器映射到外网,让微信服务器可以正常访问到,幸运的是,借助于第三方软件Ngrok,我们就可以做得到。Ngrok是一个免费的软件Ngrok,使用Ngrok后,我们就可以实现内网穿透,也就是说我们可以将内网的服务器映射到外网给别人访问,这对于我们在本地开发环境中调试微信代码是以及给用户演示一些东西非常快速和有帮助的,因为可以直接使用我们自己的内网的电脑作为服务器。不过需要翻墙访问.常用的内网穿透工具有natapp,ngrok,dingding,关于微信公众号开发,这三个工具我都使用了,只有natapp可以正常开发。
关于natapp的使用网上很多,我在这里就不在介绍了。
natapp成功标志:
可以通过访问http://xt77eg.natappfree.cc访问到我们本机的服务
微信公众号接入(校验签名)
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
@Controller
@RequestMapping(value = "wx")
public class WeiController{
/**
* 公众号appid
*/
@Value("${wx.appid}")
private String appid;
/**
* 公众号appSecret
*/
@Value("${wx.secret}")
private String secret;
/**
* 微信消息接收和token验证
* @param request
* @param response
* @throws IOException
*/
@GetMapping("/weChatToken")
public void weChat(HttpServletRequest request, HttpServletResponse response) {
boolean isGet = request.getMethod().toLowerCase().equals("get");
if (isGet) {
// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (signature != null && CheckoutUtil.checkSignature(signature, timestamp, nonce)) {
try {
PrintWriter print = response.getWriter();
print.write(echostr);
print.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public class CheckoutUtil {
public static String token = "999";
/**
* 验证签名
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] { token, timestamp, nonce };
// 将token、timestamp、nonce三个参数进行字典序排序
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToHex(digest );
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
return tmpStr != null ? tmpStr.equals(signature) : false;
}
/**
* 十六进制字节数组转为字符串
* @param hash
* @return
*/
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
|
进入微信测试公众号管理界面,在接口配置信息中填入映射的外网地址和代码中声明的token,如下图所示:
点击提交,会显示配置成功,如下图:
到此,我们的公众号应用已经能够和微信服务器正常通信了,也就是说我们的公众号已经接入到微信公众平台了。
给指定用户推送消息
网页授权获取用户openid
如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息(openId),进而实现业务逻辑。
关于网页授权回调域名的说明:
1、在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com 无法进行OAuth2.0鉴权
3、如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可
获取用户openId步骤:
1、引导用户进入授权页面同意授权,获取code
2、通过code换取openId
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
@Controller
@RequestMapping(value = "wx")
public class WeiController{
private String appid="微信公众号的appid";
private String secret="微信公众号的secret";
/**
* 获取微信用户code,并重定向获取用户openId
* @return
*/
@GetMapping("/getUserCode")
public String getUserCode(){
String backUrl = "http://xt77eg.natappfree.cc/wx/getUserOpenId";
String getOpenIdUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri="+ backUrl+"&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect";
getOpenIdUrl = getOpenIdUrl.replace("APPID",appid);
return "redirect:" + getOpenIdUrl;
}
/**
* 获取用户openId
* @return
* @throws IOException
*/
@GetMapping("/getUserOpenId")
@ResponseBody
public String getUserOpenId()throws IOException{
//获取code
String code = request.getParameter("code");
//换取用户openid
String url="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
url=url.replace("APPID", appid).replace("SECRET", secret).replace("CODE", code);
JSONObject result = Util.doGetJson(url);
JSONObject jSONObject = JSONObject.parseObject(String.valueOf(result));
String openid = jSONObject.getString("openid");
return openid;
}
|
给指定用户发送模板信息
首先要准备一个模板,测试号可自定义模板,但在正式公众号我们要申请,或者使用别人已经申请过的模板。
pom:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--微信模版消息推送三方sdk-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.1.8.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
|
Controller:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Controller
@RequestMapping(value = "wx")
public class WeiController{
@Resource
PushMessageService pushMessageService;
/**
* 向每个用户推送消息
* @return
*/
@GetMapping("/sendMessage")
@ResponseBody
public String sendMessage(){
String openId = "用户openId";
if(!"".equals(openId)){
AlarmParamsDTO dto = new AlarmParamsDTO("申请进度", "国家奖学金", "申请通过", time, "成功");
dto.setOpenId(openId);
pushMessageService.pushMessage(dto);
}
return "success";
}
|
Service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
@Service
@Slf4j
public class PushMessageServiceImpl implements PushMessageService{
private String appid="微信公众号appid";
private String secret="微信公众号secret";
/**
* 给微信公众号某个用户推送信息
* @param alarmParamsDTO
*/
@Override
public void pushMessage(AlarmParamsDTO alarmParamsDTO) {
//1,配置
WxMpInMemoryConfigStorage wxStorage = new WxMpInMemoryConfigStorage();
wxStorage.setAppId(appid);
wxStorage.setSecret(secret);
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(wxStorage);
List<WxMpTemplateData> wxMpTemplateData = Arrays.asList(
new WxMpTemplateData("first",alarmParamsDTO.getFirst(),"#000000"),
new WxMpTemplateData("keyword1",alarmParamsDTO.getKeyword1(),"#000080"),
new WxMpTemplateData("keyword2",alarmParamsDTO.getKeyword2(),"#0000FF"),
new WxMpTemplateData("keyword3",alarmParamsDTO.getKeyword3(),"#FFD700"),
new WxMpTemplateData("remark",alarmParamsDTO.getRemark(),"#00FF00")
);
//2,推送消息
WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
.toUser(alarmParamsDTO.getOpenId())
.templateId("tIDrdFcqFGMsTnc462H49_DbjgXUuIjsqIlQttq7VDE")
.data(wxMpTemplateData)
.url("http://www.baidu.com")
.build();
try {
wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
} catch (Exception e) {
System.out.println("推送失败:" + e.getMessage());
}
}
}
|
entity:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class AlarmParamsDTO {
/**
* 推送信息小标题
*/
private String first;
/**
* 学生姓名
*/
private String keyword1;
/**
* 申请资助类型
*/
private String keyword2;
/**
* 申请状态
*/
private String keyword3;
/**
* 申请结果
*/
private String remark;
/**
* 用户微信openId,唯一标识
*/
private String openId;
public AlarmParamsDTO(String first, String keyword1, String keyword2, String keyword3, String remark) {
this.first = first;
this.keyword1 = keyword1;
this.keyword2 = keyword2;
this.keyword3 = keyword3;
this.remark = remark;
}
}
|