微信小程序脚手架

06/12 10:35
阅读数 395

在这里插入图片描述

一、前言

方便要开发微信小程序的朋友们,可以快速将服务搭建起来,不要把时间浪费在服务的搭建上,专心写我们的业务代码。

你需要了解的知识:

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 lanxinghua@2dfire.com 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 lanxinghua@2dfire.com 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)
  }

到这里就差不多了,恭喜你,兄弟…

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部