SCRM:SpringBoot + RabbitMQ + 企微 实现发送消息到企业微信

使用MQ实现企业微信应用程序发送消息到指定人或部门

提前部署好RabbitMQ服务,不会的同学请看我的另一篇部署文章

Dcoker轻松部署RabbitMQ

引入相关依赖

pom.xml

        <!-- RabbitMQ依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
            <version>2.7.3</version>
        </dependency>
        <!-- http调用依赖 -->
        <dependency>
            <groupId>com.dtflys.forest</groupId>
            <artifactId>forest-spring-boot-starter</artifactId>
            <version>1.5.25</version>
        </dependency>

代码实现

controller

/**
 * 控制层
 */
@RestController
@RequestMapping("/agent/msg")
public class AgentMsgController {
    /**
     * 服务对象
     */
    @Resource
    private AgentMsgService agentMsgService;

    /**
     * 新增消息
     */
    @PostMapping("/add")
    public Result<AgentMsg> add(@RequestBody AgentMsgDTO agentMsgDto) {
        return R.ok(this.agentMsgService.insert(agentMsgDto));
    }
}

AgentMsgDTO

import lombok.Data;

import java.util.Date;
import java.util.List;

/**
 * 应用消息表(AgentMsg)DTO类
 */
@Data
public class AgentMsgDTO {
    /**
     * 主键ID
     */
    private Long id;
    /**
     * 消息标题
     */
    private String msgTitle;
    /**
     * 应用ID
     */
    private Integer agentId;
    /**
     * 范围类型 1-全部 2-自定义
     */
    private Integer scopeType;
    /**
     * 成员ID列表
     */
    private List<String> toUser;
    /**
     * 部门ID列表
     */
    private List<String> toParty;
    /**
     * 标签ID列表
     */
    private List<String> toTag;
    /**
     * 发送方式 1-立即发送 2-定时发送
     */
    private Integer sendType;
    /**
     * 发送时间
     */
    private Date sendTime;
    /**
     * 计划时间
     */
    private Date planSendTime;
    /**
     * 消息状态:0-草稿 1-待发送 2-已发送 3-发送失败 4-已撤回
     */
    private Integer status;
    /**
     * 消息ID
     */
    private String msgId;
    /**
     * 消息模板
     */
    private WeComMessageTemplate messageTemplate;
}

WeComMessageTemplate

import lombok.Data;

import javax.validation.constraints.NotNull;

/**
 * @description 应用消息通知入参
 **/
@Data
public class WeComMessageTemplate {
    /**
     * 消息类型 文本:text, 图片:image, 语音:voice, 视频:video, 文件:file, 文本卡片:textcard, 图文:news, 图文消息:link, 小程序:miniprogram
     */
    @NotNull(message = "消息类型不能为空")
    private String msgType;

    /**
     * 文本内容(文本消息必传)
     */
    private String content;

    /**
     * 素材id(语音、视频、文件 必传)
     */
    private String mediaId;

    /**
     * 小程序封面media_id
     */
    private String picMediaId;

    /**
     * 消息的标题(视频、文本卡片、图文 必传)
     */
    private String title;

    /**
     * 消息的描述(视频、文本卡片、图文 必传)
     */
    private String description;

    /**
     * 点击后跳转的链接。最长2048字节,请确保包含了协议头(http/https) (文本卡片、图文 必传)
     */
    private String linkUrl;

    /**
     * 图文消息的图片链接,支持JPG、PNG格式,较好的效果为大图 1068*455,小图150*150。(文本卡片、图文 必传)
     */
    private String picUrl;

    /**
     * 文件连接
     */
    private String fileUrl;

    /**
     * 小程序appid(可以在微信公众平台上查询),必须是关联到企业的小程序应用
     */
    private String appId;
}

service

/**
 * 应用消息表(AgentMsg)表服务实现类
 */
@Service
public class AgentMsgServiceImpl extends ServiceImpl<AgentMsgMapper, AgentMsg> implements AgentMsgService {
    @Resource
    private WeComSendMsgService sendMsgService;

    /**
     * 新增数据
     *
     * @param agentMsgDto 实例对象
     * @return 实例对象
     */
    @Override
    public AgentMsg insert(AgentMsgDTO agentMsgDto) {
        AgentMsg agentMsg = new AgentMsg();
        BeanCopyUtils.copyBean(agentMsgDto, agentMsg);
        // 此处分立即发送和定时发送,定时发送使用xxl-job实现
        if (save(agentMsg) && agentMsgDto.getStatus() == 1 && agentMsgDto.getSendType() == 1) {
            // 立即发送
            WeComAgentMsgBody agentMsgBody = new WeComAgentMsgBody();
            agentMsgBody.setMessageTemplates(agentMsgDto.getMessageTemplate());
            agentMsgBody.setBusinessType(WeComMsgTypeEnum.AGENT.getType());
            agentMsgBody.setCorpId(SecurityUtils.getCorpId());
            agentMsgBody.setCorpUserIds(agentMsgDto.getToUser());
            agentMsgBody.setDeptIds(agentMsgDto.getToParty());
            agentMsgBody.setTagIds(agentMsgDto.getToTag());
            agentMsgBody.setCallBackId(agentMsg.getId());
            sendMsgService.sendMsg(agentMsgBody);
        }
        return agentMsg;
    }

WeComSendMsgService

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @description 企微应用通知
 **/
@Slf4j
@Service
public class WeComSendMsgService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitMQSettingConfig rabbitMQSettingConfig;

    public void sendMsg(WeComAgentMsgBody body) {
        String json = JSONObject.toJSONString(body);
        log.info("发送应用通知消息到MQ入参:{}", json);
        rabbitTemplate.convertAndSend(rabbitMQSettingConfig.getWeAppMsgEx(), rabbitMQSettingConfig.getWeAppMsgRk(), json);
    }
}

消息监听消费:WeComMsgListener

import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * 企微应用消息监听
 **/
@Slf4j
@Component
public class WeComMsgListener {

    @RabbitHandler
    @RabbitListener(queues = "${wecom.mq.queue.app-msg:Qu_AppMsg}")
    public void subscribe(String msg, Channel channel, Message message) {
        try {
            log.info("应用通知消息监听:msg:{}", msg);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            WeComAgentMsgBody appMsgBody = JSONObject.parseObject(msg, WeComAgentMsgBody.class);
           WeComMsgTypeEnum msgTypeEnum = WeComMsgTypeEnum.parseEnum(appMsgBody.getBusinessType());
            if (Objects.nonNull(msgTypeEnum)) {
                if (msgTypeEnum == WeComMsgTypeEnum.AGENT) {
                    SpringUtils.getBean(msgTypeEnum.getBeanName(), AbstractWeComMsgService.class).sendAgentMsg(appMsgBody);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("应用通知消息监听-消息处理失败 msg:{},error:{}", msg, e);
        }
    }
}

AbstractWeComMsgService

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * 消息发送
 **/
@Service
@Slf4j
public abstract class AbstractWeComMsgService {

    @Resource
    private WeComAgentClient agentClient;

    @Autowired
    private CorpAccountService corpAccountService;

    /**
     * 消息结果处理
     */
    protected void callBackResult(WeComMsgVo appMsgVo) {
    }

    /**
     * 获取消息体
     */
    protected abstract WeComAgentMsgDto getWeComAgentMsg(WeComAgentMsgBody agentMsgBody);

    public void sendAgentMsg(WeComAgentMsgBody agentMsgBody) {
        try {
            WeComAgentMsgDto agentMsg = getWeComAgentMsg(agentMsgBody);
            WeComMsgVo result = agentClient.sendAgentMsg(agentMsg);
            callBackResult(result);
        } catch (Exception e) {
            log.error("sendAgentMsg 执行异常: query:{}", JSONObject.toJSONString(agentMsgBody), e);
            WeComMsgVo errorBody = new WeComMsgVo();
            errorBody.setErrMsg(e.getMessage());
            errorBody.setErrCode(-1);
            callBackResult(errorBody);
        }
    }
}

WeComAgentMsgServiceImpl

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.Objects;

/**
 * 应用消息处理
 **/
@Slf4j
@Service("WeComAgentMsgService")
public class WeComAgentMsgServiceImpl extends AbstractWeComMsgService {

    private Long callBackId;

    @Autowired
    private AgentMsgService agentMsgService;

    @Resource
    private WeComMediaClient mediaClient;

    @Override
    protected WeComAgentMsgDto getWeComAgentMsg(WeComAgentMsgBody agentMsgBody) {
        callBackId = agentMsgBody.getCallBackId();
        AgentMsg weAgentMsg = agentMsgService.getById(agentMsgBody.getCallBackId());
        if (Objects.isNull(weAgentMsg)) {
            throw new WeComException("未找到对应消息数据");
        }
        WeComAgentMsgDto query = new WeComAgentMsgDto();
        query.setAgentid(String.valueOf(weAgentMsg.getAgentId()));
        if (CollectionUtil.isNotEmpty(agentMsgBody.getCorpUserIds())) {
            query.setTouser(String.join("|", agentMsgBody.getCorpUserIds()));
        } else if (CollectionUtil.isNotEmpty(agentMsgBody.getDeptIds())) {
            query.setToparty(String.join("|", agentMsgBody.getDeptIds()));
        } else if (CollectionUtil.isNotEmpty(agentMsgBody.getTagIds())) {
            query.setTotag(String.join("|", agentMsgBody.getTagIds()));
        }
        WeComMessageTemplate messageTemplates = agentMsgBody.getMessageTemplates();
        // 消息类型判断,此处只拿text演示
        if (MessageType.TEXT.getMessageType().equals(messageTemplates.getMsgType())) {
            query.setMsgtype(MessageType.TEXT.getMessageType());
            query.setText(WeComAgentMsgDto.Text.builder().content(messageTemplates.getContent()).build());
        }
            
        return query;
    }

    /**
     * 返回结果保存
     *
     * @param appMsgVo 返回结果
     */
    @Override
    protected void callBackResult(Long id, WeComMsgVo appMsgVo) {
        log.info(">>>>>>【任务发送结果】 消息Id:{}, 返回结果:{}", id, JSONObject.toJSONString(appMsgVo));
        AgentMsg weAgentMsg = new AgentMsg();
        weAgentMsg.setId(id);
        weAgentMsg.setInvalidUser(appMsgVo.getInvalidUser());
        weAgentMsg.setInvalidParty(appMsgVo.getInvalidParty());
        weAgentMsg.setInvalidTag(appMsgVo.getInvalidTag());
        weAgentMsg.setUnlicensedUser(appMsgVo.getUnlicenseduser());
        weAgentMsg.setMsgId(appMsgVo.getMsgId());
        weAgentMsg.setResponseCode(appMsgVo.getResponse_code());
        weAgentMsg.setSendTime(new Date());
        if (appMsgVo.getErrMsg() != null && !Objects.equals(0, appMsgVo.getErrCode())) {
            weAgentMsg.setStatus(3);
        } else {
            weAgentMsg.setStatus(2);
        }
        agentMsgService.updateById(weAgentMsg);
    }
}

企微连接:WeComAgentClient

import com.dtflys.forest.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * 企微应用管理
 */
@BaseRequest(baseURL = "https://qyapi.weixin.qq.com/cgi-bin", interceptor = WeComAgentTokenInterceptor.class)
@Retry(maxRetryCount = "3", maxRetryInterval = "1000")
public interface WeComAgentClient {
    /**
     * 应用消息通知
     */
    @Post("/message/send")
    WeComMsgVo sendAgentMsg(@JSONBody WeComAgentMsgDto query);
}

拦截器:WeComAgentTokenInterceptor

获取access_token


import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.dtflys.forest.exceptions.ForestRuntimeException;
import com.dtflys.forest.http.ForestRequest;
import com.dtflys.forest.http.ForestResponse;
import com.dtflys.forest.interceptor.Interceptor;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * @description: 应用token
 * @author: seven
 * @create: 2022-11-03 22:36
 **/
@Slf4j
@Component
public class WeComAgentTokenInterceptor extends WeComForestInterceptor implements Interceptor<WeComResultVo> {

    /**
     * 该方法在请求发送之前被调用, 若返回false则不会继续发送请求
     */
    @Override
    public boolean beforeExecute(ForestRequest request) {
        if (accessTokenService == null) {
            accessTokenService = SpringUtils.getBean(WeComAccessTokenService.class);
        }
        Integer agentId = null;
        Object[] arguments = request.getArguments();
        for (Object argument : arguments) {
            if (argument instanceof WeComBaseDto) {
                WeComBaseDto query = (WeComBaseDto) argument;
                agentId = Integer.valueOf(query.getAgentid());
            }
        }
        if (Objects.isNull(agentId)) {
            throw new BadRequestException("应用编号为空,请设置应用编号!");
        }
        String token = accessTokenService.getAgentAccessToken(getCorpId(), agentId);
        request.replaceOrAddQuery("access_token", token);
        return true;
    }

    /**
     * 请求发送失败时被调用
     */
    @Override
    public void onError(ForestRuntimeException e, ForestRequest forestRequest, ForestResponse forestResponse) {
        log.info("onError url:{},------params:{},----------result:{}", forestRequest.getUrl(), JSONObject.toJSONString(forestRequest.getArguments()), forestResponse.getContent());
        if (StringUtils.isNotEmpty(forestResponse.getContent())) {
            throw new BadRequestException(forestResponse.getContent());
        } else {
            throw new BadRequestException("网络请求超时");
        }
    }

    /**
     * 请求成功调用(微信端错误异常统一处理)
     */
    @Override
    public void onSuccess(WeComResultVo resultDto, ForestRequest forestRequest, ForestResponse forestResponse) {
        log.info("onSuccess url:{},result:{}", forestRequest.getUrl(), forestResponse.getContent());
    }

    /**
     * 请求重试
     */
    @Override
    public void onRetry(ForestRequest request, ForestResponse response) {
        log.info("url:{}, query:{},params:{}, 重试原因:{}, 当前重试次数:{}", request.getUrl(), request.getQueryString(), JSONObject.toJSONString(request.getArguments()), response.getContent(), request.getCurrentRetryCount());
        WeComResultVo weComResultVo = JSONUtil.toBean(response.getContent(), WeComResultVo.class);
        // 刷新token
        if (!ObjectUtil.equal(WeErrorCodeEnum.ERROR_CODE_OWE_1.getErrorCode(), weComResultVo.getErrCode()) && Lists.newArrayList(errorCodeRetry).contains(weComResultVo.getErrCode().toString())) {
            // 删除缓存
            Integer agentId = null;
            Object[] arguments = request.getArguments();
            for (Object argument : arguments) {
                if (argument instanceof WeComBaseQuery) {
                    WeComBaseQuery query = (WeComBaseQuery) argument;
                    agentId = Integer.valueOf(query.getAgentid());
                }
            }
            if (Objects.isNull(agentId)) {
                throw new BadRequestException("应用编号为空,请设置应用编号!");
            }
            accessTokenService.removeAgentAccessToken(getCorpId(), agentId);
            String token = accessTokenService.getAgentAccessToken(getCorpId(), agentId);
            request.replaceOrAddQuery("access_token", token);
        }
    }
}

WeComForestInterceptor

获取企业id

import io.seata.common.util.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 响应文件流
 */
@Slf4j
@Component
public abstract class WeComForestInterceptor {

    @Autowired
    protected WeComAccessTokenService accessTokenService;

    @Autowired
    protected CorpAccountService corpAccountService;

    @Value("${wecom.error-code-retry}")
    protected String errorCodeRetry;

    protected String getCorpId() {
        List<CorpAccount> list = corpAccountService.list();
        if (CollectionUtils.isEmpty(list)) {
            throw new BadRequestException("企业信息获取失败,请先完善企业信息!");
        }
        return list.get(0).getCorpId();
    }
}

token获取业务类:WeComAccessTokenServiceImpl

package com.budsoft.scrm.service.wecom.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.budsoft.core.exception.BadRequestException;
import com.budsoft.scrm.client.WeComTokenClient;
import com.budsoft.scrm.common.utils.RedisUtils;
import com.budsoft.scrm.common.utils.StringUtils;
import com.budsoft.scrm.constant.WeComConstants;
import com.budsoft.scrm.domain.agent.entity.AgentInfo;
import com.budsoft.scrm.domain.entity.CorpAccount;
import com.budsoft.scrm.domain.wecom.vo.WeComAccessTokenVo;
import com.budsoft.scrm.service.CorpAccountService;
import com.budsoft.scrm.service.agent.AgentInfoService;
import com.budsoft.scrm.service.wecom.WeComAccessTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @description: 获取企微token相关接口
 * @author: seven
 * @create: 2022-08-26 14:43
 **/
@Service
public class WeComAccessTokenServiceImpl implements WeComAccessTokenService {

    @Resource
    private WeComTokenClient weComTokenClient;

    @Autowired
    private CorpAccountService corpAccountService;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private AgentInfoService agentInfoService;

    @Override
    public String getAgentAccessToken(String corpId, Integer agentId) {
        String agentTokenKey = StringUtils.format(WeComConstants.WE_AGENT_ACCESS_TOKEN, corpId, agentId);
        String accessToken = redisUtils.getCacheObject(agentTokenKey);
        // 为空,请求微信服务器同时缓存到redis中
        if (StringUtils.isEmpty(accessToken)) {
            AgentInfo agentInfo = agentInfoService.getOne(new QueryWrapper<AgentInfo>().eq("agent_id", agentId));
            if (Objects.isNull(agentInfo)) {
                throw new BadRequestException("无可用的应用");
            }
            WeComAccessTokenVo weComAccessTokenVo = weComTokenClient.getToken(corpId, agentInfo.getSecret());

            if (Objects.nonNull(weComAccessTokenVo) && StringUtils.isNotEmpty(weComAccessTokenVo.getAccessToken())) {
                accessToken = weComAccessTokenVo.getAccessToken();
                redisUtils.setCacheObject(agentTokenKey, weComAccessTokenVo.getAccessToken(), weComAccessTokenVo.getExpiresIn(), TimeUnit.SECONDS);
            }
        }
        return accessToken;
    }


    @Override
    public void removeAgentAccessToken(String corpId, Integer agentId) {
        redisUtils.deleteObject(StringUtils.format(WeComConstants.WE_AGENT_ACCESS_TOKEN, corpId, agentId));
    }
}

WeComTokenClient

import com.dtflys.forest.annotation.BaseRequest;
import com.dtflys.forest.annotation.Query;
import com.dtflys.forest.annotation.Request;
import com.dtflys.forest.annotation.Retry;

/**
 * @author seven
 * @description 获取企业微信token接口
 * @date 2021/12/13 10:30
 **/
@BaseRequest(baseURL = "https://qyapi.weixin.qq.com/cgi-bin")
@Retry(maxRetryCount = "3", maxRetryInterval = "1000")
public interface WeComTokenClient {
    /**
     * 获取token
     *
     * @param corpId 企业ID
     * @param secret 企业密钥
     * @return WeCorpTokenVo
     */
    @Request(url = "/gettoken", type = "GET", interceptor = WeComNoTokenInterceptor.class)
    WeComAccessTokenVo getToken(@Query("corpid") String corpId, @Query("corpsecret") String secret);
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值