feat(service): 实现国际化支持并优化Service层

重构文件类型相关Service以支持国际化查询
添加findOneByLang辅助方法统一查询逻辑
实现StringConverter支持UI控件显示
优化缓存配置和查询性能
新增UnitStringConverter和CustomerCatalogStringConverter
完善文档和测试用例
This commit is contained in:
2025-09-24 16:20:49 +08:00
parent 45eed8281f
commit 09b0da498b
32 changed files with 1968 additions and 78 deletions

View File

@@ -342,7 +342,7 @@ public class WebSocketClientService {
try {
sessionConsumer.accept(session);
} finally {
// closeSession(session);
// closeSession(session);vvvv
}
}

View File

@@ -1,29 +1,117 @@
package com.ecep.contract;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
/**
* WebSocket客户端任务接口
* 定义了所有通过WebSocket与服务器通信的任务的通用方法
* 包括任务名称、更新消息、更新标题、更新进度等操作
*/
public interface WebSocketClientTasker {
/**
* 获取任务名称
*
* @return 任务名称
*/
String getTaskName();
/**
* 更新任务执行过程中的消息
*
* @param level 消息级别
* @param message 消息内容
*/
void updateMessage(Level level, String message);
/**
* 更新任务标题
*
* @param title 任务标题
*/
void updateTitle(String title);
/**
* 更新任务进度
*
* @param current 当前进度
* @param total 总进度
*/
void updateProgress(long current, long total);
/**
* 取消任务执行
* 默认实现为空,可由具体任务重写以提供取消逻辑
*/
default void cancelTask() {
// 默认实现为空,由具体任务重写
}
/**
* 调用远程WebSocket任务
*
* @param holder 消息持有者,用于记录任务执行过程中的消息
* @param locale 语言环境
* @param args 任务参数
* @return 任务执行结果
*/
default Object callRemoteTask(MessageHolder holder, Locale locale, Object... args) {
WebSocketClientService webSocketService = SpringApp.getBean(WebSocketClientService.class);
// 检查WebSocket连接是否可用
if (!webSocketService.getOnlineProperty().get()) {
String errorMsg = "WebSocket连接不可用请检查网络连接或服务器状态";
holder.addMessage(Level.SEVERE, errorMsg);
return null;
}
webSocketService.withSession(session -> {
try {
session.submitTask(this, locale, args);
holder.addMessage(Level.INFO, "已提交任务到服务器: " + getTaskName());
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
String errorMsg = "任务提交失败: " + e.getMessage();
holder.addMessage(Level.SEVERE, errorMsg);
throw new RuntimeException("任务提交失败: " + e.getMessage(), e);
}
});
return null;
}
/**
* 异步调用远程WebSocket任务
*
* @param holder 消息持有者,用于记录任务执行过程中的消息
* @param locale 语言环境
* @param args 任务参数
* @return 包含任务执行结果的CompletableFuture
*/
default CompletableFuture<Object> callRemoteTaskAsync(MessageHolder holder, Locale locale, Object... args) {
CompletableFuture<Object> future = new CompletableFuture<>();
try {
// 立即执行callRemoteTask并返回结果
Object result = callRemoteTask(holder, locale, args);
future.complete(result);
} catch (Exception e) {
future.completeExceptionally(e);
}
return future;
}
/**
* 生成唯一的任务ID
*
* @return 唯一的任务ID
*/
default String generateTaskId() {
return UUID.randomUUID().toString();
}
}

View File

@@ -7,6 +7,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import com.ecep.contract.controller.table.cell.CompanyCustomerFileTableTypeTableCell;
import org.springframework.util.StringUtils;
import com.ecep.contract.CustomerFileType;
@@ -59,7 +60,7 @@ public class CustomerTabSkinFile
private CompanyCustomerFileService companyCustomerFileService;
public TableColumn<CompanyCustomerFileViewModel, Number> fileTable_idColumn;
public TableColumn<CompanyCustomerFileViewModel, String> fileTable_typeColumn;
public TableColumn<CompanyCustomerFileViewModel, CustomerFileType> fileTable_typeColumn;
public TableColumn<CompanyCustomerFileViewModel, String> fileTable_filePathColumn;
public TableColumn<CompanyCustomerFileViewModel, String> fileTable_editFilePathColumn;
public TableColumn<CompanyCustomerFileViewModel, LocalDate> fileTable_signDateColumn;
@@ -106,15 +107,15 @@ public class CustomerTabSkinFile
table.disableProperty().bind(viewModel.getPath().isEmpty());
fileTable_idColumn.setCellValueFactory(param -> param.getValue().getId());
CompanyCustomerFileTypeService fileTypeService = getCachedBean(CompanyCustomerFileTypeService.class);
ObservableMap<CustomerFileType, CustomerFileTypeLocalVo> observableMapByLocal = FXCollections
.observableMap(fileTypeService.findAll(getLocale()));
fileTable_typeColumn.setCellValueFactory(param -> Bindings.valueAt(observableMapByLocal,
param.getValue().getType()).map(BaseEnumEntity::getValue));
fileTable_typeColumn.setCellValueFactory(param -> param.getValue().getType());
fileTable_typeColumn.setCellFactory(CompanyCustomerFileTableTypeTableCell.forTableColumn(fileTypeService));
fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFilePath());
fileTable_filePathColumn.setCellFactory(param -> new FileTableFilePathTableCell());
fileTable_editFilePathColumn.setCellValueFactory(param -> param.getValue().getEditFilePath());
fileTable_editFilePathColumn.setCellFactory(param -> new FileTableFilePathTableCell());
fileTable_signDateColumn.setCellValueFactory(param -> param.getValue().getSignDate());
fileTable_validColumn.setEditable(true);
fileTable_validColumn.setCellValueFactory(param -> param.getValue().getValid());
@@ -251,21 +252,6 @@ public class CustomerTabSkinFile
});
}
@Override
public CompanyCustomerFileVo loadRowData(CompanyCustomerFileViewModel row) {
return getCompanyCustomerFileService().findById(row.getId().get());
}
@Override
public CompanyCustomerFileVo saveRowData(CompanyCustomerFileVo entity) {
return getCompanyCustomerFileService().save(entity);
}
@Override
public void deleteRowData(CompanyCustomerFileVo entity) {
getCompanyCustomerFileService().delete(entity);
}
@Override
protected boolean deleteRow(CompanyCustomerFileViewModel row) {
String path = row.getFilePath().get();

View File

@@ -0,0 +1,116 @@
package com.ecep.contract.controller.table.cell;
import static com.ecep.contract.SpringApp.getBean;
import com.ecep.contract.CustomerFileType;
import com.ecep.contract.service.CompanyCustomerFileTypeService;
import com.ecep.contract.vo.CustomerFileTypeLocalVo;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import lombok.NoArgsConstructor;
import java.util.Locale;
/**
* 公司客户文件类型表格单元格,用于在表格中显示客户文件类型信息
* 根据国际化设置显示对应的文件类型名称
*/
@NoArgsConstructor
public class CompanyCustomerFileTableTypeTableCell<T> extends AsyncUpdateTableCell<T, CustomerFileType, CustomerFileTypeLocalVo> {
private CompanyCustomerFileTypeService companyCustomerFileTypeService;
/**
* 创建一个用于表格列的单元格工厂自动获取CompanyCustomerFileTypeService实例
*
* @param <S> 表格行类型
* @return TableCell工厂回调
*/
public static <S> Callback<TableColumn<S, CustomerFileType>, TableCell<S, CustomerFileType>> forTableColumn() {
return forTableColumn(getBean(CompanyCustomerFileTypeService.class));
}
/**
* 创建一个用于表格列的单元格工厂使用提供的CompanyCustomerFileTypeService实例
*
* @param <S> 表格行类型
* @param service CompanyCustomerFileTypeService实例
* @return TableCell工厂回调
*/
public static <S> Callback<TableColumn<S, CustomerFileType>, TableCell<S, CustomerFileType>> forTableColumn(
CompanyCustomerFileTypeService service) {
return param -> new CompanyCustomerFileTableTypeTableCell<>(service);
}
/**
* 使用提供的服务创建单元格实例
*
* @param service CompanyCustomerFileTypeService实例
*/
public CompanyCustomerFileTableTypeTableCell(CompanyCustomerFileTypeService service) {
this.companyCustomerFileTypeService = service;
}
/**
* 获取CompanyCustomerFileTypeService服务实例
* 如果未设置则从Spring容器中获取
*
* @return CompanyCustomerFileTypeService实例
*/
@Override
protected CompanyCustomerFileTypeService getServiceBean() {
if (companyCustomerFileTypeService == null) {
companyCustomerFileTypeService = getBean(CompanyCustomerFileTypeService.class);
}
return companyCustomerFileTypeService;
}
/**
* 初始化文件类型实体
* 根据当前的CustomerFileType枚举值从服务中获取对应的本地化实体
*
* @return 本地化的文件类型实体
*/
@Override
protected CustomerFileTypeLocalVo initialize() {
CustomerFileType item = getItem();
if (item == null) {
return null;
}
// 获取当前用户的语言设置
Locale locale = com.ecep.contract.Desktop.instance.getActiveEmployee().localeProperty().get();
return getServiceBean().findByLocaleAndType(locale, item);
}
/**
* 格式化显示文件类型
* 如果有本地化实体,则显示本地化名称;否则显示枚举名称
*
* @param entity 本地化的文件类型实体
* @return 格式化后的显示文本
*/
@Override
public String format(CustomerFileTypeLocalVo entity) {
if (entity != null && entity.getValue() != null) {
return entity.getValue();
}
CustomerFileType item = getItem();
if (item != null) {
// 根据枚举值返回对应的中文名称
switch (item) {
case General -> {
return "普通文件";
}
case EvaluationForm -> {
return "评估表";
}
default -> {
return item.name();
}
}
}
return "未知类型";
}
}

View File

@@ -13,6 +13,14 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class CustomerCatalogTableCell<T> extends AsyncUpdateTableCell<T, Integer, CustomerCatalogVo> {
public static <S> Callback<TableColumn<S, Integer>, TableCell<S, Integer>> forTableColumn(CustomerCatalogService service) {
return param -> new CustomerCatalogTableCell<>(service);
}
public static <S> Callback<TableColumn<S, Integer>, TableCell<S, Integer>> forTableColumn() {
return forTableColumn(getBean(CustomerCatalogService.class));
}
public CustomerCatalogTableCell(CustomerCatalogService service) {
setService(service);
}
@@ -21,13 +29,10 @@ public class CustomerCatalogTableCell<T> extends AsyncUpdateTableCell<T, Integer
protected CustomerCatalogService getServiceBean() {
return SpringApp.getBean(CustomerCatalogService.class);
}
public static <S> Callback<TableColumn<S, Integer>, TableCell<S, Integer>> forTableColumn() {
return forTableColumn(getBean(CustomerCatalogService.class));
}
public static <S> Callback<TableColumn<S, Integer>, TableCell<S, Integer>> forTableColumn(CustomerCatalogService service) {
return param -> new CustomerCatalogTableCell<S>(service);
@Override
public String format(CustomerCatalogVo entity) {
return entity.getName();
}
}

View File

@@ -0,0 +1,49 @@
package com.ecep.contract.converter;
import com.ecep.contract.service.CustomerCatalogService;
import com.ecep.contract.vo.CustomerCatalogVo;
import javafx.util.StringConverter;
/**
* 客户分类字符串转换器
* 用于在UI组件中显示客户分类信息并支持从字符串还原客户分类对象
*
* @author AI Assistant
* @since 2024-01-01
*/
public class CustomerCatalogStringConverter extends StringConverter<CustomerCatalogVo> {
/** 客户分类服务,用于从字符串查找对应的客户分类对象 */
private final CustomerCatalogService service;
/**
* 构造函数
*
* @param service 客户分类服务实例
*/
public CustomerCatalogStringConverter(CustomerCatalogService service) {
this.service = service;
}
/**
* 将客户分类对象转换为字符串表示
*
* @param object 客户分类对象
* @return 客户分类的名称如果对象为null则返回空字符串
*/
@Override
public String toString(CustomerCatalogVo object) {
return object == null ? "" : object.getName();
}
/**
* 从字符串还原客户分类对象
*
* @param string 客户分类名称
* @return 对应的客户分类对象如果未找到则返回null
*/
@Override
public CustomerCatalogVo fromString(String string) {
return service.findByName(string);
}
}

View File

@@ -0,0 +1,53 @@
package com.ecep.contract.converter;
import org.springframework.stereotype.Component;
import com.ecep.contract.service.UnitService;
import com.ecep.contract.vo.UnitVo;
import javafx.util.StringConverter;
/**
* UnitVo的StringConverter实现用于JavaFX控件中的显示和转换
*/
@Component
public class UnitStringConverter extends StringConverter<UnitVo> {
private final UnitService unitService;
/**
* 构造函数
*
* @param unitService UnitService实例
*/
public UnitStringConverter(UnitService unitService) {
this.unitService = unitService;
}
/**
* 将UnitVo对象转换为字符串
*
* @param object UnitVo对象
* @return 转换后的字符串
*/
@Override
public String toString(UnitVo object) {
if (object == null) {
return "";
}
return object.getName();
}
/**
* 将字符串转换为UnitVo对象
*
* @param string 字符串
* @return 转换后的UnitVo对象
*/
@Override
public UnitVo fromString(String string) {
if (string == null || string.isEmpty()) {
return null;
}
return unitService.findByName(string);
}
}

View File

@@ -27,6 +27,11 @@ import com.ecep.contract.vo.ContractVo;
@Service
@CacheConfig(cacheNames = "customer-file")
public class CompanyCustomerFileService extends QueryService<CompanyCustomerFileVo, CompanyCustomerFileViewModel> {
public File getEvaluationFormTemplate() {
throw new UnsupportedOperationException();
}
@Cacheable
@Override
public CompanyCustomerFileVo findById(Integer id) {
@@ -95,9 +100,6 @@ public class CompanyCustomerFileService extends QueryService<CompanyCustomerFile
return SpringApp.getBean(HolidayService.class).adjustToWorkDay(setupDate.plusDays(-7));
}
public File getEvaluationFormTemplate() {
throw new UnsupportedOperationException();
}
public List<CompanyCustomerFileVo> findAllByCustomer(CompanyCustomerVo companyCustomer) {
return findAll(ParamUtils.builder().equals("customer", companyCustomer).build(), Pageable.unpaged())

View File

@@ -64,9 +64,26 @@ public class CompanyCustomerFileTypeService
return stringConverter;
}
public CustomerFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals("value", string).build(),
/**
* 根据语言标签和参数查找单个 CustomerFileTypeLocalVo 对象
*
* @param locale 语言区域
* @param key 参数键
* @param value 参数值
* @return 查找到的 CustomerFileTypeLocalVo 对象,未找到则返回 null
*/
private CustomerFileTypeLocalVo findOneByLang(Locale locale, String key, Object value) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals(key, value).build(),
Pageable.ofSize(1))
.stream().findFirst().orElse(null);
}
public CustomerFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findOneByLang(locale, "value", string);
}
public CustomerFileTypeLocalVo findByLocaleAndType(Locale locale, CustomerFileType type) {
return findOneByLang(locale, "type", type);
}
}

View File

@@ -9,27 +9,18 @@ import org.springframework.stereotype.Service;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.SpringApp;
import com.ecep.contract.constant.CompanyCustomerConstant;
import com.ecep.contract.task.CompanyCustomerRebuildFilesTasker;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyCustomerViewModel;
import com.ecep.contract.vo.CompanyCustomerVo;
import com.ecep.contract.vo.CompanyVo;
import org.springframework.util.StringUtils;
@Service
public class CompanyCustomerService extends QueryService<CompanyCustomerVo, CompanyCustomerViewModel> {
private File basePath;
public CompanyCustomerVo findByCompany(CompanyVo company) {
Page<CompanyCustomerVo> page = findAll(ParamUtils.equal("company", company.getId()), Pageable.ofSize(1));
if (page.isEmpty()) {
return null;
}
return page.getContent().getFirst();
}
public boolean reBuildingFiles(CompanyCustomerVo companyCustomer, MessageHolder holder) {
throw new UnsupportedOperationException("Unimplemented method 'reBuildingFiles'");
}
public File getBasePath() {
if (basePath == null) {
basePath = new File(
@@ -38,8 +29,107 @@ public class CompanyCustomerService extends QueryService<CompanyCustomerVo, Comp
return basePath;
}
public CompanyCustomerVo findByCompany(CompanyVo company) {
Page<CompanyCustomerVo> page = findAll(ParamUtils.equal("company", company.getId()), Pageable.ofSize(1));
if (page.isEmpty()) {
return null;
}
return page.getContent().getFirst();
}
/**
* 重建客户相关文件
*
* @param companyCustomer 客户对象
* @param holder 消息持有者,用于显示任务执行过程中的消息
* @return 如果文件重建成功则返回true否则返回false
*/
public boolean reBuildingFiles(CompanyCustomerVo companyCustomer, MessageHolder holder) {
// 首先确保客户有有效的路径
if (!makePathAbsent(companyCustomer)) {
holder.addMessage(java.util.logging.Level.WARNING, "无法创建或确认客户路径,文件重建可能失败");
}
// 创建并配置文件重建任务
CompanyCustomerRebuildFilesTasker tasker = new CompanyCustomerRebuildFilesTasker();
tasker.setCompanyCustomer(companyCustomer);
// 显示任务对话框并等待任务完成
UITools.showTaskDialogAndWait("重建客户文件", tasker, null);
// 返回任务执行结果
return tasker.isFilesUpdated();
}
/**
* 检查客户路径是否不存在,如果不存在则创建并设置路径
*
* @param companyCustomer 客户对象
* @return 如果成功创建路径并设置则返回true否则返回false
*/
public boolean makePathAbsent(CompanyCustomerVo companyCustomer) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'makePathAbsent'");
String path = companyCustomer.getPath();
if (StringUtils.hasText(path)) {
File file = new File(path);
if (file.exists()) {
return false;
}
}
File dir = makePath(companyCustomer);
if (dir == null) {
return false;
}
if (!dir.exists()) {
return false;
}
companyCustomer.setPath(dir.getAbsolutePath());
return true;
}
/**
* 为客户创建路径
*
* @param companyCustomer 客户对象
* @return 创建的目录对象如果创建失败则返回null
*/
private File makePath(CompanyCustomerVo companyCustomer) {
File basePath = getBasePath();
Integer companyId = companyCustomer.getCompanyId();
CompanyVo company = SpringApp.getBean(CompanyService.class).findById(companyId);
// 在client端company对象通常已经是完整加载的不需要额外查询
String companyName = company.getName();
// 格式化公司客户ID并创建文件名
String fileName = formatCompanyCustomerId(companyCustomer.getId()) + "-" + escapeFileName(companyName);
File dir = new File(basePath, fileName);
if (!dir.exists()) {
if (!dir.mkdir()) {
return null;
}
}
return dir;
}
/**
* 格式化公司客户ID确保格式一致
*
* @param id 客户ID
* @return 格式化后的ID字符串
*/
private String formatCompanyCustomerId(Integer id) {
// 简单实现实际应与server端的CompanyUtils.formatCompanyVendorId保持一致
return String.format("%06d", id);
}
/**
* 转义文件名,确保合法
*
* @param fileName 原始文件名
* @return 转义后的文件名
*/
private String escapeFileName(String fileName) {
// 替换文件名中的非法字符
return fileName.replaceAll("[/\\:*?\"<>|]", "_");
}
}

View File

@@ -36,13 +36,13 @@ public class CompanyFileTypeService extends QueryService<CompanyFileTypeLocalVo,
return super.findAll();
}
@Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") })
@Override
public CompanyFileTypeLocalVo save(CompanyFileTypeLocalVo entity) {
return super.save(entity);
}
@Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") })
@Override
public void delete(CompanyFileTypeLocalVo entity) {
super.delete(entity);
@@ -59,8 +59,25 @@ public class CompanyFileTypeService extends QueryService<CompanyFileTypeLocalVo,
return stringConverter;
}
public CompanyFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals("value", string).build(), Pageable.ofSize(1))
/**
* 根据语言标签和参数查找单个 CompanyFileTypeLocalVo 对象
*
* @param locale 语言区域
* @param key 参数键
* @param value 参数值
* @return 查找到的 CompanyFileTypeLocalVo 对象,未找到则返回 null
*/
private CompanyFileTypeLocalVo findOneByLang(Locale locale, String key, Object value) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals(key, value).build(),
Pageable.ofSize(1))
.stream().findFirst().orElse(null);
}
public CompanyFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findOneByLang(locale, "value", string);
}
public CompanyFileTypeLocalVo findByLocaleAndType(Locale locale, CompanyFileType type) {
return findOneByLang(locale, "type", type);
}
}

View File

@@ -65,8 +65,25 @@ public class ContractFileTypeService extends QueryService<ContractFileTypeLocalV
return stringConverter;
}
public ContractFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals("value", string).build(), Pageable.ofSize(1))
/**
* 根据语言标签和参数查找单个 ContractFileTypeLocalVo 对象
*
* @param locale 语言区域
* @param key 参数键
* @param value 参数值
* @return 查找到的 ContractFileTypeLocalVo 对象,未找到则返回 null
*/
private ContractFileTypeLocalVo findOneByLang(Locale locale, String key, Object value) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals(key, value).build(),
Pageable.ofSize(1))
.stream().findFirst().orElse(null);
}
public ContractFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findOneByLang(locale, "value", string);
}
public ContractFileTypeLocalVo findByLocaleAndType(Locale locale, ContractFileType type) {
return findOneByLang(locale, "type", type);
}
}

View File

@@ -1,8 +1,15 @@
package com.ecep.contract.service;
import javafx.util.StringConverter;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import com.ecep.contract.converter.CustomerCatalogStringConverter;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.CustomerCatalogViewModel;
import com.ecep.contract.vo.CustomerCatalogVo;
@@ -10,4 +17,38 @@ import com.ecep.contract.vo.CustomerCatalogVo;
@CacheConfig(cacheNames = "customer-catalog")
public class CustomerCatalogService extends QueryService<CustomerCatalogVo, CustomerCatalogViewModel> {
private final CustomerCatalogStringConverter stringConverter = new CustomerCatalogStringConverter(this);
@Override
@Cacheable(key = "#id")
public CustomerCatalogVo findById(Integer id) {
return super.findById(id);
}
public CustomerCatalogVo findByName(String string) {
return findAll(ParamUtils.builder().equals("name", string).build(), Pageable.ofSize(1)).stream().findFirst().orElse(null);
}
@Override
@Cacheable(key = "'all'")
public java.util.List<CustomerCatalogVo> findAll() {
return super.findAll();
}
@Caching(evict = {@CacheEvict(key = "'all'"), @CacheEvict(key = "#entity.id")})
@Override
public CustomerCatalogVo save(CustomerCatalogVo entity) {
return super.save(entity);
}
@Caching(evict = {@CacheEvict(key = "'all'"), @CacheEvict(key = "#entity.id")})
@Override
public void delete(CustomerCatalogVo entity) {
super.delete(entity);
}
@Override
public StringConverter<CustomerCatalogVo> getStringConverter() {
return stringConverter;
}
}

View File

@@ -61,9 +61,25 @@ public class ProjectFileTypeService extends QueryService<ProjectFileTypeLocalVo,
return stringConverter;
}
public ProjectFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals("value", string).build(),
/**
* 根据语言标签和参数查找单个 ProjectFileTypeLocalVo 对象
*
* @param locale 语言区域
* @param key 参数键
* @param value 参数值
* @return 查找到的 ProjectFileTypeLocalVo 对象,未找到则返回 null
*/
private ProjectFileTypeLocalVo findOneByLang(Locale locale, String key, Object value) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals(key, value).build(),
Pageable.ofSize(1))
.stream().findFirst().orElse(null);
}
public ProjectFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findOneByLang(locale, "value", string);
}
public ProjectFileTypeLocalVo findByLocaleAndType(Locale locale, ProjectFileType type) {
return findOneByLang(locale, "type", type);
}
}

View File

@@ -6,6 +6,10 @@ import java.time.LocalDateTime;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -27,8 +31,10 @@ import com.ecep.contract.vo.ProjectTypeVo;
import com.ecep.contract.vo.ProjectVo;
@Service
@CacheConfig(cacheNames = "project")
public class ProjectService extends QueryService<ProjectVo, ProjectViewModel> {
@Cacheable(key = "'name-'+#p0")
public ProjectVo findByName(String name) {
Page<ProjectVo> page = findAll(ParamUtils.builder()
.equals("name", name).build(),
@@ -39,6 +45,7 @@ public class ProjectService extends QueryService<ProjectVo, ProjectViewModel> {
return page.getContent().getFirst();
}
@Cacheable(key = "'code-'+#p0")
public ProjectVo findByCode(String code) {
Page<ProjectVo> page = findAll(ParamUtils.builder()
.equals("code", code).build(),
@@ -198,6 +205,15 @@ public class ProjectService extends QueryService<ProjectVo, ProjectViewModel> {
return page.getContent().getFirst().getCodeSequenceNumber() + 1;
}
/**
* 根据销售类型、代码年份和代码序号查找项目
* 此方法不缓存
*
* @param typeId 销售类型ID
* @param codeYear 代码年份
* @param codeSequenceNumber 代码序号
* @return 查找到的项目对象,未找到则返回 null
*/
public ProjectVo findBySaleTypeAndCodeYearAndCodeSequenceNumber(int typeId, int codeYear,
int codeSequenceNumber) {
Page<ProjectVo> page = findAll(ParamUtils.builder()
@@ -210,4 +226,30 @@ public class ProjectService extends QueryService<ProjectVo, ProjectViewModel> {
}
return page.getContent().getFirst();
}
@Override
@Cacheable(key = "#p0")
public ProjectVo findById(Integer id) {
return super.findById(id);
}
@Override
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code"),
@CacheEvict(key = "'name-'+#p0.name")
})
public ProjectVo save(ProjectVo entity) {
return super.save(entity);
}
@Override
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code"),
@CacheEvict(key = "'name-'+#p0.name")
})
public void delete(ProjectVo entity) {
super.delete(entity);
}
}

View File

@@ -192,6 +192,11 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
}
}
public T findOneByProperty(String propertyName, Object propertyValue) {
return findAll(ParamUtils.builder().equals(propertyName, propertyValue).build(), Pageable.ofSize(1)).stream()
.findFirst().orElse(null);
}
/**
* 异步执行计数操作返回CompletableFuture<Long>类型结果
*

View File

@@ -22,20 +22,21 @@ public class UnitService extends QueryService<UnitVo, UnitViewModel> {
return super.findById(id);
}
@Caching(
put = @CachePut(key = "#entity.id"),
evict = @CacheEvict(key = "'all'"))
@Cacheable(key = "#name")
public UnitVo findByName(String name) {
return findOneByProperty("name", name);
}
@Caching(put = @CachePut(key = "#entity.id"), evict = @CacheEvict(key = "'all'"))
@Override
public UnitVo save(UnitVo entity) {
return super.save(entity);
}
@Caching(
evict = {
@CacheEvict(key = "#entity.id"),
@CacheEvict(key = "'all'")
}
)
@Caching(evict = {
@CacheEvict(key = "#entity.id"),
@CacheEvict(key = "'all'")
})
@Override
public void delete(UnitVo entity) {
super.delete(entity);
@@ -50,4 +51,5 @@ public class UnitService extends QueryService<UnitVo, UnitViewModel> {
public UnitViewModel createNewViewModel() {
return new UnitViewModel();
}
}

View File

@@ -58,8 +58,24 @@ public class VendorFileTypeService extends QueryService<VendorFileTypeLocalVo, V
return stringConverter;
}
public VendorFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals("value", string).build(), Pageable.ofSize(1))
/**
* 根据语言标签和参数查找单个 VendorFileTypeLocalVo 对象
*
* @param locale 语言区域
* @param key 参数键
* @param value 参数值
* @return 查找到的 VendorFileTypeLocalVo 对象,未找到则返回 null
*/
private VendorFileTypeLocalVo findOneByLang(Locale locale, String key, Object value) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals(key, value).build(), Pageable.ofSize(1))
.stream().findFirst().orElse(null);
}
public VendorFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findOneByLang(locale, "value", string);
}
public VendorFileTypeLocalVo findByLocaleAndType(Locale locale, VendorFileType type) {
return findOneByLang(locale, "type", type);
}
}

View File

@@ -4,6 +4,10 @@ import java.io.File;
import java.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@@ -25,6 +29,7 @@ import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vo.ContractVo;
@Service
@CacheConfig(cacheNames = "vendor")
public class VendorService extends QueryService<VendorVo, CompanyVendorViewModel> {
@Autowired
private CompanyService companyService;
@@ -52,6 +57,7 @@ public class VendorService extends QueryService<VendorVo, CompanyVendorViewModel
throw new UnsupportedOperationException("Unimplemented method 'findCatalogById'");
}
@Cacheable(key = "'company-'+#p0.id")
public VendorVo findByCompany(CompanyVo company) {
Page<VendorVo> page = findAll(ParamUtils.equal("company", company.getId()), Pageable.ofSize(1));
if (page.isEmpty()) {
@@ -170,4 +176,28 @@ public class VendorService extends QueryService<VendorVo, CompanyVendorViewModel
}
return dir;
}
@Override
@Cacheable(key = "#p0")
public VendorVo findById(Integer id) {
return super.findById(id);
}
@Override
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'company-'+#p0.companyId")
})
public VendorVo save(VendorVo entity) {
return super.save(entity);
}
@Override
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'company-'+#p0.companyId")
})
public void delete(VendorVo entity) {
super.delete(entity);
}
}

View File

@@ -13,6 +13,9 @@ import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@CacheConfig(cacheNames = "vendor-type")
@@ -59,11 +62,33 @@ public class VendorTypeService extends QueryService<VendorTypeLocalVo, VendorTyp
return stringConverter;
}
public VendorTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals("value", string).build(), Pageable.ofSize(1))
@Cacheable
public Map<VendorType, VendorTypeLocalVo> findAll(Locale locale) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).build(), Pageable.unpaged()).stream()
.collect(Collectors.toMap(VendorTypeLocalVo::getType, Function.identity()));
}
/**
* 根据语言标签和参数查找单个 VendorTypeLocalVo 对象
*
* @param locale 语言区域
* @param key 参数键
* @param value 参数值
* @return 查找到的 VendorTypeLocalVo 对象,未找到则返回 null
*/
private VendorTypeLocalVo findOneByLang(Locale locale, String key, Object value) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals(key, value).build(), Pageable.ofSize(1))
.stream().findFirst().orElse(null);
}
public VendorTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findOneByLang(locale, "value", string);
}
public VendorTypeLocalVo findByLocaleAndType(Locale locale, VendorType type) {
return findOneByLang(locale, "type", type);
}
public StringConverter<VendorType> getTypeStringConverter() {
return new StringConverter<>() {
final VendorTypeService service = SpringApp.getBean(VendorTypeService.class);

View File

@@ -0,0 +1,37 @@
package com.ecep.contract.task;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.WebSocketClientTasker;
import com.ecep.contract.vo.CompanyCustomerVo;
import lombok.Getter;
import lombok.Setter;
/**
* 客户文件重建任务类
* 用于通过WebSocket与服务器通信重建客户相关文件
*/
public class CompanyCustomerRebuildFilesTasker extends Tasker<Object> implements WebSocketClientTasker {
@Getter
@Setter
private CompanyCustomerVo companyCustomer;
@Getter
protected boolean filesUpdated = false;
@Override
public String getTaskName() {
return "CompanyCustomerRebuildFilesTasker";
}
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total);
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("重建客户文件");
return callRemoteTask(holder, getLocale(), companyCustomer.getId());
}
}

View File

@@ -1,13 +1,12 @@
package com.ecep.contract.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.SpringApp;
import com.ecep.contract.WebSocketClientTasker;
import com.ecep.contract.WebSocketClientService;
import com.ecep.contract.service.YongYouU8Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 合同同步任务

View File

@@ -7,85 +7,197 @@ import java.util.function.Consumer;
import com.ecep.contract.constant.ServiceConstant;
/**
* 参数工具类,用于构建查询条件参数
* 提供了一系列静态方法和Builder模式用于创建各种查询条件
*/
public class ParamUtils {
/**
* 创建日期范围查询参数
*
* @param key 查询字段名
* @param begin 开始日期
* @param end 结束日期
* @return 包含日期范围的查询参数Map
*/
public static Map<String, Object> between(String key, LocalDate begin, LocalDate end) {
return Map.of(key, Map.of(
"begin", begin,
"end", end));
}
/**
* 创建等于条件查询参数
*
* @param key 查询字段名
* @param value 查询值
* @return 包含等于条件的查询参数Map
*/
public static Map<String, Object> equal(String key, Object value) {
return Map.of(key, value);
}
/**
* 创建字符串模糊查询参数
*
* @param key 查询字段名
* @param value 模糊查询的字符串值
* @return 包含模糊查询条件的查询参数Map
*/
public static Map<String, Object> like(String key, String value) {
Map<String, Object> params = new HashMap<>();
params.put(key, "%" + value + "%");
return params;
}
/**
* 创建日期模糊查询参数
*
* @param key 查询字段名
* @param value 模糊查询的日期值
* @return 包含日期模糊查询条件的查询参数Map
*/
public static Map<String, Object> like(String key, LocalDate value) {
Map<String, Object> params = new HashMap<>();
params.put(key, "%" + value + "%");
return params;
}
/**
* 创建非空条件查询参数
*
* @param key 查询字段名
* @return 包含非空条件的查询参数Map
*/
public static Map<String, Object> isNotNull(String key) {
Map<String, Object> params = new HashMap<>();
params.put(key, Map.of("isNotNull", true));
return params;
}
/**
* 创建小于条件的日期查询参数
*
* @param key 查询字段名
* @param value 比较的日期值
* @return 包含小于条件的查询参数Map
*/
public static Map<String, Object> lessThan(String key, LocalDate value) {
Map<String, Object> params = new HashMap<>();
params.put(key, Map.of("lessThan", value));
return params;
}
/**
* 创建参数构建器实例
*
* @return Builder实例用于链式构建复杂查询条件
*/
public static Builder builder() {
return new Builder();
}
/**
* 参数构建器类,使用建造者模式构建复杂查询条件
* 支持链式调用,提高代码可读性
*/
public static class Builder {
// 存储构建的查询参数
private Map<String, Object> params = new HashMap<>();
/**
* 私有构造方法,防止外部直接实例化
* 应通过ParamUtils.builder()方法获取实例
*/
private Builder() {
}
/**
* 添加非空条件到构建器
*
* @param key 查询字段名
* @return 当前Builder实例支持链式调用
*/
public Builder isNotNull(String key) {
params.put(key, Map.of("isNotNull", true));
return this;
}
/**
* 添加小于条件到构建器(针对日期类型)
*
* @param key 查询字段名
* @param value 比较的日期值
* @return 当前Builder实例支持链式调用
*/
public Builder lessThan(String key, LocalDate value) {
params.put(key, Map.of("lessThan", value));
return this;
}
/**
* 添加大于条件到构建器(针对日期类型)
*
* @param key 查询字段名
* @param value 比较的日期值
* @return 当前Builder实例支持链式调用
*/
public Builder greaterThan(String key, LocalDate value) {
params.put(key, Map.of("greaterThan", value));
return this;
}
/**
* 添加等于条件到构建器
*
* @param key 查询字段名
* @param value 查询值
* @return 当前Builder实例支持链式调用
*/
public Builder equals(String key, Object value) {
params.put(key, value);
return this;
}
/**
* 添加日期范围条件到构建器
*
* @param key 查询字段名
* @param begin 开始日期
* @param end 结束日期
* @return 当前Builder实例支持链式调用
*/
public Builder between(String key, LocalDate begin, LocalDate end) {
params.put(key, Map.of("begin", begin, "end", end));
return this;
}
/**
* 添加全文搜索条件到构建器
*
* @param searchText 搜索文本
* @return 当前Builder实例支持链式调用
*/
public Builder search(String searchText) {
params.put(ServiceConstant.KEY_SEARCH_TEXT, searchText);
return this;
}
/**
* 构建并返回查询参数Map
*
* @return 包含所有添加条件的查询参数Map
*/
public Map<String, Object> build() {
return params;
}
/**
* 添加AND逻辑条件组到构建器
*
* @param consumer Builder消费者用于构建子条件
* @return 当前Builder实例支持链式调用
*/
public Builder and(Consumer<Builder> consumer) {
Builder builder = new Builder();
consumer.accept(builder);
@@ -93,6 +205,12 @@ public class ParamUtils {
return this;
}
/**
* 添加OR逻辑条件组到构建器
*
* @param consumer Builder消费者用于构建子条件
* @return 当前Builder实例支持链式调用
*/
public Builder or(Consumer<Builder> consumer) {
Builder builder = new Builder();
consumer.accept(builder);