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

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

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

2、创建第三方平台

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

3、开发平台配置

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

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

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

点击开发资料,配置开发资料

前面这几个是必须配置的,这里就不截右半边了,也都有对应的说明,按照说明配置就好啦,这里配置的信息千万不要泄露!!!

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

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

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

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

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

2.1 首先引入依赖,其余依赖的大家自己去搞哈,我默认大家都是后端熟手,我这里只粘对接微信开放平台的依赖包

com.github.binarywang

weixin-java-open

4.6.0

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 deptDtoList = deptMapper.selectList(new LambdaQueryWrapperX().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 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 "go";

// }

/**

* 获取授权链接,因为是前后端分离项目,这里直接把链接生成之后返回给前端,注意的一点就是,

* 前端打开链接的页面必须是在开放平台授权的域名下面的页面,不然打开会报错

*

* @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));

}

}

}

总结

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

相关推荐

s端子(s端子是什么)
世界杯365网站打不开

s端子(s端子是什么)

📅 10-10 👁️ 619
西安,除了羊肉泡馍和凉皮,这10大“名酒”更是一绝,外地很少见
可口可乐公司 NYSE:KOThe Coca-Cola Co.
世界杯365网站打不开

可口可乐公司 NYSE:KOThe Coca-Cola Co.

📅 08-15 👁️ 6576
一到夏天手就出汗是怎么回事
mobile365体育投注

一到夏天手就出汗是怎么回事

📅 09-22 👁️ 6423
电动车气喇叭
世界杯365网站打不开

电动车气喇叭

📅 10-05 👁️ 9146
三星自研系统,彻底失败,再次抱上谷歌大腿,华为鸿蒙怎么看?