重新初始化项目

This commit is contained in:
2025-08-22 19:55:19 +08:00
commit 65bc67460b
805 changed files with 83402 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
package com.ecep.contract.manager.util;
import java.util.concurrent.*;
import java.util.function.Supplier;
import com.ecep.contract.manager.Desktop;
/**
* 异步任务工具类 - 提供超时处理等增强功能
*/
public class AsyncUtils {
/**
* 执行带超时的CompletableFuture任务
*/
public static <T> CompletableFuture<T> supplyAsyncWithTimeout(Supplier<T> supplier, long timeout, TimeUnit unit) {
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier);
return withTimeout(future, timeout, unit);
}
/**
* 执行带超时的CompletableFuture任务并指定线程池
*/
public static <T> CompletableFuture<T> supplyAsyncWithTimeout(Supplier<T> supplier, Executor executor, long timeout,
TimeUnit unit) {
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier, executor);
return withTimeout(future, timeout, unit);
}
/**
* 为已有的CompletableFuture添加超时处理
*/
public static <T> CompletableFuture<T> withTimeout(CompletableFuture<T> future, long timeout, TimeUnit unit) {
CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
ScheduledExecutorService executor = Desktop.instance.getExecutorService();
ScheduledFuture<?> schedule = executor.schedule(() -> {
TimeoutException timeoutException = new TimeoutException(
"Task timed out after " + timeout + " " + unit.name());
timeoutFuture.completeExceptionally(timeoutException);
}, timeout, unit);
;
return future.applyToEither(timeoutFuture, t -> t)
.whenComplete((t, ex) -> schedule.cancel(true));
}
/**
* 带重试机制的CompletableFuture任务
*/
public static <T> CompletableFuture<T> supplyAsyncWithRetry(Supplier<T> supplier, int maxRetries,
long delayBetweenRetries, TimeUnit unit) {
return supplyAsyncWithRetry(supplier, maxRetries, delayBetweenRetries, unit, null);
}
/**
* 带重试机制的CompletableFuture任务并指定哪些异常需要重试
*/
public static <T> CompletableFuture<T> supplyAsyncWithRetry(
Supplier<T> supplier,
int maxRetries,
long delayBetweenRetries,
TimeUnit unit,
Class<? extends Exception>[] retryableExceptions) {
CompletableFuture<T> resultFuture = new CompletableFuture<>();
// 执行任务的方法
executeWithRetry(supplier, resultFuture, 0, maxRetries, delayBetweenRetries, unit, retryableExceptions);
return resultFuture;
}
private static <T> void executeWithRetry(
Supplier<T> supplier,
CompletableFuture<T> resultFuture,
int currentAttempt,
int maxRetries,
long delayBetweenRetries,
TimeUnit unit,
Class<? extends Exception>[] retryableExceptions) {
CompletableFuture.supplyAsync(supplier)
.thenAccept(resultFuture::complete)
.exceptionally(ex -> {
if (currentAttempt < maxRetries && shouldRetry(ex, retryableExceptions)) {
// 延迟后重试
try {
Thread.sleep(unit.toMillis(delayBetweenRetries));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
resultFuture.completeExceptionally(ie);
return null;
}
// 递归调用,增加尝试次数
executeWithRetry(supplier, resultFuture, currentAttempt + 1,
maxRetries, delayBetweenRetries, unit, retryableExceptions);
} else {
// 达到最大重试次数或异常不可重试完成Future
resultFuture.completeExceptionally(ex);
}
return null;
});
}
private static boolean shouldRetry(Throwable ex, Class<? extends Exception>[] retryableExceptions) {
// 如果未指定重试异常类型,则所有异常都重试
if (retryableExceptions == null || retryableExceptions.length == 0) {
return true;
}
// 检查异常是否属于可重试类型
for (Class<? extends Exception> exceptionClass : retryableExceptions) {
if (exceptionClass.isInstance(ex.getCause())) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,172 @@
package com.ecep.contract.manager.util;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.ss.util.CellRangeAddress;
import java.time.LocalDate;
/**
*
*/
public class ExcelUtils {
public static Row getRow(Sheet sheet, int rowIndex, boolean autoCreate) {
Row row = sheet.getRow(rowIndex);
if (row == null) {
if (autoCreate) {
return sheet.createRow(rowIndex);
}
return null;
}
return row;
}
public static Cell getCell(Sheet sheet, String cellAddress) {
return getCell(sheet, cellAddress, false);
}
public static Cell getCell(Sheet sheet, String cellAddress, boolean autoCreate) {
CellAddress address = new CellAddress(cellAddress);
Row row = getRow(sheet, address.getRow(), autoCreate);
if (row == null) {
return null;
}
return getCell(row, address.getColumn(), autoCreate);
}
public static Cell getCell(Sheet sheet, int rowIndex, int columnIndex, boolean autoCreate) {
Row row = getRow(sheet, rowIndex, autoCreate);
if (row == null) {
return null;
}
return getCell(row, columnIndex, autoCreate);
}
public static Cell getCell(Row row, int columnIndex, boolean autoCreate) {
Cell cell = row.getCell(columnIndex);
if (cell == null) {
if (autoCreate) {
return row.createCell(columnIndex);
}
return null;
}
return cell;
}
public static String getCellValue(Sheet sheet, String cellAddress) {
Cell cell = getCell(sheet, cellAddress);
if (cell == null) {
return null;
}
return cell.getStringCellValue();
}
public static Cell setCellValue(Sheet sheet, String cellAddress, String text) {
CellAddress address = new CellAddress(cellAddress);
return setCellValue(sheet, address.getRow(), address.getColumn(), text);
}
public static Cell setCellValue(Sheet sheet, String cellAddress, double value) {
CellAddress address = new CellAddress(cellAddress);
return setCellValue(sheet, address.getRow(), address.getColumn(), value);
}
public static Cell setCellValue(Sheet sheet, String cellAddress, LocalDate date) {
CellAddress address = new CellAddress(cellAddress);
return setCellValue(sheet, address.getRow(), address.getColumn(), date);
}
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, String text) {
Row row = getRow(sheet, rowIndex, true);
return setCellValue(row, columnIndex, text);
}
public static Cell setCellValue(Row row, int columnIndex, String text) {
assert row != null;
Cell cell = getCell(row, columnIndex, true);
assert cell != null;
cell.setCellValue(text);
return cell;
}
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, double value) {
Row row = getRow(sheet, rowIndex, true);
return setCellValue(row, columnIndex, value);
}
public static Cell setCellValue(Row row, int columnIndex, double value) {
assert row != null;
Cell cell = getCell(row, columnIndex, true);
assert cell != null;
cell.setCellValue(value);
return cell;
}
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, LocalDate date) {
Row row = getRow(sheet, rowIndex, true);
assert row != null;
return setCellValue(row, columnIndex, date);
}
public static Cell setCellValue(Row row, int columnIndex, LocalDate date) {
assert row != null;
Cell cell = getCell(row, columnIndex, true);
assert cell != null;
cell.setCellValue(date);
return cell;
}
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, Double value) {
Row row = getRow(sheet, rowIndex, true);
assert row != null;
Cell cell = getCell(row, columnIndex, true);
assert cell != null;
if (value == null) {
cell.setBlank();
} else {
cell.setCellValue(value);
}
return cell;
}
public static void setBorder(CellStyle cellStyle, BorderStyle borderStyle) {
cellStyle.setBorderTop(borderStyle);
cellStyle.setBorderBottom(borderStyle);
cellStyle.setBorderLeft(borderStyle);
cellStyle.setBorderRight(borderStyle);
}
public static void mergedCells(Sheet sheet, String address) {
CellRangeAddress cellAddresses = CellRangeAddress.valueOf(address);
sheet.addMergedRegion(cellAddresses);
}
public static void mergedCells(Cell cell, int rows, int cols) {
Sheet sheet = cell.getSheet();
CellRangeAddress region = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + rows, cell.getColumnIndex(), cell.getColumnIndex() + cols);
sheet.addMergedRegion(region);
}
public static void copyStyle(Sheet source, String sourceCellAddress, Sheet target, String targetCellAddress) {
Cell sourceCell = getCell(source, sourceCellAddress);
Cell targetCell = getCell(target, targetCellAddress);
copyStyle(sourceCell, targetCell);
}
public static void copyStyle(Sheet source, String sourceCellAddress, Cell targetCell) {
Cell sourceCell = getCell(source, sourceCellAddress);
copyStyle(sourceCell, targetCell);
}
public static void copyStyle(Cell source, Cell target) {
if (source == null) {
return;
}
if (target == null) {
return;
}
target.setCellStyle(source.getCellStyle());
}
}

View File

@@ -0,0 +1,87 @@
package com.ecep.contract.manager.util;
import com.ecep.contract.manager.AppV2;
import com.ecep.contract.manager.SpringApp;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
public class FxmlUtils {
public static FXMLLoader newLoader(String path) {
FXMLLoader loader = new FXMLLoader();
URL location = AppV2.class.getResource(path);
if (location == null) {
throw new RuntimeException("无法找到窗口资源文件 " + path);
}
loader.setLocation(location);
loader.setControllerFactory(SpringApp::getBean);
return loader;
}
public static CompletableFuture<FXMLLoader> newLoaderAsync(String path, java.util.function.Consumer<FXMLLoader> consumer) {
return CompletableFuture.supplyAsync(() -> {
FXMLLoader loader = newLoader(path);
try {
loader.load();
} catch (IOException e) {
throw new RuntimeException("Unable open " + path, e);
}
consumer.accept(loader);
return loader;
}).whenComplete((v, ex) -> {
if (ex != null) {
UITools.showExceptionAndWait("无法打开 " + path + " 界面", ex);
}
});
}
/**
* 异步载入显示界面
*
* @param path fxml文件路径类地址 / 开头 根路径
* @param initializeLoader fxml 文件加载完毕后,回调函数
* @param runLater 在JavaFx线程中执行的回调函数
* @return CompletableFuture
*/
public static CompletableFuture<Void> newLoaderAsyncWithRunLater(
String path,
java.util.function.Consumer<FXMLLoader> initializeLoader,
java.util.function.Consumer<FXMLLoader> runLater
) {
CompletableFuture<Void> future = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
try {
FXMLLoader loader = newLoader(path);
if (initializeLoader != null) {
initializeLoader.accept(loader);
}
loader.load();
Platform.runLater(() -> {
try {
runLater.accept(loader);
future.complete(null);
} catch (Exception e) {
future.completeExceptionally(e);
}
});
} catch (IOException e) {
future.completeExceptionally(new RuntimeException("Unable open " + path, e));
}
}).whenComplete((v, ex) -> {
if (ex != null) {
future.completeExceptionally(ex);
}
});
future.whenComplete((v, ex) -> {
if (ex != null) {
UITools.showExceptionAndWait("无法打开 " + path + " 界面", ex);
}
});
return future;
}
}

View File

@@ -0,0 +1,95 @@
package com.ecep.contract.manager.util;
import com.ecep.contract.manager.SpringApp;
import javafx.beans.property.Property;
import org.hibernate.Hibernate;
import org.springframework.beans.BeanUtils;
import org.springframework.data.repository.CrudRepository;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional;
public class HibernateUtils {
/**
*
* @param bean
* @param repositoryClass
* @return
* @param <T>
* @param <ID>
*/
public static <T, ID> T initialize(
T bean,
Class<? extends CrudRepository<T, ID>> repositoryClass
) {
if (Hibernate.isInitialized(bean)) {
return bean;
}
PropertyDescriptor idDescriptor = BeanUtils.getPropertyDescriptor(bean.getClass(), "id");
if (idDescriptor != null) {
try {
ID id = (ID) idDescriptor.getReadMethod().invoke(bean);
return SpringApp.getBean(repositoryClass).findById(id).orElse(bean);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
return bean;
}
public static <T, ID> Property<T> initialize(
Property<T> property,
Class<? extends CrudRepository<T, ID>> repositoryClass
) {
T value = property.getValue();
if (value == null) {
return property;
}
if (Hibernate.isInitialized(value)) {
return property;
}
try {
PropertyDescriptor idDescriptor = BeanUtils.getPropertyDescriptor(value.getClass(), "id");
ID id = (ID) idDescriptor.getReadMethod().invoke(value);
Optional<T> optional = SpringApp.getBean(repositoryClass).findById(id);
T t = optional.get();
property.setValue(t);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
return property;
}
public static <T, ID> T initializePropertyValue(
Property<T> property,
CrudRepository<T, ID> repository
) {
Property<T> prop = initializeProxyBean(property, repository);
return prop.getValue();
}
public static <T, ID> Property<T> initializeProxyBean(
Property<T> property,
CrudRepository<T, ID> repository
) {
T value = property.getValue();
if (value == null) {
return property;
}
if (Hibernate.isInitialized(value)) {
return property;
}
try {
PropertyDescriptor idDescriptor = BeanUtils.getPropertyDescriptor(value.getClass(), "id");
ID id = (ID) idDescriptor.getReadMethod().invoke(value);
Optional<T> optional = repository.findById(id);
T t = optional.get();
property.setValue(t);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
return property;
}
}

View File

@@ -0,0 +1,51 @@
package com.ecep.contract.manager.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class HttpJsonUtils {
public static void post(String url,
Consumer<Map<String, Object>> data,
Consumer<JsonNode> consumer,
ObjectMapper objectMapper, Proxy proxy) throws IOException {
HashMap<String, Object> params = new HashMap<>();
data.accept(params);
URI uri = URI.create(url);
HttpURLConnection conn = (HttpURLConnection) (proxy == null ? uri.toURL().openConnection() : uri.toURL().openConnection(proxy));
conn.usingProxy();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setDoInput(true);
PrintWriter writer = new PrintWriter(conn.getOutputStream());
objectMapper.writeValue(writer, params);
writer.flush();
conn.connect();
try (InputStream is = conn.getInputStream()) {
JsonNode json = objectMapper.readTree(is);
consumer.accept(json);
}
}
public static void get(String url, Consumer<JsonNode> consumer, ObjectMapper objectMapper, Proxy proxy) throws IOException {
URI uri = URI.create(url);
HttpURLConnection conn = (HttpURLConnection) (proxy == null ? uri.toURL().openConnection() : uri.toURL().openConnection(proxy));
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json, text/plain, */*");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36");
try (InputStream is = conn.getInputStream()) {
JsonNode json = objectMapper.readTree(is);
consumer.accept(json);
}
}
}

View File

@@ -0,0 +1,177 @@
package com.ecep.contract.manager.util;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MyDateTimeUtils {
/**
* 日期 yyyy-MM-dd 的正则表达式
*/
public static final String REGEX_DATE = "\\d{4}-\\d{2}-\\d{2}";
/**
* 默认日期的格式
*/
public static final String DEFAULT_DATE_FORMAT_PATTERN = "yyyy-MM-dd";
/**
* 默认时间的格式
*/
public static final String DEFAULT_TIME_FORMAT_PATTERN = "HH:mm:ss";
/**
* 默认日期时间的格式
*/
public static final String DEFAULT_DATETIME_FORMAT_PATTERN = DEFAULT_DATE_FORMAT_PATTERN + " "
+ DEFAULT_TIME_FORMAT_PATTERN;
/**
* 判断 verifyDate 是否在 startDate 和 endDate 之间,
* 当 graceDays 大于 0 时startDate 往前移 graceDays 天endDate 往后移 graceDays 天
*
* @param verifyDate 判断的日期
* @param startDate 起始日期(包含)
* @param endDate 截至日期(包含)
* @param graceDays 宽限日期
* @return 在 startDate 和 endDate 之间返回true否则返回false
*/
public static boolean dateValidFilter(LocalDate verifyDate, LocalDate startDate, LocalDate endDate, int graceDays) {
// 截至日期 endDate 在 验证日期 verifyDate 前, 表示 verifyDate 已经超出 endDate 范围
if (endDate.isBefore(verifyDate)) {
// 如果允许宽限期
if (graceDays > 0) {
// 截至日期宽限至
LocalDate graceDate = endDate.plusDays(graceDays);
if (graceDate.isBefore(verifyDate)) {
return false;
}
} else {
return false;
}
}
// 起始日期 startDate 在 验证日期 verifyDate 后, 表示 verifyDate 已经超出 startDate 范围
if (startDate.isAfter(verifyDate)) {
// 如果允许宽限期
if (graceDays > 0) {
LocalDate graceDate = startDate.minusDays(graceDays);
if (graceDate.isAfter(verifyDate)) {
return false;
}
} else {
return false;
}
}
return true;
}
/**
* 格式化日期,默认格式 yyyy-MM-dd
*
* @param date 要格式化的日期对象
* @return 格式化后的日期字符串
*/
public static String format(LocalDate date) {
return format(date, DEFAULT_DATE_FORMAT_PATTERN);
}
/**
* 格式化日期,使用指定的格式
*
* @param date 要格式化的日期对象
* @param pattern 日期格式
* @return 格式化后的日期字符串
*/
public static String format(LocalDate date, String pattern) {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
return date.format(fmt);
}
/**
* 格式化时间,默认格式 HH:mm:ss
*
* @param dateTime 要格式化的时间对象
* @return 格式化后的时间字符串
*/
public static String format(LocalDateTime dateTime) {
return format(dateTime, DEFAULT_DATETIME_FORMAT_PATTERN);
}
/**
* 格式化时间,使用指定的格式
*
* @param dateTime 要格式化的时间对象
* @param pattern 时间格式
* @return 格式化后的时间字符串
*/
public static String format(LocalDateTime dateTime, String pattern) {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
return dateTime.format(fmt);
}
/**
* 格式化时间,默认格式 HH:mm:ss
*
* @param time 要格式化的时间对象
* @return 格式化后的时间字符串
*/
public static String format(LocalTime time) {
return format(time, DEFAULT_TIME_FORMAT_PATTERN);
}
/**
* 格式化时间,使用指定的格式
*
* @param time 要格式化的时间对象
* @param pattern 时间格式
* @return 格式化后的时间字符串
*/
public static String format(LocalTime time, String pattern) {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
return time.format(fmt);
}
/**
* 从字符串中提取日期, 匹配日期未 yyyy-MM-dd
*
* @param string 要日期提取的字符串
* @return 提取的日期, 如果没有匹配到则返回null
*/
public static LocalDate pickLocalDate(String string) {
return pickLocalDate(string, REGEX_DATE);
}
/**
* 从字符串中提取日期, 使用正则表达式匹配,如果字符串中有多个匹配,只提取第一个
*
* @param string 要日期提取的字符串
* @param regex 日期的正则表达式
* @return 提取的日期, 如果没有匹配到则返回null
*/
public static LocalDate pickLocalDate(String string, String regex) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(string);
while (matcher.find()) {
String date = matcher.group();
try {
return LocalDate.parse(date);
} catch (Exception ignored) {
}
}
return null;
}
public static String format(long startTime) {
return format(new Date(startTime));
}
public static String format(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat(DEFAULT_DATETIME_FORMAT_PATTERN);
return sdf.format(date);
}
}

View File

@@ -0,0 +1,80 @@
package com.ecep.contract.manager.util;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 我的字符串工具箱
*/
public class MyStringUtils {
/**
* 把 append 拼接到 description 后面,如果 append 不在 description 内时
*
* @param description 被拼接的字符串
* @param append 要拼接的内容
* @return 拼接后的字符串
*/
public static String appendIfAbsent(String description, String append) {
if (StringUtils.hasText(append)) {
if (description != null && !description.contains(append)) {
return description + ", " + description;
}
}
return description;
}
/**
* 检查字符串中的每个字符,是否都是数字 {@link Character#isDigit(char)}
*
* @param str 要检查的字符串
* @return 含有非字符串返回false否则返回true
*/
public static boolean isAllDigit(String str) {
if (!StringUtils.hasText(str)) {
return false;
}
for (byte aByte : str.getBytes()) {
if (!Character.isDigit(aByte)) {
return false;
}
}
return true;
}
public static long toLong(String string, long defaultValue) {
if (!StringUtils.hasText(string)) {
return defaultValue;
}
try {
return Long.parseLong(string);
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* 分割字符串
*
* @param str 要分割的字符串
* @param regex 要分割的字符
* @return 分割后的字符集合
*/
public static List<String> split(String str, String regex) {
List<String> list = new ArrayList<>();
// 未含有字符时,直接返回空集合
if (!StringUtils.hasText(str)) {
return list;
}
// 按照指定字符分割字符串
String[] parts = str.split(regex);
for (String part : parts) {
if (StringUtils.hasText(part)) {
list.add(part);
}
}
return list;
}
}

View File

@@ -0,0 +1,21 @@
package com.ecep.contract.manager.util;
/**
* 数字工具类
*/
public class NumberUtils {
/**
* 判断两个double是否相等
*
* @param a a
* @param b b
* @return boolean
*/
public static boolean equals(double a, double b) {
return Math.abs(a - b) < 0.01;
}
public static boolean equals(float a, float b) {
return Math.abs(a - b) < 0.01;
}
}

View File

@@ -0,0 +1,53 @@
package com.ecep.contract.manager.util;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import java.util.function.Function;
public class SpecificationUtils {
public static <T> Specification<T> and(Specification<T> one, Specification<T> two) {
if (one == null) {
return two;
}
if (two == null) {
return one;
}
return one.and(two);
}
/**
* 逻辑或
*/
public static <T> Specification<T> or(Specification<T> one, Specification<T> two) {
if (one == null) {
return two;
}
if (two == null) {
return one;
}
return one.or(two);
}
/**
* 创建 搜索条件,搜索字符串中按 “ ” 分割多个搜索条件,每个搜索条件使用 and 连接
*
* @param searchText 搜索的字符串
* @param buildSearchSpecification 创建搜索条件的方法
* @param <T> 泛型
* @return 搜索条件为空时返回null否则返回一个Specification对象
*/
public static <T> Specification<T> andWith(String searchText, Function<String, Specification<T>> buildSearchSpecification) {
Specification<T> spec = null;
String[] split = null;
do {
split = StringUtils.split(searchText, " ");
String finalSearchText = split == null ? searchText : split[0];
spec = SpecificationUtils.and(spec, buildSearchSpecification.apply(finalSearchText));
if (split != null) {
searchText = split[1];
}
} while (split != null);
return spec;
}
}

View File

@@ -0,0 +1,97 @@
package com.ecep.contract.manager.util;
import javafx.scene.Node;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.input.PickResult;
import org.springframework.data.domain.Sort;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
public class TableViewUtils {
/**
* 获取 Table 的排序
*/
public static <T> List<Sort.Order> getOrders(TableView<T> table) {
return table.getSortOrder().stream().filter(c -> {
if (!c.isSortable()) {
return false;
}
String id = c.getId();
return id.endsWith("Column");
}).map(c -> {
String id = c.getId();
id = id.replace(table.getId() + "_", "");
id = id.replace("Column", "");
Sort.Direction dir = c.getSortType() == TableColumn.SortType.ASCENDING ? Sort.Direction.ASC : Sort.Direction.DESC;
return new Sort.Order(dir, id);
}).toList();
}
public static <T> void bindDoubleClicked(TableView<T> table, Consumer<T> consumer) {
table.setOnMouseClicked(event -> {
if (event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
PickResult pickResult = event.getPickResult();
Node intersectedNode = pickResult.getIntersectedNode();
if (intersectedNode instanceof TableRow) {
// 鼠标点击的地方没有定义列,只返回了行,跳过不做处理
TableRow<T> row = (TableRow<T>) intersectedNode;
row.getItem();
return;
}
T selectedItem = table.getSelectionModel().getSelectedItem();
if (selectedItem != null) {
try {
consumer.accept(selectedItem);
} catch (UnsupportedOperationException e) {
UITools.showExceptionAndWait("当前操作不支持", e);
} catch (Exception e) {
UITools.showExceptionAndWait(e.getMessage(), e);
}
}
}
});
}
public static <T> void bindEnterPressed(TableView<T> table, Consumer<T> consumer) {
table.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ENTER) {
T selectedItem = table.getSelectionModel().getSelectedItem();
if (selectedItem != null) {
consumer.accept(selectedItem);
}
}
});
}
/**
* 计算表格的可视行数
*
* @param table 表格视图
* @param <T> T
* @return 可视行数
*/
public static <T> int getTableViewVisibleRows(TableView<T> table) {
int rows = 0;
Set<Node> tableRow = table.lookupAll("TableRow");
for (Node node : tableRow) {
if (node instanceof TableRow) {
if (!node.isVisible()) {
continue;
}
rows++;
}
}
return rows;
}
}

View File

@@ -0,0 +1,76 @@
package com.ecep.contract.manager.util;
import java.time.LocalDate;
import java.time.LocalDateTime;
public class TaxRateUtils {
/**
* 13% 一般税率(销售货物)
*/
public final static int TAX_RATE_GENERAL = 13;
/**
* 16% 一般税率(销售货物),2018 年 5 月 1 日 至 2019 年 4 月 1 日
*/
public final static int TAX_RATE_GENERAL_OLD = 16;
/**
* 17% 一般税率(销售货物),2018 年 5 月 1 日之前
*/
public final static int TAX_RATE_GENERAL_OLDEST = 17;
/**
* 9% 交通运输服务
*/
public final static int TAX_RATE_TRANSPORT = 9;
/**
* 10% 交通运输服务2018 年 5 月 1 日 至 2019 年 4 月 1 日
*/
public final static int TAX_RATE_TRANSPORT_OLD = 10;
/**
* 11% 交通运输服务2018 年 5 月 1 日之前
*/
public final static int TAX_RATE_TRANSPORT_OLDEST = 11;
/**
* 6% 技术服务税率
*/
public final static int TAX_RATE_TECH = 6;
/**
* 3% 小规模纳税人税率
*/
public final static int TAX_RATE_SMALL_SCALE = 3;
/**
* 判断日期是否在税率修改未13%之后
*
* @param theDateTime 日期
* @return 是否在13%之后
*/
public static boolean isAfterTaxRateChangeTo13(LocalDateTime theDateTime) {
if (theDateTime == null) {
return false;
}
return isAfterTaxRateChangeTo13(theDateTime.toLocalDate());
}
/**
* 判断日期是否在税率修改未13%之后
*
* @param theDate 日期
* @return 是否在13%之后
*/
public static boolean isAfterTaxRateChangeTo13(LocalDate theDate) {
if (theDate == null) {
return false;
}
return theDate.isAfter(LocalDate.of(2019, 4, 1));
}
public static double toExclusiveTax(double value, double taxRate) {
return value / (100 + taxRate) / 100;
}
public static double toInclusiveTax(double value, double taxRate) {
return value * (100 + taxRate) / 100;
}
}

View File

@@ -0,0 +1,313 @@
package com.ecep.contract.manager.util;
import com.ecep.contract.manager.Desktop;
import com.ecep.contract.manager.ds.other.EntityStringConverter;
import com.ecep.contract.manager.ds.other.model.Entity;
import com.ecep.contract.manager.ui.Message;
import com.ecep.contract.manager.ui.Tasker;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Screen;
import javafx.util.StringConverter;
import org.controlsfx.control.textfield.AutoCompletionBinding;
import org.controlsfx.control.textfield.TextFields;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
public class UITools {
private static final Logger logger = LoggerFactory.getLogger(UITools.class);
public static void showExceptionAndWait(String title, Throwable e) {
String stackTrace = printStackTrace(e);
Label label = new Label("异常堆栈信息如下:");
TextArea textArea = new TextArea(stackTrace);
textArea.setEditable(false);
textArea.setWrapText(true);
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(label, 0, 0);
expContent.add(textArea, 0, 1);
String message = null;
if (e instanceof org.springframework.orm.ObjectOptimisticLockingFailureException) {
message = "锁冲突, 数据可能已经被修改, 请重新加载, 再次重试.";
} else {
message = e.getMessage();
}
showAndWait(Alert.AlertType.ERROR, "程序异常", title, message, expContent);
}
/**
* 显示 Alert 窗口
*
* @param alertType alert 类型
* @param title 标题
* @param header 头
* @param message 消息内容
* @param content 扩张节点
*/
public static CompletableFuture<ButtonType> showAndWait(Alert.AlertType alertType, String title, String header,
String message, Node content) {
CompletableFuture<ButtonType> future = new CompletableFuture<>();
Platform.runLater(() -> {
Alert alert = new Alert(alertType);
if (content != null) {
alert.getDialogPane().setExpandableContent(content);
alert.getDialogPane().setExpanded(true);
}
alert.setTitle(title);
if (header != null)
alert.setHeaderText(header);
if (message != null)
alert.setContentText(message);
Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
alert.getDialogPane().setMaxWidth(visualBounds.getWidth() * 0.9);
alert.showAndWait();
future.complete(alert.getResult());
});
return future;
}
public static CompletableFuture<ButtonType> showConfirmation(String title, String message) {
return showAndWait(Alert.AlertType.CONFIRMATION, "请选择", title, message, null);
}
public static <T> Dialog<T> createDialog() {
Dialog<T> dialog = new Dialog<>();
VBox box = new VBox();
Label headerLabel = new Label();
headerLabel.setId("header");
VBox.setMargin(headerLabel, new Insets(5, 0, 8, 0));
ListView<T> listView = new ListView<>();
listView.setMinHeight(50);
listView.setId("list-view");
ObservableList<T> listViewDataSet = FXCollections.observableArrayList();
listView.setItems(listViewDataSet);
VBox.setVgrow(listView, Priority.ALWAYS);
box.getChildren().setAll(headerLabel, listView);
dialog.getDialogPane().setContent(box);
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.setResizable(true);
dialog.setWidth(600);
dialog.setHeight(500);
return dialog;
}
/**
* 显示一个对话框, 并等待用户关闭对话框
* <p>
* 对话框内嵌一个 ListView可在 consumer 中向 ListView 添加内容
*
* @param title 对话框标题
* @param header 对话框头
* @param consumer 对话框内容生成器
*/
public static void showDialogAndWait(String title, String header, Consumer<ObservableList<String>> consumer) {
Dialog<String> dialog = createDialog();
dialog.setTitle(title);
DialogPane dialogPane = dialog.getDialogPane();
Node content = dialogPane.getContent();
Label headerLabel = (Label) content.lookup("#header");
ListView<String> listView = (ListView<String>) content.lookup("#list-view");
headerLabel.setText(header);
CompletableFuture.runAsync(() -> {
consumer.accept(listView.getItems());
}).exceptionally(e -> {
Platform.runLater(() -> {
listView.getItems().add(e.getMessage());
});
if (logger.isErrorEnabled()) {
logger.error(e.getMessage(), e);
}
return null;
});
dialog.showAndWait();
}
/**
* 显示一个对话框, 并等待用户关闭对话框
*
* @param title 对话框标题
* @param task 任务
* @param init 初始化
*/
public static void showTaskDialogAndWait(String title, javafx.concurrent.Task<?> task,
Consumer<Consumer<Message>> init) {
Dialog<Message> dialog = createDialog();
dialog.getDialogPane().getScene().getStylesheets().add("/ui/dialog.css");
dialog.setTitle(title);
DialogPane dialogPane = dialog.getDialogPane();
VBox box = (VBox) dialogPane.getContent();
Label headerLabel = (Label) box.lookup("#header");
ListView<Message> listView = (ListView<Message>) box.lookup("#list-view");
listView.setCellFactory(param -> new Tasker.MessageListCell());
headerLabel.textProperty().bind(task.titleProperty());
java.util.function.Predicate<Message> consumer = message -> {
if (Platform.isFxApplicationThread()) {
listView.getItems().add(message);
} else {
Platform.runLater(() -> listView.getItems().add(message));
}
return false;
};
if (task instanceof Tasker<?> tasker) {
tasker.setMessageHandler(consumer);
} else {
task.messageProperty().addListener((observable, oldValue, newValue) -> {
consumer.test(Message.info(newValue));
});
}
// 加一个进度条
ProgressBar progressBar = new ProgressBar(0);
progressBar.setPrefHeight(12);
progressBar.setPrefWidth(200);
progressBar.prefWidthProperty().bind(box.widthProperty().subtract(10));
progressBar.setMinHeight(12);
progressBar.setVisible(true);
VBox.setMargin(progressBar, new Insets(6, 0, 6, 0));
Platform.runLater(() -> {
box.getChildren().add(progressBar);
});
// progressBar.disabledProperty().bind(task.runningProperty());
progressBar.visibleProperty().bind(task.runningProperty());
progressBar.progressProperty().bind(task.progressProperty());
if (task instanceof Tasker<?> tasker) {
// 提交任务
Desktop.instance.getExecutorService().submit(tasker);
}
if (init != null) {
init.accept(msg -> consumer.test(msg));
}
dialog.showAndWait();
if (task.isRunning()) {
task.cancel();
}
}
private static String printStackTrace(Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
return sw.toString();
}
public static void showAlertAndWait(String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setContentText(message);
alert.showAndWait();
}
public static <T> AutoCompletionBinding<T> autoCompletion(
TextField textField,
SimpleObjectProperty<T> property,
EntityStringConverter<T> converter) {
return autoCompletion(textField, property, converter::suggest, converter);
}
/**
* 给 TextField 增加一个 自动补全功能
*
* @param textField 要补全的文本框
* @param property 绑定的属性
* @param func 获取补全数据的方法
* @param converter 对象转换器
* @param <T> 对象类型
* @return AutoCompletionBinding
*/
public static <T> AutoCompletionBinding<T> autoCompletion(
TextField textField,
SimpleObjectProperty<T> property,
Function<AutoCompletionBinding.ISuggestionRequest, List<T>> func,
StringConverter<T> converter) {
// 先赋值
textField.textProperty().set(converter.toString(property.get()));
// 监听 property 变化
property.addListener((observable, oldValue, newValue) -> {
textField.textProperty().set(converter.toString(newValue));
});
// 关联一个 自动完成
AutoCompletionBinding<T> completionBinding = TextFields.bindAutoCompletion(textField, p -> {
if (p.isCancelled()) {
return null;
}
try {
return func.apply(p);
} catch (Exception e) {
textField.setText(e.getMessage());
throw new RuntimeException(e);
}
}, converter);
// 设置自动补全选中值到 property
completionBinding.setOnAutoCompleted(event -> {
property.set(event.getCompletion());
});
// fixed 当输入框丢失焦点时输入框内容为空时设置property 为null
textField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
if (!StringUtils.hasText(textField.getText())) {
property.set(null);
}
}
});
return completionBinding;
}
/**
* 确认对话框
*
* @param title
* @param text
* @return
*/
public static boolean showConfirmDialog(String title, String text) {
// TODO
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle(title);
alert.setHeaderText(text);
if (alert.showAndWait().get() == ButtonType.OK) {
return true;
}
return false;
}
}