微信小程序腳手架

在這裏插入圖片描述

一、前言

方便要開發微信小程序的朋友們,可以快速將服務搭建起來,不要把時間浪費在服務的搭建上,專心寫我們的業務代碼。

你需要了解的知識:

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)
  }

到這裏就差不多了,恭喜你,兄弟…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章