1. 新增分页接口聚合查询注解支持

2. 优化 databus api 日志记录的字段缺失问题
3. 新增 eplat sso 页面登录校验
4. 用户、部门编辑新增 seata 事务支持
5. 新增 iwork 流程发起接口
6. 新增 eban 同步用户时的岗位处理逻辑
7. 新增无 skywalking 时的 traceId 支持
This commit is contained in:
chenbowen
2025-11-18 10:03:34 +08:00
parent af7f103a38
commit 266eb45e00
74 changed files with 5001 additions and 102 deletions

View File

@@ -1,6 +1,8 @@
package com.zt.plat.module.system.service.auth;
import cn.hutool.core.util.ReflectUtil;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.service.CaptchaService;
import com.zt.plat.framework.common.enums.CommonStatusEnum;
import com.zt.plat.framework.common.enums.UserTypeEnum;
import com.zt.plat.framework.test.core.ut.BaseDbUnitTest;
@@ -15,13 +17,9 @@ import com.zt.plat.module.system.enums.logger.LoginResultEnum;
import com.zt.plat.module.system.enums.sms.SmsSceneEnum;
import com.zt.plat.module.system.enums.social.SocialTypeEnum;
import com.zt.plat.module.system.service.logger.LoginLogService;
import com.zt.plat.module.system.service.member.MemberService;
import com.zt.plat.module.system.service.oauth2.EbanOAuth2Service;
import com.zt.plat.module.system.service.oauth2.OAuth2TokenService;
import com.zt.plat.module.system.service.social.SocialUserService;
import com.zt.plat.module.system.service.user.AdminUserService;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.service.CaptchaService;
import jakarta.annotation.Resource;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
@@ -60,10 +58,6 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
@MockBean
private OAuth2TokenService oauth2TokenService;
@MockBean
private MemberService memberService;
@MockBean
private EbanOAuth2Service ebanOAuth2Service;
@MockBean
private Validator validator;
@BeforeEach

View File

@@ -0,0 +1,228 @@
package com.zt.plat.module.system.service.integration.iwork.impl;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDetailRecordVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkDetailTableVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFormFieldVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkOperationRespVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoReqVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkUserInfoRespVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCreateReqVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowVoidReqVO;
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.web.reactive.function.client.WebClient;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class IWorkIntegrationServiceImplTest {
private static KeyPair serverKeyPair;
private static String serverPublicKeyBase64;
private static String clientPublicKeyBase64;
private MockWebServer mockWebServer;
private IWorkIntegrationService integrationService;
private IWorkProperties properties;
@BeforeAll
static void initKeys() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(1024);
serverKeyPair = generator.generateKeyPair();
serverPublicKeyBase64 = Base64.getEncoder().encodeToString(serverKeyPair.getPublic().getEncoded());
KeyPair clientKeyPair = generator.generateKeyPair();
clientPublicKeyBase64 = Base64.getEncoder().encodeToString(clientKeyPair.getPublic().getEncoded());
}
@BeforeEach
void setUp() throws Exception {
mockWebServer = new MockWebServer();
mockWebServer.start();
properties = buildProperties();
WebClient.Builder builder = WebClient.builder();
ObjectMapper objectMapper = new ObjectMapper();
integrationService = new IWorkIntegrationServiceImpl(properties, objectMapper, builder);
}
@AfterEach
void tearDown() throws Exception {
mockWebServer.shutdown();
}
@Test
void testWorkflowLifecycle() throws Exception {
enqueueRegisterResponse();
enqueueApplyTokenResponse();
enqueueJsonResponse("{\"code\":1,\"userid\":\"1001\",\"msg\":\"OK\"}");
enqueueJsonResponse("{\"code\":\"1\",\"requestid\":\"REQ-001\",\"msg\":\"created\"}");
enqueueJsonResponse("{\"code\":\"1\",\"msg\":\"voided\"}");
IWorkUserInfoReqVO userReq = new IWorkUserInfoReqVO();
userReq.setIdentifierKey("loginid");
userReq.setIdentifierValue("zhangsan");
IWorkUserInfoRespVO userResp = integrationService.resolveUserId(userReq);
assertThat(userResp.isSuccess()).isTrue();
assertThat(userResp.getUserId()).isEqualTo("1001");
IWorkWorkflowCreateReqVO createReq = buildCreateRequest();
IWorkOperationRespVO createResp = integrationService.createWorkflow(createReq);
assertThat(createResp.isSuccess()).isTrue();
assertThat(createResp.getPayload().get("requestid")).isEqualTo("REQ-001");
IWorkWorkflowVoidReqVO voidReq = new IWorkWorkflowVoidReqVO();
voidReq.setRequestId("REQ-001");
voidReq.setReason("testing void");
IWorkOperationRespVO voidResp = integrationService.voidWorkflow(voidReq);
assertThat(voidResp.isSuccess()).isTrue();
verifyRegisterRequest(mockWebServer.takeRequest());
verifyApplyTokenRequest(mockWebServer.takeRequest());
verifyUserInfoRequest(mockWebServer.takeRequest());
verifyCreateRequest(mockWebServer.takeRequest());
verifyVoidRequest(mockWebServer.takeRequest());
assertThat(mockWebServer.getRequestCount()).isEqualTo(5);
}
private IWorkProperties buildProperties() {
IWorkProperties properties = new IWorkProperties();
properties.setEnabled(true);
properties.setBaseUrl(mockWebServer.url("/").toString());
properties.setAppId("test-app");
properties.setClientPublicKey(clientPublicKeyBase64);
properties.setUserId("1");
properties.setWorkflowId(54L);
properties.getToken().setTtlSeconds(3600L);
properties.getToken().setRefreshAheadSeconds(30L);
properties.getClient().setResponseTimeout(Duration.ofSeconds(5));
properties.getPaths().setRegister("/api/ec/dev/auth/regist");
properties.getPaths().setApplyToken("/api/ec/dev/auth/applytoken");
properties.getPaths().setUserInfo("/api/workflow/paService/getUserInfo");
properties.getPaths().setCreateWorkflow("/api/workflow/paService/doCreateRequest");
properties.getPaths().setVoidWorkflow("/api/workflow/paService/doCancelRequest");
properties.getClient().setConnectTimeout(Duration.ofSeconds(5));
return properties;
}
private void verifyRegisterRequest(RecordedRequest request) {
assertThat(request.getPath()).isEqualTo("/api/ec/dev/auth/regist");
assertThat(request.getHeader(properties.getHeaders().getAppId())).isEqualTo("test-app");
assertThat(request.getHeader(properties.getHeaders().getClientPublicKey())).isEqualTo(clientPublicKeyBase64);
}
private void verifyApplyTokenRequest(RecordedRequest request) throws Exception {
assertThat(request.getPath()).isEqualTo("/api/ec/dev/auth/applytoken");
assertThat(request.getHeader(properties.getHeaders().getAppId())).isEqualTo("test-app");
assertThat(request.getHeader(properties.getHeaders().getTime())).isEqualTo("3600");
String decryptedSecret = decryptHeader(request.getHeader(properties.getHeaders().getSecret()));
assertThat(decryptedSecret).isEqualTo("plain-secret");
}
private void verifyUserInfoRequest(RecordedRequest request) throws Exception {
assertThat(request.getPath()).isEqualTo("/api/workflow/paService/getUserInfo");
assertThat(request.getHeader(properties.getHeaders().getToken())).isEqualTo("token-123");
String decryptedUserId = decryptHeader(request.getHeader(properties.getHeaders().getUserId()));
assertThat(decryptedUserId).isEqualTo("1");
String body = request.getBody().readUtf8();
assertThat(body).contains("loginid");
assertThat(body).contains("zhangsan");
}
private void verifyCreateRequest(RecordedRequest request) throws Exception {
assertThat(request.getPath()).isEqualTo("/api/workflow/paService/doCreateRequest");
assertThat(request.getHeader(properties.getHeaders().getToken())).isEqualTo("token-123");
String decryptedUserId = decryptHeader(request.getHeader(properties.getHeaders().getUserId()));
assertThat(decryptedUserId).isEqualTo("1");
String body = request.getBody().readUtf8();
assertThat(body).contains("requestName=测试流程");
assertThat(body).contains("workflowId=54");
assertThat(body).contains("mainData=%5B");
}
private void verifyVoidRequest(RecordedRequest request) throws Exception {
assertThat(request.getPath()).isEqualTo("/api/workflow/paService/doCancelRequest");
assertThat(request.getHeader(properties.getHeaders().getToken())).isEqualTo("token-123");
String decryptedUserId = decryptHeader(request.getHeader(properties.getHeaders().getUserId()));
assertThat(decryptedUserId).isEqualTo("1");
String body = request.getBody().readUtf8();
assertThat(body).contains("requestId=REQ-001");
assertThat(body).contains("remark=testing+void");
}
private void enqueueRegisterResponse() {
enqueueJsonResponse("{" +
"\"secret\":\"plain-secret\"," +
"\"spk\":\"" + serverPublicKeyBase64 + "\"}");
}
private void enqueueApplyTokenResponse() {
enqueueJsonResponse("{\"token\":\"token-123\",\"expire\":3600}");
}
private void enqueueJsonResponse(String body) {
mockWebServer.enqueue(new MockResponse()
.setHeader("Content-Type", "application/json")
.setBody(body));
}
private IWorkWorkflowCreateReqVO buildCreateRequest() {
IWorkFormFieldVO field1 = new IWorkFormFieldVO();
field1.setFieldName("sqr");
field1.setFieldValue("张三");
IWorkFormFieldVO field2 = new IWorkFormFieldVO();
field2.setFieldName("sqrq");
field2.setFieldValue("2023-11-02");
IWorkDetailRecordVO detailRecord = new IWorkDetailRecordVO();
detailRecord.setRecordOrder(0);
IWorkFormFieldVO detailField = new IWorkFormFieldVO();
detailField.setFieldName("ddh");
detailField.setFieldValue("100010");
detailRecord.setFields(List.of(detailField));
IWorkDetailTableVO detailTable = new IWorkDetailTableVO();
detailTable.setTableDBName("formtable_main_26_dt1");
detailTable.setRecords(List.of(detailRecord));
IWorkWorkflowCreateReqVO req = new IWorkWorkflowCreateReqVO();
req.setRequestName("测试流程");
req.setMainFields(List.of(field1, field2));
req.setDetailTables(List.of(detailTable));
req.setOtherParams(Map.of("isnextflow", "0"));
return req;
}
private String decryptHeader(String headerValue) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, serverKeyPair.getPrivate());
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(headerValue));
return new String(decrypted, StandardCharsets.UTF_8);
}
}

View File

@@ -0,0 +1,44 @@
package com.zt.plat.module.system.service.sso.client;
import com.zt.plat.module.system.framework.sso.config.ExternalSsoProperties;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.StringRedisTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* 仅加载 ExternalSsoClientConfiguration校验上下文能成功创建 ExternalSsoClient Bean。
* 使用 Mock 的 RedisTemplate避免外部依赖。
*/
@SpringBootTest(classes = { ExternalSsoClientConfiguration.class, ExternalSsoClientConfigurationLoadTest.TestBeans.class })
@Import(ExternalSsoProperties.class)
class ExternalSsoClientConfigurationLoadTest {
@TestConfiguration
static class TestBeans {
@Bean
public StringRedisTemplate stringRedisTemplate() {
return mock(StringRedisTemplate.class);
}
@Bean
public ExternalSsoProperties externalSsoProperties() {
ExternalSsoProperties props = new ExternalSsoProperties();
// 提供必要的基础配置,避免初始化过程出现 NPE
props.getRemote().setBaseUrl("http://localhost");
return props;
}
}
@Test
void contextLoads(org.springframework.context.ApplicationContext context) {
Object bean = context.getBean(ExternalSsoClient.class);
assertThat(bean).isNotNull();
assertThat(bean).isInstanceOf(DefaultExternalSsoClient.class);
}
}

View File

@@ -13,10 +13,7 @@ import com.zt.plat.module.system.service.userdept.UserDeptService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.*;
import java.util.List;
import java.util.Set;
@@ -57,25 +54,60 @@ public class UserSyncServiceImplTest extends BaseMockitoUnitTest {
securityFrameworkUtilsMock.close();
}
// @Test
// void testCreateUser() {
// // Arrange
// UserCreateRequestVO requestVO = randomPojo(UserCreateRequestVO.class);
// Long newUserId = randomLongId();
// when(adminUserService.createUser(any(UserSaveReqVO.class))).thenReturn(newUserId);
//
// // Act
// UserCreateResponseVO response = userSyncService.createUser(requestVO);
//
// // Assert
// assertNotNull(response);
// assertEquals("0", response.getResultCode());
// assertEquals("success", response.getMessage());
// assertEquals(String.valueOf(newUserId), response.getUid());
// assertEquals(requestVO.getBimRequestId(), response.getBimRequestId());
//
// verify(adminUserService).createUser(any(UserSaveReqVO.class));
// }
@Test
void testCreateUser_WithPostName() {
// Arrange
UserCreateRequestVO requestVO = randomPojo(UserCreateRequestVO.class, vo -> {
vo.setPostName("岗位A");
vo.setDeptIds(null);
});
Long newUserId = randomLongId();
Long postId = randomLongId();
when(postService.getOrCreatePostByName(requestVO.getPostName())).thenReturn(postId);
when(adminUserService.createUser(any(UserSaveReqVO.class))).thenReturn(newUserId);
// Act
UserCreateResponseVO response = userSyncService.createUser(requestVO);
// Assert
assertNotNull(response);
assertEquals("0", response.getResultCode());
assertEquals("success", response.getMessage());
assertEquals(String.valueOf(newUserId), response.getUid());
assertEquals(requestVO.getBimRequestId(), response.getBimRequestId());
ArgumentCaptor<UserSaveReqVO> captor = ArgumentCaptor.forClass(UserSaveReqVO.class);
verify(adminUserService).createUser(captor.capture());
assertNotNull(captor.getValue().getPostIds());
assertTrue(captor.getValue().getPostIds().contains(postId));
verify(postService).getOrCreatePostByName(requestVO.getPostName());
}
@Test
void testCreateUser_WithoutPostName() {
// Arrange
UserCreateRequestVO requestVO = randomPojo(UserCreateRequestVO.class, vo -> {
vo.setPostName(null);
vo.setDeptIds(null);
});
Long newUserId = randomLongId();
when(adminUserService.createUser(any(UserSaveReqVO.class))).thenReturn(newUserId);
// Act
UserCreateResponseVO response = userSyncService.createUser(requestVO);
// Assert
assertNotNull(response);
assertEquals("0", response.getResultCode());
assertEquals("success", response.getMessage());
assertEquals(String.valueOf(newUserId), response.getUid());
assertEquals(requestVO.getBimRequestId(), response.getBimRequestId());
ArgumentCaptor<UserSaveReqVO> captor = ArgumentCaptor.forClass(UserSaveReqVO.class);
verify(adminUserService).createUser(captor.capture());
assertTrue(captor.getValue().getPostIds() == null || captor.getValue().getPostIds().isEmpty());
verify(postService, never()).getOrCreatePostByName(any());
}
@Test
void testDeleteUser_Success() {

View File

@@ -649,6 +649,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
o.setSex(randomEle(SexEnum.values()).getSex());
o.setDeptIds(new HashSet<>(asSet(1L, 2L)));
o.setDeptNames("-");
o.setCompanyDeptInfos(null);// 保证 deptIds 的范围
o.setUserSource(null);
};

View File

@@ -2,6 +2,16 @@ spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
config:
import: optional:classpath:application-unit-test-config.yaml # 覆盖主配置中的 Nacos 导入,避免单测加载远程配置
cloud:
nacos:
config:
enabled: false
import-check:
enabled: false
discovery:
enabled: false
--- #################### 数据库相关配置 ####################
@@ -37,6 +47,16 @@ mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
config:
server-addr: 127.0.0.1:8848
namespace: unit-test
group: DEFAULT_GROUP
username:
password:
env:
name: unit-test
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################

View File

@@ -0,0 +1,25 @@
spring:
config:
import: optional:classpath:application-unit-test-config.yaml
application:
name: system-server
profiles:
active: unit-test
cloud:
nacos:
config:
enabled: false
import-check:
enabled: false
discovery:
enabled: false
config:
server-addr: 127.0.0.1:8848
namespace: unit-test
group: DEFAULT_GROUP
username:
password:
env:
name: unit-test