背景
公司的项目,通知信息打算接入企业微信通知。提前做下实验。
版本
JDK 21
SpringBoot 3.1.5
weixin-java-cp 4.5.0
hutool-all 5.8.23
jedis 5.1.0
说明:暂时只是一个springboot小demo,后续根据项目会有组件和结构的修改。
代码
代码比较简单,配置好参数直接调用即可。
这里全部贴到这里,后面会讲解每一步所需要的参数来源及怎么配置。
POM
<properties>
<java.version>21</java.version>
<snakeyaml.version>2.2</snakeyaml.version>
<jakarta.validation-api.version>3.0.2</jakarta.validation-api.version>
<hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
<fastjson.version>2.0.43</fastjson.version>
<weixin-java-cp.version>4.5.0</weixin-java-cp.version>
<hutool-all.version>5.8.23</hutool-all.version>
<jedis.version>5.1.0</jedis.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
<!-- 可不引入 https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.binarywang/weixin-java-cp -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${weixin-java-cp.version}</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${jakarta.validation-api.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
配置实体
@Data
@Component
@ConfigurationProperties(value = "wechat2")
public class WechatConfig {
/**
* 要发送消息的应用的两个字段
*/
@Value("${wechat2.agentid}")
private Integer agentId;
/**
*
*/
@Value("${wechat2.secret}")
private String secret;
/**
* 企业id
*/
@Value("${wechat2.corpid}")
private String corpId;
/**
* 用于访问redis的密码,没设置就不需要这个字段
*/
@Value("${wechat2.redis-pwd}")
private String redisPwd;
}
工具类
redis 工具类
@Slf4j
@Configuration
public class RedisUtils {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 设定 key对应的值为一个数值,每次调用 - value
*
* @param key key
* @param value value
* @return 减 value 之后的值
*/
public long decrementBy(String key, long value) {
log.info("decrement key:{}", key);
Long len = stringRedisTemplate.opsForValue().decrement(key, value);
if (Objects.isNull(len)) {
return 0;
}
return (long) len;
}
/**
* 设定 key对应的值为一个数值,每次调用 -1
*
* @param key key
* @return 减 1 之后的值
*/
public long decrement(String key) {
log.info("DECR key:{}", key);
Long len = stringRedisTemplate.opsForValue().decrement(key);
if (Objects.isNull(len)) {
return 0;
}
return (long) len;
}
/**
* 追加值 返回追加之后的字符长度
*
* @param key key
* @param value value
* @return 字符长度
*/
public int append(String key, String value) {
log.info("APPEND key:{}, value:{}", key, value);
Integer add = stringRedisTemplate.opsForValue().append(key, value);
if (Objects.isNull(add)) {
return 0;
}
return (int) add;
}
/**
* 设值
*
* @param key key
* @param value value
*/
public void set(String key, String value) {
log.info("SET key:{}, value:{}", key, value);
stringRedisTemplate.opsForValue().set(key, value);
}
/**
* 设值
*
* @param key key
* @param value value
* @param expireTime 过期时间 单位 s
*/
public void set(String key, String value, int expireTime) {
log.info("SETEX key:{}, value:{}, expireTime:{}", key, value, expireTime);
stringRedisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
}
/**
* 取值
*
* @param key key
* @return 值
*/
public String get(String key) {
String value = stringRedisTemplate.opsForValue().get(key);
log.info("GET key:{}, value:{}", key, value);
return value;
}
/**
* 删除 key
*
* @param key key
* @return 是否删除
*/
public Boolean del(String key) {
if (exists(key)) {
return stringRedisTemplate.delete(key);
}
log.error("del key:{} 不存在", key);
return false;
}
/**
* 判断key是否存在,如果存在则删除,不存在返回 空
*
* @param key -
* @return -
*/
public Boolean getAndDelete(String key) {
String ret = stringRedisTemplate.opsForValue().getAndDelete(key);
log.info("GETDEL key:{}, result:{}", key, ret);
return StringUtils.isNotBlank(ret);
}
/**
* 判断 key 是否存在
*
* @param key key
* @return 是否存在
*/
public Boolean exists(String key) {
Boolean exists = stringRedisTemplate.hasKey(key);
log.info("exists key:{}, hasKey:{}", key, exists);
return exists;
}
}
@Slf4j
@Component
public class WechatConfigUtil {
@Resource
private WechatConfig wechatConfig;
@Autowired
private RedisUtils redisUtils;
/**
* 配置企业微信服务
*
* @return -
*/
public WxCpService wxCpService() {
WxCpService wxCpService = new WxCpServiceImpl();
WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl();
config.setAgentId(wechatConfig.getAgentId());
config.setCorpSecret(wechatConfig.getSecret());
config.setCorpId(wechatConfig.getCorpId());
resetTokenAndJsApi2(wxCpService, config);
return wxCpService;
}
public void resetTokenAndJsApi2(WxCpService wxCpService, WxCpDefaultConfigImpl wxCpDefaultConfig) {
try {
wxCpService.setWxCpConfigStorage(wxCpDefaultConfig);
String accessToken;
// 获取TOKEN
accessToken = wxCpService.getAccessToken(false);
log.info("accessToken:{}", accessToken);
wxCpDefaultConfig.setAccessToken(accessToken);
} catch (Exception e) {
log.error(e.getMessage());
}
}
/**
* 重置token
*
* @param wxCpService -
* @param wxCpDefaultConfig -
*/
public void resetTokenAndJsApi3(WxCpService wxCpService, WxCpDefaultConfigImpl wxCpDefaultConfig) {
wxCpService.setWxCpConfigStorage(wxCpDefaultConfig);
String wxAccessToken = "wx" + wechatConfig.getAgentId();
// 根据应用id获取对应token
String json = redisUtils.get(wxAccessToken);
// redis里无token
if (StringUtils.isBlank(json)) {
String accessToken;
// 获取TOKEN
try {
accessToken = wxCpService.getAccessToken(false);
} catch (WxErrorException e) {
throw new RuntimeException(e);
}
log.info("null, new accessToken:{}", accessToken);
wxCpDefaultConfig.setAccessToken(accessToken);
wxCpService.setWxCpConfigStorage(wxCpDefaultConfig);
// 缓存token
redisUtils.set(wxAccessToken, JSON.toJSONString(wxCpDefaultConfig));
return;
}
// redis 里有token
wxCpDefaultConfig = JSON.parseObject(json, WxCpDefaultConfigImpl.class);
log.info("redis entity:{}", JSON.toJSONString(wxCpDefaultConfig));
// token 到期
if (wxCpDefaultConfig.isAccessTokenExpired()) {
try {
String accessToken;
// 获取 新TOKEN
accessToken = wxCpService.getAccessToken(false);
log.info("expired, new accessToken:{}", accessToken);
wxCpDefaultConfig.setAccessToken(accessToken);
wxCpService.setWxCpConfigStorage(wxCpDefaultConfig);
// 缓存token
redisUtils.set(wxAccessToken, JSON.toJSONString(wxCpDefaultConfig));
} catch (WxErrorException e) {
log.error(e.getMessage());
}
return;
}
log.info("used accessToken:{}", wxCpDefaultConfig.getAccessToken());
wxCpDefaultConfig.setAccessToken(wxCpDefaultConfig.getAccessToken());
wxCpService.setWxCpConfigStorage(wxCpDefaultConfig);
}
/**
* 重置token 赋值有问题 删了!!!
*
* @param wxCpService -
* @param wxCpDefaultConfig -
*/
public void resetTokenAndJsApi(WxCpService wxCpService, WxCpDefaultConfigImpl wxCpDefaultConfig) {
JedisPool jedisPool = new JedisPool();
Jedis jedis = jedisPool.getResource();
if (StringUtils.isNotBlank(wechatConfig.getRedisPwd())) {
jedis.auth(wechatConfig.getRedisPwd());
}
wxCpService.setWxCpConfigStorage(wxCpDefaultConfig);
String wxAccessToken = "wx" + wechatConfig.getAgentId();
// 根据应用id获取对应token
String json = jedis.get(wxAccessToken);
if (!StringUtils.isEmpty(json)) {
wxCpDefaultConfig = JSON.parseObject(json, WxCpDefaultConfigImpl.class);
}
// token到期
if (wxCpDefaultConfig.isAccessTokenExpired()) {
try {
String accessToken;
accessToken = wxCpService.getAccessToken(false);
wxCpDefaultConfig.setAccessToken(accessToken);
} catch (WxErrorException e) {
log.error(e.getMessage());
}
}
// 缓存token
jedis.set(wxAccessToken, JSON.toJSONString(wxCpDefaultConfig));
jedis.close();
}
}
发送消息
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
@Component
public class SendWxCpMsg {
@Autowired
private WechatConfigUtil wechatConfigUtil;
public void sendToWxCp(String username) {
// 微信消息对象
WxCpMessageServiceImpl wxCpMessageService = new WxCpMessageServiceImpl(wechatConfigUtil.wxCpService());
WxCpMessage wxCpMessage = new WxCpMessage();
wxCpMessage.setSafe("0");
// 设置消息形式,这里设置为卡片消息
wxCpMessage.setMsgType("textcard");
var time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"));
// 设置发送用户
wxCpMessage.setToUser(username);
// 发送的标题
wxCpMessage.setTitle("通知标题-TEST");
// 发送内容
wxCpMessage.setDescription("祝新年快乐,幸福安康!"
+ "\n" + "当前时间:"
+ time + "\n"
+ "点击下方领取祝福");
// 设置跳转url
wxCpMessage.setUrl("https://developer.work.weixin.qq.com/community/question");
wxCpMessage.setBtnTxt("前往");
try {
// 发送消息
WxCpMessageSendResult send = wxCpMessageService.send(wxCpMessage);
log.info("send result:{}", send);
} catch (WxErrorException e) {
log.error("发送信息接口调用出错: {}", e.getMessage());
}
}
}
测试
@RestController
public class MsgSendController {
@Resource
private SendWxCpMsg sendWxCpMsg;
@GetMapping("/send")
private void send() {
sendWxCpMsg.sendToWxCp("Xxxxxx.Xxx");
}
}
配置文件
wechat2:
corpid: xxxxxxx1
agentid: 1000002
secret: xxxxxxx2
redis-pwd:
spring:
data:
redis:
ssl:
enabled: false
host: 127.0.0.1
port: 6379
database: 0
# 超时时间
timeout: 1000
password:
lettuce:
pool:
# 连接池最大的连接数
max-active: 64
# 连接尝试分配阻塞时间
max-wait: 3s
# 连接池最小空闲连接数
min-idle: 0
# 连接池最大的空闲数
max-idle: 32
# 空闲连接线程释放周期时间
time-between-eviction-runs: 1s
配置文件中的参数来源
secret
corpid
agentid
执行
到这里,基本的配置已经OK,可以跑下代码。
异常
会报如下异常:
【错误信息】:错误代码:60020, 错误信息:访问ip不在白名单之中;请确认访问ip是否在服务商白名单IP列表,微信原始报文:{"errcode":60020,"errmsg":"not allow to access from your ip, hint: [170201673442683], from ip: xxx.7xx.xxx.10, more info at https://open.work.weixin.qq.com/devtool/query?e=60020"}
原因
没有配置可信域名
这里如何配置?
见官网:设置可信域名时,提示“检查域名所有权不通过”
注意:它这里必须要有域名可以公网访问,必须要企业微信能调通,不然不会通过的。
OK,也就是到这里,就终止了实验。
后面公司申请流程通过之后,拿到公司域名再来实验,bye。
文档
- 个人申请企业微信,进入工作台地址:企业微信工作台
- 有不清楚的术语可以参考企业微信开发者中心:企业微信开发者中心
- 可参考 https://blog.youkuaiyun.com/weixin_50495215/article/details/132903103?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-2-132903103-blog-122540462.235%5Ev39%5Epc_relevant_3m_sort_dl_base4&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-2-132903103-blog-122540462.235%5Ev39%5Epc_relevant_3m_sort_dl_base4&utm_relevant_index=5