diff --git a/server/src/main/resources/application-dev.yml b/server/src/main/resources/application-dev.yml new file mode 100644 index 0000000..4f5f692 --- /dev/null +++ b/server/src/main/resources/application-dev.yml @@ -0,0 +1,39 @@ +spring: + config: + activate: + on-profile: dev + jpa: + # database-platform: com.aisino.iles.core.hibernate.MysqlDialectPlus +# database-platform: org.hibernate.dialect.Kingbase8Dialect + database-platform: com.aisino.iles.core.hibernate.PostgreSQLDialectPlus + hibernate: + ddl-auto: update + datasource: + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + # driver-class-name: com.mysql.cj.jdbc.Driver + # username: socialcollect + # password: Socialcollect@pg2025 + # jdbc-url: jdbc:mysql://mysql-dev:33060/iles_db?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8 + driver-class-name: org.postgresql.Driver + username: socialcollect + password: Socialcollect@pg2025 + jdbc-url: jdbc:postgresql://mysql-dev:60002/iles_db?timezone=Asia/Shanghai + pool-name: hikari-dev-pool + data: + redis: + host: localhost + port: 6379 + database: 5 +server: + port: 9091 + +logging: + level: + com.aisino.iles.core.hibernate.RedissonLocalStorage: DEBUG + com.aisino.iles.lawenforcement.controller.AuthController: debug + org.hibernate.SQL: debug + org.hibernate.orm.jdbc.bind: TRACE + org.springframework.web.client.RestTemplate: DEBUG + org.apache.kafka.clients.NetworkClient: ERROR diff --git a/server/src/main/resources/application-prod.yml b/server/src/main/resources/application-prod.yml new file mode 100644 index 0000000..33bef84 --- /dev/null +++ b/server/src/main/resources/application-prod.yml @@ -0,0 +1,62 @@ +spring: + jpa: + database-platform: org.hibernate.dialect.Kingbase8Dialect + hibernate: + ddl-auto: update + datasource: + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + driver-class-name: com.kingbase8.Driver + username: iles_db + password: Zhyj@2025 + jdbc-url: jdbc:kingbase8://db:54321/iles_db?timezone=Asia/Shanghai + pool-name: hikari-prod-pool + data: + redis: + host: redis + port: 6379 + database: 5 + kafka: + bootstrap-servers: 10.22.245.242:9092 + listener: + # 手动提交模式(必须配置,与enable-auto-commit: false配合) + ack-mode: MANUAL_IMMEDIATE # 调用ack.acknowledge()后立即提交 + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer + acks: all # 确认策略:所有副本确认后返回成功 + consumer: + group-id: zhzf-group + auto-offset-reset: earliest # 偏移量重置策略:从最早消息开始消费 + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + enable-auto-commit: false # 启用自动提交偏移量 +server: + port: 9091 +custom: + aws: + access-key: aisino@2021 + secret-key: aisino@2021 + bucket: iles + endpoint: http://minio:10010 + filePath: +# 整合支撑系统认证 +sso: + enabled: true + base_url: http://10.22.245.209:16165/ht + authorize_server: ${sso.base_url}/uaa + client_id: A-610100000000-0005 + client_secret: 949783e1735b45948658b44744b0c3c3 + redirect_uri: https://10.22.245.213:10011/zhzf/index.html + +third-party: + enterprise: + # 内网环境的综合执法用户的手机号码 + yddh: 18009235209 + +logging: + level: + com.aisino.iles.core.hibernate.RedissonLocalStorage: DEBUG + org.hibernate.SQL: debug + org.hibernate.orm.jdbc.bind: TRACE diff --git a/server/src/main/resources/application-redisson-cache.yml b/server/src/main/resources/application-redisson-cache.yml new file mode 100644 index 0000000..3191b8b --- /dev/null +++ b/server/src/main/resources/application-redisson-cache.yml @@ -0,0 +1,61 @@ +spring: + jpa: + properties: + hibernate: + cache: + redisson: + hibernate: + default-query-results-region: + eviction: + max_entries: 10000 + expiration: + max_idle_time: 18000 + time_to_live: 60000 + Constant: + eviction: + max_entries: 40000 + expiration: + max_idle_time: 0 + time_to_live: 0 + User: + expiration: + time_to_live: 600000 + max_idle_time: 300000 + eviction: + max_entries: 20000 + Function: + expiration: + time_to_live: 0 + max_idle_time: 0 + eviction: + max_entries: 10000 + Menu: + expiration: + time_to_live: 0 + max_idle_time: 0 + eviction: + max_entries: 4000 + UserType: + expiration: + time_to_live: 0 + max_idle_time: 0 + eviction: + max_entries: 1000 + Permission: + expiration: + time_to_live: 0 + max_idle_time: 0 + eviction: + max_entries: 5000 + Role: + expiration: + time_to_live: 0 + max_idle_time: 0 + eviction: + max_entries: 4000 + Resource: + expiration: + time_to_live: 0 + max_idle_time: 0 + eviction: + max_entries: 10000 diff --git a/server/src/main/resources/application-test.yml b/server/src/main/resources/application-test.yml new file mode 100644 index 0000000..61ec052 --- /dev/null +++ b/server/src/main/resources/application-test.yml @@ -0,0 +1,44 @@ +spring: + jpa: + database-platform: org.hibernate.dialect.Kingbase8Dialect + hibernate: + ddl-auto: update + datasource: + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + driver-class-name: com.kingbase8.Driver + username: iles_user + password: iles_user@2025 + jdbc-url: jdbc:kingbase8://db:54321/iles_db?timezone=Asia/Shanghai + pool-name: hikari-test-pool +server: + port: 9091 +custom: + aws: + access-key: aisino@2021 + secret-key: aisino@2021 + bucket: iles + endpoint: http://10.22.245.219:10010 + filePath: +# 整合支撑系统认证 +sso: + enabled: true + base_url: http://10.22.245.219:16165/ht + authorize_server: ${sso.base_url}/uaa + client_id: A-610100000000-0005 + client_secret: 949783e1735b45948658b44744b0c3c3 + redirect_uri: http://10.22.245.219:10011/zhzf/index.html + +third-party: + enterprise: + # 内网环境的综合执法用户的手机号码 + yddh: 13996761895 + +logging: + level: + com.aisino.iles.core.hibernate.RedissonLocalStorage: DEBUG + com.aisino.iles.lawenforcement.controller.AuthController: debug + org.hibernate.SQL: debug + org.hibernate.orm.jdbc.bind: TRACE + org.springframework.web.client.RestTemplate: DEBUG diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml new file mode 100644 index 0000000..5d726d1 --- /dev/null +++ b/server/src/main/resources/application.yml @@ -0,0 +1,196 @@ +spring: + profiles: + include: + - redisson-cache + jpa: + open-in-view: false + hibernate: + ddl-auto: none + properties: + hibernate: + default_batch_fetch_size: 50 + jdbc: + batch_size: 50 + cache: + use_second_level_cache: true + use_query_cache: true +# region.factory_class: org.redisson.hibernate.RedissonRegionFactory + region_prefix: hibernate + redisson: + fallback: true + generate.statistics: true + cache: + type: redis + data: + redis: + host: localhost + port: 6379 + password: null + database: 0 + repositories: + enabled: false + servlet: + multipart: + max-file-size: 1000MB + max-request-size: 1000MB + jackson: + locale: zh_CN + time-zone: Asia/Shanghai + serialization: + write_dates_as_timestamps: false # 关闭序列化的时候,把时间按照毫秒方式输出的特性 +redisson: + config: | + singleServerConfig: + address: "redis://${spring.data.redis.host}:${spring.data.redis.port}" + connectionMinimumIdleSize: 10 + connectionPoolSize: 64 + idleConnectionTimeout: 10000 + connectTimeout: 10000 + timeout: 3000 + retryAttempts: 3 + retryInterval: 1500 + password: null + subscriptionsPerConnection: 24 + clientName: "redisson-client" + sslEnableEndpointIdentification: false + database: ${spring.data.redis.database} + +server: + servlet: + session: + timeout: 180 + +logging: + file: + name: ./logs/iles.log + logback: + rollingpolicy: + max-history: 7 + max-file-size: 10MB + total-size-cap: 5GB + charset: + console: UTF-8 + file: UTF-8 + level: + org.hibernate.cache: DEBUG + com.aisino.iles: INFO + +custom: + aws: + access-key: aisino@2021 + secret-key: aisino@2021 + bucket: bucket-test + endpoint: https://mysql-dev:10010 + filePath: +# 整合支撑系统认证 +sso: + enabled: true + base_url: http://218.77.106.90:16166/ht + authorize_server: ${sso.base_url}/uaa + client_id: A-610100000000-0005 + client_secret: 949783e1735b45948658b44744b0c3c3 + redirect_uri: http://localhost:5173/zhzf/index.html + +#第三方接口相关配置 +third-party: + #消息服务 + message: + url: ${sso.base_url}/msg/v1/messages/create + dict: ${sso.base_url}/system/dict/cache/ + enterprise: + yddh: 18685429490 + #互联网 + query: ${sso.base_url}/jcxxgl/company/jbxx/queryPage + save: ${sso.base_url}/jcxxgl/company/jbxx/save + token: ${sso.base_url}/uaa/oauth/backendServiceToken + refresh: ${sso.base_url}/uaa/oauth/token + selfInsAndSelfReport: ${sso.base_url}/jcxxglApi/djbQyfxxj/queryPage + produceLicensePage: ${sso.base_url}/jcxxglApi/djbQyscxk/queryPage + hazardPage: ${sso.base_url}/jcxxglApi/djbQyyhxx/queryPage + sourcesDangerPage: ${sso.base_url}/jcxxglApi/djbQyzdbw/queryPage + + report_check: + query: https://xa-pro.12350.work/backend/php/complaintsReport/index + single: https://xa-pro.12350.work/backend/php/complaintsReport/detail + sign: x7Dp9K2zR4bY8qW3 + # 执法机构信息 + agency: + query: ${sso.base_url}/system/sysDept/queryPage + # 执法人员信息 + person: + query: ${sso.base_url}/system/sysUser/queryPage + roles: + # 执法市级 + - zhzfsj + # 执法区级 + - zhzfxj + # 执法检查 + - zhzfjc +# 文书送达 + delivery: + username: zhifa + password: zhifa@95113 + base-url: http://10.22.245.226:9080/xtjy + publish-sms-call-url: ${third-party.delivery.base-url}/kt-module-task/v1/callAndResponse/publishSMS + publish-voice-call-url: ${third-party.delivery.base-url}/kt-module-task/v1/callAndResponse/publishVoice + # 执法信息 + enforcement: + save: ${sso.base_url}/djbZfajxx/save + # 执法文书 + document: + save: ${sso.base_url}/djbZfwsxx/save + # 执法检查 + enforce-check: + save: ${sso.base_url}/djbZfjcxx/save + # 执法案件 + case: + save: ${sso.base_url}/djbZfajxx/save + agency-arr: 01610100000726000-01610100000701000-01610100000016000-01610100000715000-01610100000726000-01610100000037000 + + +iles: + sync: + api: + base-url: ${sso.base_url} + dto-converters: + "com.aisino.iles.lawenforcement.model.Document": com.aisino.iles.lawenforcement.model.dto.DjbZfwsxxRequestDto + "com.aisino.iles.lawenforcement.model.EnforcementInfo": com.aisino.iles.lawenforcement.model.dto.DjbZfjbxxRequestDto + "com.aisino.iles.lawenforcement.model.EnforceCheck": com.aisino.iles.lawenforcement.model.dto.DjbZfjcxxRequestDto + "com.aisino.iles.lawenforcement.model.Case": com.aisino.iles.lawenforcement.model.dto.DjbZfajxxRequestDto + endpoints: + com.aisino.iles.lawenforcement.model.dto.DjbZfajxxRequestDto: /djbZfajxx/save + com.aisino.iles.lawenforcement.model.dto.DjbZfwsxxRequestDto: /djbZfwsxx/save + com.aisino.iles.lawenforcement.model.dto.DjbZfjcxxRequestDto: /djbZfjcxx/save + com.aisino.iles.lawenforcement.model.dto.DjbZfjbxxRequestDto: /djbZfjbxx/save + +scheduler: + enabled: false + task: + enterprise: + cron.expression: 20 16 * * * ? + agency: + cron.expression: 0 0 1 * * ? + officer: + cron.expression: 0 0 1 * * ? + waferEnterprise: + cron.expression: 0 2 * * * ? + +data-package: + #是否打开JPA事务拦截器 默认true + enable: true + # 打包标记 + packaging-flag: false + # 打包接口 + packaging-api: http://localhost:8090/dtt/pack + # 写入目标redis失败时,重试次数 + retry-times: 3 + # 写入目标redis失败时,重试间隔时间(毫秒) + retry-interval: 200 + # 数据包后续业务处理器 + data-pack-processors: + - sendToDttPack + # 需要打包的实体名称(列表) + process-entity-names: + include: + - com.aisino.iles.core.model.* + - com.aisino.iles.lawenforcement.model.* diff --git a/server/src/main/resources/fonts/NotoSansSC-Regular.ttf b/server/src/main/resources/fonts/NotoSansSC-Regular.ttf new file mode 100644 index 0000000..a73a762 Binary files /dev/null and b/server/src/main/resources/fonts/NotoSansSC-Regular.ttf differ diff --git a/server/src/main/resources/fonts/simsun.ttf b/server/src/main/resources/fonts/simsun.ttf new file mode 100644 index 0000000..e0115ab Binary files /dev/null and b/server/src/main/resources/fonts/simsun.ttf differ diff --git a/server/src/main/resources/kms_client.yml b/server/src/main/resources/kms_client.yml new file mode 100644 index 0000000..fca616a --- /dev/null +++ b/server/src/main/resources/kms_client.yml @@ -0,0 +1,24 @@ +# 服务地址 格式 ip:port +address: 10.22.245.159:19190 +## 以下两项使用kcsp平台的kms必填,均由kcsp平台提供。非kcsp平台可为空 +contextPath: +kid: +#以下两项必填(由kms aksk 管理解密生成) +accessKey: Sgk5hVoeEEpgUhK7 +secretKey: eWjPzctSUTODF2Xm +#使用cbc模式加密的keyId,可为空 +cbcKeyId: +#使用ctr模式加密的keyId,可为空 +crtKeyId: +#是否使用tls +useTLS: false +#本地信任keyStore路径 +trustStorePath: +#本地信任keyStore密码 +trustStorePassword: +#本地keyStore路径 +keyStorePath: +#本地keyStore密码 +keyStorePassword: +#本地keyStore中key的密码 +keyPassword: diff --git a/server/src/main/resources/logback-spring.xml b/server/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..32640e6 --- /dev/null +++ b/server/src/main/resources/logback-spring.xml @@ -0,0 +1,31 @@ + + + + + + + + + logs/dead-letter.log + + + logs/dead-letter.%d{yyyy-MM-dd}.log + + 30 + + + + %msg%n + + + + + + + + + + + + + diff --git a/server/src/test/java/com/aisino/iles/core/hibernate/PostgreSQLDialectPlusIntegrationTest.java b/server/src/test/java/com/aisino/iles/core/hibernate/PostgreSQLDialectPlusIntegrationTest.java new file mode 100644 index 0000000..b279672 --- /dev/null +++ b/server/src/test/java/com/aisino/iles/core/hibernate/PostgreSQLDialectPlusIntegrationTest.java @@ -0,0 +1,72 @@ +package com.aisino.iles.core.hibernate; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.Session; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * PostgreSQLDialectPlus集成测试类 + * 用于验证SQL生成是否符合预期 + * + * @author Cascade + * @since 2025-08-08 + */ +@Slf4j +@SpringBootTest +@ActiveProfiles("test") +@Transactional +class PostgreSQLDialectPlusIntegrationTest { + + @PersistenceContext + private EntityManager entityManager; + + @Test + void testJsonValueFunctionSimplePathSqlGeneration() { + // 获取Hibernate Session + Session session = entityManager.unwrap(Session.class); + + // 创建一个包含json_value函数的HQL查询 + String hql = "SELECT json_value('{\"name\":\"John\", \"info\":{\"age\":30}}', '$.info.age')"; + + String result = (String) session.createQuery(hql).getSingleResult(); + assertEquals("30", result); + } + + @Test + void testJsonValueFunctionTranslationVerification() { + // 获取Hibernate Session + Session session = entityManager.unwrap(Session.class); + + // 测试不同的JSON路径模式 + String[] testCases = { + "SELECT json_value(jsonData, '$.property')", + "SELECT json_value(jsonData, '$.nested.object')", + "SELECT json_value(jsonData, '$.array[0].item')" + }; + + for (String hql : testCases) { + try { + // 尝试将HQL转换为SQL + String sql = session.createQuery(hql).getQueryString(); + System.out.println("HQL: " + hql); + System.out.println("生成的SQL: " + sql); + + // 验证是否包含json_extract_path_text函数 + assertTrue(sql.contains("json_extract_path_text"), + "所有json_value函数调用都应该被转译为json_extract_path_text"); + } catch (Exception e) { + // 打印异常但不失败,因为某些复杂路径可能不被支持 + System.out.println("处理HQL时出现警告: " + hql + " - " + e.getMessage()); + } + } + } + +} diff --git a/server/src/test/java/com/aisino/iles/core/service/CacheAdminServicePerformanceTest.java b/server/src/test/java/com/aisino/iles/core/service/CacheAdminServicePerformanceTest.java new file mode 100644 index 0000000..16e6bd3 --- /dev/null +++ b/server/src/test/java/com/aisino/iles/core/service/CacheAdminServicePerformanceTest.java @@ -0,0 +1,161 @@ +package com.aisino.iles.core.service; + +import com.aisino.iles.core.hibernate.RedissonLocalStorage; +import com.aisino.iles.core.hibernate.RedissonRegionFactoryPlus; +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 java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CacheAdminServicePerformanceTest { + + private static final int THREAD_COUNT = 10; + private static final int OPERATION_COUNT = 1000; + private static final String TEST_REGION = "performanceTest"; + private static final String KEY_PREFIX = "key"; + private static final String VALUE_PREFIX = "value"; + + @Mock + private RedissonRegionFactoryPlus regionFactory; + + @Mock + private RedissonLocalStorage region; + + @InjectMocks + private CacheAdminService cacheAdminService; + + private ExecutorService executorService; + + @BeforeEach + void setUp() { + when(regionFactory.getAllCacheRegions()).thenReturn(Map.of(TEST_REGION, region)); + executorService = Executors.newFixedThreadPool(THREAD_COUNT); + } + + @Test + void concurrentPutAndGetOperations_ShouldNotLoseData() { + // Arrange + AtomicInteger successCount = new AtomicInteger(); + AtomicInteger failureCount = new AtomicInteger(); + + // Act - Perform concurrent put operations + var putFutures = IntStream.range(0, OPERATION_COUNT) + .mapToObj(i -> CompletableFuture.runAsync(() -> { + try { + String key = KEY_PREFIX + i; + String value = VALUE_PREFIX + i; + when(region.getFromCache(key, null)).thenReturn(value); + doNothing().when(region).putIntoCache(key, value, null); + + boolean result = cacheAdminService.putCacheValue(TEST_REGION, key, value); + if (result) { + successCount.incrementAndGet(); + } else { + failureCount.incrementAndGet(); + } + } catch (Exception e) { + failureCount.incrementAndGet(); + } + }, executorService)) + .toArray(CompletableFuture[]::new); + + // Wait for all operations to complete + CompletableFuture.allOf(putFutures).join(); + + // Verify all puts were successful + assertEquals(OPERATION_COUNT, successCount.get()); + assertEquals(0, failureCount.get()); + verify(region, times(OPERATION_COUNT)).putIntoCache(any(), any(), any()); + + // Reset counters for get operations + successCount.set(0); + failureCount.set(0); + + // Perform concurrent get operations + var getFutures = IntStream.range(0, OPERATION_COUNT) + .mapToObj(i -> CompletableFuture.runAsync(() -> { + try { + String key = KEY_PREFIX + i; + String expectedValue = VALUE_PREFIX + i; + when(region.getFromCache(key, null)).thenReturn(expectedValue); + + Object value = cacheAdminService.getCacheValue(TEST_REGION, key); + if (expectedValue.equals(value)) { + successCount.incrementAndGet(); + } else { + failureCount.incrementAndGet(); + } + } catch (Exception e) { + failureCount.incrementAndGet(); + } + }, executorService)) + .toArray(CompletableFuture[]::new); + + // Wait for all get operations to complete + CompletableFuture.allOf(getFutures).join(); + + // Verify all gets were successful + assertEquals(OPERATION_COUNT, successCount.get()); + assertEquals(0, failureCount.get()); + } + + @Test + void testCacheEvictionPerformance() { + // Arrange + int cacheSize = 1000; + int iterations = 10000; + + // Warm up the cache + for (int i = 0; i < cacheSize; i++) { + String key = KEY_PREFIX + i; + String value = VALUE_PREFIX + i; + cacheAdminService.putCacheValue(TEST_REGION, key, value); + } + + // Act - Perform cache operations under load + long startTime = System.currentTimeMillis(); + + IntStream.range(0, iterations).parallel().forEach(i -> { + String key = KEY_PREFIX + (i % cacheSize); + if (i % 10 == 0) { + // Every 10th operation is a put + String newValue = VALUE_PREFIX + (i % cacheSize) + "_updated"; + cacheAdminService.putCacheValue(TEST_REGION, key, newValue); + } else if (i % 5 == 0) { + // Every 5th operation is a remove + cacheAdminService.removeCacheValue(TEST_REGION, key); + } else { + // Other operations are gets + cacheAdminService.getCacheValue(TEST_REGION, key); + } + }); + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Assert - Verify performance metrics + double operationsPerSecond = (double) iterations / (totalTime / 1000.0); + System.out.printf("Performance Test Results:%n"); + System.out.printf("Total operations: %d%n", iterations); + System.out.printf("Total time: %d ms%n", totalTime); + System.out.printf("Operations per second: %.2f%n", operationsPerSecond); + + // Verify that the cache operations completed in a reasonable time + assertTrue(totalTime < 5000, "Cache operations took too long: " + totalTime + "ms"); + } +} diff --git a/server/src/test/java/com/aisino/iles/core/service/CacheAdminServiceTest.java b/server/src/test/java/com/aisino/iles/core/service/CacheAdminServiceTest.java new file mode 100644 index 0000000..d92a42a --- /dev/null +++ b/server/src/test/java/com/aisino/iles/core/service/CacheAdminServiceTest.java @@ -0,0 +1,184 @@ +package com.aisino.iles.core.service; + +import com.aisino.iles.core.hibernate.RedissonLocalStorage; +import com.aisino.iles.core.hibernate.RedissonRegionFactoryPlus; +import jakarta.persistence.Cache; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +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.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CacheAdminServiceTest { + + @Mock + private RedissonRegionFactoryPlus regionFactory; + + @Mock + private EntityManager entityManager; + + @Mock + private EntityManagerFactory entityManagerFactory; + + @Mock + private Cache cache; + + @InjectMocks + private CacheAdminService cacheAdminService; + + private final String testRegion = "testRegion"; + private final String testKey = "testKey"; + private final String testValue = "testValue"; + private final String entityName = "com.example.Entity"; + private final Object entityId = 1L; + + @BeforeEach + void setUp() { + when(entityManager.getEntityManagerFactory()).thenReturn(entityManagerFactory); + when(entityManagerFactory.getCache()).thenReturn(cache); + } + + @Test + void getAllRegions_ShouldReturnAllRegions() { + // Arrange + Map expectedRegions = new HashMap<>(); + expectedRegions.put("region1", mock(RedissonLocalStorage.class)); + expectedRegions.put("region2", mock(RedissonLocalStorage.class)); + when(regionFactory.getAllCacheRegions()).thenReturn(expectedRegions); + + // Act + Map result = cacheAdminService.getAllRegions(); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.containsKey("region1")); + assertTrue(result.containsKey("region2")); + } + + @Test + void getCacheValue_WhenRegionExists_ShouldReturnValue() { + // Arrange + RedissonLocalStorage region = mock(RedissonLocalStorage.class); + when(regionFactory.getAllCacheRegions()).thenReturn(Map.of(testRegion, region)); + when(region.getFromCache(testKey, null)).thenReturn(testValue); + + // Act + Object result = cacheAdminService.getCacheValue(testRegion, testKey); + + // Assert + assertEquals(testValue, result); + } + + @Test + void getCacheValue_WhenRegionNotExists_ShouldReturnNull() { + // Arrange + when(regionFactory.getAllCacheRegions()).thenReturn(new HashMap<>()); + // Act + Object result = cacheAdminService.getCacheValue("nonExistentRegion", testKey); + // Assert + assertNull(result); + } + + @Test + void putCacheValue_ShouldReturnTrueWhenSuccessful() { + // Arrange + RedissonLocalStorage region = mock(RedissonLocalStorage.class); + when(regionFactory.getAllCacheRegions()).thenReturn(Map.of(testRegion, region)); + doNothing().when(region).putIntoCache(testKey, testValue, null); + + // Act + boolean result = cacheAdminService.putCacheValue(testRegion, testKey, testValue); + + // Assert + assertTrue(result); + verify(region).putIntoCache(testKey, testValue, null); + } + + @Test + void removeCacheValue_ShouldReturnTrueWhenSuccessful() { + // Arrange + RedissonLocalStorage region = mock(RedissonLocalStorage.class); + when(regionFactory.getAllCacheRegions()).thenReturn(Map.of(testRegion, region)); + doNothing().when(region).removeFromCache(testKey, null); + + // Act + boolean result = cacheAdminService.removeCacheValue(testRegion, testKey); + + // Assert + assertTrue(result); + verify(region).removeFromCache(testKey, null); + } + + + @Test + void removeCacheValueByEntityManager_ShouldEvictEntityCache() throws Exception { + // Arrange + Class entityClass = Object.class; + when(entityManager.getEntityManagerFactory()).thenReturn(entityManagerFactory); + when(entityManagerFactory.getCache()).thenReturn(cache); + + try (MockedStatic mocked = mockStatic(Class.class)) { + mocked.when(() -> Class.forName(entityName)).thenReturn(entityClass); + + // Act + boolean result = cacheAdminService.removeCacheValueByEntityManager(entityName, entityId); + + // Assert + assertTrue(result); + verify(cache).evict(entityClass, entityId); + } + } + + @Test + void refreshLocalCache_ShouldCallGetFromCache() { + // Arrange + RedissonLocalStorage region = mock(RedissonLocalStorage.class); + when(regionFactory.getAllCacheRegions()).thenReturn(Map.of(testRegion, region)); + + // Act + cacheAdminService.refreshLocalCache(testRegion, testKey); + // Assert + verify(region).getFromCache(testKey, null); + } + + @Test + void removeRegion_ShouldReturnTrueWhenSuccessful() { + // Arrange + RedissonLocalStorage region = mock(RedissonLocalStorage.class); + when(regionFactory.getAllCacheRegions()).thenReturn(Map.of(testRegion, region)); + doNothing().when(region).clearCache(null); + + // Act + boolean result = cacheAdminService.removeRegion(testRegion); + + // Assert + assertTrue(result); + verify(region).clearCache(null); + } + + @Test + void removeRegion_WhenExceptionOccurs_ShouldReturnFalse() { + // Arrange + RedissonLocalStorage region = mock(RedissonLocalStorage.class); + when(regionFactory.getAllCacheRegions()).thenReturn(Map.of(testRegion, region)); + doThrow(new RuntimeException("Clear cache failed")).when(region).clearCache(null); + + // Act + boolean result = cacheAdminService.removeRegion(testRegion); + + // Assert + assertFalse(result); + } +} diff --git a/server/src/test/java/com/aisino/iles/lawenforcement/schedule/service/OfficerScheduleServiceTest.java b/server/src/test/java/com/aisino/iles/lawenforcement/schedule/service/OfficerScheduleServiceTest.java new file mode 100644 index 0000000..e7f89d3 --- /dev/null +++ b/server/src/test/java/com/aisino/iles/lawenforcement/schedule/service/OfficerScheduleServiceTest.java @@ -0,0 +1,57 @@ +package com.aisino.iles.lawenforcement.schedule.service; + +import com.aisino.iles.lawenforcement.repository.OfficerRepository; +import org.junit.jupiter.api.DisplayName; +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.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * OfficerScheduleService 的集成测试. + *

+ * 注意: 这是一个集成测试,它会真实地调用外部API并与数据库进行交互。 + * 在运行此测试之前,请确保 application.yml 或相关 profile 中的配置 + * (如 third-party.person.*, sso.*, 数据库连接等) 是正确且可用的。 + *

+ */ +@SpringBootTest(properties = { + "third-party.enterprise.idno=310000199308124725" +}) +@ActiveProfiles("dev") +@DisplayName("执法人员信息同步服务集成测试") +class OfficerScheduleServiceTest { + + @Autowired + private OfficerScheduleService officerScheduleService; + + @Autowired + private OfficerRepository officerRepository; + + @Test + @DisplayName("测试 scheduleTask 方法 - 完整同步流程") + void testScheduleTask() { + // GIVEN: 记录测试前的执法人员数量 + long countBefore = officerRepository.count(); + System.out.println("同步任务执行前,数据库中有 " + countBefore + " 名执法人员。"); + + // WHEN: 执行同步任务 + // THEN: 断言它不会抛出异常 + assertDoesNotThrow(() -> { + officerScheduleService.scheduleTask(); + }, "执行执法人员同步任务时,不应抛出异常"); + + // AND: 记录测试后的执法人员数量 + long countAfter = officerRepository.count(); + System.out.println("同步任务执行后,数据库中有 " + countAfter + " 名执法人员。"); + + // AND: 断言任务执行后,数据量可能增加或保持不变(如果API没有新数据或数据已存在) + assertTrue(countAfter >= countBefore, "同步后执法人员数量应大于或等于同步前"); + + System.out.println("执法人员同步任务测试成功。"); + } + +} diff --git a/server/src/test/java/com/aisino/iles/lawenforcement/service/CaseServiceTest.java b/server/src/test/java/com/aisino/iles/lawenforcement/service/CaseServiceTest.java new file mode 100644 index 0000000..fcf5baa --- /dev/null +++ b/server/src/test/java/com/aisino/iles/lawenforcement/service/CaseServiceTest.java @@ -0,0 +1,113 @@ +package com.aisino.iles.lawenforcement.service; + +import com.aisino.iles.lawenforcement.model.dto.DjbZfajxxRequestDto; +import com.aisino.iles.lawenforcement.model.Agency; +import com.aisino.iles.lawenforcement.model.Case; +import com.aisino.iles.lawenforcement.model.EnforcementInfo; +import com.aisino.iles.lawenforcement.model.Enterprise; +import com.aisino.iles.lawenforcement.model.dto.EnforcementInfoDto; +import com.aisino.iles.lawenforcement.model.enums.FlowNode; +import com.aisino.iles.lawenforcement.repository.CaseRepository; +import com.aisino.iles.lawenforcement.repository.EnforcementInfoRepository; +import com.smartlx.sso.client.model.RemoteUserInfo; +import lombok.SneakyThrows; +import org.apache.commons.beanutils.BeanUtils; +import org.junit.jupiter.api.DisplayName; +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 org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 执法信息单元测试 + */ +@SpringBootTest +@ActiveProfiles("dev") +public class CaseServiceTest { + + @Autowired + private CaseService caseService; + + @Autowired + private CaseRepository caseRepository; + + @Autowired + private EnforcementInfoRepository enforcementInfoRepository; + + @Autowired + private EnforcementInfoService enforcementInfoService; + + /** + * 测试保存执法案件信息 + */ + @SneakyThrows + @Test + @DisplayName("测试保存执法案件信息") + @Transactional // 使用事务确保测试后数据回滚,不影响数据库 + public void testSaveLawEnforcementCase() { + // 创建测试所需的执法机构 + Agency agency = new Agency(); + agency.setAgencyId("1"); + agency.setAgencyName("测试执法机构"); + agency.setAgencyCode("TEST_AGENCY_CODE"); + + // 创建测试所需的企业 + Enterprise enterprise = new Enterprise(); +// enterprise.setEnterpriseId(UUID.randomUUID().toString()); + enterprise.setUnitName("测试企业"); + enterprise.setUnifiedSocialCode("TEST_UNIT_CODE"); + + // 创建执法信息 + EnforcementInfo enforcementInfo = new EnforcementInfo(); +// enforcementInfo.setEnforcementId(UUID.randomUUID().toString()); + enforcementInfo.setAgency(agency); + enforcementInfo.setAgencyId(agency.getAgencyId()); + enforcementInfo.setEnterprise(enterprise); + enforcementInfo.setCurrentNodeCode(FlowNode.filed); + enforcementInfo.setCreateTime(LocalDateTime.now()); + EnforcementInfoDto enforcementInfoDto = new EnforcementInfoDto(); + BeanUtils.copyProperties(enforcementInfoDto,enforcementInfo); + enforcementInfoDto.setEnterpriseIds(List.of("01jpebdtden62z6qw01e338zwm")); + // 保存执法信息 + List objects = enforcementInfoService.saveEnforcementInfo(enforcementInfoDto, new RemoteUserInfo()); + EnforcementInfo savedEnforcementInfo = (EnforcementInfo) objects.get(0); + + // 创建案件信息 + Case lawCase = new Case(); +// lawCase.setCaseId(UUID.randomUUID().toString()); + lawCase.setCaseName("测试执法案件"); + lawCase.setCaseNum("TEST-CASE-" + System.currentTimeMillis()); + lawCase.setFillingDate(LocalDate.now()); + lawCase.setEnforcementInfo(enforcementInfo); + lawCase.setStatus(Case.CaseStatus.filed); + + // 保存案件信息 + Case savedCase = caseService.saveCase(lawCase); + + // 断言保存成功 + assertNotNull(savedCase); + assertNotNull(savedCase.getCaseId()); + assertEquals("测试执法案件", savedCase.getCaseName()); + assertNotNull(savedCase.getEnforcementInfo()); +// assertEquals("测试执法机构", savedCase.getEnforcementInfo().getAgency().getAgencyName()); +// assertEquals("测试企业", savedCase.getEnforcementInfo().getEnterprise().getUnitName()); +// assertEquals(FlowNode.filed, savedCase.getEnforcementInfo().getCurrentNodeCode()); + + // 测试转换为DTO + DjbZfajxxRequestDto dto = new DjbZfajxxRequestDto(); + DjbZfajxxRequestDto convertedDto = dto.convert(savedCase); + + // 断言DTO转换成功 + assertNotNull(convertedDto); + assertEquals(savedCase.getCaseId(), convertedDto.getCaseId()); + assertEquals(savedCase.getCaseName(), convertedDto.getCaseName()); + assertEquals(savedCase.getCaseNum(), convertedDto.getCaseNum()); + } +}