ソースを参照

[update] message:1.给订阅用户发送消息功能

kelei 6 ヶ月 前
コミット
03f024377e

+ 2 - 0
admin/src/main/java/com/flyer/foster/AdminApplication.java

@@ -3,6 +3,7 @@ package com.flyer.foster;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 /**
  * com.flyer.foster.AdminApplication
@@ -11,6 +12,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
  * @since 2022/4/2/15:15
  */
 @SpringBootApplication(scanBasePackages = {"com.flyer"})
+@EnableScheduling
 public class AdminApplication {
     public static void main(String[] args) {
         SpringApplication.run(AdminApplication.class, args);

+ 2 - 0
admin/src/main/java/com/flyer/foster/consts/RedisKeyConst.java

@@ -8,4 +8,6 @@ package com.flyer.foster.consts;
  */
 public interface RedisKeyConst {
     String RSA_PRIVATE_KEY = "rsa:private_key:";
+
+    String WECHAT_ACCESS_TOKEN_KEY = "wechat:access_token_key:";
 }

+ 5 - 0
admin/src/main/java/com/flyer/foster/consts/WechatConst.java

@@ -16,4 +16,9 @@ public interface WechatConst {
      * 小程序秘钥
      */
     String APP_SECRET = "590f35a04adc9672618bbff4d6aa3468";
+
+    /**
+     * 订阅模板-留言通知
+     */
+    String SUBSCRIBE_TEMPLATE1 = "95DLTaGhZl7NG8eavp6QigtmOuaoefuxYnWW_Wu13Yg";
 }

+ 33 - 0
admin/src/main/java/com/flyer/foster/controller/app/AppSubscribeController.java

@@ -0,0 +1,33 @@
+package com.flyer.foster.controller.app;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.flyer.foster.dto.app.AppSubscribeAddDTO;
+import com.flyer.foster.entity.AppSubscribe;
+import com.flyer.foster.pojo.StpAppUtil;
+import com.flyer.foster.service.IAppSubscribeService;
+import com.flyer.util.R;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDateTime;
+
+/**
+ * 小程序订阅用户
+ *
+ * @author flyer
+ * @since 2024-05-17
+ */
+@RestController
+@RequestMapping("/app/subscribe")
+public class AppSubscribeController {
+    @Autowired
+    private IAppSubscribeService iAppSubscribeService;
+
+    @PostMapping("")
+    public R addSubscribe() {
+        return R.ok().result(iAppSubscribeService.addSubscribe());
+    }
+}

+ 6 - 3
admin/src/main/java/com/flyer/foster/controller/app/AppUserController.java

@@ -52,11 +52,14 @@ public class AppUserController {
         return R.ok().result(birdseyeToken);
     }
 
+    /**
+     * 给订阅用户发送消息
+     * @return
+     */
     @PostMapping("/send-sub-msg")
     public R sendSubscribeMessage() {
-        WxAccessToken wxAccessToken = WxAccessToken.getInstance(weChatApiUtil, WechatConst.APP_ID, WechatConst.APP_SECRET);
-        String accessToken = wxAccessToken.getAccess_token();
-        Map<String, Object> subMsg = weChatApiUtil.subscribeMessage(accessToken, "oiUkI7dJILmw43YavQ6SD9BuiDsU");
+        String accessToken = weChatApiUtil.getAccessToken(WechatConst.APP_ID, WechatConst.APP_SECRET);
+        weChatApiUtil.subscribeMessage(accessToken, "oiUkI7dJILmw43YavQ6SD9BuiDsU", WechatConst.SUBSCRIBE_TEMPLATE1);
         return R.ok();
     }
 }

+ 17 - 0
admin/src/main/java/com/flyer/foster/dto/app/AppSubscribeAddDTO.java

@@ -0,0 +1,17 @@
+package com.flyer.foster.dto.app;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * AppSubscribeAddDTO
+ *
+ * @author kelei
+ * @since 2024/5/17/9:18
+ */
+@Data
+public class AppSubscribeAddDTO {
+    @NotNull(message = "用户id不能为空")
+    private Integer appUserId;
+}

+ 13 - 0
admin/src/main/java/com/flyer/foster/dto/app/AppSubscribeQueryDTO.java

@@ -0,0 +1,13 @@
+package com.flyer.foster.dto.app;
+
+import lombok.Data;
+
+/**
+ * AppSubscribeAddDTO
+ *
+ * @author kelei
+ * @since 2024/5/17/9:18
+ */
+@Data
+public class AppSubscribeQueryDTO {
+}

+ 16 - 0
admin/src/main/java/com/flyer/foster/dto/app/AppSubscribeRespDTO.java

@@ -0,0 +1,16 @@
+package com.flyer.foster.dto.app;
+
+import lombok.Data;
+
+/**
+ * AppSubscribeAddDTO
+ *
+ * @author kelei
+ * @since 2024/5/17/9:18
+ */
+@Data
+public class AppSubscribeRespDTO {
+    private Integer appUserId;
+
+    private String openId;
+}

+ 93 - 0
admin/src/main/java/com/flyer/foster/entity/AppSubscribe.java

@@ -0,0 +1,93 @@
+package com.flyer.foster.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.Version;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 小程序订阅用户
+ * </p>
+ *
+ * @author flyer
+ * @since 2024-05-17
+ */
+@Getter
+@Setter
+@TableName("tb_app_subscribe")
+public class AppSubscribe implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 小程序用户主键id
+     */
+    private Integer appUserId;
+
+    /**
+     * 订阅次数
+     */
+    private Integer count;
+
+    /**
+     * 状态-{0.不可用 1.可用}
+     */
+    private Integer status;
+
+    /**
+     * 租户号
+     */
+    private Integer tenantId;
+
+    /**
+     * 乐观锁
+     */
+    @Version
+    private Integer version;
+
+    /**
+     * 是否删除
+     */
+    @TableLogic
+    private Integer isDeleted;
+
+    /**
+     * 创建人
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private String createdBy;
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createdTime;
+
+    /**
+     * 更新人
+     */
+    @TableField(fill = FieldFill.UPDATE)
+    private String updatedBy;
+
+    /**
+     * 更新时间
+     */
+    @TableField(fill = FieldFill.UPDATE)
+    private LocalDateTime updatedTime;
+
+
+}

+ 20 - 0
admin/src/main/java/com/flyer/foster/mapper/IAppSubscribeMapper.java

@@ -0,0 +1,20 @@
+package com.flyer.foster.mapper;
+
+import com.flyer.foster.dto.app.AppSubscribeRespDTO;
+import com.flyer.foster.entity.AppSubscribe;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 小程序订阅用户 Mapper 接口
+ * </p>
+ *
+ * @author flyer
+ * @since 2024-05-17
+ */
+public interface IAppSubscribeMapper extends BaseMapper<AppSubscribe> {
+
+    List<AppSubscribeRespDTO> getSubscribeUserInfo();
+}

+ 44 - 0
admin/src/main/java/com/flyer/foster/schedule/SendSubscribeMsgSchedule.java

@@ -0,0 +1,44 @@
+package com.flyer.foster.schedule;
+
+import com.flyer.foster.consts.WechatConst;
+import com.flyer.foster.dto.app.AppSubscribeRespDTO;
+import com.flyer.foster.service.IAppSubscribeService;
+import com.flyer.foster.util.WeChatApiUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * SendSubscribeMsg
+ *
+ * @author kelei
+ * @since 2024/5/17/9:24
+ */
+@Slf4j
+@Component
+public class SendSubscribeMsgSchedule {
+    @Autowired
+    private IAppSubscribeService iAppSubscribeService;
+
+    @Autowired
+    private WeChatApiUtil weChatApiUtil;
+
+    /**
+     * 每天上午9点、晚上9点发送订阅消息
+     */
+//    @Scheduled(cron = "0 0 9,21 * * ?")
+    public void execute() {
+        log.info("begin SendSubscribeMsgSchedule--->execute");
+        String accessToken = weChatApiUtil.getAccessToken(WechatConst.APP_ID, WechatConst.APP_SECRET);
+
+        // 获取已经订阅用户信息
+        List<AppSubscribeRespDTO> subscribeUserInfoList = iAppSubscribeService.getSubscribeUserInfo();
+        for (AppSubscribeRespDTO respDTO : subscribeUserInfoList) {
+            // 给订阅用户发消息
+            weChatApiUtil.subscribeMessage(accessToken, respDTO.getOpenId(), WechatConst.SUBSCRIBE_TEMPLATE1);
+        }
+        log.info("end SendSubscribeMsgSchedule--->execute");
+    }
+}

+ 22 - 0
admin/src/main/java/com/flyer/foster/service/IAppSubscribeService.java

@@ -0,0 +1,22 @@
+package com.flyer.foster.service;
+
+import com.flyer.foster.dto.app.AppSubscribeRespDTO;
+import com.flyer.foster.entity.AppSubscribe;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 小程序订阅用户 服务类
+ * </p>
+ *
+ * @author flyer
+ * @since 2024-05-17
+ */
+public interface IAppSubscribeService extends IService<AppSubscribe> {
+
+    List<AppSubscribeRespDTO> getSubscribeUserInfo();
+
+    boolean addSubscribe();
+}

+ 52 - 0
admin/src/main/java/com/flyer/foster/service/impl/AppSubscribeServiceImpl.java

@@ -0,0 +1,52 @@
+package com.flyer.foster.service.impl;
+
+import com.flyer.foster.dto.app.AppSubscribeRespDTO;
+import com.flyer.foster.entity.AppSubscribe;
+import com.flyer.foster.mapper.IAppSubscribeMapper;
+import com.flyer.foster.pojo.StpAppUtil;
+import com.flyer.foster.service.IAppSubscribeService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ * 小程序订阅用户 服务实现类
+ * </p>
+ *
+ * @author flyer
+ * @since 2024-05-17
+ */
+@Service
+public class AppSubscribeServiceImpl extends ServiceImpl<IAppSubscribeMapper, AppSubscribe> implements IAppSubscribeService {
+
+    @Override
+    public List<AppSubscribeRespDTO> getSubscribeUserInfo() {
+        return baseMapper.getSubscribeUserInfo();
+    }
+
+    @Override
+    public boolean addSubscribe() {
+        int tenantId = StpAppUtil.getTokenSession().getInt("tenantId");
+        int appUserId = StpAppUtil.getLoginIdAsInt();
+        AppSubscribe appSubscribe = this.lambdaQuery().eq(AppSubscribe::getAppUserId, appUserId).one();
+        if (appSubscribe == null) {
+            appSubscribe = new AppSubscribe();
+            appSubscribe.setAppUserId(appUserId);
+            appSubscribe.setTenantId(tenantId);
+            appSubscribe.setCreatedBy("kelei");
+            appSubscribe.setCreatedTime(LocalDateTime.now());
+            appSubscribe.setUpdatedBy("kelei");
+            appSubscribe.setUpdatedTime(LocalDateTime.now());
+            return this.save(appSubscribe);
+        } else {
+            Integer count = appSubscribe.getCount();
+            return this.lambdaUpdate()
+                    .set(AppSubscribe::getCount, ++count)
+                    .eq(AppSubscribe::getId, appSubscribe.getId())
+                    .update();
+        }
+    }
+}

+ 3 - 1
admin/src/main/java/com/flyer/foster/service/impl/TreePosterContentServiceImpl.java

@@ -76,8 +76,10 @@ public class TreePosterContentServiceImpl extends ServiceImpl<ITreePosterContent
                 treePosterContent.setStatus(1);
                 // 新增
                 this.save(treePosterContent);
+                BeanUtil.copyProperties(treePosterContent, respDTO);
+            } else {
+                BeanUtil.copyProperties(treePosterContentList.get(0), respDTO);
             }
-            BeanUtil.copyProperties(treePosterContentList, respDTO);
         }
         return respDTO;
     }

+ 53 - 8
admin/src/main/java/com/flyer/foster/util/WeChatApiUtil.java

@@ -3,8 +3,11 @@ package com.flyer.foster.util;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.flyer.exception.BusinessException;
+import com.flyer.foster.consts.RedisKeyConst;
+import com.flyer.foster.consts.WechatConst;
 import com.flyer.foster.converter.WxMappingJackson2HttpMessageConverter;
 import com.flyer.foster.pojo.Code2SessionResp;
+import com.flyer.util.RedisUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpEntity;
@@ -16,6 +19,7 @@ import org.springframework.web.client.RestTemplate;
 
 import javax.annotation.PostConstruct;
 import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.util.HashMap;
 import java.util.Map;
@@ -29,6 +33,9 @@ public class WeChatApiUtil {
     @Autowired
     private RestTemplate restTemplate;
 
+    @Autowired
+    private RedisUtil redisUtil;
+
     @PostConstruct
     public void initRestTemplate() {
         // 解决调用微信服务返回信息无法反序列化
@@ -63,8 +70,7 @@ public class WeChatApiUtil {
      *
      * @return
      */
-    public JSONObject getAccessToken(String appId, String secret) {
-        String accessToken = null;
+    public JSONObject getAccessToken1(String appId, String secret) {
         String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type={grantType}&appid={appId}&secret={secret}";
         HashMap<String, String> map = new HashMap<>();
         map.put("grantType", "client_credential");
@@ -80,6 +86,33 @@ public class WeChatApiUtil {
         return res;
     }
 
+    public String getAccessToken(String appId, String secret) {
+        // 从缓存中获取accessToken
+        Object accessTokenCache = redisUtil.get(RedisKeyConst.WECHAT_ACCESS_TOKEN_KEY + appId);
+        if (accessTokenCache == null) {
+            String accessToken;
+            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type={grantType}&appid={appId}&secret={secret}";
+            HashMap<String, String> map = new HashMap<>();
+            map.put("grantType", "client_credential");
+            map.put("appId", appId);
+            map.put("secret", secret);
+            JSONObject res;
+            try {
+                res = restTemplate.getForObject(url, JSONObject.class, map);
+                // token失效时间
+                Integer expires = res.getInteger("expires_in");
+                long cacheExpires = LocalDateTime.now().plusSeconds(expires - 100).toEpochSecond(ZoneOffset.of("+8"));
+                accessToken = res.getString("access_token");
+                redisUtil.set(RedisKeyConst.WECHAT_ACCESS_TOKEN_KEY + appId, accessToken, cacheExpires);
+            } catch (RestClientException e) {
+                throw new BusinessException("调用微信服务错误");
+            }
+            return accessToken;
+        } else {
+            return accessTokenCache.toString();
+        }
+    }
+
     //    public String getPhoneNumber(String appId, String appSecret, String code) {
 //        String accessToken = this.getAccessToken(appId, appSecret);
 //        String tel = "";
@@ -101,7 +134,15 @@ public class WeChatApiUtil {
 //        return tel;
 //    }
 
-    public Map<String, Object> subscribeMessage(String accessToken, String openId) {
+    /**
+     * 给订阅用户发送消息
+     *
+     * @param accessToken
+     * @param openId
+     * @param templateId  消息模板id
+     * @return
+     */
+    public Map<String, Object> subscribeMessage(String accessToken, String openId, String templateId) {
         // 首先获取微信调用凭证
         if (null == accessToken) {
             throw new BusinessException("获取微信调用凭证失败");
@@ -109,15 +150,19 @@ public class WeChatApiUtil {
         String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken;
 
         HashMap<String, Object> param = new HashMap<>();
-        param.put("template_id", "4HrnTVgSa1LU1m5a-2ySDz_1N6Ndi3WnkV6rRsV3ROM");
+        param.put("template_id", templateId);
         param.put("touser", openId);
         param.put("page", "pages/subPages/login/index");
-        JSONObject data = new JSONObject();
-        data.put("thing1", setVal("value", "test12399"));
-        data.put("date2", setVal("value", "2024年10月1日"));
-        param.put("data", data);
         param.put("miniprogram_state", "trial");
+        JSONObject data = new JSONObject();
+        // 留言内容
+        data.put("thing2", setVal("value", "test12399"));
+        // 留言人
+        data.put("name1", setVal("value", "守护一棵树"));
+        // 留言时间
+        data.put("time3", setVal("value", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))));
 
+        param.put("data", data);
         try {
             // 微信调用返回结果
             JSONObject res = restTemplate.postForObject(url, param, JSONObject.class);

+ 1 - 1
admin/src/main/java/com/flyer/foster/util/WxAccessToken.java

@@ -18,7 +18,7 @@ public class WxAccessToken {
         if(instance != null && !instance.isExpires()){
             return instance;
         }
-        JSONObject obj= weChatApiUtil.getAccessToken(appId, secret);
+        JSONObject obj= weChatApiUtil.getAccessToken1(appId, secret);
         instance = new WxAccessToken();
         instance.expires_in = obj.getInteger("expires_in");
         instance.expires = LocalDateTime.now().plusSeconds(instance.expires_in - 100);

+ 11 - 0
admin/src/main/resources/mapper/AppSubscribeMapper.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.flyer.foster.mapper.IAppSubscribeMapper">
+
+    <select id="getSubscribeUserInfo" resultType="com.flyer.foster.dto.app.AppSubscribeRespDTO">
+        select a.app_user_id as appUserId,
+               b.open_id     as openId
+        from tb_app_subscribe a
+                 inner join tb_app_user b on a.app_user_id = b.id
+    </select>
+</mapper>

+ 17 - 0
admin/src/main/resources/mysql/ddl/init-table.sql

@@ -289,3 +289,20 @@ create table tb_whisper
 )
     comment '悄悄话';
 
+create table tb_app_subscribe
+(
+    id           int auto_increment comment '主键'
+        primary key,
+    app_user_id  int default 0 null comment '小程序用户主键id',
+    count        int default 1 null comment '订阅次数',
+    status       int default 1 null comment '状态-{0.不可用 1.可用}',
+    tenant_id    int default 0 null comment '租户号',
+    version      int default 1 null comment '乐观锁',
+    is_deleted   int default 0 null comment '是否删除',
+    created_by   varchar(32)   null comment '创建人',
+    created_time datetime      null comment '创建时间',
+    updated_by   varchar(32)   null comment '更新人',
+    updated_time datetime      null comment '更新时间'
+)
+    comment '小程序订阅用户';
+