1.通过应用发送消息
2.通过群机器人发送群消息
要清楚几个概念
使用企业微信管理员登录到控制台
企业微信后台管理地址
1.CorpId
这个是每个企业独有的企业ID
2.agentId 和 secret
agentId 相当于是应用ID,secret就理解成key
可以自己创建个应用,然后绑定到相关部门或者具体到人
知道这三个概念后,就可以通过SDK发送企业微信消息了
1.引入依赖
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>3.7.0</version>
</dependency>
2.添加固定写法的代码
2.1.增加config类,自己新增个config包写到里面
package com.linkyoyo.weatherpredict.config;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @author GuoZiXuan
* @date 2022/06/13 14:45
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Component
public class AppConfig {
/**
* 设置微信企业应用的AgentId
*/
private Integer agentId;
/**
* 设置微信企业应用的Secret
*/
private String secret;
/**
* 设置微信企业号的token
*/
private String token;
/**
* 设置微信企业号的EncodingAESKey
*/
private String aesKey;
}
package com.linkyoyo.weatherpredict.config;
import com.linkyoyo.weatherpredict.handler.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.val;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import me.chanjar.weixin.cp.constant.WxCpConsts;
import me.chanjar.weixin.cp.message.WxCpMessageRouter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
//@Configuration
//@EnableConfigurationProperties(WxCpProperties.class)
@Component
public class WxCpConfiguration {
private LogHandler logHandler;
private NullHandler nullHandler;
private LocationHandler locationHandler;
private MenuHandler menuHandler;
private MsgHandler msgHandler;
private UnsubscribeHandler unsubscribeHandler;
private SubscribeHandler subscribeHandler;
private WxCpProperties properties;
private static Map<Integer, WxCpMessageRouter> routers = Maps.newHashMap();
private static Map<Integer, WxCpService> cpServices = Maps.newHashMap();
@Autowired
public WxCpConfiguration(LogHandler logHandler, NullHandler nullHandler, LocationHandler locationHandler,
MenuHandler menuHandler, MsgHandler msgHandler, UnsubscribeHandler unsubscribeHandler,
SubscribeHandler subscribeHandler, WxCpProperties properties) {
this.logHandler = logHandler;
this.nullHandler = nullHandler;
this.locationHandler = locationHandler;
this.menuHandler = menuHandler;
this.msgHandler = msgHandler;
this.unsubscribeHandler = unsubscribeHandler;
this.subscribeHandler = subscribeHandler;
this.properties = properties;
}
public static Map<Integer, WxCpMessageRouter> getRouters() {
return routers;
}
public WxCpService getCpService(Integer agentId, String secret, String corpId) {
AppConfig appConfig = AppConfig.builder()
.agentId(agentId)
.secret(secret)
.build();
List<AppConfig> list = Lists.newArrayList();
list.add(appConfig);
properties.setAppConfigs(list);
properties.setCorpId(corpId);
cpServices = this.properties.getAppConfigs().stream().map(a -> {
val configStorage = new WxCpDefaultConfigImpl();
configStorage.setCorpId(this.properties.getCorpId());
configStorage.setAgentId(a.getAgentId());
configStorage.setCorpSecret(a.getSecret());
configStorage.setToken(a.getToken());
configStorage.setAesKey(a.getAesKey());
val service = new WxCpServiceImpl();
service.setWxCpConfigStorage(configStorage);
routers.put(a.getAgentId(), this.newRouter(service));
return service;
}).collect(Collectors.toMap(service -> service.getWxCpConfigStorage().getAgentId(), a -> a));
return cpServices.get(agentId);
}
private WxCpMessageRouter newRouter(WxCpService wxCpService) {
final val newRouter = new WxCpMessageRouter(wxCpService);
// 记录所有事件的日志 (异步执行)
newRouter.rule().handler(this.logHandler).next();
// 自定义菜单事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.CLICK).handler(this.menuHandler).end();
// 点击菜单链接事件(这里使用了一个空的处理器,可以根据自己需要进行扩展)
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.VIEW).handler(this.nullHandler).end();
// 关注事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SUBSCRIBE).handler(this.subscribeHandler)
.end();
// 取消关注事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.UNSUBSCRIBE)
.handler(this.unsubscribeHandler).end();
// 上报地理位置事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.LOCATION).handler(this.locationHandler)
.end();
// 接收地理位置消息
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION)
.handler(this.locationHandler).end();
// 扫码事件(这里使用了一个空的处理器,可以根据自己需要进行扩展)
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SCAN).handler(this.nullHandler).end();
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxCpConsts.EventType.CHANGE_CONTACT).handler(new ContactChangeHandler()).end();
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxCpConsts.EventType.ENTER_AGENT).handler(new EnterAgentHandler()).end();
// 默认
newRouter.rule().async(false).handler(this.msgHandler).end();
return newRouter;
}
}
package com.linkyoyo.weatherpredict.config;
import com.linkyoyo.weatherpredict.util.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Component
public class WxCpProperties {
/**
* 设置微信企业号的corpId
*/
private String corpId;
private List<AppConfig> appConfigs;
@Override
public String toString() {
return JsonUtils.toJson(this);
}
}
2.2.增加builder类,自己新增个builder包写到里面
package com.linkyoyo.weatherpredict.builder;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
public abstract class AbstractBuilder {
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 创建WxCpXmlOutMessage信息
* @param content
* @param wxMessage
* @param service
* @return
*/
public abstract WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, WxCpService service);
}
package com.linkyoyo.weatherpredict.builder;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
public class TextBuilder extends AbstractBuilder {
@Override
public WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage,
WxCpService service) {
WxCpXmlOutTextMessage m = WxCpXmlOutMessage.TEXT().content(content)
.fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName())
.build();
return m;
}
}
2.3 增加handle类,放在handler包下
package com.linkyoyo.weatherpredict.handler;
import me.chanjar.weixin.cp.message.WxCpMessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
public abstract class AbstractHandler implements WxCpMessageHandler {
protected Logger logger = LoggerFactory.getLogger(getClass());
}
package com.linkyoyo.weatherpredict.handler;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
public abstract class AbstractScanHandler extends AbstractHandler {
}
package com.linkyoyo.weatherpredict.handler;
import com.linkyoyo.weatherpredict.builder.TextBuilder;
import com.linkyoyo.weatherpredict.util.JsonUtils;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 通讯录变更事件处理器.
*
* @author Binary Wang(https://github.com/binarywang)
*/
@Component
public class ContactChangeHandler extends AbstractHandler {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
WxSessionManager sessionManager) {
String content = "收到通讯录变更事件,内容:" + JsonUtils.toJson(wxMessage);
this.logger.info(content);
return new TextBuilder().build(content, wxMessage, cpService);
}
}
package com.linkyoyo.weatherpredict.handler;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import java.util.Map;
/**
* <pre>
*
* Created by Binary Wang on 2018/8/27.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Slf4j
public class EnterAgentHandler extends AbstractHandler {
private static final int TEST_AGENT = 1000002;
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService, WxSessionManager sessionManager) throws WxErrorException {
// do something
return null;
}
}
package com.linkyoyo.weatherpredict.handler;
import com.linkyoyo.weatherpredict.builder.TextBuilder;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
@Component
public class LocationHandler extends AbstractHandler {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
WxSessionManager sessionManager) {
if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.LOCATION)) {
//TODO 接收处理用户发送的地理位置消息
try {
String content = "感谢反馈,您的的地理位置已收到!";
return new TextBuilder().build(content, wxMessage, null);
} catch (Exception e) {
this.logger.error("位置消息接收处理失败", e);
return null;
}
}
//上报地理位置事件
this.logger.info("\n上报地理位置,纬度 : {}\n经度 : {}\n精度 : {}",
wxMessage.getLatitude(), wxMessage.getLongitude(), String.valueOf(wxMessage.getPrecision()));
//TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用
return null;
}
}
package com.linkyoyo.weatherpredict.handler;
import com.linkyoyo.weatherpredict.util.JsonUtils;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
@Component
public class LogHandler extends AbstractHandler {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
WxSessionManager sessionManager) {
this.logger.info("\n接收到请求消息,内容:{}", JsonUtils.toJson(wxMessage));
return null;
}
}
package com.linkyoyo.weatherpredict.handler;
import me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
@Component
public class MenuHandler extends AbstractHandler {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
WxSessionManager sessionManager) {
String msg = String.format("type:%s, event:%s, key:%s",
wxMessage.getMsgType(), wxMessage.getEvent(),
wxMessage.getEventKey());
if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) {
return null;
}
return WxCpXmlOutMessage.TEXT().content(msg)
.fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName())
.build();
}
}
package com.linkyoyo.weatherpredict.handler;
import com.linkyoyo.weatherpredict.builder.TextBuilder;
import com.linkyoyo.weatherpredict.util.JsonUtils;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
@Component
public class MsgHandler extends AbstractHandler {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
WxSessionManager sessionManager) {
if (!wxMessage.getMsgType().equals(WxConsts.XmlMsgType.EVENT)) {
//TODO 可以选择将消息保存到本地
}
//TODO 组装回复消息
String content = "收到信息内容:" + JsonUtils.toJson(wxMessage);
return new TextBuilder().build(content, wxMessage, cpService);
}
}
package com.linkyoyo.weatherpredict.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
@Component
public class NullHandler extends AbstractHandler {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
WxSessionManager sessionManager) {
return null;
}
}
package com.linkyoyo.weatherpredict.handler;
import com.linkyoyo.weatherpredict.builder.TextBuilder;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
@Component
public class SubscribeHandler extends AbstractHandler {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
WxSessionManager sessionManager) throws WxErrorException {
this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUserName());
// 获取微信用户基本信息
WxCpUser userWxInfo = cpService.getUserService().getById(wxMessage.getFromUserName());
if (userWxInfo != null) {
// TODO 可以添加关注用户到本地
}
WxCpXmlOutMessage responseResult = null;
try {
responseResult = handleSpecial(wxMessage);
} catch (Exception e) {
this.logger.error(e.getMessage(), e);
}
if (responseResult != null) {
return responseResult;
}
try {
return new TextBuilder().build("感谢关注", wxMessage, cpService);
} catch (Exception e) {
this.logger.error(e.getMessage(), e);
}
return null;
}
/**
* 处理特殊请求,比如如果是扫码进来的,可以做相应处理
*/
private WxCpXmlOutMessage handleSpecial(WxCpXmlMessage wxMessage) {
//TODO
return null;
}
}
package com.linkyoyo.weatherpredict.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
@Component
public class UnsubscribeHandler extends AbstractHandler {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
WxSessionManager sessionManager) {
String openId = wxMessage.getFromUserName();
this.logger.info("取消关注用户 OPENID: " + openId);
// TODO 可以更新本地数据库为取消关注状态
return null;
}
}
2.4 增加表及实体类,用于存应用的信息
这里使用的是postresql,字段一样就行,增加该表的原因是,后面发送消息需要传入应用的agentId和secret还有公司Id,所以需要维护起来
CREATE TABLE "public"."tbl_department" (
"id" int4 NOT NULL DEFAULT nextval('tbl_department_id_seq'::regclass),
"agent_id" int4 NOT NULL,
"secret" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"name" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"corp_id" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"create_time" timestamp(6),
"update_time" timestamp(6),
CONSTRAINT "tbl_department_pkey" PRIMARY KEY ("id")
)
;
ALTER TABLE "public"."tbl_department"
OWNER TO "hzcloud_master";
COMMENT ON COLUMN "public"."tbl_department"."agent_id" IS '微信-应用-agentId';
COMMENT ON COLUMN "public"."tbl_department"."secret" IS '微信-应用-secret';
COMMENT ON COLUMN "public"."tbl_department"."name" IS '微信-应用(部门)-名称';
COMMENT ON COLUMN "public"."tbl_department"."corp_id" IS '微信-企业ID';
COMMENT ON COLUMN "public"."tbl_department"."create_time" IS '创建时间';
COMMENT ON COLUMN "public"."tbl_department"."update_time" IS '修改时间';
COMMENT ON TABLE "public"."tbl_department" IS '微信-部门表';
package com.linkyoyo.weatherpredict.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
/**
* @author ZhangWeiXiang
* @date 2021/11/15 14:19
* 微信部门
*/
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Builder
@DynamicInsert
@DynamicUpdate
@EntityListeners(AuditingEntityListener.class)
@Entity
@Table(name = "tbl_department")
public class Department {
/**
* 主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
/**
* 每个应用都有唯一的agentid
*/
@Column(name = "agent_id")
private Integer agentId;
/**
* 独立的访问密钥
*/
@Column(name = "secret")
private String secret;
/**
* 应用名称
*/
private String name;
/**
* 企业ID
*/
@Column(name = "corp_id")
private String corpId;
/**
* 创建时间
*/
@CreatedDate
@Column(name = "create_time")
private LocalDateTime createTime;
/**
* 修改时间
*/
@LastModifiedDate
@Column(name = "update_time")
private LocalDateTime updateTime;
}
2.5 增加工具类
package com.linkyoyo.weatherpredict.util;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* @author Binary Wang(https://github.com/binarywang)
*/
public class JsonUtils {
private static final ObjectMapper JSON = new ObjectMapper();
static {
JSON.setSerializationInclusion(Include.NON_NULL);
JSON.configure(SerializationFeature.INDENT_OUTPUT, Boolean.TRUE);
}
public static String toJson(Object obj) {
try {
return JSON.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
下面就是我自己封装后的写法了,这里主要有两个,发送普通消息和发送文件消息
1.引入hutool工具类的依赖,我后面有用到
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.2</version>
</dependency>
2.编写文件转换方法,就是将上传的文件暂存
2.1新增类FileStoreService
package com.linkyoyo.weatherpredict.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
public interface FileStoreService {
File upload(MultipartFile multipartFile);
}
// A code block
var foo = 'bar';
2.2编写实现类
package com.linkyoyo.weatherpredict.service.impl;
import cn.hutool.core.io.FileUtil;
import com.linkyoyo.weatherpredict.exception.BizException;
import com.linkyoyo.weatherpredict.properties.LinkyoyoProperties;
import com.linkyoyo.weatherpredict.result.CodeMsg;
import com.linkyoyo.weatherpredict.service.FileStoreService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Service
@Slf4j
@Transactional
@RequiredArgsConstructor
public class FileStoreServiceImpl implements FileStoreService {
@Autowired
private LinkyoyoProperties linkyoyoProperties ;
@Override
public File upload(MultipartFile multipartFile) {
String fileName = multipartFile.getOriginalFilename();
if(!FileUtil.exist(linkyoyoProperties.getPy().getWordPath()+ File.separator+"temporary"))
FileUtil.mkdir(linkyoyoProperties.getPy().getWordPath()+File.separator+"temporary") ;
//linkyoyoProperties.getPy().getWordPath() 这个方法就是从我的配置类获取个路径,这个你可以自己设置成自己的本地某个路径就好
String path = linkyoyoProperties.getPy().getWordPath()+File.separator+"temporary"+File.separator;
if(!FileUtil.exist(path))
FileUtil.mkdir(path) ;
File file = FileUtil.touch(new File(path, fileName));
try {
multipartFile.transferTo(file);
} catch (IOException e) {
log.error("文件转存失败 :FileStoreService" + file.getAbsolutePath());
throw new BizException(CodeMsg.FILE_UPLOAD_ERROR.fillArgs("转存失败"));
}
return file;
}
}
LinkyoyoProperties 这个类的属性在配置文件中
@Data
@ConfigurationProperties(prefix = "linkyoyo")
public class LinkyoyoProperties {
/**
* python相关配置
*/
private PythonProperties py = new PythonProperties();
/**
* 企业微信相关配置
*/
private WxProperties wx = new WxProperties();
}
@Data
public class PythonProperties {
private String pyExplainerPath;
private String pythonPath;
private String wordPath;
private String filePath;
private String imagePath;
private String wordTemplate;
private String designWordScriptName;
private String postEvaluationWordScriptName;
private String doc2PdfScriptName;
}
@Data
public class WxProperties {
//企业Id
private String corpId;
//应用Id
private String agentId;
//应用密钥
private String secret;
//企业微信群机器人 url
private String url;
//企业微信群机器人 key
private String key;
}
2.2编写接收类,主要是用于调用企业微信API后,转换为实体类用
package com.linkyoyo.weatherpredict.info;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
private String userid;
}
3.!重点来了,编写消息发送工具类
package com.linkyoyo.weatherpredict.util;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.linkyoyo.weatherpredict.config.WxCpConfiguration;
import com.linkyoyo.weatherpredict.entity.Department;
import com.linkyoyo.weatherpredict.exception.BizException;
import com.linkyoyo.weatherpredict.info.UserInfo;
import com.linkyoyo.weatherpredict.result.CodeMsg;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpAgent;
import me.chanjar.weixin.cp.bean.WxCpDepart;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
@Slf4j
public class WxSendUtils {
@Autowired
private WxCpConfiguration wxCpConfiguration;
public void sendTextMsg(Department department, String msg) throws Exception{
WxCpService cpService = wxCpConfiguration.getCpService(department.getAgentId(), department.getSecret(), department.getCorpId());
//拿到所有应用的名称和agentId
List<WxCpAgent> list = cpService.getAgentService().list();
if(CollectionUtils.isEmpty(list)){
throw new BizException(CodeMsg.API_CALL_ERROR.fillArgs("该应用不存在!"));
}
//获取到该应用下绑定的所有部门
List<WxCpDepart> wxCpDepartList = cpService.getDepartmentService().list(null);
//对该应用绑定的部门进行消息的发送
wxCpDepartList.forEach(wxCpDepart -> {
WxCpMessage wxCpMessage = WxCpMessage.TEXT()
.agentId(department.getAgentId()) // 企业号应用ID
.toParty(wxCpDepart.getId()+"")
.content(msg)
.build();
try {
cpService.messageSend(wxCpMessage);
} catch (WxErrorException e) {
log.info("微信告警发送报错: {}", e.getError());
}
});
//对该应用绑定的个人进行消息的发送
List<String> userInfos = getUseInfos(department.getCorpId(), department.getSecret(), department.getAgentId());
userInfos.forEach(userInfo -> {
WxCpMessage wxCpMessage = WxCpMessage.TEXT()
.agentId(department.getAgentId()) // 企业号应用ID
.toUser(userInfo)
.content(msg)
.build();
try {
cpService.messageSend(wxCpMessage);
} catch (WxErrorException e) {
log.info("微信告警发送报错: {}", e.getError());
}
});
}
public void sendFileMsg(Department department, File file) throws WxErrorException {
//调用资源上传接口并返回资源Id
String mediaId = getMediaId(department.getCorpId(), department.getSecret(), "file", file);
WxCpService cpService = wxCpConfiguration.getCpService(department.getAgentId(), department.getSecret(), department.getCorpId());
//拿到所有应用的名称和agentId
List<WxCpAgent> list = cpService.getAgentService().list();
if(CollectionUtils.isEmpty(list)){
throw new BizException(CodeMsg.API_CALL_ERROR.fillArgs("该应用不存在!"));
}
//获取到该应用下绑定的所有部门
List<WxCpDepart> wxCpDepartList = cpService.getDepartmentService().list(null);
//对该应用绑定的部门进行消息的发送
wxCpDepartList.forEach(wxCpDepart -> {
WxCpMessage wxCpMessage = WxCpMessage.FILE()
.agentId(department.getAgentId()) // 企业号应用ID
.toParty(wxCpDepart.getId()+"")
.mediaId(mediaId)
.build();
try {
cpService.messageSend(wxCpMessage);
} catch (WxErrorException e) {
log.info("微信告警发送报错: {}", e.getError());
}
});
//对该应用绑定的个人进行消息的发送
List<String> userInfos = getUseInfos(department.getCorpId(), department.getSecret(), department.getAgentId());
userInfos.forEach(userInfo -> {
WxCpMessage wxCpMessage = WxCpMessage.FILE()
.agentId(department.getAgentId()) // 企业号应用ID
.toUser(userInfo)
.mediaId(mediaId)
.build();
try {
cpService.messageSend(wxCpMessage);
} catch (WxErrorException e) {
log.info("微信告警发送报错: {}", e.getError());
}
});
}
/**
*
* @param corpid 企业ID
* @param corpsecret Secret
* @param type 媒体文件类型:图片(image)、语音(voice)、视频(video),普通文件(file)
* @param file
* @throws Exception
*/
public String getMediaId(String corpid, String corpsecret, String type,File file){
String token = null;
String mediaId = null;
//获取token
try {
String toKenBody = HttpUtil.createGet("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid="+corpid+"&corpsecret="+corpsecret)
.execute().body();
JSONObject toKenObject = JSONObject.parseObject(toKenBody);
token = toKenObject.get("access_token").toString();
}catch (Exception e){
throw new BizException(CodeMsg.API_CALL_ERROR.fillArgs("企业微信 获取token接口 调用失败"));
}
try {
String body = HttpUtil.createPost("https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token="+token+"&type="+type)
.form("media",file)
.execute().body();
JSONObject jsonObject = JSONObject.parseObject(body);
mediaId = jsonObject.get("media_id").toString();
}catch (Exception e){
throw new BizException(CodeMsg.API_CALL_ERROR.fillArgs("企业微信 上传临时素材接口 调用失败"));
}
return mediaId;
}
public List<String> getUseInfos(String corpid, String corpsecret,Integer agentid){
try {
String body = HttpUtil.createGet("https://qyapi.weixin.qq.com/cgi-bin/agent/get?access_token="+getToKen(corpid,corpsecret)+"&agentid="+agentid)
.execute().body();
JSONObject toKenObject = JSONObject.parseObject(body);
Map<String,Object> userinfo = (Map<String, Object>) toKenObject.get("allow_userinfos");
if(CollectionUtils.isEmpty(userinfo))
return null;
JSONArray user = (JSONArray)userinfo.get("user");
List<UserInfo> userInfos = user.toJavaList(UserInfo.class);
if(CollectionUtils.isEmpty(user))
return null;
return userInfos.stream().map(e->e.getUserid()).collect(Collectors.toList());
}catch (Exception e){
throw new BizException(CodeMsg.API_CALL_ERROR.fillArgs("企业微信 上传临时素材接口 调用失败"));
}
}
public String getToKen(String corpid, String corpsecret){
try {
String toKenBody = HttpUtil.createGet("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid="+corpid+"&corpsecret="+corpsecret)
.execute().body();
JSONObject toKenObject = JSONObject.parseObject(toKenBody);
return toKenObject.get("access_token").toString();
}catch (Exception e){
throw new BizException(CodeMsg.API_CALL_ERROR.fillArgs("企业微信 获取token接口 调用失败"));
}
}
}
代码讲解:普通消息的发送只需要传入四个重要信息,1.公司Id 2.agentId 3.secret 4.消息,而文件消息就有点不一样了,发送的文件必须先调用上传临时文件接口,然后会返回一个mediaId,然后发送文件消息的时候就传这个Id,这样就实现了文件消息的发送。
还有个点就是,应用既可以绑部门,也可以绑个人,所以可以通过方法先获取到该应用下绑定的部门或个人,然后再去往这个部门下的所有人去发消息,或去发个人。
4.编写测试类
package com.linkyoyo.weatherpredict.controller;
import com.linkyoyo.weatherpredict.entity.Department;
import com.linkyoyo.weatherpredict.result.R;
import com.linkyoyo.weatherpredict.service.FileStoreService;
import com.linkyoyo.weatherpredict.util.WxSendUtils;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequiredArgsConstructor
@RequestMapping("/sendWx")
public class SendWxController {
private final WxSendUtils wxSendUtils;
private final FileStoreService fileStoreService;
//发送文件消息
@PostMapping(value = "/FileMsg", headers = "Content-Type=multipart/form-data")
public R FileMsg(@RequestPart("file") MultipartFile file) throws WxErrorException {
Department department = new Department();
department.setName("消息推送");
department.setCorpId("***");
department.setAgentId(***);
department.setSecret("***");
wxSendUtils.sendFileMsg(department, fileStoreService.upload(file));
return R.ok();
}
//发送普通文本消息
@PostMapping(value = "/TextMsg")
public R TextMsg(String msg) throws Exception {
Department department = new Department();
department.setName("消息推送");
department.setCorpId("***");
department.setAgentId(***);
department.setSecret("***");
wxSendUtils.sendTextMsg(department, msg);
return R.ok();
}
@PostMapping(value = "/getUse")
public R getUse() throws Exception {
Department department = new Department();
department.setName("消息推送");
department.setCorpId("***");
department.setAgentId(***);
department.setSecret("***");
wxSendUtils.getUseInfos(department.getCorpId(),department.getSecret(),department.getAgentId());
return R.ok();
}
}
发送群消息
1.创建群机器人
主要就是通过这个地址去发送消息的
相关API地址:https://developer.work.weixin.qq.com/document/path/91770