From 98e48c520f5d015446e9c3d49b60747790e22cbf Mon Sep 17 00:00:00 2001 From: songqq Date: Fri, 12 Sep 2025 16:00:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(proxy):=20=E5=AE=9E=E7=8E=B0=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E5=AF=B9=E8=B1=A1=E5=88=9D=E5=A7=8B=E5=8C=96=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E4=B8=8E=E6=87=92=E5=8A=A0=E8=BD=BD=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加ProxyUtils工具类用于检查代理对象初始化状态 实现代理对象创建和标记初始化功能 添加ProxyObjectDeserializerModifier处理反序列化时的代理对象创建 修改WebSocketService错误消息字段从errorMsg改为message 实现ContractItemService.findAllByInventory方法 优化ContractService查询条件处理并添加缓存支持 重构InventoryTabSkinHistoryPrice的service获取方式 --- .../com/ecep/contract/WebSocketService.java | 2 +- .../InventoryTabSkinHistoryPrice.java | 32 ++----- .../ProxyObjectDeserializerModifier.java | 95 +++++++++++++++++++ .../contract/service/ContractItemService.java | 5 +- .../contract/service/ContractService.java | 43 ++++++++- .../com/ecep/contract/util/ProxyUtils.java | 90 +++++++++++++++++- .../ds/contract/service/ContractService.java | 9 +- 7 files changed, 243 insertions(+), 33 deletions(-) create mode 100644 client/src/main/java/com/ecep/contract/serializer/ProxyObjectDeserializerModifier.java diff --git a/client/src/main/java/com/ecep/contract/WebSocketService.java b/client/src/main/java/com/ecep/contract/WebSocketService.java index 0788738..77a71fe 100644 --- a/client/src/main/java/com/ecep/contract/WebSocketService.java +++ b/client/src/main/java/com/ecep/contract/WebSocketService.java @@ -100,7 +100,7 @@ public class WebSocketService { } } else if (node.has("errorCode")) { int errorCode = node.get("errorCode").asInt(); - String errorMsg = node.get("errorMsg").asText(); + String errorMsg = node.get("message").asText(); // TODO 需要重新登录 logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg); } diff --git a/client/src/main/java/com/ecep/contract/controller/inventory/InventoryTabSkinHistoryPrice.java b/client/src/main/java/com/ecep/contract/controller/inventory/InventoryTabSkinHistoryPrice.java index 31b226f..9bb4e30 100644 --- a/client/src/main/java/com/ecep/contract/controller/inventory/InventoryTabSkinHistoryPrice.java +++ b/client/src/main/java/com/ecep/contract/controller/inventory/InventoryTabSkinHistoryPrice.java @@ -72,41 +72,25 @@ public class InventoryTabSkinHistoryPrice public TableColumn miniPurchaseTaxPriceColumn; public TableColumn miniPurchasePriceDateColumn; - @Setter - InventoryHistoryPriceService service; - @Setter - ContractService contractService; - @Setter - ContractItemService contractItemService; - public InventoryTabSkinHistoryPrice(InventoryWindowController controller) { super(controller); } @Override protected InventoryHistoryPriceService getViewModelService() { - return getService(); + return getHistoryPriceService(); } - InventoryHistoryPriceService getService() { - if (service == null) { - service = getBean(InventoryHistoryPriceService.class); - } - return service; + InventoryHistoryPriceService getHistoryPriceService() { + return getCachedBean(InventoryHistoryPriceService.class); } ContractItemService getContractItemService() { - if (contractItemService == null) { - contractItemService = getBean(ContractItemService.class); - } - return contractItemService; + return getCachedBean(ContractItemService.class); } ContractService getContractService() { - if (contractService == null) { - contractService = getBean(ContractService.class); - } - return contractService; + return getCachedBean(ContractService.class); } @Override @@ -182,10 +166,10 @@ public class InventoryTabSkinHistoryPrice HashMap historyPriceMap = new HashMap<>(); - for (InventoryHistoryPrice historyPrice : getService().findAllByInventory(getParent())) { + for (InventoryHistoryPrice historyPrice : getHistoryPriceService().findAllByInventory(getParent())) { InventoryHistoryPrice oldValue = historyPriceMap.put(historyPrice.getYear().getValue(), historyPrice); if (oldValue != null) { - getService().delete(oldValue); + getHistoryPriceService().delete(oldValue); } } @@ -236,7 +220,7 @@ public class InventoryTabSkinHistoryPrice } }); - getService().save(historyPrice); + getHistoryPriceService().save(historyPrice); }); loadTableDataSet(); diff --git a/client/src/main/java/com/ecep/contract/serializer/ProxyObjectDeserializerModifier.java b/client/src/main/java/com/ecep/contract/serializer/ProxyObjectDeserializerModifier.java new file mode 100644 index 0000000..189e35f --- /dev/null +++ b/client/src/main/java/com/ecep/contract/serializer/ProxyObjectDeserializerModifier.java @@ -0,0 +1,95 @@ +package com.ecep.contract.serializer; + +import com.ecep.contract.model.IdentityEntity; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; +import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * 自定义的反序列化器修改器,用于实现类似Hibernate的代理对象功能 + */ +public class ProxyObjectDeserializerModifier extends BeanDeserializerModifier { + + private final ObjectMapper objectMapper; + + public ProxyObjectDeserializerModifier(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public JsonDeserializer modifyDeserializer(DeserializationConfig config, + BeanDescription beanDesc, JsonDeserializer deserializer) { + // 检查是否是IdentityEntity的实现类 + if (IdentityEntity.class.isAssignableFrom(beanDesc.getBeanClass())) { + // 返回包装后的反序列化器,处理代理对象的创建 + return new ProxyObjectDeserializer((JsonDeserializer) deserializer, + beanDesc.getBeanClass(), objectMapper); + } + return deserializer; + } + + /** + * 代理对象反序列化器,用于处理对象字段的懒加载 + */ + private static class ProxyObjectDeserializer extends StdDeserializer implements ResolvableDeserializer { + + private final JsonDeserializer delegate; + private final Class targetClass; + private final ObjectMapper objectMapper; + + @SuppressWarnings("unchecked") + public ProxyObjectDeserializer(JsonDeserializer delegate, + Class targetClass, ObjectMapper objectMapper) { + super((Class) targetClass); + this.delegate = delegate; + this.targetClass = targetClass; + this.objectMapper = objectMapper; + } + + @Override + public Object deserialize(com.fasterxml.jackson.core.JsonParser p, + com.fasterxml.jackson.databind.DeserializationContext ctxt) throws java.io.IOException { + // 首先检查是否是一个只有id的对象节点 + if (p.isExpectedStartObjectToken()) { + ObjectNode node = p.readValueAsTree(); + + // 检查是否只包含id和_proxy_字段 + if (node.has("id") && node.has("_proxy_")) { + try { + // 创建目标类的实例 + Object instance = targetClass.getDeclaredConstructor().newInstance(); + + // 设置id字段 + if (instance instanceof IdentityEntity) { + ((IdentityEntity) instance).setId(node.get("id").asInt()); + } + return instance; + } catch (Exception e) { + // 如果创建实例失败,回退到原始反序列化器 + return delegate.deserialize(p, ctxt); + } + } + // 对于普通对象,使用原始反序列化器 + return delegate.deserialize(p, ctxt); + } + // 对于非对象类型,使用原始反序列化器 + return delegate.deserialize(p, ctxt); + } + + @Override + public void resolve(DeserializationContext ctxt) throws JsonMappingException { + // 如果委托的反序列化器也是ResolvableDeserializer,则调用其resolve方法 + if (delegate instanceof ResolvableDeserializer) { + ((ResolvableDeserializer) delegate).resolve(ctxt); + } + } + + } +} \ No newline at end of file diff --git a/client/src/main/java/com/ecep/contract/service/ContractItemService.java b/client/src/main/java/com/ecep/contract/service/ContractItemService.java index 0aad692..6f62800 100644 --- a/client/src/main/java/com/ecep/contract/service/ContractItemService.java +++ b/client/src/main/java/com/ecep/contract/service/ContractItemService.java @@ -1,5 +1,6 @@ package com.ecep.contract.service; +import java.util.HashMap; import java.util.List; import org.springframework.stereotype.Service; @@ -12,7 +13,9 @@ import com.ecep.contract.vm.ContractItemViewModel; public class ContractItemService extends QueryService { public List findAllByInventory(Inventory parent) { - throw new UnsupportedOperationException("Unimplemented method 'findAllByInventory'"); + HashMap params = new HashMap<>(); + params.put("inventory", parent.getId()); + return findAll(params, null).getContent(); } } diff --git a/client/src/main/java/com/ecep/contract/service/ContractService.java b/client/src/main/java/com/ecep/contract/service/ContractService.java index 6141b96..b828110 100644 --- a/client/src/main/java/com/ecep/contract/service/ContractService.java +++ b/client/src/main/java/com/ecep/contract/service/ContractService.java @@ -2,8 +2,14 @@ package com.ecep.contract.service; import java.io.File; import java.time.LocalDate; +import java.util.HashMap; import java.util.List; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import com.ecep.contract.MessageHolder; @@ -13,14 +19,42 @@ import com.ecep.contract.model.ContractFile; import com.ecep.contract.model.Project; import com.ecep.contract.vm.ContractViewModel; +import io.micrometer.common.util.StringUtils; + @Service +@CacheConfig(cacheNames = "contract") public class ContractService extends QueryService { + @Cacheable(key = "#p0") + public Contract findById(Integer id) { + return super.findById(id); + } + + /** + * 保存实体对象,异步方法 + */ + @Caching(evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'code-'+#p0.code") + }) + public Contract save(Contract contract) { + return super.save(contract); + } + + @Caching(evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'code-'+#p0.code") + }) + public void delete(Contract contract) { + super.delete(contract); + } + public boolean updateParentCode(Contract contract) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'updateParentCode'"); } + @Cacheable(key = "'code-'+#p0") public Contract findByCode(String string) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'findByCode'"); @@ -31,8 +65,13 @@ public class ContractService extends QueryService { } public List findAllBySaleContract(Contract contract) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'findAllBySaleContract'"); + String parentCode = contract.getCode(); + if (StringUtils.isEmpty(parentCode)) { + return List.of(); + } + HashMap params = new HashMap<>(); + params.put("parentCode", contract.getCode()); + return findAll(params, Pageable.unpaged()).getContent(); } public boolean checkContractPathInBasePath(Contract v) { diff --git a/client/src/main/java/com/ecep/contract/util/ProxyUtils.java b/client/src/main/java/com/ecep/contract/util/ProxyUtils.java index 6e9688a..531729b 100644 --- a/client/src/main/java/com/ecep/contract/util/ProxyUtils.java +++ b/client/src/main/java/com/ecep/contract/util/ProxyUtils.java @@ -1,13 +1,97 @@ package com.ecep.contract.util; -import java.util.Objects; +import java.lang.reflect.Field; +import java.lang.reflect.Proxy; + +import com.ecep.contract.model.IdentityEntity; +import com.fasterxml.jackson.annotation.JsonIgnore; public class ProxyUtils { /** * 判断对象是否已初始化 - * 在客户端环境中,如果对象不为null,则认为已初始化 + * 在客户端环境中,从服务器端返回的数据中,代理对象序列化时只包含Id属性, + * 其他属性为null,因此需要判断对象是否已初始化 + * 初始化条件:对象不为null,且不是代理对象或已被标记为初始化 */ public static boolean isInitialized(Object proxy) { - return proxy != null; + if (proxy == null) { + return false; + } + + // 检查 proxy 中除了id其他属性都为null + try { + Field[] fields = proxy.getClass().getFields(); + for (Field field : fields) { + if (field.getName().equals("id")) { + continue; + } + // 忽略被JsonIgnore注解标记的字段 + if (field.getAnnotation(JsonIgnore.class) != null) { + continue; + } + // 检查字段是否为null + if (field.get(proxy) != null) { + return true; + } + } + // 如果除了id其他属性都为null,认为未初始化 + return false; + } catch (Exception e) { + // 发生异常时默认已初始化 + return true; + } + } + + /** + * 检查对象是否是代理对象 + */ + public static boolean isProxyObject(Object obj) { + return false; + } + + /** + * 创建代理对象 + */ + @SuppressWarnings("unchecked") + public static T createProxy(T original, Class... interfaces) { + if (original == null) { + return null; + } + + // 如果对象已经是代理对象,直接返回 + if (isProxyObject(original)) { + return original; + } + + // 创建代理对象 + T proxy = (T) Proxy.newProxyInstance( + original.getClass().getClassLoader(), + interfaces, + (proxyObj, method, args) -> { + // 如果代理对象未初始化,先初始化 + if (!isInitialized(proxyObj)) { + // 这里可以添加初始化逻辑 + markInitialized(proxyObj); + } + // 调用原始对象的方法 + return method.invoke(original, args); + }); + return proxy; + } + + /** + * 标记对象为已初始化 + */ + public static void markInitialized(Object proxy) { + if (proxy != null && isProxyObject(proxy)) { + // 标记为已初始化 + } + } + + /** + * 检查对象是否是实体类 + */ + public static boolean isEntityClass(Class clazz) { + return clazz != null && IdentityEntity.class.isAssignableFrom(clazz); } } diff --git a/server/src/main/java/com/ecep/contract/ds/contract/service/ContractService.java b/server/src/main/java/com/ecep/contract/ds/contract/service/ContractService.java index e8b891f..e50bda9 100644 --- a/server/src/main/java/com/ecep/contract/ds/contract/service/ContractService.java +++ b/server/src/main/java/com/ecep/contract/ds/contract/service/ContractService.java @@ -135,9 +135,14 @@ public class ContractService implements IEntityService, QueryService { + return cb.equal(root.get("payWay"), payWay); + }); + } + spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "parentCode"); // relation spec = SpecificationUtils.andParam(spec, paramsNode, "group", "kind", "type", "group", "company", "project");