diff --git a/client/pom.xml b/client/pom.xml index b2c09d1..8785e61 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -6,12 +6,12 @@ com.ecep.contract Contract-Manager - 0.0.49-SNAPSHOT + 0.0.53-SNAPSHOT com.ecep.contract client - 0.0.49-SNAPSHOT + 0.0.53-SNAPSHOT ${java.version} @@ -22,7 +22,7 @@ com.ecep.contract common - 0.0.49-SNAPSHOT + 0.0.53-SNAPSHOT @@ -78,6 +78,29 @@ 2 + + org.codehaus.mojo + versions-maven-plugin + 2.16.2 + + + true + increment-version + install + + set + + + false + true + + true + 3 + + + + + diff --git a/common/pom.xml b/common/pom.xml index e11a1da..ba67d1e 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,12 +6,12 @@ com.ecep.contract Contract-Manager - 0.0.49-SNAPSHOT + 0.0.53-SNAPSHOT com.ecep.contract common - 0.0.49-SNAPSHOT + 0.0.53-SNAPSHOT ${java.version} diff --git a/common/src/main/java/com/ecep/contract/model/Employee.java b/common/src/main/java/com/ecep/contract/model/Employee.java index 121fb89..27cd84e 100644 --- a/common/src/main/java/com/ecep/contract/model/Employee.java +++ b/common/src/main/java/com/ecep/contract/model/Employee.java @@ -1,15 +1,29 @@ package com.ecep.contract.model; -import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -import org.hibernate.proxy.HibernateProxy; - import java.io.Serializable; import java.time.LocalDate; import java.util.Objects; +import org.hibernate.proxy.HibernateProxy; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + @Getter @Setter @jakarta.persistence.Entity @@ -33,6 +47,7 @@ public class Employee implements BasedEntity, IdentityEntity, NamedEntity, Seria @JoinColumn(name = "DEPARTMENT_ID") @ManyToOne(fetch = FetchType.LAZY) @ToString.Exclude + @JsonIgnoreProperties({ "leader" }) private Department department; /** @@ -87,6 +102,8 @@ public class Employee implements BasedEntity, IdentityEntity, NamedEntity, Seria @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE }) @JoinTable(name = "EMPLOYEE_ROLES", joinColumns = @JoinColumn(name = "EMPLOYEE_ID"), inverseJoinColumns = @JoinColumn(name = "ROLE_ID")) + @ToString.Exclude + @JsonIgnore private java.util.List roles = new java.util.ArrayList<>(); @Override diff --git a/common/src/main/java/com/ecep/contract/model/EmployeeRole.java b/common/src/main/java/com/ecep/contract/model/EmployeeRole.java index d0b07ee..e027e5e 100644 --- a/common/src/main/java/com/ecep/contract/model/EmployeeRole.java +++ b/common/src/main/java/com/ecep/contract/model/EmployeeRole.java @@ -6,55 +6,51 @@ import lombok.Setter; import java.io.Serializable; +import com.fasterxml.jackson.annotation.JsonIgnore; + @Getter @Setter @jakarta.persistence.Entity @Table(name = "EMPLOYEE_ROLE", schema = "supplier_ms") public class EmployeeRole implements IdentityEntity, NamedEntity, Serializable { - private static final long serialVersionUID = 1L; - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "ID", nullable = false) - private Integer id; + private static final long serialVersionUID = 1L; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; - @Column(name = "CODE") - private String code; + @Column(name = "CODE") + private String code; - @Column(name = "NAME") - private String name; + @Column(name = "NAME") + private String name; - /** - * 是否系统管理员 - */ - @Column(name = "SYS_ADMIN") - private boolean systemAdministrator; + /** + * 是否系统管理员 + */ + @Column(name = "SYS_ADMIN") + private boolean systemAdministrator; - /** - * 是否管理层 - */ - @Column(name = "MANAGER") - private boolean manager; + /** + * 是否管理层 + */ + @Column(name = "MANAGER") + private boolean manager; - /** - * 是否启用 - */ - @Column(name = "IS_ACTIVE") - private boolean active = true; + /** + * 是否启用 + */ + @Column(name = "IS_ACTIVE") + private boolean active = true; - @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE }) - @JoinTable( - name = "EMPLOYEE_ROLE_FUNCTIONS", - joinColumns = @JoinColumn(name = "ROLE_ID"), - inverseJoinColumns = @JoinColumn(name = "FUNC_ID") - ) - private java.util.List functions = new java.util.ArrayList<>(); + @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE }) + @JoinTable(name = "EMPLOYEE_ROLE_FUNCTIONS", joinColumns = @JoinColumn(name = "ROLE_ID"), inverseJoinColumns = @JoinColumn(name = "FUNC_ID")) + @JsonIgnore + private java.util.List functions = new java.util.ArrayList<>(); - @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) - @JoinTable( - name = "EMPLOYEE_ROLES", - joinColumns = @JoinColumn(name = "ROLE_ID"), - inverseJoinColumns = @JoinColumn(name = "EMPLOYEE_ID") - ) - private java.util.List employees = new java.util.ArrayList<>(); + @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE }) + @JoinTable(name = "EMPLOYEE_ROLES", joinColumns = @JoinColumn(name = "ROLE_ID"), inverseJoinColumns = @JoinColumn(name = "EMPLOYEE_ID")) + @JsonIgnore + private java.util.List employees = new java.util.ArrayList<>(); } diff --git a/pom.xml b/pom.xml index c68b47d..657da93 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.ecep.contract Contract-Manager - 0.0.49-SNAPSHOT + 0.0.53-SNAPSHOT pom server @@ -149,29 +149,6 @@ - - org.codehaus.mojo - versions-maven-plugin - 2.16.2 - - - true - increment-version - install - - set - - - false - true - - true - 3 - - - - - diff --git a/server/pom.xml b/server/pom.xml index 69a380f..7ad33a6 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -6,12 +6,12 @@ com.ecep.contract Contract-Manager - 0.0.49-SNAPSHOT + 0.0.53-SNAPSHOT com.ecep.contract server - 0.0.49-SNAPSHOT + 0.0.53-SNAPSHOT ${java.version} @@ -22,7 +22,7 @@ com.ecep.contract common - 0.0.49-SNAPSHOT + 0.0.53-SNAPSHOT org.springframework.boot @@ -48,16 +48,16 @@ spring-boot-starter-security - org.springframework.boot - spring-boot-starter-data-redis - + org.springframework.boot + spring-boot-starter-data-redis + org.springframework.boot spring-boot-starter-mail - - + + com.zaxxer HikariCP @@ -69,11 +69,11 @@ org.springframework.data spring-data-jpa - - - - - + + + + + com.mysql @@ -89,6 +89,27 @@ + + org.codehaus.mojo + versions-maven-plugin + 2.16.2 + + + true + increment-version + install + + set + + + false + true + true + 3 + + + + org.springframework.boot spring-boot-maven-plugin @@ -103,5 +124,4 @@ - \ No newline at end of file diff --git a/server/src/main/java/com/ecep/contract/SpringApp.java b/server/src/main/java/com/ecep/contract/SpringApp.java index 46b4465..bbb1b8a 100644 --- a/server/src/main/java/com/ecep/contract/SpringApp.java +++ b/server/src/main/java/com/ecep/contract/SpringApp.java @@ -1,10 +1,6 @@ package com.ecep.contract; import java.time.Duration; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.Properties; @@ -23,27 +19,20 @@ import org.springframework.boot.context.metrics.buffering.StartupTimeline; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.EventListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.metrics.StartupStep; +import org.springframework.data.web.config.EnableSpringDataWebSupport; +import org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import com.ecep.contract.cloud.CloudRepositoriesConfig; import com.ecep.contract.ds.DsRepositoriesConfig; import com.ecep.contract.util.TaskMonitorCenter; -import com.fasterxml.jackson.databind.ObjectMapper; -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; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; @SpringBootApplication(exclude = { org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration.class, @@ -52,6 +41,8 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; @EnableScheduling @EnableAsync @EnableCaching + +@EnableSpringDataWebSupport(pageSerializationMode = PageSerializationMode.VIA_DTO) public class SpringApp { private final TaskMonitorCenter taskMonitorCenter; @@ -230,24 +221,6 @@ public class SpringApp { } } - // Redis缓存配置已移至application.properties文件 - // Spring Boot会根据配置自动创建RedisCacheManager - - @Bean - public ObjectMapper objectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - JavaTimeModule javaTimeModule = new JavaTimeModule(); - javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE)); - javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer( - DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN))); - javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); - javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)); - javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer( - DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN))); - javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME)); - objectMapper.registerModule(javaTimeModule); - return objectMapper; - } public static TaskMonitorCenter getTaskMonitorCenter() { return getBean(TaskMonitorCenter.class); diff --git a/server/src/main/java/com/ecep/contract/cloud/AbstractCtx.java b/server/src/main/java/com/ecep/contract/cloud/AbstractCtx.java index ffff8ad..43dad0b 100644 --- a/server/src/main/java/com/ecep/contract/cloud/AbstractCtx.java +++ b/server/src/main/java/com/ecep/contract/cloud/AbstractCtx.java @@ -38,8 +38,8 @@ public class AbstractCtx { return confService; } - - public boolean updateText(Supplier getter, Consumer setter, String text, MessageHolder holder, String topic) { + public boolean updateText(Supplier getter, Consumer setter, String text, MessageHolder holder, + String topic) { if (!Objects.equals(getter.get(), text)) { setter.accept(text); holder.info(topic + "修改为: " + text); @@ -48,7 +48,8 @@ public class AbstractCtx { return false; } - public boolean updateAppendText(Supplier getter, Consumer setter, String text, MessageHolder holder, String topic) { + public boolean updateAppendText(Supplier getter, Consumer setter, String text, MessageHolder holder, + String topic) { if (StringUtils.hasText(text)) { String str = MyStringUtils.appendIfAbsent(getter.get(), text); if (!Objects.equals(getter.get(), str)) { @@ -60,19 +61,21 @@ public class AbstractCtx { return false; } - - public boolean updateLocalDate(Supplier getter, Consumer setter, java.sql.Date date, MessageHolder holder, String topic) { + public boolean updateLocalDate(Supplier getter, Consumer setter, java.sql.Date date, + MessageHolder holder, String topic) { if (date != null) { return updateLocalDate(getter, setter, date.toLocalDate(), holder, topic); } return false; } - public boolean updateLocalDate(Supplier getter, Consumer setter, LocalDate date, MessageHolder holder, String topic) { + public boolean updateLocalDate(Supplier getter, Consumer setter, LocalDate date, + MessageHolder holder, String topic) { return updateLocalDate(getter, setter, date, holder, topic, false); } - public boolean updateLocalDate(Supplier getter, Consumer setter, LocalDate date, MessageHolder holder, String topic, boolean allowNull) { + public boolean updateLocalDate(Supplier getter, Consumer setter, LocalDate date, + MessageHolder holder, String topic, boolean allowNull) { if (date == null && !allowNull) { return false; } @@ -84,7 +87,8 @@ public class AbstractCtx { return false; } - public boolean updateLocalDate(Supplier getter, Consumer setter, String strDate, MessageHolder holder, String topic) { + public boolean updateLocalDate(Supplier getter, Consumer setter, String strDate, + MessageHolder holder, String topic) { LocalDate date = null; if (StringUtils.hasText(strDate)) { try { @@ -96,7 +100,8 @@ public class AbstractCtx { return updateLocalDate(getter, setter, date, holder, topic); } - public boolean updateLocalDate(Supplier getter, Consumer setter, Timestamp timestamp, MessageHolder holder, String topic) { + public boolean updateLocalDate(Supplier getter, Consumer setter, Timestamp timestamp, + MessageHolder holder, String topic) { LocalDate date = null; if (timestamp != null) { @@ -109,7 +114,8 @@ public class AbstractCtx { return updateLocalDate(getter, setter, date, holder, topic); } - public boolean updateLocalDateTime(Supplier getter, Consumer setter, Timestamp timestamp, MessageHolder holder, String topic) { + public boolean updateLocalDateTime(Supplier getter, Consumer setter, + Timestamp timestamp, MessageHolder holder, String topic) { LocalDateTime dateTime = null; if (timestamp != null) { @@ -121,7 +127,11 @@ public class AbstractCtx { holder.warn("解析日期" + timestamp + " 异常:" + e.getMessage()); } } + return updateLocalDateTime(getter, setter, dateTime, holder, topic); + } + public boolean updateLocalDateTime(Supplier getter, Consumer setter, + LocalDateTime dateTime, MessageHolder holder, String topic) { if (!Objects.equals(getter.get(), dateTime)) { setter.accept(dateTime); holder.info(topic + "修改为: " + dateTime); @@ -130,7 +140,8 @@ public class AbstractCtx { return false; } - public boolean updateInstant(Supplier getter, Consumer setter, Instant instant, MessageHolder holder, String topic) { + public boolean updateInstant(Supplier getter, Consumer setter, Instant instant, + MessageHolder holder, String topic) { if (!Objects.equals(getter.get(), instant)) { setter.accept(instant); holder.info(topic + "修改为: " + instant); @@ -139,13 +150,14 @@ public class AbstractCtx { return false; } - - public boolean updateNumber(Supplier getter, Consumer setter, BigDecimal value, MessageHolder holder, String topic) { + public boolean updateNumber(Supplier getter, Consumer setter, BigDecimal value, + MessageHolder holder, String topic) { double val = value.doubleValue(); return updateNumber(getter, setter, val, holder, topic); } - public boolean updateNumber(Supplier getter, Consumer setter, double value, MessageHolder holder, String topic) { + public boolean updateNumber(Supplier getter, Consumer setter, double value, MessageHolder holder, + String topic) { if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) { setter.accept(value); holder.info(topic + "修改为: " + value); @@ -154,7 +166,8 @@ public class AbstractCtx { return false; } - public boolean updateNumber(Supplier getter, Consumer setter, float value, MessageHolder holder, String topic) { + public boolean updateNumber(Supplier getter, Consumer setter, float value, MessageHolder holder, + String topic) { if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) { setter.accept(value); holder.info(topic + "修改为: " + value); @@ -163,8 +176,8 @@ public class AbstractCtx { return false; } - - public boolean updateNumber(Supplier getter, Consumer setter, Integer value, MessageHolder holder, String topic) { + public boolean updateNumber(Supplier getter, Consumer setter, Integer value, MessageHolder holder, + String topic) { if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) { setter.accept(value); holder.info(topic + "修改为: " + value); diff --git a/server/src/main/java/com/ecep/contract/cloud/rk/CloudRkService.java b/server/src/main/java/com/ecep/contract/cloud/rk/CloudRkService.java index b2fa62d..da5ef4c 100644 --- a/server/src/main/java/com/ecep/contract/cloud/rk/CloudRkService.java +++ b/server/src/main/java/com/ecep/contract/cloud/rk/CloudRkService.java @@ -5,13 +5,13 @@ import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import com.ecep.contract.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -36,6 +36,7 @@ import com.ecep.contract.ds.other.service.SysConfService; import com.ecep.contract.model.CloudRk; import com.ecep.contract.model.Company; import com.ecep.contract.model.CompanyBlackReason; +import com.ecep.contract.util.FileUtils; import com.ecep.contract.util.HttpJsonUtils; import com.ecep.contract.util.MyStringUtils; import com.fasterxml.jackson.annotation.JsonAlias; @@ -200,7 +201,7 @@ public class CloudRkService implements IEntityService { if (!reasonList.isEmpty()) { companyBlackReasonRepository.saveAll(reasonList); } - cloudRk.setCloudBlackListUpdated(Instant.now()); + cloudRk.setCloudBlackListUpdated(LocalDateTime.now()); } private void toCompanyBlackReasonList( @@ -234,12 +235,12 @@ public class CloudRkService implements IEntityService { */ public boolean checkBlackListUpdateElapse( Company company, CloudRk cloudRk, BlackListUpdateContext context) { - Instant start = cloudRk.getCloudBlackListUpdated(); + LocalDateTime start = cloudRk.getCloudBlackListUpdated(); if (start == null) { return true; } - Instant elapse = start.plusSeconds(context.getElapse()); - return elapse.isBefore(Instant.now()); + LocalDateTime elapse = start.plusSeconds(context.getElapse()); + return elapse.isBefore(LocalDateTime.now()); } public CloudRk getOrCreateCloudRk(CloudInfo info) { diff --git a/server/src/main/java/com/ecep/contract/cloud/rk/CloudRkSyncTask.java b/server/src/main/java/com/ecep/contract/cloud/rk/CloudRkSyncTask.java index 7866a48..c2abcc7 100644 --- a/server/src/main/java/com/ecep/contract/cloud/rk/CloudRkSyncTask.java +++ b/server/src/main/java/com/ecep/contract/cloud/rk/CloudRkSyncTask.java @@ -1,7 +1,7 @@ package com.ecep.contract.cloud.rk; -import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -96,7 +96,7 @@ public class CloudRkSyncTask extends Tasker { } catch (Exception e) { throw new RuntimeException(e); } finally { - cloudRk.setLatestUpdate(Instant.now()); + cloudRk.setLatestUpdate(LocalDateTime.now()); service.save(cloudRk); } // updateProgress(counter.incrementAndGet(), total); diff --git a/server/src/main/java/com/ecep/contract/cloud/rk/EntReportParser.java b/server/src/main/java/com/ecep/contract/cloud/rk/EntReportParser.java index 0195e66..8bd122f 100644 --- a/server/src/main/java/com/ecep/contract/cloud/rk/EntReportParser.java +++ b/server/src/main/java/com/ecep/contract/cloud/rk/EntReportParser.java @@ -37,7 +37,6 @@ public class EntReportParser { private CloudRk cloudRk; private boolean modified = false; - public void parse(JsonNode json) { if (!json.has("B1001")) { // 没有数据 @@ -79,7 +78,6 @@ public class EntReportParser { } - private void updateLegalRepresentativeContact(JsonNode data) { String legalRepresentative = company.getLegalRepresentative(); if (!StringUtils.hasText(legalRepresentative)) { @@ -94,7 +92,7 @@ public class EntReportParser { return; } if (contactList.isEmpty()) { - //没有,创建法人联系人 + // 没有,创建法人联系人 contact = new CompanyContact(); contact.setCompany(company); contact.setName(legalRepresentative); @@ -107,10 +105,11 @@ public class EntReportParser { any = contactList.stream().findAny(); } contact = any.get(); -// if (contact.getPostion() == null || !contact.setPostion().contains("法定代表人")) { -// contact.setMemo("法定代表人"); -// modified = true; -// } + // if (contact.getPostion() == null || !contact.setPostion().contains("法定代表人")) + // { + // contact.setMemo("法定代表人"); + // modified = true; + // } } if (!StringUtils.hasText(contact.getEmail())) { @@ -190,7 +189,7 @@ public class EntReportParser { modified = true; } } else { - //fixed 当平台返回的 社会统一信用代码为空时,如果原来已经有的,则不做更新 + // fixed 当平台返回的 社会统一信用代码为空时,如果原来已经有的,则不做更新 if (StringUtils.hasText(company.getUniscid())) { if (logger.isInfoEnabled()) { logger.info("来自平台的 UNISCID 为空,但本地{}已经记录{},不做更改", company.getName(), company.getUniscid()); @@ -212,7 +211,7 @@ public class EntReportParser { return; } LocalDateTime updated = objectMapper.convertValue(node, LocalDateTime.class); - cloudRk.setCloudEntUpdate(updated.toInstant(ZoneOffset.ofHours(8))); + cloudRk.setCloudEntUpdate(updated); } private void updateCompanyProperty(String field, JsonNode node, String... excludeValues) { @@ -255,7 +254,6 @@ public class EntReportParser { } } - private void saveJsonToFile(JsonNode json) { String companyPath = company.getPath(); if (StringUtils.hasText(companyPath)) { diff --git a/server/src/main/java/com/ecep/contract/cloud/rk/ctx/CloudRkCtx.java b/server/src/main/java/com/ecep/contract/cloud/rk/ctx/CloudRkCtx.java index fbe485a..b17748e 100644 --- a/server/src/main/java/com/ecep/contract/cloud/rk/ctx/CloudRkCtx.java +++ b/server/src/main/java/com/ecep/contract/cloud/rk/ctx/CloudRkCtx.java @@ -25,7 +25,6 @@ import java.util.stream.Collectors; import javax.net.ssl.SSLException; -import com.ecep.contract.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; @@ -44,6 +43,7 @@ import com.ecep.contract.model.Company; import com.ecep.contract.model.CompanyBlackReason; import com.ecep.contract.model.CompanyContact; import com.ecep.contract.model.CompanyOldName; +import com.ecep.contract.util.FileUtils; import com.ecep.contract.util.HttpJsonUtils; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; @@ -99,8 +99,7 @@ public class CloudRkCtx extends AbstractCtx { Proxy.Type proxyType = Proxy.Type.valueOf(proxyUri.getScheme().toUpperCase()); socksProxy = new Proxy( proxyType, - new InetSocketAddress(proxyUri.getHost(), proxyUri.getPort()) - ); + new InetSocketAddress(proxyUri.getHost(), proxyUri.getPort())); } return socksProxy; } @@ -109,7 +108,6 @@ public class CloudRkCtx extends AbstractCtx { HttpJsonUtils.post(url, data, consumer, getObjectMapper(), getSocksProxy()); } - public boolean syncCompany(Company company, CloudRk cloudRk, MessageHolder holder) { if (!StringUtils.hasText(cloudRk.getCloudId())) { holder.warn("未定义平台编号, 尝试从平台上自动获取"); @@ -157,19 +155,17 @@ public class CloudRkCtx extends AbstractCtx { return updated; } - /** * 更新黑名单列表 */ public boolean updateBlackList( - Company company, CloudRk cloudRk, MessageHolder holder - ) throws IOException { - Instant start = cloudRk.getCloudBlackListUpdated(); + Company company, CloudRk cloudRk, MessageHolder holder) throws IOException { + LocalDateTime start = cloudRk.getCloudBlackListUpdated(); if (start != null) { long elapse = getConfService().getLong(CloudRkService.KEY_BLACK_LIST_ELAPSE); if (elapse > 0) { - Instant next = start.plusSeconds(elapse); - if (next.isAfter(Instant.now())) { + LocalDateTime next = start.plusSeconds(elapse); + if (next.isAfter(LocalDateTime.now())) { holder.debug("更新时间未到, 上次更新时间 = " + start); return false; } @@ -179,7 +175,6 @@ public class CloudRkCtx extends AbstractCtx { String api = getConfService().getString(CloudRkService.KEY_BLACK_LIST_URL); List companyNames = getCompanyService().getAllNames(company); - List reasonList = new ArrayList<>(); List dbReasons = getCompanyBlackReasonService().findAllByCompany(company); for (String name : companyNames) { @@ -195,22 +190,21 @@ public class CloudRkCtx extends AbstractCtx { } if (reasonList.isEmpty()) { - cloudRk.setCloudBlackListUpdated(Instant.now()); + cloudRk.setCloudBlackListUpdated(LocalDateTime.now()); return false; } for (CompanyBlackReason companyBlackReason : reasonList) { getCompanyBlackReasonService().save(companyBlackReason); } - cloudRk.setCloudBlackListUpdated(Instant.now()); + cloudRk.setCloudBlackListUpdated(LocalDateTime.now()); return true; } private boolean applyBlackReason( JsonNode json, Company company, CloudRk cloudRk, List reasonList, List dbReasons, - MessageHolder holder - ) { + MessageHolder holder) { if (isUnSuccess(json, holder)) { return false; } @@ -246,11 +240,11 @@ public class CloudRkCtx extends AbstractCtx { private void toCompanyBlackReasonList( Company company, BlackReasonType type, JsonNode reason, List dbReasons, - List reasonList - ) throws JsonMappingException { + List reasonList) throws JsonMappingException { ObjectNode object = (ObjectNode) reason; String key = "rk-" + object.remove("id").asText(); - CompanyBlackReason cbr = dbReasons.stream().filter(r -> r.getKey().equals(key)).findAny().orElseGet(CompanyBlackReason::new); + CompanyBlackReason cbr = dbReasons.stream().filter(r -> r.getKey().equals(key)).findAny() + .orElseGet(CompanyBlackReason::new); getObjectMapper().updateValue(cbr, reason); cbr.setCompany(company); cbr.setType(type); @@ -258,13 +252,11 @@ public class CloudRkCtx extends AbstractCtx { reasonList.add(cbr); } - /** * 更新评分 */ public boolean updateEnterpriseCredit( - Company company, CloudRk cloudRk, MessageHolder holder - ) throws IOException { + Company company, CloudRk cloudRk, MessageHolder holder) throws IOException { String api = getConfService().getString(CloudRkService.KEY_ENT_SCORE_URL); AtomicBoolean modified = new AtomicBoolean(false); try { @@ -332,8 +324,7 @@ public class CloudRkCtx extends AbstractCtx { * 客户信用 */ public boolean updateCustomerScore( - Company company, CloudRk cloudRk, MessageHolder holder - ) throws IOException { + Company company, CloudRk cloudRk, MessageHolder holder) throws IOException { String url = getConfService().getString(CloudRkService.KEY_CUSTOMER_REPORT_URL); AtomicBoolean modified = new AtomicBoolean(false); try { @@ -381,16 +372,15 @@ public class CloudRkCtx extends AbstractCtx { if (updateNumber(cloudRk::getCustomerScore, cloudRk::setCustomerScore, score, holder, "客户信用总分")) { modified = true; } - if (updateText(cloudRk::getCustomerDescription, cloudRk::setCustomerDescription, description, holder, "客户信用评级说明")) { + if (updateText(cloudRk::getCustomerDescription, cloudRk::setCustomerDescription, description, holder, + "客户信用评级说明")) { modified = true; } return modified; } - public boolean updateVendorScore( - Company company, CloudRk cloudRk, MessageHolder holder - ) throws IOException { + Company company, CloudRk cloudRk, MessageHolder holder) throws IOException { String url = getConfService().getString(CloudRkService.KEY_VENDOR_REPORT_URL); AtomicBoolean modified = new AtomicBoolean(false); try { @@ -437,7 +427,8 @@ public class CloudRkCtx extends AbstractCtx { if (updateNumber(cloudRk::getVendorScore, cloudRk::setVendorScore, score, holder, "供应商信用总分")) { modified = true; } - if (updateText(cloudRk::getVendorDescription, cloudRk::setVendorDescription, description, holder, "供应商信用评级说明")) { + if (updateText(cloudRk::getVendorDescription, cloudRk::setVendorDescription, description, holder, + "供应商信用评级说明")) { modified = true; } return modified; @@ -451,8 +442,7 @@ public class CloudRkCtx extends AbstractCtx { * @return true 更新了 cloudId,否则false */ private boolean queryCloudIdAndSelectOne( - Company company, CloudRk cloudRk, MessageHolder holder - ) { + Company company, CloudRk cloudRk, MessageHolder holder) { try { List entInfos = queryEnterpriseWithFuzzy(company, cloudRk, holder); // 返回的查询结果为空时 @@ -464,14 +454,17 @@ public class CloudRkCtx extends AbstractCtx { } // 在返回的结果中,找到与公司名字一致的一个 - Optional optional = entInfos.stream().filter(n -> n.getName().equals(company.getName())).findAny(); + Optional optional = entInfos.stream() + .filter(n -> n.getName().equals(company.getName())).findAny(); if (optional.isPresent()) { cloudRk.setCloudId(optional.get().getId()); return true; } // - holder.error("在平台中查询到多个匹配 (" + entInfos.stream().map(CloudRkService.EntInfo::getName).collect(Collectors.joining(", ")) + "),请手工同步选择匹配"); + holder.error("在平台中查询到多个匹配 (" + + entInfos.stream().map(CloudRkService.EntInfo::getName).collect(Collectors.joining(", ")) + + "),请手工同步选择匹配"); return false; } catch (Exception e) { @@ -484,13 +477,11 @@ public class CloudRkCtx extends AbstractCtx { } } - /** * 使用模糊查询接口查询相关企业信息 */ public List queryEnterpriseWithFuzzy( - Company company, CloudRk cloudRk, MessageHolder holder - ) throws IOException { + Company company, CloudRk cloudRk, MessageHolder holder) throws IOException { String url = getConfService().getString(CloudRkService.KEY_ENT_FUZZY_URL); List results = new ArrayList<>(); ObjectMapper objectMapper = getObjectMapper(); @@ -508,7 +499,8 @@ public class CloudRkCtx extends AbstractCtx { return results; } - private boolean applyEnterpriseQuery(JsonNode json, Company company, CloudRk cloudRk, List results, MessageHolder holder) { + private boolean applyEnterpriseQuery(JsonNode json, Company company, CloudRk cloudRk, + List results, MessageHolder holder) { if (!json.has("data")) { // 没有数据 holder.error("数据异常,返回的json中没有 data 字段"); @@ -542,8 +534,7 @@ public class CloudRkCtx extends AbstractCtx { * 更新企业工商注册信息 */ public boolean updateEnterpriseInfo( - Company company, CloudRk cloudRk, MessageHolder holder - ) throws IOException { + Company company, CloudRk cloudRk, MessageHolder holder) throws IOException { String api = getConfService().getString(CloudRkService.KEY_ENT_REPORT_URL); Proxy socksProxy = getSocksProxy(); holder.debug("更新企业工商注册信息: " + company.getName() + " @ " + api + ", proxy=" + socksProxy); @@ -609,10 +600,12 @@ public class CloudRkCtx extends AbstractCtx { if (updateText(company::getRegisteredCapital, company::setRegisteredCapital, data, "regcap", holder, "注册资金")) { modified = true; } - if (updateText(company::getRegisteredCapitalCurrency, company::setRegisteredCapitalCurrency, data, "regcapcur", holder, "资本金币种")) { + if (updateText(company::getRegisteredCapitalCurrency, company::setRegisteredCapitalCurrency, data, "regcapcur", + holder, "资本金币种")) { modified = true; } - if (updateText(company::getLegalRepresentative, company::setLegalRepresentative, data, "frname", holder, "法人")) { + if (updateText(company::getLegalRepresentative, company::setLegalRepresentative, data, "frname", holder, + "法人")) { modified = true; } if (updateText(company::getDistrict, company::setDistrict, data, "regorgprovince", holder, "注册区域")) { @@ -636,10 +629,21 @@ public class CloudRkCtx extends AbstractCtx { updateCompanyNameHistory(company, data, holder.sub("曾用名")); updateLegalRepresentativeContact(company, data, holder.sub("法人联系方式")); - updateInstant(cloudRk::getCloudEntUpdate, cloudRk::setCloudEntUpdate, data, "updated", holder, "更新时间", false); + + updateLocalDateTime(cloudRk::getCloudEntUpdate, cloudRk::setCloudEntUpdate, data, "updated", holder, "更新时间"); return modified; } + public boolean updateLocalDateTime(Supplier getter, Consumer setter, JsonNode data,String field, + MessageHolder holder, String topic) { + 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); + return false; + } /** * 更新法人联系人联系方式 @@ -650,7 +654,6 @@ public class CloudRkCtx extends AbstractCtx { return; } - CompanyContactService contactService = SpringApp.getBean(CompanyContactService.class); List contactList = contactService.findAllByCompanyAndName(company, legalRepresentative); if (contactList == null) { @@ -660,7 +663,7 @@ public class CloudRkCtx extends AbstractCtx { CompanyContact contact = null; boolean modified = false; if (contactList.isEmpty()) { - //没有,创建法人联系人 + // 没有,创建法人联系人 contact = new CompanyContact(); contact.setCompany(company); contact.setName(legalRepresentative); @@ -705,7 +708,8 @@ public class CloudRkCtx extends AbstractCtx { } private boolean updateOperationPeriodBegin(Company company, JsonNode data, MessageHolder holder) { - return updateLocalDate(company::getOperationPeriodBegin, company::setOperationPeriodBegin, data, "opfrom", holder, "营业期限起始日期", true); + return updateLocalDate(company::getOperationPeriodBegin, company::setOperationPeriodBegin, data, "opfrom", + holder, "营业期限起始日期", true); } private boolean updateOperationPeriodEnd(Company company, JsonNode data, MessageHolder holder) { @@ -716,14 +720,15 @@ public class CloudRkCtx extends AbstractCtx { String text = node.asText(); if (StringUtils.hasText(text)) { if (text.equals("-")) { - return updateLocalDate(company::getOperationPeriodEnd, company::setOperationPeriodEnd, (LocalDate) null, holder, "营业期限截至日期", true); + return updateLocalDate(company::getOperationPeriodEnd, company::setOperationPeriodEnd, (LocalDate) null, + holder, "营业期限截至日期", true); } - return updateLocalDate(company::getOperationPeriodEnd, company::setOperationPeriodEnd, data, "opto", holder, "营业期限截至日期", true); + return updateLocalDate(company::getOperationPeriodEnd, company::setOperationPeriodEnd, data, "opto", holder, + "营业期限截至日期", true); } return false; } - private void updateCompanyNameHistory(Company company, JsonNode data, MessageHolder holder) { JsonNode node = data.get("nameHistory"); if (node == null) { @@ -754,7 +759,8 @@ public class CloudRkCtx extends AbstractCtx { } } - private boolean updateLocalDate(Supplier getter, Consumer setter, JsonNode data, String field, MessageHolder holder, String topic, boolean allowNull) { + private boolean updateLocalDate(Supplier getter, Consumer setter, JsonNode data, String field, + MessageHolder holder, String topic, boolean allowNull) { JsonNode node = data.get(field); if (node == null || node.isNull()) { return false; @@ -766,11 +772,13 @@ public class CloudRkCtx extends AbstractCtx { return updateLocalDate(getter, setter, localDate, holder, topic, allowNull); } - private boolean updateLocalDate(Supplier getter, Consumer setter, JsonNode data, String field, MessageHolder holder, String topic) { + private boolean updateLocalDate(Supplier getter, Consumer setter, JsonNode data, String field, + MessageHolder holder, String topic) { return updateLocalDate(getter, setter, data, field, holder, topic, false); } - private void updateInstant(Supplier getter, Consumer setter, JsonNode data, String field, MessageHolder holder, String topic, boolean allowNull) { + private void updateInstant(Supplier getter, Consumer setter, JsonNode data, String field, + MessageHolder holder, String topic, boolean allowNull) { JsonNode node = data.get("updated"); if (node == null) { return; @@ -787,7 +795,8 @@ public class CloudRkCtx extends AbstractCtx { updateInstant(getter, setter, instant, holder, topic); } - private boolean updateText(Supplier getter, Consumer setter, JsonNode data, String field, MessageHolder holder, String topic) { + private boolean updateText(Supplier getter, Consumer setter, JsonNode data, String field, + MessageHolder holder, String topic) { JsonNode node = data.get(field); if (node == null || node.isNull()) { return false; diff --git a/server/src/main/java/com/ecep/contract/config/GlobalExceptionHandler.java b/server/src/main/java/com/ecep/contract/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..41bfaef --- /dev/null +++ b/server/src/main/java/com/ecep/contract/config/GlobalExceptionHandler.java @@ -0,0 +1,160 @@ +package com.ecep.contract.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; + +import java.util.HashMap; +import java.util.Map; + +/** + * 全局异常处理器,捕获并处理所有Controller层抛出的异常,将错误信息以JSON格式返回给前端 + */ +// @RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 处理所有未捕获的异常 + */ + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception e) { + logger.error("系统异常", e); + Map result = new HashMap<>(); + result.put("code", 500); + result.put("message", "系统内部错误:" + e.getMessage()); + result.put("errorType", e.getClass().getName()); + result.put("success", false); + return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * 处理运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public ResponseEntity> handleRuntimeException(RuntimeException e) { + logger.error("运行时异常", e); + Map result = new HashMap<>(); + result.put("code", 500); + result.put("message", "运行时错误:" + e.getMessage()); + result.put("errorType", e.getClass().getName()); + result.put("success", false); + return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * 处理请求方法不支持异常 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ResponseEntity> handleHttpRequestMethodNotSupportedException( + HttpRequestMethodNotSupportedException e) { + logger.warn("请求方法不支持", e); + Map result = new HashMap<>(); + result.put("code", 405); + result.put("message", "请求方法不支持:" + e.getMessage()); + result.put("errorType", "MethodNotAllowed"); + result.put("success", false); + return new ResponseEntity<>(result, HttpStatus.METHOD_NOT_ALLOWED); + } + + /** + * 处理资源未找到异常 + */ + @ExceptionHandler(NoHandlerFoundException.class) + public ResponseEntity> handleNoHandlerFoundException(NoHandlerFoundException e) { + logger.warn("资源未找到", e); + Map result = new HashMap<>(); + result.put("code", 404); + result.put("message", "请求的资源不存在:" + e.getRequestURL()); + result.put("errorType", "ResourceNotFound"); + result.put("success", false); + return new ResponseEntity<>(result, HttpStatus.NOT_FOUND); + } + + /** + * 处理参数绑定异常 + */ + @ExceptionHandler(BindException.class) + public ResponseEntity> handleBindException(BindException e) { + logger.warn("参数绑定异常", e); + Map result = new HashMap<>(); + result.put("code", 400); + result.put("message", "参数绑定错误:" + e.getAllErrors().get(0).getDefaultMessage()); + result.put("errorType", "ParameterBindingError"); + result.put("success", false); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + } + + /** + * 处理方法参数验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleMethodArgumentNotValidException( + MethodArgumentNotValidException e) { + logger.warn("方法参数验证异常", e); + Map result = new HashMap<>(); + result.put("code", 400); + result.put("message", "参数验证错误:" + e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); + result.put("errorType", "ParameterValidationError"); + result.put("success", false); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + } + + /** + * 处理缺少请求参数异常 + */ + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity> handleMissingServletRequestParameterException( + MissingServletRequestParameterException e) { + logger.warn("缺少请求参数异常", e); + Map result = new HashMap<>(); + result.put("code", 400); + result.put("message", "缺少必要参数:" + e.getParameterName()); + result.put("errorType", "MissingParameter"); + result.put("success", false); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + } + + /** + * 处理方法参数类型不匹配异常 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity> handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException e) { + logger.warn("方法参数类型不匹配异常", e); + Map result = new HashMap<>(); + result.put("code", 400); + result.put("message", "参数类型错误:参数 '" + e.getName() + "' 应类型为 " + + (e.getRequiredType() != null ? e.getRequiredType().getSimpleName() : "未知类型")); + result.put("errorType", "ParameterTypeError"); + result.put("success", false); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + } + + /** + * 处理HTTP消息不可读异常(如JSON解析错误) + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity> handleHttpMessageNotReadableException( + HttpMessageNotReadableException e) { + logger.warn("HTTP消息不可读异常", e); + Map result = new HashMap<>(); + result.put("code", 400); + result.put("message", "请求体格式错误:" + e.getMessage()); + result.put("errorType", "MessageFormatError"); + result.put("success", false); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + } +} \ No newline at end of file diff --git a/server/src/main/java/com/ecep/contract/config/JacksonConfig.java b/server/src/main/java/com/ecep/contract/config/JacksonConfig.java new file mode 100644 index 0000000..850b151 --- /dev/null +++ b/server/src/main/java/com/ecep/contract/config/JacksonConfig.java @@ -0,0 +1,239 @@ +package com.ecep.contract.config; + +import java.io.IOException; +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.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.databind.BeanDescription; +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.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; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; + +/** + * Jackson配置类,用于配置JSON序列化相关的设置,解决循环引用问题 + */ +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { + ObjectMapper objectMapper = builder.build(); + + // 关闭日期时间格式化输出为时间戳,而是输出为ISO格式的字符串 + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + // 配置处理循环引用的策略,使用INDENT_OUTPUT格式化输出 + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + // 处理循环引用,启用引用处理功能 + objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + + // 配置Java 8时间模块的序列化格式 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE)); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer( + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); + javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer( + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME)); + objectMapper.registerModule(javaTimeModule); + + // 添加自定义模块,用于处理JPA/Hibernate代理对象 + 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; + } + }); + + objectMapper.registerModule(proxyModule); + + return objectMapper; + } + + /** + * 专门用于处理Hibernate代理对象的序列化器 + */ + private static class HibernateProxySerializer extends StdSerializer { + + 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(); + } + } + } + } + + /** + * 用于处理集合类型的序列化器,避免懒加载集合在会话关闭时无法序列化的问题 + */ + private static class CollectionSerializer extends StdSerializer> { + + protected CollectionSerializer() { + super((Class>) (Class) Collection.class); + } + + @Override + public void serialize(Collection value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + // 检查集合是否为Hibernate的持久化集合 + if (value instanceof PersistentCollection) { + PersistentCollection persistentCollection = (PersistentCollection) value; + // 如果集合未初始化,返回空集合 + if (!persistentCollection.wasInitialized()) { + gen.writeStartArray(); + gen.writeEndArray(); + return; + } + } + + // 如果已初始化,使用默认序列化 + serializers.defaultSerializeValue(value, gen); + } + } + + /** + * 用于处理IdentityEntity类型的序列化器包装器 + */ + private static class IdentityEntitySerializer extends StdSerializer { + private final JsonSerializer delegate; + + protected IdentityEntitySerializer(JsonSerializer delegate) { + super(Object.class); + this.delegate = delegate; + } + + @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) delegate).serialize(value, gen, serializers); + } + } + } + + /** + * 用于处理对象引用的序列化器,通过检测和管理对象引用避免循环引用问题 + */ + private static class IdentityReferenceSerializer extends StdSerializer { + private final JsonSerializer delegate; + private final ThreadLocal> 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 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) delegate).serialize(value, gen, serializers); + } finally { + // 清理访问记录 + visited.remove(value); + if (visited.isEmpty()) { + visitedObjects.remove(); + } + } + } + } +} \ No newline at end of file diff --git a/server/src/main/java/com/ecep/contract/config/SecurityConfig.java b/server/src/main/java/com/ecep/contract/config/SecurityConfig.java index e4a2dd7..932f910 100644 --- a/server/src/main/java/com/ecep/contract/config/SecurityConfig.java +++ b/server/src/main/java/com/ecep/contract/config/SecurityConfig.java @@ -8,8 +8,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -22,7 +23,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.config.Customizer; import com.ecep.contract.ds.other.service.EmployeeService; import com.ecep.contract.model.Employee; @@ -34,8 +34,9 @@ import com.ecep.contract.model.EmployeeRole; */ @Configuration @EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true) // 开启 @PreAuthorize 等注解 public class SecurityConfig { - + @Lazy @Autowired private EmployeeService employeeService; @@ -45,49 +46,53 @@ public class SecurityConfig { * 启用表单登录和HTTP Basic认证 */ @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) + throws Exception { http - .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/login.html", "/css/**", "/js/**", "/images/**", "/webjars/**", "/login", "/error").permitAll() // 允许静态资源、登录页面和错误页面访问 - .anyRequest().authenticated() // 其他所有请求需要认证 - ) - .csrf(AbstractHttpConfigurer::disable) // 禁用CSRF保护,适合开发环境 - .formLogin(form -> form - .loginPage("/login.html") // 直接使用静态登录页面 - .loginProcessingUrl("/login") // 登录处理URL - .permitAll() // 允许所有人访问登录页面 - .defaultSuccessUrl("/", true) // 登录成功后重定向到首页 - .failureUrl("/login.html?error=true") // 登录失败后重定向到登录页面并显示错误 - .usernameParameter("username") // 用户名参数名 - .passwordParameter("password") // 密码参数名 - ) - .httpBasic(Customizer.withDefaults()) // 启用HTTP Basic认证 - .logout(logout -> logout - .logoutUrl("/logout") // 注销URL - .logoutSuccessUrl("/login?logout=true") // 注销成功后重定向到登录页面 - .invalidateHttpSession(true) // 使会话失效 - .deleteCookies("JSESSIONID") // 删除会话cookie - .permitAll() // 允许所有人访问注销URL - ) - .sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 根据需要创建会话 - .maximumSessions(1) // 每个用户最多1个会话 - .expiredUrl("/login?expired=true") // 会话过期后重定向到登录页面 - ) - .authenticationManager(authenticationManager); // 设置认证管理器 - + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/login.html", "/css/**", "/js/**", "/images/**", "/webjars/**", "/login", + "/error") + .permitAll() // 允许静态资源、登录页面和错误页面访问 + .anyRequest().authenticated() // 其他所有请求需要认证 + ) + .csrf(AbstractHttpConfigurer::disable) // 禁用CSRF保护,适合开发环境 + .formLogin(form -> form + .loginPage("/login.html") // 直接使用静态登录页面 + .loginProcessingUrl("/login") // 登录处理URL + .permitAll() // 允许所有人访问登录页面 + .defaultSuccessUrl("/", true) // 登录成功后重定向到首页 + .failureUrl("/login.html?error=true") // 登录失败后重定向到登录页面并显示错误 + .usernameParameter("username") // 用户名参数名 + .passwordParameter("password") // 密码参数名 + ) + .httpBasic(Customizer.withDefaults()) // 启用HTTP Basic认证 + .logout(logout -> logout + .logoutUrl("/logout") // 注销URL + .logoutSuccessUrl("/login?logout=true") // 注销成功后重定向到登录页面 + .invalidateHttpSession(true) // 使会话失效 + .deleteCookies("JSESSIONID") // 删除会话cookie + .permitAll() // 允许所有人访问注销URL + ) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 根据需要创建会话 + .maximumSessions(1) // 每个用户最多1个会话 + .expiredUrl("/login?expired=true") // 会话过期后重定向到登录页面 + ) + .authenticationManager(authenticationManager); // 设置认证管理器 + return http.build(); } - + /** * 配置AuthenticationManager * 用于处理认证请求 */ @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) + throws Exception { return authenticationConfiguration.getAuthenticationManager(); } - + /** * 配置密码编码器 * BCryptPasswordEncoder是Spring Security推荐的密码编码器 @@ -96,7 +101,7 @@ public class SecurityConfig { public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - + /** * 配置基于EmployeeService的用户认证 * 通过EmployeeService获取员工信息并转换为Spring Security的UserDetails @@ -106,31 +111,32 @@ public class SecurityConfig { return username -> { // 使用EmployeeService根据用户名查找员工 Employee employee = employeeService.findByAccount(username); - + // 如果找不到员工,抛出UsernameNotFoundException异常 if (employee == null) { throw new UsernameNotFoundException("用户不存在: " + username); } - + // 检查员工是否活跃 if (!employee.isActive()) { throw new UsernameNotFoundException("用户已禁用: " + username); } - + // 将员工角色转换为Spring Security的GrantedAuthority - List authorities = getAuthoritiesFromRoles(employee.getRoles()); - + List authorities = getAuthoritiesFromRoles( + employeeService.getRolesByEmployeeId(employee.getId())); + // 创建并返回UserDetails对象 // 注意:根据系统设计,Employee实体中没有密码字段,系统使用IP/MAC绑定认证 // 这里使用密码编码器加密后的固定密码,确保认证流程能够正常工作 return User.builder() - .username(employee.getName()) - .password(passwordEncoder().encode("default123")) // 使用默认密码进行加密 - .authorities(authorities) - .build(); + .username(employee.getName()) + .password(passwordEncoder().encode("default123")) // 使用默认密码进行加密 + .authorities(authorities) + .build(); }; } - + /** * 将EmployeeRole列表转换为Spring Security的GrantedAuthority列表 */ @@ -138,23 +144,24 @@ public class SecurityConfig { if (roles == null || roles.isEmpty()) { return List.of(); } - + // 为每个角色创建GrantedAuthority // 系统管理员拥有ADMIN角色,其他用户拥有USER角色 return roles.stream() - .filter(EmployeeRole::isActive) // 只包含活跃的角色 - .map(role -> { - if (role.isSystemAdministrator()) { - return new SimpleGrantedAuthority("ROLE_ADMIN"); - } else { - return new SimpleGrantedAuthority("ROLE_USER"); - } - }) - .collect(Collectors.toList()); + .filter(EmployeeRole::isActive) // 只包含活跃的角色 + .map(role -> { + if (role.isSystemAdministrator()) { + return new SimpleGrantedAuthority("ROLE_ADMIN"); + } else { + return new SimpleGrantedAuthority("ROLE_USER"); + } + }) + .collect(Collectors.toList()); } - + // Spring Security会自动配置一个使用我们定义的UserDetailsService的DaoAuthenticationProvider // 移除显式的authenticationProvider Bean定义以避免警告 - // 当同时存在AuthenticationProvider和UserDetailsService Bean时,Spring Security会优先使用AuthenticationProvider + // 当同时存在AuthenticationProvider和UserDetailsService Bean时,Spring + // Security会优先使用AuthenticationProvider // 而忽略直接的UserDetailsService,虽然这不影响功能,但会产生警告 } \ No newline at end of file diff --git a/server/src/main/java/com/ecep/contract/controller/IndexController.java b/server/src/main/java/com/ecep/contract/controller/IndexController.java index 4318bc8..6c2feac 100644 --- a/server/src/main/java/com/ecep/contract/controller/IndexController.java +++ b/server/src/main/java/com/ecep/contract/controller/IndexController.java @@ -5,7 +5,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** - * 基础控制器,处理根路径请求和错误处理 + * 基础控制器,处理根路径请求 */ @RestController public class IndexController { @@ -17,12 +17,5 @@ public class IndexController { public ResponseEntity index() { return ResponseEntity.ok("合同管理系统 REST API 服务已启动"); } - - /** - * 处理错误路径请求,避免Whitelabel Error Page显示 - */ - @GetMapping("/error") - public ResponseEntity error() { - return ResponseEntity.status(404).body("请求的资源不存在"); - } + } \ No newline at end of file diff --git a/server/src/main/java/com/ecep/contract/ds/company/tasker/CompanyCompositeUpdateTasker.java b/server/src/main/java/com/ecep/contract/ds/company/tasker/CompanyCompositeUpdateTasker.java index 2668bbc..4d6cef5 100644 --- a/server/src/main/java/com/ecep/contract/ds/company/tasker/CompanyCompositeUpdateTasker.java +++ b/server/src/main/java/com/ecep/contract/ds/company/tasker/CompanyCompositeUpdateTasker.java @@ -2,6 +2,7 @@ package com.ecep.contract.ds.company.tasker; import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,7 +77,7 @@ public class CompanyCompositeUpdateTasker extends Tasker { } catch (Exception e) { cloudRk.setDescription(e.getMessage()); } finally { - cloudRk.setLatestUpdate(Instant.now()); + cloudRk.setLatestUpdate(LocalDateTime.now()); cloudRkService.save(cloudRk); } } diff --git a/server/src/main/java/com/ecep/contract/ds/other/controller/BankController.java b/server/src/main/java/com/ecep/contract/ds/other/controller/BankController.java new file mode 100644 index 0000000..ec8d2d3 --- /dev/null +++ b/server/src/main/java/com/ecep/contract/ds/other/controller/BankController.java @@ -0,0 +1,51 @@ +package com.ecep.contract.ds.other.controller; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.ecep.contract.ds.other.service.BankService; +import com.ecep.contract.model.Bank; + +@RestController +@RequestMapping("/bank") +public class BankController { + @Autowired + private BankService bankService; + + @RequestMapping("/findById") + public Bank findById(Integer id) { + return bankService.findById(id); + } + + @RequestMapping("/list") + public Page list( + Map params, + @RequestParam(defaultValue = "0", name = "page") int pageNumber, + @RequestParam(defaultValue = "10", name = "size") int pageSize) { + Specification spec = null; + Sort sort = Sort.by(Sort.Order.desc("id")); + Pageable pageable = PageRequest.of(pageNumber, pageSize, sort); + // PagedModel pagedModel = new PagedModel<>(); + return bankService.findAll(spec, pageable); + } + + @RequestMapping("/save") + public Bank save(Bank bank) { + return bankService.save(bank); + } + + @RequestMapping("/delete") + public void delete(Integer id) { + Bank bank = bankService.findById(id); + bankService.delete(bank); + } +} diff --git a/server/src/main/java/com/ecep/contract/ds/other/controller/EmployyeController.java b/server/src/main/java/com/ecep/contract/ds/other/controller/EmployeeController.java similarity index 87% rename from server/src/main/java/com/ecep/contract/ds/other/controller/EmployyeController.java rename to server/src/main/java/com/ecep/contract/ds/other/controller/EmployeeController.java index ed3cdcf..1191704 100644 --- a/server/src/main/java/com/ecep/contract/ds/other/controller/EmployyeController.java +++ b/server/src/main/java/com/ecep/contract/ds/other/controller/EmployeeController.java @@ -14,10 +14,11 @@ import org.springframework.web.bind.annotation.RestController; import com.ecep.contract.ds.other.service.EmployeeService; import com.ecep.contract.model.Employee; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @RestController @RequestMapping("/employee") -public class EmployyeController { +public class EmployeeController { @Autowired private EmployeeService employeeService; @@ -29,9 +30,9 @@ public class EmployyeController { @RequestMapping("/list") public Page list( Map params, - @RequestParam(defaultValue = "0") int pageNumber, + @RequestParam(defaultValue = "0", name = "page") int pageNumber, @RequestParam(defaultValue = "10") int pageSize) { - Specification spec = (root, query, cb) -> cb.conjunction(); + Specification spec = null; Sort sort = Sort.by(Sort.Order.desc("id")); Pageable pageable = PageRequest.of(pageNumber, pageSize, sort); return employeeService.findAll(spec, pageable); diff --git a/server/src/main/java/com/ecep/contract/ds/other/service/EmployeeService.java b/server/src/main/java/com/ecep/contract/ds/other/service/EmployeeService.java index bafcafd..1e8a334 100644 --- a/server/src/main/java/com/ecep/contract/ds/other/service/EmployeeService.java +++ b/server/src/main/java/com/ecep/contract/ds/other/service/EmployeeService.java @@ -51,7 +51,8 @@ public class EmployeeService implements IEntityService { // 从员工仓库中根据ID查找员工信息,如果不存在则返回null return employeeRepository.findById(id).orElse(null); } - @Cacheable(key = "'ac-'+#p0") + + @Cacheable(key = "'ac-'+#p0") public Employee findByAccount(String username) { return employeeRepository.findByAccount(username).orElse(null); } @@ -86,7 +87,7 @@ public class EmployeeService implements IEntityService { */ @Caching(evict = { @CacheEvict(key = "#p0.id"), - @CacheEvict(key = "'ac-'+#p0.account"), + @CacheEvict(key = "'ac-'+#p0.account"), @CacheEvict(key = "'name-'+#p0.name"), @CacheEvict(key = "'code-'+#p0.code") }) @@ -155,4 +156,6 @@ public class EmployeeService implements IEntityService { return null; } + + } diff --git a/server/src/main/resources/application.properties b/server/src/main/resources/application.properties index 99fc3d5..e647aac 100644 --- a/server/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -35,13 +35,24 @@ logging.level.org.hibernate.tool.hbm2ddl=DEBUG logging.level.org.springframework.boot.context.metrics.buffering=DEBUG logging.level.com.ecep.contract.manager.SpringApp=DEBUG +# JSON序列化错误调试日志 +logging.level.org.springframework.http.converter=DEBUG +logging.level.com.fasterxml.jackson=TRACE +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.use-key-prefix=true -spring.cache.redis.cache-null-values=false +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 \ No newline at end of file +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