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