refactor(service): 修改IEntityService泛型为VO类型并优化缓存策略
重构所有注解@CacheConfig的Service类,将IEntityService泛型从实体类改为VO类 实现实体与VO之间的转换逻辑,使用VO替代实体进行缓存以避免序列化问题 更新相关依赖组件和测试用例,确保功能完整性和系统兼容性 优化Redis缓存配置,清理旧缓存数据并验证新缓存策略有效性
This commit is contained in:
@@ -44,8 +44,6 @@ import com.ecep.contract.util.TaskMonitorCenter;
|
||||
})
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
@EnableCaching
|
||||
|
||||
@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO)
|
||||
public class SpringApp {
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ public class LoginApiController {
|
||||
// 其他错误
|
||||
result.put("success", false);
|
||||
result.put("error", "登录过程中发生错误: " + e.getMessage());
|
||||
logger.error("登录错误:{}", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
// 已经存在的移除
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -47,10 +47,6 @@ public class AbstractYongYouU8Ctx extends AbstractCtx {
|
||||
}
|
||||
}
|
||||
|
||||
public CompanyService getCompanyService() {
|
||||
return getCachedBean(CompanyService.class);
|
||||
}
|
||||
|
||||
public CompanyCustomerService getCompanyCustomerService() {
|
||||
return getCachedBean(CompanyCustomerService.class);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) // 账户未锁定
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
server/src/main/resources/static/favicon.ico
Normal file
BIN
server/src/main/resources/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -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": "任务注册信息"
|
||||
}
|
||||
Reference in New Issue
Block a user