审批同意后,推送订阅消息
This commit is contained in:
@@ -1,58 +1,28 @@
|
|||||||
package com.example.mini_program.controller;
|
package com.example.mini_program.controller;
|
||||||
|
|
||||||
import com.example.mini_program.aes.WXBizMsgCrypt;
|
import com.example.mini_program.service.VisitorApprovalService;
|
||||||
import com.example.mini_program.mapper.VisitApplicationMapper;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.json.XML;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.scheduling.annotation.Async;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/visitor")
|
@RequestMapping("/visitor")
|
||||||
public class VisitorApprovalController {
|
public class VisitorApprovalController {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(VisitorApprovalController.class);
|
private static final Logger log = LoggerFactory.getLogger(VisitorApprovalController.class);
|
||||||
//发起文件审批时的审批编号,唯一标识一次审批
|
|
||||||
private static final Set<String> processedSet = ConcurrentHashMap.newKeySet();
|
|
||||||
|
|
||||||
@Value("${wx.corp.corpid:}")
|
|
||||||
private String corpId;
|
|
||||||
|
|
||||||
@Value("${wx.corp.token:}")
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
@Value("${wx.corp.encodingAESKey:}")
|
|
||||||
private String encodingAESKey;
|
|
||||||
|
|
||||||
@Value("${wx.corp.approval-template-id:}")
|
|
||||||
private String templateId;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private VisitApplicationMapper visitApplicationMapper;
|
private VisitorApprovalService visitorApprovalService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证URL有效性(GET请求)
|
* 验证URL有效性(GET请求)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/approval/callback")
|
@GetMapping("/approval/callback")
|
||||||
public String verifyUrl(@RequestParam(value = "msg_signature", required = false) String msgSignature, @RequestParam(value = "timestamp", required = false) String timestamp, @RequestParam(value = "nonce", required = false) String nonce, @RequestParam(value = "echostr", required = false) String echostr) {
|
public String verifyUrl(@RequestParam(value = "msg_signature", required = false) String msgSignature, @RequestParam(value = "timestamp", required = false) String timestamp, @RequestParam(value = "nonce", required = false) String nonce, @RequestParam(value = "echostr", required = false) String echostr) {
|
||||||
|
|
||||||
log.info("URL验证请求: msgSignature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce);
|
log.info("URL验证请求: msgSignature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce);
|
||||||
|
return visitorApprovalService.verifyUrl(msgSignature, timestamp, nonce, echostr);
|
||||||
|
|
||||||
try {
|
|
||||||
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpId);
|
|
||||||
return wxcpt.VerifyURL(msgSignature, timestamp, nonce, echostr);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("URL验证失败", e);
|
|
||||||
return "error: " + e.getMessage();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,37 +35,7 @@ public class VisitorApprovalController {
|
|||||||
@RequestBody String requestBody) {
|
@RequestBody String requestBody) {
|
||||||
System.out.println("----------------------------------");
|
System.out.println("----------------------------------");
|
||||||
// 异步处理,立即返回
|
// 异步处理,立即返回
|
||||||
processApprovalAsync(msgSignature, timestamp, nonce, requestBody);
|
visitorApprovalService.processApprovalAsync(msgSignature, timestamp, nonce, requestBody);
|
||||||
return "success";
|
return "success";
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 异步处理审批(解密 + 解析 + 处理)
|
|
||||||
*/
|
|
||||||
@Async
|
|
||||||
public void processApprovalAsync(String msgSignature, String timestamp, String nonce, String requestBody) {
|
|
||||||
try {
|
|
||||||
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpId);
|
|
||||||
String sMsg = wxcpt.DecryptMsg(msgSignature, timestamp, nonce, requestBody);
|
|
||||||
JSONObject jsonObject = XML.toJSONObject(sMsg).getJSONObject("xml");
|
|
||||||
JSONObject approvalInfo = jsonObject.getJSONObject("ApprovalInfo");
|
|
||||||
// 检查是否是目标审批模板且已通过
|
|
||||||
if (!templateId.equals(approvalInfo.getString("TemplateId"))) {
|
|
||||||
log.info("模板【{}】非目标审批模板",approvalInfo.getString("TemplateId") );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.info("审批回调报文: {}", jsonObject);
|
|
||||||
//审批编号(唯一标识每次发起的审批)
|
|
||||||
String spNo = approvalInfo.getString("SpNoStr");
|
|
||||||
//审批状态
|
|
||||||
int spStatus = approvalInfo.getInt("SpStatus");
|
|
||||||
//审批同意
|
|
||||||
if (2 == spStatus) {
|
|
||||||
visitApplicationMapper.updateStatusBySpNo(spNo, "approved", "审批同意");
|
|
||||||
}else if(3 == spStatus){
|
|
||||||
visitApplicationMapper.updateStatusBySpNo(spNo, "rejected", "审批拒绝");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("处理审批失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public interface VisitApplicationMapper {
|
|||||||
*/
|
*/
|
||||||
List<VisitApplication> selectListByOpenid(@Param("openid") String openid);
|
List<VisitApplication> selectListByOpenid(@Param("openid") String openid);
|
||||||
|
|
||||||
|
|
||||||
|
VisitApplication selectBySpNo(@Param("spNo") String spNo);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增预约记录
|
* 新增预约记录
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -76,14 +76,14 @@ public class AppointmentService {
|
|||||||
visitApplicationMapper.insert(record);
|
visitApplicationMapper.insert(record);
|
||||||
log.info("创建预约成功, id={}", record.getId());
|
log.info("创建预约成功, id={}", record.getId());
|
||||||
|
|
||||||
// 推送订阅消息
|
// // 推送订阅消息
|
||||||
try {
|
// try {
|
||||||
wxSubscribeMessageService.sendSubscribeMessage(
|
// wxSubscribeMessageService.sendSubscribeMessage(
|
||||||
record.getOpenid(), record.getName(), record.getReason(),
|
// record.getOpenid(), record.getName(), record.getReason(),
|
||||||
formatVisitTime(record), record.getArea(), "待审核");
|
// formatVisitTime(record), record.getArea(), "待审核");
|
||||||
} catch (Exception e) {
|
// } catch (Exception e) {
|
||||||
log.error("订阅消息推送失败", e);
|
// log.error("订阅消息推送失败", e);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
@@ -140,12 +140,4 @@ public class AppointmentService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 拼接访问日期+时间 */
|
|
||||||
private String formatVisitTime(VisitApplication record) {
|
|
||||||
String time = record.getVisitDate();
|
|
||||||
if (record.getVisitTime() != null && !record.getVisitTime().isEmpty()) {
|
|
||||||
time = record.getVisitDate() + " " + record.getVisitTime();
|
|
||||||
}
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package com.example.mini_program.service;
|
||||||
|
|
||||||
|
import com.example.mini_program.aes.WXBizMsgCrypt;
|
||||||
|
import com.example.mini_program.entity.VisitApplication;
|
||||||
|
import com.example.mini_program.mapper.VisitApplicationMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.XML;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class VisitorApprovalService {
|
||||||
|
|
||||||
|
@Value("${wx.corp.corpid:}")
|
||||||
|
private String corpId;
|
||||||
|
|
||||||
|
@Value("${wx.corp.token:}")
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Value("${wx.corp.encodingAESKey:}")
|
||||||
|
private String encodingAESKey;
|
||||||
|
|
||||||
|
@Value("${wx.corp.approval-template-id:}")
|
||||||
|
private String templateId;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VisitApplicationMapper visitApplicationMapper;
|
||||||
|
@Autowired
|
||||||
|
private WxSubscribeMessageService wxSubscribeMessageService;
|
||||||
|
|
||||||
|
public String verifyUrl(String msgSignature, String timestamp, String nonce, String echostr) {
|
||||||
|
log.info("URL验证请求: msgSignature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce);
|
||||||
|
try {
|
||||||
|
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpId);
|
||||||
|
return wxcpt.VerifyURL(msgSignature, timestamp, nonce, echostr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("URL验证失败", e);
|
||||||
|
return "error: " + e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void processApprovalAsync(String msgSignature, String timestamp, String nonce, String requestBody) {
|
||||||
|
try {
|
||||||
|
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpId);
|
||||||
|
String sMsg = wxcpt.DecryptMsg(msgSignature, timestamp, nonce, requestBody);
|
||||||
|
JSONObject jsonObject = XML.toJSONObject(sMsg).getJSONObject("xml");
|
||||||
|
JSONObject approvalInfo = jsonObject.getJSONObject("ApprovalInfo");
|
||||||
|
// 检查是否是目标审批模板且已通过
|
||||||
|
if (!templateId.equals(approvalInfo.getString("TemplateId"))) {
|
||||||
|
log.info("模板【{}】非目标审批模板", approvalInfo.getString("TemplateId"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("审批回调报文: {}", jsonObject);
|
||||||
|
//审批编号(唯一标识每次发起的审批)
|
||||||
|
String spNo = approvalInfo.getString("SpNoStr");
|
||||||
|
//审批状态
|
||||||
|
int spStatus = approvalInfo.getInt("SpStatus");
|
||||||
|
//根据审批编号查询访客预约信息
|
||||||
|
VisitApplication record = visitApplicationMapper.selectBySpNo(spNo);
|
||||||
|
if (Objects.isNull(record)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//审批同意
|
||||||
|
if (2 == spStatus) {
|
||||||
|
visitApplicationMapper.updateStatus(record.getId(), "approved", "审批同意");
|
||||||
|
//发送订阅消息
|
||||||
|
try {
|
||||||
|
wxSubscribeMessageService.sendSubscribeMessage(record.getOpenid(), record.getName(), record.getReason(),
|
||||||
|
formatVisitTime(record), record.getArea(), "审批同意");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("订阅消息推送失败", e);
|
||||||
|
}
|
||||||
|
} else if (3 == spStatus) {
|
||||||
|
visitApplicationMapper.updateStatusBySpNo(spNo, "rejected", "审批拒绝");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理审批失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼接访问日期+时间
|
||||||
|
*/
|
||||||
|
private String formatVisitTime(VisitApplication record) {
|
||||||
|
String time = record.getVisitDate();
|
||||||
|
if (record.getVisitTime() != null && !record.getVisitTime().isEmpty()) {
|
||||||
|
time = record.getVisitDate() + " " + record.getVisitTime();
|
||||||
|
}
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,13 +14,13 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* 微信小程序订阅消息服务
|
* 微信小程序订阅消息服务
|
||||||
* 调用微信 /cgi-bin/message/subscribe/send 接口推送订阅消息
|
* 调用微信 /cgi-bin/message/subscribe/send 接口推送订阅消息
|
||||||
*
|
* <p>
|
||||||
* 模板字段映射:
|
* 模板字段映射:
|
||||||
* name1 (姓名) → 访客姓名
|
* name1 (姓名) → 访客姓名
|
||||||
* thing3 (事物) → 来访事由
|
* thing3 (事物) → 来访事由
|
||||||
* date8 (日期) → 预约时间
|
* date8 (日期) → 预约时间
|
||||||
* thing10 (事物) → 到访区域
|
* thing10 (事物) → 到访区域
|
||||||
* phrase18 (短语) → 审批状态
|
* phrase18 (短语) → 审批状态
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@@ -38,7 +38,9 @@ public class WxSubscribeMessageService {
|
|||||||
@Value("${wx.miniapp.subscribe-template-id:}")
|
@Value("${wx.miniapp.subscribe-template-id:}")
|
||||||
private String templateId;
|
private String templateId;
|
||||||
|
|
||||||
/** 获取小程序 access_token */
|
/**
|
||||||
|
* 获取小程序 access_token
|
||||||
|
*/
|
||||||
public String getAccessToken() {
|
public String getAccessToken() {
|
||||||
String url = String.format(GET_TOKEN_URL, wxMiniAppConfig.getAppid(), wxMiniAppConfig.getSecret());
|
String url = String.format(GET_TOKEN_URL, wxMiniAppConfig.getAppid(), wxMiniAppConfig.getSecret());
|
||||||
try {
|
try {
|
||||||
@@ -57,12 +59,11 @@ public class WxSubscribeMessageService {
|
|||||||
* 所有字段不允许为空字符串(微信返回47003),name类型不接受纯数字
|
* 所有字段不允许为空字符串(微信返回47003),name类型不接受纯数字
|
||||||
*/
|
*/
|
||||||
public void sendSubscribeMessage(String openid, String visitorName, String reason,
|
public void sendSubscribeMessage(String openid, String visitorName, String reason,
|
||||||
String visitTime, String area, String status) {
|
String visitTime, String area, String status) {
|
||||||
if (templateId == null || templateId.isEmpty()) {
|
if (templateId == null || templateId.isEmpty()) {
|
||||||
log.warn("未配置 subscribe-template-id,跳过订阅消息推送");
|
log.warn("未配置 subscribe-template-id,跳过订阅消息推送");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String accessToken = getAccessToken();
|
String accessToken = getAccessToken();
|
||||||
String url = String.format(SEND_MSG_URL, accessToken);
|
String url = String.format(SEND_MSG_URL, accessToken);
|
||||||
|
|
||||||
@@ -82,8 +83,10 @@ public class WxSubscribeMessageService {
|
|||||||
Map<String, Object> body = new HashMap<>();
|
Map<String, Object> body = new HashMap<>();
|
||||||
body.put("touser", openid);
|
body.put("touser", openid);
|
||||||
body.put("template_id", templateId);
|
body.put("template_id", templateId);
|
||||||
|
//点击模板卡片后的跳转页面,仅限本小程序内的页面。
|
||||||
body.put("page", "pages/records/records");
|
body.put("page", "pages/records/records");
|
||||||
body.put("data", data);
|
body.put("data", data);
|
||||||
|
//跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
|
||||||
body.put("miniprogram_state", "formal");
|
body.put("miniprogram_state", "formal");
|
||||||
body.put("lang", "zh_CN");
|
body.put("lang", "zh_CN");
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,13 @@
|
|||||||
ORDER BY create_time DESC
|
ORDER BY create_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectBySpNo" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM visit_application
|
||||||
|
WHERE sp_no = #{spNo}
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectByIdAndOpenid" resultMap="BaseResultMap">
|
<select id="selectByIdAndOpenid" resultMap="BaseResultMap">
|
||||||
SELECT <include refid="Base_Column_List"/>
|
SELECT <include refid="Base_Column_List"/>
|
||||||
FROM visit_application
|
FROM visit_application
|
||||||
|
|||||||
Reference in New Issue
Block a user