新增小程序消息订阅模板功能回调和批次任务

This commit is contained in:
liyj 2025-09-19 16:03:13 +08:00
parent 4563779ce3
commit a5871fb9b0
12 changed files with 235 additions and 28 deletions

View File

@ -477,5 +477,18 @@ public class AccountController {
return accountUserBaseService.getOne(queryWrapper);
}
@RequestMapping(value = "/getAllBindPage", method = RequestMethod.GET)
public List<AccountUserBindConnect> getAllBindPage(@RequestParam(name = "bind_type") Integer bind_type,
@RequestParam(name = "bindTmpl") String bindTmpl,
@RequestParam(name = "pageNum", defaultValue = "1") Integer pageNum,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
return accountUserBindConnectService.getAllBindPage(bind_type,bindTmpl,pageNum,pageSize);
}
@RequestMapping(value = "/getAllBindCount", method = RequestMethod.GET)
public long getAllBindCount(@RequestParam(name = "bind_type") Integer bind_type,
@RequestParam(name = "bindTmpl") String bindTmpl) {
return accountUserBindConnectService.getAllBindCount(bind_type,bindTmpl);
}
}

View File

@ -1,5 +1,7 @@
package com.suisung.mall.account.controller.mobile;
import cn.hutool.json.JSONObject;
import com.suisung.mall.account.service.AccountUserBindConnectService;
import com.suisung.mall.account.service.WeiXinService;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.service.impl.BaseControllerImpl;
@ -7,10 +9,7 @@ import com.suisung.mall.common.utils.I18nUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -24,6 +23,9 @@ public class WeiXinController extends BaseControllerImpl {
@Autowired
private WeiXinService weiXinService;
@Autowired
private AccountUserBindConnectService accountUserBindConnectService;
/**
* <pre>
* 验证推送过来的消息的正确性
@ -138,4 +140,19 @@ public class WeiXinController extends BaseControllerImpl {
weiXinService.callbackPc(code, response);
}
@ApiOperation(value = "小程序回调", notes = "小程序回调")
@RequestMapping(value = "/xcxCallBack")
public CommonResult xcxCallBack(@RequestBody JSONObject jsonObject,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce) {
boolean checked=weiXinService.checkSignature(timestamp, nonce, signature);
if(!checked){
return CommonResult.failed("校验失败");
}
accountUserBindConnectService.bindTmplId(jsonObject);
return CommonResult.success("小程序订阅消息模板绑定成功");
}
}

View File

@ -1,9 +1,10 @@
package com.suisung.mall.account.service;
import cn.hutool.json.JSONObject;
import com.suisung.mall.common.modules.account.AccountUserBindConnect;
import com.suisung.mall.common.pojo.req.WxUserInfoReq;
import com.suisung.mall.core.web.service.IBaseService;
import java.util.List;
import java.util.Map;
/**
@ -87,4 +88,10 @@ public interface AccountUserBindConnectService extends IBaseService<AccountUserB
* @return
*/
AccountUserBindConnect initAccountUserBindConnect(String bindId, Integer bindType, Integer userId, Integer userType);
List<AccountUserBindConnect> getAllBindPage(Integer bind_type,String bindTmpl,Integer pageNum, Integer pageSize);
long getAllBindCount(Integer bind_type,String bindTmpl);
boolean bindTmplId(JSONObject jsonObject);
}

View File

@ -3,6 +3,8 @@ package com.suisung.mall.account.service.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.account.mapper.AccountUserBindConnectMapper;
import com.suisung.mall.account.service.AccountUserBaseService;
@ -25,10 +27,7 @@ import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.*;
/**
@ -501,4 +500,56 @@ public class AccountUserBindConnectServiceImpl extends BaseServiceImpl<AccountUs
return null;
}
}
@Override
public List<AccountUserBindConnect> getAllBindPage(Integer bind_type,String bindTmpl,Integer pageNum, Integer pageSize) {
QueryWrapper<AccountUserBindConnect> queryWrapper = new QueryWrapper<>();
queryWrapper.select("bind_id,bind_type,user_id,user_type");
queryWrapper.eq("bind_type", bind_type)
.eq("user_type", CommonConstant.USER_TYPE_NORMAL)
.eq("bind_active", CommonConstant.Enable)
.eq("bind_tmpl",bindTmpl)
.orderByAsc("bind_time");
return this.lists(queryWrapper,pageNum,pageSize).getRecords();
}
@Override
public long getAllBindCount(Integer bind_type,String bindTmpl) {
QueryWrapper<AccountUserBindConnect> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("bind_type", bind_type)
.eq("user_type", CommonConstant.USER_TYPE_NORMAL)
.eq("bind_active", CommonConstant.Enable)
.eq("bind_tmpl",bindTmpl)
.orderByAsc("bind_time");
return this.count(queryWrapper);
}
@Override
public boolean bindTmplId(JSONObject jsonObject) {
if (jsonObject == null) {
return false;
}
String openId=jsonObject.getStr("FromUserName");//用户openid
QueryWrapper<AccountUserBindConnect> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("bind_id",openId)
.eq("user_type", CommonConstant.USER_TYPE_NORMAL)
.eq("bind_type",15)
.eq("bind_active", CommonConstant.Enable);
AccountUserBindConnect accountUserBindConnect= findOne(queryWrapper);
if (accountUserBindConnect != null) {
JSONArray jsonArray= jsonObject.getJSONArray("List");
if (jsonArray != null) {
JSONObject object= (JSONObject) jsonArray.get(0);
String templateId= object.getStr("TemplateId");//模板id
String SubscribeStatusString= object.getStr("SubscribeStatusString");//订阅结果accept接收reject拒收参考地址https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message.html#%E8%AE%A2%E9%98%85%E6%B6%88%E6%81%AF%E8%AF%AD%E9%9F%B3%E6%8F%90%E9%86%92
if(SubscribeStatusString.equals("accept")){
accountUserBindConnect.setBind_tmpl(templateId);
updateById(accountUserBindConnect);
}
}
}
return true;
}
}

View File

@ -294,4 +294,14 @@ public interface AccountService {
*/
@PostMapping(value = "/admin/account/account-base-config/get/value")
String getAccountBaseConfigValue(@RequestParam(name = "config_key") String config_key);
@GetMapping(value = "/admin/account/accountController/getAllBindPage")
List<AccountUserBindConnect> getAllBindPage(@RequestParam(name = "bind_type") Integer bind_type,
@RequestParam(name = "bindTmpl") String bindTmpl,
@RequestParam(name = "pageNum", defaultValue = "1") Integer pageNum,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize);
@GetMapping(value = "/admin/account/accountController/getAllBindCount")
long getAllBindCount(@RequestParam(name = "bind_type") Integer bind_type,@RequestParam(name = "bindTmpl") String bindTmpl);
}

View File

@ -105,4 +105,7 @@ public class AccountUserBindConnect implements Serializable {
@ApiModelProperty(value = "更新时间")
private Date updated_at;
@ApiModelProperty(value = "允许通知的消息通知模板id")
private String bind_tmpl;
}

View File

@ -0,0 +1,26 @@
package com.suisung.mall.shop.components.quartz.job;
import com.suisung.mall.shop.config.SpringUtil;
import com.suisung.mall.shop.message.service.ShopMessageTemplateService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;
/**
* 小程序订阅号消息发送 定时任务
*/
public class XcxSubSendMessageJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Logger logger = LoggerFactory.getLogger(UpdateVoucherStatusJob.class);
ShopMessageTemplateService shopMessageTemplateService = SpringUtil.getBean(ShopMessageTemplateService.class);
logger.info("小程序订阅号消息发送消息开始--------start");
shopMessageTemplateService.sendToXcxAllUserMessage();
logger.info("小程序订阅号消息发送消息结束-------end");
}
}

View File

@ -96,12 +96,22 @@ public class ShopMessageTemplateController extends BaseControllerImpl {
* 发送消息推送
* @return
*/
@ApiOperation(value = "消息模板表-编辑", notes = "消息模板表-编辑")
@ApiOperation(value = "发送消息推送", notes = "发送消息推送")
@RequestMapping(value = "/sendToXcxUserMessage", method = RequestMethod.POST)
public CommonResult sendToXcxUserMessage(@RequestParam(name = "userId") Integer userId) {
return shopMessageTemplateService.sendToXcxUserMessage(userId);
}
/**
* 发送消息推送给所有用户
* @return
*/
@ApiOperation(value = "小程序消息推送", notes = "小程序消息推送")
@RequestMapping(value = "/sendToXcxAllUserMessage", method = RequestMethod.POST)
public CommonResult sendToXcxAllUserMessage() {
shopMessageTemplateService.sendToXcxAllUserMessage();
return CommonResult.success();
}
}

View File

@ -45,4 +45,6 @@ public interface ShopMessageTemplateService extends IBaseService<ShopMessageTemp
Integer aliyunSmsSend(List<String> mobiles, String tmplCode, Map<String,Object> tmplParams);
CommonResult sendToXcxUserMessage(Integer user_id);
void sendToXcxAllUserMessage();
}

View File

@ -23,6 +23,7 @@ import com.suisung.mall.common.feignService.AccountService;
import com.suisung.mall.common.feignService.SnsService;
import com.suisung.mall.common.modules.account.AccountBaseConfig;
import com.suisung.mall.common.modules.account.AccountUserBase;
import com.suisung.mall.common.modules.account.AccountUserBindConnect;
import com.suisung.mall.common.modules.account.AccountUserLogin;
import com.suisung.mall.common.modules.message.ShopMessageTemplate;
import com.suisung.mall.common.modules.store.ShopStoreBase;
@ -42,6 +43,7 @@ import com.suisung.mall.shop.message.mapper.ShopMessageTemplateMapper;
import com.suisung.mall.shop.message.service.ShopMessageTemplateService;
import com.suisung.mall.shop.message.vo.WxTelMsgPushVo;
import com.suisung.mall.shop.message.vo.WxXcxMsgPushVo;
import com.suisung.mall.shop.sixun.utils.CommonUtil;
import com.suisung.mall.shop.store.service.ShopStoreBaseService;
import com.suisung.mall.shop.wechat.service.ShopWechatTplmsgService;
import lombok.extern.slf4j.Slf4j;
@ -55,10 +57,10 @@ import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import static com.suisung.mall.common.utils.I18nUtil._;
@ -96,6 +98,9 @@ public class ShopMessageTemplateServiceImpl extends BaseServiceImpl<ShopMessageT
@Autowired
private Environment environment;
// 批处理阈值
private static final int BATCH_SIZE = 30;
public String dealMessageTemplate(String message_content, Map args) {
StringSubstitutor sub = new StringSubstitutor(args);
@ -671,7 +676,7 @@ public class ShopMessageTemplateServiceImpl extends BaseServiceImpl<ShopMessageT
@Override
public CommonResult sendToXcxUserMessage(Integer user_id) {
Map bind_row = accountService.getBind(user_id, BindCode.WEIXIN_XCX);
Map bind_row = accountService.getBind(user_id, BindCode.MOBILE);
if(bind_row == null){
return CommonResult.failed("账号不存在");
}
@ -696,6 +701,8 @@ public class ShopMessageTemplateServiceImpl extends BaseServiceImpl<ShopMessageT
ShopStoreBase shopStoreBase= shopStoreBaseService.get(wechatTplmsg.getStore_id());
args.put("storeName", shopStoreBase.getStore_name());
if (wechatTplmsg != null) {
Map timeArgs=getActiveTime();
args.putAll(timeArgs);
String wechatTplData = getXcxWechatTplData(open_id, wechatTplmsg, args);
log.info(wechatTplData);
String result = WxHttpUtil.request(WxHttpUtil.MethodType.POST, WxHttpUtil.WxType.XCX, accessToken, url, null, wechatTplData);
@ -711,6 +718,74 @@ public class ShopMessageTemplateServiceImpl extends BaseServiceImpl<ShopMessageT
return CommonResult.success();
}
private Map getActiveTime(){
Map args = new HashMap();
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 加上12小时
LocalDateTime futureTime = now.plusHours(12);
// 格式化输出
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedNow = now.format(formatter);
String formattedFuture = futureTime.format(formatter);
args.put("formattedNow", formattedNow);
args.put("formattedFuture", formattedFuture);
return args;
}
@Override
public void sendToXcxAllUserMessage() {
long allBindCount = accountService.getAllBindCount(BindCode.MOBILE,CommonConstant.BIND_SUB_TMPL_SKILL);
if(allBindCount==0){
return;
}
String accessToken = accountService.getXcxAccessToken(true);
String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken;
QueryWrapper<ShopWechatTplmsg> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("tplmsg_id", 1022);//todo 后期改为动态
ShopWechatTplmsg wechatTplmsg = shopWechatTplmsgService.getOne(queryWrapper);
ShopStoreBase shopStoreBase= shopStoreBaseService.get(wechatTplmsg.getStore_id());
Map args=new HashMap();
String[] activeProfiles = environment.getActiveProfiles();
String activeProfile = activeProfiles[0];
args.put("storeName", shopStoreBase.getStore_name());
args.put("evn",activeProfile);
Map timeArgs=getActiveTime();
args.putAll(timeArgs);
int total= (int) allBindCount;
Integer pages= CommonUtil.getPagesCount(total,BATCH_SIZE);
ExecutorService executor = Executors.newFixedThreadPool(6);
List<Future<?>> futures = new ArrayList<>();
for (int i=1;i<=pages;i++){
List<AccountUserBindConnect> finalList=accountService.getAllBindPage(BindCode.MOBILE,CommonConstant.BIND_SUB_TMPL_SKILL,i,BATCH_SIZE);
int finalI = i;
futures.add(executor.submit(() -> {
finalList.forEach(accountUserBindConnect -> {
String open_id=accountUserBindConnect.getBind_openid();
if(StrUtil.isNotEmpty(open_id)){
String wechatTplData = getXcxWechatTplData(open_id, wechatTplmsg, args);
String result = WxHttpUtil.request(WxHttpUtil.MethodType.POST, WxHttpUtil.WxType.XCX, accessToken, url, null, wechatTplData);
JSONObject resultJson = JSONUtil.parseObj(result);
Integer errcode = Convert.toInt(resultJson.get("errcode"));
String errmsg = Convert.toStr(resultJson.get("errmsg"));
if (errcode != 0) {
log.error("微信公众号推送失败,失败原因:{}", errmsg);
}
}
});
return "完成推送批次:"+ finalI;
}));
}
// 等待所有任务完成
for (Future<?> future : futures) {
try {
log.info("规格任务结果: {}", future.get());
} catch (Exception e) {
log.info("规格任务执行异常: {}", e.getMessage());
}
}
executor.shutdown();
}
/**
@ -750,19 +825,11 @@ public class ShopMessageTemplateServiceImpl extends BaseServiceImpl<ShopMessageT
phrase3.setValue("秒杀特价");
BaseValue.setPhrase3(phrase3);
amount4.setValue("0.01元起");
amount4.setValue("0.01");
BaseValue.setAmount4(amount4);
String formattedNow=Convert.toStr(args.get("formattedNow"));
String formattedFuture=Convert.toStr(args.get("formattedFuture"));
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 加上12小时
LocalDateTime futureTime = now.plusHours(12);
// 格式化输出
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedNow = now.format(formatter);
String formattedFuture = futureTime.format(formatter);
time6.setValue(formattedNow);
BaseValue.setTime6(time6);
time9.setValue(formattedFuture);
@ -772,5 +839,4 @@ public class ShopMessageTemplateServiceImpl extends BaseServiceImpl<ShopMessageT
return JSON.toJSONString(wxXcxMsgPushVo);
}
}

View File

@ -120,4 +120,5 @@ job:
- "UpdateHallOrderStatusJob"
- "UpdatePayCardStateJob"
- "RetryMqMsgJob"
- "ProductItemAutoFillJob"
- "ProductItemAutoFillJob"
- "XcxSubSendMessageJob"

View File

@ -0,0 +1 @@
alter table account_user_bind_connect add column bind_tmpl varchar(500) comment '允许通知的消息通知模板id';