From ff6f8aa3031e90b57971e85507ebf72727d99db2 Mon Sep 17 00:00:00 2001 From: liyongde <1419499670@qq.com> Date: Thu, 29 Jan 2026 13:19:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20mock=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/nl/tool/api/MockApi.java | 22 ++ .../nl/tool/core/enums/HttpMethodEnum.java | 22 ++ .../core/config/MockConfigProperties.java | 44 ++++ .../mock/core/handle/HttpClientWrapper.java | 189 ++++++++++++++++++ .../mock/core/handle/MockCacheService.java | 131 ++++++++++++ .../nl/tool/mock/core/handle/MockService.java | 59 ++++++ .../org/nl/tool/mock/core/package-info.java | 6 + .../mockconfig/dto/CreateMockConfigDTO.java | 50 +++++ .../mockconfig/dto/MockConfigDTOMapper.java | 84 ++++++++ .../modular/mockconfig/dto/MockConfigVO.java | 54 +++++ .../mockconfig/dto/UpdateMockConfigDTO.java | 47 +++++ .../modular/mockconfig/entity/MockConfig.java | 10 +- .../mockconfig/provider/MockApiProvider.java | 35 ++++ .../mockconfig/service/MockConfigService.java | 3 + .../service/impl/MockConfigServiceImpl.java | 10 + .../nl/tool/mock/modular/package-info.java | 6 + .../src/main/resources/application.yml | 19 ++ nl-vue/src/views/mock/mockconfig/form.vue | 25 ++- nl-vue/src/views/mock/mockconfig/index.vue | 2 +- .../org/nl/core/config/MockInterceptor.java | 100 +++++++++ 20 files changed, 909 insertions(+), 9 deletions(-) create mode 100644 nl-plugin-tool-api/src/main/java/org/nl/tool/api/MockApi.java create mode 100644 nl-plugin-tool-api/src/main/java/org/nl/tool/core/enums/HttpMethodEnum.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/core/config/MockConfigProperties.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/HttpClientWrapper.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/MockCacheService.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/MockService.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/core/package-info.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/CreateMockConfigDTO.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/MockConfigDTOMapper.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/MockConfigVO.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/UpdateMockConfigDTO.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/provider/MockApiProvider.java create mode 100644 nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/package-info.java create mode 100644 nl-plugin-tool/src/main/resources/application.yml create mode 100644 nl-web-app/src/main/java/org/nl/core/config/MockInterceptor.java diff --git a/nl-plugin-tool-api/src/main/java/org/nl/tool/api/MockApi.java b/nl-plugin-tool-api/src/main/java/org/nl/tool/api/MockApi.java new file mode 100644 index 0000000..2143fce --- /dev/null +++ b/nl-plugin-tool-api/src/main/java/org/nl/tool/api/MockApi.java @@ -0,0 +1,22 @@ +package org.nl.tool.api; + +import com.alibaba.fastjson.JSONObject; +import org.nl.tool.core.enums.HttpMethodEnum; + +/** + * mock-api提供者接口 + * @author: lyd + * @date: 2026/1/28 + */ +public interface MockApi { + + /** + * 通用的执行请求 + * @param url 路径(ip:端口号/api地址) + * @param method 方法类型:GET、POST、PUT、DELETED + * @param body 数据内容:JSONObject.toString + * @see JSONObject#toJSONString() + * @return + */ + String executeRequestCommon(String url, HttpMethodEnum method, String body); +} diff --git a/nl-plugin-tool-api/src/main/java/org/nl/tool/core/enums/HttpMethodEnum.java b/nl-plugin-tool-api/src/main/java/org/nl/tool/core/enums/HttpMethodEnum.java new file mode 100644 index 0000000..ec87d29 --- /dev/null +++ b/nl-plugin-tool-api/src/main/java/org/nl/tool/core/enums/HttpMethodEnum.java @@ -0,0 +1,22 @@ +package org.nl.tool.core.enums; + +import lombok.Getter; + +/** + * + * @author: lyd + * @date: 2026/1/29 + */ +@Getter +public enum HttpMethodEnum { + GET("GET"), + POST("POST"), + PUT("PUT"), + DELETE("DELETE"); + + private final String code; + + HttpMethodEnum(String code) { + this.code = code; + } +} diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/config/MockConfigProperties.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/config/MockConfigProperties.java new file mode 100644 index 0000000..3a9971f --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/config/MockConfigProperties.java @@ -0,0 +1,44 @@ +package org.nl.tool.mock.core.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Mock配置属性类 + * 使用@ConfigurationProperties从application.yml中绑定mock配置 + *

+ * 配置示例: + * mock: + * enabled: true + * cache-expiration-seconds: 300 + * log-mock-usage: true + * @author: lyd + * @date: 2026/1/28 + */ +@Component +@ConfigurationProperties(prefix = "mock") +@Data +public class MockConfigProperties { + + /** + * 全局Mock功能开关 + * 当设置为false时,系统将绕过所有Mock逻辑,直接执行实际请求 + * 默认值:true + */ + private boolean enabled = true; + + /** + * 缓存过期时间(秒) + * Mock配置在缓存中的存活时间,超过此时间后将从数据库重新加载 + * 默认值:300秒(5分钟) + */ + private long cacheExpirationSeconds = 300; + + /** + * 是否记录Mock使用日志 + * 当设置为true时,系统将记录每次使用Mock数据的详细信息 + * 默认值:true + */ + private boolean logMockUsage = true; +} diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/HttpClientWrapper.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/HttpClientWrapper.java new file mode 100644 index 0000000..1a2d45d --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/HttpClientWrapper.java @@ -0,0 +1,189 @@ +package org.nl.tool.mock.core.handle; + +import cn.hutool.http.HttpRequest; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.nl.tool.mock.core.config.MockConfigProperties; +import org.nl.tool.mock.modular.mockconfig.entity.MockConfig; +import org.springframework.stereotype.Component; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Optional; + +/** + * HTTP客户端封装器 + * 封装Hutool的HTTP请求方法,提供出站Mock功能 + *

+ * 工作流程: + * 1. 检查全局Mock开关 + * 2. 从URL提取路径 + * 3. 查询Mock配置 + * 4. 如果Mock启用,返回Mock数据;否则发起实际HTTP请求 + *

+ * 支持的HTTP方法:GET, POST, PUT, DELETE + * @author: lyd + * @date: 2026/1/28 + */ +@Component +@Slf4j +public class HttpClientWrapper { + @Resource + private MockService mockService; + @Resource + private MockConfigProperties properties; + /** + * 发起GET请求 + * + * @param url 目标URL + * @return 响应内容 + */ + public String get(String url) { + return executeRequest(url, "GET", null); + } + + /** + * 发起POST请求 + * + * @param url 目标URL + * @param body 请求体 + * @return 响应内容 + */ + public String post(String url, String body) { + return executeRequest(url, "POST", body); + } + + /** + * 发起PUT请求 + * + * @param url 目标URL + * @param body 请求体 + * @return 响应内容 + */ + public String put(String url, String body) { + return executeRequest(url, "PUT", body); + } + + /** + * 发起DELETE请求 + * + * @param url 目标URL + * @return 响应内容 + */ + public String delete(String url) { + return executeRequest(url, "DELETE", null); + } + + /** + * 执行HTTP请求的核心逻辑 + * + * 流程: + * 1. 检查全局Mock开关,如果禁用则直接发起实际请求 + * 2. 从URL提取路径 + * 3. 查询Mock配置 + * 4. 如果Mock配置存在且启用,返回Mock数据 + * 5. 否则发起实际HTTP请求 + * + * @param url 目标URL + * @param method HTTP方法 + * @param body 请求体(可为null) + * @return 响应内容 + */ + private String executeRequest(String url, String method, String body) { + // 检查全局Mock开关 + if (!properties.isEnabled()) { + log.debug("Mock is globally disabled, making real request to {} {}", method, url); + return makeRealRequest(url, method, body); + } + + try { + // 从URL提取路径 + String path = extractPath(url); + + // 查询Mock配置 + Optional mockConfig = mockService.getMockConfig(path, method); + + // 如果Mock配置存在且启用,返回Mock数据 + if (mockConfig.isPresent() && mockConfig.get().getIsEnabled()) { + if (properties.isLogMockUsage()) { + log.info("Mock response used for outbound {} {}", method, url); + } + return mockConfig.get().getResponseJson(); + } + + // Mock未启用或不存在,发起实际请求 + log.debug("No active mock config found for {} {}, making real request", method, path); + return makeRealRequest(url, method, body); + + } catch (Exception e) { + log.error("Error during mock check for {} {}, falling back to real request: {}", + method, url, e.getMessage()); + return makeRealRequest(url, method, body); + } + } + + /** + * 发起实际的HTTP请求 + * 使用Hutool的HttpRequest工具类 + * + * @param url 目标URL + * @param method HTTP方法 + * @param body 请求体(可为null) + * @return 响应内容 + * @throws IllegalArgumentException 如果HTTP方法不支持 + */ + private String makeRealRequest(String url, String method, String body) { + log.debug("Making real HTTP request: {} {}", method, url); + + try { + String response = switch (method.toUpperCase()) { + case "GET" -> HttpRequest.get(url).execute().body(); + case "POST" -> HttpRequest.post(url).body(body).execute().body(); + case "PUT" -> HttpRequest.put(url).body(body).execute().body(); + case "DELETE" -> HttpRequest.delete(url).execute().body(); + default -> { + log.error("Unsupported HTTP method: {}", method); + throw new IllegalArgumentException("Unsupported HTTP method: " + method); + } + }; + + log.debug("Real HTTP request completed: {} {}, response length: {}", + method, url, response != null ? response.length() : 0); + return response; + + } catch (Exception e) { + log.error("Error making real HTTP request to {} {}: {}", method, url, e.getMessage(), e); + throw new RuntimeException("Failed to make HTTP request: " + e.getMessage(), e); + } + } + + /** + * 从完整URL中提取路径部分 + * + * 例如: + * - "http://example.com/api/user/info" -> "/api/user/info" + * - "https://example.com:8080/api/data?id=1" -> "/api/data" + * + * @param url 完整URL + * @return URL的路径部分 + * @throws IllegalArgumentException 如果URL格式无效 + */ + private String extractPath(String url) { + try { + URI uri = new URI(url); + String path = uri.getPath(); + + if (path == null || path.isEmpty()) { + log.warn("URL has no path component: {}, using root path '/'", url); + return "/"; + } + + log.debug("Extracted path '{}' from URL '{}'", path, url); + return path; + + } catch (URISyntaxException e) { + log.error("Invalid URL format: {}", url, e); + throw new IllegalArgumentException("Invalid URL: " + url, e); + } + } +} diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/MockCacheService.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/MockCacheService.java new file mode 100644 index 0000000..1832ae5 --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/MockCacheService.java @@ -0,0 +1,131 @@ +package org.nl.tool.mock.core.handle; + +import lombok.extern.slf4j.Slf4j; +import org.nl.tool.mock.core.config.MockConfigProperties; +import org.nl.tool.mock.modular.mockconfig.entity.MockConfig; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Mock配置缓存服务 + * 使用ConcurrentHashMap实现线程安全的内存缓存,提升Mock配置查询性能 + * 支持缓存过期机制,确保数据的时效性 + * @author: lyd + * @date: 2026/1/29 + */ +@Service +@Slf4j +public class MockCacheService { + private final ConcurrentHashMap> cache; + private final long cacheExpirationMs; + + /** + * 构造函数,初始化缓存和过期时间 + * + * @param properties Mock配置属性,包含缓存过期时间配置 + */ + public MockCacheService(MockConfigProperties properties) { + this.cache = new ConcurrentHashMap<>(); + this.cacheExpirationMs = properties.getCacheExpirationSeconds() * 1000; + log.info("MockCacheService initialized with expiration time: {} ms", cacheExpirationMs); + } + + /** + * 从缓存中获取Mock配置 + * 如果缓存条目已过期,将自动移除并返回空 + * + * @param cacheKey 缓存键,格式为 "apiPath:apiMethod" + * @return Optional包装的MockConfig,如果不存在或已过期则返回空 + */ + public Optional get(String cacheKey) { + CacheEntry entry = cache.get(cacheKey); + + if (entry == null) { + log.debug("Cache miss for key: {}", cacheKey); + return Optional.empty(); + } + + if (entry.isExpired(cacheExpirationMs)) { + log.debug("Cache entry expired for key: {}, removing from cache", cacheKey); + cache.remove(cacheKey); + return Optional.empty(); + } + + log.debug("Cache hit for key: {}", cacheKey); + return Optional.of(entry.getValue()); + } + + /** + * 将Mock配置放入缓存 + * + * @param cacheKey 缓存键,格式为 "apiPath:apiMethod" + * @param config 要缓存的MockConfig对象 + */ + public void put(String cacheKey, MockConfig config) { + CacheEntry entry = new CacheEntry<>(config); + cache.put(cacheKey, entry); + log.debug("Cache updated for key: {}", cacheKey); + } + + /** + * 使指定缓存条目失效(移除) + * 通常在Mock配置被更新或删除时调用 + * + * @param cacheKey 要失效的缓存键 + */ + public void invalidate(String cacheKey) { + cache.remove(cacheKey); + log.debug("Cache invalidated for key: {}", cacheKey); + } + + /** + * 清空所有缓存条目 + * 通常在需要强制刷新所有缓存时调用 + */ + public void clear() { + int size = cache.size(); + cache.clear(); + log.info("Cache cleared, removed {} entries", size); + } + + /** + * 缓存条目内部类 + * 封装缓存值和时间戳,用于实现过期检查 + * + * @param 缓存值的类型 + */ + private static class CacheEntry { + private final T value; + private final long timestamp; + + /** + * 构造函数,创建缓存条目并记录当前时间戳 + * + * @param value 要缓存的值 + */ + public CacheEntry(T value) { + this.value = value; + this.timestamp = System.currentTimeMillis(); + } + + /** + * 获取缓存的值 + * @return 缓存的值 + */ + public T getValue() { + return value; + } + + /** + * 检查缓存条目是否已过期 + * + * @param expirationMs 过期时间(毫秒) + * @return true如果已过期,否则返回false + */ + public boolean isExpired(long expirationMs) { + return System.currentTimeMillis() - timestamp > expirationMs; + } + } +} diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/MockService.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/MockService.java new file mode 100644 index 0000000..bd514c6 --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/handle/MockService.java @@ -0,0 +1,59 @@ +package org.nl.tool.mock.core.handle; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.nl.tool.mock.modular.mockconfig.entity.MockConfig; +import org.nl.tool.mock.modular.mockconfig.service.MockConfigService; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * Mock配置服务 + * 提供Mock配置的业务逻辑,包括CRUD操作、缓存管理和JSON验证 + * 遵循DDD应用层服务模式 + * @author: lyd + * @date: 2026/1/29 + */ +@Service +@Slf4j +public class MockService { + @Resource + private MockConfigService mockConfigService; + @Resource + private MockCacheService mockCacheService; + + /** + * 获取Mock配置(集成缓存查询) + * 优先从缓存获取,缓存未命中时从数据库查询并更新缓存 + * + * @param apiPath API路径 + * @param apiMethod HTTP方法 + * @return Optional包装的MockConfig + */ + public Optional getMockConfig(String apiPath, String apiMethod) { + String cacheKey = apiPath + ":" + apiMethod; + + // 先查缓存 + Optional cached = mockCacheService.get(cacheKey); + if (cached.isPresent()) { + log.debug("Mock config found in cache for {} {}", apiMethod, apiPath); + return cached; + } + + // 缓存未命中,查数据库 + Optional config = mockConfigService.findByApiPathAndApiMethod(apiPath, apiMethod); + + // 如果找到,更新缓存 + config.ifPresent(c -> { + mockCacheService.put(cacheKey, c); + log.debug("Mock config loaded from database and cached for {} {}", apiMethod, apiPath); + }); + + if (config.isEmpty()) { + log.debug("Mock config not found for {} {}", apiMethod, apiPath); + } + + return config; + } +} diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/package-info.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/package-info.java new file mode 100644 index 0000000..d6b9a1a --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/core/package-info.java @@ -0,0 +1,6 @@ +/** + * 核心包:配置、插件之类 + * @author: lyd + * @date: 2026/1/28 + */ +package org.nl.tool.mock.core; \ No newline at end of file diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/CreateMockConfigDTO.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/CreateMockConfigDTO.java new file mode 100644 index 0000000..043af29 --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/CreateMockConfigDTO.java @@ -0,0 +1,50 @@ +package org.nl.tool.mock.modular.mockconfig.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * @author: lyd + * @date: 2026/1/29 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CreateMockConfigDTO { + + /** + * API路径 + * 不能为空,最大长度500 + */ + @NotBlank(message = "API path cannot be blank") + @Size(max = 500, message = "API path too long") + private String apiPath; + + /** + * HTTP请求方法 + * 不能为空,必须是GET、POST、PUT、DELETE之一 + */ + @NotBlank(message = "API method cannot be blank") + @Pattern(regexp = "GET|POST|PUT|DELETE", message = "Invalid HTTP method") + private String apiMethod; + + /** + * 返回的JSON数据 + * 不能为空 + */ + @NotBlank(message = "Response JSON cannot be blank") + private String responseJson; + + /** + * 是否启用 + * 默认为true + */ + private Boolean isEnabled = true; +} diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/MockConfigDTOMapper.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/MockConfigDTOMapper.java new file mode 100644 index 0000000..cfdfab6 --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/MockConfigDTOMapper.java @@ -0,0 +1,84 @@ +package org.nl.tool.mock.modular.mockconfig.dto; + +import org.nl.tool.mock.modular.mockconfig.entity.MockConfig; +import org.springframework.stereotype.Component; + +/** + * Mock配置DTO映射器 + * 负责在Entity和DTO之间进行转换 + * @author: lyd + * @date: 2026/1/29 + */ +@Component +public class MockConfigDTOMapper { + + /** + * 将MockConfig实体转换为MockConfigVO + * + * @param entity MockConfig实体 + * @return MockConfigVO视图对象 + */ + public MockConfigVO toVO(MockConfig entity) { + if (entity == null) { + return null; + } + + return MockConfigVO.builder() + .id(entity.getId()) + .apiPath(entity.getApiPath()) + .apiMethod(entity.getApiMethod()) + .responseJson(entity.getResponseJson()) + .isEnabled(entity.getIsEnabled()) + .createTime(entity.getCreateTime()) + .updateTime(entity.getUpdateTime()) + .build(); + } + + /** + * 将CreateMockConfigDTO转换为MockConfig实体 + * + * @param dto 创建DTO + * @return MockConfig实体 + */ + public MockConfig toEntity(CreateMockConfigDTO dto) { + if (dto == null) { + return null; + } + + return MockConfig.builder() + .apiPath(dto.getApiPath()) + .apiMethod(dto.getApiMethod()) + .responseJson(dto.getResponseJson()) + .isEnabled(dto.getIsEnabled() != null ? dto.getIsEnabled() : true) + .build(); + } + + /** + * 使用UpdateMockConfigDTO更新MockConfig实体 + * 只更新DTO中非null的字段 + * + * @param entity 要更新的实体 + * @param dto 更新DTO + */ + public void updateEntity(MockConfig entity, UpdateMockConfigDTO dto) { + if (entity == null || dto == null) { + return; + } + + if (dto.getApiPath() != null) { + entity.setApiPath(dto.getApiPath()); + } + + if (dto.getApiMethod() != null) { + entity.setApiMethod(dto.getApiMethod()); + } + + if (dto.getResponseJson() != null) { + entity.setResponseJson(dto.getResponseJson()); + } + + if (dto.getIsEnabled() != null) { + entity.setIsEnabled(dto.getIsEnabled()); + } + } +} diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/MockConfigVO.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/MockConfigVO.java new file mode 100644 index 0000000..760233b --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/MockConfigVO.java @@ -0,0 +1,54 @@ +package org.nl.tool.mock.modular.mockconfig.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * Mock配置的视图对象(Value Object) + * 用于向客户端返回Mock配置数据 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MockConfigVO { + + /** + * 配置ID + */ + private String id; + + /** + * API路径 + */ + private String apiPath; + + /** + * HTTP请求方法 + */ + private String apiMethod; + + /** + * 返回的JSON数据 + */ + private String responseJson; + + /** + * 是否启用 + */ + private Boolean isEnabled; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; +} diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/UpdateMockConfigDTO.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/UpdateMockConfigDTO.java new file mode 100644 index 0000000..5434c52 --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/dto/UpdateMockConfigDTO.java @@ -0,0 +1,47 @@ +package org.nl.tool.mock.modular.mockconfig.dto; + +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * @author: lyd + * @date: 2026/1/29 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UpdateMockConfigDTO { + + /** + * API路径 + * 可选,如果提供则最大长度500 + */ + @Size(max = 500, message = "API path too long") + private String apiPath; + + /** + * HTTP请求方法 + * 可选,如果提供则必须是GET、POST、PUT、DELETE之一 + */ + @Pattern(regexp = "GET|POST|PUT|DELETE", message = "Invalid HTTP method") + private String apiMethod; + + /** + * 返回的JSON数据 + * 可选 + */ + private String responseJson; + + /** + * 是否启用 + * 可选 + */ + private Boolean isEnabled; +} + diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/entity/MockConfig.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/entity/MockConfig.java index a0a4620..ab98ca7 100644 --- a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/entity/MockConfig.java +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/entity/MockConfig.java @@ -14,8 +14,8 @@ package org.nl.tool.mock.modular.mockconfig.entity; import com.baomidou.mybatisplus.annotation.*; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; -import lombok.Setter; +import lombok.*; + import java.math.BigDecimal; import java.util.Date; @@ -25,8 +25,10 @@ import java.util.Date; * @author liyongde * @date 2026/01/28 17:50 **/ -@Getter -@Setter +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor @TableName("tool_mock_config") public class MockConfig { diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/provider/MockApiProvider.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/provider/MockApiProvider.java new file mode 100644 index 0000000..0baa0f0 --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/provider/MockApiProvider.java @@ -0,0 +1,35 @@ +package org.nl.tool.mock.modular.mockconfig.provider; + +import jakarta.annotation.Resource; +import org.nl.tool.api.MockApi; +import org.nl.tool.core.enums.HttpMethodEnum; +import org.nl.tool.mock.core.handle.HttpClientWrapper; +import org.springframework.stereotype.Service; + +/** + * + * @author: lyd + * @date: 2026/1/28 + */ +@Service +public class MockApiProvider implements MockApi { + @Resource + private HttpClientWrapper httpClientWrapper; + + /** + * 执行器,获取得到的数据自行转换(此功能只是通用调用三方接口,不做业务处理) + * @param url 路径(ip:端口号/api地址) + * @param method 方法类型:GET、POST、PUT、DELETED + * @param body 数据内容:JSONObject.toString + * @return + */ + @Override + public String executeRequestCommon(String url, HttpMethodEnum method, String body) { + return switch (method) { + case GET -> httpClientWrapper.get(url); + case POST -> httpClientWrapper.post(url, body); + case PUT -> httpClientWrapper.put(url, body); + case DELETE -> httpClientWrapper.delete(url); + }; + } +} diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/service/MockConfigService.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/service/MockConfigService.java index 5dd5bf3..1fcf536 100644 --- a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/service/MockConfigService.java +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/service/MockConfigService.java @@ -21,6 +21,7 @@ import org.nl.tool.mock.modular.mockconfig.param.MockConfigIdParam; import org.nl.tool.mock.modular.mockconfig.param.MockConfigPageParam; import java.util.List; +import java.util.Optional; /** * Mock配置表Service接口 @@ -77,4 +78,6 @@ public interface MockConfigService extends IService { * @date 2026/01/28 17:50 **/ MockConfig queryEntity(String id); + + Optional findByApiPathAndApiMethod(String apiPath, String apiMethod); } diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/service/impl/MockConfigServiceImpl.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/service/impl/MockConfigServiceImpl.java index 5f66230..2b64c68 100644 --- a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/service/impl/MockConfigServiceImpl.java +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/mockconfig/service/impl/MockConfigServiceImpl.java @@ -16,6 +16,7 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollStreamUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -33,6 +34,7 @@ import org.nl.tool.mock.modular.mockconfig.param.MockConfigPageParam; import org.nl.tool.mock.modular.mockconfig.service.MockConfigService; import java.util.List; +import java.util.Optional; /** * Mock配置表Service接口实现类 @@ -100,4 +102,12 @@ public class MockConfigServiceImpl extends ServiceImpl findByApiPathAndApiMethod(String apiPath, String apiMethod) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(MockConfig::getApiPath, apiPath) + .eq(MockConfig::getApiMethod, apiMethod); + return Optional.ofNullable(getOne(wrapper)); + } } diff --git a/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/package-info.java b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/package-info.java new file mode 100644 index 0000000..6d6ec1c --- /dev/null +++ b/nl-plugin-tool/src/main/java/org/nl/tool/mock/modular/package-info.java @@ -0,0 +1,6 @@ +/** + * 不同业务领域 + * @author: lyd + * @date: 2026/1/28 + */ +package org.nl.tool.mock.modular; \ No newline at end of file diff --git a/nl-plugin-tool/src/main/resources/application.yml b/nl-plugin-tool/src/main/resources/application.yml new file mode 100644 index 0000000..89bb1d9 --- /dev/null +++ b/nl-plugin-tool/src/main/resources/application.yml @@ -0,0 +1,19 @@ +# ============================================ +# Mock系统核心配置 +# ============================================ +mock: + # 全局Mock功能开关 + # true: 启用Mock功能(开发/测试环境) + # false: 禁用Mock功能(生产环境) + enabled: true + + # 缓存过期时间(秒) + # 建议值:300秒(5分钟) + # 较短的过期时间可以更快地反映配置变化 + # 较长的过期时间可以减少数据库访问 + cache-expiration-seconds: 300 + + # 是否记录Mock使用日志 + # true: 记录每次Mock数据的使用(便于调试) + # false: 不记录(减少日志量) + log-mock-usage: true \ No newline at end of file diff --git a/nl-vue/src/views/mock/mockconfig/form.vue b/nl-vue/src/views/mock/mockconfig/form.vue index ecd6b6c..e376531 100644 --- a/nl-vue/src/views/mock/mockconfig/form.vue +++ b/nl-vue/src/views/mock/mockconfig/form.vue @@ -58,11 +58,22 @@ // 打开抽屉 const onOpen = (record) => { open.value = true - if (record) { - let recordData = cloneDeep(record) - formData.value = Object.assign({}, recordData) - } + // 加载是否启用字典(0=否,1=是) isEnabledOptions.value = tool.dictList('IS_USED') + if (record) { + const recordData = cloneDeep(record) + // 后端/表格里是布尔值 true/false,这里统一转换成字典需要的 '0' / '1' + if (typeof recordData.isEnabled === 'boolean') { + recordData.isEnabled = recordData.isEnabled ? '1' : '0' + } else if (recordData.isEnabled === 1 || recordData.isEnabled === 0) { + recordData.isEnabled = String(recordData.isEnabled) + } + formData.value = Object.assign({}, recordData) + } else { + // 新增时默认启用:'1' + formData.value = {} + formData.value.isEnabled = '1' + } } // 关闭抽屉 const onClose = () => { @@ -80,6 +91,12 @@ .then(() => { submitLoading.value = true const formDataParam = cloneDeep(formData.value) + // 提交前将 '0' / '1' 转回数字 0 / 1(如果后端需要的话) + if (formDataParam.isEnabled === '1') { + formDataParam.isEnabled = 1 + } else if (formDataParam.isEnabled === '0') { + formDataParam.isEnabled = 0 + } mockConfigApi .mockConfigSubmitForm(formDataParam, formDataParam.id) .then(() => { diff --git a/nl-vue/src/views/mock/mockconfig/index.vue b/nl-vue/src/views/mock/mockconfig/index.vue index 0d8e71a..d783d68 100644 --- a/nl-vue/src/views/mock/mockconfig/index.vue +++ b/nl-vue/src/views/mock/mockconfig/index.vue @@ -50,7 +50,7 @@