feat: 实现节假日服务功能并更新项目版本

refactor(CloudRkManagerWindowController): 简化任务初始化逻辑
feat(HolidayService): 添加节假日调整和检查功能
feat(HolidayTable): 实现Voable接口并添加toVo方法
feat(HolidayTableRepository): 添加节假日查询方法
docs: 添加服务器端Service类规则文档
build: 更新项目版本至0.0.101-SNAPSHOT
This commit is contained in:
2025-10-10 10:21:51 +08:00
parent e49952a63c
commit 0dcc9236a8
14 changed files with 744 additions and 62 deletions

View File

@@ -60,7 +60,7 @@
- `customer/`: 客户相关业务
- `project/`: 项目相关业务
- `vendor/`: 供应商相关业务
- 每个业务领域包含:`model/`(实体类)、`repository/`(数据访问接口)、`service/`(业务逻辑)、`vo/`(视图对象)
- 每个业务领域包含:`model/`(实体类)、`repository/`(数据访问接口)、`service/`(业务逻辑, 详细规范见 `.trae\rules\server_service_rules.md`)、`tasker/`(任务处理器)、`controller/`(控制器)
- `handler/`: WebSocket处理器
- `service/`: 服务层,包含一些通用服务和任务处理器
- `ui/`: UI相关组件

View File

@@ -0,0 +1,260 @@
# 服务器端 Service 类规则文档
## 1. 概述
本规则文档定义了 Contract-Manager 项目服务器端server模块Service 类的设计规范、实现标准和最佳实践。所有服务器端 Service 类必须严格遵循本规则,以确保代码的一致性、可维护性和性能。
## 2. 目录结构
Service 类按业务领域组织,位于 `server/src/main/java/com/ecep/contract/ds/{业务领域}/service/` 目录下。其中 `{业务领域}` 对应具体的业务模块,如 `customer``contract``company``project``other` 等。
**示例:**
- 客户分类服务:`server/src/main/java/com/ecep/contract/ds/customer/service/CustomerCatalogService.java`
- 员工服务:`server/src/main/java/com/ecep/contract/ds/other/service/EmployeeService.java`
## 3. 命名规范
- **类名**:采用驼峰命名法,首字母大写,以 `Service` 结尾,表示这是一个服务类。
**示例**`CustomerCatalogService``EmployeeService`
## 4. 接口实现
所有业务领域的 Service 类必须实现以下三个核心接口:
### 4.1 IEntityService<T>
提供实体类的基本 CRUD 操作。泛型 `T` 表示实体类类型。
**主要方法:**
- `T getById(Integer id)`:根据 ID 查询实体对象
- `Page<T> findAll(Specification<T> spec, Pageable pageable)`:根据条件和分页参数查询实体列表
- `Specification<T> getSpecification(String searchText)`:构建搜索条件
- `void delete(T entity)`:删除实体
- `T save(T entity)`:保存实体
### 4.2 QueryService<Vo>
提供 VO 对象的查询能力。泛型 `Vo` 表示视图对象类型。
**主要方法:**
- `Vo findById(Integer id)`:根据 ID 查询 VO 对象
- `Page<Vo> findAll(JsonNode paramsNode, Pageable pageable)`:根据 JSON 查询参数和分页条件查询 VO 列表
- `default long count(JsonNode paramsNode)`:根据查询参数统计数据总数
### 4.3 VoableService<M, Vo>
提供从 VO 对象更新实体对象的能力。泛型 `M` 表示实体类类型,`Vo` 表示视图对象类型。
**主要方法:**
- `void updateByVo(M model, Vo vo)`:根据 VO 对象更新实体对象
**实现示例:**
```java
@Lazy
@Service
@CacheConfig(cacheNames = "customer-catalog")
public class CustomerCatalogService implements IEntityService<CustomerCatalog>, QueryService<CustomerCatalogVo>,
VoableService<CustomerCatalog, CustomerCatalogVo> {
// 实现方法...
}
```
## 5. 注解规范
Service 类必须使用以下注解:
### 5.1 类级别注解
- `@Service`:标记这是一个 Spring 服务类,使其能够被自动扫描和管理
- `@Lazy`:延迟加载服务类,提高应用启动性能
- `@CacheConfig(cacheNames = "缓存名称")`:配置缓存名称,缓存名称通常与业务领域相关
**示例**`@CacheConfig(cacheNames = "customer-catalog")``@CacheConfig(cacheNames = "employee")`
### 5.2 方法级别注解
#### 5.2.1 查询方法注解
- `@Cacheable(key = "缓存键")`:将查询结果缓存起来,下次相同查询可以直接从缓存获取
**示例**`@Cacheable(key = "#p0")`(使用方法第一个参数作为缓存键)、`@Cacheable(key = "'code-'+#p0")`(使用前缀+参数值作为缓存键)
#### 5.2.2 数据修改方法注解
- `@Caching(evict = { ... })`:在保存或删除操作时清除相关缓存
**示例**
```java
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code"),
@CacheEvict(key = "'name-'+#p0.name"),
@CacheEvict(key = "'all'")
})
```
- `@Transactional`:标记方法需要在事务中执行,确保数据一致性
**示例**:用于包含多个数据操作的复杂业务方法
## 6. 缓存策略
Service 类必须遵循以下缓存策略:
### 6.1 查询缓存
- 所有 `findById`、`findByCode`、`findByName` 等单条查询方法都应使用 `@Cacheable` 注解缓存结果
- 缓存键设计应具有唯一性和可读性,通常包含参数值和适当的前缀
- 列表查询(如 `findAll`)可以考虑使用 `@Cacheable`,但需谨慎管理缓存失效
### 6.2 缓存清理
- 所有 `save`、`delete` 等修改数据的方法都应使用 `@Caching` 和 `@CacheEvict` 注解清理相关缓存
- 清理缓存时应考虑所有可能影响的查询,确保缓存数据的一致性
## 7. 方法实现规范
### 7.1 IEntityService 方法实现
- `getById`:直接调用 Repository 的 `findById` 方法,返回实体对象
```java
@Override
public CustomerCatalog getById(Integer id) {
return repository.findById(id).orElse(null);
}
```
- `findAll(Specification<T>, Pageable)`:直接调用 Repository 的 `findAll` 方法,返回实体分页对象
```java
@Override
public Page<CustomerCatalog> findAll(Specification<CustomerCatalog> spec, Pageable pageable) {
return repository.findAll(spec, pageable);
}
```
- `save/delete`:调用 Repository 的相应方法,并添加缓存清理注解
```java
@Caching(evict = { ... })
@Override
public CustomerCatalog save(CustomerCatalog catalog) {
return repository.save(catalog);
}
```
### 7.2 QueryService 方法实现
- `findById`:调用 Repository 的 `findById` 方法,然后调用实体类的 `toVo` 方法转换为 VO 对象
```java
@Cacheable(key = "#p0")
@Override
public CustomerCatalogVo findById(Integer id) {
return repository.findById(id).map(CustomerCatalog::toVo).orElse(null);
}
```
- `findAll(JsonNode, Pageable)`:构建 Specification调用 IEntityService 的 `findAll` 方法,然后使用 Stream API 的 `map` 方法将结果转换为 VO 对象
```java
@Override
public Page<CustomerCatalogVo> findAll(JsonNode paramsNode, Pageable pageable) {
Specification<CustomerCatalog> spec = null;
if (paramsNode.has(ServiceConstant.KEY_SEARCH_TEXT)) {
spec = getSpecification(paramsNode.get(ServiceConstant.KEY_SEARCH_TEXT).asText());
}
// 字段等值查询
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "code", "name", "description");
return repository.findAll(spec, pageable).map(CustomerCatalog::toVo);
}
```
### 7.3 VoableService 方法实现
- `updateByVo`:将 VO 对象的属性逐个复制到实体对象,实现 VO 到实体的转换
```java
@Override
public void updateByVo(CustomerCatalog model, CustomerCatalogVo vo) {
// 参数校验
if (model == null) {
throw new ServiceException("实体对象不能为空");
}
if (vo == null) {
throw new ServiceException("VO对象不能为空");
}
// 映射基本属性
model.setCode(vo.getCode());
model.setName(vo.getName());
model.setDescription(vo.getDescription());
}
```
## 8. 依赖注入规范
- Service 类应使用 `@Autowired` 注解注入所需的 Repository 和其他依赖
- 为了避免循环依赖,所有注入的依赖都应使用 `@Lazy` 注解标记为延迟加载
```java
@Lazy
@Autowired
private CustomerCatalogRepository repository;
```
## 9. 查询条件构建
- 使用 `SpecificationUtils` 工具类辅助构建查询条件
- 实现 `getSpecification(String searchText)` 方法,提供基于搜索文本的模糊查询能力
```java
@Override
public Specification<CustomerCatalog> getSpecification(String searchText) {
if (!StringUtils.hasText(searchText)) {
return null;
}
String likeText = "%" + searchText + "%";
return (root, query, builder) -> {
return builder.or(
builder.like(root.get("code"), likeText),
builder.like(root.get("name"), likeText),
builder.like(root.get("description"), likeText));
};
}
```
## 10. 异常处理
- Service 类应适当处理异常,特别是对输入参数的校验
- 对于业务逻辑异常,应抛出 `ServiceException`
```java
if (model == null) {
throw new ServiceException("实体对象不能为空");
}
```
## 11. 最佳实践
1. **VO优先原则**QueryService 接口应使用 VO 类型作为泛型参数,返回 VO 对象而不是实体对象
2. **缓存粒度**:缓存键应设计得足够细粒度,避免缓存过大或频繁失效
3. **事务管理**:包含多个数据操作的方法应使用 `@Transactional` 注解确保事务一致性
4. **延迟加载**:所有依赖都应使用 `@Lazy` 注解,避免循环依赖问题
5. **参数校验**:方法开始时应进行参数校验,确保输入数据的合法性
6. **文档注释**:关键方法应有清晰的 JavaDoc 注释,说明其功能、参数和返回值
7. **代码复用**:尽量使用 SpecificationUtils 等工具类辅助构建查询条件,提高代码复用性
## 12. 不符合规范的Service类示例
以下是不符合规范的Service类实现应避免
```java
// 错误QueryService泛型参数使用实体类型而非VO类型
@Service
@CacheConfig(cacheNames = "company")
public class CompanyService extends EntityService<Company, Integer>
implements IEntityService<Company>, QueryService<Company>, VoableService<Company, CompanyVo> {
// 实现方法...
}
// 错误:未使用缓存注解
@Service
public class ExampleService implements IEntityService<Example>, QueryService<ExampleVo>,
VoableService<Example, ExampleVo> {
// 未使用@Cacheable、@CacheEvict等缓存注解
}
```
## 13. 总结
本规则文档定义了服务器端 Service 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。

View File

@@ -6,12 +6,12 @@
<parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.100-SNAPSHOT</version>
<version>0.0.101-SNAPSHOT</version>
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>client</artifactId>
<version>0.0.100-SNAPSHOT</version>
<version>0.0.101-SNAPSHOT</version>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>
@@ -22,7 +22,7 @@
<dependency>
<groupId>com.ecep.contract</groupId>
<artifactId>common</artifactId>
<version>0.0.100-SNAPSHOT</version>
<version>0.0.101-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>

View File

@@ -65,32 +65,12 @@ public class CloudRkManagerWindowController
getTitle().set("数据源:集团相关方");
}
private void initializeTask(Task<Object> task, String prefix, Consumer<String> consumer) {
task.setOnScheduled(e -> {
consumer.accept("正在从相关方平台同步" + prefix + ",请稍后...");
});
task.setOnRunning(e -> {
consumer.accept("开始" + prefix + "...");
});
task.setOnSucceeded(e -> {
consumer.accept(prefix + "完成...");
});
task.exceptionProperty().addListener((observable, oldValue, newValue) -> {
consumer.accept(newValue.getMessage());
logger.error("{} 发生异常", prefix, newValue);
});
SpringApp.getBean(ScheduledExecutorService.class).submit(task);
consumer.accept("任务已创建...");
}
public void onDataRepairAction(ActionEvent event) {
}
public void onSyncAction(ActionEvent event) {
CloudRkSyncTask task = new CloudRkSyncTask();
UITools.showTaskDialogAndWait("同步数据", task, consumer -> {
initializeTask(task, "同步数据", msg -> consumer.accept(Message.info(msg)));
});
UITools.showTaskDialogAndWait("同步数据", task, null);
}
@Override

View File

@@ -1,15 +1,71 @@
package com.ecep.contract.service;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import com.ecep.contract.WebSocketClientService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 节假日服务类
*
* @author System
*/
@Lazy
@Service
@RequiredArgsConstructor
@Slf4j
public class HolidayService {
@Autowired
private WebSocketClientService webSocketClientService;
@Autowired
protected ObjectMapper objectMapper;
/**
* 调整日期到工作日
*
* @param date 要调整的日期
* @return 调整的日期
*/
public LocalDate adjustToWorkDay(LocalDate date) {
throw new RuntimeException("Not implemented");
if (date == null) {
return null;
}
try {
JsonNode json = webSocketClientService.invoke("holidayService", "adjustToWorkDay", date).get();
if (json != null && !json.isEmpty()) {
return objectMapper.convertValue(json, LocalDate.class);
}
} catch (Exception e) {
log.error("调整日期到工作日失败: {}", e.getMessage(), e);
}
// 如果调用失败,使用客户端本地逻辑作为后备
return adjustToWorkDayLocally(date);
}
/**
* 客户端本地日期调整逻辑,作为服务器调用失败的后备方案
*
* @param date 要调整的日期
* @return 调整的日期
*/
private LocalDate adjustToWorkDayLocally(LocalDate date) {
while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY) {
date = date.plusDays(1);
}
return date;
}
}

View File

@@ -1,5 +1,6 @@
package com.ecep.contract.service;
import java.io.File;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.List;
@@ -7,6 +8,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import com.ecep.contract.SpringApp;
import com.ecep.contract.vo.ContractVo;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@@ -22,13 +25,62 @@ import com.ecep.contract.vo.VendorVo;
@Service
public class VendorFileService extends QueryService<VendorFileVo, CompanyVendorFileViewModel> {
public LocalDate getNextSignDate(VendorVo companyVendor, Consumer<String> state) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getNextSignDate'");
}
public Map<VendorFileType, VendorFileTypeLocal> getFileTypeLocalMap(Locale locale) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getFileTypeLocalMap'");
public LocalDate getNextSignDate(VendorVo vendor, Consumer<String> state) {
LocalDate miniContractDate = LocalDate.of(2022, 1, 1);
// 检索全部合同
ContractService contractService = SpringApp.getBean(ContractService.class);
List<ContractVo> contractList = contractService.findAllByCompanyVendor(vendor, null, null);
if (contractList.isEmpty()) {
state.accept("未发现已登记的合同");
return null;
}
// 检索评价表
List<VendorFileVo> files = findAllByVendorAndType(vendor, VendorFileType.EvaluationForm);
VendorFileVo latestFile = files.stream()
.filter(v -> v.getSignDate() != null && v.isValid())
.max(Comparator.comparing(VendorFileVo::getSignDate))
.orElse(null);
// 没有有效的评价表的评价日期
if (latestFile == null) {
state.accept("未发现有效的评价表");
// 返回最早的合同日期
ContractVo firstContract = contractList.stream()
.filter(v -> v.getSetupDate() != null && !v.getSetupDate().isBefore(miniContractDate))
.min(Comparator.comparing(ContractVo::getSetupDate))
.orElse(null);
if (firstContract == null) {
state.accept("最早的合同不存在?");
return null;
}
LocalDate setupDate = firstContract.getSetupDate();
state.accept("依据合同 " + firstContract.getCode() + " 的日期 " + setupDate + " 推算");
return SpringApp.getBean(HolidayService.class).adjustToWorkDay(setupDate.plusDays(-7));
}
// 检查失效日期起的第一个合同
LocalDate nextInValidDate = latestFile.getSignDate().plusYears(1);
File file = new File(latestFile.getFilePath());
state.accept("依据 " + file.getName() + " 的失效期 " + nextInValidDate + " 检索合同");
List<ContractVo> matchedContracts = contractList.stream()
.filter(v -> v.getSetupDate().isAfter(nextInValidDate)).toList();
// 没有在失效日期后的合同时,使用失效日期
if (matchedContracts.isEmpty()) {
state.accept("未发现失效期 " + nextInValidDate + " 后的合同");
return null;
}
state.accept("发现匹配合同 " + matchedContracts.size() + "");
// 按时间取最早一个
ContractVo firstContract = matchedContracts.stream()
.min(Comparator.comparing(ContractVo::getSetupDate))
.orElse(null);
LocalDate setupDate = firstContract.getSetupDate();
state.accept("匹配失效期 " + nextInValidDate + " 后的第一个合同 " + firstContract.getCode());
state.accept("依据合同 " + firstContract.getCode() + " 的日期 " + setupDate + " 推算");
return SpringApp.getBean(HolidayService.class).adjustToWorkDay(setupDate.plusDays(-7));
}
public void verify(VendorVo companyVendor, LocalDate verifyDate, MessageHolder holder) {

View File

@@ -6,12 +6,12 @@
<parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.100-SNAPSHOT</version>
<version>0.0.101-SNAPSHOT</version>
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>common</artifactId>
<version>0.0.100-SNAPSHOT</version>
<version>0.0.101-SNAPSHOT</version>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>

View File

@@ -7,6 +7,7 @@ import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import com.ecep.contract.util.HibernateProxyUtils;
import com.ecep.contract.vo.HolidayTableVo;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -21,7 +22,7 @@ import lombok.ToString;
@Entity
@Table(name = "HOLIDAY_TABLE")
@ToString
public class HolidayTable {
public class HolidayTable implements Voable<HolidayTableVo> {
@Id
@Column(name = "ID", nullable = false)
@JdbcTypeCode(SqlTypes.DATE)
@@ -47,4 +48,12 @@ public class HolidayTable {
public final int hashCode() {
return HibernateProxyUtils.hashCode(this);
}
@Override
public HolidayTableVo toVo() {
HolidayTableVo vo = new HolidayTableVo();
vo.setId(this.id);
vo.setHoliday(this.holiday);
return vo;
}
}

View File

@@ -0,0 +1,37 @@
package com.ecep.contract.vo;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 节假日表视图对象
*
* @author System
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HolidayTableVo implements Serializable {
private static final long serialVersionUID = 1L;
private LocalDate id;
private boolean holiday;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HolidayTableVo that = (HolidayTableVo) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@@ -10,7 +10,7 @@
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.100-SNAPSHOT</version>
<version>0.0.101-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>server</module>

View File

@@ -6,12 +6,12 @@
<parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.100-SNAPSHOT</version>
<version>0.0.101-SNAPSHOT</version>
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>server</artifactId>
<version>0.0.100-SNAPSHOT</version>
<version>0.0.101-SNAPSHOT</version>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>
@@ -22,7 +22,7 @@
<dependency>
<groupId>com.ecep.contract</groupId>
<artifactId>common</artifactId>
<version>0.0.100-SNAPSHOT</version>
<version>0.0.101-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -21,7 +21,6 @@ import com.ecep.contract.CustomerFileType;
import com.ecep.contract.VendorFileType;
import com.ecep.contract.SpringApp;
import com.ecep.contract.ds.company.CompanyFileUtils;
import com.ecep.contract.ds.other.repository.HolidayTableRepository;
import com.ecep.contract.ds.company.model.Company;
import com.ecep.contract.model.CompanyBasicFile;
import com.ecep.contract.ds.customer.model.CompanyCustomerFile;
@@ -46,11 +45,11 @@ public abstract class CompanyBasicService {
date = date.plusDays(-2);
}
HolidayTableRepository holidayTableRepository = SpringApp.getBean(HolidayTableRepository.class);
//TODO 跳过节假日
HolidayService holidayTableRepository = SpringApp.getBean(HolidayService.class);
// TODO 跳过节假日
int tryDays = 15;
while (tryDays-- > 0) {
HolidayTable holidayTable = holidayTableRepository.findById(date).orElse(null);
HolidayTable holidayTable = holidayTableRepository.getById(date);
if (holidayTable == null) {
// 没有节假日定义,检查是否是工作日
DayOfWeek dayOfWeek = date.getDayOfWeek();
@@ -78,7 +77,6 @@ public abstract class CompanyBasicService {
@Autowired
protected CompanyService companyService;
protected boolean isEditableFile(String fileName) {
return FileUtils.withExtensions(fileName,
FileUtils.XLS, FileUtils.XLSX,
@@ -93,7 +91,8 @@ public abstract class CompanyBasicService {
public abstract <T, F extends CompanyBasicFile<T>, ID> void deleteFile(F file);
protected <T, F extends CompanyBasicFile<T>, ID> boolean fetchDbFiles(List<F> dbFiles, Map<String, F> map, Consumer<String> status) {
protected <T, F extends CompanyBasicFile<T>, ID> boolean fetchDbFiles(List<F> dbFiles, Map<String, F> map,
Consumer<String> status) {
boolean modified = false;
List<File> editFiles = new ArrayList<>();
// 排除掉数据库中重复的
@@ -166,8 +165,8 @@ public abstract class CompanyBasicService {
* @see CompanyCustomerFile
* @see CustomerFileType
*/
protected abstract <T, F extends CompanyBasicFile<T>> boolean fillFileAsDefaultType(F dbFile, File file, Consumer<String> status);
protected abstract <T, F extends CompanyBasicFile<T>> boolean fillFileAsDefaultType(F dbFile, File file,
Consumer<String> status);
protected void moveFileToCompany(Company company, List<File> needMoveToCompanyPath) {
if (needMoveToCompanyPath.isEmpty()) {
@@ -219,8 +218,7 @@ public abstract class CompanyBasicService {
List<File> needMoveToCompanyPath,
List<F> retrieveFiles,
Map<String, F> map,
Consumer<String> status
) {
Consumer<String> status) {
if (!StringUtils.hasText(path)) {
return;
}
@@ -281,7 +279,8 @@ public abstract class CompanyBasicService {
* @param <T> 类型类
* @param <F> 文件类
*/
protected abstract <T, F extends CompanyBasicFile<T>> F fillFileType(File file, List<File> fileList, Consumer<String> status);
protected abstract <T, F extends CompanyBasicFile<T>> F fillFileType(File file, List<File> fileList,
Consumer<String> status);
/**
* @param customerFile 文件对象
@@ -292,7 +291,8 @@ public abstract class CompanyBasicService {
* @param <F> 文件类
* @return true 有修改
*/
protected <T, F extends CompanyBasicFile<T>> boolean fillFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
protected <T, F extends CompanyBasicFile<T>> boolean fillFile(F customerFile, File file, List<File> fileList,
Consumer<String> status) {
String fileName = file.getName();
boolean modified = CompanyFileUtils.fillApplyDateAbsent(file, customerFile, F::getSignDate, F::setSignDate);
// 评估表
@@ -320,7 +320,8 @@ public abstract class CompanyBasicService {
* @param <F> 文件类
* @return true文件对象有修改否则返回false
*/
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsEvaluationFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsEvaluationFile(F customerFile, File file,
List<File> fileList, Consumer<String> status) {
boolean modified = setFileTypeAsEvaluationForm(customerFile);
String fileName = file.getName();
@@ -353,7 +354,8 @@ public abstract class CompanyBasicService {
return modified;
}
private <T, F extends CompanyBasicFile<T>> boolean useAsEditableFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
private <T, F extends CompanyBasicFile<T>> boolean useAsEditableFile(F customerFile, File file, List<File> fileList,
Consumer<String> status) {
if (fileList == null) {
return false;
}
@@ -403,7 +405,8 @@ public abstract class CompanyBasicService {
return false;
}
private <T, F extends CompanyBasicFile<T>> boolean useAsArchiveFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
private <T, F extends CompanyBasicFile<T>> boolean useAsArchiveFile(F customerFile, File file, List<File> fileList,
Consumer<String> status) {
if (fileList == null) {
return false;
}
@@ -449,7 +452,6 @@ public abstract class CompanyBasicService {
return false;
}
/**
* 设置文件类型为表单文件
*
@@ -468,5 +470,4 @@ public abstract class CompanyBasicService {
*/
protected abstract boolean isEvaluationFile(String fileName);
}

View File

@@ -0,0 +1,254 @@
package com.ecep.contract.ds.company.service;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import com.ecep.contract.IEntityService;
import com.ecep.contract.QueryService;
import com.ecep.contract.ds.other.repository.HolidayTableRepository;
import com.ecep.contract.model.HolidayTable;
import com.ecep.contract.service.VoableService;
import com.ecep.contract.vo.HolidayTableVo;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.extern.slf4j.Slf4j;
/**
* 节假日表服务类
*
* @author System
*/
@Service
@CacheConfig(cacheNames = "HolidayTable")
@Slf4j
public class HolidayService implements IEntityService<HolidayTable>, QueryService<HolidayTableVo>, VoableService<HolidayTable, HolidayTableVo> {
@Autowired
private HolidayTableRepository holidayTableRepository;
/**
* 调整日期到工作日
*
* @param date 要调整的日期
* @return 调整的日期
*/
public LocalDate adjustToWorkDay(LocalDate date) {
if (date == null) {
return null;
}
while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY || isHoliday(date)) {
date = date.plusDays(1);
}
return date;
}
/**
* 检查日期是否为节假日
*
* @param date 日期
* @return 是否为节假日
*/
public boolean isHoliday(LocalDate date) {
if (date == null) {
return false;
}
HolidayTable holidayTable = holidayTableRepository.findById(date).orElse(null);
return holidayTable != null && holidayTable.isHoliday();
}
@Override
public HolidayTable getById(Integer id) {
throw new UnsupportedOperationException("HolidayTable uses LocalDate as ID, please use getById(LocalDate id) method instead");
}
/**
* 根据LocalDate类型的ID查询实体
* @param id 实体ID
* @return 实体对象
*/
public HolidayTable getById(LocalDate id) {
if (id == null) {
return null;
}
return holidayTableRepository.findById(id).orElse(null);
}
@Override
public HolidayTable save(HolidayTable entity) {
if (entity == null) {
return null;
}
return holidayTableRepository.save(entity);
}
@Override
public void delete(HolidayTable entity) {
if (entity != null && entity.getId() != null) {
holidayTableRepository.deleteById(entity.getId());
}
}
/**
* 根据LocalDate类型的ID删除实体
* @param id 实体ID
*/
public void deleteById(LocalDate id) {
if (id != null) {
holidayTableRepository.deleteById(id);
}
}
/**
* 获取所有节假日
* @param pageable 分页参数
* @return 分页结果
*/
public Page<HolidayTable> findAll(Pageable pageable) {
return holidayTableRepository.findAll(pageable);
}
@Override
public Page<HolidayTable> findAll(Specification<HolidayTable> spec, Pageable pageable) {
// 由于HolidayTableRepository不支持Specification查询返回所有节假日
return findAll(pageable);
}
@Override
public Specification<HolidayTable> getSpecification(String searchText) {
// 实现根据搜索文本构建规格化查询
return (Root<HolidayTable> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (searchText != null && !searchText.trim().isEmpty()) {
try {
// 尝试将搜索文本解析为日期
LocalDate date = LocalDate.parse(searchText.trim());
predicates.add(criteriaBuilder.equal(root.get("id"), date));
} catch (DateTimeParseException e) {
// 如果不是日期格式,尝试其他搜索方式
// 由于HolidayTable只有id和holiday字段这里无法进行其他字段的模糊搜索
log.warn("Search text '{}' is not a valid date format", searchText);
}
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
@Override
public HolidayTableVo findById(Integer id) {
throw new UnsupportedOperationException("HolidayTable uses LocalDate as ID, please use findById(Object id) instead");
}
@Cacheable(key = "#id", unless = "#result == null")
public HolidayTableVo findById(LocalDate id) {
HolidayTable entity = getById(id);
return entity != null ? entity.toVo() : null;
}
@Override
public Page<HolidayTableVo> findAll(JsonNode paramsNode, Pageable pageable) {
// 实现根据JSON参数过滤节假日
Page<HolidayTable> entityPage;
if (paramsNode == null || paramsNode.isEmpty()) {
// 如果没有参数,返回所有节假日
entityPage = findAll(pageable);
} else {
// 处理参数过滤
Boolean isHoliday = null;
LocalDate startDate = null;
LocalDate endDate = null;
if (paramsNode.has("isHoliday")) {
isHoliday = paramsNode.get("isHoliday").asBoolean();
}
if (paramsNode.has("startDate")) {
try {
startDate = LocalDate.parse(paramsNode.get("startDate").asText());
} catch (Exception e) {
log.warn("Failed to parse startDate: {}", e.getMessage());
}
}
if (paramsNode.has("endDate")) {
try {
endDate = LocalDate.parse(paramsNode.get("endDate").asText());
} catch (Exception e) {
log.warn("Failed to parse endDate: {}", e.getMessage());
}
}
// 根据参数组合选择合适的查询方法
if (isHoliday != null && startDate != null && endDate != null) {
// 组合条件:是否为节假日 + 日期范围
entityPage = holidayTableRepository.findByHolidayAndIdBetween(isHoliday, startDate, endDate, pageable);
} else if (isHoliday != null) {
// 单个条件:是否为节假日
entityPage = holidayTableRepository.findByHoliday(isHoliday, pageable);
} else if (startDate != null && endDate != null) {
// 单个条件:日期范围
entityPage = holidayTableRepository.findByIdBetween(startDate, endDate, pageable);
} else {
// 不满足上述条件,返回所有节假日
entityPage = findAll(pageable);
}
}
return entityPage.map(HolidayTable::toVo);
}
@Override
public long count(JsonNode paramsNode) {
// 简单实现,返回所有节假日数量
return holidayTableRepository.count();
}
@Override
public void updateByVo(HolidayTable model, HolidayTableVo vo) {
if (model == null || vo == null) {
return;
}
// 更新实体属性
model.setHoliday(vo.isHoliday());
save(model);
}
/**
* 创建节假日
*
* @param vo 节假日VO对象
* @return 创建后的节假日VO对象
*/
public HolidayTableVo createByVo(HolidayTableVo vo) {
if (vo == null || vo.getId() == null) {
throw new IllegalArgumentException("HolidayTableVo or ID cannot be null");
}
HolidayTable entity = new HolidayTable();
entity.setId(vo.getId());
entity.setHoliday(vo.isHoliday());
HolidayTable savedEntity = save(entity);
return savedEntity.toVo();
}
}

View File

@@ -1,14 +1,47 @@
package com.ecep.contract.ds.other.repository;
import java.time.LocalDate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import com.ecep.contract.model.HolidayTable;
import java.time.LocalDate;
@Repository
public interface HolidayTableRepository // curd
extends CrudRepository<HolidayTable, LocalDate>, PagingAndSortingRepository<HolidayTable, LocalDate> {
public interface HolidayTableRepository // curd
extends CrudRepository<HolidayTable, LocalDate>, PagingAndSortingRepository<HolidayTable, LocalDate> {
/**
* 根据是否为节假日过滤
*
* @param isHoliday 是否为节假日
* @param pageable 分页参数
* @return 分页结果
*/
Page<HolidayTable> findByHoliday(boolean isHoliday, Pageable pageable);
/**
* 根据日期范围过滤
*
* @param startDate 开始日期
* @param endDate 结束日期
* @param pageable 分页参数
* @return 分页结果
*/
Page<HolidayTable> findByIdBetween(LocalDate startDate, LocalDate endDate, Pageable pageable);
/**
* 根据是否为节假日和日期范围组合过滤
*
* @param isHoliday 是否为节假日
* @param startDate 开始日期
* @param endDate 结束日期
* @param pageable 分页参数
* @return 分页结果
*/
Page<HolidayTable> findByHolidayAndIdBetween(boolean isHoliday, LocalDate startDate, LocalDate endDate,
Pageable pageable);
}