diff --git a/.gitignore b/.gitignore index f1f022e..52bd810 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ nbdist/ AGENTS.md /.claude/ /llm-guard-modules/llm-guard-biz/scripts/ +/deploy/ +/docs/ diff --git a/llm-guard-common/llm-guard-common-security/src/main/java/com/llm/guard/common/security/config/ApplicationConfig.java b/llm-guard-common/llm-guard-common-security/src/main/java/com/llm/guard/common/security/config/ApplicationConfig.java index 2ada617..079503c 100644 --- a/llm-guard-common/llm-guard-common-security/src/main/java/com/llm/guard/common/security/config/ApplicationConfig.java +++ b/llm-guard-common/llm-guard-common-security/src/main/java/com/llm/guard/common/security/config/ApplicationConfig.java @@ -1,14 +1,18 @@ package com.llm.guard.common.security.config; +import java.text.SimpleDateFormat; import java.util.TimeZone; +import org.springframework.context.annotation.Configuration; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; +import java.util.Date; /** * 系统配置 * * @author llh */ +@Configuration public class ApplicationConfig { /** @@ -17,6 +21,12 @@ public class ApplicationConfig @Bean public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() { - return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder + .timeZone(TimeZone.getDefault()) + .simpleDateFormat("yyyy-MM-dd HH:mm:ss") + .deserializerByType(Date.class, new JacksonDateDeserializer()) + .serializerByType(Date.class, + new com.fasterxml.jackson.databind.ser.std.DateSerializer(false, + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))); } } diff --git a/llm-guard-common/llm-guard-common-security/src/main/java/com/llm/guard/common/security/config/JacksonDateDeserializer.java b/llm-guard-common/llm-guard-common-security/src/main/java/com/llm/guard/common/security/config/JacksonDateDeserializer.java new file mode 100644 index 0000000..f3cfc1b --- /dev/null +++ b/llm-guard-common/llm-guard-common-security/src/main/java/com/llm/guard/common/security/config/JacksonDateDeserializer.java @@ -0,0 +1,60 @@ +package com.llm.guard.common.security.config; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import com.fasterxml.jackson.databind.util.StdDateFormat; +import com.llm.guard.common.core.utils.DateUtils; + +import java.io.IOException; +import java.util.Date; + +/** + * Global Date deserializer that accepts legacy business formats and ISO-8601. + */ +public class JacksonDateDeserializer extends JsonDeserializer +{ + private static final StdDateFormat ISO_DATE_FORMAT = new StdDateFormat(); + + @Override + public Date deserialize(JsonParser parser, DeserializationContext context) throws IOException + { + String text = parser.getValueAsString(); + if (text == null) + { + return null; + } + + String value = text.trim(); + if (value.isEmpty()) + { + return null; + } + + if (value.matches("^\\d{10,13}$")) + { + long timestamp = Long.parseLong(value); + if (value.length() == 10) + { + timestamp = timestamp * 1000; + } + return new Date(timestamp); + } + + Date parsed = DateUtils.parseDate(value); + if (parsed != null) + { + return parsed; + } + + try + { + return ISO_DATE_FORMAT.parse(value); + } + catch (Exception ex) + { + throw InvalidFormatException.from(parser, "Unsupported date format", value, Date.class); + } + } +} diff --git a/llm-guard-common/llm-guard-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/llm-guard-common/llm-guard-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 73a996c..65f11e2 100644 --- a/llm-guard-common/llm-guard-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/llm-guard-common/llm-guard-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,4 @@ +com.llm.guard.common.security.config.ApplicationConfig com.llm.guard.common.security.config.WebMvcConfig com.llm.guard.common.security.service.TokenService com.llm.guard.common.security.aspect.PreAuthorizeAspect diff --git a/llm-guard-modules/llm-guard-biz/src/main/java/com/llm/guard/biz/domain/resp/AttackRuleResp.java b/llm-guard-modules/llm-guard-biz/src/main/java/com/llm/guard/biz/domain/resp/AttackRuleResp.java index 28efa40..cdadef3 100644 --- a/llm-guard-modules/llm-guard-biz/src/main/java/com/llm/guard/biz/domain/resp/AttackRuleResp.java +++ b/llm-guard-modules/llm-guard-biz/src/main/java/com/llm/guard/biz/domain/resp/AttackRuleResp.java @@ -1,5 +1,6 @@ package com.llm.guard.biz.domain.resp; +import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -39,6 +40,7 @@ public class AttackRuleResp { @Schema(description = "版本号") private String versionNo; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Schema(description = "到期时间") private Date expireAt; @@ -51,9 +53,11 @@ public class AttackRuleResp { @Schema(description = "状态") private String status; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Schema(description = "创建时间") private Date createdAt; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Schema(description = "更新时间") private Date updatedAt; } diff --git a/llm-guard-modules/llm-guard-biz/src/main/java/com/llm/guard/biz/service/config/impl/ConfigAttackBizServiceImpl.java b/llm-guard-modules/llm-guard-biz/src/main/java/com/llm/guard/biz/service/config/impl/ConfigAttackBizServiceImpl.java index 37835e9..34b7035 100644 --- a/llm-guard-modules/llm-guard-biz/src/main/java/com/llm/guard/biz/service/config/impl/ConfigAttackBizServiceImpl.java +++ b/llm-guard-modules/llm-guard-biz/src/main/java/com/llm/guard/biz/service/config/impl/ConfigAttackBizServiceImpl.java @@ -33,6 +33,8 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import java.util.Arrays; +import java.util.Locale; +import java.util.UUID; @Service @AllArgsConstructor @@ -112,6 +114,9 @@ public class ConfigAttackBizServiceImpl extends ConfigBizSupport implements Conf @Override public AjaxResult addAttackRule(AttackRuleParam param) { + if (param == null) { + return error("请求参数不能为空"); + } AjaxResult validateResult = validateScope(param.getScopeCode()); if (validateResult != null) { return validateResult; @@ -120,11 +125,15 @@ public class ConfigAttackBizServiceImpl extends ConfigBizSupport implements Conf if (validateResult != null) { return validateResult; } + normalizeAttackRuleParam(param, true); return toAjax(attackRuleService.save(convert(param, AttackRule::new))); } @Override public AjaxResult editAttackRule(AttackRuleParam param) { + if (param == null) { + return error("请求参数不能为空"); + } AjaxResult validateResult = validateScope(param.getScopeCode()); if (validateResult != null) { return validateResult; @@ -133,6 +142,7 @@ public class ConfigAttackBizServiceImpl extends ConfigBizSupport implements Conf if (validateResult != null) { return validateResult; } + normalizeAttackRuleParam(param, false); return toAjax(attackRuleService.updateById(convert(param, AttackRule::new))); } @@ -298,4 +308,40 @@ public class ConfigAttackBizServiceImpl extends ConfigBizSupport implements Conf ); return buildPageResp(query.getPageNum(), query.getPageSize(), page.getTotal(), convertList(page.getRecords(), AttackRuleResp::new)); } + + private void normalizeAttackRuleParam(AttackRuleParam param, boolean forCreate) { + if (StringUtils.isNotBlank(param.getCategory())) { + param.setCategory(param.getCategory().trim().toUpperCase(Locale.ROOT)); + } + if (StringUtils.isNotBlank(param.getSubType())) { + param.setSubType(param.getSubType().trim().toUpperCase(Locale.ROOT)); + } else if (StringUtils.isNotBlank(param.getEngineType())) { + param.setSubType(param.getEngineType().trim().toUpperCase(Locale.ROOT)); + } + if (forCreate && StringUtils.isBlank(param.getRuleCode())) { + param.setRuleCode(buildRuleCode(param.getCategory(), param.getSubType())); + } + if (forCreate && StringUtils.isBlank(param.getDetectMode())) { + param.setDetectMode("MIXED"); + } + if (forCreate && StringUtils.isBlank(param.getRiskLevel())) { + param.setRiskLevel("MEDIUM"); + } + if (forCreate && StringUtils.isBlank(param.getAction())) { + param.setAction("BLOCK"); + } + if (forCreate && StringUtils.isBlank(param.getStatus())) { + param.setStatus("ENABLED"); + } + if (forCreate && StringUtils.isBlank(param.getVersionNo())) { + param.setVersionNo("v1.0.0"); + } + } + + private String buildRuleCode(String category, String subType) { + String categoryPart = StringUtils.isBlank(category) ? "RULE" : category.trim().toUpperCase(Locale.ROOT); + String subTypePart = StringUtils.isBlank(subType) ? "GEN" : subType.trim().toUpperCase(Locale.ROOT); + String randomPart = UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase(Locale.ROOT); + return categoryPart + "_" + subTypePart + "_" + randomPart; + } }