Compare commits
27 Commits
12bbc62ef0
...
v0.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fdb02f3c1 | |||
|
|
80fe2f8cb9 | ||
| 5770a41893 | |||
|
|
fecf693578 | ||
| a919cf0a52 | |||
|
|
3d8168c726 | ||
| 5612d6a24a | |||
|
|
381d149cd2 | ||
| db530c1fed | |||
|
|
3b572491d0 | ||
| 1fe9cf7b77 | |||
|
|
452d525d83 | ||
| 0d21b154ca | |||
|
|
8153df0b90 | ||
| acba4c11d9 | |||
|
|
af349ca4d6 | ||
| d21fba0fc1 | |||
|
|
4b4dcc00b8 | ||
|
|
46f79f8b6b | ||
|
|
2a948a3c59 | ||
|
|
534f8eef81 | ||
|
|
77c53b422e | ||
|
|
50c9a416a6 | ||
|
|
90366671d1 | ||
|
|
f1aede1100 | ||
|
|
8de5f416f8 | ||
|
|
bde63864b1 |
111
.gitea/workflows/ci.yml
Normal file
111
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,111 @@
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
build-deploy:
|
||||
runs-on: act_runner_java
|
||||
if: ${{ github.event.pull_request.merged == true }}
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-21-openjdk
|
||||
steps:
|
||||
- name: Checkout code
|
||||
run: |
|
||||
git clone ${{ github.server_url }}/${{ github.repository }}.git .
|
||||
git checkout ${{ github.sha }}
|
||||
- name: Set up environment
|
||||
run: |
|
||||
echo "PR #${{ github.event.number }} merged into master"
|
||||
echo "Source branch: ${{ github.event.pull_request.head.ref }}"
|
||||
echo "Target branch: ${{ github.event.pull_request.base.ref }}"
|
||||
- name: Run tests
|
||||
run: |
|
||||
echo "Running test suite..."
|
||||
- name: Build project
|
||||
run: |
|
||||
mvn -B -DskipTests clean package source:jar javadoc:jar
|
||||
- name: Deploy to Nexus
|
||||
if: success()
|
||||
run: |
|
||||
if [ -z "${{ secrets.NEXUS_USERNAME }}" ] || [ -z "${{ secrets.NEXUS_PASSWORD }}" ]; then
|
||||
echo "Missing secrets.NEXUS_USERNAME or secrets.NEXUS_PASSWORD"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ~/.m2
|
||||
cat > ~/.m2/settings.xml <<EOF
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
<servers>
|
||||
<server>
|
||||
<id>timi-nexus</id>
|
||||
<username>${{ secrets.NEXUS_USERNAME }}</username>
|
||||
<password>${{ secrets.NEXUS_PASSWORD }}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
EOF
|
||||
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
|
||||
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
|
||||
main_jar="target/${artifact_id}-${version}.jar"
|
||||
sources_jar="target/${artifact_id}-${version}-sources.jar"
|
||||
javadoc_jar="target/${artifact_id}-${version}-javadoc.jar"
|
||||
if [ ! -f "$main_jar" ] || [ ! -f "$sources_jar" ] || [ ! -f "$javadoc_jar" ]; then
|
||||
echo "Missing build artifacts in target"
|
||||
exit 1
|
||||
fi
|
||||
mvn -B deploy:deploy-file \
|
||||
-Dfile="$main_jar" \
|
||||
-Dsources="$sources_jar" \
|
||||
-Djavadoc="$javadoc_jar" \
|
||||
-DpomFile="./pom.xml" \
|
||||
-Durl="https://nexus.imyeyu.com/repository/maven-releases/" \
|
||||
-DrepositoryId="timi-nexus" \
|
||||
-Dhttps.protocols=TLSv1.2 \
|
||||
-Djdk.tls.client.protocols=TLSv1.2
|
||||
- name: Create release
|
||||
if: ${{ success() && startsWith(github.event.pull_request.title, 'v') }}
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }}
|
||||
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||
GITEA_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_TAG: ${{ github.event.pull_request.title }}
|
||||
RELEASE_TARGET: ${{ github.sha }}
|
||||
run: |
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "Missing secrets.RUNNER_TOKEN"
|
||||
exit 1
|
||||
fi
|
||||
api_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases"
|
||||
payload=$(cat <<EOF
|
||||
{
|
||||
"tag_name": "$RELEASE_TAG",
|
||||
"name": "$RELEASE_TAG",
|
||||
"target_commitish": "$RELEASE_TARGET",
|
||||
"draft": false,
|
||||
"prerelease": false
|
||||
}
|
||||
EOF
|
||||
)
|
||||
response=$(curl -sS -X POST "$api_url" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload")
|
||||
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -n 1 | grep -o '[0-9]*')
|
||||
if [ -z "$release_id" ] || echo "$response" | grep -q '"message"'; then
|
||||
echo "Create release failed: $response"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release created: id=$release_id"
|
||||
for asset_path in target/*.jar; do
|
||||
asset_name=$(basename "$asset_path")
|
||||
curl -sS -X POST "$api_url/$release_id/assets?name=$asset_name" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @"$asset_path"
|
||||
done
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
/.claude
|
||||
/AGENTS.md
|
||||
/CLAUDE.md
|
||||
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
|
||||
47
pom.xml
47
pom.xml
@@ -6,14 +6,14 @@
|
||||
|
||||
<groupId>com.imyeyu.java</groupId>
|
||||
<artifactId>timi-java</artifactId>
|
||||
<version>0.0.2</version>
|
||||
<version>0.0.4</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.test.skip>true</maven.test.skip>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.test.skip>true</maven.test.skip>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@@ -27,29 +27,43 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok-maven-plugin</artifactId>
|
||||
<version>1.18.20.0</version>
|
||||
<configuration>
|
||||
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
|
||||
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
|
||||
<addOutputDirectory>false</addOutputDirectory>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<phase>package</phase>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
<goal>delombok</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.36</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.11.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<sourcepath>${project.build.directory}/delombok</sourcepath>
|
||||
<encoding>UTF-8</encoding>
|
||||
<charset>UTF-8</charset>
|
||||
<docencoding>UTF-8</docencoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
@@ -75,6 +89,11 @@
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.40</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
|
||||
@@ -3,15 +3,21 @@ package com.imyeyu.java;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 通用工具
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-02-13 11:39
|
||||
*/
|
||||
public interface TimiJava {
|
||||
|
||||
/** 全 0 UUID */
|
||||
String ZERO_UUID = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
/**
|
||||
* 通用判空
|
||||
* <pre>
|
||||
@@ -66,30 +72,88 @@ public interface TimiJava {
|
||||
return !isEmpty(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为空时取默认值
|
||||
*
|
||||
* @param obj 判空对象
|
||||
* @param defaultObj 默认对象
|
||||
* @return 最终值
|
||||
* @param <T> 对象类型
|
||||
*/
|
||||
static <T> T defaultIfNull(T obj, T defaultObj) {
|
||||
return Objects.requireNonNullElse(obj, defaultObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为空时取默认值
|
||||
*
|
||||
* @param obj 判空对象
|
||||
* @param defaultObj 默认对象
|
||||
* @return 最终值
|
||||
* @param <T> 对象类型
|
||||
*/
|
||||
static <T> T defaultIfEmpty(T obj, T defaultObj) {
|
||||
if (isEmpty(obj)) {
|
||||
return defaultObj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取第一个非空对象
|
||||
*
|
||||
* @param objects 对象列表
|
||||
* @return 最终值
|
||||
* @param <T> 对象类型
|
||||
*/
|
||||
@SafeVarargs
|
||||
static <T> T firstNotNull(T... objects) {
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
if (objects[i] != null) {
|
||||
return objects[i];
|
||||
for (T object : objects) {
|
||||
if (object != null) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取第一个非空对象
|
||||
*
|
||||
* @param objects 对象列表
|
||||
* @return 最终值
|
||||
* @param <T> 对象类型
|
||||
*/
|
||||
@SafeVarargs
|
||||
static <T> T firstNotEmpty(T... objects) {
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
if (TimiJava.isNotEmpty(objects[i])) {
|
||||
return objects[i];
|
||||
for (T object : objects) {
|
||||
if (TimiJava.isNotEmpty(object)) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String toString(Exception e) {
|
||||
/**
|
||||
* 打印异常
|
||||
*
|
||||
* @param e 异常
|
||||
* @return 异常文本
|
||||
*/
|
||||
static String serializeThrowable(Throwable e) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
e.printStackTrace(pw);
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 空安全迭代器,使 for 迭代可以入参为空
|
||||
*
|
||||
* @param iterable 可迭代对象
|
||||
* @return 安全迭代对象
|
||||
* @param <T> 迭代对象类型
|
||||
*/
|
||||
static <T> Iterable<T> safeIterable(Iterable<T> iterable) {
|
||||
return defaultIfNull(iterable, Collections::emptyIterator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,33 @@
|
||||
package com.imyeyu.java.bean;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-07-25 11:04
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class BasePage {
|
||||
|
||||
/** 下标 */
|
||||
protected int index = 0;
|
||||
|
||||
/** 数据量 */
|
||||
protected int size = 16;
|
||||
|
||||
/** 关键字 */
|
||||
protected String keyword;
|
||||
|
||||
public BasePage() {
|
||||
}
|
||||
|
||||
public BasePage(int index, int size) {
|
||||
this.index = index;
|
||||
this.size = size;
|
||||
}
|
||||
protected long size = 16;
|
||||
|
||||
/** 上一页 */
|
||||
public void prev() {
|
||||
index--;
|
||||
}
|
||||
|
||||
/** 下一页 */
|
||||
public void next() {
|
||||
index++;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public void setKeyword(String keyword) {
|
||||
this.keyword = keyword;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.imyeyu.java.bean;
|
||||
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页结果
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-07-25 11:04
|
||||
*/
|
||||
@Data
|
||||
public class BasePageResult<T> {
|
||||
|
||||
/** 总数据量 */
|
||||
@@ -16,15 +20,25 @@ public class BasePageResult<T> {
|
||||
/** 总页数 */
|
||||
protected int pages;
|
||||
|
||||
/** 分页数据列表 */
|
||||
protected List<T> list;
|
||||
|
||||
/**
|
||||
* 获取总数据量
|
||||
* 结果是否为空
|
||||
*
|
||||
* @return 总数据量
|
||||
* @return true 为空
|
||||
*/
|
||||
public long getTotal() {
|
||||
return total;
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结果是否非空
|
||||
*
|
||||
* @return true 为非空
|
||||
*/
|
||||
public boolean isNotEmpty() {
|
||||
return !isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,18 +53,15 @@ public class BasePageResult<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分页数据列表
|
||||
*
|
||||
* @param list 分页数据列表
|
||||
*/
|
||||
public void setList(List<T> list) {
|
||||
this.list = list;
|
||||
if (TimiJava.isNotEmpty(list)) {
|
||||
pages = (int) Math.ceil(1D * total / list.size());
|
||||
}
|
||||
}
|
||||
|
||||
public int getPages() {
|
||||
return pages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ public interface Callback {
|
||||
/** 执行程序 */
|
||||
void handler() throws RuntimeException;
|
||||
|
||||
/**
|
||||
* 非空时执行回调
|
||||
*
|
||||
* @param callback 回调
|
||||
*/
|
||||
static void handle(Callback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
|
||||
@@ -21,7 +21,9 @@ public interface CallbackArg<T> {
|
||||
/**
|
||||
* 非空时执行回调
|
||||
*
|
||||
* @param t 参数
|
||||
* @param callbackArg 回调
|
||||
* @param <T> 入参类型
|
||||
*/
|
||||
static <T> void handle(T t, CallbackArg<T> callbackArg) {
|
||||
if (callbackArg == null) {
|
||||
|
||||
@@ -23,7 +23,10 @@ public interface CallbackArgReturn<T, R> {
|
||||
/**
|
||||
* 非空时执行回调
|
||||
*
|
||||
* @param t 参数
|
||||
* @param callbackArgReturn 回调
|
||||
* @param <T> 入参类型
|
||||
* @param <R> 返回类型
|
||||
*/
|
||||
static <T, R> R handle(T t, CallbackArgReturn<T, R> callbackArgReturn) {
|
||||
if (callbackArgReturn == null) {
|
||||
|
||||
@@ -22,6 +22,7 @@ public interface CallbackReturn<R> {
|
||||
* 非空时执行回调
|
||||
*
|
||||
* @param callbackReturn 回调
|
||||
* @param <R> 返回类型
|
||||
*/
|
||||
static <R> R handle(CallbackReturn<R> callbackReturn) {
|
||||
if (callbackReturn == null) {
|
||||
|
||||
@@ -1,47 +1,74 @@
|
||||
package com.imyeyu.java.bean;
|
||||
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 多语言
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-02-23 11:25
|
||||
*/
|
||||
public enum Language {
|
||||
|
||||
/** 英语 */
|
||||
en_US("English"),
|
||||
|
||||
/** 简中 */
|
||||
zh_CN("简体中文"),
|
||||
|
||||
/** 繁中 */
|
||||
zh_TW("繁体中文"),
|
||||
|
||||
/** 日语 */
|
||||
ja_JP("日本語"),
|
||||
|
||||
/** 韩语 */
|
||||
ko_KR("한국인"),
|
||||
|
||||
/** 俄语 */
|
||||
ru_RU("русский"),
|
||||
|
||||
/** 德语 */
|
||||
de_DE("Deutsch");
|
||||
|
||||
/** 名称 */
|
||||
final String name;
|
||||
|
||||
Language(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@Data
|
||||
public class Language {
|
||||
|
||||
/**
|
||||
* 获取语言名称
|
||||
* 支持语言枚举
|
||||
*
|
||||
* @return 语言名称
|
||||
* @author 夜雨
|
||||
* @since 2025-12-05 14:31
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
public enum Enum {
|
||||
|
||||
/** 英语 */
|
||||
en_US,
|
||||
|
||||
/** 简中 */
|
||||
zh_CN,
|
||||
|
||||
/** 繁中 */
|
||||
zh_TW,
|
||||
|
||||
/** 日语 */
|
||||
ja_JP,
|
||||
|
||||
/** 韩语 */
|
||||
ko_KR,
|
||||
|
||||
/** 俄语 */
|
||||
ru_RU,
|
||||
|
||||
/** 德语 */
|
||||
de_DE;
|
||||
}
|
||||
|
||||
protected String key;
|
||||
|
||||
protected String zhCN;
|
||||
|
||||
protected String zhTW;
|
||||
|
||||
protected String enUS;
|
||||
|
||||
protected String ruRU;
|
||||
|
||||
protected String koKR;
|
||||
|
||||
protected String jaJP;
|
||||
|
||||
protected String deDE;
|
||||
|
||||
/**
|
||||
* 获取指定语言值
|
||||
*
|
||||
* @param language 指定语言
|
||||
* @return 值
|
||||
*/
|
||||
public String getValue(Enum language) {
|
||||
try {
|
||||
return Ref.getFieldValue(this, language.toString().replace("_", ""), String.class);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,52 @@ package com.imyeyu.java.bean;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 多语言映射接口
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-04-03 11:27
|
||||
*/
|
||||
public interface LanguageMapping {
|
||||
|
||||
/**
|
||||
* 添加映射
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
*/
|
||||
void add(String key, String value);
|
||||
|
||||
/**
|
||||
* 是否存在映射
|
||||
*
|
||||
* @param key 键
|
||||
* @return true 为存在
|
||||
*/
|
||||
boolean has(String key);
|
||||
|
||||
/**
|
||||
* 获取映射值
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
String text(String key);
|
||||
|
||||
/**
|
||||
* 获取映射值,不存在时使用默认值
|
||||
*
|
||||
* @param key 键
|
||||
* @param def 默认值
|
||||
* @return 值
|
||||
*/
|
||||
String text(String key, String def);
|
||||
|
||||
/**
|
||||
* 获取映射值并插入参数
|
||||
*
|
||||
* @param key 键
|
||||
* @param argsMap 参数列表
|
||||
* @return 值
|
||||
*/
|
||||
String textArgs(String key, Map<String, Object> argsMap);
|
||||
}
|
||||
|
||||
@@ -3,26 +3,79 @@ package com.imyeyu.java.bean;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 多语言消息接口
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-04-01 10:28
|
||||
*/
|
||||
public interface LanguageMsgMapping<T> {
|
||||
|
||||
/**
|
||||
* 设置消息
|
||||
*
|
||||
* @param msg 消息
|
||||
* @return 原对象
|
||||
*/
|
||||
T msg(String msg);
|
||||
|
||||
/**
|
||||
* 设置消息多语言键
|
||||
*
|
||||
* @param msgKey 键
|
||||
* @return 原对象
|
||||
*/
|
||||
T msgKey(String msgKey);
|
||||
|
||||
/**
|
||||
* 设置消息多语言键并附加插值参数
|
||||
*
|
||||
* @param msgKey 键
|
||||
* @param msgArgs 插值参数
|
||||
* @return 原对象
|
||||
*/
|
||||
T msgKey(String msgKey, Map<String, Object> msgArgs);
|
||||
|
||||
/**
|
||||
* 设置消息
|
||||
*
|
||||
* @param msg 消息
|
||||
*/
|
||||
void setMsg(String msg);
|
||||
|
||||
/**
|
||||
* 获取消息
|
||||
*
|
||||
* @return 消息
|
||||
*/
|
||||
String getMsg();
|
||||
|
||||
/**
|
||||
* 设置消息多语言键
|
||||
*
|
||||
* @param msgKey 键
|
||||
*/
|
||||
void setMsgKey(String msgKey);
|
||||
|
||||
|
||||
/**
|
||||
* 设置消息多语言键并附加插值参数列表
|
||||
*
|
||||
* @param msgKey 键
|
||||
* @param msgArgs 插值参数
|
||||
*/
|
||||
void setMsgKey(String msgKey, Map<String, Object> msgArgs);
|
||||
|
||||
/**
|
||||
* 获取消息多语言键
|
||||
*
|
||||
* @return 键
|
||||
*/
|
||||
String getMsgKey();
|
||||
|
||||
/**
|
||||
* 获取附加插值参数
|
||||
*
|
||||
* @return 附加插值参数列表
|
||||
*/
|
||||
Map<String, Object> getMsgArgs();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.imyeyu.java.bean.timi;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 通用代码(基于 HTTP 代码扩展)
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-05-21 14:32
|
||||
*/
|
||||
@Getter
|
||||
public enum TimiCode {
|
||||
|
||||
// ---------- 200 正常 ----------
|
||||
@@ -74,6 +77,7 @@ public enum TimiCode {
|
||||
/** 服务繁忙 */
|
||||
ERROR_SERVICE_BUSY(50300);
|
||||
|
||||
/** 代码 */
|
||||
final Integer value;
|
||||
|
||||
TimiCode(Integer value) {
|
||||
@@ -108,15 +112,6 @@ public enum TimiCode {
|
||||
return new TimiResponse<>(this).msg(toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码
|
||||
*
|
||||
* @return 代码
|
||||
*/
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代码返回对象
|
||||
*
|
||||
@@ -125,9 +120,9 @@ public enum TimiCode {
|
||||
*/
|
||||
public static TimiCode fromCode(int code) {
|
||||
TimiCode[] codes = values();
|
||||
for (int i = 0; i < codes.length; i++) {
|
||||
if (codes[i].getValue() == code) {
|
||||
return codes[i];
|
||||
for (TimiCode timiCode : codes) {
|
||||
if (timiCode.getValue() == code) {
|
||||
return timiCode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.imyeyu.java.bean.timi;
|
||||
|
||||
import com.imyeyu.java.bean.LanguageMsgMapping;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -13,6 +14,7 @@ import java.util.Map;
|
||||
public class TimiError extends AssertionError implements LanguageMsgMapping<TimiError> {
|
||||
|
||||
/** 代码 */
|
||||
@Getter
|
||||
protected final TimiCode code;
|
||||
|
||||
protected transient String msgKey;
|
||||
@@ -35,15 +37,6 @@ public class TimiError extends AssertionError implements LanguageMsgMapping<Timi
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码
|
||||
*
|
||||
* @return 代码
|
||||
*/
|
||||
public TimiCode getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public TimiResponse<?> toResponse() {
|
||||
return new TimiResponse<>(code).msg(getMessage()).msgKey(msgKey, msgArgs);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.imyeyu.java.bean.timi;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.CallbackReturn;
|
||||
import com.imyeyu.java.bean.LanguageMsgMapping;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -15,6 +16,7 @@ import java.util.Map;
|
||||
public class TimiException extends RuntimeException implements LanguageMsgMapping<TimiException> {
|
||||
|
||||
/** 代码 */
|
||||
@Getter
|
||||
protected final TimiCode code;
|
||||
|
||||
protected transient String msgKey;
|
||||
@@ -48,15 +50,6 @@ public class TimiException extends RuntimeException implements LanguageMsgMappin
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码
|
||||
*
|
||||
* @return 代码
|
||||
*/
|
||||
public TimiCode getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public TimiResponse<?> toResponse() {
|
||||
return new TimiResponse<>(code).msg(getMessage()).msgKey(msgKey, msgArgs);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.imyeyu.java.bean.timi;
|
||||
|
||||
import com.imyeyu.java.bean.LanguageMsgMapping;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
@@ -11,9 +14,12 @@ import java.util.Map;
|
||||
* @author 夜雨
|
||||
* @since 2021-07-01 20:18
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class TimiResponse<T> implements Serializable, LanguageMsgMapping<TimiResponse<T>> {
|
||||
|
||||
/** 代码 */
|
||||
/** 响应代码 */
|
||||
@Setter
|
||||
@Getter
|
||||
protected Integer code;
|
||||
|
||||
/** 消息 */
|
||||
@@ -24,12 +30,10 @@ public class TimiResponse<T> implements Serializable, LanguageMsgMapping<TimiRes
|
||||
protected transient Map<String, Object> msgArgs;
|
||||
|
||||
/** 数据体 */
|
||||
@Setter
|
||||
@Getter
|
||||
protected T data;
|
||||
|
||||
/** 默认构造器 */
|
||||
public TimiResponse() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造器
|
||||
*
|
||||
@@ -85,42 +89,6 @@ public class TimiResponse<T> implements Serializable, LanguageMsgMapping<TimiRes
|
||||
return code.toException(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码
|
||||
*
|
||||
* @return 代码
|
||||
*/
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置代码
|
||||
*
|
||||
* @param code 代码
|
||||
*/
|
||||
public void setCode(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据体
|
||||
*
|
||||
* @return 数据体
|
||||
*/
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据体
|
||||
*
|
||||
* @param data 数据体
|
||||
*/
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加消息(避免和泛型字符串冲突)
|
||||
*
|
||||
|
||||
@@ -3,16 +3,33 @@ package com.imyeyu.java.obs;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 值变更监听器
|
||||
*
|
||||
* @param <T> 监听值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:15
|
||||
*/
|
||||
public interface ChangeListener<T> {
|
||||
|
||||
/**
|
||||
* 处理值变更事件
|
||||
*
|
||||
* @param from 变更前值
|
||||
* @param to 变更后值
|
||||
*/
|
||||
void handler(T from, T to);
|
||||
|
||||
/**
|
||||
* 通知全部监听器处理本次变更
|
||||
*
|
||||
* @param listenerList 监听器列表
|
||||
* @param from 变更前值
|
||||
* @param to 变更后值
|
||||
* @param <T> 监听值类型
|
||||
*/
|
||||
static <T> void notifyListener(List<ChangeListener<T>> listenerList, T from, T to) {
|
||||
for (int i = 0; i < listenerList.size(); i++) {
|
||||
listenerList.get(i).handler(from, to);
|
||||
for (ChangeListener<T> listener : listenerList) {
|
||||
listener.handler(from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,49 @@ package com.imyeyu.java.obs;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 集合元素变更监听器
|
||||
*
|
||||
* @param <E> 元素类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:59
|
||||
*/
|
||||
public interface CollectionChangeListener<E> {
|
||||
|
||||
/**
|
||||
* 集合变更类型
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 19:47
|
||||
*/
|
||||
enum ChangeType {
|
||||
|
||||
/** 新增元素 */
|
||||
ADD,
|
||||
|
||||
/** 删除元素 */
|
||||
REMOVE
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理集合元素变更
|
||||
*
|
||||
* @param type 变更类型
|
||||
* @param e 本次变更元素
|
||||
* @throws RuntimeException 监听器处理失败时抛出
|
||||
*/
|
||||
void handler(ChangeType type, E e) throws RuntimeException;
|
||||
|
||||
/**
|
||||
* 通知全部集合监听器处理本次变更
|
||||
*
|
||||
* @param listenerList 监听器列表
|
||||
* @param type 变更类型
|
||||
* @param element 本次变更元素
|
||||
* @param <E> 元素类型
|
||||
*/
|
||||
static <E> void notifyListener(List<CollectionChangeListener<E>> listenerList, ChangeType type, E element) {
|
||||
for (int i = 0; i < listenerList.size(); i++) {
|
||||
listenerList.get(i).handler(type, element);
|
||||
for (CollectionChangeListener<E> listener : listenerList) {
|
||||
listener.handler(type, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,32 +3,56 @@ package com.imyeyu.java.obs;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 映射变更监听器
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:01
|
||||
*/
|
||||
public interface MapChangeListener<K, V> {
|
||||
|
||||
/**
|
||||
*
|
||||
* 映射变更类型
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 23:51
|
||||
*/
|
||||
enum ChangeType {
|
||||
|
||||
/** 新增键值对 */
|
||||
ADD,
|
||||
|
||||
/** 更新既有键值对 */
|
||||
UPDATE,
|
||||
|
||||
/** 删除键值对 */
|
||||
REMOVE
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理映射变更
|
||||
*
|
||||
* @param type 变更类型
|
||||
* @param key 变更键
|
||||
* @param value 变更值
|
||||
*/
|
||||
void handler(ChangeType type, K key, V value);
|
||||
|
||||
/**
|
||||
* 通知全部映射监听器处理本次变更
|
||||
*
|
||||
* @param listenerList 监听器列表
|
||||
* @param type 变更类型
|
||||
* @param key 变更键
|
||||
* @param value 变更值
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <K, V> void notifyListener(List<MapChangeListener<K, V>> listenerList, ChangeType type, Object key, V value) {
|
||||
for (int i = 0; i < listenerList.size(); i++) {
|
||||
listenerList.get(i).handler(type, (K) key, value);
|
||||
for (MapChangeListener<K, V> kvMapChangeListener : listenerList) {
|
||||
kvMapChangeListener.handler(type, (K) key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,39 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
/**
|
||||
* 可观察对象接口
|
||||
*
|
||||
* @param <T> 可观察值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:07
|
||||
*/
|
||||
public interface Observable<T> {
|
||||
|
||||
/**
|
||||
* 获取当前值
|
||||
*
|
||||
* @return 当前值
|
||||
*/
|
||||
T get();
|
||||
|
||||
/**
|
||||
* 设置当前值
|
||||
*
|
||||
* @param value 新值
|
||||
*/
|
||||
void set(T value);
|
||||
|
||||
/**
|
||||
* 添加值变更监听器
|
||||
*
|
||||
* @param changeListener 值变更监听器
|
||||
*/
|
||||
void addListener(ChangeListener<T> changeListener);
|
||||
|
||||
/**
|
||||
* 移除值变更监听器
|
||||
*
|
||||
* @param changeListener 值变更监听器
|
||||
*/
|
||||
void removeListener(ChangeListener<T> changeListener);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察布尔值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableBoolean extends ObservableObject<Boolean> {
|
||||
|
||||
public ObservableBoolean() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察布尔值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableBoolean(Boolean value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察双精度值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableDouble extends ObservableObject<Double> {
|
||||
|
||||
public ObservableDouble() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定数值创建可观察双精度值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableDouble(Number value) {
|
||||
super(value.doubleValue());
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察浮点值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableFloat extends ObservableObject<Float> {
|
||||
|
||||
public ObservableFloat() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察浮点值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableFloat(Float value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察整型值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:06
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableInteger extends ObservableObject<Integer> {
|
||||
|
||||
public ObservableInteger() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察整型值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableInteger(Integer value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@@ -7,28 +7,56 @@ import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 可观察列表,支持监听元素新增和删除事件
|
||||
*
|
||||
* @param <E> 元素类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 17:51
|
||||
*/
|
||||
public class ObservableList<E> extends ArrayList<E> {
|
||||
|
||||
/** 集合变更监听器列表 */
|
||||
private final List<CollectionChangeListener<E>> changeListenerList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 创建空的可观察列表
|
||||
*/
|
||||
public ObservableList() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定集合创建可观察列表
|
||||
*
|
||||
* @param c 初始化集合
|
||||
*/
|
||||
public ObservableList(Collection<? extends E> c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加集合变更监听器
|
||||
*
|
||||
* @param listener 集合变更监听器
|
||||
*/
|
||||
public void addChangeListener(CollectionChangeListener<E> listener) {
|
||||
changeListenerList.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除集合变更监听器
|
||||
*
|
||||
* @param listener 集合变更监听器
|
||||
*/
|
||||
public void removeChangeListener(CollectionChangeListener<E> listener) {
|
||||
changeListenerList.add(listener);
|
||||
changeListenerList.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增元素并通知监听器
|
||||
*
|
||||
* @param e 待新增元素
|
||||
* @return true 表示列表发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
boolean result = super.add(e);
|
||||
@@ -38,12 +66,24 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定下标新增元素并通知监听器
|
||||
*
|
||||
* @param index 插入下标
|
||||
* @param element 待新增元素
|
||||
*/
|
||||
@Override
|
||||
public void add(int index, E element) {
|
||||
super.add(index, element);
|
||||
CollectionChangeListener.notifyListener(changeListenerList, CollectionChangeListener.ChangeType.ADD, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增元素并逐个通知监听器
|
||||
*
|
||||
* @param c 待新增元素集合
|
||||
* @return true 表示列表发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
boolean result = super.addAll(c);
|
||||
@@ -55,6 +95,13 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定下标开始批量新增元素并逐个通知监听器
|
||||
*
|
||||
* @param index 起始下标
|
||||
* @param c 待新增元素集合
|
||||
* @return true 表示列表发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends E> c) {
|
||||
boolean result = super.addAll(index, c);
|
||||
@@ -66,6 +113,12 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定下标元素并通知监听器
|
||||
*
|
||||
* @param index 待删除元素下标
|
||||
* @return 被删除元素
|
||||
*/
|
||||
@Override
|
||||
public E remove(int index) {
|
||||
E removed = super.remove(index);
|
||||
@@ -75,11 +128,23 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定元素并通知监听器
|
||||
*
|
||||
* @param o 待删除元素
|
||||
* @return true 表示删除成功
|
||||
*/
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return remove(indexOf(o)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除元素并逐个通知监听器
|
||||
*
|
||||
* @param c 待删除元素集合
|
||||
* @return true 表示列表发生变化
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
@@ -92,6 +157,12 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件删除元素
|
||||
*
|
||||
* @param filter 删除条件
|
||||
* @return true 表示至少删除一个元素
|
||||
*/
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super E> filter) {
|
||||
boolean removed = false;
|
||||
@@ -106,6 +177,13 @@ public class ObservableList<E> extends ArrayList<E> {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子列表的可观察副本
|
||||
*
|
||||
* @param fromIndex 起始下标(含)
|
||||
* @param toIndex 结束下标(不含)
|
||||
* @return 子列表副本
|
||||
*/
|
||||
@Override
|
||||
public ObservableList<E> subList(int fromIndex, int toIndex) {
|
||||
List<E> subList = super.subList(fromIndex, toIndex);
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察长整型值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableLong extends ObservableObject<Long> {
|
||||
|
||||
public ObservableLong() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察长整型值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableLong(Long value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@@ -6,21 +6,43 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可观察映射,支持监听键值对新增、更新和删除事件
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:32
|
||||
*/
|
||||
public class ObservableMap<K, V> extends HashMap<K, V> {
|
||||
|
||||
/** 映射变更监听器列表 */
|
||||
private final List<MapChangeListener<K, V>> changeListenerList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 添加映射变更监听器
|
||||
*
|
||||
* @param listener 映射变更监听器
|
||||
*/
|
||||
public void addChangeListener(MapChangeListener<K, V> listener) {
|
||||
changeListenerList.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除映射变更监听器
|
||||
*
|
||||
* @param listener 映射变更监听器
|
||||
*/
|
||||
public void removeChangeListener(MapChangeListener<K, V> listener) {
|
||||
changeListenerList.add(listener);
|
||||
changeListenerList.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入键值对并在必要时通知监听器
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 旧值,不存在则为 null
|
||||
*/
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
V v = super.put(key, value);
|
||||
@@ -32,6 +54,11 @@ public class ObservableMap<K, V> extends HashMap<K, V> {
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量写入键值对
|
||||
*
|
||||
* @param m 待写入映射
|
||||
*/
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
for (Map.Entry<? extends K, ? extends V> item : m.entrySet()) {
|
||||
@@ -39,6 +66,12 @@ public class ObservableMap<K, V> extends HashMap<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定键并通知监听器
|
||||
*
|
||||
* @param key 键
|
||||
* @return 被删除值,不存在则为 null
|
||||
*/
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
V removed = super.remove(key);
|
||||
@@ -48,6 +81,13 @@ public class ObservableMap<K, V> extends HashMap<K, V> {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅当键值都匹配时删除并通知监听器
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return true 表示删除成功
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
|
||||
@@ -1,30 +1,42 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 可观察对象抽象基类
|
||||
*
|
||||
* @param <T> 可观察值类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 15:22
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public abstract class ObservableObject<T> implements Observable<T> {
|
||||
|
||||
/** 值变更监听器列表 */
|
||||
private final List<ChangeListener<T>> changeListenerList = new ArrayList<>();
|
||||
|
||||
/** 当前值 */
|
||||
private T value;
|
||||
|
||||
public ObservableObject() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察对象
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableObject(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final T get() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final void set(T toValue) {
|
||||
final T from = this.value;
|
||||
@@ -35,11 +47,13 @@ public abstract class ObservableObject<T> implements Observable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void addListener(ChangeListener<T> changeListener) {
|
||||
changeListenerList.add(changeListener);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void removeListener(ChangeListener<T> changeListener) {
|
||||
changeListenerList.remove(changeListener);
|
||||
|
||||
@@ -8,21 +8,41 @@ import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 可观察集合,支持监听元素新增和删除事件
|
||||
*
|
||||
* @param <E> 元素类型
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:29
|
||||
*/
|
||||
public class ObservableSet<E> extends HashSet<E> {
|
||||
|
||||
/** 集合变更监听器列表 */
|
||||
private final List<CollectionChangeListener<E>> changeListenerList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 添加集合变更监听器
|
||||
*
|
||||
* @param listener 集合变更监听器
|
||||
*/
|
||||
public void addChangeListener(CollectionChangeListener<E> listener) {
|
||||
changeListenerList.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除集合变更监听器
|
||||
*
|
||||
* @param listener 集合变更监听器
|
||||
*/
|
||||
public void removeChangeListener(CollectionChangeListener<E> listener) {
|
||||
changeListenerList.add(listener);
|
||||
changeListenerList.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增元素并通知监听器
|
||||
*
|
||||
* @param e 待新增元素
|
||||
* @return true 表示集合发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
boolean result = super.add(e);
|
||||
@@ -32,6 +52,12 @@ public class ObservableSet<E> extends HashSet<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增元素并逐个通知监听器
|
||||
*
|
||||
* @param c 待新增元素集合
|
||||
* @return true 表示集合发生变化
|
||||
*/
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
boolean result = super.addAll(c);
|
||||
@@ -43,6 +69,12 @@ public class ObservableSet<E> extends HashSet<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定元素并通知监听器
|
||||
*
|
||||
* @param o 待删除元素
|
||||
* @return true 表示删除成功
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
@@ -53,6 +85,12 @@ public class ObservableSet<E> extends HashSet<E> {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除元素并逐个通知监听器
|
||||
*
|
||||
* @param c 待删除元素集合
|
||||
* @return true 表示集合发生变化
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
@@ -65,6 +103,12 @@ public class ObservableSet<E> extends HashSet<E> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按条件删除元素
|
||||
*
|
||||
* @param filter 删除条件
|
||||
* @return true 表示至少删除一个元素
|
||||
*/
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super E> filter) {
|
||||
boolean removed = false;
|
||||
|
||||
@@ -1,27 +1,48 @@
|
||||
package com.imyeyu.java.obs;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 可观察字符串值
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 18:43
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class ObservableString extends ObservableObject<String> {
|
||||
|
||||
public ObservableString() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定初始值创建可观察字符串值
|
||||
*
|
||||
* @param value 初始值
|
||||
*/
|
||||
public ObservableString(String value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前字符串是否为空或全空白
|
||||
*
|
||||
* @return true 表示空串或全空白
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return get().isEmpty() || get().trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前字符串是否非空且非全空白
|
||||
*
|
||||
* @return true 表示非空且包含非空白字符
|
||||
*/
|
||||
public boolean isNotEmpty() {
|
||||
return !isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前字符串值
|
||||
*
|
||||
* @return 当前字符串值
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return get();
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 反射相关
|
||||
* 反射工具类,提供字段与方法查找、字段读写、字符串转枚举等能力
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2023-05-04 15:05
|
||||
@@ -15,9 +15,9 @@ import java.util.List;
|
||||
public class Ref {
|
||||
|
||||
/**
|
||||
* 获取类字段列表
|
||||
* 获取指定类声明的字段列表(不包含父类)
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param clazz 类型对象
|
||||
* @return 字段列表
|
||||
*/
|
||||
public static List<Field> listFields(Class<?> clazz) {
|
||||
@@ -25,9 +25,9 @@ public class Ref {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类字段列表(包括父类)
|
||||
* 获取指定类及其父类声明的字段列表(直到 {@link Object} 之前)
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param clazz 类型对象
|
||||
* @return 字段列表
|
||||
*/
|
||||
public static List<Field> listAllFields(Class<?> clazz) {
|
||||
@@ -40,20 +40,21 @@ public class Ref {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将外部键名转换为 Java 字段名
|
||||
*
|
||||
* @param keyName
|
||||
* @return
|
||||
* @param keyName 外部键名,支持中划线、下划线和空格分隔
|
||||
* @return 驼峰命名字段名
|
||||
*/
|
||||
public static String getFieldName(String keyName) {
|
||||
String[] splits = {"-", "_", " "};
|
||||
StringBuilder full = new StringBuilder(keyName.substring(0, 1).toUpperCase() + keyName.substring(1));
|
||||
for (int i = 0; i < splits.length; i++) {
|
||||
if (keyName.contains(splits[i])) {
|
||||
// 存在分隔符
|
||||
for (String split : splits) {
|
||||
if (keyName.contains(split)) {
|
||||
// 存在分隔符时,先按分隔符拆词,再按驼峰拼接
|
||||
full.setLength(0);
|
||||
String[] word = keyName.split(splits[i]);
|
||||
for (int j = 0; j < word.length; j++) {
|
||||
full.append(word[j].substring(0, 1).toUpperCase()).append(word[j].substring(1));
|
||||
String[] word = keyName.split(split);
|
||||
for (String s : word) {
|
||||
full.append(s.substring(0, 1).toUpperCase()).append(s.substring(1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -62,11 +63,12 @@ public class Ref {
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射获取对象字段,包括父级类,直至 {@link Object},如果都不存在则返回 null
|
||||
* 反射获取对象字段,包括父级类,直至 {@link Object},如果都不存在则抛出异常
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param fieldName 字段名
|
||||
* @return 字段
|
||||
* @return 字段对象
|
||||
* @throws NullPointerException 找不到字段时抛出异常
|
||||
*/
|
||||
public static Field getField(Class<?> clazz, String fieldName) {
|
||||
do {
|
||||
@@ -78,7 +80,7 @@ public class Ref {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
} while (clazz != Object.class);
|
||||
throw new NullPointerException(String.format("not found field: %s in %s", fieldName, clazz));
|
||||
throw new NullPointerException("not found field: %s in %s".formatted(fieldName, clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +107,7 @@ public class Ref {
|
||||
* @param <T> 返回类型
|
||||
* @return 字段值
|
||||
* @throws IllegalAccessException 反射访问失败
|
||||
* @throws NullPointerException 向上反射直至 {@link Object} 也找不到该字段
|
||||
* @throws NullPointerException 字段对象为空
|
||||
*/
|
||||
public static <T> T getFieldValue(Object object, Field field, Class<? extends T> toClass) throws IllegalAccessException, NullPointerException {
|
||||
if (field == null) {
|
||||
@@ -120,7 +122,7 @@ public class Ref {
|
||||
*
|
||||
* @param objectClass 类
|
||||
* @param fieldName 字段名
|
||||
* @return 字段
|
||||
* @return 字段对象,不存在时返回 null
|
||||
*/
|
||||
public static Field getClassField(Class<?> objectClass, String fieldName) {
|
||||
try {
|
||||
@@ -142,7 +144,7 @@ public class Ref {
|
||||
* @param <T> 值类型
|
||||
* @return 字段值
|
||||
* @throws IllegalAccessException 反射访问失败
|
||||
* @throws NoSuchFieldException 字段不存在
|
||||
* @throws NoSuchFieldException 字段不存在
|
||||
*/
|
||||
public static <T> T getClassFieldValue(Object object, Class<?> objectClass, String fieldName, Class<T> toClass) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field field = getClassField(objectClass, fieldName);
|
||||
@@ -160,16 +162,19 @@ public class Ref {
|
||||
* @param fieldName 字段名
|
||||
* @param value 字段值
|
||||
* @throws IllegalAccessException 反射访问失败
|
||||
* @throws NoSuchFieldException 向上反射直至 {@link Object} 也找不到该字段
|
||||
*/
|
||||
public static void setFieldValue(Object object, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field field = getField(object.getClass(), fieldName);
|
||||
if (field == null) {
|
||||
throw new NoSuchFieldException("not found " + fieldName + " field in " + object.getClass().getSimpleName());
|
||||
}
|
||||
setFieldValue(object, field, value);
|
||||
public static void setFieldValue(Object object, String fieldName, Object value) throws IllegalAccessException {
|
||||
setFieldValue(object, getField(object.getClass(), fieldName), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射设置对象字段值
|
||||
*
|
||||
* @param object 对象
|
||||
* @param field 字段对象
|
||||
* @param value 字段值
|
||||
* @throws IllegalAccessException 反射访问失败
|
||||
*/
|
||||
public static void setFieldValue(Object object, Field field, Object value) throws IllegalAccessException {
|
||||
field.setAccessible(true);
|
||||
field.set(object, value);
|
||||
@@ -200,7 +205,8 @@ public class Ref {
|
||||
* @param clazz 类
|
||||
* @param methodName 方法名
|
||||
* @param parameterTypes 可选参
|
||||
* @return 方法对象
|
||||
* @return 方法对象,不存在时返回 null
|
||||
* @throws NullPointerException 类对象为空
|
||||
*/
|
||||
public static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
||||
if (clazz == null) {
|
||||
@@ -224,7 +230,8 @@ public class Ref {
|
||||
* @param clazz 枚举类
|
||||
* @param string 字符串
|
||||
* @param <T> 泛型
|
||||
* @return 泛型
|
||||
* @return 匹配到的枚举值,找不到或输入为空时返回 null
|
||||
* @throws IllegalArgumentException 传入类型不是枚举类型
|
||||
*/
|
||||
public static <T extends Enum<?>> T toType(Class<T> clazz, String string) {
|
||||
if (string == null) {
|
||||
@@ -234,31 +241,32 @@ public class Ref {
|
||||
if (ts == null) {
|
||||
throw new IllegalArgumentException(clazz.getName() + " is not an enum type");
|
||||
}
|
||||
for (int i = 0; i < ts.length; i++) {
|
||||
if (ts[i].name().equalsIgnoreCase(string)) {
|
||||
return ts[i];
|
||||
for (T t : ts) {
|
||||
if (t.name().equalsIgnoreCase(string)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建字段反射元信息对象
|
||||
*
|
||||
*
|
||||
* @param owner
|
||||
* @param keyName
|
||||
* @return
|
||||
* @param owner 字段所属类型
|
||||
* @param keyName 字段键名
|
||||
* @return 字段反射元信息
|
||||
*/
|
||||
public static RefField field(Class<?> owner, String keyName) {
|
||||
return new RefField(owner, keyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过无参构造器创建实例
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
* @param <T>
|
||||
* @throws Exception
|
||||
* @param type 类型对象
|
||||
* @param <T> 泛型类型
|
||||
* @return 新实例
|
||||
* @throws Exception 反射创建实例异常
|
||||
*/
|
||||
public static <T> T newInstance(Class<T> type) throws Exception {
|
||||
return type.getDeclaredConstructor().newInstance();
|
||||
|
||||
@@ -1,21 +1,39 @@
|
||||
package com.imyeyu.java.ref;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
*
|
||||
* 字段反射元信息,包含字段类型以及对应的 getter/setter 方法
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-04-26 15:46
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class RefField {
|
||||
|
||||
/** 字段类型 */
|
||||
private Class<?> type;
|
||||
|
||||
/** 字段对象 */
|
||||
private Field field;
|
||||
|
||||
/** 字段 setter 方法 */
|
||||
private Method setter;
|
||||
|
||||
/** 字段 getter 方法 */
|
||||
private Method getter;
|
||||
|
||||
/**
|
||||
* 根据拥有者类型和键名构建字段反射元信息
|
||||
*
|
||||
* @param owner 字段所属类型
|
||||
* @param keyName 字段键名,支持中划线、下划线和空格形式
|
||||
*/
|
||||
RefField(Class<?> owner, String keyName) {
|
||||
String fieldName = Ref.getFieldName(keyName);
|
||||
String fieldNameUpper = String.valueOf(fieldName.charAt(0)).toUpperCase() + fieldName.substring(1);
|
||||
@@ -38,40 +56,7 @@ public class RefField {
|
||||
setterName = "set" + fieldNameUpper;
|
||||
getterName = "get" + fieldNameUpper;
|
||||
}
|
||||
|
||||
setter = Ref.getMethod(owner, setterName, field.getType());
|
||||
getter = Ref.getMethod(owner, getterName);
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(Class<?> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Field getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public void setField(Field field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public Method getSetter() {
|
||||
return setter;
|
||||
}
|
||||
|
||||
public void setSetter(Method setter) {
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
public Method getGetter() {
|
||||
return getter;
|
||||
}
|
||||
|
||||
public void setGetter(Method getter) {
|
||||
this.getter = getter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,17 @@ package com.imyeyu.java.thread;
|
||||
|
||||
import com.imyeyu.java.bean.Callback;
|
||||
import com.imyeyu.java.bean.CallbackArg;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 异步可重试执行器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-11-06 17:45
|
||||
@@ -24,16 +31,65 @@ public class AsyncRetryExecutor {
|
||||
/** true 为守护进程 */
|
||||
private static final boolean DEFAULT_DAEMON = false;
|
||||
|
||||
/** 实际执行重试逻辑的线程 */
|
||||
private final Thread thread;
|
||||
|
||||
/** true 为执行成功 */
|
||||
@Getter
|
||||
private volatile boolean isSuccess = false;
|
||||
|
||||
/** true 为处于运行状态 */
|
||||
@Getter
|
||||
private volatile boolean isRunning = false;
|
||||
|
||||
private AsyncRetryExecutor(Builder builder) {
|
||||
/**
|
||||
* 任务
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-11-06 23:37
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Task {
|
||||
|
||||
/** 必填回调方法 */
|
||||
private Callback callback;
|
||||
|
||||
/** 最大重试次数,-1 为无限重试 */
|
||||
@Builder.Default
|
||||
private int maxRetry = DEFAULT_MAX_RETRY;
|
||||
|
||||
/** 重试间隔毫秒数 */
|
||||
@Builder.Default
|
||||
private long retryInterval = DEFAULT_RETRY_INTERVAL;
|
||||
|
||||
/** 线程名前缀 */
|
||||
@Builder.Default
|
||||
private String threadNamePrefix = DEFAULT_THREAD_NAME_PREFIX;
|
||||
|
||||
/** 是否设置为守护线程 */
|
||||
@Builder.Default
|
||||
private boolean daemon = DEFAULT_DAEMON;
|
||||
|
||||
/** 重试耗尽时的异常回调 */
|
||||
private CallbackArg<Exception> onRetryExhausted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据构建器参数创建异步重试执行器
|
||||
*
|
||||
* @param task 构建器对象
|
||||
*/
|
||||
private AsyncRetryExecutor(Task task) {
|
||||
TimiException.required(task, "not found task");
|
||||
TimiException.required(task.getCallback(), "not found task.callback");
|
||||
thread = new Thread(() -> {
|
||||
int retryCount = 0;
|
||||
while (isRunning && !isSuccess && (builder.maxRetry < 0 || retryCount <= builder.maxRetry)) {
|
||||
while (isRunning && !isSuccess && (task.maxRetry < 0 || retryCount <= task.maxRetry)) {
|
||||
try {
|
||||
builder.callback.handler();
|
||||
task.callback.handler();
|
||||
isSuccess = true;
|
||||
} catch (Exception e) {
|
||||
retryCount++;
|
||||
@@ -41,88 +97,71 @@ public class AsyncRetryExecutor {
|
||||
// 中断
|
||||
break;
|
||||
}
|
||||
if (0 < builder.maxRetry && builder.maxRetry < retryCount) {
|
||||
if (0 < task.maxRetry && task.maxRetry < retryCount) {
|
||||
// 超过重试次数
|
||||
builder.onRetryExhausted.handler(e);
|
||||
if (task.onRetryExhausted != null) {
|
||||
task.onRetryExhausted.handler(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// 重试
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(builder.retryInterval);
|
||||
TimeUnit.MILLISECONDS.sleep(task.retryInterval);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, builder.threadNamePrefix + Thread.currentThread().threadId());
|
||||
thread.setDaemon(builder.daemon);
|
||||
}, task.threadNamePrefix + Thread.currentThread().threadId());
|
||||
thread.setDaemon(task.daemon);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造器
|
||||
* 使用默认参数创建执行器
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-11-06 23:37
|
||||
* @param task 任务
|
||||
* @return 执行器实例
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
// 必需参数
|
||||
private final Callback callback;
|
||||
|
||||
// 可选参数(带默认值)
|
||||
private int maxRetry = DEFAULT_MAX_RETRY;
|
||||
private long retryInterval = DEFAULT_RETRY_INTERVAL;
|
||||
private String threadNamePrefix = DEFAULT_THREAD_NAME_PREFIX;
|
||||
private boolean daemon = DEFAULT_DAEMON;
|
||||
private CallbackArg<Exception> onRetryExhausted;
|
||||
|
||||
public Builder(Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public Builder maxRetry(int maxRetry) {
|
||||
this.maxRetry = maxRetry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder retryInterval(long retryInterval) {
|
||||
this.retryInterval = retryInterval;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder threadNamePrefix(String prefix) {
|
||||
this.threadNamePrefix = prefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder daemon(boolean daemon) {
|
||||
this.daemon = daemon;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder onRetryExhausted(CallbackArg<Exception> onRetryExhausted) {
|
||||
this.onRetryExhausted = onRetryExhausted;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AsyncRetryExecutor build() {
|
||||
return new AsyncRetryExecutor(this);
|
||||
}
|
||||
public static AsyncRetryExecutor create(Task task) {
|
||||
return new AsyncRetryExecutor(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用默认参数创建执行器
|
||||
*
|
||||
* @param callback 执行回调
|
||||
* @return 执行器实例
|
||||
*/
|
||||
public static AsyncRetryExecutor create(Callback callback) {
|
||||
return new Builder(callback).build();
|
||||
return new AsyncRetryExecutor(Task.builder().callback(callback).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定最大重试次数创建执行器
|
||||
*
|
||||
* @param callback 执行回调
|
||||
* @param maxRetry 最大重试次数
|
||||
* @return 执行器实例
|
||||
*/
|
||||
public static AsyncRetryExecutor create(Callback callback, int maxRetry) {
|
||||
return new Builder(callback).maxRetry(maxRetry).build();
|
||||
return new AsyncRetryExecutor(Task.builder().callback(callback).maxRetry(maxRetry).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定重试间隔创建执行器
|
||||
*
|
||||
* @param callback 执行回调
|
||||
* @param interval 重试间隔毫秒数
|
||||
* @return 执行器实例
|
||||
*/
|
||||
public static AsyncRetryExecutor create(Callback callback, long interval) {
|
||||
return new Builder(callback).retryInterval(interval).build();
|
||||
return new AsyncRetryExecutor(Task.builder().callback(callback).retryInterval(interval).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步执行
|
||||
*/
|
||||
public void start() {
|
||||
if (!isRunning) {
|
||||
isRunning = true;
|
||||
@@ -131,18 +170,13 @@ public class AsyncRetryExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 中断执行并停止后续重试
|
||||
*/
|
||||
public void interrupt() {
|
||||
isRunning = false;
|
||||
if (thread.isAlive() && !thread.isInterrupted()) {
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
package test;
|
||||
package com.imyeyu.java;
|
||||
|
||||
import com.imyeyu.java.thread.AsyncRetryExecutor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* 线程工具相关测试
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-11-06 23:59
|
||||
*/
|
||||
public class TestThread {
|
||||
public class AsyncRetryExecutorTest {
|
||||
|
||||
/**
|
||||
* 验证异步重试执行器在成功后状态为成功
|
||||
*
|
||||
* @throws InterruptedException 等待异步完成时中断异常
|
||||
*/
|
||||
@Test
|
||||
public void testAsyncRetryExecutor() throws InterruptedException {
|
||||
Object lock = new Object();
|
||||
@@ -25,11 +34,9 @@ public class TestThread {
|
||||
}
|
||||
});
|
||||
executor.start();
|
||||
|
||||
synchronized (lock) {
|
||||
lock.wait();
|
||||
}
|
||||
|
||||
assert executor.isSuccess();
|
||||
assertTrue(executor.isSuccess());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package test;
|
||||
package com.imyeyu.java;
|
||||
|
||||
import com.imyeyu.java.obs.ObservableList;
|
||||
import com.imyeyu.java.obs.ObservableMap;
|
||||
@@ -7,12 +7,18 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
/**
|
||||
* 可观察对象相关测试
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-09-01 23:26
|
||||
*/
|
||||
public class TestObs {
|
||||
public class ObservableTest {
|
||||
|
||||
/** 验证可观察字符串赋值后值会更新 */
|
||||
@Test
|
||||
public void testString() {
|
||||
ObservableString obsString = new ObservableString("test1");
|
||||
@@ -20,9 +26,10 @@ public class TestObs {
|
||||
obsString.addListener((from, to) -> System.out.printf("from %s to %s%n", from, to));
|
||||
obsString.set("test2");
|
||||
System.out.println("now value = " + obsString);
|
||||
assert obsString.get().equals("test2");
|
||||
assertEquals("test2", obsString.get());
|
||||
}
|
||||
|
||||
/** 验证可观察列表在多次增删后的最终状态 */
|
||||
@Test
|
||||
public void testList() {
|
||||
ObservableList<String> list = new ObservableList<>();
|
||||
@@ -40,8 +47,10 @@ public class TestObs {
|
||||
list.removeAll(List.of("test3", "test4"));
|
||||
list.removeIf(item -> item.equals("test1")); // 移除 0, 1, 2, 3, 4
|
||||
System.out.println(list);
|
||||
assertEquals(List.of("test5", "test7", "test8"), list);
|
||||
}
|
||||
|
||||
/** 验证可观察映射在新增、更新与删除后的最终状态 */
|
||||
@Test
|
||||
public void testMap() {
|
||||
ObservableMap<String, String> map = new ObservableMap<>();
|
||||
@@ -56,5 +65,11 @@ public class TestObs {
|
||||
map.remove("t1");
|
||||
map.remove("t2", "test2");
|
||||
System.out.println(map);
|
||||
assertEquals(3, map.size());
|
||||
assertEquals("test3", map.get("t3"));
|
||||
assertEquals("test4", map.get("t4"));
|
||||
assertEquals("test5", map.get("t5"));
|
||||
assertFalse(map.containsKey("t1"));
|
||||
assertFalse(map.containsKey("t2"));
|
||||
}
|
||||
}
|
||||
109
src/test/java/com/imyeyu/java/RefTest.java
Normal file
109
src/test/java/com/imyeyu/java/RefTest.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package com.imyeyu.java;
|
||||
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.java.ref.RefField;
|
||||
import lombok.Data;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* 反射工具相关测试
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2024-04-26 11:05
|
||||
*/
|
||||
@Data
|
||||
public class RefTest {
|
||||
|
||||
/** 布尔字段,使用 is 前缀 */
|
||||
private boolean isBoy;
|
||||
/** 布尔字段,不使用 is 前缀 */
|
||||
private boolean boy;
|
||||
/** 普通字符串字段 */
|
||||
private String fieldName;
|
||||
|
||||
/** 验证字段名标准化转换规则 */
|
||||
@Test
|
||||
public void test() {
|
||||
String t1 = Ref.getFieldName("is-boy");
|
||||
assertEquals("isBoy", t1);
|
||||
String t2 = Ref.getFieldName("is_boy");
|
||||
assertEquals("isBoy", t2);
|
||||
String t3 = Ref.getFieldName("is boy");
|
||||
assertEquals("isBoy", t3);
|
||||
String t4 = Ref.getFieldName("isBoy");
|
||||
assertEquals("isBoy", t4);
|
||||
String t5 = Ref.getFieldName("boy");
|
||||
assertEquals("boy", t5);
|
||||
String t6 = Ref.getFieldName("fieldName");
|
||||
assertEquals("fieldName", t6);
|
||||
String t7 = Ref.getFieldName("field name");
|
||||
assertEquals("fieldName", t7);
|
||||
String t8 = Ref.getFieldName("field-Name");
|
||||
assertEquals("fieldName", t8);
|
||||
String t9 = Ref.getFieldName("field_Name");
|
||||
assertEquals("fieldName", t9);
|
||||
}
|
||||
|
||||
/** 验证布尔字段 is-boy 能映射到标准 getter/setter */
|
||||
@Test
|
||||
public void refBooleanField() {
|
||||
RefField refField = Ref.field(RefTest.class, "is-boy");
|
||||
assertNotNull(refField.getSetter());
|
||||
assertEquals("setBoy", refField.getSetter().getName());
|
||||
assertNotNull(refField.getGetter());
|
||||
assertEquals("isBoy", refField.getGetter().getName());
|
||||
}
|
||||
|
||||
/** 验证布尔字段 boy 能映射到标准 getter/setter */
|
||||
@Test
|
||||
public void refBoolean2Field() {
|
||||
RefField refField = Ref.field(RefTest.class, "boy");
|
||||
assertNotNull(refField.getSetter());
|
||||
assertEquals("setBoy", refField.getSetter().getName());
|
||||
assertNotNull(refField.getGetter());
|
||||
assertEquals("isBoy", refField.getGetter().getName());
|
||||
}
|
||||
|
||||
/** 验证字段名 fieldName 能映射到标准 getter/setter */
|
||||
@Test
|
||||
public void refStringField() {
|
||||
RefField refField = Ref.field(RefTest.class, "fieldName");
|
||||
assertNotNull(refField.getSetter());
|
||||
assertEquals("setFieldName", refField.getSetter().getName());
|
||||
assertNotNull(refField.getGetter());
|
||||
assertEquals("getFieldName", refField.getGetter().getName());
|
||||
}
|
||||
|
||||
/** 验证字段名 field-name 能映射到标准 getter/setter */
|
||||
@Test
|
||||
public void refString2Field() {
|
||||
RefField refField = Ref.field(RefTest.class, "field-name");
|
||||
assertNotNull(refField.getSetter());
|
||||
assertEquals("setFieldName", refField.getSetter().getName());
|
||||
assertNotNull(refField.getGetter());
|
||||
assertEquals("getFieldName", refField.getGetter().getName());
|
||||
}
|
||||
|
||||
/** 验证字段名 field_name 能映射到标准 getter/setter */
|
||||
@Test
|
||||
public void refString3Field() {
|
||||
RefField refField = Ref.field(RefTest.class, "field_name");
|
||||
assertNotNull(refField.getSetter());
|
||||
assertEquals("setFieldName", refField.getSetter().getName());
|
||||
assertNotNull(refField.getGetter());
|
||||
assertEquals("getFieldName", refField.getGetter().getName());
|
||||
}
|
||||
|
||||
/** 验证字段名 field name 能映射到标准 getter/setter */
|
||||
@Test
|
||||
public void refString4Field() {
|
||||
RefField refField = Ref.field(RefTest.class, "field name");
|
||||
assertNotNull(refField.getSetter());
|
||||
assertEquals("setFieldName", refField.getSetter().getName());
|
||||
assertNotNull(refField.getGetter());
|
||||
assertEquals("getFieldName", refField.getGetter().getName());
|
||||
}
|
||||
}
|
||||
87
src/test/java/com/imyeyu/java/TimiJavaTest.java
Normal file
87
src/test/java/com/imyeyu/java/TimiJavaTest.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package com.imyeyu.java;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* {@link TimiJava} 静态工具方法测试
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-04-27 10:00
|
||||
*/
|
||||
public class TimiJavaTest {
|
||||
|
||||
/** 验证 isEmpty 与 isNotEmpty 的主要判空分支 */
|
||||
@Test
|
||||
public void testEmptyCheck() {
|
||||
assertTrue(TimiJava.isEmpty(null));
|
||||
assertTrue(TimiJava.isEmpty(""));
|
||||
assertTrue(TimiJava.isEmpty(" "));
|
||||
assertTrue(TimiJava.isEmpty(Collections.emptyList()));
|
||||
assertTrue(TimiJava.isEmpty(Collections.emptyMap()));
|
||||
assertFalse(TimiJava.isEmpty("value"));
|
||||
assertTrue(TimiJava.isNotEmpty("value"));
|
||||
}
|
||||
|
||||
/** 验证 defaultIfNull 与 defaultIfEmpty 返回默认值逻辑 */
|
||||
@Test
|
||||
public void testDefaultValue() {
|
||||
assertEquals("default", TimiJava.defaultIfNull(null, "default"));
|
||||
assertEquals("value", TimiJava.defaultIfNull("value", "default"));
|
||||
assertEquals("default", TimiJava.defaultIfEmpty(" ", "default"));
|
||||
assertEquals("value", TimiJava.defaultIfEmpty("value", "default"));
|
||||
}
|
||||
|
||||
/** 验证 firstNotNull 与 firstNotEmpty 的首个命中逻辑 */
|
||||
@Test
|
||||
public void testFirstValue() {
|
||||
assertEquals("v1", TimiJava.firstNotNull(null, "v1", "v2"));
|
||||
assertNull(TimiJava.firstNotNull(null, null));
|
||||
assertEquals("v1", TimiJava.firstNotEmpty("", " ", "v1", "v2"));
|
||||
assertNull(TimiJava.firstNotEmpty("", " ", null));
|
||||
}
|
||||
|
||||
/** 验证异常序列化结果包含异常类型与消息 */
|
||||
@Test
|
||||
public void testSerializeThrowable() {
|
||||
String text = TimiJava.serializeThrowable(new IllegalArgumentException("bad"));
|
||||
assertTrue(text.contains(IllegalArgumentException.class.getName()));
|
||||
assertTrue(text.contains("bad"));
|
||||
}
|
||||
|
||||
/** 验证 safeIterable 在空值与非空场景下都可安全迭代 */
|
||||
@Test
|
||||
public void testSafeIterable() {
|
||||
Iterable<String> empty = TimiJava.safeIterable(null);
|
||||
assertNotNull(empty);
|
||||
assertFalse(empty.iterator().hasNext());
|
||||
|
||||
Iterable<String> values = TimiJava.safeIterable(List.of("a", "b"));
|
||||
assertIterableEquals(List.of("a", "b"), values);
|
||||
}
|
||||
|
||||
/** 验证 ZERO_UUID 常量值 */
|
||||
@Test
|
||||
public void testZeroUuid() {
|
||||
assertEquals("00000000-0000-0000-0000-000000000000", TimiJava.ZERO_UUID);
|
||||
}
|
||||
|
||||
/** 验证数组和映射判空路径 */
|
||||
@Test
|
||||
public void testArrayAndMapEmptyCheck() {
|
||||
assertTrue(TimiJava.isEmpty(new String[]{}));
|
||||
assertFalse(TimiJava.isEmpty(new String[]{"v"}));
|
||||
assertTrue(TimiJava.isEmpty(Map.of()));
|
||||
assertFalse(TimiJava.isEmpty(Map.of("k", "v")));
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package test;
|
||||
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.java.ref.RefField;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @since 2024-04-26 11:05
|
||||
*/
|
||||
public class TestRef {
|
||||
|
||||
private boolean isBoy;
|
||||
private boolean boy;
|
||||
private String fieldName;
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String t1 = Ref.getFieldName("is-boy");
|
||||
assert t1.equals("isBoy");
|
||||
String t2 = Ref.getFieldName("is_boy");
|
||||
assert t2.equals("isBoy");
|
||||
String t3 = Ref.getFieldName("is boy");
|
||||
assert t3.equals("isBoy");
|
||||
String t4 = Ref.getFieldName("isBoy");
|
||||
assert t4.equals("isBoy");
|
||||
String t5 = Ref.getFieldName("boy");
|
||||
assert t5.equals("boy");
|
||||
String t6 = Ref.getFieldName("fieldName");
|
||||
assert t6.equals("fieldName");
|
||||
String t7 = Ref.getFieldName("field name");
|
||||
assert t7.equals("fieldName");
|
||||
String t8 = Ref.getFieldName("field-Name");
|
||||
assert t8.equals("fieldName");
|
||||
String t9 = Ref.getFieldName("field_Name");
|
||||
assert t9.equals("fieldName");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refBooleanField() {
|
||||
RefField refField = Ref.field(TestRef.class, "is-boy");
|
||||
assert refField.getSetter() != null && refField.getSetter().getName().equals("setBoy");
|
||||
assert refField.getGetter() != null && refField.getGetter().getName().equals("isBoy");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refBoolean2Field() {
|
||||
RefField refField = Ref.field(TestRef.class, "boy");
|
||||
assert refField.getSetter() != null && refField.getSetter().getName().equals("setBoy");
|
||||
assert refField.getGetter() != null && refField.getGetter().getName().equals("isBoy");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refStringField() {
|
||||
RefField refField = Ref.field(TestRef.class, "fieldName");
|
||||
assert refField.getSetter() != null && refField.getSetter().getName().equals("setFieldName");
|
||||
assert refField.getGetter() != null && refField.getGetter().getName().equals("getFieldName");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refString2Field() {
|
||||
RefField refField = Ref.field(TestRef.class, "field-name");
|
||||
assert refField.getSetter() != null && refField.getSetter().getName().equals("setFieldName");
|
||||
assert refField.getGetter() != null && refField.getGetter().getName().equals("getFieldName");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refString3Field() {
|
||||
RefField refField = Ref.field(TestRef.class, "field_name");
|
||||
assert refField.getSetter() != null && refField.getSetter().getName().equals("setFieldName");
|
||||
assert refField.getGetter() != null && refField.getGetter().getName().equals("getFieldName");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refString4Field() {
|
||||
RefField refField = Ref.field(TestRef.class, "field name");
|
||||
assert refField.getSetter() != null && refField.getSetter().getName().equals("setFieldName");
|
||||
assert refField.getGetter() != null && refField.getGetter().getName().equals("getFieldName");
|
||||
}
|
||||
|
||||
public boolean isBoy() {
|
||||
return isBoy;
|
||||
}
|
||||
|
||||
public void setBoy(boolean boy) {
|
||||
isBoy = boy;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public void setFieldName(String fieldName) {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user