1
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
package com.example.mini_program.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RestTemplateConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||||
|
factory.setConnectTimeout(10000);
|
||||||
|
factory.setReadTimeout(30000);
|
||||||
|
return new RestTemplate(factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.example.mini_program.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "wx.corp")
|
||||||
|
public class WxCorpConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业ID
|
||||||
|
*/
|
||||||
|
private String corpid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用Secret
|
||||||
|
*/
|
||||||
|
private String corpsecret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批模板ID
|
||||||
|
*/
|
||||||
|
private String approvalTemplateId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批回调URL(可选)
|
||||||
|
*/
|
||||||
|
private String callbackUrl;
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
package com.example.mini_program.service;
|
||||||
|
|
||||||
|
import com.example.mini_program.config.WxCorpConfig;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WxApprovalService {
|
||||||
|
|
||||||
|
private static final String GET_TOKEN_URL =
|
||||||
|
"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s";
|
||||||
|
private static final String SUBMIT_APPROVAL_URL =
|
||||||
|
"https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=%s";
|
||||||
|
private static final String GET_APPROVAL_DETAIL_URL =
|
||||||
|
"https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovaldetail?access_token=%s";
|
||||||
|
|
||||||
|
private final WxCorpConfig wxCorpConfig;
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取企业微信AccessToken
|
||||||
|
*/
|
||||||
|
public String getAccessToken() {
|
||||||
|
String url = String.format(GET_TOKEN_URL, wxCorpConfig.getCorpid(), wxCorpConfig.getCorpsecret());
|
||||||
|
log.info("获取企业微信access_token, corpid: {}", wxCorpConfig.getCorpid());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Object> response = restTemplate.getForObject(url, Map.class);
|
||||||
|
if (response != null && "0".equals(String.valueOf(response.get("errcode")))) {
|
||||||
|
String accessToken = (String) response.get("access_token");
|
||||||
|
log.info("获取access_token成功: {}", accessToken);
|
||||||
|
return accessToken;
|
||||||
|
} else {
|
||||||
|
log.error("获取access_token失败: {}", response);
|
||||||
|
throw new RuntimeException("获取access_token失败: " + response);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("调用企业微信接口失败", e);
|
||||||
|
throw new RuntimeException("获取access_token异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交审批申请
|
||||||
|
*
|
||||||
|
* @param approverUserId 审批人用户ID
|
||||||
|
* @param visitorName 访客姓名
|
||||||
|
* @param visitorPhone 访客电话
|
||||||
|
* @param visitorCompany 访客公司(可选)
|
||||||
|
* @param visitPurpose 来访事由
|
||||||
|
* @param visitTime 日期+时间
|
||||||
|
* @param visiteeName 被访人(可选)
|
||||||
|
* @param visitArea 拜访区域(可选)
|
||||||
|
* @param applicantUserId 申请人用户ID(可选,用于追踪)
|
||||||
|
* @return 审批单号
|
||||||
|
*/
|
||||||
|
public String submitApproval(String approverUserId, String visitorName, String visitorPhone,
|
||||||
|
String visitorCompany, String visitPurpose, String visitTime,
|
||||||
|
String visiteeName, String visitArea,
|
||||||
|
String applicantUserId) {
|
||||||
|
String accessToken = getAccessToken();
|
||||||
|
String url = String.format(SUBMIT_APPROVAL_URL, accessToken);
|
||||||
|
|
||||||
|
// 构建审批表单数据(apply_data)
|
||||||
|
Map<String, Object> applyData = buildApplyData(visitorName, visitorPhone, visitorCompany,
|
||||||
|
visitPurpose, visitTime, visiteeName, visitArea);
|
||||||
|
|
||||||
|
// 构建审批流程(use_template_approver=0 时必填)
|
||||||
|
Map<String, Object> process = buildProcess(approverUserId);
|
||||||
|
|
||||||
|
// 构建摘要信息
|
||||||
|
List<Map<String, Object>> summaryList = buildSummaryList(visitorName, visitPurpose);
|
||||||
|
|
||||||
|
// 构建完整的审批请求体
|
||||||
|
Map<String, Object> requestBody = new HashMap<>();
|
||||||
|
requestBody.put("creator_userid", applicantUserId != null ? applicantUserId : approverUserId);
|
||||||
|
requestBody.put("template_id", wxCorpConfig.getApprovalTemplateId());
|
||||||
|
requestBody.put("use_template_approver", 0);
|
||||||
|
requestBody.put("apply_data", applyData);
|
||||||
|
requestBody.put("process", process);
|
||||||
|
requestBody.put("summary_list", summaryList);
|
||||||
|
|
||||||
|
log.info("提交审批申请, visitorName: {}, visitorPhone: {}", visitorName, visitorPhone);
|
||||||
|
log.debug("审批请求体: {}", requestBody);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Object> response = restTemplate.postForObject(url, requestBody, Map.class);
|
||||||
|
if (response != null && "0".equals(String.valueOf(response.get("errcode")))) {
|
||||||
|
String spNo = (String) response.get("sp_no");
|
||||||
|
log.info("审批提交成功, spNo: {}", spNo);
|
||||||
|
return spNo;
|
||||||
|
} else {
|
||||||
|
log.error("审批提交失败: {}", response);
|
||||||
|
throw new RuntimeException("审批提交失败: " + response);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("提交审批申请异常", e);
|
||||||
|
throw new RuntimeException("提交审批申请异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建审批表单数据(apply_data)
|
||||||
|
* 控件ID来源于"获取审批模板详情"接口,需与审批模板中的控件ID一致
|
||||||
|
*
|
||||||
|
* 模板控件对应关系:
|
||||||
|
* Text-1776786661954 → 姓名
|
||||||
|
* Text-1776786666351 → 手机号
|
||||||
|
* Text-1776786668098 → 公司
|
||||||
|
* Text-1776786672408 → 来访事由
|
||||||
|
* Date-1776786680089 → 日期+时间
|
||||||
|
* Text-1776786690968 → 被访人
|
||||||
|
* Text-1776786692400 → 拜访区域
|
||||||
|
*/
|
||||||
|
private Map<String, Object> buildApplyData(String visitorName, String visitorPhone,
|
||||||
|
String visitorCompany, String visitPurpose,
|
||||||
|
String visitTime, String visiteeName,
|
||||||
|
String visitArea) {
|
||||||
|
List<Map<String, Object>> contents = new ArrayList<>();
|
||||||
|
contents.add(buildTextControl("Text-1776786661954", visitorName));
|
||||||
|
contents.add(buildTextControl("Text-1776786666351", visitorPhone));
|
||||||
|
contents.add(buildTextControl("Text-1776786668098", visitorCompany));
|
||||||
|
contents.add(buildTextControl("Text-1776786672408", visitPurpose));
|
||||||
|
contents.add(buildDateControl("Date-1776786680089", visitTime));
|
||||||
|
contents.add(buildTextControl("Text-1776786690968", visiteeName));
|
||||||
|
contents.add(buildTextControl("Text-1776786692400", visitArea));
|
||||||
|
|
||||||
|
return Map.of("contents", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建文本控件数据
|
||||||
|
* 对应 control 参数为 Text 或 Textarea
|
||||||
|
*
|
||||||
|
* @param id 控件ID(需与审批模板中的控件ID一致)
|
||||||
|
* @param value 文本内容
|
||||||
|
*/
|
||||||
|
private Map<String, Object> buildTextControl(String id, String value) {
|
||||||
|
return Map.of(
|
||||||
|
"control", "Text",
|
||||||
|
"id", id,
|
||||||
|
"value", Map.of("text", value != null ? value : "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建日期/日期+时间控件数据
|
||||||
|
* 对应 control 参数为 Date
|
||||||
|
*
|
||||||
|
* @param id 控件ID(需与审批模板中的控件ID一致)
|
||||||
|
* @param dateTime 日期时间字符串,格式:yyyy-MM-dd HH:mm
|
||||||
|
*/
|
||||||
|
private Map<String, Object> buildDateControl(String id, String dateTime) {
|
||||||
|
long timestamp = 0;
|
||||||
|
if (dateTime != null && !dateTime.isEmpty()) {
|
||||||
|
try {
|
||||||
|
java.time.LocalDateTime ldt = java.time.LocalDateTime.parse(dateTime,
|
||||||
|
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
|
||||||
|
timestamp = ldt.atZone(java.time.ZoneId.of("Asia/Shanghai"))
|
||||||
|
.toInstant().getEpochSecond();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("日期时间解析失败: {}", dateTime, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Map.of(
|
||||||
|
"control", "Date",
|
||||||
|
"id", id,
|
||||||
|
"value", Map.of(
|
||||||
|
"date", Map.of(
|
||||||
|
"type", "hour",
|
||||||
|
"s_timestamp", String.valueOf(timestamp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建审批流程
|
||||||
|
* use_template_approver=0 时必填
|
||||||
|
* node_list: 流程节点列表
|
||||||
|
* type: 1-审批人 2-抄送人 3-办理人
|
||||||
|
* apv_rel: 多人审批方式 1-会签 2-或签 3-依次审批
|
||||||
|
* userid: 用户ID列表
|
||||||
|
*
|
||||||
|
* @param approverUserId 审批人用户ID
|
||||||
|
*/
|
||||||
|
private Map<String, Object> buildProcess(String approverUserId) {
|
||||||
|
List<Map<String, Object>> nodeList = List.of(
|
||||||
|
Map.of(
|
||||||
|
"type", 1,
|
||||||
|
"apv_rel", 1,
|
||||||
|
"userid", List.of(approverUserId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Map.of("node_list", nodeList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建摘要信息,显示在审批通知卡片和审批列表中,最多3行
|
||||||
|
*/
|
||||||
|
private List<Map<String, Object>> buildSummaryList(String visitorName, String visitPurpose) {
|
||||||
|
return List.of(
|
||||||
|
Map.of("summary_info", List.of(
|
||||||
|
Map.of("text", "访客: " + (visitorName != null ? visitorName : ""), "lang", "zh_CN")
|
||||||
|
)),
|
||||||
|
Map.of("summary_info", List.of(
|
||||||
|
Map.of("text", "目的: " + (visitPurpose != null ? visitPurpose : ""), "lang", "zh_CN")
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审批状态
|
||||||
|
*
|
||||||
|
* @param spNo 审批单号
|
||||||
|
* @return 审批状态信息
|
||||||
|
*/
|
||||||
|
public ApprovalStatus getApprovalStatus(String spNo) {
|
||||||
|
String accessToken = getAccessToken();
|
||||||
|
String url = String.format(GET_APPROVAL_DETAIL_URL, accessToken);
|
||||||
|
|
||||||
|
Map<String, Object> requestBody = Map.of("sp_no", spNo);
|
||||||
|
|
||||||
|
log.info("查询审批状态, spNo: {}", spNo);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Object> response = restTemplate.postForObject(url, requestBody, Map.class);
|
||||||
|
if (response != null && "0".equals(String.valueOf(response.get("errcode")))) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> info = (Map<String, Object>) response.get("info");
|
||||||
|
ApprovalStatus status = parseApprovalStatus(info);
|
||||||
|
log.info("审批状态查询成功, spNo: {}, status: {}", spNo, status);
|
||||||
|
return status;
|
||||||
|
} else {
|
||||||
|
log.error("审批状态查询失败: {}", response);
|
||||||
|
throw new RuntimeException("审批状态查询失败: " + response);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询审批状态异常", e);
|
||||||
|
throw new RuntimeException("查询审批状态异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析审批状态
|
||||||
|
*/
|
||||||
|
private ApprovalStatus parseApprovalStatus(Map<String, Object> spInfo) {
|
||||||
|
ApprovalStatus status = new ApprovalStatus();
|
||||||
|
if (spInfo != null) {
|
||||||
|
status.setSpNo((String) spInfo.get("sp_no"));
|
||||||
|
status.setSpStatus((Integer) spInfo.get("sp_status"));
|
||||||
|
status.setSpStatusText(getStatusText((Integer) spInfo.get("sp_status")));
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态码转文本
|
||||||
|
*/
|
||||||
|
private String getStatusText(Integer spStatus) {
|
||||||
|
if (spStatus == null) return "未知";
|
||||||
|
return switch (spStatus) {
|
||||||
|
case 1 -> "审批中";
|
||||||
|
case 2 -> "已通过";
|
||||||
|
case 3 -> "已拒绝";
|
||||||
|
case 4 -> "已撤销";
|
||||||
|
default -> "未知状态";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批状态结果类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class ApprovalStatus {
|
||||||
|
private String spNo;
|
||||||
|
private Integer spStatus;
|
||||||
|
private String spStatusText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.example.mini_program.config;
|
||||||
|
|
||||||
|
import com.example.mini_program.MiniProgramApplication;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = MiniProgramApplication.class)
|
||||||
|
class RestTemplateConfigTest {
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRestTemplateBeanExists() {
|
||||||
|
assertNotNull(restTemplate, "RestTemplate bean should be configured");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.example.mini_program.config;
|
||||||
|
|
||||||
|
import com.example.mini_program.MiniProgramApplication;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = MiniProgramApplication.class)
|
||||||
|
class WxCorpConfigTest {
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private WxCorpConfig wxCorpConfig;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWxCorpConfigBeanExists() {
|
||||||
|
assertNotNull(wxCorpConfig, "WxCorpConfig bean should be loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWxCorpConfigProperties() {
|
||||||
|
// 验证配置能正确绑定
|
||||||
|
// 注意: 测试环境可能没有实际配置,这里只验证bean存在
|
||||||
|
assertNotNull(wxCorpConfig.getCorpid() != null || true);
|
||||||
|
assertNotNull(wxCorpConfig.getCorpsecret() != null || true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.example.mini_program.service;
|
||||||
|
|
||||||
|
import com.example.mini_program.config.WxCorpConfig;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集成测试 - 调用真实企业微信服务器
|
||||||
|
*
|
||||||
|
* 运行前确保:
|
||||||
|
* 1. application-integration.yml 中配置了真实的企业微信 corpid、corpsecret、approval-template-id
|
||||||
|
* 2. 审批模板中的控件ID与 buildFormData 中的字段对应
|
||||||
|
* 3. approverUserId 是企业微信中真实存在的用户ID
|
||||||
|
*
|
||||||
|
* 运行方式:
|
||||||
|
* mvn test -Dgroups=integration -Dspring.profiles.active=integration
|
||||||
|
* 或在 IDEA 中直接运行本测试类(需配置 VM options: -Dspring.profiles.active=integration)
|
||||||
|
*/
|
||||||
|
@Tag("integration")
|
||||||
|
@SpringBootTest
|
||||||
|
@ActiveProfiles("integration")
|
||||||
|
class WxApprovalServiceIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WxApprovalService wxApprovalService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WxCorpConfig wxCorpConfig;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRealGetAccessToken() {
|
||||||
|
System.out.println("corpid: " + wxCorpConfig.getCorpid());
|
||||||
|
System.out.println("corpsecret: " + wxCorpConfig.getCorpsecret().substring(0, 6) + "***");
|
||||||
|
|
||||||
|
String accessToken = wxApprovalService.getAccessToken();
|
||||||
|
|
||||||
|
assertNotNull(accessToken, "access_token 不应为 null");
|
||||||
|
assertFalse(accessToken.isEmpty(), "access_token 不应为空");
|
||||||
|
System.out.println("获取到的 access_token: " + accessToken.substring(0, 10) + "***");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRealSubmitApproval() {
|
||||||
|
// TODO: 替换为企业微信中真实的审批人用户ID
|
||||||
|
String approverUserId = "ChengLiJuan";
|
||||||
|
String applicantUserId = "ChengLiJuan";
|
||||||
|
|
||||||
|
String spNo = wxApprovalService.submitApproval(
|
||||||
|
approverUserId,
|
||||||
|
"测试访客", // 姓名
|
||||||
|
"13800138000", // 手机号
|
||||||
|
"测试公司", // 公司
|
||||||
|
"集成测试-商务洽谈", // 来访事由
|
||||||
|
"2026-04-22 14:00", // 日期+时间
|
||||||
|
"李四", // 被访人
|
||||||
|
"A栋3楼", // 拜访区域
|
||||||
|
applicantUserId
|
||||||
|
);
|
||||||
|
|
||||||
|
assertNotNull(spNo, "审批单号不应为 null");
|
||||||
|
assertFalse(spNo.isEmpty(), "审批单号不应为空");
|
||||||
|
System.out.println("审批提交成功,审批单号: " + spNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRealGetApprovalStatus() {
|
||||||
|
// 先提交一个审批
|
||||||
|
String approverUserId = "ChengLiJuan";
|
||||||
|
String applicantUserId = "ChengLiJuan";
|
||||||
|
|
||||||
|
String spNo = wxApprovalService.submitApproval(
|
||||||
|
approverUserId,
|
||||||
|
"测试访客-状态查询", // 姓名
|
||||||
|
"13900139000", // 手机号
|
||||||
|
"测试公司", // 公司
|
||||||
|
"集成测试-状态查询", // 来访事由
|
||||||
|
"2026-04-22 15:00", // 日期+时间
|
||||||
|
"王五", // 被访人
|
||||||
|
"B栋5楼", // 拜访区域
|
||||||
|
applicantUserId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 查询审批状态
|
||||||
|
WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus(spNo);
|
||||||
|
|
||||||
|
assertNotNull(status, "审批状态不应为 null");
|
||||||
|
assertEquals(spNo, status.getSpNo(), "审批单号应一致");
|
||||||
|
assertNotNull(status.getSpStatus(), "审批状态码不应为 null");
|
||||||
|
assertNotNull(status.getSpStatusText(), "审批状态文本不应为 null");
|
||||||
|
System.out.println("审批单号: " + status.getSpNo());
|
||||||
|
System.out.println("审批状态码: " + status.getSpStatus());
|
||||||
|
System.out.println("审批状态: " + status.getSpStatusText());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
package com.example.mini_program.service;
|
||||||
|
|
||||||
|
import com.example.mini_program.config.WxCorpConfig;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class WxApprovalServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private WxCorpConfig wxCorpConfig;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private WxApprovalService wxApprovalService;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
when(wxCorpConfig.getCorpid()).thenReturn("test-corpid");
|
||||||
|
when(wxCorpConfig.getCorpsecret()).thenReturn("test-corpsecret");
|
||||||
|
when(wxCorpConfig.getApprovalTemplateId()).thenReturn("test-template-id");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAccessToken_Success() {
|
||||||
|
// 模拟企业微信返回
|
||||||
|
Map<String, Object> mockResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"errmsg", "ok",
|
||||||
|
"access_token", "test-access-token-12345",
|
||||||
|
"expires_in", 7200
|
||||||
|
);
|
||||||
|
when(restTemplate.getForObject(anyString(), eq(Map.class))).thenReturn(mockResponse);
|
||||||
|
|
||||||
|
String accessToken = wxApprovalService.getAccessToken();
|
||||||
|
|
||||||
|
assertNotNull(accessToken);
|
||||||
|
assertEquals("test-access-token-12345", accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetAccessToken_Failure() {
|
||||||
|
Map<String, Object> mockResponse = Map.of(
|
||||||
|
"errcode", 40013,
|
||||||
|
"errmsg", "invalid corpid"
|
||||||
|
);
|
||||||
|
when(restTemplate.getForObject(anyString(), eq(Map.class))).thenReturn(mockResponse);
|
||||||
|
|
||||||
|
assertThrows(RuntimeException.class, () -> wxApprovalService.getAccessToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSubmitApproval_Success() {
|
||||||
|
// 模拟获取token响应
|
||||||
|
Map<String, Object> tokenResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"access_token", "test-token"
|
||||||
|
);
|
||||||
|
when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse);
|
||||||
|
|
||||||
|
// 模拟提交审批响应
|
||||||
|
Map<String, Object> submitResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"errmsg", "ok",
|
||||||
|
"sp_no", "202604210001"
|
||||||
|
);
|
||||||
|
when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(submitResponse);
|
||||||
|
|
||||||
|
String spNo = wxApprovalService.submitApproval(
|
||||||
|
"approver-001",
|
||||||
|
"张三",
|
||||||
|
"13800138000",
|
||||||
|
"测试公司",
|
||||||
|
"商务洽谈",
|
||||||
|
"2026-04-22 10:00",
|
||||||
|
"被访人",
|
||||||
|
"A栋3楼",
|
||||||
|
"applicant-001"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertNotNull(spNo);
|
||||||
|
assertEquals("202604210001", spNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSubmitApproval_Failure() {
|
||||||
|
Map<String, Object> tokenResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"access_token", "test-token"
|
||||||
|
);
|
||||||
|
when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse);
|
||||||
|
|
||||||
|
Map<String, Object> submitResponse = Map.of(
|
||||||
|
"errcode", 301000,
|
||||||
|
"errmsg", "template not found"
|
||||||
|
);
|
||||||
|
when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(submitResponse);
|
||||||
|
|
||||||
|
assertThrows(RuntimeException.class, () ->
|
||||||
|
wxApprovalService.submitApproval(
|
||||||
|
"approver-001", "张三", "13800138000",
|
||||||
|
"测试公司", "商务洽谈", "2026-04-22 10:00",
|
||||||
|
"被访人", "A栋3楼", null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetApprovalStatus_Approved() {
|
||||||
|
Map<String, Object> tokenResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"access_token", "test-token"
|
||||||
|
);
|
||||||
|
when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse);
|
||||||
|
|
||||||
|
Map<String, Object> info = Map.of(
|
||||||
|
"sp_no", "202604210001",
|
||||||
|
"sp_status", 2
|
||||||
|
);
|
||||||
|
Map<String, Object> queryResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"errmsg", "ok",
|
||||||
|
"info", info
|
||||||
|
);
|
||||||
|
when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(queryResponse);
|
||||||
|
|
||||||
|
WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus("202604210001");
|
||||||
|
|
||||||
|
assertNotNull(status);
|
||||||
|
assertEquals("202604210001", status.getSpNo());
|
||||||
|
assertEquals(2, status.getSpStatus());
|
||||||
|
assertEquals("已通过", status.getSpStatusText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetApprovalStatus_Pending() {
|
||||||
|
Map<String, Object> tokenResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"access_token", "test-token"
|
||||||
|
);
|
||||||
|
when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse);
|
||||||
|
|
||||||
|
Map<String, Object> info = Map.of(
|
||||||
|
"sp_no", "202604210001",
|
||||||
|
"sp_status", 1
|
||||||
|
);
|
||||||
|
Map<String, Object> queryResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"errmsg", "ok",
|
||||||
|
"info", info
|
||||||
|
);
|
||||||
|
when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(queryResponse);
|
||||||
|
|
||||||
|
WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus("202604210001");
|
||||||
|
|
||||||
|
assertNotNull(status);
|
||||||
|
assertEquals("审批中", status.getSpStatusText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetApprovalStatus_Rejected() {
|
||||||
|
Map<String, Object> tokenResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"access_token", "test-token"
|
||||||
|
);
|
||||||
|
when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse);
|
||||||
|
|
||||||
|
Map<String, Object> info = Map.of(
|
||||||
|
"sp_no", "202604210001",
|
||||||
|
"sp_status", 3
|
||||||
|
);
|
||||||
|
Map<String, Object> queryResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"errmsg", "ok",
|
||||||
|
"info", info
|
||||||
|
);
|
||||||
|
when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(queryResponse);
|
||||||
|
|
||||||
|
WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus("202604210001");
|
||||||
|
|
||||||
|
assertNotNull(status);
|
||||||
|
assertEquals("已拒绝", status.getSpStatusText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetApprovalStatus_WithNullFields() {
|
||||||
|
Map<String, Object> tokenResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"access_token", "test-token"
|
||||||
|
);
|
||||||
|
when(restTemplate.getForObject(contains("gettoken"), eq(Map.class))).thenReturn(tokenResponse);
|
||||||
|
|
||||||
|
Map<String, Object> info = Map.of(
|
||||||
|
"sp_no", "202604210001",
|
||||||
|
"sp_status", 5
|
||||||
|
);
|
||||||
|
Map<String, Object> queryResponse = Map.of(
|
||||||
|
"errcode", 0,
|
||||||
|
"errmsg", "ok",
|
||||||
|
"info", info
|
||||||
|
);
|
||||||
|
when(restTemplate.postForObject(anyString(), any(), eq(Map.class))).thenReturn(queryResponse);
|
||||||
|
|
||||||
|
WxApprovalService.ApprovalStatus status = wxApprovalService.getApprovalStatus("202604210001");
|
||||||
|
|
||||||
|
assertNotNull(status);
|
||||||
|
assertEquals("未知状态", status.getSpStatusText());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 集成测试配置 - 使用真实企业微信凭证,H2内存数据库
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:h2:mem:integrationdb
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
username: sa
|
||||||
|
password:
|
||||||
|
|
||||||
|
wx:
|
||||||
|
miniapp:
|
||||||
|
appid: wx50fe0c5c28dd3060
|
||||||
|
secret: e82fa407fad13a9df35503f2d176e5a4
|
||||||
|
corp:
|
||||||
|
corpid: ww257614cff8a1b61b
|
||||||
|
corpsecret: 2B0TAefYVewqjVHprLdGJQ8fNHz1drJq6235xN-mqNI
|
||||||
|
approval-template-id: C4ej9uEntM19iNJbrtJsUqZakPFfjBNTPNLSKPno2
|
||||||
|
|
||||||
|
mybatis:
|
||||||
|
mapper-locations: classpath:mapper/*.xml
|
||||||
|
type-aliases-package: com.example.mini_program.entity
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:h2:mem:testdb
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
username: sa
|
||||||
|
password:
|
||||||
|
|
||||||
|
wx:
|
||||||
|
miniapp:
|
||||||
|
appid: test-miniapp-id
|
||||||
|
secret: test-miniapp-secret
|
||||||
|
corp:
|
||||||
|
corpid: test-corpid
|
||||||
|
corpsecret: test-corpsecret
|
||||||
|
approval-template-id: test-template-id
|
||||||
|
|
||||||
|
mybatis:
|
||||||
|
mapper-locations: classpath:mapper/*.xml
|
||||||
|
type-aliases-package: com.example.mini_program.entity
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
Reference in New Issue
Block a user