WeChat official account information push to achieve business decoupling

order
Recently, the third-party service needs to increase the wechat template information push capability, which should be decoupled from the business system.
1, Technology selection
After reading the information push development document of WeChat official account, it was found that it was not difficult to make a pretext for the estimate. Then I saw the Wx java series. I looked at it and felt very fragrant. Open source, complete ecology, good disk it.
2, Dependency import

<!-- WeChat official account -->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>wx-java-mp-spring-boot-starter</artifactId>
    <version>4.1.0</version>
</dependency>

Here, the official account of WeChat is wx-java-mp. If you have other needs, you can go to its wiki to see that the ecosystem is still complete, and the documents are also complete and easy to use. Here, I want to consider the problem of decoupling from the business, so that the appKey and secretKey can be read dynamically and initialized.
3, Decoupling design idea
1. Configure persistent repository, dynamic read initialization
2. Wechat template persistence repository, push information and read organization information
The business boundary and coupling method are involved here. Here, I am coupling configuration, template and business. The fields are company id and project id. See table design for details.
The overall flow chart is not needed, but the process steps:
1. Call to obtain the authorization address, and the returned address is the openId used to obtain the user
2, WeChat calls 1 to return the address, get openId (no attention, return no attention, WeChat official account, for users to search the official account)
3. The business system can do openId association or re acquire each time (the business system determines by itself)
4. The push information is transferred to openId, template id, and the business data corresponding to the template (return the push result)
4, Table structure design
Wechat configuration information table:

Template configuration master table

I plan to configure multiple templates, such as e-mail, SMS, voice, etc
Template configuration key details

5, Core code
Wx Java configuration class

import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Wechat configuration class
 * wx-java-mp Configuration class for
 *
 * @author zhengwen
 **/
@Configuration
public class WxConfigurer {

    @Autowired
    private ApplicationContext ctx;

    /**
     * Top level service instantiation
     *
     * @return WxMpService Top level service
     */
    @Bean
    public WxMpService wxMpService() {
        WxMpService wxMpService = new WxMpServiceImpl();
        registerWxMpSubService(wxMpService);
        return wxMpService;
    }

    @ConditionalOnBean(WxMpService.class)
    public Object registerWxMpSubService(WxMpService wxMpService) {
        //Register the bean singleton, which can be injected for use after registration
        ConfigurableListableBeanFactory factory = (ConfigurableListableBeanFactory) ctx.getAutowireCapableBeanFactory();
        factory.registerSingleton("wxMpKefuService", wxMpService.getKefuService());
        factory.registerSingleton("wxMpMaterialService", wxMpService.getMaterialService());
        factory.registerSingleton("wxMpMenuService", wxMpService.getMenuService());
        factory.registerSingleton("wxMpUserService", wxMpService.getUserService());
        factory.registerSingleton("wxMpUserTagService", wxMpService.getUserTagService());
        factory.registerSingleton("wxMpQrcodeService", wxMpService.getQrcodeService());
        factory.registerSingleton("wxMpCardService", wxMpService.getCardService());
        factory.registerSingleton("wxMpDataCubeService", wxMpService.getDataCubeService());
        factory.registerSingleton("wxMpUserBlacklistService", wxMpService.getBlackListService());
        factory.registerSingleton("wxMpStoreService", wxMpService.getStoreService());
        factory.registerSingleton("wxMpTemplateMsgService", wxMpService.getTemplateMsgService());
        factory.registerSingleton("wxMpSubscribeMsgService", wxMpService.getSubscribeMsgService());
        factory.registerSingleton("wxMpDeviceService", wxMpService.getDeviceService());
        factory.registerSingleton("wxMpShakeService", wxMpService.getShakeService());
        factory.registerSingleton("wxMpMemberCardService", wxMpService.getMemberCardService());
        factory.registerSingleton("wxMpMassMessageService", wxMpService.getMassMessageService());
        return Boolean.TRUE;
    }

}

controller class

import com.fillersmart.fsihouse.data.core.Result;
import com.fillersmart.fsihouse.data.vo.msgpush.MsgTemplateSendVo;
import com.fillersmart.fsihouse.data.vo.msgpush.WxBaseParamVo;
import com.fillersmart.fsihouse.data.vo.msgpush.WxMsgTemplatePageVo;
import com.fillersmart.fsihouse.data.vo.msgpush.WxMsgTemplateVo;
import com.fillersmart.fsihouse.pushservice.service.WeixinMsgPushService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * Wechat information push Controller
 *
 * @author zhengwen
 * 1,Application information template required for wechat configuration
 * 2,You need to deploy the server ip configuration white list
 **/
@Slf4j
@RestController
@RequestMapping("/weixin/push")
public class WeixinPushController {

    @Value("${weixin.register.url}")
    private String weixinRegister;

    @Value("${weixin.register.getOpenId.path}")
    private String getOpenIdPath;

    @Resource
    private WeixinMsgPushService weixinMsgPushService;


    /**
     * Obtain wechat authorization information
     *
     * @param wxBaseParamVo Wechat incoming reference vo
     * @return Unified output parameter
     */
    @PostMapping("/getWeixinAuthorizeInfo")
    public Result<?> getWeixinAuthorizeInfo(@RequestBody WxBaseParamVo wxBaseParamVo) {
        log.info("--Obtain wechat authorization Url Information( service)--");
        String redictUrl = weixinRegister + getOpenIdPath + wxBaseParamVo.getAgentId();
        wxBaseParamVo.setReturnUrl(redictUrl);
        return weixinMsgPushService.getAuthorizeInfo(wxBaseParamVo);
    }

    /**
     * Wechat user information
     *
     * @param code  Login code
     * @param state state
     * @return Unified output parameter
     */
    @GetMapping("/userInfo")
    public Result<?> userInfo(@RequestParam("code") String code, @RequestParam("state") String state, @RequestParam("agentId") Long agentId) {
        log.info("[Wechat website authorization] code={}", code);
        log.info("[Wechat website authorization] state={}", state);
        WxBaseParamVo wxBaseParamVo = new WxBaseParamVo(agentId, code, state);
        return weixinMsgPushService.userInfo(wxBaseParamVo);
    }

    /**
     * Official account information push
     *
     * @param msgTemplateSendVo Template info vo
     * @return Unified output parameter
     */
    @PostMapping("/commonMsgTemplateSend")
    public Result<?> commonMsgTemplateSend(@RequestBody MsgTemplateSendVo msgTemplateSendVo) {
        log.info("--Official account information push(service)--");
        return weixinMsgPushService.commonMsgTemplateSend(msgTemplateSendVo.getToUser(), msgTemplateSendVo.getGoUrl(), msgTemplateSendVo.getTemplateId(), msgTemplateSendVo.getMiniProgramVo(), msgTemplateSendVo.getDataVo(), msgTemplateSendVo.getAgentId());
    }

    /**
     * Save wechat information template (add, modify and share)
     *
     * @param wxMsgTemplateVo Wechat information template vo
     * @return Unified output parameter
     */
    @PostMapping("/saveMsgTemplate")
    public Result<?> saveMsgTemplate(@RequestBody WxMsgTemplateVo wxMsgTemplateVo) {
        log.info("--Save wechat information template(service)--");
        return weixinMsgPushService.saveMsgTemplate(wxMsgTemplateVo);
    }

    /**
     * Delete wechat information template
     *
     * @param wxMsgTemplateVo Wechat information template vo
     * @return Unified output parameter
     */
    @PostMapping("/delMsgTemplate")
    public Result<?> delMsgTemplate(@RequestBody WxMsgTemplateVo wxMsgTemplateVo) {
        log.info("--Delete wechat information template(service)--");
        return weixinMsgPushService.delMsgTemplate(wxMsgTemplateVo);
    }

    /**
     * Details of wechat template information
     *
     * @param id Primary key
     * @return Unified output parameter
     */
    @GetMapping("/msgTemplateDetail/{id}")
    public Result<?> msgTemplateDetail(@PathVariable Long id) {
        log.info("--Details of wechat information template(service)--");
        return weixinMsgPushService.msgTemplateDetail(id);
    }

    /**
     * Wechat information template list
     *
     * @param wxMsgTemplatePageVo Wechat information template vo
     * @return Unified output parameter
     */
    @PostMapping("/msgTemplateList")
    public Result<?> msgTemplateList(@RequestBody WxMsgTemplatePageVo wxMsgTemplatePageVo) {
        log.info("--Wechat information template list(service)--");
        return weixinMsgPushService.msgTemplateList(wxMsgTemplatePageVo);
    }

}

service implementation class

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fillersmart.fsihouse.data.common.api.ResponseCodeI18n;
import com.fillersmart.fsihouse.data.constant.ConstantsEnum;
import com.fillersmart.fsihouse.data.constant.oldfsihouse.BusinessConstant;
import com.fillersmart.fsihouse.data.core.RedisUtil;
import com.fillersmart.fsihouse.data.core.Result;
import com.fillersmart.fsihouse.data.core.ResultCode;
import com.fillersmart.fsihouse.data.core.ResultGenerator;
import com.fillersmart.fsihouse.data.dao.MsgPushRecordMapper;
import com.fillersmart.fsihouse.data.dao.MsgTemplateInfoMapper;
import com.fillersmart.fsihouse.data.dao.MsgTemplateKeyInfoMapper;
import com.fillersmart.fsihouse.data.dao.PayServerSettingMapper;
import com.fillersmart.fsihouse.data.dto.msgpush.MsgTemplateInfoDto;
import com.fillersmart.fsihouse.data.model.MsgPushRecord;
import com.fillersmart.fsihouse.data.model.MsgTemplateInfo;
import com.fillersmart.fsihouse.data.model.MsgTemplateKeyInfo;
import com.fillersmart.fsihouse.data.model.PayServerSetting;
import com.fillersmart.fsihouse.data.vo.msgpush.*;
import com.fillersmart.fsihouse.pushservice.interceptor.MapInitializer;
import com.fillersmart.fsihouse.pushservice.service.WeixinMsgPushService;
import com.fillersmart.fsihouse.pushservice.service.util.WxMessageUtil;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author zhengwen
 **/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class WeixinMsgPushServiceImpl implements WeixinMsgPushService {

    @Resource
    private MsgPushRecordMapper msgPushRecordMapper;
    @Resource
    private MsgTemplateInfoMapper msgTemplateInfoMapper;
    @Resource
    private MsgTemplateKeyInfoMapper msgTemplateKeyInfoMapper;
    @Resource
    private PayServerSettingMapper payServerSettingMapper;
    @Autowired
    private WxMpService wxMpService;
    @Resource
    private RedisUtil redisUtil;


    @Override
    public Result<?> getEnterpriseAccessToken(Long id, String tokenType) {
        if (id == null) {
            return ResultGenerator.genFailResult(ResponseCodeI18n.ID_NULL.getMsg());
        }
        PayServerSetting payServerSetting = payServerSettingMapper.selectByPrimaryKey(id);
        return getEnterpriseAccessTokenByServerSetting(payServerSetting, tokenType);
    }

    @Override
    public Result<?> getEnterpriseAccessTokenByServerSetting(PayServerSetting payServerSetting, String tokenType) {
        String wxAccessToken = "";

        wxAccessToken = WxMessageUtil.getOrCacheAccessToken(redisUtil, tokenType, payServerSetting);
        if (StringUtils.isBlank(wxAccessToken)) {
            return ResultGenerator.genFailResult(ResponseCodeI18n.GET_TOKEN_FAILED.getMsg());
        }
        return ResultGenerator.genSuccessResult(wxAccessToken);
    }

    /**
     * Push enterprise messages to users
     *
     * @param agentId     Application ID
     * @param touser      Member ID list (message recipients, multiple recipients separated by '|', up to 1000 supported). Special case: if @ all is specified, it will be sent to all members who pay attention to the enterprise application
     * @param toparty     Department ID list. Multiple recipients are separated by '|', with a maximum of 100 supported. This parameter is ignored when touser is @ all
     * @param totag       Tag ID list. Multiple recipients are separated by '|', with a maximum of 100 supported. This parameter is ignored when touser is @ all
     * @param msgtype     Message type
     * @param initializer Message body parameter reference https://work.weixin.qq.com/api/doc#90000/90135/90236
     */
    @Override
    public Result<?> orgMsgSend(String msgtype, String touser, String toparty, String totag, MapInitializer<String, String> initializer, Long agentId) {

        log.info("Enterprise application{}send out{}Message to{}", agentId, msgtype, touser);
        //Preparing to construct json
        JSONObject firstjsonObject = new JSONObject();
        JSONObject msgtypejsonObject = new JSONObject();
        //json construction of graphic class
        JSONArray jsonArray = new JSONArray();
        JSONObject secandjsonObject = new JSONObject();
        //Populate message parameters
        Map<String, String> map = new HashMap<>();
        //Fill multi node parameters of graphic class
        List<Map<String, String>> list = new ArrayList<>();
        //Construct common message parameter json
        firstjsonObject.put("touser", touser);
        firstjsonObject.put("toparty", toparty);
        firstjsonObject.put("totag", totag);
        firstjsonObject.put("msgtype", msgtype);
        firstjsonObject.put("agentid", agentId);
        switch (msgtype) {
            case "text":
                initializer.init(map);
                //Construct message body json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("text", msgtypejsonObject);
                firstjsonObject.put("safe", 0);
                break;
            case "image":
                initializer.init(map);
                //Construct message body json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("image", msgtypejsonObject);
                firstjsonObject.put("safe", 0);
                break;
            case "voice":
                initializer.init(map);
                //Construct message body json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("voice", msgtypejsonObject);
                break;
            case "video":
                initializer.init(map);
                //Construct message body json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("video", msgtypejsonObject);
                firstjsonObject.put("safe", 0);
                break;
            case "file":
                initializer.init(map);
                //Construct message body json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("file", msgtypejsonObject);
                firstjsonObject.put("safe", 0);
                break;
            case "textcard":
                initializer.init(map);
                //Construct message body json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("textcard", msgtypejsonObject);
                break;
            case "news":
                initializer.initListMap(list);
                //Construct message body json
                for (int i = 0; i <= list.size() - 1; i++) {
                    secandjsonObject = new JSONObject();
                    for (String paramName : list.get(i).keySet()) {
                        secandjsonObject.put(paramName, list.get(i).get(paramName));
                    }
                    jsonArray.add(secandjsonObject);
                }
                log.info(jsonArray.toJSONString());
                msgtypejsonObject.put("articles", jsonArray);
                firstjsonObject.put("news", msgtypejsonObject);
                break;
            case "mpnews":
                initializer.initListMap(list);
                //Construct message body json
                for (int i = 0; i <= list.size() - 1; i++) {
                    secandjsonObject = new JSONObject();
                    for (String paramName : list.get(i).keySet()) {
                        secandjsonObject.put(paramName, list.get(i).get(paramName));
                    }
                    jsonArray.add(secandjsonObject);
                }
                log.info(jsonArray.toJSONString());
                msgtypejsonObject.put("articles", jsonArray);
                firstjsonObject.put("mpnews", msgtypejsonObject);
                break;
            case "markdown":
                initializer.init(map);
                //Construct message body json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                firstjsonObject.put("markdown", msgtypejsonObject);
                break;
            case "miniprogram_notice":
                initializer.init(map);
                initializer.initListMap(list);
                //Construct message body json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                //Construct message node json
                for (int i = 0; i <= list.size() - 1; i++) {
                    secandjsonObject = new JSONObject();
                    for (String paramName : list.get(i).keySet()) {
                        secandjsonObject.put(paramName, list.get(i).get(paramName));
                    }
                    jsonArray.add(secandjsonObject);
                }
                log.info(jsonArray.toJSONString());
                msgtypejsonObject.put("content_item", jsonArray);
                firstjsonObject.put("miniprogram_notice", msgtypejsonObject);
                break;
            case "taskcard":
                initializer.init(map);
                initializer.initListMap(list);
                //Construct message body json
                for (String paramName : map.keySet()) {
                    msgtypejsonObject.put(paramName, map.get(paramName));
                }
                //Construct message node json
                for (int i = 0; i <= list.size() - 1; i++) {
                    secandjsonObject = new JSONObject();
                    for (String paramName : list.get(i).keySet()) {
                        secandjsonObject.put(paramName, list.get(i).get(paramName));
                    }
                    jsonArray.add(secandjsonObject);
                }
                log.info(jsonArray.toJSONString());
                msgtypejsonObject.put("btn", jsonArray);
                firstjsonObject.put("taskcard", msgtypejsonObject);
                break;
            default:
                return ResultGenerator.genFailResult(ResponseCodeI18n.PUSH_MSG_TYPE_UNKNOWN.getMsg());
        }
        return orgSendTo(agentId, firstjsonObject.toJSONString());
    }

    /**
     * Push message
     *
     * @param agentId
     * @param tempdata
     */
    @Override
    public Result<?> orgSendTo(Long agentId, String tempdata) {
        String accessToken = null;

        Result<?> res = getEnterpriseAccessToken(agentId, ConstantsEnum.WeiXinTokenType.ACCESS_TOKEN_ORG.getType());
        if (res == null || !res.getCode().equals(ResultCode.SUCCESS.code())) {
            return ResultGenerator.genFailResult(ResponseCodeI18n.GET_TOKEN_FAILED.getMsg());
        }
        Object accessTokenObj = res.getData();
        if (accessTokenObj != null) {
            accessToken = accessTokenObj.toString();
            String url = WxMessageUtil.URL_SEND_ORG.replace("{{ACCESS_TOKEN}}", accessToken);
            JSONObject jsonObject = WxMessageUtil.post(url, tempdata);
            if (!StringUtils.isEmpty(jsonObject.toJSONString()) && jsonObject.getInteger("errcode") == 0) {
                log.info("Enterprise message pushed successfully");
                return ResultGenerator.genSuccessResult();
            } else {
                log.info("Enterprise message push failure reason:{}", jsonObject.toJSONString());
                return ResultGenerator.genFailResult(jsonObject.toJSONString());
            }
        }
        return res;
    }

    @Override
    public Result<?> commonMsgTemplateSend(String toUser, String goUrl, Long templateId, MiniProgramVo miniProgramVo, List<WxTemplateDataVo> dataVo, Long agentId) {
        //Wx Java encapsulation tool is used to push common template information
        Result<?> res = WxMessageUtil.checkCommonMsgTemplateSendParam(toUser, goUrl, templateId, miniProgramVo, dataVo, agentId);
        if (res != null) {
            return res;
        }

        //Query official account configuration information
        PayServerSetting payServerSetting = payServerSettingMapper.selectByPrimaryKey(agentId);
        if (payServerSetting == null) {
            return ResultGenerator.genFailResult("WeChat official account configuration information does not exist.");
        }

        //Query template key configuration information
        //Template information master record
        MsgTemplateInfo msgTemplateInfo = msgTemplateInfoMapper.selectByPrimaryKey(templateId);
        if (msgTemplateInfo == null || BusinessConstant.IS_DELETE.equals(msgTemplateInfo.getIsDeleted())) {
            return ResultGenerator.genFailResult("Wechat template does not exist");
        }
        MsgTemplateKeyInfo mtkRecord = new MsgTemplateKeyInfo();
        mtkRecord.setTemplateId(templateId);
        mtkRecord.setIsDeleted(BusinessConstant.NO_DELETE);
        List<MsgTemplateKeyInfo> msgTemplateKeyInfoLs = msgTemplateKeyInfoMapper.select(mtkRecord);
        if (CollectionUtil.isEmpty(msgTemplateKeyInfoLs)) {
            return ResultGenerator.genFailResult("Wechat template information template key Configuration is empty");
        }

        //Parameter conversion
        //Applet parameters
        WxMpTemplateMessage.MiniProgram miniProgram = null;
        if (miniProgramVo != null) {
            miniProgram = new WxMpTemplateMessage.MiniProgram(miniProgramVo.getAppid(), miniProgramVo.getPagePath(), miniProgramVo.getUsePath());
        }
        //Template information parameters
        List<WxMpTemplateData> data = new ArrayList<>();
        boolean templateMatch = WxMessageUtil.checkTempMatchParamTrans(data, dataVo, msgTemplateKeyInfoLs);
        if (!templateMatch) {
            return ResultGenerator.genFailResult("The wechat push information template does not match the information input parameters");
        }

        //Organization wechat template sending parameters
        WxMpTemplateMessage wxMpTemplateMessage = new WxMpTemplateMessage(toUser, msgTemplateInfo.getTemplateCode(), goUrl, miniProgram, data);

        //Reload configuration of wechat top-level service (avoid access_token failure)
        WxMpConfigStorage wxMpConfigStorage = WxMessageUtil.initCommonWxMpConfigStorage(payServerSetting, redisUtil, ConstantsEnum.WeiXinTokenType.ACCESS_TOKEN_COMMON.getType());
        Map<String, WxMpConfigStorage> configStorageMap = new HashMap<>(1);
        configStorageMap.put("wxMpConfigStorage", wxMpConfigStorage);
        wxMpService.setMultiConfigStorages(configStorageMap);
        //Organization push information record
        MsgPushRecord msgPushRecord = WxMessageUtil.initMsgPushRecord(payServerSetting, wxMpTemplateMessage);
        try {
            //Check whether official account is concerned.
            res = checkUserSubscribe(wxMpService, toUser, null, payServerSetting);
            if (res != null) {
                return res;
            }
            //Push
            String msgId = wxMpService.getTemplateMsgService().sendTemplateMsg(wxMpTemplateMessage);
            boolean isSend = WxMessageUtil.analyzeWxTemplateMsgSendResult(msgId, msgPushRecord);
            log.info("---Push results msgId: {},Is it successful{}", msgId, isSend);
        } catch (WxErrorException e) {
            log.error("--Push exception:{}", e.getMessage(), e);
            msgPushRecord.setSendResult(e.getMessage());
        }

        return ResultGenerator.genSuccessResult(msgPushRecord);
    }

    @Override
    public Result<?> getAuthorizeInfo(WxBaseParamVo wxBaseParamVo) {
        Result<?> res = WxMessageUtil.checkGetAuthorizeInfoParam(wxBaseParamVo);
        if (res != null) {
            return res;
        }
        String state = wxBaseParamVo.getState();
        String returnUrl = wxBaseParamVo.getReturnUrl();
        Long agentId = wxBaseParamVo.getAgentId();
        //Query official account configuration information
        PayServerSetting payServerSetting = payServerSettingMapper.selectByPrimaryKey(agentId);
        if (payServerSetting == null) {
            return ResultGenerator.genFailResult("WeChat official account configuration information does not exist.");
        }

        //Reload configuration of wechat top-level service (avoid access_token failure)
        WxMpConfigStorage wxMpConfigStorage = WxMessageUtil.initCommonWxMpConfigStorage(payServerSetting, redisUtil, ConstantsEnum.WeiXinTokenType.ACCESS_TOKEN_COMMON.getType());
        Map<String, WxMpConfigStorage> configStorageMap = new HashMap<>(1);
        configStorageMap.put("wxMpConfigStorage", wxMpConfigStorage);
        wxMpService.setMultiConfigStorages(configStorageMap);

        String redirectURL = wxMpService.getOAuth2Service().buildAuthorizationUrl(returnUrl, WxConsts.OAuth2Scope.SNSAPI_USERINFO, state);
        log.info("[Wechat web page authorization] acquisition code,redirectURL={}", redirectURL);

        return ResultGenerator.genSuccessResult(redirectURL);
    }

    @Override
    public Result<?> userInfo(WxBaseParamVo wxBaseParamVo) {
        Result<?> res = WxMessageUtil.checkUserInfoParam(wxBaseParamVo);
        if (res != null) {
            return res;
        }
        Long agentId = wxBaseParamVo.getAgentId();
        //Query official account configuration information
        PayServerSetting payServerSetting = payServerSettingMapper.selectByPrimaryKey(agentId);
        if (payServerSetting == null) {
            return ResultGenerator.genFailResult("WeChat official account configuration information does not exist.");
        }

        //Reload configuration of wechat top-level service (avoid access_token failure)
        WxMpConfigStorage wxMpConfigStorage = WxMessageUtil.initCommonWxMpConfigStorage(payServerSetting, redisUtil, ConstantsEnum.WeiXinTokenType.ACCESS_TOKEN_COMMON.getType());
        Map<String, WxMpConfigStorage> configStorageMap = new HashMap<>(1);
        configStorageMap.put("wxMpConfigStorage", wxMpConfigStorage);
        wxMpService.setMultiConfigStorages(configStorageMap);

        //Get wechat user information
        String code = wxBaseParamVo.getCode();
        try {
            WxOAuth2AccessToken wxOAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
            String openId = wxOAuth2AccessToken.getOpenId();
            log.info("[Wechat website authorization] openId={}", openId);
            if (StringUtils.isBlank(openId)) {
                return ResultGenerator.genFailResult("Get wechat users openId fail");
            }
            //Check whether it is concerned
            res = checkUserSubscribe(wxMpService, openId, code, payServerSetting);
            if (res != null) {
                return res;
            }

            return ResultGenerator.genSuccessResult(openId);
        } catch (Exception e) {
            log.error("---Failed to obtain wechat user information, reason:{}", e.getMessage(), e);
            return ResultGenerator.genFailResult("Failed to obtain wechat user information");
        }

    }

    /**
     * Check whether the user is concerned about the official account.
     *
     * @param wxMpService      Wechat service
     * @param openId           User openId
     * @param code             User login code
     * @param payServerSetting WeChat official account configuration
     * @return Unified output parameter
     */
    private Result<?> checkUserSubscribe(WxMpService wxMpService, String openId, String code, PayServerSetting payServerSetting) {
        try {
            WxMpUser wxMpUser = wxMpService.getUserService().userInfo(openId);
            if (wxMpUser == null) {
                log.info("Wechat users code:{}User openId:{}The user for does not exist", code, openId);
                return ResultGenerator.genFailResult("Wechat user does not exist");
            }
            boolean subscribe = wxMpUser.getSubscribe();
            if (!subscribe) {
                log.info("Wechat users code:{}User openId:{}User not following", code, openId);
                log.info("--Wechat users:{}", JSON.toJSONString(wxMpUser));
                return ResultGenerator.genFailResult("Wechat users[" + wxMpUser.getNickname() + "]If you do not pay attention to the official account, please pay attention to the official account.[" + payServerSetting.getAppName() + "]");
            }
        } catch (WxErrorException e) {
            log.info("--Check whether the user is concerned about the official account number, and why.:{}", e.getMessage(), e);
            return ResultGenerator.genFailResult("Check WeChat users' attention to official account number exception:{}" + e.getMessage());
        }
        return null;
    }

    @Override
    public Result<?> saveMsgTemplate(WxMsgTemplateVo wxMsgTemplateVo) {
        //Parameter verification
        Result<?> res = WxMessageUtil.checkSaveMsgTemplate(wxMsgTemplateVo);
        if (res != null) {
            return res;
        }
        boolean isAdd = true;
        //Query whether the template of code and service provider exists
        String templateCode = wxMsgTemplateVo.getTemplateCode();
        Integer serverProvider = wxMsgTemplateVo.getServerProvider();
        MsgTemplateInfo tempInfoRecord = new MsgTemplateInfo();
        tempInfoRecord.setTemplateCode(templateCode);
        tempInfoRecord.setServerProvider(serverProvider);
        MsgTemplateInfo dbMt = msgTemplateInfoMapper.selectOne(tempInfoRecord);
        if (dbMt != null) {
            isAdd = false;
            wxMsgTemplateVo.setId(dbMt.getId());
        }
        //Master-slave information splitting
        MsgTemplateInfo mt = new MsgTemplateInfo();
        BeanUtils.copyProperties(wxMsgTemplateVo, mt);
        if (isAdd) {
            //New information supplement
            mt.setIsDeleted(BusinessConstant.NO_DELETE);
            mt.setCreateTime(DateUtil.date());
            msgTemplateInfoMapper.insert(mt);
        } else {
            //Modification information supplement
            mt.setUpdateTime(DateUtil.date());
            msgTemplateInfoMapper.updateByPrimaryKeySelective(mt);
        }
        //From information processing
        //Query original template information
        Map<String, MsgTemplateKeyInfo> keyMp = new HashMap<>();
        MsgTemplateKeyInfo mtkRecord = new MsgTemplateKeyInfo();
        mtkRecord.setTemplateId(mt.getId());
        List<MsgTemplateKeyInfo> dbMtkLs = msgTemplateKeyInfoMapper.select(mtkRecord);
        if (CollectionUtil.isNotEmpty(dbMtkLs)) {
            keyMp = dbMtkLs.stream().collect(Collectors.toMap(MsgTemplateKeyInfo::getKeyEnName, c -> c));
        }

        List<MsgTemplateKeyInfo> mtKeyLs = wxMsgTemplateVo.getTemplateKeyInfoList();
        //Cyclic processing
        if (CollectionUtil.isNotEmpty(mtKeyLs)) {
            Map<String, MsgTemplateKeyInfo> finalKeyMp = keyMp;
            mtKeyLs.stream().forEach(c -> {
                mtkRecord.setKeyEnName(c.getKeyEnName());
                finalKeyMp.remove(mtkRecord.getKeyEnName());
                MsgTemplateKeyInfo dbMtk = msgTemplateKeyInfoMapper.selectOne(mtkRecord);
                c.setIsDeleted(BusinessConstant.NO_DELETE);
                if (dbMtk == null) {
                    c.setCreateTime(mt.getCreateTime());
                    c.setCreateBy(mt.getCreateBy());
                    msgTemplateKeyInfoMapper.insert(c);
                } else {
                    BeanUtil.copyProperties(c, dbMtk, CopyOptions.create().setIgnoreNullValue(Boolean.TRUE));
                    dbMtk.setUpdateBy(mt.getUpdateBy());
                    dbMtk.setUpdateTime(mt.getUpdateTime());
                    msgTemplateKeyInfoMapper.updateByPrimaryKeySelective(dbMtk);
                }
            });
            //Remove the original
            finalKeyMp.entrySet().stream().forEach(d -> {
                MsgTemplateKeyInfo del = d.getValue();
                del.setIsDeleted(BusinessConstant.IS_DELETE);
                del.setUpdateTime(mt.getUpdateTime());
                del.setUpdateBy(mt.getUpdateBy());
                msgTemplateKeyInfoMapper.updateByPrimaryKeySelective(del);
            });
        }
        return ResultGenerator.genSuccessResult();
    }

    @Override
    public Result<?> delMsgTemplate(WxMsgTemplateVo wxMsgTemplateVo) {
        Long id = wxMsgTemplateVo.getId();
        if (id == null) {
            return ResultGenerator.genFailResult(ResponseCodeI18n.ID_NULL.getMsg());
        }
        MsgTemplateInfo mt = msgTemplateInfoMapper.selectByPrimaryKey(id);
        if (mt == null) {
            return ResultGenerator.genFailResult(ResponseCodeI18n.DATA_NOT_EXIST.getMsg());
        }
        mt.setIsDeleted(BusinessConstant.IS_DELETE);
        mt.setUpdateTime(DateUtil.date());
        int delNum = msgTemplateInfoMapper.updateByPrimaryKeySelective(mt);
        if (delNum != 1) {
            return ResultGenerator.genFailResult(ResponseCodeI18n.UPDATE_FAIL.getMsg());
        }
        return ResultGenerator.genSuccessResult();
    }

    @Override
    public Result<?> msgTemplateDetail(Long id) {
        if (id == null) {
            return ResultGenerator.genFailResult(ResponseCodeI18n.ID_NULL.getMsg());
        }
        MsgTemplateInfoDto msgTemplateInfoDto = msgTemplateInfoMapper.findMsgTemplateDtoById(id);
        return ResultGenerator.genSuccessResult(msgTemplateInfoDto);
    }

    @Override
    public Result<?> msgTemplateList(WxMsgTemplatePageVo wxMsgTemplatePageVo) {
        PageHelper.startPage(wxMsgTemplatePageVo.getPage(), wxMsgTemplatePageVo.getSize());
        List<MsgTemplateInfoDto> list = msgTemplateInfoMapper.queryMsgTemplateDtoList(wxMsgTemplatePageVo);
        return ResultGenerator.genSuccessResult(list);
    }


}

Here are:
1, the enterprise official account information push (original docking WeChat), the company did not do business certification, no pro test.
2. The addition, deletion, modification, query and maintenance of template information can be implemented by yourself if your orm layer is different from me
3. Enterprise access_token, ordinary access_token acquisition
The key to dynamically read the configuration information and reload is to transfer the parameter configuration id and initialize the loading after query:

//Reload configuration of wechat top-level service (avoid access_token failure)
WxMpConfigStorage wxMpConfigStorage = WxMessageUtil.initCommonWxMpConfigStorage(payServerSetting, redisUtil, ConstantsEnum.WeiXinTokenType.ACCESS_TOKEN_COMMON.getType());
Map<String, WxMpConfigStorage> configStorageMap = new HashMap<>(1);
configStorageMap.put("wxMpConfigStorage", wxMpConfigStorage);
wxMpService.setMultiConfigStorages(configStorageMap);

Before getting the corresponding service from wxMpService, you must do this initialization and reload.
6, Push examples and effects


7, Summary
The implementation details should be very detailed. I won't say much about the rest. This decoupling idea should still be OK. I hope it can help you.

Added by CBaZ on Mon, 17 Jan 2022 10:31:49 +0200