微信公众号自动提取资源
案例目录
《微信公众号自动提取资源系统》
1、准备工作、项目搭建、服务器校验
2、接收公众号消息、指定回复内容、配置公众号自动回复
3、接入数据库,查询数据库的资源、使用客服消息
项目技术栈介绍
后端:Springboot+mybatisPlus
jdk:1.8以上
数据库:mysql
源码获取地址(以上前后端代码已经全部打包好了)
https://gitee.com/xuxiaofei1996/case-source-code.git
为了方便大家更好的学习,本平台经常分享一些完整的单个功能案例代码给大家去练习,如果本平台没有你要学习的功能案例,你可以联系小编,提供你的小需求给我,我安排我们这边的开发团队免费帮你完成你的案例。
接入数据库,查询数据库的资源
创建库和表
创建wx_remind_data数据库,然后创建resource表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for resource
-- ----------------------------
DROP TABLE IF EXISTS `resource`;
CREATE TABLE `resource` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`code` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '资源提取码',
`description` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '资源描述',
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '资源地址',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
表结构如下
项目配置编写
引入mysql、mybatisPlus的依赖
pom.xml
<!--mybatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--mysql连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
修改配置
application.properties
# DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://xx.xx.xx.xx:3306/wx_remind_data?serverTimezone=GMT%2B8
useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=xxx
# MybatisPlus
#指定mapper中xml文件的位置
mybatis-plus.mapper-locations = classpath*:mapper/xml/*.xml
#mybatis中pojo的位置
mybatis-plus.type-aliases-package = com.demo.model
#mybatis的主键策略
mybatis-plus.global-config.db-config.id-type =auto
#打印Sql
mybatis-plus.configuration.log-impl = org.apache.ibatis.logging.stdout.StdOutImpl
项目接口编写
ResourceController.java
@RestController
@Api(tags = {""})
@RequestMapping("/resource")
public class ResourceController {
@Autowired
private ResourceService resourceService;
@RequestMapping(value = "/queryOne", method = RequestMethod.POST)
@ApiOperation("获取资源")
public ResultData<ResourceVO> queryOne(@RequestBody ResourceQueryDTO dto) {
ResultData<ResourceVO> resultData = resourceService.queryOne(dto);
return resultData;
}
@RequestMapping(value = "/add", method = RequestMethod.POST)
@ApiOperation("新增")
public ResultData<String> add(@RequestBody @Valid ResourceInsertDTO dto, BindingResult bindingResult) throws IOException {
if(bindingResult.hasErrors()){
return ResultData.error(bindingResult.getFieldError().getDefaultMessage());
}
return resourceService.add(dto);
}
}
ResourceService.java
public interface ResourceService {
// 获取列表
ResultData<ResourceVO> queryOne(ResourceQueryDTO dto);
// 新增
ResultData<String> add(ResourceInsertDTO dto);
}
ResourceServiceImpl.java
@Service
public class ResourceServiceImpl extends ServiceImpl<ResourceMapper, Resource> implements ResourceService {
@Override
public ResultData<ResourceVO> queryOne(ResourceQueryDTO dto) {
Resource resource = BeanUtils.copyProperties(dto, Resource.class);
QueryWrapper<Resource> queryWrapper = new QueryWrapper<>(resource);
Resource resultData = this.getOne(queryWrapper);
return ResultData.success("查询成功",BeanUtils.copyProperties(resultData, ResourceVO.class));
}
@Override
public ResultData<String> add(ResourceInsertDTO dto) {
Boolean result = this.save(BeanUtils.copyProperties(dto,Resource.class));
if (!result) {
return ResultData.error("添加失败!");
}
return ResultData.success("添加成功!");
}
}
接口测试
测试添加
测试查询
将查询加入代码中
根据用户发送的代码查询数据库中的数据进行回复
WxMessageController.java
@RequestMapping(value="/urlR",method= RequestMethod.POST)
public void index(@RequestBody String xml,HttpServletResponse resp) throws IOException {
//将接收到的xml消息转为map
Map<String, Object> stringObjectMap = XmlUtil.xmlToMap(xml);
//将map转为标准的message对象
Message message = BeanUtil.fillBeanWithMap(stringObjectMap, new Message(), false);
log.info("接收到微信发来的消息:{}",message);
//返回的内容
String resStr = "";
if("event".equals(message.getMsgType())){
resStr = "Hello!欢迎关注我。\n我是您的网盘资源获取小助手\n" +
"通过查看公众号往期文章来获取资源的提取码\n" +
"公众号置顶,避免错过重要消息哦!";
}
if(!StringUtils.isEmpty(message.getContent())){
ResourceQueryDTO resourceQueryDTO = new ResourceQueryDTO();
resourceQueryDTO.setCode(message.getContent());
ResultData<ResourceVO> resultData = resourceService.queryOne(resourceQueryDTO);
if(resultData.isFail()){
resStr = "查询失败!";
}else {
if(StringUtils.isEmpty(resultData.getData().getUrl())){
resStr = "未查询到对应提取码的资源!";
}else {
resStr = resultData.getData().getDescription()+"的百度网盘链接:"+resultData.getData().getUrl();
}
}
}
//构建返回的map
Map<Object, Object> resMap = new HashMap<>();
resMap.put("ToUserName",message.getFromUserName());
resMap.put("FromUserName",message.getToUserName());
resMap.put("CreateTime",System.currentTimeMillis());
resMap.put("MsgType","text");
resMap.put("Content",resStr);
//因处理消息的时间可能在5秒之外,所以这里回复空串,使用客服消息接口回复消息即可
resp.setContentType("text/xml;charset=UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(XmlUtil.mapToXmlStr(resMap,"xml"));
}
最终测试
测试结果
客服消息
客服消息介绍
上面的功能已经基本实现,但是根据微信官方文档的要求,如果使用上文使用的被动回复的消息,在用户关注或发送消息时,只能回复一条消息。并且如果5秒内收不到回复,就会发起重试。所以,如果我们希望可以回复多条信息或者我们要处理的逻辑在5秒内无法保证处理完毕,这时候我们就需要使用客服消息接口。
客服消息接口需要单独获得,只有经过微信认证的用户才可以使用客服消息接口。具体微信认证的步骤这里不做详细赘述。
根据微信公众号官方文档的描述,在请求发送客服消息之前,我们首先需要获取accessToken
获取accessToken
这里分别填写上面的两个内容
application.properties
# 公众号配置(必填)
wx.appId = 你的appId
wx.secret = 你的secret
获取代码编写
KefuMessage.java
@Configuration
@Slf4j
public class KefuMessage {
@Value("${wx.appId}")
private String appid;
@Value("${wx.secret}")
private String secret;
//获取accessToken
public ResultData<String> getAccessToken(){
String url = "https://api.weixin.qq.com/cgi-bin/token";
Map<String,Object> param = new HashMap<>();
param.put("grant_type","client_credential");
param.put("appid",appid);
param.put("secret",secret);
String result = HttpUtil.get(url+"?"+HttpUtil.toParams(param));
Map<Object, Object> map = JSONObject.parseObject(result, Map.class);
String access_token = (String)map.get("access_token");
if(StringUtils.isEmpty(access_token)){
log.error("获取accessToken失败:{}",result);
return ResultData.error(result);
}
log.info("获取到accessToken:{}",access_token);
return ResultData.success("",access_token);
}
}
获取accessToken测试
调用accessToken后,发现提示errcode:40164,也就是请求不允许的意思。这时,我们需要去配置白名单。在设置了AppSecret后,就可以配置白名单了,将你花生壳下面的地址和你电脑的本机地址都填进去,然后保存即可。
再次请求接口,正常获取accessToken。
添加过滤器
因为我们需要在关注公众号或给公众号发送消息时发送客服消息,那么我们就需要时时刻刻都有accessToken。所以我们将获取accessToken的方法加在过滤器中,每一次请求都会去尝试获取accessToken。accessToken有两个小时的有效期,并且一天只能调用20次,每调用一次,都会产生一个新的accessToken,所以我们需要在还有5分钟过期时再获取新的accessToken
引入Redis的依赖和配置
application.properties
#Redis的配置
spring.redis.host=xx.xx.xx.xx
spring.redis.port=6379
spring.redis.password=xxx
spring.redis.database=0
pom.xml
<!--springboot-redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
TokenInterceptor.java
@Component
@Slf4j
public class TokenInterceptor extends HandlerInterceptorAdapter {
@Autowired
private RedisUtil redisUtil;
@Autowired
private KefuMessage kefuMessage;
private static final String key ="wx:accessToken";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String accessToken = redisUtil.get(key);
if(StringUtils.isEmpty(accessToken)){
ResultData<String> resultData = kefuMessage.getAccessToken();
if(resultData.isSuccess()){
redisUtil.setEx(key,resultData.getData(),2, TimeUnit.HOURS);
}
return true;
}
//5分钟的秒
Long fiveMinute = 60*5L;
//剩余过期时间的秒
Long expire = redisUtil.getExpire(key);
if(expire < fiveMinute){
log.info("accessToken的过期时间不到5分钟,已刷新");
redisUtil.setEx(key,kefuMessage.getAccessToken().getData(),2, TimeUnit.HOURS);
}
return true;
}
}
WebConfig.java
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor);
}
}
添加发送客服消息的方法
KefuMessage.java
@Configuration
@Slf4j
public class KefuMessage {
@Autowired
private RedisUtil redisUtil;
public ResultData<WxResult> sendKFMessage(KFMessageSendDto kfMessageSendDto) throws IOException, URISyntaxException
{
if(StringUtils.isEmpty(kfMessageSendDto.getContent())){
return ResultData.error("发送的内容不可为空!");
}
if(StringUtils.isEmpty(kfMessageSendDto.getFromUserName())){
return ResultData.error("发送人不可为空!");
}
if(StringUtils.isEmpty(kfMessageSendDto.getToUserName())){
return ResultData.error("收信人不可为空!");
}
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
Map<String, Object> params = new HashMap<>();
params.put("touser",kfMessageSendDto.getToUserName());
params.put("msgtype",kfMessageSendDto.getMsgType());
params.put("text", MapUtil.of("content",kfMessageSendDto.getContent()));
Map<String, String> urlParam = new HashMap<>();
urlParam.put("access_token",redisUtil.get("wx:accessToken"));
String result = HttpUtil.post(url+"?"+HttpUtil.toParams(urlParam),JSON.toJSONString(params));
WxResult wxResult = JSONObject.parseObject(result, WxResult.class);
if(wxResult.getErrcode() != 0){
return ResultData.error(wxResult);
}
return ResultData.success();
}
}
将获取消息的接口改造成如下,在获取到关注事件后,会发送两条客服消息
WxMessageController.java
@RequestMapping(value="/urlR",method= RequestMethod.POST)
public void index(@RequestBody String xml,HttpServletResponse resp) throws IOException, URISyntaxException {
//将接收到的xml消息转为map
Map<String, Object> stringObjectMap = XmlUtil.xmlToMap(xml);
//将map转为标准的message对象
Message message = BeanUtil.fillBeanWithMap(stringObjectMap, new Message(), false);
log.info("接收到微信发来的消息:{}",message);
//返回的内容
String resStr = "";
if("event".equals(message.getMsgType())){
resStr = "Hello!欢迎关注我。\n我是您的网盘资源获取小助手\n" +
"通过查看公众号往期文章来获取资源的提取码\n" +
"公众号置顶,避免错过重要消息哦!";
KFMessageSendDto kfMessageSendDto = new KFMessageSendDto();
kfMessageSendDto.setToUserName(message.getFromUserName());
kfMessageSendDto.setFromUserName(message.getToUserName());
kfMessageSendDto.setContent(resStr);
kefuMessage.sendKFMessage(kfMessageSendDto);
kfMessageSendDto.setContent("第二条");
kefuMessage.sendKFMessage(kfMessageSendDto);
return;
}
if(!StringUtils.isEmpty(message.getContent())){
ResourceQueryDTO resourceQueryDTO = new ResourceQueryDTO();
resourceQueryDTO.setCode(message.getContent());
ResultData<ResourceVO> resultData = resourceService.queryOne(resourceQueryDTO);
if(resultData.isFail()){
resStr = "查询失败!";
}else {
if(StringUtils.isEmpty(resultData.getData().getUrl())){
resStr = "未查询到对应提取码的资源!";
}else {
resStr = resultData.getData().getDescription()+"的百度网盘链接:"+resultData.getData().getUrl();
}
}
}
//构建返回的map
Map<Object, Object> resMap = new HashMap<>();
resMap.put("ToUserName",message.getFromUserName());
resMap.put("FromUserName",message.getToUserName());
resMap.put("CreateTime",System.currentTimeMillis());
resMap.put("MsgType","text");
resMap.put("Content",resStr);
//因处理消息的时间可能在5秒之外,所以这里回复空串,使用客服消息接口回复消息即可
resp.setContentType("text/xml;charset=UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(XmlUtil.mapToXmlStr(resMap,"xml"));
}
关注后的效果如下图,成功实现!
源码获取地址(以上前后端代码已经全部打包好了)
https://gitee.com/xuxiaofei1996/case-source-code.git
为了方便大家更好的学习,本平台经常分享一些完整的单个功能案例代码给大家去练习,如果本平台没有你要学习的功能案例,你可以联系小编,提供你的小需求给我,我安排我们这边的开发团队免费帮你完成你的案例。