Merge pull request 'v1.0.0' (#1) from dev into master

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-04-08 08:48:12 +00:00
2 changed files with 349 additions and 2 deletions

349
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,349 @@
name: CI
on:
pull_request:
branches:
- master
types:
- closed
jobs:
build-deploy:
runs-on: act_runner_java
if: ${{ github.event.pull_request.merged == true }}
outputs:
deployment_status: ${{ steps.set_status.outputs.status }}
env:
JAVA_HOME: /usr/lib/jvm/java-21-openjdk
steps:
- name: Checkout code
uses: actions/checkout@v4
- 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: Setup Maven settings
run: |
if [ -z "${{ vars.TIMI_NEXUS_USERNAME }}" ] || [ -z "${{ vars.TIMI_NEXUS_PASSWORD }}" ]; then
echo "Missing vars.TIMI_NEXUS_USERNAME or vars.TIMI_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>${{ vars.TIMI_NEXUS_USERNAME }}</username>
<password>${{ vars.TIMI_NEXUS_PASSWORD }}</password>
</server>
</servers>
</settings>
EOF
- name: Build project
run: |
mvn -B -DskipTests clean package
- name: Deploy service
if: success()
env:
HOST: host.docker.internal
APP_PATH: ${{ vars.APP_PATH }}
DOCKER_CONTAINER_NAME: ${{ vars.DOCKER_CONTAINER_NAME }}
SSHPASS: ${{ secrets.TIMI_SERVER_SSH_PWD }}
MAX_RETRIES: 3
RETRY_DELAY: 10
run: |
if [ -z "$HOST" ] || [ -z "$APP_PATH" ] || [ -z "DOCKER_CONTAINER_NAME" ] || [ -z "$SSHPASS" ]; then
echo "Missing production environment variables"
echo "Required: APP_PATH, DOCKER_CONTAINER_NAME, TIMI_SERVER_SSH_PWD"
exit 1
fi
# 重试函数
retry_command() {
local cmd="$1"
local desc="$2"
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
echo "[$desc] Attempt $attempt/$MAX_RETRIES..."
if eval "$cmd"; then
echo "✓ $desc succeeded"
return 0
fi
echo "✗ $desc failed (attempt $attempt/$MAX_RETRIES)"
if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
attempt=$((attempt + 1))
done
echo "✗ $desc failed after $MAX_RETRIES attempts"
return 1
}
# SSH 配置(使用密码认证)
SSH_PORT="22"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30 -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -p $SSH_PORT"
SCP_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30 -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -P $SSH_PORT"
# 获取构建产物信息
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar"
if [ ! -f "$jar_file" ]; then
echo "Build artifact not found: $jar_file"
exit 1
fi
# 目标文件名(去掉版本号)
target_jar="${artifact_id}.jar"
echo "Deploying $jar_file to $HOST:$APP_PATH/$target_jar"
# 上传文件(带重试)
if ! retry_command "sshpass -e scp $SCP_OPTS \"$jar_file\" \"root@$HOST:$APP_PATH/$target_jar\"" "SCP upload"; then
exit 1
fi
# 重启 Docker 服务(带重试)
echo "Restarting Docker service: $DOCKER_SERVICE_NAME"
if ! retry_command "sshpass -e ssh $SSH_OPTS \"root@$HOST\" \"docker restart $DOCKER_SERVICE_NAME\"" "Docker restart"; then
exit 1
fi
echo "Deployment completed successfully"
- 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_INTERNAL_URL: ${{ vars.TIMI_GITEA_INTERNAL_URL }}
GITEA_REPOSITORY: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.pull_request.title }}
RELEASE_TARGET: ${{ github.sha }}
MAX_RETRIES: 3
RETRY_DELAY: 10
run: |
if [ -z "$GITEA_TOKEN" ]; then
echo "Missing secrets.RUNNER_TOKEN"
exit 1
fi
# Use internal URL if available, fallback to public URL
if [ -n "$GITEA_INTERNAL_URL" ]; then
api_base_url="$GITEA_INTERNAL_URL"
echo "Using internal Gitea URL: $api_base_url"
else
api_base_url="$GITEA_SERVER_URL"
echo "Using public Gitea URL: $api_base_url"
fi
# 获取构建产物信息
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar"
if [ ! -f "$jar_file" ]; then
echo "Build artifact not found: $jar_file"
exit 1
fi
file_size=$(stat -c%s "$jar_file" 2>/dev/null || stat -f%z "$jar_file" 2>/dev/null || echo "unknown")
echo "Found fat jar: $jar_file (size: $file_size bytes)"
api_url="$api_base_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
)
echo "Creating release with tag: $RELEASE_TAG"
echo "API URL: $api_url"
echo "Target commit: $RELEASE_TARGET"
# 使用唯一临时文件避免跨 job 污染
release_response_file=$(mktemp /tmp/release_response_XXXXXX.json)
trap "rm -f $release_response_file" EXIT
# 创建 release带重试处理幂等性
release_id=""
attempt=1
while [ $attempt -le $MAX_RETRIES ] && [ -z "$release_id" ]; do
echo "[Create release] Attempt $attempt/$MAX_RETRIES..."
# 清空临时文件
> "$release_response_file"
http_code=$(curl -sS -w "%{http_code}" -o "$release_response_file" -X POST "$api_url" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
--connect-timeout 30 \
--max-time 60 \
-d "$payload" 2>/dev/null) || http_code="000"
response=$(cat "$release_response_file" 2>/dev/null || echo "{}")
echo "HTTP Status: $http_code"
if [ "$http_code" = "201" ]; then
# 提取第一个 id 字段的值,确保去除换行符
if command -v jq >/dev/null 2>&1; then
release_id=$(echo "$response" | jq -r '.id' 2>/dev/null)
else
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi
echo "✓ Release created: id=$release_id"
elif [ "$http_code" = "409" ]; then
# HTTP 409 Conflict: Release 已存在,获取现有的 release_id
echo "Release already exists (HTTP 409), fetching existing release..."
existing=$(curl -sS "$api_url" -H "Authorization: token $GITEA_TOKEN" --connect-timeout 30 2>/dev/null || echo "[]")
# 使用 jq 解析 JSON如果没有 jq 则用 grep
if command -v jq >/dev/null 2>&1; then
release_id=$(echo "$existing" | jq -r ".[] | select(.tag_name==\"$RELEASE_TAG\") | .id" 2>/dev/null | head -1)
else
release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi
if [ -n "$release_id" ]; then
echo "✓ Found existing release: id=$release_id"
else
echo "✗ Could not find existing release id"
fi
else
echo "✗ Failed (HTTP $http_code)"
if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
fi
attempt=$((attempt + 1))
done
if [ -z "$release_id" ]; then
echo "✗ Failed to create/find release after $MAX_RETRIES attempts"
exit 1
fi
# 上传 fat jar带重试
asset_name=$(basename "$jar_file")
echo "Uploading asset: $asset_name (size: $file_size bytes)"
upload_url="$api_url/$release_id/assets?name=$asset_name"
echo "Upload URL: $upload_url"
# 使用唯一临时文件避免跨 job 污染
asset_response_file=$(mktemp /tmp/asset_response_XXXXXX.json)
trap "rm -f $release_response_file $asset_response_file" EXIT
upload_success=false
attempt=1
while [ $attempt -le $MAX_RETRIES ] && [ "$upload_success" = "false" ]; do
echo "[Upload asset] Attempt $attempt/$MAX_RETRIES..."
# 清空临时文件
> "$asset_response_file"
# Gitea API 要求使用 multipart/form-data 格式上传文件
http_code=$(curl -sS -w "%{http_code}" -o "$asset_response_file" -X POST "$upload_url" \
-H "Authorization: token $GITEA_TOKEN" \
--connect-timeout 30 \
--max-time 300 \
-F "attachment=@$jar_file" 2>/dev/null) || http_code="000"
if [ "$http_code" = "201" ]; then
upload_success=true
echo "✓ Successfully uploaded: $asset_name"
else
echo "✗ Upload failed (HTTP $http_code)"
cat "$asset_response_file" 2>/dev/null || true
fi
if [ "$upload_success" = "false" ] && [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
attempt=$((attempt + 1))
done
if [ "$upload_success" = "false" ]; then
echo "✗ Failed to upload asset after $MAX_RETRIES attempts"
exit 1
fi
- name: Mark deployment success
id: set_status
if: always()
run: |
echo "status=success" >> $GITHUB_OUTPUT
notify-on-failure:
runs-on: act_runner_java
needs: build-deploy
if: ${{ always() && github.event.pull_request.merged == true && needs.build-deploy.result == 'failure' }}
steps:
- name: Notify CI failure
env:
PR_NUMBER: ${{ github.event.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
SOURCE_BRANCH: ${{ github.event.pull_request.head.ref }}
AUTHOR: ${{ github.event.pull_request.user.login }}
COMMIT_SHA: ${{ github.sha }}
REPO: ${{ github.repository }}
SERVER_URL: ${{ github.server_url }}
# 通知配置(按需启用)
WEBHOOK_URL: ${{ vars.NOTIFY_WEBHOOK_URL }}
run: |
echo "========================================="
echo "CI Pipeline Failed - Manual Review Required"
echo "========================================="
echo ""
echo "PR: #$PR_NUMBER - $PR_TITLE"
echo "Branch: $SOURCE_BRANCH"
echo "Author: $AUTHOR"
echo "Commit: $COMMIT_SHA"
echo ""
echo "Actions:"
echo " 1. Re-run CI: $SERVER_URL/$REPO/actions"
echo " 2. Revert PR: $PR_URL (click 'Revert' button)"
echo ""
echo "========================================="
# 发送 Webhook 通知(钉钉/企业微信/Slack 等)
if [ -n "$WEBHOOK_URL" ]; then
message="🚨 CI 部署失败\n\nPR: #$PR_NUMBER - $PR_TITLE\n分支: $SOURCE_BRANCH\n提交者: $AUTHOR\n\n请检查并决定:\n• 重试 CI\n• 回滚合并"
# 通用 JSON 格式(适配大多数 Webhook
payload=$(cat <<EOF
{
"msgtype": "text",
"text": {
"content": "$message"
}
}
EOF
)
curl -sS -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$payload" || echo "Warning: Failed to send notification"
echo "✓ Notification sent"
else
echo "Note: Set vars.NOTIFY_WEBHOOK_URL to enable webhook notifications"
fi

View File

@@ -1,6 +1,5 @@
package test;
import com.google.gson.Gson;
import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.modules.blog.entity.Article;
import com.imyeyu.api.modules.blog.mapper.ArticleMapper;
@@ -118,6 +117,5 @@ public class SpringTest {
for (int i = 0; i < icon.size(); i++) {
map.put(icon.get(i).getName(), icon.get(i).getSvg());
}
System.out.println(new Gson().toJson(map));
}
}