Compare commits

...

6 Commits

Author SHA1 Message Date
45c9fc814a Merge pull request 'v1.0.0' (#4) from dev into master
Reviewed-on: #4
2026-04-09 05:16:04 +00:00
Timi
78163441dd v1.0.0
All checks were successful
CI / build-deploy (pull_request) Successful in 16s
CI / notify-on-failure (pull_request) Has been skipped
2026-04-09 13:14:20 +08:00
407dc13ac4 Merge pull request 'v1.0.0' (#3) from dev into master
Reviewed-on: #3
2026-04-09 04:09:16 +00:00
Timi
9762be1244 v1.0.0
Some checks failed
CI / build-deploy (pull_request) Failing after 51s
CI / notify-on-failure (pull_request) Successful in 0s
2026-04-09 12:08:24 +08:00
0c06bf16c2 Merge pull request 'v1.0.0' (#2) from dev into master
Reviewed-on: #2
2026-04-09 04:03:43 +00:00
Timi
971cad7365 v1.0.0
Some checks failed
CI / build-deploy (pull_request) Failing after 5s
CI / notify-on-failure (pull_request) Successful in 0s
2026-04-09 12:03:15 +08:00
2 changed files with 53 additions and 60 deletions

View File

@@ -52,25 +52,22 @@ jobs:
- name: Build project - name: Build project
run: | run: |
mvn -B -DskipTests clean package mvn -B -DskipTests clean package -P prod-linux
- name: Deploy service - name: Deploy service
if: success() if: success()
env: env:
HOST: host.docker.internal CONTAINER_NAME: ${{ vars.CONTAINER_NAME }}
APP_PATH: ${{ vars.APP_PATH }} CONTAINER_TARGET_PATH: ${{ vars.CONTAINER_TARGET_PATH }}
DOCKER_CONTAINER_NAME: ${{ vars.DOCKER_CONTAINER_NAME }}
SSHPASS: ${{ secrets.TIMI_SERVER_SSH_PWD }}
MAX_RETRIES: 3 MAX_RETRIES: 3
RETRY_DELAY: 10 RETRY_DELAY: 10
run: | run: |
if [ -z "$HOST" ] || [ -z "$APP_PATH" ] || [ -z "DOCKER_CONTAINER_NAME" ] || [ -z "$SSHPASS" ]; then if [ -z "$CONTAINER_NAME" ] || [ -z "$CONTAINER_TARGET_PATH" ]; then
echo "Missing production environment variables" echo "Missing production environment variables"
echo "Required: APP_PATH, DOCKER_CONTAINER_NAME, TIMI_SERVER_SSH_PWD" echo "Required: CONTAINER_NAME, CONTAINER_TARGET_PATH"
exit 1 exit 1
fi fi
# 重试函数
retry_command() { retry_command() {
local cmd="$1" local cmd="$1"
local desc="$2" local desc="$2"
@@ -79,10 +76,10 @@ jobs:
while [ $attempt -le $MAX_RETRIES ]; do while [ $attempt -le $MAX_RETRIES ]; do
echo "[$desc] Attempt $attempt/$MAX_RETRIES..." echo "[$desc] Attempt $attempt/$MAX_RETRIES..."
if eval "$cmd"; then if eval "$cmd"; then
echo " $desc succeeded" echo "OK: $desc succeeded"
return 0 return 0
fi fi
echo " $desc failed (attempt $attempt/$MAX_RETRIES)" echo "FAIL: $desc failed (attempt $attempt/$MAX_RETRIES)"
if [ $attempt -lt $MAX_RETRIES ]; then if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..." echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY sleep $RETRY_DELAY
@@ -90,16 +87,10 @@ jobs:
attempt=$((attempt + 1)) attempt=$((attempt + 1))
done done
echo " $desc failed after $MAX_RETRIES attempts" echo "FAIL: $desc failed after $MAX_RETRIES attempts"
return 1 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) version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId) artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar" jar_file="target/${artifact_id}-${version}.jar"
@@ -109,18 +100,26 @@ jobs:
exit 1 exit 1
fi fi
# 目标文件名(去掉版本号) if ! command -v docker >/dev/null 2>&1; then
target_jar="${artifact_id}.jar" echo "docker command not found in runner environment"
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 exit 1
fi fi
# 重启 Docker 服务(带重试) if ! docker inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
echo "Restarting Docker service: $DOCKER_SERVICE_NAME" echo "Docker container not found: $CONTAINER_NAME"
if ! retry_command "sshpass -e ssh $SSH_OPTS \"root@$HOST\" \"docker restart $DOCKER_SERVICE_NAME\"" "Docker restart"; then exit 1
fi
target_jar="${artifact_id}.jar"
container_target="${CONTAINER_TARGET_PATH%/}/$target_jar"
echo "Deploying $jar_file to container $CONTAINER_NAME:$container_target"
if ! retry_command "docker cp \"$jar_file\" \"$CONTAINER_NAME:$container_target\"" "Docker copy"; then
exit 1
fi
echo "Restarting Docker container: $CONTAINER_NAME"
if ! retry_command "docker restart \"$CONTAINER_NAME\"" "Docker restart"; then
exit 1 exit 1
fi fi
echo "Deployment completed successfully" echo "Deployment completed successfully"
@@ -142,7 +141,6 @@ jobs:
exit 1 exit 1
fi fi
# Use internal URL if available, fallback to public URL
if [ -n "$GITEA_INTERNAL_URL" ]; then if [ -n "$GITEA_INTERNAL_URL" ]; then
api_base_url="$GITEA_INTERNAL_URL" api_base_url="$GITEA_INTERNAL_URL"
echo "Using internal Gitea URL: $api_base_url" echo "Using internal Gitea URL: $api_base_url"
@@ -151,7 +149,6 @@ jobs:
echo "Using public Gitea URL: $api_base_url" echo "Using public Gitea URL: $api_base_url"
fi fi
# 获取构建产物信息
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version) version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId) artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
jar_file="target/${artifact_id}-${version}.jar" jar_file="target/${artifact_id}-${version}.jar"
@@ -179,17 +176,14 @@ jobs:
echo "API URL: $api_url" echo "API URL: $api_url"
echo "Target commit: $RELEASE_TARGET" echo "Target commit: $RELEASE_TARGET"
# 使用唯一临时文件避免跨 job 污染
release_response_file=$(mktemp /tmp/release_response_XXXXXX.json) release_response_file=$(mktemp /tmp/release_response_XXXXXX.json)
trap "rm -f $release_response_file" EXIT trap "rm -f $release_response_file" EXIT
# 创建 release带重试处理幂等性
release_id="" release_id=""
attempt=1 attempt=1
while [ $attempt -le $MAX_RETRIES ] && [ -z "$release_id" ]; do while [ $attempt -le $MAX_RETRIES ] && [ -z "$release_id" ]; do
echo "[Create release] Attempt $attempt/$MAX_RETRIES..." echo "[Create release] Attempt $attempt/$MAX_RETRIES..."
# 清空临时文件
> "$release_response_file" > "$release_response_file"
http_code=$(curl -sS -w "%{http_code}" -o "$release_response_file" -X POST "$api_url" \ http_code=$(curl -sS -w "%{http_code}" -o "$release_response_file" -X POST "$api_url" \
@@ -203,30 +197,27 @@ jobs:
echo "HTTP Status: $http_code" echo "HTTP Status: $http_code"
if [ "$http_code" = "201" ]; then if [ "$http_code" = "201" ]; then
# 提取第一个 id 字段的值,确保去除换行符
if command -v jq >/dev/null 2>&1; then if command -v jq >/dev/null 2>&1; then
release_id=$(echo "$response" | jq -r '.id' 2>/dev/null) release_id=$(echo "$response" | jq -r '.id' 2>/dev/null)
else else
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r') release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi fi
echo " Release created: id=$release_id" echo "OK: Release created: id=$release_id"
elif [ "$http_code" = "409" ]; then elif [ "$http_code" = "409" ]; then
# HTTP 409 Conflict: Release 已存在,获取现有的 release_id
echo "Release already exists (HTTP 409), fetching existing release..." 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 "[]") 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 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) release_id=$(echo "$existing" | jq -r ".[] | select(.tag_name==\"$RELEASE_TAG\") | .id" 2>/dev/null | head -1)
else else
release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r') release_id=$(echo "$existing" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 | tr -d '\n\r')
fi fi
if [ -n "$release_id" ]; then if [ -n "$release_id" ]; then
echo " Found existing release: id=$release_id" echo "OK: Found existing release: id=$release_id"
else else
echo " Could not find existing release id" echo "FAIL: Could not find existing release id"
fi fi
else else
echo "✗ Failed (HTTP $http_code)" echo "FAIL: Create release failed (HTTP $http_code)"
if [ $attempt -lt $MAX_RETRIES ]; then if [ $attempt -lt $MAX_RETRIES ]; then
echo "Retrying in ${RETRY_DELAY}s..." echo "Retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY sleep $RETRY_DELAY
@@ -236,17 +227,15 @@ jobs:
done done
if [ -z "$release_id" ]; then if [ -z "$release_id" ]; then
echo " Failed to create/find release after $MAX_RETRIES attempts" echo "FAIL: Failed to create or find release after $MAX_RETRIES attempts"
exit 1 exit 1
fi fi
# 上传 fat jar带重试
asset_name=$(basename "$jar_file") asset_name=$(basename "$jar_file")
echo "Uploading asset: $asset_name (size: $file_size bytes)" echo "Uploading asset: $asset_name (size: $file_size bytes)"
upload_url="$api_url/$release_id/assets?name=$asset_name" upload_url="$api_url/$release_id/assets?name=$asset_name"
echo "Upload URL: $upload_url" echo "Upload URL: $upload_url"
# 使用唯一临时文件避免跨 job 污染
asset_response_file=$(mktemp /tmp/asset_response_XXXXXX.json) asset_response_file=$(mktemp /tmp/asset_response_XXXXXX.json)
trap "rm -f $release_response_file $asset_response_file" EXIT trap "rm -f $release_response_file $asset_response_file" EXIT
@@ -255,10 +244,8 @@ jobs:
while [ $attempt -le $MAX_RETRIES ] && [ "$upload_success" = "false" ]; do while [ $attempt -le $MAX_RETRIES ] && [ "$upload_success" = "false" ]; do
echo "[Upload asset] Attempt $attempt/$MAX_RETRIES..." echo "[Upload asset] Attempt $attempt/$MAX_RETRIES..."
# 清空临时文件
> "$asset_response_file" > "$asset_response_file"
# Gitea API 要求使用 multipart/form-data 格式上传文件
http_code=$(curl -sS -w "%{http_code}" -o "$asset_response_file" -X POST "$upload_url" \ http_code=$(curl -sS -w "%{http_code}" -o "$asset_response_file" -X POST "$upload_url" \
-H "Authorization: token $GITEA_TOKEN" \ -H "Authorization: token $GITEA_TOKEN" \
--connect-timeout 30 \ --connect-timeout 30 \
@@ -267,9 +254,9 @@ jobs:
if [ "$http_code" = "201" ]; then if [ "$http_code" = "201" ]; then
upload_success=true upload_success=true
echo " Successfully uploaded: $asset_name" echo "OK: Successfully uploaded: $asset_name"
else else
echo " Upload failed (HTTP $http_code)" echo "FAIL: Upload failed (HTTP $http_code)"
cat "$asset_response_file" 2>/dev/null || true cat "$asset_response_file" 2>/dev/null || true
fi fi
@@ -281,7 +268,7 @@ jobs:
done done
if [ "$upload_success" = "false" ]; then if [ "$upload_success" = "false" ]; then
echo " Failed to upload asset after $MAX_RETRIES attempts" echo "FAIL: Failed to upload asset after $MAX_RETRIES attempts"
exit 1 exit 1
fi fi
@@ -306,7 +293,6 @@ jobs:
COMMIT_SHA: ${{ github.sha }} COMMIT_SHA: ${{ github.sha }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
SERVER_URL: ${{ github.server_url }} SERVER_URL: ${{ github.server_url }}
# 通知配置(按需启用)
WEBHOOK_URL: ${{ vars.NOTIFY_WEBHOOK_URL }} WEBHOOK_URL: ${{ vars.NOTIFY_WEBHOOK_URL }}
run: | run: |
echo "=========================================" echo "========================================="
@@ -324,11 +310,9 @@ jobs:
echo "" echo ""
echo "=========================================" echo "========================================="
# 发送 Webhook 通知(钉钉/企业微信/Slack 等)
if [ -n "$WEBHOOK_URL" ]; then if [ -n "$WEBHOOK_URL" ]; then
message="🚨 CI 部署失败\n\nPR: #$PR_NUMBER - $PR_TITLE\n分支: $SOURCE_BRANCH\n提交者: $AUTHOR\n\n请检查并决定:\n 重试 CI\n 回滚合并" message="CI 部署失败\n\nPR: #$PR_NUMBER - $PR_TITLE\n分支: $SOURCE_BRANCH\n提交者: $AUTHOR\n\n请检查并决定:\n- 重试 CI\n- 回滚合并"
# 通用 JSON 格式(适配大多数 Webhook
payload=$(cat <<EOF payload=$(cat <<EOF
{ {
"msgtype": "text", "msgtype": "text",
@@ -343,7 +327,7 @@ jobs:
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$payload" || echo "Warning: Failed to send notification" -d "$payload" || echo "Warning: Failed to send notification"
echo " Notification sent" echo "OK: Notification sent"
else else
echo "Note: Set vars.NOTIFY_WEBHOOK_URL to enable webhook notifications" echo "Note: Set vars.NOTIFY_WEBHOOK_URL to enable webhook notifications"
fi fi

27
pom.xml
View File

@@ -23,13 +23,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<repositories>
<repository>
<id>apache-maven</id>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</repositories>
<profiles> <profiles>
<profile> <profile>
<id>dev-windows</id> <id>dev-windows</id>
@@ -112,17 +105,33 @@
<configuration> <configuration>
<excludeDevtools>true</excludeDevtools> <excludeDevtools>true</excludeDevtools>
<mainClass>com.imyeyu.api.TimiServerAPI</mainClass> <mainClass>com.imyeyu.api.TimiServerAPI</mainClass>
<finalName>${project.artifactId}</finalName>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<repositories>
<repository>
<id>apache-maven</id>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
<repository>
<id>timi_nexus</id>
<url>https://nexus.imyeyu.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.imyeyu.spring</groupId> <groupId>com.imyeyu.spring</groupId>
<artifactId>timi-spring</artifactId> <artifactId>timi-spring</artifactId>
<version>0.0.9</version> <version>0.0.10</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.imyeyu.network</groupId> <groupId>com.imyeyu.network</groupId>