微信三方代开发(服务商开发模式)全网最细完整流程

1、首先注册 微信开发平台,步骤略过

在这里插入图片描述

2、创建第三方平台

在这里插入图片描述
注意:账号类型一般选择平台型,可基于第三方平台接口能力,在获取商家扫码授权后,为商家提供代开发、代运营公众号和小程序服务。如果是选择的定制化型,是无法代开发的,只是生成票据用于标记商家小程序是由该服务商开发;
开发模式自行选择,我这里选择的是传统模式

3、开发平台配置

下面是审核成功之后的页面,主要是配置红框里面的内容

这个是审核成功之后的页面

以小程序为例,首先配置小程序权限集(权限集是一个或者多个接口的权限的集合,当小程序或公众号管理员将某权限集授权给服务商,则服务商就获得了该权限集包含的接口的调用权限。)

在这里插入图片描述

勾选对应的小程序权限之后,划到最下面保存,权限集就配置好啦

在这里插入图片描述

点击开发资料,配置开发资料在这里插入图片描述
前面这几个是必须配置的,这里就不截右半边了,也都有对应的说明,按照说明配置就好啦,这里配置的信息千万不要泄露!!!

在这里插入图片描述

然后在其他里面配置一下授权测试公众号/小程序列表(在全网发布之前,仅该列表内公众号或小程序才可进行授权,以便测试。请填写公众号或小程序的原始ID(可在公众平台网站的公众号设置页或小程序设置页找到,照搬的原说明)和白名单ip地址列表

在这里插入图片描述

绑定一个代开发小程序(该小程序是用来开发小程序代码的,不是客户的小程序!!!不是客户的小程序!!!不是客户的小程序!!!),到这里基本上开发平台创建和配置就搞定了,接下来就是码农最喜欢的编码环节了。

在这里插入图片描述

3、前后端编码,我是后端哈,前端主要是配置ext.json。重点介绍后端需要哪些接口

1、前端配置文件ext.json,测试阶段可以将directCommit设置成true,代码不会提交到模板库,而是直接上传到客户的小程序上并且会直接生成体验版。当然这仅仅是用于快速上线测试,其他情况还是老老实实的提交到模板库,后端拿到模板id调用接口将代码上传到客户小程序上

在这里插入图片描述

2、 这个是apifox上面写的接口文档,当然还缺少很多接口,我这里只挑这部分,其他的大家按照官方文档来敲就好啦,这里用到了weixin-java-open包

在这里插入图片描述

2.1 首先引入依赖,其余依赖的大家自己去搞哈,我默认大家都是后端熟手,我这里只粘对接微信开放平台的依赖包
  <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-open</artifactId>
             <version>4.6.0</version>
        </dependency>
2.2 配置文件

在这里插入图片描述`

package com.yycm.platform.system.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @Description
 * @Author ZJF
 * @Date 2024/9/27 12:30
 */
@Data
@ConfigurationProperties(prefix = "wx.open")
public class WxComponentProperties {

    /**
     * 设置微信三方平台的appid
     */
    private String componentAppId;

    /**
     * 设置微信三方平台的app secret
     */
    private String componentSecret;

    /**
     * 设置微信三方平台的token
     */
    private String componentToken;

    /**
     * 设置微信三方平台的EncodingAESKey
     */
    private String componentAesKey;
}

2.3 配置wxOpenService
package com.yycm.platform.system.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.yycm.platform.starter.mybatis.core.query.LambdaQueryWrapperX;
import com.yycm.platform.system.mapper.DeptMapper;
import com.yycm.platform.system.pojo.dto.DeptDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.open.api.WxOpenService;
import me.chanjar.weixin.open.api.impl.WxOpenInRedissonConfigStorage;
import me.chanjar.weixin.open.api.impl.WxOpenMessageRouter;
import me.chanjar.weixin.open.api.impl.WxOpenServiceImpl;
import org.redisson.api.RedissonClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import yycm.platform.common.exception.PlatformException;

import java.util.List;

/**
 * @Description
 * @Author ZJF
 * @Date 2024/9/27 12:42
 */
@RequiredArgsConstructor
@Slf4j
@Configuration
@EnableConfigurationProperties(WxComponentProperties.class)
public class WxComponentConfiguration {

    private final WxComponentProperties properties;

    private final RedissonClient redissonClient;


    private final DeptMapper deptMapper;


    @Bean
    public WxOpenService wxOpenService() {
        WxOpenService wxOpenService = buildWxOpenService();
        //将授权小程序的appid的refreshtoken设置进来。我是将客户小程序绑定到组织机构上的,
        // 所以把refreshtoken也放到上面的,各位按自己的业务来。refreshtoken咋来的,看后面文章哈,有讲到的
        List<DeptDto> deptDtoList = deptMapper.selectList(new LambdaQueryWrapperX<DeptDto>().isNotNull(DeptDto::getWxAd).isNotNull(DeptDto::getAuthorizerRefreshToken));
        if (CollUtil.isNotEmpty(deptDtoList)) {
            deptDtoList.forEach(o -> wxOpenService.getWxOpenConfigStorage().setAuthorizerRefreshToken(o.getWxAd(), o.getAuthorizerRefreshToken()));
        }
        return wxOpenService;
    }


    private WxOpenService buildWxOpenService() {
        WxOpenService wxOpenService = new WxOpenServiceImpl();
        WxOpenInRedissonConfigStorage inRedisConfigStorage = new WxOpenInRedissonConfigStorage(redissonClient);
        inRedisConfigStorage.setComponentAppId(properties.getComponentAppId());
        inRedisConfigStorage.setComponentAppSecret(properties.getComponentSecret());
        inRedisConfigStorage.setComponentToken(properties.getComponentToken());
        inRedisConfigStorage.setComponentAesKey(properties.getComponentAesKey());
        wxOpenService.setWxOpenConfigStorage(inRedisConfigStorage);
        return wxOpenService;
    }


//  这里是接收公众号的消息bean
    @Bean
    public WxOpenMessageRouter wxOpenMessageRouter(WxOpenService wxOpenService) {
        WxOpenMessageRouter wxOpenMessageRouter = new WxOpenMessageRouter(wxOpenService);
        wxOpenMessageRouter.rule().handler((wxMpXmlMessage, params, wxMpService, wxSessionManager) -> {
            log.info("\n接收到 {} 公众号请求消息,内容:{}", wxMpService.getWxMpConfigStorage().getAppId(), wxMpXmlMessage);
            return null;
        }).next();
        return wxOpenMessageRouter;
    }
}

主要的一些接口
在这里插入代码片package com.yycm.platform.system.controller;

import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.yycm.platform.system.dept.service.DeptService;
import com.yycm.platform.system.pojo.dto.DeptDto;
import com.yycm.platform.system.upload.service.FileUploadService;
import com.yycm.platform.system.wx.vo.FastRegisterWeappVo;
import com.yycm.platform.system.wx.vo.SubmitAuditReqVo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.open.api.WxOpenService;
import me.chanjar.weixin.open.api.impl.WxOpenMessageRouter;
import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo;
import me.chanjar.weixin.open.bean.message.WxOpenMaSubmitAuditMessage;
import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage;
import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult;
import org.springframework.web.bind.annotation.*;
import yycm.platform.common.result.BaseResult;
import yycm.platform.common.result.Result;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * @Description
 * @Author ZJF
 * @Date 2024/9/27 12:54
 */
@SaIgnore
@RequiredArgsConstructor
@Slf4j
@RestController
@RequestMapping("wx/open")
public class WxComponentController implements BaseResult {

    private final WxOpenService wxOpenService;

    private final WxOpenMessageRouter wxOpenMessageRouter;
    private final DeptService deptService;
    private final FileUploadService fileUploadService;

    /**
    * 该接口用来接收微信事件推送的
     * component_verify_ticket 的有效时间为12小时,
     * 比 component_access_token 更长,建议保存最近可用的component_verify_ticket,
     * 在 component_access_token 过期之前都可以直接使用该 component_verify_ticket 进行更新,
     * 避免出现因为 component_verify_ticket 接收失败而无法更新 component_access_token 的情况。
     *
     * @param requestBody
     * @param timestamp
     * @param nonce
     * @param signature
     * @param encType
     * @param msgSignature
     * @return
     */
    @SaIgnore
    @RequestMapping("/receiveTicket")
    public Object receiveTicket(
            @RequestBody(required = false) String requestBody,
            @RequestParam("timestamp") String timestamp,
            @RequestParam("nonce") String nonce,
            @RequestParam("signature") String signature,
            @RequestParam(name = "encrypt_type", required = false) String encType,
            @RequestParam(name = "msg_signature", required = false) String msgSignature) {
        log.info(
                "\n receiveTicket 接收微信请求:[[signature=[{}], encType=[{}], msgSignature=[{}],"
                        + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", signature, encType, msgSignature, timestamp, nonce, requestBody);
        if (!StrUtil.equalsIgnoreCase("aes", encType)
                || !wxOpenService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) {
            throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
        }
        // aes加密的消息
        WxOpenXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedXml(requestBody,
                wxOpenService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature);
        log.debug("\n消息解密后内容为:\n{} ", inMessage.toString());
        // TODO 注册小程序事件推送。这里后续考虑注册成功之后直接绑定到部门上面去
        if (StrUtil.isNotBlank(inMessage.getRegistAppId())) {
            //拿到微信
            List<DeptDto> brandList = deptService.getBrandList();
            DeptDto deptDto = brandList.get(0);
            deptDto.setWxAd(inMessage.getRegistAppId());
            deptService.updateById(deptDto);
        }
        try {
            String componentVerifyTicket = inMessage.getComponentVerifyTicket();
            wxOpenService.getWxOpenConfigStorage().setComponentVerifyTicket(componentVerifyTicket);
            String out = wxOpenService.getWxOpenComponentService().route(inMessage);
            log.debug("\n组装回复信息:{}", out);
        } catch (WxErrorException e) {
            log.error("receive_ticket", e);
        }
        return "success";
    }

    /**
     * 代授权的小程序或公众号接收微信事件
     *
     * @param requestBody
     * @param appId
     * @param signature
     * @param timestamp
     * @param nonce
     * @param openid
     * @param encType
     * @param msgSignature
     * @return
     */
    @SaIgnore
    @RequestMapping("{appId}/callback")
    public Object callback(@RequestBody(required = false) String requestBody,
                           @PathVariable("appId") String appId,
                           @RequestParam("signature") String signature,
                           @RequestParam("timestamp") String timestamp,
                           @RequestParam("nonce") String nonce,
                           @RequestParam("openid") String openid,
                           @RequestParam("encrypt_type") String encType,
                           @RequestParam("msg_signature") String msgSignature) {
        log.info(
                "\ncallback 接收微信通知事件:[appId=[{}], openid=[{}], signature=[{}], encType=[{}], msgSignature=[{}],"
                        + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
                appId, openid, signature, encType, msgSignature, timestamp, nonce, requestBody);
        if (!StrUtil.equalsIgnoreCase("aes", encType)
                || !wxOpenService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) {
            throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
        }

        String out = "";
        // aes加密的消息
        WxMpXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedMpXml(requestBody,
                wxOpenService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature);
        log.debug("\n消息解密后内容为:\n{} ", inMessage.toString());
        // 全网发布测试用例
        DeptDto brandByAppId = deptService.getBrandByAppId(appId);
        if (null == brandByAppId) {
            out = "无该小程序配置无法授权";
        }
        if (StrUtil.equalsAnyIgnoreCase(appId, "gh_294e39961b22")) {
            try {
                if (StrUtil.equals(inMessage.getMsgType(), "text")) {
                    if (StrUtil.equals(inMessage.getContent(), "TESTCOMPONENT_MSG_TYPE_TEXT")) {
                        out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml(
                                WxMpXmlOutMessage.TEXT().content("TESTCOMPONENT_MSG_TYPE_TEXT_callback")
                                        .fromUser(inMessage.getToUser())
                                        .toUser(inMessage.getFromUser())
                                        .build(),
                                wxOpenService.getWxOpenConfigStorage()
                        );
                    } else if (StrUtil.startWith(inMessage.getContent(), "QUERY_AUTH_CODE:")) {
                        String msg = inMessage.getContent().replace("QUERY_AUTH_CODE:", "") + "_from_api";
                        WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(msg).toUser(inMessage.getFromUser()).build();
                        wxOpenService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage);
                    }
                } else if (StrUtil.equals(inMessage.getMsgType(), "event")) {
                    WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(inMessage.getEvent() + "from_callback").toUser(inMessage.getFromUser()).build();
                    wxOpenService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage);
                }
            } catch (WxErrorException e) {
                log.error("callback", e);
            }
        } else {
            WxMpXmlOutMessage outMessage = wxOpenMessageRouter.route(inMessage, appId);
            if (outMessage != null) {
                out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml(outMessage, wxOpenService.getWxOpenConfigStorage());
            }
        }
        return out;

    }


//    @GetMapping("/auth/goto_auth_url_show")
//    @ResponseBody
//    public String gotoPreAuthUrlShow() {
//        return "<a href='gotoAuthUrl'>go</a>";
//    }
    /**
     * 获取授权链接,因为是前后端分离项目,这里直接把链接生成之后返回给前端,注意的一点就是,
     * 前端打开链接的页面必须是在开放平台授权的域名下面的页面,不然打开会报错
     *
     * @param request
     */
    @SaIgnore
    @GetMapping("/auth/getAuthUrl")
    public Result getPreAuthUrl(HttpServletRequest request) {
        String host = request.getHeader("host");
        String url = "https://" + host + "/admin-api/wx/open/auth/jump";
        try {
            return Result.success(wxOpenService.getWxOpenComponentService().getPreAuthUrl(url));
        } catch (WxErrorException e) {
            log.error("getPreAuthUrl error", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 客户点击授权链接之后跳转的链接,这里会拿到客户小程序的refreshtoken,建议保存到数据库,因为后续代小程序调用接口都需要该参数
     *
     * @param authorizationCode
     * @return
     */
    @SaIgnore
    @GetMapping("/auth/jump")
    @ResponseBody
    public Result jump(@RequestParam("auth_code") String authorizationCode, HttpServletResponse response) {
        try {
            WxOpenQueryAuthResult queryAuthResult = wxOpenService.getWxOpenComponentService().getQueryAuth(authorizationCode);
            WxOpenAuthorizationInfo authorizationInfo = queryAuthResult.getAuthorizationInfo();
            String authorizerAppid = authorizationInfo.getAuthorizerAppid();
            //该小程序不存在
            DeptDto deptDto = deptService.getBrandByAppId(authorizerAppid);
            if (null != deptDto) {
              deptDto.setAuthorizerRefreshToken(authorizationInfo.getAuthorizerRefreshToken());
                deptService.updateById(deptDto);
            }
            //将appid和授权集合放到
            log.info("getQueryAuth:{}", queryAuthResult);
            String url = "XXX";
            response.addHeader("Referer", "XXX");
            response.sendRedirect(url);
            return Result.success();
        } catch (WxErrorException e) {
            log.error("gotoPreAuthUrl", e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

//    @GetMapping("/get_authorizer_info")
//    @ResponseBody
//    public WxOpenAuthorizerInfoResult getAuthorizerInfo(@RequestParam String appId) {
//        try {
//            return wxOpenService.getWxOpenComponentService().getAuthorizerInfo(appId);
//        } catch (WxErrorException e) {
//            log.error("getAuthorizerInfo", e);
//            throw new RuntimeException(e);
//        }
//    }


    /**
     * 获取已授权的小程序列表
     *
     * @return
     * @throws WxErrorException
     */
    @SaIgnore
    @GetMapping("authorizerList")
    public Result getAuthorizerList() throws WxErrorException {
        return Result.success(wxOpenService.getWxOpenComponentService().getAuthorizerList(0, 500));
    }

    /**
     * 获取已经提交到客户小程序的代码页面列表
     *
     * @return
     */
    @SaIgnore
    @GetMapping("pageList/{appid}")
    public Result get_qrcode(@PathVariable("appid") String appid) throws WxErrorException {
        return Result.success(wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appid)
                .getPageList());
    }


/****
 生成体验版二维码
*/
    @SaIgnore
    @GetMapping("testQrCode/{appId}")
    public Result testQrCode(@PathVariable("appId") String appId,String page) throws WxErrorException, IOException {
//        wxOpenService
        File file = wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appId)
                .getTestQrcode(page, null);
        String uploadImage = fileUploadService.uploadImage(appId+".png", file);
        return Result.success(uploadImage);
    }

/***
快速帮客户注册小程序接口,详见微信开发平台官方文档,帮客户注册小程序,有好几种方法,我们是直接复用主体注册的小程序
*/
    @SaIgnore
    @PostMapping("fastRegisterWeapp")
    public Result fastRegisterWeapp(@RequestBody FastRegisterWeappVo fastRegisterWeappVo) throws WxErrorException {
        return Result.success(wxOpenService.getWxOpenComponentService().fastRegisterWeapp(
                fastRegisterWeappVo.getName(),
                fastRegisterWeappVo.getCode(),
                fastRegisterWeappVo.getCodeType(),
                fastRegisterWeappVo.getLegalPersonaWechat(),
                fastRegisterWeappVo.getLegalPersonaName(),
                fastRegisterWeappVo.getComponentPhone()
        ));
    }

/**
* 查询快速注册的小程序列表
*/
    @SaIgnore
    @GetMapping("fastRegisterWeappSearch")
    public Result fastRegisterWeappSearch(String name, String legalPersonaWechat, String legalPersonaName) throws WxErrorException {
        return success(
                wxOpenService.getWxOpenComponentService().fastRegisterWeappSearch(name, legalPersonaWechat, legalPersonaName)
        );
    }

    /**
     * 提交代码审核
     * @param wxOpenMaSubmitAuditMessage
     * @param appid
     * @return
     */
    @SaIgnore
    @PostMapping("submitAudit/{appid}")
    public Result submitAudit(@RequestBody WxOpenMaSubmitAuditMessage wxOpenMaSubmitAuditMessage, @PathVariable("appid") String appid){
        try {
            return success(wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appid)
                    .submitAudit(wxOpenMaSubmitAuditMessage));
        } catch (WxErrorException e) {
           return error(ExceptionUtil.getRootCauseMessage(e));
        }
    }

/***
查询可用的类目,用来做提交代码审核的入参
*/
    @SaIgnore
    @GetMapping("categoryList/{appid}")
    public Result getCategoryList( @PathVariable("appid") String appid){
        try {
            return success(wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appid).getCategoryList());
        } catch (WxErrorException e) {
            return error(ExceptionUtil.getRootCauseMessage(e));
        }
    }

    /**
     * 查询审核单状态
     * @param appid
     * @return
     */
    @SaIgnore
    @GetMapping("auditStatus/{appid}/{auditId}")
    public Result getAuditStatus(@PathVariable("auditId") Long auditId, @PathVariable("appid") String appid){
        try {
            return success(wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appid).getAuditStatus(auditId));
        } catch (WxErrorException e) {
            return error(ExceptionUtil.getRootCauseMessage(e));
        }
    }

    /**
     * 查询最近一次提交的审核单状态
     * @param appid
     * @return
     */
    @SaIgnore
    @GetMapping("latestAuditStatus/{appid}")
    public Result getAuditStatus(@PathVariable("appid") String appid){
        try {
            return success(wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appid).getLatestAuditStatus());
        } catch (WxErrorException e) {
            return error(ExceptionUtil.getRootCauseMessage(e));
        }
    }

    /**
     * 撤回当前的代码审核单
     * @param appid
     * @return
     */
    @SaIgnore
    @PostMapping("undoCodeAudit/{appid}")
    public Result undoCodeAudit(@PathVariable("appid") String appid){
        try {
            return success(wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appid).undoCodeAudit());
        } catch (WxErrorException e) {
            return error(ExceptionUtil.getRootCauseMessage(e));
        }
    }

    /**
     * 发布最后一个审核通过的小程序代码版本(发布成正式的小程序)
     * @param appid
     * @return
     */
    @SaIgnore
    @PostMapping("releaseAudited/{appid}")
    public Result releaseAudited(@PathVariable("appid") String appid){
        try {
            return success(wxOpenService.getWxOpenComponentService().getWxMaServiceByAppid(appid).releaseAudited());
        } catch (WxErrorException e) {
            return error(ExceptionUtil.getRootCauseMessage(e));
        }
    }



}

总结

这里没有详细介绍怎么给客户注册小程序,大家自行去看官方文档吧哈哈哈,也比较简单,就不在这里赘诉了。已经有大佬给我们搭好了基础,所以站在巨人的肩膀上再去扩展就相对容易很多啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值