叫号消息订阅

This commit is contained in:
张松 2025-03-20 14:30:33 +08:00
parent fc51063872
commit 8f46ce129e
6 changed files with 351 additions and 5 deletions

View File

@ -1,5 +1,6 @@
package com.czg.controller.admin;
import com.czg.account.dto.WxMsgSubDTO;
import com.czg.account.dto.calltable.*;
import com.czg.account.entity.CallConfig;
import com.czg.account.entity.CallQueue;
@ -173,4 +174,15 @@ public class CallTableController {
public CzgResult<Boolean> updateConfig(@RequestBody UpdateConfigDTO configDTO) {
return CzgResult.success(callTableService.updateConfig(StpKit.USER.getShopId(), configDTO));
}
/**
* 消息订阅
* @return 是否成功
*/
@PostMapping("subMsg")
public CzgResult<?> subMsg(
@Validated @RequestBody CallSubMsgDTO subMsgDTO
) {
return CzgResult.success(callTableService.subMsg(subMsgDTO));
}
}

View File

@ -0,0 +1,27 @@
package com.czg.account.dto.calltable;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author Administrator
*/
@Data
public class CallSubMsgDTO {
/**
* 店铺id
*/
@NotNull
private Long shopId;
/**
* 队列id
*/
@NotNull
private Long queueId;
/**
* openId
*/
@NotEmpty
private String openId;
}

View File

@ -21,4 +21,5 @@ public class CallTableNumDTO {
* 号码
*/
private String callNum;
private Long queueId;
}

View File

@ -36,4 +36,6 @@ public interface CallTableService extends IService<CallTable> {
CallConfig getConfig(Long shopId);
boolean updateConfig(Long shopId, UpdateConfigDTO configDTO);
boolean subMsg(CallSubMsgDTO subMsgDTO);
}

View File

@ -15,6 +15,7 @@ import com.czg.resp.CzgResult;
import com.czg.service.account.mapper.CallQueueMapper;
import com.czg.service.account.mapper.CallTableMapper;
import com.czg.service.account.util.FunUtil;
import com.czg.service.account.util.WechatMiniMsgUtil;
import com.czg.system.dto.SysParamsDTO;
import com.czg.system.service.SysParamsService;
import com.czg.utils.JoinQueryWrapper;
@ -62,6 +63,8 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
private CallQueueMapper callQueueMapper;
@Resource
private RabbitPublisher rabbitPublisher;
@Resource
private WechatMiniMsgUtil miniMsgUtil;
@Override
public CallTablePage get(Long shopId, Long callTableId, Integer state) {
@ -222,7 +225,7 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
rabbitPublisher.printCallNumTicket(callQueue.getId());
return new CallTableNumDTO().setTableName(callTable.getName()).setTableNote(callTable.getNote())
.setCallNum(callQueue.getCallNum());
.setCallNum(callQueue.getCallNum()).setQueueId(callQueue.getId());
}
private String getCallNumber(Long shopId, CallTable callTable) {
@ -322,8 +325,8 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
return -1;
}
// wxMiniUtils.sendCurrentOrNearCallMsg(shopInfo.getShopName(), getStrByState(Integer.valueOf(callQueue.getState())),
// callQueue.getCallNum(), current.isEmpty() ? "" : current.get(0).getCallNum(), "排号信息", callQueue.getOpenId(), false);
miniMsgUtil.sendCurrentOrNearCallMsg(shopInfo.getShopName(), getStrByState(callQueue.getState()),
callQueue.getCallNum(), current.isEmpty() ? "" : current.getFirst().getCallNum(), "排号信息", callQueue.getOpenId(), false);
CallConfig config = getConfig(shopId);
// 临近用户提醒
@ -334,11 +337,20 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
.page(new Page<>(config.getNearNum(), 1)).getRecords();
if (!nearList.isEmpty()) {
CallQueue nearQueue = nearList.getFirst();
// wxMiniUtils.sendCurrentOrNearCallMsg(shopInfo.getShopName(), getStrByState(Integer.valueOf(nearQueue.getState())),
// nearQueue.getCallNum(), current.isEmpty() ? "" : current.get(0).getCallNum(), "排号信息", nearQueue.getOpenId(), true);
miniMsgUtil.sendCurrentOrNearCallMsg(shopInfo.getShopName(), getStrByState(nearQueue.getState()),
nearQueue.getCallNum(), current.isEmpty() ? "" : current.getFirst().getCallNum(), "排号信息", nearQueue.getOpenId(), true);
}
return 1;
}
private String getStrByState(Integer state) {
return switch (state) {
case -1 -> "已取消";
case 0 -> "排队中";
case 1 -> "已到号";
case 3 -> "已过号";
default -> "";
};
}
@Override
public boolean updateInfo(Long shopId, UpdateCallQueueDTO updateCallQueueDTO) {
@ -543,4 +555,40 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
tbCallConfig.setId(config.getId());
return callConfigService.updateById(tbCallConfig);
}
@Override
public boolean subMsg(CallSubMsgDTO subMsgDTO) {
CallQueue queue = callQueueService.getOne(new QueryWrapper()
.eq(CallQueue::getShopId, subMsgDTO.getShopId())
.eq(CallQueue::getId, subMsgDTO.getQueueId()));
if (queue == null) {
throw new ApiNotPrintException("您未排号请先排号");
}
if (queue.getOpenId() != null && queue.getOpenId().equals(subMsgDTO.getOpenId()) && queue.getSubState() == 1) {
return true;
}
if (StrUtil.isNotBlank(queue.getOpenId()) && queue.getSubState() == 1) {
throw new ApiNotPrintException("此号码已被其他用户订阅");
}
if (!subMsgDTO.getOpenId().equals(queue.getOpenId()) && queue.getSubState() == 0) {
long count = callQueueService.queryChain()
// .eq(CallQueue::getPhone, takeNumberDTO.getPhone())
.eq(CallQueue::getOpenId, subMsgDTO.getOpenId())
.eq(CallQueue::getShopId, subMsgDTO.getShopId())
.eq(CallQueue::getCreateDay, DateUtil.date().toString("yyyy-MM-dd"))
.in(CallQueue::getState, 0, 1)
.ne(CallQueue::getIsPostpone, 2)
.eq(CallQueue::getCallTableId, queue.getCallTableId()).count();
if (count > 0) {
throw new ApiNotPrintException("您已订阅其他号码,请勿重复订阅");
}
}
queue.setSubState(1);
queue.setOpenId(subMsgDTO.getOpenId());
return callQueueService.updateById(queue);
}
}

View File

@ -0,0 +1,256 @@
package com.czg.service.account.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.czg.config.RedisCst;
import com.czg.resp.CzgResult;
import com.czg.service.RedisService;
import com.czg.system.dto.SysParamsDTO;
import com.czg.system.enums.SysParamCodeEnum;
import com.czg.system.service.SysParamsService;
import com.google.gson.JsonObject;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotBlank;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author Administrator
*/
@Slf4j
@Component
public class WechatMiniMsgUtil {
@DubboReference(check = false)
private SysParamsService sysParamsService;
@Resource
private RedisService redisService;
@Resource
private AliOssUtil aliOssUtil;
private static final String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
private static final String QR_CODE_URL = "https://api.weixin.qq.com/wxa/getwxacode";
static LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
static {
linkedHashMap.put("40001", "获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口");
linkedHashMap.put("40003", "不合法的 OpenID ,请开发者确认 OpenID (该用户)是否已关注公众号,或是否是其他公众号的 OpenID");
linkedHashMap.put("40014", "不合法的 access_token ,请开发者认真比对 access_token 的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口");
linkedHashMap.put("40037", "不合法的 template_id");
linkedHashMap.put("43101", "用户未订阅消息");
linkedHashMap.put("43107", "订阅消息能力封禁");
linkedHashMap.put("43108", "并发下发消息给同一个粉丝");
linkedHashMap.put("45168", "命中敏感词");
linkedHashMap.put("47003", "参数错误");
}
public JSONObject sendTempMsg(String tempId, String toUserOpenId, Map<String, Object> data, String note) {
log.info("开始发送" + note + "模板消息, 接收用户openId: {}, 消息数据: {}", toUserOpenId, data);
String token= getAccessToken();
JSONObject object1=new JSONObject();
object1.put("template_id", tempId);
object1.put("touser", toUserOpenId);
object1.put("data",data);
object1.put("miniprogram_state","trial");
object1.put("lang","zh_CN");
String response= HttpRequest.post("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=".concat(token)).body(object1.toString()).execute().body();
log.info("微信模板消息发送成功,相应内容:{}",response);
JSONObject resObj= JSONObject.parseObject(response);
if(ObjectUtil.isNotEmpty(resObj)&&ObjectUtil.isNotNull(resObj)&&"0".equals(resObj.get("errcode")+"")){
return resObj;
}
throw new RuntimeException(linkedHashMap.getOrDefault(resObj.get("errcode") + "", "未知错误"));
}
public void sendCurrentOrNearCallMsg(String shopName, String state, String callNum, String currentNum, String note, String openId, boolean isNear) {
CzgResult<SysParamsDTO> callNear = sysParamsService.getParamsByCode("wx_mini_msg_call_near");
CzgResult<SysParamsDTO> callCurrent = sysParamsService.getParamsByCode("wx_mini_msg_call_current");
Map<String, Object> data = new HashMap<String, Object>() {{
put("thing1", new HashMap<String, Object>() {{
put("value", shopName);
}});
put("phrase2", new HashMap<String, Object>() {{
put("value", state);
}});
put("character_string3", new HashMap<String, Object>() {{
put("value", callNum);
}});
put("character_string4", new HashMap<String, Object>() {{
put("value", currentNum);
}});
put("thing5", new HashMap<String, Object>() {{
put("value", note);
}});
}};
try {
sendTempMsg(isNear ? callNear.getData().getParamValue() : callCurrent.getData().getParamValue(), openId, data, "排队到号");
} catch (Exception e) {
log.error("发送失败, openId:{}, msg: {}", openId, e.getMessage());
}
}
public void sendPassCallMsg(String shopName, String state, String callNum, String currentNum, String note, String openId) {
CzgResult<SysParamsDTO> callPass = sysParamsService.getParamsByCode("wx_mini_msg_call_pass");
Map<String, Object> data = new HashMap<String, Object>() {{
put("thing1", new HashMap<String, Object>() {{
put("value", shopName);
}});
put("character_string2", new HashMap<String, Object>() {{
put("value", callNum);
}});
put("character_string3", new HashMap<String, Object>() {{
put("value", currentNum);
}});
put("phrase4", new HashMap<String, Object>() {{
put("value", state);
}});
put("thing5", new HashMap<String, Object>() {{
put("value", note);
}});
}};
try {
sendTempMsg(callPass.getData().getParamValue(), openId, data, "过号");
} catch (Exception e) {
log.error("发送失败, openId:{}, msg: {}", openId, e.getMessage());
}
}
public String getAccountOpenId(String code) {
CzgResult<SysParamsDTO> wxAccountAppId = sysParamsService.getParamsByCode("wx_account_app_id");
CzgResult<SysParamsDTO> wxAccountSecrete = sysParamsService.getParamsByCode("wx_account_secrete");
String accountAppId = wxAccountAppId.getData().getParamValue();
String accountSecrete = wxAccountSecrete.getData().getParamValue();
String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?";
Map<String, Object> requestUrlParam = new HashMap<>();
// https://mp.weixin.qq.com/wxopen/devprofile?action=get_profile&token=164113089&lang=zh_CN
//小程序appId
requestUrlParam.put("appid", accountAppId);
//小程序secret
requestUrlParam.put("secret", accountSecrete);
//小程序端返回的code
requestUrlParam.put("code", code);
//默认参数
requestUrlParam.put("grant_type", "authorization_code");
log.info("微信获取openid请求报文{}", requestUrlParam);
//发送post请求读取调用微信接口获取openid用户唯一标识
String resp = HttpUtil.post(requestUrl, requestUrlParam);
log.info("响应报文{}", resp);
return JSONObject.parseObject(resp).getString("openid");
}
//获取小程序token
private String getAccessToken() {
CzgResult<SysParamsDTO> wxMiniAppId = sysParamsService.getParamsByCode("wx_mini_app_id");
CzgResult<SysParamsDTO> wxMiniSecrete = sysParamsService.getParamsByCode("wx_mini_secrete");
String appId = wxMiniAppId.getData().getParamValue();
String secrete = wxMiniSecrete.getData().getParamValue();
String url = String.format("%s?grant_type=client_credential&appid=%s&secret=%s", TOKEN_URL, appId, secrete);
String response = HttpUtil.get(url);
JSONObject jsonResponse = JSONObject.parseObject(response);
if (!jsonResponse.containsKey("access_token")) {
throw new RuntimeException("Failed to retrieve access token: " + response);
}
return jsonResponse.getString("access_token");
}
/**
* 生成 小程序码 跳转对应页面
*
*/
public String getFetchQrCode(Map<String, Object> params) throws Exception {
String url = aliOssUtil.upload(fetchQrCode(params), aliOssUtil.getPath("shopVip", "png"));
redisService.set(RedisCst.SHOP_VIP_CODE + params.get("shopId"), url);
return url;
}
//生成页面地址
private InputStream fetchQrCode(Map<String, Object> params) {
JsonObject jsonObject = new JsonObject();
//路径
jsonObject.addProperty("path", sysParamsService.getSysParamValue(SysParamCodeEnum.WX_MINI_VIP_URL.getCode()) + "?shopId=" + params.get("shopId"));
//是否需要透明底色 true 生成透明底色的小程序码
jsonObject.addProperty("is_hyaline", true);
//正式版为 release体验版为 trial开发版为 develop
if (params.containsKey("env_version")) {
jsonObject.addProperty("env_version", "trial");
}
String accessToken = getAccessToken();
String url = String.format("%s?access_token=%s", QR_CODE_URL, accessToken);
return HttpUtil.createPost(url)
.body(jsonObject.toString(), "application/json")
.execute()
.bodyStream();
}
public JSONObject getSession(String code) {
CzgResult<SysParamsDTO> wxMiniSecrete = sysParamsService.getParamsByCode("wx_mini_secrete");
CzgResult<SysParamsDTO> wxMiniAppId = sysParamsService.getParamsByCode("wx_mini_app_id");
String appId = wxMiniAppId.getData().getParamValue();
String secrete = wxMiniSecrete.getData().getParamValue();
String requestUrl = "https://api.weixin.qq.com/sns/jscode2session";
Map<String, Object> requestUrlParam = new HashMap<>();
// https://mp.weixin.qq.com/wxopen/devprofile?action=get_profile&token=164113089&lang=zh_CN
//小程序appId
requestUrlParam.put("appid", appId);
//小程序secret
requestUrlParam.put("secret", secrete);
//小程序端返回的code
requestUrlParam.put("js_code", code);
//默认参数
requestUrlParam.put("grant_type", "authorization_code");
//发送post请求读取调用微信接口获取openid用户唯一标识
String resp = HttpUtil.post(requestUrl, requestUrlParam);
JSONObject jsonObject = JSON.parseObject(resp);
log.info("微信获取openid响应报文{}", resp);
return jsonObject;
}
public String getSessionKey(String code, String key) {
JSONObject session = getSession(code);
String info = session.getString(key);
if (StrUtil.isBlank(info)) {
throw new RuntimeException(key + "获取失败");
}
return info;
}
public String getSessionKeyOrOpenId(String code, boolean isAccount) {
return getSessionKey(code, "openid");
}
public static String decrypt(String sessionKey, @NotBlank(message = "数据不能为空") String encryptedData, String iv) {
// Base64 解码
byte[] keyBytes = Base64.decode(sessionKey);
byte[] encryptedBytes = Base64.decode(encryptedData);
byte[] ivBytes = Base64.decode(iv);
// 使用 Hutool 进行 AES-CBC 解密
AES aes = new AES("CBC", "PKCS5Padding", keyBytes, ivBytes);
byte[] decryptedBytes = aes.decrypt(encryptedBytes);
return new String(decryptedBytes, java.nio.charset.StandardCharsets.UTF_8);
}
}