预约核验

This commit is contained in:
chenglijuan
2026-04-29 15:37:15 +08:00
parent 67c359dd2b
commit 13087b15bc
10 changed files with 154 additions and 97 deletions
@@ -33,4 +33,9 @@ public class WxCorpConfig {
* 审批申请人用户ID(提交审批的企微用户) * 审批申请人用户ID(提交审批的企微用户)
*/ */
private String creatorUserid; private String creatorUserid;
/**
* 访客预约应用AgentId(用于发送应用消息)
*/
private String agentid;
} }
@@ -102,22 +102,6 @@ public class AppointmentController {
return success ? Result.success(true) : Result.error("审批失败,记录不存在或状态不允许"); return success ? Result.success(true) : Result.error("审批失败,记录不存在或状态不允许");
} }
/**
* 根据id获取审批详情
*/
// @PutMapping("/detail")
// public Result<VisitApplicationVo> detail(@RequestParam String id, @RequestParam String openid) throws Exception {
// if (id == null || id.trim().isEmpty()) {
// return Result.error("id不能为空");
// }
// if (openid == null || openid.trim().isEmpty()) {
// return Result.error("openid不能为空");
// }
// VisitApplicationVo vo = appointmentService.detail(id, openid);
// return Result.success(vo);
// }
/** /**
* 受访人下拉框值 * 受访人下拉框值
*/ */
@@ -16,6 +16,17 @@ public class VisitorApprovalController {
@Autowired @Autowired
private VisitorApprovalService visitorApprovalService; private VisitorApprovalService visitorApprovalService;
/**
* 通知受访者访客已到达
*
* @param id 预约记录ID
*/
@GetMapping("/notify-host")
public void notifyHostArrival(@RequestParam String id) {
log.info("通知受访者访客已到达, id={}", id);
visitorApprovalService.notifyHostArrival(id);
}
/** /**
* 验证URL有效性(GET请求) * 验证URL有效性(GET请求)
*/ */
@@ -34,8 +45,11 @@ public class VisitorApprovalController {
@RequestParam(value = "nonce", required = false) String nonce, @RequestParam(value = "nonce", required = false) String nonce,
@RequestBody String requestBody) { @RequestBody String requestBody) {
System.out.println("----------------------------------"); System.out.println("----------------------------------");
// 异步处理,立即返回 // 异步处理,立即返回
visitorApprovalService.processApprovalAsync(msgSignature, timestamp, nonce, requestBody); visitorApprovalService.processApprovalAsync(msgSignature, timestamp, nonce, requestBody);
return "success"; return "success";
} }
} }
@@ -19,10 +19,12 @@ public class VisitApplication {
private String visitTime; private String visitTime;
private String hostName; private String hostName;
private String receptionPersonId;
private String area; private String area;
private String status; private String status;
private String statusText; private String statusText;
private String openid; private String openid;
private String createTime; private String createTime;
private String spNo; private String spNo;
private String checkStatus;
} }
@@ -71,6 +71,15 @@ public interface VisitApplicationMapper {
* @return 影响行数 * @return 影响行数
*/ */
int updateStatus(@Param("id") String id, @Param("status") String status, @Param("statusText") String statusText); int updateStatus(@Param("id") String id, @Param("status") String status, @Param("statusText") String statusText);
/**
* 更新审批状态
*
* @param id 记录ID
* @return 影响行数
*/
void updateCheckStatusById(@Param("id") String id, @Param("checkStatus") String checkStatus);
/** /**
* 根据审批单号更新审批状态 * 根据审批单号更新审批状态
* *
@@ -31,9 +31,6 @@ public class AppointmentService {
private final WxApprovalService wxApprovalService; private final WxApprovalService wxApprovalService;
@Autowired @Autowired
private ReceptionPersonMapper receptionPersonMapper; private ReceptionPersonMapper receptionPersonMapper;
@Autowired
private final WxSubscribeMessageService wxSubscribeMessageService;
@Value("${wx.corp.creator-userid:}") @Value("${wx.corp.creator-userid:}")
private String creatorUserId; private String creatorUserId;
@@ -60,8 +57,10 @@ public class AppointmentService {
log.info("查询到 {} 条预约记录", list.size()); log.info("查询到 {} 条预约记录", list.size());
return list; return list;
} }
public VisitApplication getDetail(String id) { public VisitApplication getDetail(String id) {
return visitApplicationMapper.selectById(id); VisitApplication v = visitApplicationMapper.selectById(id);
return v;
} }
/** /**
@@ -89,16 +88,6 @@ public class AppointmentService {
visitApplicationMapper.insert(record); visitApplicationMapper.insert(record);
log.info("创建预约成功, id={}", record.getId()); log.info("创建预约成功, id={}", record.getId());
// // 推送订阅消息
// try {
// wxSubscribeMessageService.sendSubscribeMessage(
// record.getOpenid(), record.getName(), record.getReason(),
// formatVisitTime(record), record.getArea(), "待审核");
// } catch (Exception e) {
// log.error("订阅消息推送失败", e);
// }
return record; return record;
} }
@@ -173,7 +162,7 @@ public class AppointmentService {
public List<ReceptionPersonVo> personSelector(String department) { public List<ReceptionPersonVo> personSelector(String department) {
List<ReceptionPersonPO> receiptPersonList = receptionPersonMapper.selectReceptionPerson(department); List<ReceptionPersonPO> receiptPersonList = receptionPersonMapper.selectReceptionPerson(department);
List<ReceptionPersonVo> voList = new ArrayList<>(); List<ReceptionPersonVo> voList = new ArrayList<>();
for(ReceptionPersonPO po : receiptPersonList){ for (ReceptionPersonPO po : receiptPersonList) {
ReceptionPersonVo vo = new ReceptionPersonVo(); ReceptionPersonVo vo = new ReceptionPersonVo();
BeanUtils.copyProperties(po, vo); BeanUtils.copyProperties(po, vo);
voList.add(vo); voList.add(vo);
@@ -1,8 +1,11 @@
package com.example.mini_program.service; package com.example.mini_program.service;
import com.example.mini_program.aes.WXBizMsgCrypt; import com.example.mini_program.aes.WXBizMsgCrypt;
import com.example.mini_program.config.WxCorpConfig;
import com.example.mini_program.entity.VisitApplication; import com.example.mini_program.entity.VisitApplication;
import com.example.mini_program.mapper.VisitApplicationMapper; import com.example.mini_program.mapper.VisitApplicationMapper;
import com.example.mini_program.util.HttpUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject; import org.json.JSONObject;
import org.json.XML; import org.json.XML;
@@ -11,12 +14,17 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
@Slf4j @Slf4j
@Service @Service
public class VisitorApprovalService { public class VisitorApprovalService {
private static final String SEND_MSG_URL =
"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s";
@Value("${wx.corp.corpid:}") @Value("${wx.corp.corpid:}")
private String corpId; private String corpId;
@@ -33,6 +41,12 @@ public class VisitorApprovalService {
private VisitApplicationMapper visitApplicationMapper; private VisitApplicationMapper visitApplicationMapper;
@Autowired @Autowired
private WxSubscribeMessageService wxSubscribeMessageService; private WxSubscribeMessageService wxSubscribeMessageService;
@Autowired
private WxCorpConfig wxCorpConfig;
@Autowired
private WxApprovalService wxApprovalService;
@Autowired
private ObjectMapper objectMapper;
public String verifyUrl(String msgSignature, String timestamp, String nonce, String echostr) { public String verifyUrl(String msgSignature, String timestamp, String nonce, String echostr) {
log.info("URL验证请求: msgSignature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce); log.info("URL验证请求: msgSignature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce);
@@ -95,4 +109,60 @@ public class VisitorApprovalService {
} }
return time; return time;
} }
/**
* 通知受访者访客已到达
* 根据预约ID查询企微用户ID,发送应用消息
*
* @param id 预约记录ID
*/
public void notifyHostArrival(String id) {
VisitApplication record = visitApplicationMapper.selectById(id);
if (record == null) {
log.warn("【通知受访者访客已到达】未找到预约记录, id={}", id);
return;
}
if ("1".equals(record.getCheckStatus())) {
log.warn("【通知受访者访客已到达】访客预约记录已核销, id={}", id);
return;
}
visitApplicationMapper.updateCheckStatusById(id, "1");
//受访者企微id
String receptionPersonId = record.getReceptionPersonId();
if (receptionPersonId == null || receptionPersonId.isEmpty()) {
log.warn("通知受访者访客已到达】受访者ID为空, id={}", id);
return;
}
String message = String.format("访客【%s】已到达,访问区域:【%s】,来访事由:【%s】",
record.getName(), record.getArea(), record.getReason());
sendMessageToUser(receptionPersonId, message);
}
/**
* 发送应用消息给企微用户
*/
private void sendMessageToUser(String userId, String content) {
try {
String accessToken = wxApprovalService.getAccessToken();
String url = String.format(SEND_MSG_URL, accessToken);
Map<String, Object> body = new HashMap<>();
body.put("touser", userId);
body.put("msgtype", "text");
body.put("agentid", wxCorpConfig.getAgentid());
body.put("text", Map.of("content", content));
String json = objectMapper.writeValueAsString(body);
@SuppressWarnings("unchecked")
Map<String, Object> resp = objectMapper.readValue(HttpUtil.postJson(url, json), Map.class);
if ("0".equals(String.valueOf(resp.get("errcode")))) {
log.info("应用消息发送成功, userId={}", userId);
} else {
log.error("应用消息发送失败: {}", resp);
}
} catch (Exception e) {
log.error("发送应用消息异常, userId={}", userId, e);
}
}
} }
@@ -35,7 +35,9 @@ public class WxApprovalService {
private final WxCorpConfig wxCorpConfig; private final WxCorpConfig wxCorpConfig;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
/** 获取企业微信 access_token */ /**
* 获取企业微信 access_token
*/
public String getAccessToken() { public String getAccessToken() {
String url = String.format(GET_TOKEN_URL, wxCorpConfig.getCorpid(), wxCorpConfig.getCorpsecret()); String url = String.format(GET_TOKEN_URL, wxCorpConfig.getCorpid(), wxCorpConfig.getCorpsecret());
try { try {
@@ -49,7 +51,9 @@ public class WxApprovalService {
} }
} }
/** 提交审批申请 */ /**
* 提交审批申请
*/
public String submitApproval(String creatorUserId, String visitorName, String visitorPhone, public String submitApproval(String creatorUserId, String visitorName, String visitorPhone,
String visitorCompany, String visitPurpose, String visitTime, String visitorCompany, String visitPurpose, String visitTime,
String visiteeName, String visitArea) { String visiteeName, String visitArea) {
@@ -77,31 +81,7 @@ public class WxApprovalService {
} }
} }
/** 查询审批状态 */
public ApprovalStatus getApprovalStatus(String spNo) {
String url = String.format(DETAIL_URL, getAccessToken());
try {
String json = objectMapper.writeValueAsString(Map.of("sp_no", spNo));
Map<String, Object> resp = objectMapper.readValue(HttpUtil.postJson(url, json), Map.class);
if ("0".equals(String.valueOf(resp.get("errcode")))) {
@SuppressWarnings("unchecked")
Map<String, Object> info = (Map<String, Object>) resp.get("info");
ApprovalStatus status = new ApprovalStatus();
if (info != null) {
status.setSpNo((String) info.get("sp_no"));
status.setSpStatus((Integer) info.get("sp_status"));
status.setSpStatusText(toStatusText((Integer) info.get("sp_status")));
}
return status;
}
throw new RuntimeException("审批状态查询失败: " + resp);
} catch (Exception e) {
throw new RuntimeException("查询审批状态异常: " + e.getMessage(), e);
}
}
// ---- 内部方法 ---- // ---- 内部方法 ----
private Map<String, Object> buildApplyData(String visitorName, String visitorPhone, private Map<String, Object> buildApplyData(String visitorName, String visitorPhone,
String visitorCompany, String visitPurpose, String visitorCompany, String visitPurpose,
String visitTime, String visiteeName, String visitArea) { String visitTime, String visiteeName, String visitArea) {
@@ -145,17 +125,6 @@ public class WxApprovalService {
); );
} }
private static String toStatusText(Integer spStatus) {
if (spStatus == null) return "未知";
return switch (spStatus) {
case 1 -> "审批中";
case 2 -> "已通过";
case 3 -> "已拒绝";
case 4 -> "已撤销";
default -> "未知状态";
};
}
@Data @Data
public static class ApprovalStatus { public static class ApprovalStatus {
private String spNo; private String spNo;
+2
View File
@@ -29,6 +29,8 @@ wx:
callback-url: http://your-domain.com/api/wx-corp/approval-callback callback-url: http://your-domain.com/api/wx-corp/approval-callback
# 审批申请人用户ID(提交审批的企微用户) # 审批申请人用户ID(提交审批的企微用户)
creator-userid: ChengLiJuan creator-userid: ChengLiJuan
# 访客预约应用AgentId(用于发送应用消息)
agentid: 1000006
# 【访客预约】token # 【访客预约】token
@@ -11,16 +11,18 @@
<result column="visit_date" property="visitDate"/> <result column="visit_date" property="visitDate"/>
<result column="visit_time" property="visitTime"/> <result column="visit_time" property="visitTime"/>
<result column="host_name" property="hostName"/> <result column="host_name" property="hostName"/>
<result column="reception_person_id" property="receptionPersonId"/>
<result column="area" property="area"/> <result column="area" property="area"/>
<result column="status" property="status"/> <result column="status" property="status"/>
<result column="status_text" property="statusText"/> <result column="status_text" property="statusText"/>
<result column="openid" property="openid"/> <result column="openid" property="openid"/>
<result column="create_time" property="createTime"/> <result column="create_time" property="createTime"/>
<result column="sp_no" property="spNo"/> <result column="sp_no" property="spNo"/>
<result column="check_status" property="checkStatus"/>
</resultMap> </resultMap>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, name, phone, company, reason, id, name, phone, company, reason,reception_person_id,check_status,
DATE_FORMAT(visit_date, '%Y-%m-%d') AS visit_date, DATE_FORMAT(visit_date, '%Y-%m-%d') AS visit_date,
DATE_FORMAT(visit_time, '%H:%i') AS visit_time, DATE_FORMAT(visit_time, '%H:%i') AS visit_time,
host_name, area, status, status_text, openid, host_name, area, status, status_text, openid,
@@ -29,7 +31,8 @@
</sql> </sql>
<select id="selectLatestByOpenid" resultMap="BaseResultMap"> <select id="selectLatestByOpenid" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/> SELECT
<include refid="Base_Column_List"/>
FROM visit_application FROM visit_application
WHERE openid = #{openid} WHERE openid = #{openid}
ORDER BY create_time DESC ORDER BY create_time DESC
@@ -37,7 +40,8 @@
</select> </select>
<select id="selectListByOpenid" resultMap="BaseResultMap"> <select id="selectListByOpenid" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/> SELECT
<include refid="Base_Column_List"/>
FROM visit_application FROM visit_application
WHERE openid = #{openid} WHERE openid = #{openid}
ORDER BY create_time DESC ORDER BY create_time DESC
@@ -45,14 +49,16 @@
</select> </select>
<select id="selectBySpNo" resultMap="BaseResultMap"> <select id="selectBySpNo" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/> SELECT
<include refid="Base_Column_List"/>
FROM visit_application FROM visit_application
WHERE sp_no = #{spNo} WHERE sp_no = #{spNo}
ORDER BY create_time DESC ORDER BY create_time DESC
</select> </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
WHERE id = #{id} AND openid = #{openid} WHERE id = #{id} AND openid = #{openid}
</select> </select>
@@ -73,7 +79,8 @@
</update> </update>
<select id="selectById" resultMap="BaseResultMap"> <select id="selectById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/> SELECT
<include refid="Base_Column_List"/>
FROM visit_application FROM visit_application
WHERE id = #{id} WHERE id = #{id}
</select> </select>
@@ -84,6 +91,12 @@
WHERE id = #{id} AND status = 'pending' WHERE id = #{id} AND status = 'pending'
</update> </update>
<update id="updateCheckStatusById">
UPDATE visit_application
SET check_status = #{status}
WHERE id = #{id}
</update>
<update id="updateStatusBySpNo"> <update id="updateStatusBySpNo">
UPDATE visit_application UPDATE visit_application
SET status = #{status}, status_text = #{statusText} SET status = #{status}, status_text = #{statusText}