一、前言
方便要開發微信小程序的朋友們,可以快速將服務搭建起來,不要把時間浪費在服務的搭建上,專心寫我們的業務代碼。
你需要了解的知識:
1.微信小程序大概的開發流程
2.註冊小程序(個人測試賬號)
3.服務器的配置
4.內網穿透(我用的是花生殼)
5.小程序開發文檔先大概看一遍
廢話不多說了,你懂得,直接講重點…
源碼下載地址(服務端):下載
源碼下載地址(小程序demo):下載
二、腳手架預覽
2.1 項目結構
2.2 小程序demo
整體小程序上就實現上面這些功能。
三、怎麼快速把項目跑起來(服務端)
3.1 修改配置 application.xml
logging:
level:
org.springframework.web: info
com.lxh.miniapp: debug
cn.binarywang.wx.miniapp: debug
server:
port: 80
servlet:
context-path: /
wx:
miniapp:
configs:
- appid: wx2d88824e64axxxxxx
secret: f1e1d8785bfbe9d36d538xxxxxxxxx
token: miniapp
aesKey: K1ewVmypZKPTl2BIB8ySTY9C5rpteZxxxxxxxxx
msgDataFormat: JSON
3.2 啓動服務
WxMiniAppDemoApplication.java
四、核心代碼
本項目依賴WxJava - 微信開發 Java SDK。https://github.com/Wechat-Group/WxJava
由於本項目支持多個小程序,在調用接口時,要先調用下面接口切換一下小程序。
public static WxMaService switchover(String appid){
if (StringUtils.isBlank(appid)){
throw new IllegalArgumentException("appId爲空");
}
WxMaService wxMaService = WxMiniAppConfiguration.getMaService(appid);
if (wxMaService == null) {
throw new IllegalArgumentException(String.format("未找到對應appid=[%s]的配置,請覈實!", appid));
}
return wxMaService;
}
4.1 WxMiniAppConfiguration.java
配置消息處理,將WxJava SDK進行初始化
package com.lxh.miniapp.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.bean.*;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Data;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* created by [email protected] on 2020/2/23
*/
@Configuration
@Data
@EnableConfigurationProperties(WxMiniAppProperties.class)
public class WxMiniAppConfiguration {
private static final Logger logger = LoggerFactory.getLogger(WxMiniAppConfiguration.class);
private WxMiniAppProperties miniAppProperties;
private static Map<String/*appid*/, WxMaMessageRouter> routers = Maps.newHashMap();
private static Map<String/*appid*/, WxMaService> maServices = Maps.newHashMap();
public WxMiniAppConfiguration(WxMiniAppProperties miniAppProperties) {
this.miniAppProperties = miniAppProperties;
}
public static WxMaService getMaService(String appid) {
WxMaService wxService = maServices.get(appid);
if (wxService == null) {
throw new IllegalArgumentException(String.format("未找到對應appid=[%s]的配置,請覈實!", appid));
}
return wxService;
}
public static WxMaMessageRouter getRouter(String appid) {
return routers.get(appid);
}
/**
* 優先進行初始化
*/
@PostConstruct
public void init(){
// 1.獲取到所有小程序的配置
List<WxMiniAppProperties.Config> configs = miniAppProperties.getConfigs();
if (configs == null) {
throw new RuntimeException("請添加下相關配置,注意別配錯了!");
}
// 2.初始化maService和消息路由
maServices = configs.stream().map(e -> {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
config.setAppid(e.getAppid());
config.setSecret(e.getSecret());
config.setToken(e.getToken());
config.setAesKey(e.getAesKey());
config.setMsgDataFormat(e.getMsgDataFormat());
WxMaService maService = new WxMaServiceImpl();
maService.setWxMaConfig(config);
routers.put(e.getAppid(), this.newRouter(maService));
return maService;
}).collect(Collectors.toMap(a-> a.getWxMaConfig().getAppid(), a-> a));
}
/**
* 新建路由
* @param maService
* @return
*/
private WxMaMessageRouter newRouter(WxMaService maService){
final WxMaMessageRouter router = new WxMaMessageRouter(maService);
// 配置路由規則
router.rule().handler(LogHandler).next()
.rule().async(false).msgType(WxConsts.KefuMsgType.TEXT).handler(textHandler).end();
return router;
}
private final WxMaMessageHandler LogHandler = (message, context, service, sessionManager) -> {
logger.info("【消息日誌】:" + message.toString());
return null;
};
private final WxMaMessageHandler textHandler = (message, context, service, sessionManager) -> {
logger.info("textHandler:" + message.toString());
String msg = message.getContent();
switch (msg){
case "圖片":{
sendPic(service, message);
break;
}
case "模板":{
sendTemplate(service, message);
break;
}
case "訂閱":{
sendSubscribeMsg(service, message);
break;
}
case "二維碼":{
sendQrCode(service, message);
break;
}
default:{
sendText(service, message);
break;
}
}
return null;
};
private void sendText(WxMaService service, WxMaMessage message){
try {
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回覆文本消息")
.toUser(message.getFromUser()).build());
}catch (Exception e){
logger.error(e.getMessage());
}
}
private void sendPic(WxMaService service, WxMaMessage message){
try {
WxMediaUploadResult uploadResult = service.getMediaService()
.uploadMedia("image", "jpg", ClassLoader.getSystemResourceAsStream("lxh.jpg"));
service.getMsgService().sendKefuMsg(
WxMaKefuMessage
.newImageBuilder()
.mediaId(uploadResult.getMediaId())
.toUser(message.getFromUser())
.build());
} catch (WxErrorException e) {
logger.error(e.getMessage());
}
}
// 由於“模板消息”將下線,已不再支持添加模板,請儘快接入“訂閱消息”。
private void sendTemplate(WxMaService service, WxMaMessage message){
try {
service.getMsgService().sendTemplateMsg(WxMaTemplateMessage.builder()
.templateId("2KwZNrdSs78NToB2CBzNKMfwoDN4fAggC7jJAweA4hM")
.formId("自己替換可用的formid")
.data(Lists.newArrayList(
new WxMaTemplateData("keyword1", "模板替換值。。。", "#173177")))
.toUser(message.getFromUser())
.build());
}catch (Exception e){
logger.error(e.getMessage());
}
}
private void sendSubscribeMsg(WxMaService service, WxMaMessage message){
try {
service.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder()
.templateId("2KwZNrdSs78NToB2CBzNKMfwoDN4fAggC7jJAweA4hM")
.data(Lists.newArrayList(
new WxMaSubscribeData("thing1", "一起學習呀", "#173177"),
new WxMaSubscribeData("thing2", "今晚一起去圖書館學習啊", "#173177")
))
.toUser(message.getFromUser())
.build());
}catch (Exception e){
logger.error(e.getMessage());
}
}
private void sendQrCode(WxMaService service, WxMaMessage message){
try {
final File file = service.getQrcodeService().createQrcode("qrCode", 430);
WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file);
service.getMsgService().sendKefuMsg(
WxMaKefuMessage
.newImageBuilder()
.mediaId(uploadResult.getMediaId())
.toUser(message.getFromUser())
.build());
} catch (WxErrorException e) {
logger.error(e.getMessage());
}
}
}
4.2 WxPortalController.java
package com.lxh.miniapp.controller;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
import com.lxh.miniapp.utils.MiniAppUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.util.Objects;
/**
* created by [email protected] on 2020/2/21
* 門戶網站
*/
@Slf4j
@RestController
@RequestMapping("/wx/portal/{appid}")
public class WxPortalController {
private static final Logger logger = LoggerFactory.getLogger(WxPortalController.class);
private WxMaService wxMaService;
@GetMapping(produces = "text/plain;charset=utf-8")
public String authGet(@PathVariable String appid,
@RequestParam(name = "signature", required = false) String signature,
@RequestParam(name = "timestamp", required = false) String timestamp,
@RequestParam(name = "nonce", required = false) String nonce,
@RequestParam(name = "echostr", required = false) String echostr) {
log.info("\n接收到來自微信服務器的認證消息:[{}, {}, {}, {}]", signature, timestamp, nonce, echostr);
if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
throw new IllegalArgumentException("請求參數非法,請覈實!");
}
// 切換小程序
wxMaService = MiniAppUtils.switchover(appid);
if (wxMaService.checkSignature(timestamp, nonce, signature)) {
return echostr;
}
return "非法請求";
}
@PostMapping(produces = "application/xml; charset=UTF-8")
public String post(@PathVariable String appid,
@RequestBody String requestBody,
@RequestParam(name = "msg_signature", required = false) String msgSignature,
@RequestParam(name = "signature", required = false) String signature,
@RequestParam(name = "encrypt_type", required = false) String encryptType,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce) {
logger.info("\n接收微信請求:[msg_signature=[{}], encrypt_type=[{}], signature=[{}]," + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
msgSignature, encryptType, signature, timestamp, nonce, requestBody);
wxMaService = MiniAppUtils.switchover(appid);
final boolean isJson = Objects.equals(wxMaService.getWxMaConfig().getMsgDataFormat(), WxMaConstants.MsgDataFormat.JSON);
// 明文傳輸的消息
if (StringUtils.isBlank(encryptType)) {
WxMaMessage inMessage;
if (isJson) {
inMessage = WxMaMessage.fromJson(requestBody);
} else {//xml
inMessage = WxMaMessage.fromXml(requestBody);
}
this.route(inMessage, appid);
return "success";
}
// aes加密的消息
if ("aes".equals(encryptType)) {
WxMaMessage inMessage;
if (isJson) {
inMessage = WxMaMessage.fromEncryptedJson(requestBody, wxMaService.getWxMaConfig());
} else {//xml
inMessage = WxMaMessage.fromEncryptedXml(requestBody, wxMaService.getWxMaConfig(), timestamp, nonce, msgSignature);
}
this.route(inMessage, appid);
return "success";
}
throw new RuntimeException("不可識別的加密類型:" + encryptType);
}
private void route(WxMaMessage message, String appid) {
try {
WxMaMessageRouter router = MiniAppUtils.getRouter(appid);
router.route(message);
} catch (Exception e) {
this.logger.error(e.getMessage(), e);
}
}
}
五、小程序核心代碼
5.1 通過code到後臺換取 openId, sessionKey, unionId
// 登錄
wx.login({
success: res => {
if(this.globalData.openId != null){
console.log("openId:" + this.globalData.openId)
return;
}
// 發送 res.code 到後臺換取 openId, sessionKey, unionId
console.log("登錄:"+res.code)
var oauthUrl = this.globalData.baseUrl + "wx/user/" + this.globalData.appId + "/login";
if (res.code) {
wx.request({
url: oauthUrl,
data: {
code: res.code
},
success: function (res) {
console.log(res)
const self = this
//獲取到用戶憑證 存儲 3rd_session
var json = JSON.parse(res.data.Data)
wx.setStorage({
key: "third_Session",
data: json.third_Session
})
},
fail: function (res) {
}
})
}
},
fail: function (res) {
}
})
},
5.2 全局變量設置app.js
globalData: {
userInfo: null,
openId: 'o0aPU5JJUSr1DoNtkJXqSFa1CqoU',
unionId: null,
baseUrl: 'http://chenxingxing.51vip.biz/',
appId: 'wx2d88824e64aacf4d'
}
5.3 獲取用戶信息
onLoad() {
var that = this;
wx.login({
success: function (e) {
// 獲取用戶信息
var code = e.code;
wx.getSetting({
success: res => {
console.log(res.authSetting);
if (res.authSetting['scope.userInfo']) {
// 已經授權,可以直接調用 getUserInfo 獲取頭像暱稱,不會彈框
wx.getUserInfo({
success: res => {
console.log(res)
// 可以將 res 發送給後臺解碼出 unionId
that.setData({ userInfo: res.userInfo })
that.setData({ isAuth: true })
var userObj = {};
userObj.encryptedData = res.encryptedData;
userObj.iv = res.iv;
userObj.expires_in = Date.now() + 24 * 7 * 60 * 60;
wx.setStorageSync('userInfo', res.userInfo);
wx.setStorageSync('userObj', userObj);
app.globalData.userInfo = res.userInfo;
that.thirdLogin(code,userObj);
// 所以此處加入 callback 以防止這種情況
if (that.userInfoReadyCallback) {
that.userInfoReadyCallback(res)
}
}
})
}else{
that.setData({ isAuth: false })
}
}
})
}
});
},
5.4 web-view
建立一個專門跳網頁page,通過動態傳遞url,進行跳轉
jump: function(){
var type = "baidu";
wx.navigateTo({
url: "/pages/webview/book?type="+ type,
})
console.log(this.data.userInfo)
}
/**
* 生命週期函數--監聽頁面加載
*/
onLoad: function (options) {
if (options.type = "baidu") {
this.setData({
url: "www.baidu.com"
})
}
}
<web-view src="https://{{url}}"></web-view>
5.5 用戶訂閱
subscribeMessage: function(e) {
console.log(e)
wx.requestSubscribeMessage({
tmplIds: ['2KwZNrdSs78NToB2CBzNKMfwoDN4fAggC7jJAweA4hM', 'fl9rdF50TxnOujwzCbl6KytQJrj3dKCsDTEz2O8gpGQ'],
success(res) {
console.log(res)
}
})
}
5.6 用戶授權後刷新頁面
<button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo">授權登錄</button>
bindGetUserInfo: function (e) {
console.log(e.detail.userInfo)
if (e.detail.userInfo) {
//用戶按了允許授權按鈕
this.onLoad();
} else {
//用戶按了拒絕按鈕
}
}
5.7 撥打電話
call:function(){
wx.makePhoneCall({
phoneNumber: phone//僅爲示例,並非真實的電話號碼
})
}
5.8 分享小程序
onShareAppMessage: function (res) {
if (res.from === 'button') {
console.log(res.target)
}
return {
title: '小程序標題',
path: 'pages/index/index'
}
}
5.9 預覽圖片
不支持本地圖片
previewImage: function (e) {
wx.previewImage({
current: this.data.url, // 當前顯示圖片的http鏈接
urls: [this.data.url] // 需要預覽的圖片http鏈接列表
})
}
5.10 地圖
<map id="map" longitude="112.984954" latitude="28.174734" scale="14" controls="{{controls}}" bindcontroltap="controltap" markers="{{markers}}" bindmarkertap="markertap" bindregionchange="regionchange" show-location style="width: 100%; height: 300px;"></map>
Page({
data: {
title: '',
markers: [{
iconPath: "../../images/location.png",
id: 0,
latitude: 28.18116,
longitude: 112.991562,
width: 50,
height: 50,
}],
},
regionchange(e) {
console.log(e.type)
},
markertap(e) {
console.log(e.markerId)
},
controltap(e) {
console.log(e.controlId)
}
到這裏就差不多了,恭喜你,兄弟…