mirror of
https://github.moeyy.xyz/https://github.com/GeWuYou/forgeboot
synced 2025-10-28 00:24:37 +08:00
Compare commits
6 Commits
401b9cf065
...
2c6a721176
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c6a721176 | |||
| 40f58efd82 | |||
| d71262b550 | |||
| be03204a73 | |||
| 2880eafc03 | |||
| e92a53b194 |
@ -6,55 +6,100 @@ stages:
|
|||||||
|
|
||||||
variables:
|
variables:
|
||||||
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
|
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
|
||||||
|
GIT_STRATEGY: fetch
|
||||||
|
GIT_DEPTH: "0"
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- rm -rf $GRADLE_USER_HOME/.tmp || true
|
- rm -rf "$GRADLE_USER_HOME/.tmp" || true
|
||||||
|
|
||||||
# 🏷️ 自动打标签
|
# -------- 复用片段(anchors) ------------------------------------
|
||||||
tag:
|
|
||||||
stage: tag
|
# 统一镜像:alpine/git + 清空 ENTRYPOINT,避免 "git sh" 问题
|
||||||
image: alpine:latest
|
.alpine_git_image: &alpine_git_image
|
||||||
rules:
|
image:
|
||||||
- if: '$CI_COMMIT_BRANCH == "main" && $CI_COMMIT_MESSAGE !~ /ci/i'
|
name: alpine/git:latest
|
||||||
script:
|
entrypoint: [ "" ]
|
||||||
- set -euo pipefail
|
|
||||||
- apk add --no-cache git
|
# 统一 Git 身份配置
|
||||||
|
.git_identity: &git_identity
|
||||||
- git config --global user.email "pipeline@${GITLAB_URL}"
|
- git config --global user.email "pipeline@${GITLAB_URL}"
|
||||||
- git config --global user.name "Project Pipeline Bot"
|
- git config --global user.name "Project Pipeline Bot"
|
||||||
- git fetch --tags
|
|
||||||
- git fetch origin main
|
|
||||||
- MAIN_COMMIT=$(git rev-parse origin/main)
|
|
||||||
- echo "🔗 main commit -> $MAIN_COMMIT"
|
|
||||||
|
|
||||||
|
# 统一远端地址变量(GitLab & GitHub)
|
||||||
|
.remotes: &remotes
|
||||||
|
- export GL_REPO_URL="https://oauth2:${PIPELINE_BOT_TOKEN}@${GITLAB_URL}/${CI_PROJECT_PATH}.git"
|
||||||
|
- export GH_REPO_URL="https://x-access-token:${GITHUB_PUSH_TOKEN}@github.com/GeWuYou/forgeboot.git"
|
||||||
|
|
||||||
|
# tag 任务常用准备:清理本地 tag、拉取远端 tag 和 main
|
||||||
|
.tag_prepare: &tag_prepare
|
||||||
|
- echo "🧹 删除所有本地 tag..."
|
||||||
|
- git tag -l | xargs -r git tag -d || true
|
||||||
|
- echo "🔍 拉取远程 tag 和 main"
|
||||||
|
- git fetch --tags --force --prune
|
||||||
|
- git fetch origin main --force
|
||||||
|
- MAIN_COMMIT=$(git rev-parse origin/main)
|
||||||
|
- echo "🔗 当前 main commit -> $MAIN_COMMIT"
|
||||||
|
|
||||||
|
# 🏷️ 自动打标签
|
||||||
|
# tag job: 在main分支提交时自动创建并推送新的语义化版本标签
|
||||||
|
# 参数:
|
||||||
|
# stage: 指定该job属于tag阶段
|
||||||
|
# image: 指定使用alpine/git镜像,并清空entrypoint避免git sh问题
|
||||||
|
# rules: 定义触发规则,仅当提交到main分支且提交信息不包含"ci"(忽略大小写)时触发
|
||||||
|
# script: 执行标签创建和推送的具体脚本
|
||||||
|
# tags: 指定运行该job的runner标签为java
|
||||||
|
tag:
|
||||||
|
stage: tag
|
||||||
|
<<: *alpine_git_image
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_BRANCH == "main" && $CI_COMMIT_MESSAGE !~ /ci/i'
|
||||||
|
tags: [ java ]
|
||||||
|
before_script:
|
||||||
|
- *git_identity
|
||||||
|
- *remotes
|
||||||
|
- *tag_prepare
|
||||||
|
script:
|
||||||
|
- echo "📦 获取最新 tag..."
|
||||||
|
# 使用 git 自带排序(无需 coreutils 的 sort -V)
|
||||||
- LATEST_TAG=$(git tag --list '*' --sort=-v:refname | head -n1 || true)
|
- LATEST_TAG=$(git tag --list '*' --sort=-v:refname | head -n1 || true)
|
||||||
- if [ -z "$LATEST_TAG" ]; then LATEST_TAG="0.0.0"; fi
|
- if [ -z "$LATEST_TAG" ]; then LATEST_TAG="0.0.0"; fi
|
||||||
- echo "🔖 最新 tag -> $LATEST_TAG"
|
- echo "🔖 最新 tag -> $LATEST_TAG"
|
||||||
|
|
||||||
|
# 解析 semver(不带 v 前缀)
|
||||||
- VERSION=${LATEST_TAG#v}
|
- VERSION=${LATEST_TAG#v}
|
||||||
- MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
- MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
||||||
- MINOR=$(echo "$VERSION" | cut -d. -f2)
|
- MINOR=$(echo "$VERSION" | cut -d. -f2)
|
||||||
- PATCH=$(echo "$VERSION" | cut -d. -f3)
|
- PATCH=$(echo "$VERSION" | cut -d. -f3)
|
||||||
|
|
||||||
|
# 规则:patch +1,满 10 进位到 minor
|
||||||
- PATCH=$((PATCH + 1))
|
- PATCH=$((PATCH + 1))
|
||||||
|
- if [ "$PATCH" -ge 10 ]; then PATCH=0; MINOR=$((MINOR+1)); echo "🔁 patch 达 10 进位:MINOR=$MINOR, PATCH=$PATCH"; fi
|
||||||
- NEW_TAG="${MAJOR}.${MINOR}.${PATCH}"
|
- NEW_TAG="${MAJOR}.${MINOR}.${PATCH}"
|
||||||
- echo "🏷️ 新 tag -> $NEW_TAG"
|
- echo "🏷️ 新 tag -> $NEW_TAG"
|
||||||
|
|
||||||
|
# 同一 commit 不重复打 tag
|
||||||
- if git tag --points-at "$MAIN_COMMIT" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' >/dev/null; then
|
- if git tag --points-at "$MAIN_COMMIT" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' >/dev/null; then
|
||||||
echo "⏭️ 已存在 tag,跳过创建";
|
echo "⏭️ 当前 commit 已有 semver tag,跳过";
|
||||||
exit 0;
|
exit 0;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- git tag $NEW_TAG $MAIN_COMMIT
|
- git tag "$NEW_TAG" "$MAIN_COMMIT"
|
||||||
- git push https://oauth2:${PIPELINE_BOT_TOKEN}@${GITLAB_URL}/${CI_PROJECT_PATH}.git $NEW_TAG
|
- git push "$GL_REPO_URL" "$NEW_TAG"
|
||||||
- echo "✅ tag $NEW_TAG 已推送"
|
- echo "✅ tag $NEW_TAG 已推送到 GitLab"
|
||||||
# 添加 GitHub 远程并推送 tag
|
|
||||||
- git remote add github https://x-access-token:${GITHUB_PUSH_TOKEN}@github.com/GeWuYou/forgeboot.git
|
- git remote add github "$GH_REPO_URL" || git remote set-url github "$GH_REPO_URL"
|
||||||
- git push github $NEW_TAG
|
- git push github "$NEW_TAG"
|
||||||
- echo "✅ tag $NEW_TAG 已同步至 GitHub"
|
- echo "✅ tag $NEW_TAG 已同步至 GitHub"
|
||||||
tags:
|
|
||||||
- java
|
|
||||||
|
|
||||||
# 📦 发布至 GitLab 与 GitHub Maven 仓库
|
# 📦 发布至 GitLab 与 GitHub Maven 仓库
|
||||||
|
# publish job: 将构建产物发布到GitLab和GitHub的Maven仓库
|
||||||
|
# 参数:
|
||||||
|
# stage: 指定该job属于publish阶段
|
||||||
|
# needs: 指定依赖tag job完成后再执行
|
||||||
|
# rules: 定义触发规则,仅当提交到main分支且提交信息不包含"ci"(忽略大小写)时触发
|
||||||
|
# cache: 定义缓存策略,缓存Gradle相关目录以加速构建
|
||||||
|
# script: 执行发布到Maven仓库的具体脚本
|
||||||
|
# tags: 指定运行该job的runner标签为java
|
||||||
publish:
|
publish:
|
||||||
stage: publish
|
stage: publish
|
||||||
needs: [ "tag" ]
|
needs: [ "tag" ]
|
||||||
@ -72,53 +117,63 @@ publish:
|
|||||||
- .gradle/kotlin-profile/
|
- .gradle/kotlin-profile/
|
||||||
- .kotlin/
|
- .kotlin/
|
||||||
policy: pull-push
|
policy: pull-push
|
||||||
|
tags: [ java ]
|
||||||
script:
|
script:
|
||||||
- echo "🔧 授予 gradlew 执行权限..."
|
- echo "🔧 授予 gradlew 执行权限..."
|
||||||
- chmod +x gradlew
|
- chmod +x gradlew
|
||||||
- ./gradlew publishMavenJavaPublicationToGitLabRepository
|
- ./gradlew publishMavenJavaPublicationToGitLabRepository
|
||||||
- ./gradlew publishMavenJavaPublicationToGitHubRepository --continue
|
- ./gradlew publishMavenJavaPublicationToGitHubRepository --continue
|
||||||
tags:
|
|
||||||
- java
|
|
||||||
|
|
||||||
# 🔄 重建 test 分支
|
# 🔄 重建 test 分支
|
||||||
|
# reset job: 基于main分支重建test分支
|
||||||
|
# 参数:
|
||||||
|
# stage: 指定该job属于reset阶段
|
||||||
|
# image: 指定使用alpine/git镜像,并清空entrypoint避免git sh问题
|
||||||
|
# rules: 定义触发规则,仅当提交到main分支时触发
|
||||||
|
# script: 执行重建test分支的具体脚本
|
||||||
|
# tags: 指定运行该job的runner标签为java
|
||||||
reset:
|
reset:
|
||||||
stage: reset
|
stage: reset
|
||||||
image: alpine:latest
|
<<: *alpine_git_image
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
- if: '$CI_COMMIT_BRANCH == "main"'
|
||||||
|
tags: [ java ]
|
||||||
|
before_script:
|
||||||
|
- *git_identity
|
||||||
|
- *remotes
|
||||||
script:
|
script:
|
||||||
- set -euo pipefail
|
- set -euo pipefail
|
||||||
- apk add --no-cache git
|
- echo "🔄 重建 test 分支..."
|
||||||
- git config --global user.email "pipeline@${GITLAB_URL}"
|
- git clone --branch main "$GL_REPO_URL" repo
|
||||||
- git config --global user.name "Project Pipeline Bot"
|
|
||||||
- git clone --branch main https://oauth2:${PIPELINE_BOT_TOKEN}@${GITLAB_URL}/${CI_PROJECT_PATH}.git repo
|
|
||||||
- cd repo
|
- cd repo
|
||||||
- git checkout -B test
|
- git checkout -B test
|
||||||
- git push origin test --force
|
- git push origin test --force
|
||||||
- echo "✅ test 分支已重建完成"
|
- echo "✅ test 分支已重建完成"
|
||||||
tags:
|
|
||||||
- java
|
# 🔁 同步到 GitHub
|
||||||
# Mirror to GitHub
|
# mirror-to-github job: 将GitLab的main分支同步到GitHub
|
||||||
|
# 参数:
|
||||||
|
# stage: 指定该job属于mirror阶段
|
||||||
|
# image: 指定使用alpine/git镜像,并清空entrypoint避免git sh问题
|
||||||
|
# rules: 定义触发规则,仅当提交到main分支时触发
|
||||||
|
# script: 执行同步到GitHub的具体脚本
|
||||||
|
# tags: 指定运行该job的runner标签为java
|
||||||
mirror-to-github:
|
mirror-to-github:
|
||||||
stage: mirror
|
stage: mirror
|
||||||
image: alpine:latest
|
<<: *alpine_git_image
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
- if: '$CI_COMMIT_BRANCH == "main"'
|
||||||
|
tags: [ java ]
|
||||||
|
before_script:
|
||||||
|
- *git_identity
|
||||||
|
- *remotes
|
||||||
script:
|
script:
|
||||||
- set -euo pipefail
|
- set -euo pipefail
|
||||||
- apk add --no-cache git openssh
|
- echo "🔄 clone GitLab 仓库..."
|
||||||
- git config --global user.name "Project Pipeline Bot"
|
- git clone --branch main "$GL_REPO_URL" repo
|
||||||
- git config --global user.email "pipeline@${GITLAB_URL}"
|
|
||||||
|
|
||||||
- echo "🔄 正在 clone 当前 GitLab 仓库..."
|
|
||||||
- git clone --branch main https://oauth2:${PIPELINE_BOT_TOKEN}@${GITLAB_URL}/${CI_PROJECT_PATH}.git repo
|
|
||||||
- cd repo
|
- cd repo
|
||||||
|
- echo "🔗 添加/更新 GitHub 远程..."
|
||||||
- echo "🔗 添加 GitHub 远程地址..."
|
- git remote add github "$GH_REPO_URL" || git remote set-url github "$GH_REPO_URL"
|
||||||
- git remote add github https://x-access-token:${GITHUB_PUSH_TOKEN}@github.com/GeWuYou/forgeboot.git
|
- echo "🚀 推送 main 到 GitHub..."
|
||||||
|
|
||||||
- echo "🚀 推送 main 分支到 GitHub..."
|
|
||||||
- git push github main --force
|
- git push github main --force
|
||||||
- echo "✅ GitHub 同步完成"
|
- echo "✅ GitHub 同步完成"
|
||||||
tags:
|
|
||||||
- java
|
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
// This file is used to define the repositories used by the project.
|
// This file is used to define the repositories used by the project.
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
val host = System.getenv("GITEA_HOST")
|
|
||||||
// host?.let {
|
|
||||||
// maven{
|
|
||||||
// url = uri("${host}/api/packages/gewuyou/maven")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://maven.aliyun.com/repository/public/")
|
url = uri("https://maven.aliyun.com/repository/public/")
|
||||||
|
content {
|
||||||
|
excludeModule("io.ktor", "ktor-client-mock")
|
||||||
|
excludeModule("io.ktor", "ktor-client-mock-jvm") // 如果你之前用的是 jvm 变体,也一并排除
|
||||||
|
}
|
||||||
}
|
}
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://maven.aliyun.com/repository/spring/")
|
url = uri("https://maven.aliyun.com/repository/spring/")
|
||||||
|
|||||||
3
forgeboot-http/.gitattributes
vendored
Normal file
3
forgeboot-http/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
forgeboot-http/.gitignore
vendored
Normal file
40
forgeboot-http/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
4
forgeboot-http/build.gradle.kts
Normal file
4
forgeboot-http/build.gradle.kts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
3
forgeboot-http/forgeboot-http-ktor/.gitattributes
vendored
Normal file
3
forgeboot-http/forgeboot-http-ktor/.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
40
forgeboot-http/forgeboot-http-ktor/.gitignore
vendored
Normal file
40
forgeboot-http/forgeboot-http-ktor/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Kotlin ###
|
||||||
|
.kotlin
|
||||||
14
forgeboot-http/forgeboot-http-ktor/build.gradle.kts
Normal file
14
forgeboot-http/forgeboot-http-ktor/build.gradle.kts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
dependencies {
|
||||||
|
api(libs.io.ktor.clientCore)
|
||||||
|
implementation(libs.io.ktor.clientContentNegotiation)
|
||||||
|
implementation(libs.io.ktor.serializationKotlinxJson)
|
||||||
|
implementation(libs.io.ktor.clientLogging)
|
||||||
|
|
||||||
|
// test
|
||||||
|
testImplementation(libs.org.junit.jupiter.api)
|
||||||
|
testImplementation(libs.io.ktor.clientCio)
|
||||||
|
testImplementation(libs.io.ktor.clientMock)
|
||||||
|
testImplementation(libs.kotlinxCorountinesTest)
|
||||||
|
testRuntimeOnly(libs.org.junit.jupiter.engine)
|
||||||
|
testRuntimeOnly(libs.org.junit.platform)
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.gewuyou.forgeboot.http.ktor
|
||||||
|
|
||||||
|
import com.gewuyou.forgeboot.http.ktor.entities.KtorHttpClientConfig
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带重试机制的执行函数
|
||||||
|
*
|
||||||
|
* 根据配置的重试策略,对给定的代码块进行执行,如果执行失败或返回指定的状态码,
|
||||||
|
* 则按照配置的重试策略进行重试。
|
||||||
|
*
|
||||||
|
* @param conf Ktor客户端配置,包含重试相关配置
|
||||||
|
* @param block 需要执行的代码块
|
||||||
|
* @return 执行结果
|
||||||
|
* @throws Exception 当达到最大重试次数或不满足重试条件时抛出异常
|
||||||
|
*/
|
||||||
|
suspend fun <T> withRetry(conf: KtorHttpClientConfig, block: suspend () -> T): T {
|
||||||
|
// 如果未启用重试机制,则直接执行代码块并返回结果
|
||||||
|
if (!conf.retry.enabled) return block()
|
||||||
|
|
||||||
|
var attempt = 0
|
||||||
|
var backoff = conf.retry.initialBackoffMillis.coerceAtLeast(1)
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
attempt++
|
||||||
|
try {
|
||||||
|
val res = block()
|
||||||
|
// 如果结果是HttpResponse且状态码在重试列表中,并且未达到最大重试次数,则进行重试
|
||||||
|
if (res is HttpResponse && res.status.value in conf.retry.retryOnStatus && attempt < conf.retry.maxAttempts) {
|
||||||
|
delay(jitter(backoff, conf.retry.jitterMillis)); backoff *= 2; continue
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 如果不满足重试条件或已达到最大重试次数,则抛出异常
|
||||||
|
if (!conf.retry.retryOnNetworkError || attempt >= conf.retry.maxAttempts) throw e
|
||||||
|
delay(jitter(backoff, conf.retry.jitterMillis)); backoff *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加抖动的延迟计算函数
|
||||||
|
*
|
||||||
|
* 为避免惊群效应,在基础延迟时间上添加随机抖动
|
||||||
|
*
|
||||||
|
* @param base 基础延迟时间(毫秒)
|
||||||
|
* @param j 抖动范围(毫秒)
|
||||||
|
* @return 添加抖动后的延迟时间,最小为1毫秒
|
||||||
|
*/
|
||||||
|
private fun jitter(base: Long, j: Long) = (base + Random.nextLong(-j, j + 1)).coerceAtLeast(1)
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package com.gewuyou.forgeboot.http.ktor.client
|
||||||
|
|
||||||
|
import com.gewuyou.forgeboot.http.ktor.entities.KtorHttpClientConfig
|
||||||
|
import com.gewuyou.forgeboot.http.ktor.withRetry
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.call.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单的HTTP客户端
|
||||||
|
*
|
||||||
|
* 提供了基本的HTTP操作方法,包括GET、POST、PUT和DELETE,并内置重试机制。
|
||||||
|
* 所有请求都会根据配置的重试策略在失败时自动重试。
|
||||||
|
*
|
||||||
|
* @property client Ktor HTTP客户端实例
|
||||||
|
* @property conf Ktor客户端配置,包含重试等相关配置
|
||||||
|
* @since 2025-08-13 15:14:50
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
class SimpleHttpClient(
|
||||||
|
val client: HttpClient,
|
||||||
|
val conf: KtorHttpClientConfig,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 执行GET请求
|
||||||
|
*
|
||||||
|
* @param path 请求路径
|
||||||
|
* @return 响应结果,类型为R
|
||||||
|
*/
|
||||||
|
suspend inline fun <reified R> get(path: String): R =
|
||||||
|
withRetry(conf) { client.get(path) }.body()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行POST请求
|
||||||
|
*
|
||||||
|
* @param path 请求路径
|
||||||
|
* @param body 请求体数据
|
||||||
|
* @return 响应结果,类型为Res
|
||||||
|
*/
|
||||||
|
suspend inline fun <reified Req : Any, reified Res> post(path: String, body: Req): Res =
|
||||||
|
withRetry(conf) { client.post(path) { contentType(ContentType.Application.Json); setBody(body) } }.body()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行PUT请求
|
||||||
|
*
|
||||||
|
* @param path 请求路径
|
||||||
|
* @param body 请求体数据
|
||||||
|
* @return 响应结果,类型为Res
|
||||||
|
*/
|
||||||
|
suspend inline fun <reified Req : Any, reified Res> put(path: String, body: Req): Res =
|
||||||
|
withRetry(conf) { client.put(path) { contentType(ContentType.Application.Json); setBody(body) } }.body()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行DELETE请求
|
||||||
|
*
|
||||||
|
* @param path 请求路径
|
||||||
|
* @return 响应结果,类型为R
|
||||||
|
*/
|
||||||
|
suspend inline fun <reified R> delete(path: String): R =
|
||||||
|
withRetry(conf) { client.delete(path) }.body()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭HTTP客户端,释放资源
|
||||||
|
*/
|
||||||
|
fun close() = client.close()
|
||||||
|
}
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
package com.gewuyou.forgeboot.http.ktor.entities
|
||||||
|
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP客户端配置
|
||||||
|
*
|
||||||
|
* 用于配置HTTP客户端的各种参数,包括基础URL、超时设置、连接数限制、认证信息、
|
||||||
|
* JSON处理配置、日志配置和重试策略等。
|
||||||
|
*
|
||||||
|
* @property baseUrl 基础URL地址,所有请求将基于此URL进行构建
|
||||||
|
* @property connectTimeout 连接超时时间,默认5秒
|
||||||
|
* @property requestTimeout 请求超时时间,默认30秒
|
||||||
|
* @property socketTimeout Socket超时时间,默认30秒
|
||||||
|
* @property maxConnections 最大连接数,默认100
|
||||||
|
* @property auth 认证配置,支持无认证、API Key、Bearer Token和Basic认证
|
||||||
|
* @property json JSON序列化配置
|
||||||
|
* @property logging 日志配置
|
||||||
|
* @property retry 重试策略配置
|
||||||
|
* @since 2025-08-13 14:54:44
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
data class KtorHttpClientConfig(
|
||||||
|
val baseUrl: String? = null,
|
||||||
|
val connectTimeout: Duration = Duration.ofSeconds(5),
|
||||||
|
val requestTimeout: Duration = Duration.ofSeconds(30),
|
||||||
|
val socketTimeout: Duration = Duration.ofSeconds(30),
|
||||||
|
val maxConnections: Int = 100,
|
||||||
|
val auth: Auth = Auth.None,
|
||||||
|
val json: Json = Json(),
|
||||||
|
val logging: Logging = Logging(),
|
||||||
|
val retry: Retry = Retry(),
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 认证接口,定义了不同类型的认证方式
|
||||||
|
*/
|
||||||
|
sealed interface Auth {
|
||||||
|
/**
|
||||||
|
* 无认证
|
||||||
|
*/
|
||||||
|
data object None : Auth
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Key认证
|
||||||
|
* @property header 认证头名称
|
||||||
|
* @property value 认证值
|
||||||
|
*/
|
||||||
|
data class ApiKey(val header: String, val value: String) : Auth
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bearer Token认证
|
||||||
|
* @property token 认证令牌
|
||||||
|
*/
|
||||||
|
data class Bearer(val token: String) : Auth
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic认证
|
||||||
|
* @property username 用户名
|
||||||
|
* @property password 密码
|
||||||
|
*/
|
||||||
|
data class Basic(val username: String, val password: String) : Auth
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON配置
|
||||||
|
*
|
||||||
|
* @property ignoreUnknownKeys 是否忽略未知的JSON字段,默认为true
|
||||||
|
* @property prettyPrint 是否格式化输出JSON,默认为false
|
||||||
|
* @property explicitNulls 是否显式输出null值,默认为false
|
||||||
|
*/
|
||||||
|
data class Json(
|
||||||
|
val ignoreUnknownKeys: Boolean = true,
|
||||||
|
val prettyPrint: Boolean = false,
|
||||||
|
val explicitNulls: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志配置
|
||||||
|
*
|
||||||
|
* @property enabled 是否启用日志,默认为true
|
||||||
|
* @property level 日志级别,默认为"INFO"
|
||||||
|
*/
|
||||||
|
data class Logging(val enabled: Boolean = true, val level: String = "INFO")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试策略配置
|
||||||
|
*
|
||||||
|
* @property enabled 是否启用重试机制,默认为false
|
||||||
|
* @property maxAttempts 最大重试次数,默认为3次
|
||||||
|
* @property initialBackoffMillis 初始退避时间(毫秒),默认为200毫秒
|
||||||
|
* @property jitterMillis 抖动时间(毫秒),默认为50毫秒
|
||||||
|
* @property retryOnStatus 需要重试的HTTP状态码集合,默认包含429, 500, 502, 503, 504
|
||||||
|
* @property retryOnNetworkError 是否在网络错误时重试,默认为true
|
||||||
|
*/
|
||||||
|
data class Retry(
|
||||||
|
val enabled: Boolean = false,
|
||||||
|
val maxAttempts: Int = 3,
|
||||||
|
val initialBackoffMillis: Long = 200,
|
||||||
|
val jitterMillis: Long = 50,
|
||||||
|
val retryOnStatus: Set<Int> = setOf(429, 500, 502, 503, 504),
|
||||||
|
val retryOnNetworkError: Boolean = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
package com.gewuyou.forgeboot.http.ktor.factory
|
||||||
|
|
||||||
|
import com.gewuyou.forgeboot.http.ktor.entities.KtorHttpClientConfig
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.*
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
|
import io.ktor.client.plugins.logging.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP客户工厂
|
||||||
|
*
|
||||||
|
* 用于创建和配置Ktor HTTP客户端的工厂对象。提供了多种创建方式,
|
||||||
|
* 支持使用已实例化的引擎或引擎工厂来创建客户端,并统一配置插件和默认请求设置。
|
||||||
|
*
|
||||||
|
* @since 2025-08-13 14:56:45
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
object HttpClientFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用已实例化的引擎创建HTTP客户端
|
||||||
|
*
|
||||||
|
* 由调用方完全掌控引擎的配置与生命周期
|
||||||
|
*
|
||||||
|
* @param engine 已实例化的HTTP客户端引擎
|
||||||
|
* @param conf Ktor客户端配置
|
||||||
|
* @return 配置好的HttpClient实例
|
||||||
|
*/
|
||||||
|
fun create(engine: HttpClientEngine, conf: KtorHttpClientConfig): HttpClient =
|
||||||
|
build(HttpClient(engine), conf)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用引擎工厂创建HTTP客户端
|
||||||
|
*
|
||||||
|
* 由Ktor负责创建和管理引擎,适用于简单场景
|
||||||
|
*
|
||||||
|
* @param engineFactory HTTP客户端引擎工厂
|
||||||
|
* @param conf Ktor客户端配置
|
||||||
|
* @param configureEngine 引擎配置函数,可选
|
||||||
|
* @return 配置好的HttpClient实例
|
||||||
|
*/
|
||||||
|
fun <TConfig : HttpClientEngineConfig> create(
|
||||||
|
engineFactory: HttpClientEngineFactory<TConfig>,
|
||||||
|
conf: KtorHttpClientConfig,
|
||||||
|
configureEngine: (TConfig.() -> Unit)? = null,
|
||||||
|
): HttpClient = build(HttpClient(engineFactory) {
|
||||||
|
configureEngine?.let { engine(it) }
|
||||||
|
}, conf)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一安装插件和配置默认请求
|
||||||
|
*
|
||||||
|
* 配置内容包括内容协商、日志记录、认证头、默认内容类型和基础URL等
|
||||||
|
*
|
||||||
|
* @param client HTTP客户端实例
|
||||||
|
* @param conf Ktor客户端配置
|
||||||
|
* @return 配置好的HttpClient实例
|
||||||
|
*/
|
||||||
|
private fun build(client: HttpClient, conf: KtorHttpClientConfig): HttpClient = client.config {
|
||||||
|
// 安装内容协商插件并配置JSON序列化
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(
|
||||||
|
Json {
|
||||||
|
ignoreUnknownKeys = conf.json.ignoreUnknownKeys
|
||||||
|
prettyPrint = conf.json.prettyPrint
|
||||||
|
explicitNulls = conf.json.explicitNulls
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据配置决定是否安装日志插件
|
||||||
|
if (conf.logging.enabled) {
|
||||||
|
install(Logging) {
|
||||||
|
logger = Logger.SIMPLE
|
||||||
|
level = when (conf.logging.level.uppercase()) {
|
||||||
|
"NONE" -> LogLevel.NONE
|
||||||
|
"HEADERS" -> LogLevel.HEADERS
|
||||||
|
"BODY" -> LogLevel.BODY
|
||||||
|
else -> LogLevel.INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置默认请求设置
|
||||||
|
defaultRequest {
|
||||||
|
// 根据认证配置添加相应的认证头
|
||||||
|
when (val a = conf.auth) {
|
||||||
|
is KtorHttpClientConfig.Auth.ApiKey -> headers.append(a.header, a.value)
|
||||||
|
is KtorHttpClientConfig.Auth.Bearer -> headers.append("Authorization", "Bearer ${a.token}")
|
||||||
|
is KtorHttpClientConfig.Auth.Basic -> {
|
||||||
|
val raw = "${a.username}:${a.password}"
|
||||||
|
val basic = java.util.Base64.getEncoder().encodeToString(raw.toByteArray())
|
||||||
|
headers.append("Authorization", "Basic $basic")
|
||||||
|
}
|
||||||
|
|
||||||
|
KtorHttpClientConfig.Auth.None -> {
|
||||||
|
// not required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置默认内容类型为JSON
|
||||||
|
if (contentType() == null) contentType(ContentType.Application.Json)
|
||||||
|
// 设置基础URL(当使用相对路径时生效)
|
||||||
|
conf.baseUrl?.let { url(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
package com.gewuyou.forgeboot.http.ktor.client
|
||||||
|
|
||||||
|
import com.gewuyou.forgeboot.http.ktor.entities.KtorHttpClientConfig
|
||||||
|
import com.gewuyou.forgeboot.http.ktor.factory.HttpClientFactory
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.mock.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
/**
|
||||||
|
*HttpClientFactoryTest
|
||||||
|
*
|
||||||
|
* @since 2025-08-13 15:45:50
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
class HttpClientFactoryTest {
|
||||||
|
@Test
|
||||||
|
fun `factory should apply baseUrl, auth header and default content-type`() = runTest {
|
||||||
|
var capturedUrl: String? = null
|
||||||
|
var capturedAuth: String? = null
|
||||||
|
var capturedContentType: String? = null
|
||||||
|
|
||||||
|
val engine = MockEngine { request ->
|
||||||
|
capturedUrl = request.url.toString()
|
||||||
|
capturedAuth = request.headers[HttpHeaders.Authorization]
|
||||||
|
capturedContentType = request.headers[HttpHeaders.ContentType] // 来自 defaultRequest 的默认 JSON
|
||||||
|
respond(
|
||||||
|
content = """{"ok":true}""",
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val conf = KtorHttpClientConfig(
|
||||||
|
baseUrl = "https://example.com",
|
||||||
|
auth = KtorHttpClientConfig.Auth.Bearer("TOKEN-XYZ"),
|
||||||
|
// 打开日志不会影响请求,但要保证装配不报错
|
||||||
|
logging = KtorHttpClientConfig.Logging(enabled = true, level = "INFO")
|
||||||
|
)
|
||||||
|
|
||||||
|
val client: HttpClient = HttpClientFactory.create(engine, conf)
|
||||||
|
|
||||||
|
// 不通过 SimpleHttpClient,直接用 HttpClient 也应该生效
|
||||||
|
client.get("/echo")
|
||||||
|
|
||||||
|
assertEquals("https://example.com/echo", capturedUrl)
|
||||||
|
assertEquals("Bearer TOKEN-XYZ", capturedAuth)
|
||||||
|
assertEquals(ContentType.Application.Json.toString(), capturedContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `factory create with engineFactory should accept engine configure lambda`() = runTest {
|
||||||
|
// 这里用 MockEngineFactory + MockEngineConfig 验证“引擎工厂”重载能正常工作
|
||||||
|
val engineFactory = MockEngine
|
||||||
|
var seen = false
|
||||||
|
|
||||||
|
val conf = KtorHttpClientConfig(baseUrl = "https://e.com")
|
||||||
|
|
||||||
|
val client = HttpClientFactory.create(engineFactory, conf) {
|
||||||
|
seen = true
|
||||||
|
addHandler { request ->
|
||||||
|
// 命中即证明客户端按 baseUrl 拼接了路径
|
||||||
|
if (request.url.toString() == "https://e.com/ping") {
|
||||||
|
respond(
|
||||||
|
content = """{"ok":true}""",
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
respond(
|
||||||
|
content = """{"ok":false}""",
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起请求,触发我们在 configureEngine 中注册的 handler
|
||||||
|
client.get("/ping")
|
||||||
|
|
||||||
|
assertTrue(seen, "engine configure lambda should be executed")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package com.gewuyou.forgeboot.http.ktor.client
|
||||||
|
|
||||||
|
import com.gewuyou.forgeboot.http.ktor.entities.KtorHttpClientConfig
|
||||||
|
import com.gewuyou.forgeboot.http.ktor.factory.HttpClientFactory
|
||||||
|
import com.gewuyou.forgeboot.http.ktor.withRetry
|
||||||
|
import io.ktor.client.call.*
|
||||||
|
import io.ktor.client.engine.mock.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleHttpClientTest {
|
||||||
|
@Test
|
||||||
|
fun `GET should retry on 503 and succeed on second attempt`() = runTest {
|
||||||
|
var attempts = 0
|
||||||
|
|
||||||
|
val engine = MockEngine { request ->
|
||||||
|
attempts++
|
||||||
|
if (request.url.fullPath == "/v1/ping") {
|
||||||
|
if (attempts == 1) {
|
||||||
|
respondError(HttpStatusCode.ServiceUnavailable)
|
||||||
|
} else {
|
||||||
|
respond(
|
||||||
|
content = """{"ok":true}""",
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
respondError(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val conf = KtorHttpClientConfig(
|
||||||
|
baseUrl = "https://api.example.com",
|
||||||
|
retry = KtorHttpClientConfig.Retry(
|
||||||
|
enabled = true, maxAttempts = 3, initialBackoffMillis = 1, jitterMillis = 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val client = HttpClientFactory.create(engine, conf)
|
||||||
|
val http = SimpleHttpClient(client, conf)
|
||||||
|
|
||||||
|
val res: Map<String, Boolean> = http.get("/v1/ping")
|
||||||
|
|
||||||
|
assertTrue(res["ok"] == true, "should parse JSON body to Map and get ok=true")
|
||||||
|
assertEquals(2, attempts, "should retry exactly once (503 -> 200)")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `POST should send JSON and parse response`() = runTest {
|
||||||
|
val engine = MockEngine { request ->
|
||||||
|
val bodyCt = request.body.contentType
|
||||||
|
assertEquals(ContentType.Application.Json, bodyCt?.withoutParameters())
|
||||||
|
assertEquals("https://api.example.com/v1/echo", request.url.toString())
|
||||||
|
|
||||||
|
respond(
|
||||||
|
content = """{"echo":true}""",
|
||||||
|
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val conf = KtorHttpClientConfig(baseUrl = "https://api.example.com")
|
||||||
|
val client = HttpClientFactory.create(engine, conf)
|
||||||
|
|
||||||
|
val res: Map<String, Boolean> = withRetry(conf) {
|
||||||
|
client.post("/v1/echo") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody("""{"x":1}""")
|
||||||
|
}
|
||||||
|
}.body()
|
||||||
|
|
||||||
|
assertTrue(res["echo"] == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -18,11 +18,14 @@ redisson-version = "3.50.0"
|
|||||||
org-pf4j-version = "3.13.0"
|
org-pf4j-version = "3.13.0"
|
||||||
org-pf4j-spring-version = "0.10.0"
|
org-pf4j-spring-version = "0.10.0"
|
||||||
org-yaml-snakeyaml-version = "2.4"
|
org-yaml-snakeyaml-version = "2.4"
|
||||||
jimmer-version = "0.9.101"
|
jimmer-version = "0.9.105"
|
||||||
|
ktor-version = "3.2.3"
|
||||||
|
junit-jupiter-version = "5.13.4"
|
||||||
[libraries]
|
[libraries]
|
||||||
org-babyfish-jimmer-springBootStarter = { group = "org.babyfish.jimmer", name = "jimmer-spring-boot-starter", version.ref = "jimmer-version" }
|
org-babyfish-jimmer-springBootStarter = { group = "org.babyfish.jimmer", name = "jimmer-spring-boot-starter", version.ref = "jimmer-version" }
|
||||||
kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-version" }
|
kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-version" }
|
||||||
kotlinxDatetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime-version" }
|
kotlinxDatetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime-version" }
|
||||||
|
kotlinxCorountinesTest = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version = "1.10.2" }
|
||||||
kotlinxSerialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJSON-version" }
|
kotlinxSerialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJSON-version" }
|
||||||
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-version" }
|
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-version" }
|
||||||
#kotlinxCoroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines-version" }
|
#kotlinxCoroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines-version" }
|
||||||
@ -39,6 +42,7 @@ springBootStarter-webflux = { group = "org.springframework.boot", name = "spring
|
|||||||
springBootStarter-jpa = { group = "org.springframework.boot", name = "spring-boot-starter-data-jpa" }
|
springBootStarter-jpa = { group = "org.springframework.boot", name = "spring-boot-starter-data-jpa" }
|
||||||
springBootStarter-validation = { group = "org.springframework.boot", name = "spring-boot-starter-validation" }
|
springBootStarter-validation = { group = "org.springframework.boot", name = "spring-boot-starter-validation" }
|
||||||
springBootStarter-redis = { group = "org.springframework.boot", name = "spring-boot-starter-data-redis" }
|
springBootStarter-redis = { group = "org.springframework.boot", name = "spring-boot-starter-data-redis" }
|
||||||
|
springBootStarter-test = { group = "org.springframework.boot", name = "spring-boot-starter-test" }
|
||||||
|
|
||||||
redisson-springBootStarter = { group = "org.redisson", name = "redisson-spring-boot-starter", version.ref = "redisson-version" }
|
redisson-springBootStarter = { group = "org.redisson", name = "redisson-spring-boot-starter", version.ref = "redisson-version" }
|
||||||
springBoot-configuration-processor = { group = "org.springframework.boot", name = "spring-boot-configuration-processor", version.ref = "spring-boot-version" }
|
springBoot-configuration-processor = { group = "org.springframework.boot", name = "spring-boot-configuration-processor", version.ref = "spring-boot-version" }
|
||||||
@ -57,6 +61,9 @@ jackson-module-kotlin = { group = "com.fasterxml.jackson.module", name = "jackso
|
|||||||
|
|
||||||
reactor-core = { group = "io.projectreactor", name = "reactor-core" }
|
reactor-core = { group = "io.projectreactor", name = "reactor-core" }
|
||||||
#org
|
#org
|
||||||
|
org-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit-jupiter-version" }
|
||||||
|
org-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit-jupiter-version" }
|
||||||
|
org-junit-platform = { group = "org.junit.platform", name = "junit-platform-launcher", version = "1.13.4" }
|
||||||
org-yaml-snakeyaml = { group = "org.yaml", name = "snakeyaml", version.ref = "org-yaml-snakeyaml-version" }
|
org-yaml-snakeyaml = { group = "org.yaml", name = "snakeyaml", version.ref = "org-yaml-snakeyaml-version" }
|
||||||
org-pf4j = { group = "org.pf4j", name = "pf4j", version.ref = "org-pf4j-version" }
|
org-pf4j = { group = "org.pf4j", name = "pf4j", version.ref = "org-pf4j-version" }
|
||||||
org-pf4jSpring = { group = "org.pf4j", name = "pf4j-spring", version.ref = "org-pf4j-spring-version" }
|
org-pf4jSpring = { group = "org.pf4j", name = "pf4j-spring", version.ref = "org-pf4j-spring-version" }
|
||||||
@ -68,6 +75,12 @@ jjwt-jackson = { module = "io.jsonwebtoken:jjwt-jackson", version.ref = "jjwt-ve
|
|||||||
# com
|
# com
|
||||||
com-github-benManes-caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine-version" }
|
com-github-benManes-caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine-version" }
|
||||||
# io
|
# io
|
||||||
|
io-ktor-clientCore = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor-version" }
|
||||||
|
io-ktor-clientContentNegotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor-version" }
|
||||||
|
io-ktor-serializationKotlinxJson = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor-version" }
|
||||||
|
io-ktor-clientLogging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor-version" }
|
||||||
|
io-ktor-clientCio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor-version" }
|
||||||
|
io-ktor-clientMock = { group = "io.ktor", name = "ktor-client-mock-jvm", version.ref = "ktor-version" }
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines-core"]
|
kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines-core"]
|
||||||
|
|||||||
@ -154,6 +154,7 @@ include(
|
|||||||
":forgeboot-demo:forgeboot-plugin-demo:forgeboot-plugin-demo-server",
|
":forgeboot-demo:forgeboot-plugin-demo:forgeboot-plugin-demo-server",
|
||||||
)
|
)
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
//region module plugin
|
//region module plugin
|
||||||
include(
|
include(
|
||||||
"forgeboot-plugin",
|
"forgeboot-plugin",
|
||||||
@ -164,6 +165,7 @@ project(":forgeboot-plugin").name = "forgeboot-plugin-spring-boot-starter"
|
|||||||
project(":forgeboot-plugin:forgeboot-plugin-core").name = "forgeboot-plugin-core"
|
project(":forgeboot-plugin:forgeboot-plugin-core").name = "forgeboot-plugin-core"
|
||||||
project(":forgeboot-plugin:forgeboot-plugin-spring").name = "forgeboot-plugin-spring"
|
project(":forgeboot-plugin:forgeboot-plugin-spring").name = "forgeboot-plugin-spring"
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
//region module cache
|
//region module cache
|
||||||
include(
|
include(
|
||||||
"forgeboot-cache",
|
"forgeboot-cache",
|
||||||
@ -176,3 +178,12 @@ project(":forgeboot-cache:forgeboot-cache-api").name = "forgeboot-cache-api"
|
|||||||
project(":forgeboot-cache:forgeboot-cache-impl").name = "forgeboot-cache-impl"
|
project(":forgeboot-cache:forgeboot-cache-impl").name = "forgeboot-cache-impl"
|
||||||
project(":forgeboot-cache:forgeboot-cache-autoconfigure").name = "forgeboot-cache-autoconfigure"
|
project(":forgeboot-cache:forgeboot-cache-autoconfigure").name = "forgeboot-cache-autoconfigure"
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
//region module http
|
||||||
|
include(
|
||||||
|
"forgeboot-http",
|
||||||
|
":forgeboot-http:forgeboot-http-ktor",
|
||||||
|
)
|
||||||
|
project(":forgeboot-http").name = "forgeboot-http"
|
||||||
|
project(":forgeboot-http:forgeboot-http-ktor").name = "forgeboot-http-ktor"
|
||||||
|
//endregion
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user