Skip to content

Commit 25674f0

Browse files
authored
Merge pull request #29 from yyyCode/feat/notification-service-split
feat(notification): 拆分统一通知服务(message)并接入独立 CI 双链
2 parents c3dcfad + b62e9ce commit 25674f0

45 files changed

Lines changed: 2300 additions & 178 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 115 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,82 @@
1-
# OpenBlog:push 到 master 时编译后端、打包前端,并将静态资源与 Docker 相关文件同步到阿里云,在服务器执行 docker.sh 更新 Nginx 容器。
2-
# 后端由内网自托管 Runner 构建为 Docker 容器(openblog-business,8082)。
3-
#
4-
# 需在仓库 Settings → Secrets and variables → Actions 中配置:
5-
# SERVER_SSH_KEY 服务器 SSH 私钥(完整 PEM)
6-
# SERVER_REMOTE_HOST 公网 IP 或域名
7-
# SERVER_REMOTE_USER SSH 登录用户(如 root 或 deploy)
8-
# SERVER_TARGET 服务器上部署目录绝对路径(如 /opt/openblog/web),该目录下会有 dist、Dockerfile、docker.sh、nginx.conf
9-
#
10-
# 后端 Spring Boot 以 Docker 容器运行(openblog-business,8082),前端容器内 Nginx 通过 host.docker.internal 反代到宿主机 8082。
11-
# 前端容器默认映射宿主机端口 18088 → 容器 80,访问 http://IP:18088/ ,请在安全组放行 TCP 18088。
121
name: Build and deploy OpenBlog
132

143
on:
154
workflow_dispatch:
165
push:
176
branches:
187
- master
19-
# 文档(*.md)变更不触发部署;具体哪些 job 要跑由 jobs.changes 细粒度过滤
208
paths-ignore:
219
- '**/*.md'
2210

2311
jobs:
24-
# 检测变更范围,供后续 job 条件判断
12+
# 检测变更范围。business 与 message 两条链互不影响;共享契约(OpenBlog-api/**)变更会同时触发双链。
2513
changes:
2614
runs-on: ubuntu-latest
2715
outputs:
28-
backend: ${{ steps.filter.outputs.backend }}
16+
business: ${{ steps.filter.outputs.business }}
17+
message: ${{ steps.filter.outputs.message }}
2918
frontend: ${{ steps.filter.outputs.frontend }}
3019
steps:
3120
- uses: actions/checkout@v4
3221
- uses: dorny/paths-filter@v3
3322
id: filter
3423
with:
3524
filters: |
36-
backend:
25+
business:
3726
- 'OpenBlog-business/**'
3827
- 'OpenBlog-framework*/**'
39-
- 'OpenBlog-message/**'
40-
- 'OpenBlog-message-api/**'
28+
- 'OpenBlog-common/**'
29+
- 'OpenBlog-api/**'
4130
- 'deploy/business/**'
4231
- 'pom.xml'
32+
message:
33+
- 'OpenBlog-message/**'
34+
- 'OpenBlog-common/**'
35+
- 'OpenBlog-api/**'
36+
- 'deploy/message/**'
37+
- 'pom.xml'
4338
frontend:
4439
- 'vue/**'
4540
46-
build-backend:
41+
# ============ business 链(独立) ============
42+
build-business:
4743
needs: changes
48-
if: ${{ needs.changes.outputs.backend == 'true' || github.event_name == 'workflow_dispatch' }}
44+
if: ${{ needs.changes.outputs.business == 'true' || github.event_name == 'workflow_dispatch' }}
4945
runs-on: ubuntu-latest
5046
env:
5147
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
5248

5349
steps:
5450
- name: Checkout
5551
uses: actions/checkout@v4
56-
5752
- name: Set up JDK 17
5853
uses: actions/setup-java@v4
5954
with:
6055
java-version: "17"
6156
distribution: "temurin"
6257
cache: "maven"
63-
64-
- name: Build backend (Maven)
58+
- name: Build business (Maven)
6559
run: mvn -B -ntp package -DskipTests -pl OpenBlog-business -am
66-
67-
- name: Upload backend JAR
60+
- name: Upload business JAR
6861
uses: actions/upload-artifact@v4
6962
with:
70-
name: backend-jar
63+
name: business-jar
7164
path: OpenBlog-business/target/OpenBlog-business-*.jar
7265
retention-days: 1
7366

74-
deploy-backend:
75-
needs: build-backend
76-
if: ${{ needs.build-backend.result == 'success' || github.event_name == 'workflow_dispatch' }}
67+
deploy-business:
68+
needs: build-business
69+
if: ${{ needs.build-business.result == 'success' || github.event_name == 'workflow_dispatch' }}
7770
runs-on: openblog-backend
7871

7972
steps:
8073
- name: Checkout(获取 deploy/business 下的 Docker 部署文件)
8174
uses: actions/checkout@v4
82-
83-
- name: Download backend JAR
75+
- name: Download business JAR
8476
uses: actions/download-artifact@v4
8577
with:
86-
name: backend-jar
87-
path: backend
88-
78+
name: business-jar
79+
path: business
8980
- name: Deploy via Docker(构建镜像 + 重启容器)
9081
run: |
9182
set -euo pipefail
@@ -102,28 +93,113 @@ jobs:
10293
cp "$TARGET_JAR" "$TARGET_DIR/backup/OpenBlog-business-$(date +%Y%m%d-%H%M%S).jar" 2>/dev/null || true
10394
fi
10495
105-
# 3. 拷贝新 jar + Docker 部署文件(.dockerignore 缩小构建上下文)
106-
JAR_FILE=$(ls backend/OpenBlog-business-*.jar | head -1)
96+
# 3. 拷贝新 jar + Docker 部署文件
97+
JAR_FILE=$(ls business/OpenBlog-business-*.jar | head -1)
10798
cp "$JAR_FILE" "$TARGET_JAR"
10899
cp deploy/business/Dockerfile deploy/business/docker-compose.yml deploy/business/.dockerignore "$TARGET_DIR/"
109100
110-
# 4. Docker 构建并启动容器(兼容 compose v2「docker compose」/ v1「docker-compose」)
101+
# 4. Docker 构建并启动容器
111102
cd "$TARGET_DIR"
112-
docker info >/dev/null 2>&1 || { echo "!! Docker 守护进程未运行,请先启动 Docker(宝塔 → Docker 管理器)" >&2; exit 1; }
103+
docker info >/dev/null 2>&1 || { echo "!! Docker 守护进程未运行,请先启动 Docker" >&2; exit 1; }
113104
if docker compose version >/dev/null 2>&1; then
114105
DC="docker compose"
115106
elif docker-compose version >/dev/null 2>&1; then
116107
DC="docker-compose"
117108
else
118-
echo "!! 服务器上没有 docker compose / docker-compose,请先安装 Docker(宝塔 → Docker 管理器)" >&2
109+
echo "!! 服务器上没有 docker compose / docker-compose" >&2
119110
exit 1
120111
fi
121112
echo "==> 使用: $DC"
122113
$DC up -d --build --remove-orphans
123114
$DC ps
124-
125115
echo "==> Deployed OpenBlog-business via Docker"
126116
117+
# ============ message 链(独立,与 business 互不影响) ============
118+
build-message:
119+
needs: changes
120+
if: ${{ needs.changes.outputs.message == 'true' || github.event_name == 'workflow_dispatch' }}
121+
runs-on: ubuntu-latest
122+
env:
123+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
124+
125+
steps:
126+
- name: Checkout
127+
uses: actions/checkout@v4
128+
- name: Set up JDK 17
129+
uses: actions/setup-java@v4
130+
with:
131+
java-version: "17"
132+
distribution: "temurin"
133+
cache: "maven"
134+
- name: Build message (Maven)
135+
run: mvn -B -ntp package -DskipTests -pl OpenBlog-message -am
136+
- name: Upload message JAR
137+
uses: actions/upload-artifact@v4
138+
with:
139+
name: message-jar
140+
path: OpenBlog-message/target/OpenBlog-message-*.jar
141+
retention-days: 1
142+
143+
deploy-message:
144+
needs: build-message
145+
if: ${{ needs.build-message.result == 'success' || github.event_name == 'workflow_dispatch' }}
146+
runs-on: openblog-backend
147+
148+
steps:
149+
- name: Checkout(获取 deploy/message 下的 Docker 部署文件)
150+
uses: actions/checkout@v4
151+
- name: Download message JAR
152+
uses: actions/download-artifact@v4
153+
with:
154+
name: message-jar
155+
path: message
156+
- name: Deploy via Docker(构建镜像 + 重启容器 + 注入阿里云凭据)
157+
env:
158+
ALIYUN_AK: ${{ secrets.ALIYUN_AK }}
159+
ALIYUN_SK: ${{ secrets.ALIYUN_SK }}
160+
ALIYUN_FROM: ${{ secrets.ALIYUN_FROM }}
161+
run: |
162+
set -euo pipefail
163+
TARGET_DIR="/www/wwwroot/java/openblog-message"
164+
TARGET_JAR="$TARGET_DIR/OpenBlog-message-1.0.0-SNAPSHOT.jar"
165+
166+
# 1. 停掉旧 宝塔/手动部署的 message 进程(释放 8083/20883;若无则该步 no-op)
167+
systemctl stop openblog-message 2>/dev/null || true
168+
169+
# 2. 备份旧 jar
170+
mkdir -p "$TARGET_DIR"
171+
if [ -f "$TARGET_JAR" ]; then
172+
mkdir -p "$TARGET_DIR/backup"
173+
cp "$TARGET_JAR" "$TARGET_DIR/backup/OpenBlog-message-$(date +%Y%m%d-%H%M%S).jar" 2>/dev/null || true
174+
fi
175+
176+
# 3. 拷贝新 jar + Docker 部署文件
177+
JAR_FILE=$(ls message/OpenBlog-message-*.jar | head -1)
178+
cp "$JAR_FILE" "$TARGET_JAR"
179+
cp deploy/message/Dockerfile deploy/message/docker-compose.yml deploy/message/.dockerignore "$TARGET_DIR/"
180+
181+
# 4. 阿里云凭据写入服务器 .env(compose 自动加载),供 message 容器读取
182+
printf 'ALIYUN_AK=%s\nALIYUN_SK=%s\nALIYUN_FROM=%s\n' \
183+
"$ALIYUN_AK" "$ALIYUN_SK" "$ALIYUN_FROM" \
184+
> "$TARGET_DIR/.env"
185+
186+
# 5. Docker 构建并启动容器
187+
cd "$TARGET_DIR"
188+
docker info >/dev/null 2>&1 || { echo "!! Docker 守护进程未运行,请先启动 Docker" >&2; exit 1; }
189+
if docker compose version >/dev/null 2>&1; then
190+
DC="docker compose"
191+
elif docker-compose version >/dev/null 2>&1; then
192+
DC="docker-compose"
193+
else
194+
echo "!! 服务器上没有 docker compose / docker-compose" >&2
195+
exit 1
196+
fi
197+
echo "==> 使用: $DC"
198+
$DC up -d --build --remove-orphans
199+
$DC ps
200+
echo "==> Deployed OpenBlog-message via Docker"
201+
202+
# ============ 前端链(原样保留) ============
127203
build-frontend:
128204
needs: changes
129205
if: ${{ needs.changes.outputs.frontend == 'true' || github.event_name == 'workflow_dispatch' }}
@@ -134,31 +210,26 @@ jobs:
134210
steps:
135211
- name: Checkout
136212
uses: actions/checkout@v4
137-
138213
- name: Set up Node.js
139214
uses: actions/setup-node@v4
140215
with:
141216
node-version: "22"
142217
cache: "npm"
143218
cache-dependency-path: vue/package-lock.json
144-
145219
- name: Install frontend dependencies and build
146220
working-directory: vue
147221
run: |
148222
npm ci
149223
npm run build
150-
151224
- name: Prepare release bundle for server
152225
run: |
153226
mkdir -p release
154227
cp -r vue/dist release/dist
155228
cp vue/Dockerfile vue/docker.sh vue/nginx.conf release/
156-
157229
- name: Build Docker image on runner
158230
run: |
159231
docker build -t openblog-web release/
160232
docker save openblog-web | gzip > release/openblog-web.tar.gz
161-
162233
- name: Upload release bundle
163234
uses: actions/upload-artifact@v4
164235
with:
@@ -176,7 +247,6 @@ jobs:
176247
with:
177248
name: release-frontend
178249
path: release/
179-
180250
- name: Deploy files to server
181251
uses: easingthemes/ssh-deploy@v5.1.1
182252
with:
@@ -187,7 +257,6 @@ jobs:
187257
REMOTE_USER: ${{ secrets.SERVER_REMOTE_USER }}
188258
TARGET: ${{ secrets.SERVER_TARGET }}
189259
SCRIPT_BEFORE: ls -la
190-
191260
- name: Run Docker commands on server
192261
uses: appleboy/ssh-action@v1.2.0
193262
with:

‎OpenBlog-api/OpenBlog-message-api/src/main/java/com/yqz/openblog/message/api/EmailRpcService.java‎

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.yqz.openblog.message.api;
2+
3+
import com.yqz.openblog.notification.NotificationMessage;
4+
5+
/**
6+
* 统一通知 RPC 接口(business 消费,message 提供)。
7+
* <p>
8+
* 与旧 EmailRPC 服务的区别:入参是渠道无关的 {@link NotificationMessage},
9+
* 由 message 内部按渠道路由投递;同步返回结果对象(显式错误码,不依赖 Dubbo 异常传播)。
10+
*/
11+
public interface NotificationRpcService {
12+
13+
/** 注册验证码模板 code(共享常量:business 构造消息与 message 渲染共用同一来源)。 */
14+
String TEMPLATE_REGISTER_VERIFICATION_CODE = "register-verification-code";
15+
16+
/** 邮件渠道不可用(同步链路降级码,随 result.errorCode 跨服务回传,business 映射回 BizException(5002))。 */
17+
int ERROR_CODE_EMAIL_UNAVAILABLE = 5002;
18+
19+
/** 通用内部错误(未预期的非业务异常,如 DB/序列化等)。 */
20+
int ERROR_CODE_INTERNAL = 5000;
21+
22+
/** 同步提交一条通知并投递。失败时返回 result.success=false + 错误码/消息。 */
23+
NotificationSendResult submit(NotificationMessage message);
24+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.yqz.openblog.message.api;
2+
3+
import java.io.Serializable;
4+
5+
/**
6+
* 通知提交结果(跨服务显式错误语义)。
7+
* <p>
8+
* success=false 时携带 errorCode / errorMsg(如 5002「邮件服务暂不可用」),
9+
* 供 business 侧映射回 {@code BizException} 并做相应清理。
10+
*/
11+
public class NotificationSendResult implements Serializable {
12+
13+
private static final long serialVersionUID = 1L;
14+
15+
private boolean success;
16+
private Integer errorCode;
17+
private String errorMsg;
18+
19+
public NotificationSendResult() {}
20+
21+
public NotificationSendResult(boolean success, Integer errorCode, String errorMsg) {
22+
this.success = success;
23+
this.errorCode = errorCode;
24+
this.errorMsg = errorMsg;
25+
}
26+
27+
public static NotificationSendResult ok() {
28+
return new NotificationSendResult(true, null, null);
29+
}
30+
31+
public static NotificationSendResult fail(int errorCode, String errorMsg) {
32+
return new NotificationSendResult(false, errorCode, errorMsg);
33+
}
34+
35+
public boolean isSuccess() { return success; }
36+
public void setSuccess(boolean success) { this.success = success; }
37+
38+
public Integer getErrorCode() { return errorCode; }
39+
public void setErrorCode(Integer errorCode) { this.errorCode = errorCode; }
40+
41+
public String getErrorMsg() { return errorMsg; }
42+
public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; }
43+
}

OpenBlog-business/src/main/java/com/yqz/openblog/notification/NotificationChannelType.java renamed to OpenBlog-api/OpenBlog-message-api/src/main/java/com/yqz/openblog/notification/NotificationChannelType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/**
44
* 通知渠道类型。
55
* <p>
6-
* 当前只实现 EMAIL(经 Dubbo 调 email 模块直发)。SMS / FEISHU 为未来扩展位,
6+
* 当前只实现 EMAIL(message 内进程直调 EmailService 投递)。SMS / FEISHU 为未来扩展位,
77
* 接入时新增枚举值 + 对应 {@link NotificationChannel} 策略 + 配置即可,主链路零改动。
88
*/
99
public enum NotificationChannelType {

OpenBlog-business/src/main/java/com/yqz/openblog/notification/NotificationMessage.java renamed to OpenBlog-api/OpenBlog-message-api/src/main/java/com/yqz/openblog/notification/NotificationMessage.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.yqz.openblog.notification;
22

3+
import java.io.Serializable;
34
import java.util.Map;
45

56
/**
@@ -9,7 +10,9 @@
910
* 经 {@link NotificationTemplateService} 渲染成各渠道内容。未来接 SMS/飞书时扩展 templateCode
1011
* 与模板即可,调用方无需感知渠道实现差异。
1112
*/
12-
public class NotificationMessage {
13+
public class NotificationMessage implements Serializable {
14+
15+
private static final long serialVersionUID = 1L;
1316

1417
/**
1518
* 全局幂等键(业务方生成,如 UUID):贯穿 outbox / MQ / 消费端投递。

0 commit comments

Comments
 (0)