refactor(service): 修改IEntityService泛型为VO类型并优化缓存策略

重构所有注解@CacheConfig的Service类,将IEntityService泛型从实体类改为VO类
实现实体与VO之间的转换逻辑,使用VO替代实体进行缓存以避免序列化问题
更新相关依赖组件和测试用例,确保功能完整性和系统兼容性
优化Redis缓存配置,清理旧缓存数据并验证新缓存策略有效性
This commit is contained in:
2025-09-28 18:19:00 +08:00
parent df6188db40
commit b03b5385a5
75 changed files with 3144 additions and 1377 deletions

View File

@@ -44,8 +44,6 @@ import com.ecep.contract.util.TaskMonitorCenter;
})
@EnableScheduling
@EnableAsync
@EnableCaching
@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO)
public class SpringApp {

View File

@@ -160,6 +160,7 @@ public class LoginApiController {
// 其他错误
result.put("success", false);
result.put("error", "登录过程中发生错误: " + e.getMessage());
logger.error("登录错误:{}", e);
}
return result;

View File

@@ -34,14 +34,14 @@ public class AbstractCtx {
private Map<Class<?>, Object> cachedBeans = new HashMap<>();
public <T> T getBean(Class<T> requiredType) throws BeansException {
return SpringApp.getBean(requiredType);
return getCachedBean(requiredType);
}
@SuppressWarnings("unchecked")
public <T> T getCachedBean(Class<T> requiredType) throws BeansException {
Object object = cachedBeans.get(requiredType);
if (object == null) {
object = getBean(requiredType);
object = SpringApp.getBean(requiredType);
cachedBeans.put(requiredType, object);
}
return (T) object;
@@ -60,7 +60,7 @@ public class AbstractCtx {
}
public boolean updateText(Supplier<String> getter, Consumer<String> setter, String text, MessageHolder holder,
String topic) {
String topic) {
if (!Objects.equals(getter.get(), text)) {
setter.accept(text);
holder.info(topic + "修改为: " + text);
@@ -70,7 +70,7 @@ public class AbstractCtx {
}
public boolean updateAppendText(Supplier<String> getter, Consumer<String> setter, String text, MessageHolder holder,
String topic) {
String topic) {
if (StringUtils.hasText(text)) {
String str = MyStringUtils.appendIfAbsent(getter.get(), text);
if (!Objects.equals(getter.get(), str)) {
@@ -83,7 +83,7 @@ public class AbstractCtx {
}
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, java.sql.Date date,
MessageHolder holder, String topic) {
MessageHolder holder, String topic) {
if (date != null) {
return updateLocalDate(getter, setter, date.toLocalDate(), holder, topic);
}
@@ -91,12 +91,12 @@ public class AbstractCtx {
}
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, LocalDate date,
MessageHolder holder, String topic) {
MessageHolder holder, String topic) {
return updateLocalDate(getter, setter, date, holder, topic, false);
}
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, LocalDate date,
MessageHolder holder, String topic, boolean allowNull) {
MessageHolder holder, String topic, boolean allowNull) {
if (date == null && !allowNull) {
return false;
}
@@ -109,7 +109,7 @@ public class AbstractCtx {
}
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, String strDate,
MessageHolder holder, String topic) {
MessageHolder holder, String topic) {
LocalDate date = null;
if (StringUtils.hasText(strDate)) {
try {
@@ -122,7 +122,7 @@ public class AbstractCtx {
}
public boolean updateLocalDate(Supplier<LocalDate> getter, Consumer<LocalDate> setter, Timestamp timestamp,
MessageHolder holder, String topic) {
MessageHolder holder, String topic) {
LocalDate date = null;
if (timestamp != null) {
@@ -136,7 +136,7 @@ public class AbstractCtx {
}
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter,
Timestamp timestamp, MessageHolder holder, String topic) {
Timestamp timestamp, MessageHolder holder, String topic) {
LocalDateTime dateTime = null;
if (timestamp != null) {
@@ -152,7 +152,7 @@ public class AbstractCtx {
}
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter,
LocalDateTime dateTime, MessageHolder holder, String topic) {
LocalDateTime dateTime, MessageHolder holder, String topic) {
if (!Objects.equals(getter.get(), dateTime)) {
setter.accept(dateTime);
holder.info(topic + "修改为: " + dateTime);
@@ -162,7 +162,7 @@ public class AbstractCtx {
}
public boolean updateInstant(Supplier<Instant> getter, Consumer<Instant> setter, Instant instant,
MessageHolder holder, String topic) {
MessageHolder holder, String topic) {
if (!Objects.equals(getter.get(), instant)) {
setter.accept(instant);
holder.info(topic + "修改为: " + instant);
@@ -172,13 +172,13 @@ public class AbstractCtx {
}
public boolean updateNumber(Supplier<Double> getter, Consumer<Double> setter, BigDecimal value,
MessageHolder holder, String topic) {
MessageHolder holder, String topic) {
double val = value.doubleValue();
return updateNumber(getter, setter, val, holder, topic);
}
public boolean updateNumber(Supplier<Double> getter, Consumer<Double> setter, double value, MessageHolder holder,
String topic) {
String topic) {
if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
setter.accept(value);
holder.info(topic + "修改为: " + value);
@@ -188,7 +188,7 @@ public class AbstractCtx {
}
public boolean updateNumber(Supplier<Float> getter, Consumer<Float> setter, float value, MessageHolder holder,
String topic) {
String topic) {
if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
setter.accept(value);
holder.info(topic + "修改为: " + value);
@@ -198,7 +198,7 @@ public class AbstractCtx {
}
public boolean updateNumber(Supplier<Integer> getter, Consumer<Integer> setter, Integer value, MessageHolder holder,
String topic) {
String topic) {
if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
setter.accept(value);
holder.info(topic + "修改为: " + value);

View File

@@ -39,9 +39,8 @@ public class CloudRkSyncTask extends Tasker<Object> {
try {
cloudRkCtx = new CloudRkCtx();
service = SpringApp.getBean(CloudRkService.class);
cloudRkCtx.setCloudRkService(service);
} catch (BeansException e) {
holder.error("没有找到 " + CloudServiceConstant.RK_NAME + " 服务");
holder.error(CloudServiceConstant.RK_NAME + " 服务未启用");
return null;
}

View File

@@ -1,4 +1,12 @@
package com.ecep.contract.cloud.rk.ctx;
import com.ecep.contract.cloud.rk.CloudRkService;
import org.springframework.beans.BeansException;
public interface CloudRkContext {
<T> T getBean(Class<T> requiredType) throws BeansException;
default CloudRkService getCloudRkService() {
return getBean(CloudRkService.class);
}
}

View File

@@ -1,7 +1,5 @@
package com.ecep.contract.cloud.rk.ctx;
import static com.ecep.contract.SpringApp.getBean;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -31,13 +29,11 @@ import org.springframework.util.StringUtils;
import com.ecep.contract.BlackReasonType;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.SpringApp;
import com.ecep.contract.cloud.AbstractCtx;
import com.ecep.contract.cloud.rk.CloudRkService;
import com.ecep.contract.ds.company.service.CompanyBlackReasonService;
import com.ecep.contract.ds.company.service.CompanyContactService;
import com.ecep.contract.ds.company.service.CompanyOldNameService;
import com.ecep.contract.ds.company.service.CompanyService;
import com.ecep.contract.model.CloudRk;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyBlackReason;
@@ -50,46 +46,16 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Setter;
public class CloudRkCtx extends AbstractCtx {
public class CloudRkCtx extends AbstractCtx implements CloudRkContext {
private static final Logger logger = LoggerFactory.getLogger(CloudRkCtx.class);
@Setter
private CloudRkService cloudRkService;
@Setter
private CompanyService companyService;
@Setter
private CompanyBlackReasonService companyBlackReasonService;
@Setter
private ObjectMapper objectMapper;
private Proxy socksProxy;
public CloudRkService getCloudRkService() {
if (cloudRkService == null) {
cloudRkService = getBean(CloudRkService.class);
}
return cloudRkService;
}
public CompanyService getCompanyService() {
if (companyService == null) {
companyService = getBean(CompanyService.class);
}
return companyService;
}
CompanyBlackReasonService getCompanyBlackReasonService() {
if (companyBlackReasonService == null) {
companyBlackReasonService = getBean(CompanyBlackReasonService.class);
}
return companyBlackReasonService;
return getCachedBean(CompanyBlackReasonService.class);
}
ObjectMapper getObjectMapper() {
if (objectMapper == null) {
objectMapper = getBean(ObjectMapper.class);
}
return objectMapper;
return getCachedBean(ObjectMapper.class);
}
Proxy getSocksProxy() {
@@ -150,7 +116,11 @@ public class CloudRkCtx extends AbstractCtx {
} catch (Exception e) {
// 异常
logger.error("使用评分接口更新企业资信评价等级时发生错误", e);
cloudRk.setDescription("评分接口错误:" + e.getMessage());
String message = "评分接口错误:" + e.getMessage();
if (message.length() > 50) {
message = message.substring(0, 50);
}
cloudRk.setDescription(message);
}
return updated;
}
@@ -172,11 +142,12 @@ public class CloudRkCtx extends AbstractCtx {
}
}
CompanyBlackReasonService companyBlackReasonService = getCompanyBlackReasonService();
String api = getConfService().getString(CloudRkService.KEY_BLACK_LIST_URL);
List<String> companyNames = getCompanyService().getAllNames(company);
List<CompanyBlackReason> reasonList = new ArrayList<>();
List<CompanyBlackReason> dbReasons = getCompanyBlackReasonService().findAllByCompany(company);
List<CompanyBlackReason> dbReasons = companyBlackReasonService.findAllByCompany(company);
for (String name : companyNames) {
String url = api + URLEncoder.encode(name, StandardCharsets.UTF_8);
try {
@@ -195,7 +166,7 @@ public class CloudRkCtx extends AbstractCtx {
}
for (CompanyBlackReason companyBlackReason : reasonList) {
getCompanyBlackReasonService().save(companyBlackReason);
companyBlackReasonService.save(companyBlackReason);
}
cloudRk.setCloudBlackListUpdated(LocalDateTime.now());
return true;
@@ -634,14 +605,15 @@ public class CloudRkCtx extends AbstractCtx {
return modified;
}
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter, JsonNode data,String field,
public boolean updateLocalDateTime(Supplier<LocalDateTime> getter, Consumer<LocalDateTime> setter, JsonNode data,
String field,
MessageHolder holder, String topic) {
JsonNode node = data.get(field);
JsonNode node = data.get(field);
if (node == null || node.isNull()) {
return false;
}
LocalDateTime updated = getObjectMapper().convertValue(node, LocalDateTime.class);
updateLocalDateTime(getter, setter, updated, holder, topic);
LocalDateTime updated = getObjectMapper().convertValue(node, LocalDateTime.class);
updateLocalDateTime(getter, setter, updated, holder, topic);
return false;
}
@@ -654,7 +626,7 @@ public class CloudRkCtx extends AbstractCtx {
return;
}
CompanyContactService contactService = SpringApp.getBean(CompanyContactService.class);
CompanyContactService contactService = getCachedBean(CompanyContactService.class);
List<CompanyContact> contactList = contactService.findAllByCompanyAndName(company, legalRepresentative);
if (contactList == null) {
// db error
@@ -746,7 +718,7 @@ public class CloudRkCtx extends AbstractCtx {
historyNames.add(trimmed);
}
}
CompanyOldNameService service = SpringApp.getBean(CompanyOldNameService.class);
CompanyOldNameService service = getCachedBean(CompanyOldNameService.class);
List<CompanyOldName> oldNames = service.findAllByCompany(company);
for (CompanyOldName oldName : oldNames) {
// 已经存在的移除

View File

@@ -6,10 +6,9 @@ import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.service.tasker.WebSocketServerTasker;
import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -47,10 +47,6 @@ public class AbstractYongYouU8Ctx extends AbstractCtx {
}
}
public CompanyService getCompanyService() {
return getCachedBean(CompanyService.class);
}
public CompanyCustomerService getCompanyCustomerService() {
return getCachedBean(CompanyCustomerService.class);
}

View File

@@ -6,6 +6,7 @@ import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
import com.ecep.contract.ds.company.CompanyContext;
import org.springframework.util.StringUtils;
import com.ecep.contract.MessageHolder;
@@ -18,17 +19,7 @@ import com.ecep.contract.model.CompanyOldName;
import lombok.Setter;
public class CompanyCtx extends AbstractYongYouU8Ctx {
@Setter
private CompanyOldNameService companyOldNameService;
CompanyOldNameService getCompanyOldNameService() {
if (companyOldNameService == null) {
companyOldNameService = getBean(CompanyOldNameService.class);
}
return companyOldNameService;
}
public class CompanyCtx extends AbstractYongYouU8Ctx implements CompanyContext {
public boolean updateCompanyNameIfAbsent(Company company, String name, MessageHolder holder) {
if (!StringUtils.hasText(name)) {
return false;
@@ -110,7 +101,7 @@ public class CompanyCtx extends AbstractYongYouU8Ctx {
return null;
}
CompanyService companyService = getCompanyService();
String autoCreateAfter = getConfService().getString(CloudYuConstant.KEY_AUTO_CREATE_COMPANY_AFTER);
String autoCreateAfter = getConfService().getString(CloudYuConstant.KEY_AUTO_CREATE_COMPANY_AFTER);
// 当配置存在,且开发时间小于指定时间,不创建
if (StringUtils.hasText(autoCreateAfter)) {
LocalDate miniDate = LocalDate.parse(autoCreateAfter);

View File

@@ -0,0 +1,64 @@
package com.ecep.contract.config;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
/**
* 自定义LocalDateTime反序列化器支持多种格式
*/
class CustomLocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> {
private final List<DateTimeFormatter> formatters;
public CustomLocalDateTimeDeserializer() {
super(LocalDateTime.class);
// 支持多种日期时间格式
this.formatters = new ArrayList<>();
// ISO标准格式
this.formatters.add(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
// 项目默认格式 yyyy-MM-dd HH:mm:ss
this.formatters.add(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// 其他可能的格式
this.formatters.add(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
this.formatters.add(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss"));
this.formatters.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm"));
this.formatters.add(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
}
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String value = p.getText().trim();
if (value == null || value.isEmpty()) {
return null;
}
// 尝试使用各种格式解析
for (DateTimeFormatter formatter : formatters) {
try {
return LocalDateTime.parse(value, formatter);
} catch (Exception e) {
// 尝试下一种格式
continue;
}
}
// 如果所有格式都失败尝试使用ISO_INSTANT格式可能是时间戳字符串
try {
return LocalDateTime.ofInstant(Instant.parse(value), ZoneId.systemDefault());
} catch (Exception e) {
// 仍然失败,抛出异常
throw new JsonParseException(p, "无法解析为LocalDateTime: " + value);
}
}
}

View File

@@ -0,0 +1,111 @@
package com.ecep.contract.config;
import java.io.IOException;
import org.hibernate.proxy.HibernateProxy;
import com.ecep.contract.model.IdentityEntity;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
/**
* 用于处理Hibernate代理对象的反序列化器
* 专门处理序列化时写入的id和_proxy_字段
*/
class HibernateProxyDeserializer extends StdDeserializer<HibernateProxy> {
protected HibernateProxyDeserializer() {
super(HibernateProxy.class);
}
@Override
public HibernateProxy deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// 检查是否是对象类型
if (p.currentToken() != JsonToken.START_OBJECT) {
return (HibernateProxy) ctxt.handleUnexpectedToken(HibernateProxy.class, p);
}
// 读取对象字段
JsonToken token;
Object id = null;
String proxyClassName = null;
// 解析JSON对象的所有字段
while ((token = p.nextToken()) != JsonToken.END_OBJECT) {
if (token == JsonToken.FIELD_NAME) {
String fieldName = p.getCurrentName();
p.nextToken(); // 移动到字段值
if ("id".equals(fieldName)) {
id = p.readValueAs(Object.class);
} else if ("_proxy_".equals(fieldName)) {
proxyClassName = p.getText();
}
}
}
// 如果同时存在id和_proxy_字段说明这是一个Hibernate代理对象
if (id != null && proxyClassName != null) {
HibernateProxy proxy = null;
try {
// 尝试获取实体类类型(去掉代理类名中的字节码增强部分)
// 代理类名通常格式为com.ecep.contract.model.Entity$HibernateProxy$XXXX
int proxyIndex = proxyClassName.indexOf("$HibernateProxy$");
String entityClassName = proxyIndex > 0 ? proxyClassName.substring(0, proxyIndex) : proxyClassName;
// 加载实体类
Class<?> entityClass = Class.forName(entityClassName);
// 检查是否是IdentityEntity类型
if (IdentityEntity.class.isAssignableFrom(entityClass)) {
// 创建实体对象并设置ID
IdentityEntity entity = (IdentityEntity) entityClass.getDeclaredConstructor().newInstance();
if (id instanceof Number) {
entity.setId(((Number) id).intValue());
} else {
// 处理非数值类型的ID
try {
entity.setId(Integer.parseInt(id.toString()));
} catch (NumberFormatException e) {
// 如果无法转换为Integer记录警告但继续处理
}
}
return (HibernateProxy) entity;
}
} catch (Exception e) {
// 发生异常时返回一个只包含ID的简单对象
// 这可以避免反序列化失败
return (HibernateProxy) createSimpleIdObject(id);
}
}
// 如果不是代理对象或处理失败,回退到默认的反序列化逻辑
// 使用 skipChildren() 确保解析器在正确的位置开始默认反序列化
if (p.currentToken() != JsonToken.START_OBJECT) {
// 尝试查找对象开始位置
while (p.currentToken() != null && p.currentToken() != JsonToken.START_OBJECT) {
p.nextToken();
}
}
if (p.currentToken() == JsonToken.START_OBJECT) {
return (HibernateProxy) ctxt.readValue(p, Object.class);
}
return null;
}
/**
* 创建一个只包含ID字段的简单对象
*/
private Object createSimpleIdObject(final Object id) {
return new Object() {
@Override
public String toString() {
return "ProxyObject[id=" + id + "]";
}
};
}
}

View File

@@ -0,0 +1,111 @@
package com.ecep.contract.config;
import java.io.IOException;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import com.ecep.contract.model.IdentityEntity;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
/**
* 专门用于处理HibernateProxy对象的序列化器
*/
class HibernateProxySerializer extends StdSerializer<HibernateProxy> {
protected HibernateProxySerializer() {
super(HibernateProxy.class);
}
@Override
public void serialize(HibernateProxy value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
// 尝试初始化代理对象如果未初始化则只输出ID
try {
LazyInitializer lazyInitializer = value.getHibernateLazyInitializer();
if (lazyInitializer.isUninitialized()) {
// 如果代理对象未初始化只输出ID
if (lazyInitializer.getIdentifier() != null) {
gen.writeStartObject();
gen.writeFieldName("id");
gen.writeObject(lazyInitializer.getIdentifier());
gen.writeStringField("_proxy_", lazyInitializer.getEntityName());
gen.writeEndObject();
} else {
gen.writeNull();
}
} else {
// 如果代理对象已初始化,获取实际对象并序列化
Object unwrapped = lazyInitializer.getImplementation();
if (unwrapped instanceof IdentityEntity) {
// 对于IdentityEntity类型输出更简洁的格式
gen.writeStartObject();
gen.writeNumberField("id", ((IdentityEntity) unwrapped).getId());
gen.writeEndObject();
} else {
// 对于非IdentityEntity类型使用默认序列化
serializers.defaultSerializeValue(unwrapped, gen);
}
}
} catch (Exception e) {
// 如果发生异常,输出最小化信息
gen.writeStartObject();
gen.writeStringField("error", "Failed to serialize Hibernate proxy: " + e.getMessage());
gen.writeStringField("class", value.getClass().getName());
gen.writeEndObject();
}
}
@Override
public void serializeWithType(HibernateProxy value, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
// 当启用类型信息时Jackson会调用这个方法
try {
LazyInitializer lazyInitializer = value.getHibernateLazyInitializer();
// 获取实际的实体类型名称
String entityTypeName = lazyInitializer.getEntityName();
Class<?> entityClass = Class.forName(entityTypeName);
if (lazyInitializer.isUninitialized()) {
// 确保输出@class: 'org.hibernate.proxy.HibernateProxy'
// 直接使用HibernateProxy.class作为typeId
gen.writeStartObject();
gen.writeStringField("@class", "org.hibernate.proxy.HibernateProxy");
// 对于未初始化的对象,添加代理信息
gen.writeStringField("_proxy_", entityTypeName);
// 写入ID信息
if (lazyInitializer.getIdentifier() != null) {
gen.writeFieldName("id");
gen.writeObject(lazyInitializer.getIdentifier());
}
gen.writeEndObject();
} else {
// 如果代理对象已初始化,获取实际对象
Object unwrapped = lazyInitializer.getImplementation();
// 使用实际对象的类型信息进行序列化
// 查找能够处理类型信息的序列化器
JsonSerializer<Object> serializer = serializers.findTypedValueSerializer(
unwrapped.getClass(), true, null);
// 委托给实际对象的序列化器处理,确保类型信息正确添加
serializer.serializeWithType(unwrapped, gen, serializers, typeSer);
}
} catch (Exception e) {
// 如果发生异常,输出最小化信息并添加类型信息
// 先写入类型前缀
typeSer.writeTypePrefix(gen, typeSer.typeId(value.getClass(), JsonToken.START_OBJECT));
// 输出错误信息
gen.writeStringField("error", "Failed to serialize Hibernate proxy with type: " + e.getMessage());
gen.writeStringField("class", value.getClass().getName());
// 写入类型后缀
typeSer.writeTypeSuffix(gen, typeSer.typeId(value.getClass(), JsonToken.START_OBJECT));
}
}
}

View File

@@ -5,30 +5,32 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Collections;
import org.hibernate.Hibernate;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import com.ecep.contract.model.IdentityEntity;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
@@ -43,7 +45,6 @@ public class JacksonConfig {
@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.build();
// 关闭日期时间格式化输出为时间戳而是输出为ISO格式的字符串
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
@@ -52,6 +53,9 @@ public class JacksonConfig {
// 处理循环引用,启用引用处理功能
objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
// 禁用在遇到空Bean时抛出异常
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 配置Java 8时间模块的序列化格式
JavaTimeModule javaTimeModule = new JavaTimeModule();
// LocalDate
@@ -63,154 +67,23 @@ public class JacksonConfig {
// LocalDateTime
javaTimeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
javaTimeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// 自定义LocalDateTime反序列化器支持多种格式
javaTimeModule.addDeserializer(LocalDateTime.class, new CustomLocalDateTimeDeserializer());
objectMapper.registerModule(javaTimeModule);
// 添加自定义模块用于处理JPA/Hibernate代理对象
SimpleModule proxyModule = new SimpleModule("HibernateProxyModule");
// SimpleModule proxyModule = new SimpleModule("HibernateProxyModule");
// 添加代理对象序列化器只输出ID字段
proxyModule.addSerializer(HibernateProxy.class, new HibernateProxySerializer());
// 使用BeanSerializerModifier来处理IdentityEntity类型避免递归调用
proxyModule.setSerializerModifier(new BeanSerializerModifier() {
@Override
public JsonSerializer<?> modifySerializer(SerializationConfig config,
BeanDescription beanDesc,
JsonSerializer<?> serializer) {
// 只对IdentityEntity类型进行修改
if (IdentityEntity.class.isAssignableFrom(beanDesc.getBeanClass()) &&
!HibernateProxy.class.isAssignableFrom(beanDesc.getBeanClass())) {
return new IdentityEntitySerializer(serializer);
}
return serializer;
}
});
// // 添加懒加载初始化器序列化器
// proxyModule.addSerializer(LazyInitializer.class, new
// HibernateProxySerializer());
// // 添加Hibernate代理对象反序列化器
// proxyModule.addDeserializer(HibernateProxy.class, new
// HibernateProxyDeserializer());
objectMapper.registerModule(proxyModule);
// objectMapper.registerModule(proxyModule);
return objectMapper;
}
/**
* 专门用于处理Hibernate代理对象的序列化器
*/
private static class HibernateProxySerializer extends StdSerializer<HibernateProxy> {
protected HibernateProxySerializer() {
super(HibernateProxy.class);
}
@Override
public void serialize(HibernateProxy value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
// 尝试初始化代理对象如果未初始化则只输出ID
try {
Object unwrapped = value.getHibernateLazyInitializer().getImplementation();
// 检查是否为IdentityEntity实现类
if (unwrapped instanceof IdentityEntity) {
gen.writeStartObject();
gen.writeNumberField("id", ((IdentityEntity) unwrapped).getId());
gen.writeEndObject();
} else {
// 如果不是IdentityEntity使用默认序列化
serializers.defaultSerializeValue(unwrapped, gen);
}
} catch (Exception e) {
// 如果初始化失败只输出ID
if (value instanceof IdentityEntity) {
gen.writeStartObject();
gen.writeNumberField("id", ((IdentityEntity) value).getId());
gen.writeEndObject();
} else {
// 如果不是IdentityEntity输出对象的基本信息
gen.writeStartObject();
gen.writeStringField("class", value.getClass().getName());
gen.writeEndObject();
}
}
}
}
/**
* 用于处理IdentityEntity类型的序列化器包装器
*/
private static class IdentityEntitySerializer extends StdSerializer<Object> {
private final JsonSerializer<?> delegate;
protected IdentityEntitySerializer(JsonSerializer<?> delegate) {
super(Object.class);
this.delegate = delegate;
}
@SuppressWarnings("unchecked")
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
// 对于IdentityEntity对象如果未初始化则只输出ID
if (value instanceof IdentityEntity && !Hibernate.isInitialized(value)) {
gen.writeStartObject();
gen.writeNumberField("id", ((IdentityEntity) value).getId());
gen.writeStringField("_proxy_", value.getClass().getName());
gen.writeEndObject();
} else {
// 已初始化的实体,使用原始的序列化器进行序列化,避免递归调用
((JsonSerializer<Object>) delegate).serialize(value, gen, serializers);
}
}
}
/**
* 用于处理对象引用的序列化器,通过检测和管理对象引用避免循环引用问题
*/
private static class IdentityReferenceSerializer extends StdSerializer<Object> {
private final JsonSerializer<?> delegate;
private final ThreadLocal<java.util.Set<Object>> visitedObjects = ThreadLocal
.withInitial(java.util.HashSet::new);
protected IdentityReferenceSerializer(JsonSerializer<?> delegate) {
super(Object.class);
this.delegate = delegate;
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 处理null值
if (value == null) {
serializers.defaultSerializeNull(gen);
return;
}
java.util.Set<Object> visited = visitedObjects.get();
try {
// 检查对象是否已经被访问过
if (visited.contains(value)) {
// 如果对象实现了IdentityEntity只输出ID
if (value instanceof IdentityEntity) {
gen.writeStartObject();
gen.writeNumberField("id", ((IdentityEntity) value).getId());
gen.writeStringField("_ref_", "" + ((IdentityEntity) value).getId());
gen.writeEndObject();
} else {
// 对于非IdentityEntity对象输出对象的toString或hashCode
gen.writeString(value.toString());
}
return;
}
// 标记对象为已访问
visited.add(value);
// 使用委托序列化器进行正常序列化
((JsonSerializer<Object>) delegate).serialize(value, gen, serializers);
} finally {
// 清理访问记录
visited.remove(value);
if (visited.isEmpty()) {
visitedObjects.remove();
}
}
}
}
}

View File

@@ -0,0 +1,131 @@
package com.ecep.contract.config;
import java.time.Duration;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.util.HibernateProxyUtils;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
/**
* Redis缓存配置类用于配置Redis的缓存管理器和序列化器
*/
@Configuration
@EnableCaching
public class RedisCacheConfig {
/**
* 配置RedisTemplate使用与JacksonConfig相同的ObjectMapper以确保Hibernate代理对象被正确序列化
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory,
ObjectMapper objectMapper) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 设置键的序列化器
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 使用GenericJackson2JsonRedisSerializer并配置为使用我们自定义的ObjectMapper
// 这个ObjectMapper已经包含了处理Hibernate代理对象的序列化器
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(
objectMapper);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
/**
* 配置RedisCacheManager使用与JacksonConfig相同的ObjectMapper以确保Hibernate代理对象被正确序列化
* 并启用类型信息存储以解决LinkedHashMap无法转换为具体类型的问题
*/
// @Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
// 创建ObjectMapper的副本而不是直接修改注入的实例
ObjectMapper cacheObjectMapper = objectMapper.copy();
// 配置ObjectMapper副本以保留类型信息仅用于Redis缓存
// 使用activateDefaultTyping代替已弃用的enableDefaultTyping
cacheObjectMapper.activateDefaultTyping(
cacheObjectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
// 确保我们的HibernateProxyDeserializer被正确注册
// 创建一个新的模块来注册自定义反序列化器
SimpleModule proxyModule = new SimpleModule("CacheHibernateProxyModule");
// 添加代理对象序列化器只输出ID字段
proxyModule.addSerializer(HibernateProxy.class, new HibernateProxySerializer());
proxyModule.addDeserializer(HibernateProxy.class, new HibernateProxyDeserializer());
// 使用BeanSerializerModifier来处理IdentityEntity类型避免递归调用
proxyModule.setSerializerModifier(new BeanSerializerModifier() {
@Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc,
JsonSerializer<?> serializer) {
Class<?> beanClass = beanDesc.getBeanClass();
if (beanClass.isPrimitive()) {
return serializer;
}
System.out.println("modifySerializer:" + beanDesc.getBeanClass() + ", serializer:"
+ serializer);
return serializer;
}
});
proxyModule.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
Class<?> beanClass = beanDesc.getBeanClass();
if (beanClass.isPrimitive()) {
return deserializer;
}
System.out.println("modifyDeserializer:" + beanDesc.getBeanClass() + ", deserializer:"
+ deserializer);
return deserializer;
}
});
cacheObjectMapper.registerModule(proxyModule);
// 创建Redis缓存配置使用包含Hibernate代理处理的ObjectMapper副本
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(6)) // 设置缓存过期时间为6小时
.serializeKeysWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer(cacheObjectMapper)))
.disableCachingNullValues(); // 不缓存null值
// 创建Redis缓存管理器为所有缓存名称配置相同的序列化策略以确保类型安全反序列化
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
}

View File

@@ -130,7 +130,7 @@ public class SecurityConfig {
// 注意根据系统设计Employee实体中没有密码字段系统使用IP/MAC绑定认证
// 这里使用密码编码器加密后的固定密码,确保认证流程能够正常工作
return User.builder()
.username(employee.getName())
.username(employee.getAccount())
.password(passwordEncoder().encode("default123")) // 使用默认密码进行加密
.accountExpired(false) // 账户未过期
.accountLocked(false) // 账户未锁定

View File

@@ -1,9 +1,17 @@
package com.ecep.contract.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ecep.contract.SpringApp;
import com.ecep.contract.ds.other.service.EmployeeService;
import com.ecep.contract.model.Employee;
/**
* 基础控制器,处理根路径请求
*/
@@ -15,7 +23,22 @@ public class IndexController {
*/
@GetMapping("/")
public ResponseEntity<?> index() {
return ResponseEntity.ok("合同管理系统 REST API 服务已启动");
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails userDetails) {
System.out.println(userDetails.getUsername());
EmployeeService employeeService = SpringApp.getBean(EmployeeService.class);
try {
Employee employee = employeeService.findByAccount(userDetails.getUsername());
return ResponseEntity.ok(employee);
} catch (Exception e) {
e.printStackTrace();
}
}
return ResponseEntity.ok(authentication);
}
}

View File

@@ -1,4 +1,37 @@
package com.ecep.contract.ds.company;
import com.ecep.contract.ds.company.service.*;
import org.springframework.beans.BeansException;
public interface CompanyContext {
<T> T getBean(Class<T> requiredType) throws BeansException;
default CompanyService getCompanyService() {
return getBean(CompanyService.class);
}
default CompanyOldNameService getCompanyOldNameService() {
return getBean(CompanyOldNameService.class);
}
default CompanyFileTypeService getCompanyFileTypeService() {
return getBean(CompanyFileTypeService.class);
}
default CompanyFileService getCompanyFileService() {
return getBean(CompanyFileService.class);
}
default CompanyBankAccountService getCompanyBankAccountService() {
return getBean(CompanyBankAccountService.class);
}
default CompanyBlackReasonService getCompanyBlackReasonService() {
return getBean(CompanyBlackReasonService.class);
}
default CompanyInvoiceInfoService getCompanyInvoiceInfoService() {
return getBean(CompanyInvoiceInfoService.class);
}
}

View File

@@ -83,7 +83,7 @@ public class ContractFileTypeService
}
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "#p0.id"), @CacheEvict(key = "'by-type-'+#p0.type.name()+'-'+#p0.lang"),
@CacheEvict(key = "'all-'+#p0.getLang()")
})
@Override
@@ -93,6 +93,7 @@ public class ContractFileTypeService
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'by-type-'+#p0.type.name()+'-'+#p0.lang"),
@CacheEvict(key = "'all-'+#p0.getLang()")
})
@Override
@@ -117,4 +118,8 @@ public class ContractFileTypeService
model.setSuggestFileName(vo.getSuggestFileName());
}
@Cacheable(key = "'by-type-'+#p0.name()+'-'+#p1.toLanguageTag()")
public ContractFileTypeLocal findByTypeAndLang(ContractFileType type, Locale locale) {
return repository.findByTypeAndLang(type, locale.toLanguageTag());
}
}

View File

@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.DoubleSummaryStatistics;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -29,7 +28,7 @@ import com.ecep.contract.ds.company.service.CompanyExtendInfoService;
import com.ecep.contract.ds.contract.service.ExtendVendorInfoService;
import com.ecep.contract.ds.converter.NumberStringConverter;
import com.ecep.contract.ds.other.service.EmployeeService;
import com.ecep.contract.ds.project.ProjectCostImportItemsFromContractsTasker;
import com.ecep.contract.service.tasker.ProjectCostImportItemsFromContractsTasker;
import com.ecep.contract.ds.project.service.ProjectBidService;
import com.ecep.contract.ds.project.service.ProjectCostService;
import com.ecep.contract.ds.project.service.ProjectQuotationService;
@@ -197,7 +196,7 @@ public class ContractVerifyComm extends VerifyContext {
/**
* 验证合同是否合规
*
*
* @param company
* @param contract
* @param holder
@@ -227,7 +226,9 @@ public class ContractVerifyComm extends VerifyContext {
case RECEIVE -> {
// 销售合同
CompanyExtendInfo companyExtendInfo = getCompanyExtendInfoService().findByCompany(company);
verifyAsCustomer(company, companyExtendInfo, contract, holder);
if (!verifyAsCustomer(company, companyExtendInfo, contract, holder)) {
passed = false;
}
// 销售合同下的采购合同
List<Contract> list = getContractService().findAllByParent(contract);
@@ -325,7 +326,7 @@ public class ContractVerifyComm extends VerifyContext {
}
private void verifyVendorFile(VendorGroup group, boolean assignedProvider, Contract contract,
MessageHolder holder) {
MessageHolder holder) {
if (group == null) {
return;
}
@@ -400,7 +401,7 @@ public class ContractVerifyComm extends VerifyContext {
}
String getFileTypeLocalValue(ContractFileType type) {
ContractFileTypeLocal fileTypeLocal = getFileTypeLocal(type);
ContractFileTypeLocal fileTypeLocal = getContractFileTypeService().findByTypeAndLang(type, getLocale());
if (fileTypeLocal == null) {
return type.name();
}
@@ -420,18 +421,20 @@ public class ContractVerifyComm extends VerifyContext {
}
private boolean verifyAsCustomer(Company company, CompanyExtendInfo companyExtendInfo, Contract contract,
MessageHolder holder) {
MessageHolder holder) {
boolean passed = true;
Project project = contract.getProject();
if (project == null) {
// 收款的合同时,检查是否关联了项目,如果没有则创建
if (contract.getPayWay() == ContractPayWay.RECEIVE) {
holder.debug("未关联项目,测试关联/创建项目...");
project = getProjectService().findByCode(contract.getCode());
if (project == null) {
holder.info("创建关联项目");
holder.info("根据合同号 " + contract.getCode() + ", 未找到相关项目, 创建相关项目");
try {
project = getProjectService().newInstanceByContract(contract);
project = getProjectService().save(project);
holder.info("创建关联项目成功:" + project.getCode() + " " + project.getName());
} catch (Exception e) {
holder.error("创建关联项目失败: " + e.getMessage());
throw new RuntimeException("code=" + contract.getCode(), e);
@@ -439,20 +442,26 @@ public class ContractVerifyComm extends VerifyContext {
}
contract.setProject(project);
contract = getContractService().save(contract);
holder.info("关联项目:" + project.getCode() + " " + project.getName());
} else {
holder.warn("未关联项目");
}
}
// fixed no hibernate session
if (project != null) {
if (!Hibernate.isInitialized(project)) {
project = getProjectService().findById(project.getId());
// fixed
contract.setProject(project);
}
}
if (project != null) {
holder.info("验证项目信息:" + project.getCode() + " " + project.getName());
verifyProject(contract, project, holder.sub("项目"));
holder.info("验证项目:" + project.getCode() + " " + project.getName());
if (!verifyProject(contract, project, holder.sub("项目"))) {
passed = false;
}
ProjectSaleType saleType = project.getSaleType();
if (saleType != null) {
@@ -463,6 +472,7 @@ public class ContractVerifyComm extends VerifyContext {
}
if (!contract.getPath().startsWith(saleType.getPath())) {
holder.error("合同目录未在销售类型目录下");
passed = false;
}
}
}
@@ -474,7 +484,7 @@ public class ContractVerifyComm extends VerifyContext {
}
if (verifyCustomerFiles) {
holder.debug("核验文件...");
holder.info("核验客户文件...");
if (!verifyContractFileAsCustomer(project, contract, holder)) {
passed = false;
}
@@ -519,7 +529,7 @@ public class ContractVerifyComm extends VerifyContext {
}
private boolean verifyCustomerFileByContract(CompanyCustomer companyCustomer, Contract contract,
MessageHolder holder) {
MessageHolder holder) {
List<LocalDate> verifyDates = new ArrayList<>();
LocalDate minDate = LocalDate.of(2022, 1, 1);
LocalDate developDate = companyCustomer.getDevelopDate();
@@ -605,7 +615,11 @@ public class ContractVerifyComm extends VerifyContext {
return false;
}
private void verifyProject(Contract contract, Project project, MessageHolder holder) {
/**
* 核查合同对应的项目是否合规
*/
private boolean verifyProject(Contract contract, Project project, MessageHolder holder) {
boolean passed = true;
ProjectSaleType saleType = project.getSaleType();
if (saleType == null) {
String code = contract.getCode();
@@ -619,6 +633,7 @@ public class ContractVerifyComm extends VerifyContext {
}
if (project.getAmount() == null || project.getAmount() <= 0) {
holder.error("金额小于等于0");
passed = false;
}
//
@@ -709,6 +724,7 @@ public class ContractVerifyComm extends VerifyContext {
holder.warn("报价未创建");
}
}
return passed;
}
private boolean verifyContractFileAsCustomer(Project project, Contract contract, MessageHolder holder) {

View File

@@ -188,7 +188,7 @@ public class CompanyCustomerService extends CompanyBasicService
@SuppressWarnings("unchecked")
@Override
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsDefaultType(F dbFile, File file,
Consumer<String> status) {
Consumer<String> status) {
dbFile.setType((T) CustomerFileType.General);
fillFile(dbFile, file, null, status);
companyCustomerFileService.save((CompanyCustomerFile) dbFile);
@@ -197,7 +197,7 @@ public class CompanyCustomerService extends CompanyBasicService
@Override
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsEvaluationFile(F customerFile, File file,
List<File> fileList, Consumer<String> status) {
List<File> fileList, Consumer<String> status) {
boolean modified = super.fillFileAsEvaluationFile(customerFile, file, fileList, status);
if (fileList != null) {
@@ -231,7 +231,7 @@ public class CompanyCustomerService extends CompanyBasicService
}
private <T, F extends CompanyBasicFile<T>> void updateEvaluationFileByJsonFile(F customerFile, File jsonFile,
Consumer<String> status) throws IOException {
Consumer<String> status) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode root = objectMapper.readTree(jsonFile);
if (!root.isObject()) {
@@ -257,7 +257,7 @@ public class CompanyCustomerService extends CompanyBasicService
@SuppressWarnings("unchecked")
@Override
protected <T, F extends CompanyBasicFile<T>> F fillFileType(File file, List<File> fileList,
Consumer<String> status) {
Consumer<String> status) {
CompanyCustomerFile customerFile = new CompanyCustomerFile();
customerFile.setType(CustomerFileType.General);
if (fillFile(customerFile, file, fileList, status)) {
@@ -281,7 +281,7 @@ public class CompanyCustomerService extends CompanyBasicService
return (fileName.contains(CompanyCustomerConstant.EVALUATION_FORM_NAME1)
|| fileName.contains(CompanyCustomerConstant.EVALUATION_FORM_NAME2))
&& (FileUtils.withExtensions(fileName, FileUtils.JPG, FileUtils.JPEG,
FileUtils.PDF));
FileUtils.PDF));
}
public boolean makePathAbsent(CompanyCustomer companyCustomer) {
@@ -395,11 +395,28 @@ public class CompanyCustomerService extends CompanyBasicService
@Override
public void updateByVo(CompanyCustomer customer, CompanyCustomerVo vo) {
customer.setCompany(SpringApp.getBean(CompanyService.class).findById(vo.getCompanyId()));
customer.setCatalog(SpringApp.getBean(CustomerCatalogService.class).findById(vo.getCatalogId()));
if (vo.getCompanyId() == null) {
customer.setCompany(null);
} else {
CompanyService service = SpringApp.getBean(CompanyService.class);
customer.setCompany(service.findById(vo.getCompanyId()));
}
if (vo.getCatalogId() == null) {
customer.setCatalog(null);
} else {
CustomerCatalogService service = SpringApp.getBean(CustomerCatalogService.class);
customer.setCatalog(service.findById(vo.getCatalogId()));
}
customer.setDevelopDate(vo.getDevelopDate());
customer.setPath(vo.getPath());
customer.setContact(SpringApp.getBean(CompanyContactService.class).findById(vo.getContactId()));
if (vo.getContactId() == null) {
customer.setContact(null);
} else {
CompanyContactService service = SpringApp.getBean(CompanyContactService.class);
customer.setContact(service.findById(vo.getContactId()));
}
customer.setDescription(vo.getDescription());
customer.setCreated(vo.getCreated());

View File

@@ -16,7 +16,7 @@ public interface BaseEnumEntityRepository<N extends Enum<?>, T extends BaseEnumE
List<T> findAllByLang(String lang);
default Map<N, T> getCompleteMapByLocal(String lang) {
HashMap<N, T> map = new HashMap<>();
Map<N, T> map = new HashMap<>();
for (T t : findAllByLang(lang)) {
map.put(t.getType(), t);
}

View File

@@ -18,7 +18,6 @@ import com.ecep.contract.IEntityService;
import com.ecep.contract.QueryService;
import com.ecep.contract.ds.other.repository.DepartmentRepository;
import com.ecep.contract.model.Department;
import com.ecep.contract.model.Employee;
import com.ecep.contract.service.VoableService;
import com.ecep.contract.vo.DepartmentVo;
import com.fasterxml.jackson.databind.JsonNode;

View File

@@ -0,0 +1,62 @@
package com.ecep.contract.handler;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import org.springframework.web.socket.PingMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import com.ecep.contract.constant.WebSocketConstant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
@Data
public class SessionInfo {
private Integer employeeId;
private Integer loginHistoryId;
private WebSocketSession session;
private ObjectMapper objectMapper;
private ScheduledFuture<?> pingPongScheduledFuture;
public void click() {
try {
session.sendMessage(new PingMessage(ByteBuffer.wrap("ping".getBytes())));
} catch (IOException e) {
WebSocketServerHandler.logger.error("发送ping消息失败 (会话ID: " + session.getId() + "): " + e.getMessage(), e);
}
}
public void sendError(int errorCode, String errorMessage) throws JsonProcessingException, IOException {
Map<String, Object> data = Map.of(
WebSocketConstant.ERROR_CODE_FIELD_NAME, errorCode,
WebSocketConstant.SUCCESS_FIELD_NAME, false,
WebSocketConstant.MESSAGE_FIELD_NAME, errorMessage);
send(objectMapper.writeValueAsString(data));
}
public void sendError(String extendField, String extendValue, int errorCode, String errorMessage)
throws JsonProcessingException, IOException {
Map<String, Object> data = Map.of(
extendField, extendValue,
WebSocketConstant.ERROR_CODE_FIELD_NAME, errorCode,
WebSocketConstant.SUCCESS_FIELD_NAME, false,
WebSocketConstant.MESSAGE_FIELD_NAME, errorMessage);
send(data);
}
public void send(Map<String, Object> data) throws JsonProcessingException, IOException {
send(objectMapper.writeValueAsString(data));
}
public void send(String text) throws IOException {
if (session == null || !session.isOpen()) {
throw new IOException("会话已关闭,无法发送消息");
}
session.sendMessage(new TextMessage(text));
}
}

View File

@@ -1,16 +1,10 @@
package com.ecep.contract.handler;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -18,45 +12,34 @@ import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PingMessage;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.ecep.contract.IEntityService;
import com.ecep.contract.PageArgument;
import com.ecep.contract.PageContent;
import com.ecep.contract.QueryService;
import com.ecep.contract.SpringApp;
import com.ecep.contract.constant.WebSocketConstant;
import com.ecep.contract.ds.other.service.EmployeeLoginHistoryService;
import com.ecep.contract.ds.other.service.EmployeeService;
import com.ecep.contract.model.Employee;
import com.ecep.contract.model.EmployeeLoginHistory;
import com.ecep.contract.model.Voable;
import com.ecep.contract.service.VoableService;
import com.ecep.contract.service.WebSocketServerTaskManager;
import com.ecep.contract.service.WebSocketServerCallbackManager;
import com.ecep.contract.service.tasker.WebSocketServerTaskManager;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
/**
* WebSocket处理器
* 处理与客户端的WebSocket连接、消息传递和断开连接
*/
@Component
public class WebSocketServerHandler extends TextWebSocketHandler {
private static final Logger logger = LoggerFactory.getLogger(WebSocketServerHandler.class);
static final Logger logger = LoggerFactory.getLogger(WebSocketServerHandler.class);
private final AuthenticationManager authenticationManager;
private final ObjectMapper objectMapper;
@@ -69,26 +52,12 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
private ScheduledExecutorService scheduledExecutorService;
@Autowired
private WebSocketServerTaskManager taskManager;
@Autowired
private WebSocketServerCallbackManager callbackManager;
// 存储所有活跃的WebSocket会话
private final Map<Integer, SessionInfo> activeSessions = Collections.synchronizedMap(new HashMap<>());
@Data
static class SessionInfo {
private Integer employeeId;
private Integer loginHistoryId;
private WebSocketSession session;
private ScheduledFuture<?> pingPongScheduledFuture;
void click() {
try {
session.sendMessage(new PingMessage(ByteBuffer.wrap("ping".getBytes())));
} catch (IOException e) {
logger.error("发送ping消息失败 (会话ID: " + session.getId() + "): " + e.getMessage(), e);
}
}
}
WebSocketServerHandler(ObjectMapper objectMapper, AuthenticationManager authenticationManager) {
this.objectMapper = objectMapper;
this.authenticationManager = authenticationManager;
@@ -101,6 +70,7 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 添加会话到活跃会话集合
SessionInfo sessionInfo = new SessionInfo();
sessionInfo.setObjectMapper(objectMapper);
sessionInfo.setSession(session);
sessionInfo.setLoginHistoryId((Integer) session.getAttributes().get("loginHistoryId"));
sessionInfo.setEmployeeId((Integer) session.getAttributes().get("employeeId"));
@@ -108,7 +78,7 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
if (sessionInfo.getEmployeeId() == null) {
logger.error("会话未绑定用户: {}", session.getId());
sendError(session, WebSocketConstant.ERROR_CODE_UNAUTHORIZED, "会话未绑定用户");
session.close(CloseStatus.NOT_ACCEPTABLE);
// session.close(CloseStatus.NOT_ACCEPTABLE);
return;
}
@@ -157,389 +127,49 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
private boolean handleAsJson(WebSocketSession session, String payload) {
if (!session.isOpen()) {
logger.warn("尝试在已关闭的WebSocket会话上处理消息回调");
logger.warn("尝试在已关闭的WebSocket会话[{}]上处理消息回调", session.getId());
return true;
}
SessionInfo sessionInfo = activeSessions.get(session.getAttributes().get("employeeId"));
if (sessionInfo == null) {
logger.error("未绑定用户: {}", session.getId());
sendError(session, WebSocketConstant.ERROR_CODE_UNAUTHORIZED, "会话未绑定用户");
// session.close(CloseStatus.NOT_ACCEPTABLE);
return true;
}
JsonNode jsonNode = null;
try {
jsonNode = objectMapper.readTree(payload);
} catch (JsonProcessingException e) {
if (payload.startsWith("[") || payload.startsWith("{")) {
logger.warn("解析消息回调JSON失败: {}", payload, e);
// 全局错误
try {
sessionInfo.sendError(WebSocketConstant.ERROR_CODE_INTERNAL_SERVER_ERROR, "JSON格式错误");
} catch (IOException e1) {
logger.error("发送错误消息失败: {}", e1.getMessage(), e1);
}
return true;
}
return false;
} catch (Exception e) {
logger.warn("解析消息回调JSON失败: {}", payload, e);
return false;
}
if (jsonNode.has(WebSocketConstant.MESSAGE_ID_FIELD_NAME)) {
// 处理 messageId 的消息
String messageId = jsonNode.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME).asText();
try {
handleAsMessageCallback(session, messageId, jsonNode);
} catch (Exception e) {
sendError(session, messageId, e.getMessage());
logger.warn("处理消息回调失败 (消息ID: {}): {}", messageId, e.getMessage(), e);
}
callbackManager.onMessage(sessionInfo, jsonNode);
return true;
}
if (jsonNode.has(WebSocketConstant.SESSION_ID_FIELD_NAME)) {
taskManager.onMessage(session, jsonNode);
taskManager.onMessage(sessionInfo, jsonNode);
return true;
}
return false;
}
private void handleAsMessageCallback(WebSocketSession session, String messageId, JsonNode jsonNode)
throws Exception {
if (!jsonNode.has(WebSocketConstant.SERVICE_FIELD_NAME)) {
throw new IllegalArgumentException("缺失 service 参数");
}
String serviceName = jsonNode.get(WebSocketConstant.SERVICE_FIELD_NAME).asText();
Object service = null;
try {
service = SpringApp.getBean(serviceName);
} catch (Exception e) {
throw new IllegalArgumentException("未找到服务: " + serviceName);
}
if (!jsonNode.has(WebSocketConstant.METHOD_FIELD_NAME)) {
throw new IllegalArgumentException("缺失 method 参数");
}
String methodName = jsonNode.get(WebSocketConstant.METHOD_FIELD_NAME).asText();
JsonNode argumentsNode = jsonNode.get(WebSocketConstant.ARGUMENTS_FIELD_NAME);
Object result = null;
if (methodName.equals("findAll")) {
result = invokerFindAllMethod(service, argumentsNode);
} else if (methodName.equals("findById")) {
result = invokerFindByIdMethod(service, argumentsNode);
} else if (methodName.equals("save")) {
result = invokerSaveMethod(service, argumentsNode);
} else if (methodName.equals("delete")) {
result = invokerDeleteMethod(service, argumentsNode);
} else if (methodName.equals("count")) {
result = invokerCountMethod(service, argumentsNode);
} else {
result = invokerOtherMethod(service, methodName, argumentsNode);
}
Map<String, Object> map = new HashMap<>();
if (result instanceof Voable<?>) {
map.put("data", ((Voable<?>) result).toVo());
} else {
map.put("data", result);
}
map.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId);
map.put(WebSocketConstant.SUCCESS_FIELD_VALUE, true);
String response = objectMapper.writeValueAsString(map);
session.sendMessage(new TextMessage(response));
}
private Object invokerOtherMethod(Object service, String methodName, JsonNode argumentsNode)
throws NoSuchMethodException, SecurityException, InvocationTargetException, IllegalAccessException,
ClassNotFoundException, JsonProcessingException {
int size = argumentsNode.size();
Class<?> targetClass = getTargetClass(service.getClass());
if (size == 0) {
Method method = targetClass.getMethod(methodName);
return method.invoke(service);
}
if (!argumentsNode.get(0).isArray()) {
Class<?> parameterType = Class.forName(argumentsNode.get(1).asText());
Object arg = objectMapper.treeToValue(argumentsNode.get(0), parameterType);
Method method = targetClass.getMethod(methodName, parameterType);
return method.invoke(service, arg);
}
// 参数值
JsonNode paramsNode = argumentsNode.get(0);
// 参数类型
JsonNode typesNode = argumentsNode.get(1);
Class<?>[] parameterTypes = new Class<?>[typesNode.size()];
Object[] args = new Object[paramsNode.size()];
for (int i = 0; i < typesNode.size(); i++) {
String type = typesNode.get(i).asText();
parameterTypes[i] = Class.forName(type);
args[i] = objectMapper.treeToValue(paramsNode.get(i), parameterTypes[i]);
}
try {
Method method = targetClass.getMethod(methodName, parameterTypes);
return method.invoke(service, args);
} catch (NoSuchMethodException e) {
logger.error("NoSuchMethodException, targetClass: {}, Methods:{}", targetClass, targetClass.getMethods());
throw e;
}
}
private Object invokerDeleteMethod(Object service, JsonNode argumentsNode) {
JsonNode paramsNode = argumentsNode.get(0);
if (!paramsNode.has("id")) {
throw new IllegalArgumentException("缺失 id 参数");
}
int id = paramsNode.get("id").asInt();
IEntityService<Object> entityService = (IEntityService<Object>) service;
Object entity = entityService.findById(id);
if (entity == null) {
throw new NoSuchElementException("未找到实体: #" + id);
}
entityService.delete(entity);
return entity;
}
private Object invokerSaveMethod(Object service, JsonNode argumentsNode) throws JsonMappingException {
JsonNode paramsNode = argumentsNode.get(0);
if (service instanceof IEntityService<?> entityService) {
Object entity = null;
if (paramsNode.has("id") && !paramsNode.get("id").isNull()) {
int id = paramsNode.get("id").asInt();
entity = entityService.findById(id);
if (entity == null) {
throw new NoSuchElementException("未找到实体: #" + id);
}
} else {
entity = createNewEntity(entityService);
}
if (service instanceof VoableService<?, ?>) {
String typeClz = argumentsNode.get(1).asText();
Class<?> type = null;
try {
type = Class.forName(typeClz);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Object object = objectMapper.convertValue(paramsNode, type);
((VoableService<Object, Object>) service).updateByVo(entity, object);
} else {
objectMapper.updateValue(entity, paramsNode);
}
return ((IEntityService<Object>) entityService).save(entity);
}
return null;
}
private <T> T createNewEntity(IEntityService<T> entityService) {
try {
// 通过分析接口的泛型参数来获取实体类型
Class<?> serviceClass = entityService.getClass();
// 1. 直接检查接口
Class<T> entityClass = findEntityTypeInInterfaces(serviceClass);
if (entityClass != null) {
return entityClass.getDeclaredConstructor().newInstance();
}
// 2. 处理Spring代理类 - 获取原始类
Class<?> targetClass = getTargetClass(serviceClass);
if (targetClass != serviceClass) {
entityClass = findEntityTypeInInterfaces(targetClass);
if (entityClass != null) {
return entityClass.getDeclaredConstructor().newInstance();
}
}
// 3. 尝试查找父类
entityClass = findEntityTypeInSuperclass(serviceClass);
if (entityClass != null) {
return entityClass.getDeclaredConstructor().newInstance();
}
// 4. 如果上述方法都失败,尝试从参数类型推断
entityClass = findEntityTypeFromMethodParameters(serviceClass);
if (entityClass != null) {
return entityClass.getDeclaredConstructor().newInstance();
}
// 如果所有方法都失败,抛出更具描述性的异常
throw new UnsupportedOperationException("无法确定实体类型,请检查服务实现: " + serviceClass.getName());
} catch (Exception e) {
throw new RuntimeException("无法创建Entity实例: " + e.getMessage(), e);
}
}
/**
* 从接口中查找实体类型
*/
@SuppressWarnings("unchecked")
private <T> Class<T> findEntityTypeInInterfaces(Class<?> serviceClass) {
Type[] interfaces = serviceClass.getGenericInterfaces();
for (Type iface : interfaces) {
if (iface instanceof ParameterizedType paramType) {
if (IEntityService.class.isAssignableFrom((Class<?>) paramType.getRawType())) {
// 获取IEntityService的泛型参数类型
Type entityType = paramType.getActualTypeArguments()[0];
if (entityType instanceof Class<?>) {
return (Class<T>) entityType;
} else if (entityType instanceof ParameterizedType) {
// 处理参数化类型
Type rawType = ((ParameterizedType) entityType).getRawType();
if (rawType instanceof Class<?>) {
return (Class<T>) rawType;
}
}
}
}
}
return null;
}
/**
* 从父类中查找实体类型
*/
@SuppressWarnings("unchecked")
private <T> Class<T> findEntityTypeInSuperclass(Class<?> serviceClass) {
Type genericSuperclass = serviceClass.getGenericSuperclass();
while (genericSuperclass != null && genericSuperclass != Object.class) {
if (genericSuperclass instanceof ParameterizedType paramType) {
Type rawType = paramType.getRawType();
if (rawType instanceof Class<?> && IEntityService.class.isAssignableFrom((Class<?>) rawType)) {
Type entityType = paramType.getActualTypeArguments()[0];
if (entityType instanceof Class<?>) {
return (Class<T>) entityType;
}
}
}
// 继续查找父类的父类
if (genericSuperclass instanceof Class<?>) {
genericSuperclass = ((Class<?>) genericSuperclass).getGenericSuperclass();
} else {
break;
}
}
return null;
}
/**
* 尝试从方法参数类型推断实体类型
*/
@SuppressWarnings("unchecked")
private <T> Class<T> findEntityTypeFromMethodParameters(Class<?> serviceClass) {
try {
// 尝试通过findById方法推断实体类型
Method findByIdMethod = serviceClass.getMethod("findById", Integer.class);
if (findByIdMethod != null) {
return (Class<T>) findByIdMethod.getReturnType();
}
// 尝试通过findAll方法推断实体类型
Method[] methods = serviceClass.getMethods();
for (Method method : methods) {
if (method.getName().equals("findAll") && method.getParameterCount() > 0) {
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType paramType &&
paramType.getRawType() instanceof Class<?> &&
"org.springframework.data.domain.Page"
.equals(((Class<?>) paramType.getRawType()).getName())) {
Type[] actualTypeArguments = paramType.getActualTypeArguments();
if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class<?>) {
return (Class<T>) actualTypeArguments[0];
}
}
}
}
} catch (Exception e) {
// 忽略异常,继续尝试其他方法
}
return null;
}
/**
* 获取被代理的原始类
*/
private Class<?> getTargetClass(Class<?> proxyClass) {
// 处理CGLIB代理类
if (proxyClass.getName().contains("$$SpringCGLIB$$")) {
return proxyClass.getSuperclass();
}
return proxyClass;
}
/*
* see client QueryService#findById(Integer)
*/
private Object invokerFindByIdMethod(Object service, JsonNode argumentsNode) {
JsonNode paramsNode = argumentsNode.get(0);
if (service instanceof IEntityService<?> entityService) {
Integer id = paramsNode.asInt();
return entityService.findById(id);
}
try {
JsonNode typesNode = argumentsNode.get(1);
if (paramsNode.isInt()) {
Method method = service.getClass().getMethod("findById", Integer.class);
return method.invoke(service, paramsNode.asInt());
}
if (paramsNode.isTextual()) {
Method method = service.getClass().getMethod("findById", String.class);
return method.invoke(service, paramsNode.asText());
}
throw new IllegalArgumentException("unable to invoke findById method, paramsNode is not int or text");
} catch (Exception e) {
throw new RuntimeException("unable to invoke findById method", e);
}
}
private Object invokerFindAllMethod(Object service, JsonNode argumentsNode) throws JsonProcessingException {
JsonNode paramsNode = argumentsNode.get(0);
JsonNode pageableNode = argumentsNode.get(1);
PageArgument pageArgument = objectMapper.treeToValue(pageableNode, PageArgument.class);
QueryService<?> entityService = (QueryService<?>) service;
Page<?> page = entityService.findAll(paramsNode, pageArgument.toPageable());
return PageContent.of(page.map(entity -> {
if (entity instanceof Voable<?>) {
return ((Voable<?>) entity).toVo();
}
return entity;
}));
}
private Object invokerCountMethod(Object service, JsonNode argumentsNode) {
JsonNode paramsNode = argumentsNode.get(0);
if (service instanceof QueryService<?> entityService) {
return entityService.count(paramsNode);
}
return null;
}
private void sendError(WebSocketSession session, String messageId, String message) {
_sendError(session, WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId, message);
}
private void _sendError(WebSocketSession session, String fieldName, String messageId, String message) {
if (session == null || !session.isOpen()) {
logger.warn("尝试向已关闭的WebSocket会话发送错误消息: {}", message);
return;
}
try {
String errorMessage = objectMapper.writeValueAsString(Map.of(
fieldName, messageId,
WebSocketConstant.SUCCESS_FIELD_VALUE, false,
WebSocketConstant.MESSAGE_FIELD_NAME, message));
// 检查会话状态并尝试发送错误消息
if (session.isOpen()) {
session.sendMessage(new TextMessage(errorMessage));
} else {
logger.warn("会话已关闭,无法发送错误消息: {}", message);
}
} catch (Exception e) {
// 捕获所有可能的异常,防止影响主流程
logger.error("发送错误消息失败 (会话ID: {})", session.getId(), e);
}
}
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
logger.info("收到来自客户端的二进制消息: " + message.getPayload() + " (会话ID: " + session.getId() + ")");
@@ -642,7 +272,7 @@ public class WebSocketServerHandler extends TextWebSocketHandler {
try {
ObjectNode objectNode = objectMapper.createObjectNode();
objectNode.put(WebSocketConstant.ERROR_CODE_FIELD_NAME, errorCode);
objectNode.put(WebSocketConstant.SUCCESS_FIELD_VALUE, false);
objectNode.put(WebSocketConstant.SUCCESS_FIELD_NAME, false);
objectNode.put(WebSocketConstant.MESSAGE_FIELD_NAME, message);
String errorMessage = objectMapper.writeValueAsString(objectNode);

View File

@@ -0,0 +1,389 @@
package com.ecep.contract.service;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import com.ecep.contract.IEntityService;
import com.ecep.contract.PageArgument;
import com.ecep.contract.PageContent;
import com.ecep.contract.QueryService;
import com.ecep.contract.SpringApp;
import com.ecep.contract.constant.WebSocketConstant;
import com.ecep.contract.handler.SessionInfo;
import com.ecep.contract.model.Voable;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@Service
public class WebSocketServerCallbackManager {
static final Logger logger = LoggerFactory.getLogger(WebSocketServerCallbackManager.class);
private final ObjectMapper objectMapper;
public WebSocketServerCallbackManager(@Autowired ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public void onMessage(SessionInfo session, JsonNode jsonNode) {
// 处理 messageId 的消息
String messageId = jsonNode.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME).asText();
try {
Object result = handleAsMessageCallback(session, messageId, jsonNode);
send(session, messageId, result);
} catch (Exception e) {
sendError(session, messageId, 500, e.getMessage());
logger.warn("处理消息回调失败 (消息ID: {}): {}", messageId, e.getMessage(), e);
}
}
private void sendError(SessionInfo session, String messageId, int errorCode, String message) {
try {
session.sendError(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId, errorCode, message);
} catch (IOException e) {
logger.warn("发送错误消息失败 (消息ID: {}): {}", messageId, e.getMessage(), e);
}
}
private void send(SessionInfo session, String messageId, Object data) {
Map<String, Object> map = new HashMap<>();
if (data instanceof Voable<?>) {
map.put("data", ((Voable<?>) data).toVo());
} else {
map.put("data", data);
}
map.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId);
map.put(WebSocketConstant.SUCCESS_FIELD_NAME, true);
try {
session.send(map);
} catch (IOException e) {
logger.warn("发送消息失败 (消息ID: {}): {}", messageId, e.getMessage(), e);
}
}
private Object handleAsMessageCallback(SessionInfo session, String messageId, JsonNode jsonNode)
throws Exception {
if (!jsonNode.has(WebSocketConstant.SERVICE_FIELD_NAME)) {
throw new IllegalArgumentException("缺失 service 参数");
}
String serviceName = jsonNode.get(WebSocketConstant.SERVICE_FIELD_NAME).asText();
Object service = null;
try {
service = SpringApp.getBean(serviceName);
} catch (Exception e) {
throw new IllegalArgumentException("未找到服务: " + serviceName);
}
if (!jsonNode.has(WebSocketConstant.METHOD_FIELD_NAME)) {
throw new IllegalArgumentException("缺失 method 参数");
}
String methodName = jsonNode.get(WebSocketConstant.METHOD_FIELD_NAME).asText();
JsonNode argumentsNode = jsonNode.get(WebSocketConstant.ARGUMENTS_FIELD_NAME);
Object result = null;
if (methodName.equals("findAll")) {
result = invokerFindAllMethod(service, argumentsNode);
} else if (methodName.equals("findById")) {
result = invokerFindByIdMethod(service, argumentsNode);
} else if (methodName.equals("save")) {
result = invokerSaveMethod(service, argumentsNode);
} else if (methodName.equals("delete")) {
result = invokerDeleteMethod(service, argumentsNode);
} else if (methodName.equals("count")) {
result = invokerCountMethod(service, argumentsNode);
} else {
result = invokerOtherMethod(service, methodName, argumentsNode);
}
return result;
}
private Object invokerOtherMethod(Object service, String methodName, JsonNode argumentsNode)
throws NoSuchMethodException, SecurityException, InvocationTargetException, IllegalAccessException,
ClassNotFoundException, JsonProcessingException {
int size = argumentsNode.size();
Class<?> targetClass = getTargetClass(service.getClass());
if (size == 0) {
Method method = targetClass.getMethod(methodName);
return method.invoke(service);
}
if (!argumentsNode.get(0).isArray()) {
Class<?> parameterType = Class.forName(argumentsNode.get(1).asText());
Object arg = objectMapper.treeToValue(argumentsNode.get(0), parameterType);
Method method = targetClass.getMethod(methodName, parameterType);
return method.invoke(service, arg);
}
// 参数值
JsonNode paramsNode = argumentsNode.get(0);
// 参数类型
JsonNode typesNode = argumentsNode.get(1);
Class<?>[] parameterTypes = new Class<?>[typesNode.size()];
Object[] args = new Object[paramsNode.size()];
for (int i = 0; i < typesNode.size(); i++) {
String type = typesNode.get(i).asText();
parameterTypes[i] = Class.forName(type);
args[i] = objectMapper.treeToValue(paramsNode.get(i), parameterTypes[i]);
}
try {
Method method = targetClass.getMethod(methodName, parameterTypes);
return method.invoke(service, args);
} catch (NoSuchMethodException e) {
logger.error("NoSuchMethodException, targetClass: {}, Methods:{}", targetClass, targetClass.getMethods());
throw e;
}
}
private Object invokerDeleteMethod(Object service, JsonNode argumentsNode) {
JsonNode paramsNode = argumentsNode.get(0);
if (!paramsNode.has("id")) {
throw new IllegalArgumentException("缺失 id 参数");
}
int id = paramsNode.get("id").asInt();
IEntityService<Object> entityService = (IEntityService<Object>) service;
Object entity = entityService.findById(id);
if (entity == null) {
throw new NoSuchElementException("未找到实体: #" + id);
}
entityService.delete(entity);
return entity;
}
private Object invokerSaveMethod(Object service, JsonNode argumentsNode) throws JsonMappingException {
JsonNode paramsNode = argumentsNode.get(0);
if (service instanceof IEntityService<?> entityService) {
Object entity = null;
if (paramsNode.has("id") && !paramsNode.get("id").isNull()) {
int id = paramsNode.get("id").asInt();
entity = entityService.findById(id);
if (entity == null) {
throw new NoSuchElementException("未找到实体: #" + id);
}
} else {
entity = createNewEntity(entityService);
}
if (service instanceof VoableService<?, ?>) {
String typeClz = argumentsNode.get(1).asText();
Class<?> type = null;
try {
type = Class.forName(typeClz);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Object object = objectMapper.convertValue(paramsNode, type);
((VoableService<Object, Object>) service).updateByVo(entity, object);
} else {
objectMapper.updateValue(entity, paramsNode);
}
return ((IEntityService<Object>) entityService).save(entity);
}
return null;
}
private <T> T createNewEntity(IEntityService<T> entityService) {
try {
// 通过分析接口的泛型参数来获取实体类型
Class<?> serviceClass = entityService.getClass();
// 1. 直接检查接口
Class<T> entityClass = findEntityTypeInInterfaces(serviceClass);
if (entityClass != null) {
return entityClass.getDeclaredConstructor().newInstance();
}
// 2. 处理Spring代理类 - 获取原始类
Class<?> targetClass = getTargetClass(serviceClass);
if (targetClass != serviceClass) {
entityClass = findEntityTypeInInterfaces(targetClass);
if (entityClass != null) {
return entityClass.getDeclaredConstructor().newInstance();
}
}
// 3. 尝试查找父类
entityClass = findEntityTypeInSuperclass(serviceClass);
if (entityClass != null) {
return entityClass.getDeclaredConstructor().newInstance();
}
// 4. 如果上述方法都失败,尝试从参数类型推断
entityClass = findEntityTypeFromMethodParameters(serviceClass);
if (entityClass != null) {
return entityClass.getDeclaredConstructor().newInstance();
}
// 如果所有方法都失败,抛出更具描述性的异常
throw new UnsupportedOperationException("无法确定实体类型,请检查服务实现: " + serviceClass.getName());
} catch (Exception e) {
throw new RuntimeException("无法创建Entity实例: " + e.getMessage(), e);
}
}
/**
* 从接口中查找实体类型
*/
@SuppressWarnings("unchecked")
private <T> Class<T> findEntityTypeInInterfaces(Class<?> serviceClass) {
Type[] interfaces = serviceClass.getGenericInterfaces();
for (Type iface : interfaces) {
if (iface instanceof ParameterizedType paramType) {
if (IEntityService.class.isAssignableFrom((Class<?>) paramType.getRawType())) {
// 获取IEntityService的泛型参数类型
Type entityType = paramType.getActualTypeArguments()[0];
if (entityType instanceof Class<?>) {
return (Class<T>) entityType;
} else if (entityType instanceof ParameterizedType) {
// 处理参数化类型
Type rawType = ((ParameterizedType) entityType).getRawType();
if (rawType instanceof Class<?>) {
return (Class<T>) rawType;
}
}
}
}
}
return null;
}
/**
* 从父类中查找实体类型
*/
@SuppressWarnings("unchecked")
private <T> Class<T> findEntityTypeInSuperclass(Class<?> serviceClass) {
Type genericSuperclass = serviceClass.getGenericSuperclass();
while (genericSuperclass != null && genericSuperclass != Object.class) {
if (genericSuperclass instanceof ParameterizedType paramType) {
Type rawType = paramType.getRawType();
if (rawType instanceof Class<?> && IEntityService.class.isAssignableFrom((Class<?>) rawType)) {
Type entityType = paramType.getActualTypeArguments()[0];
if (entityType instanceof Class<?>) {
return (Class<T>) entityType;
}
}
}
// 继续查找父类的父类
if (genericSuperclass instanceof Class<?>) {
genericSuperclass = ((Class<?>) genericSuperclass).getGenericSuperclass();
} else {
break;
}
}
return null;
}
/**
* 尝试从方法参数类型推断实体类型
*/
@SuppressWarnings("unchecked")
private <T> Class<T> findEntityTypeFromMethodParameters(Class<?> serviceClass) {
try {
// 尝试通过findById方法推断实体类型
Method findByIdMethod = serviceClass.getMethod("findById", Integer.class);
if (findByIdMethod != null) {
return (Class<T>) findByIdMethod.getReturnType();
}
// 尝试通过findAll方法推断实体类型
Method[] methods = serviceClass.getMethods();
for (Method method : methods) {
if (method.getName().equals("findAll") && method.getParameterCount() > 0) {
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType paramType &&
paramType.getRawType() instanceof Class<?> &&
"org.springframework.data.domain.Page"
.equals(((Class<?>) paramType.getRawType()).getName())) {
Type[] actualTypeArguments = paramType.getActualTypeArguments();
if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class<?>) {
return (Class<T>) actualTypeArguments[0];
}
}
}
}
} catch (Exception e) {
// 忽略异常,继续尝试其他方法
}
return null;
}
/**
* 获取被代理的原始类
*/
private Class<?> getTargetClass(Class<?> proxyClass) {
// 处理CGLIB代理类
if (proxyClass.getName().contains("$$SpringCGLIB$$")) {
return proxyClass.getSuperclass();
}
return proxyClass;
}
/*
* see client QueryService#findById(Integer)
*/
private Object invokerFindByIdMethod(Object service, JsonNode argumentsNode) {
JsonNode paramsNode = argumentsNode.get(0);
if (service instanceof IEntityService<?> entityService) {
Integer id = paramsNode.asInt();
return entityService.findById(id);
}
try {
JsonNode typesNode = argumentsNode.get(1);
if (paramsNode.isInt()) {
Method method = service.getClass().getMethod("findById", Integer.class);
return method.invoke(service, paramsNode.asInt());
}
if (paramsNode.isTextual()) {
Method method = service.getClass().getMethod("findById", String.class);
return method.invoke(service, paramsNode.asText());
}
throw new IllegalArgumentException("unable to invoke findById method, paramsNode is not int or text");
} catch (Exception e) {
throw new RuntimeException("unable to invoke findById method", e);
}
}
private Object invokerFindAllMethod(Object service, JsonNode argumentsNode) throws JsonProcessingException {
JsonNode paramsNode = argumentsNode.get(0);
JsonNode pageableNode = argumentsNode.get(1);
PageArgument pageArgument = objectMapper.treeToValue(pageableNode, PageArgument.class);
QueryService<?> entityService = (QueryService<?>) service;
Page<?> page = entityService.findAll(paramsNode, pageArgument.toPageable());
return PageContent.of(page.map(entity -> {
if (entity instanceof Voable<?>) {
return ((Voable<?>) entity).toVo();
}
return entity;
}));
}
private Object invokerCountMethod(Object service, JsonNode argumentsNode) {
JsonNode paramsNode = argumentsNode.get(0);
if (service instanceof QueryService<?> entityService) {
return entityService.count(paramsNode);
}
return null;
}
}

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.ds.company.tasker;
package com.ecep.contract.service.tasker;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.cloud.rk.CloudRkService;
@@ -13,15 +13,12 @@ import com.ecep.contract.constant.CloudServiceConstant;
import com.ecep.contract.model.CloudRk;
import com.ecep.contract.model.CloudYu;
import com.ecep.contract.model.Company;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.ui.Tasker;
import com.ecep.contract.util.MyStringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.ds.customer.tasker;
package com.ecep.contract.service.tasker;
import static com.ecep.contract.util.ExcelUtils.setCellValue;
@@ -32,7 +32,6 @@ import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyCustomer;
import com.ecep.contract.model.CompanyCustomerEvaluationFormFile;
import com.ecep.contract.model.CompanyCustomerFile;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.ui.Tasker;
import com.ecep.contract.util.CompanyUtils;
import com.fasterxml.jackson.databind.JsonNode;

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.ds.customer.tasker;
package com.ecep.contract.service.tasker;
import java.time.LocalDate;
import java.util.Comparator;
@@ -12,7 +12,6 @@ import com.ecep.contract.ds.customer.service.CompanyCustomerService;
import com.ecep.contract.model.CompanyCustomer;
import com.ecep.contract.model.CompanyCustomerFile;
import com.ecep.contract.model.Contract;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.ui.Tasker;
import com.fasterxml.jackson.databind.JsonNode;

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.ds.customer.tasker;
package com.ecep.contract.service.tasker;
import org.hibernate.Hibernate;
import org.springframework.util.StringUtils;
@@ -8,7 +8,6 @@ import com.ecep.contract.ds.company.service.CompanyService;
import com.ecep.contract.ds.customer.service.CompanyCustomerService;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyCustomer;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.ui.Tasker;
import com.fasterxml.jackson.databind.JsonNode;

View File

@@ -1,18 +1,14 @@
package com.ecep.contract.ds.company.tasker;
package com.ecep.contract.service.tasker;
import java.time.LocalDate;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.ds.company.service.CompanyService;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.ds.contract.tasker.ContractVerifyComm;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.Contract;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.ui.Tasker;
import com.fasterxml.jackson.databind.JsonNode;

View File

@@ -1,10 +1,10 @@
package com.ecep.contract.ds.contract.tasker;
package com.ecep.contract.service.tasker;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReference;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.ds.contract.tasker.AbstContractRepairTasker;
import com.ecep.contract.ds.contract.tasker.ContractRepairComm;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.util.StringUtils;
@@ -13,12 +13,11 @@ import com.ecep.contract.MessageHolder;
import com.ecep.contract.model.Contract;
import lombok.Getter;
import lombok.Setter;
/**
* 合同修复任务
*/
public class ContractRepairTask extends AbstContractRepairTasker implements WebSocketServerTasker {
public class ContractRepairTasker extends AbstContractRepairTasker implements WebSocketServerTasker {
@Getter
private Contract contract;
@Getter
@@ -27,7 +26,7 @@ public class ContractRepairTask extends AbstContractRepairTasker implements WebS
private final ContractRepairComm comm = new ContractRepairComm();
public ContractRepairTask() {
public ContractRepairTasker() {
}
public void init(JsonNode argsNode) {

View File

@@ -1,11 +1,11 @@
package com.ecep.contract.ds.contract.tasker;
package com.ecep.contract.service.tasker;
import java.util.Locale;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.ds.contract.tasker.ContractVerifyComm;
import com.ecep.contract.model.Contract;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.ui.Tasker;
import com.fasterxml.jackson.databind.JsonNode;
@@ -24,15 +24,18 @@ public class ContractVerifyTasker extends Tasker<Object> implements WebSocketSer
@Override
protected Object execute(MessageHolder holder) {
updateTitle("验证合同 " + contract.getCode() + " 及其子合同是否符合合规要求");
updateProgress(1, 10);
if (!comm.verify(contract, holder)) {
passed = false;
}
updateProgress(9, 10);
if (passed) {
holder.info("合规验证通过");
} else {
holder.error("合规验证不通过");
}
updateProgress(10, 10);
updateProperty("passed", passed);
return null;
}

View File

@@ -1,13 +1,11 @@
package com.ecep.contract.ds.customer.tasker;
package com.ecep.contract.service.tasker;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.ds.company.service.CompanyService;
import com.ecep.contract.ds.customer.service.CompanyCustomerFileService;
import com.ecep.contract.ds.customer.service.CompanyCustomerService;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyCustomer;
import com.ecep.contract.model.CompanyCustomerFile;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.ui.Tasker;
import com.fasterxml.jackson.databind.JsonNode;

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.ds.project;
package com.ecep.contract.service.tasker;
import java.time.LocalDateTime;
import java.util.ArrayList;
@@ -26,7 +26,6 @@ import com.ecep.contract.model.Inventory;
import com.ecep.contract.model.Project;
import com.ecep.contract.model.ProjectCost;
import com.ecep.contract.model.ProjectCostItem;
import com.ecep.contract.service.WebSocketServerTasker;
import com.ecep.contract.ui.Tasker;
import com.ecep.contract.util.NumberUtils;
import com.ecep.contract.util.TaxRateUtils;

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.service;
package com.ecep.contract.service.tasker;
import java.io.IOException;
import java.io.InputStream;
@@ -42,11 +42,17 @@ public class WebSocketServerTaskManager implements InitializingBean {
Resource resource = resourceLoader.getResource("classpath:tasker_mapper.json");
try (InputStream inputStream = resource.getInputStream()) {
JsonNode rootNode = objectMapper.readTree(inputStream);
JsonNode taskersNode = rootNode.get("taskers");
if (taskersNode != null && taskersNode.isObject()) {
JsonNode tasksNode = rootNode.get("tasks");
if (tasksNode != null && tasksNode.isObject()) {
Map<String, String> taskMap = new java.util.HashMap<>();
taskersNode.fields().forEachRemaining(entry -> {
tasksNode.fields().forEachRemaining(entry -> {
taskMap.put(entry.getKey(), entry.getValue().asText());
try {
Class.forName(entry.getValue().asText());
} catch (ClassNotFoundException e) {
logger.error("Failed to load task class: {}", entry.getValue().asText(), e);
}
});
taskClzMap = taskMap;
}

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.service;
package com.ecep.contract.service.tasker;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;

View File

@@ -42,17 +42,14 @@ logging.level.org.springframework.web.servlet.mvc.method.annotation=TRACE
# Redis缓存配置
spring.cache.type=redis
spring.cache.redis.time-to-live=6h
spring.cache.redis.key-prefix=contract_manager_
spring.cache.redis.time-to-live=1h
spring.cache.redis.key-prefix=cms::
spring.cache.redis.use-key-prefix=true
spring.cache.redis.cache-null-values=true
# Redis序列化配置
spring.data.redis.serializer.key=org.springframework.data.redis.serializer.StringRedisSerializer
spring.data.redis.serializer.value=org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
# 禁用默认的Whitelabel错误页面
server.error.whitelabel.enabled=false
# 设置错误处理路径确保404等错误能被全局异常处理器捕获
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,15 +1,15 @@
{
"taskers": {
"ContractSyncTask": "com.ecep.contract.cloud.u8.ContractSyncTask",
"ContractRepairTask": "com.ecep.contract.ds.contract.tasker.ContractRepairTask",
"ContractVerifyTask": "com.ecep.contract.ds.contract.tasker.ContractVerifyTask",
"ProjectCostImportItemsFromContractsTasker": "com.ecep.contract.ds.project.ProjectCostImportItemsFromContractsTasker",
"CompanyCustomerEvaluationFormUpdateTask": "com.ecep.contract.ds.customer.tasker.CompanyCustomerEvaluationFormUpdateTask",
"CompanyCustomerNextSignDateTask": "com.ecep.contract.ds.customer.tasker.CompanyCustomerNextSignDateTask",
"CompanyCustomerRebuildFilesTasker": "com.ecep.contract.ds.customer.tasker.CompanyCustomerRebuildFilesTasker",
"CustomerFileMoveTasker": "com.ecep.contract.ds.customer.tasker.CustomerFileMoveTasker",
"CompanyCompositeUpdateTasker": "com.ecep.contract.ds.company.tasker.CompanyCompositeUpdateTasker",
"CompanyVerifyTasker": "com.ecep.contract.ds.company.tasker.CompanyVerifyTasker"
},
"descriptions": "任务注册信息"
"tasks": {
"ContractSyncTask": "com.ecep.contract.cloud.u8.ContractSyncTask",
"ContractRepairTask": "com.ecep.contract.service.tasker.ContractRepairTasker",
"ContractVerifyTasker": "com.ecep.contract.service.tasker.ContractVerifyTasker",
"ProjectCostImportItemsFromContractsTasker": "com.ecep.contract.service.tasker.ProjectCostImportItemsFromContractsTasker",
"CompanyCustomerEvaluationFormUpdateTask": "com.ecep.contract.service.tasker.CompanyCustomerEvaluationFormUpdateTask",
"CompanyCustomerNextSignDateTask": "com.ecep.contract.service.tasker.CompanyCustomerNextSignDateTask",
"CompanyCustomerRebuildFilesTasker": "com.ecep.contract.service.tasker.CompanyCustomerRebuildFilesTasker",
"CustomerFileMoveTasker": "com.ecep.contract.service.tasker.CustomerFileMoveTasker",
"CompanyCompositeUpdateTasker": "com.ecep.contract.service.tasker.CompanyCompositeUpdateTasker",
"CompanyVerifyTasker": "com.ecep.contract.service.tasker.CompanyVerifyTasker"
},
"descriptions": "任务注册信息"
}