Skip to content

Commit e336adb

Browse files
committed
feat: 增加通知中心管理
1 parent 23dabdc commit e336adb

29 files changed

Lines changed: 1275 additions & 71 deletions
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Notification Center Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Build database-managed notification scenes, channel templates, send records, and manual retry APIs in `base-sysadmin`.
6+
7+
**Architecture:** Follow the existing Spring Boot and MyBatis-Plus feature package style. Add focused `po`, `form`, `vo`, `dao`, `service`, and `rest` classes under `notification`, then route sends through database templates and existing SMS/email providers.
8+
9+
**Tech Stack:** Java 17, Spring Boot, MyBatis-Plus, JUnit 5, AssertJ, Mockito, MySQL DDL scripts.
10+
11+
---
12+
13+
### Task 1: Notification Persistence Model
14+
15+
**Files:**
16+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/model/po/NotificationScene.java`
17+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/model/po/NotificationTemplateConfig.java`
18+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/model/po/NotificationRecord.java`
19+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/enums/NotificationSendStatus.java`
20+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/dao/NotificationSceneMapper.java`
21+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/dao/NotificationTemplateMapper.java`
22+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/dao/NotificationRecordMapper.java`
23+
- Modify: `src/main/resources/db/os-base-sysadmin-ddl.sql`
24+
- Modify: `src/main/resources/db/os-base-sysadmin-db.sql`
25+
26+
- [ ] Add PO classes with `@TableName`, Lombok builders, and fields matching the DDL.
27+
- [ ] Add mapper interfaces extending `BaseMapper`.
28+
- [ ] Add DDL for scene, template, and record tables.
29+
- [ ] Add seed rows for login captcha SMS/email scene templates.
30+
- [ ] Run `mvn -DskipTests compile`.
31+
32+
### Task 2: Notification Management Services
33+
34+
**Files:**
35+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/service/INotificationSceneService.java`
36+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/service/INotificationTemplateConfigService.java`
37+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/service/INotificationRecordService.java`
38+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/service/impl/NotificationSceneService.java`
39+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/service/impl/NotificationTemplateConfigService.java`
40+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/service/impl/NotificationRecordService.java`
41+
42+
- [ ] Implement scene CRUD by `sceneCode`, enabled listing, and validation.
43+
- [ ] Implement template CRUD by id and lookup by `sceneCode + channel`.
44+
- [ ] Implement record creation, query by id, page query, and retry counter updates.
45+
- [ ] Run focused service tests.
46+
47+
### Task 3: Runtime Send Flow
48+
49+
**Files:**
50+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/model/form/NotificationSendForm.java`
51+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/model/vo/NotificationSendResponse.java`
52+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/model/vo/NotificationPageData.java`
53+
- Modify: `src/main/java/io/github/opensabre/sysadmin/notification/service/INotificationService.java`
54+
- Modify: `src/main/java/io/github/opensabre/sysadmin/notification/service/NotificationServiceManager.java`
55+
- Modify: `src/main/java/io/github/opensabre/sysadmin/notification/service/impl/SmsNotificationService.java`
56+
- Modify: `src/main/java/io/github/opensabre/sysadmin/notification/service/impl/EmailNotificationService.java`
57+
58+
- [ ] Add content-based `sendContent(target, title, content)` support to channel services.
59+
- [ ] Add template rendering with named placeholders `{name}`.
60+
- [ ] Add send-by-scene logic that selects explicit channel or first enabled template by sort.
61+
- [ ] Persist success and failure records.
62+
- [ ] Implement manual retry for failed records.
63+
64+
### Task 4: REST APIs
65+
66+
**Files:**
67+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/rest/NotificationSceneController.java`
68+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/rest/NotificationTemplateController.java`
69+
- Create: `src/main/java/io/github/opensabre/sysadmin/notification/rest/NotificationRecordController.java`
70+
- Modify: `src/main/java/io/github/opensabre/sysadmin/notification/rest/NotificationController.java`
71+
72+
- [ ] Add scene CRUD endpoints.
73+
- [ ] Add template CRUD endpoints.
74+
- [ ] Add send endpoint using `NotificationSendForm`.
75+
- [ ] Add record page/detail/retry endpoints.
76+
- [ ] Keep controller naming consistent with existing modules.
77+
78+
### Task 5: Verification
79+
80+
**Files:**
81+
- Create: `src/test/java/io/github/opensabre/sysadmin/notification/service/NotificationServiceManagerTest.java`
82+
- Create: `src/test/java/io/github/opensabre/sysadmin/notification/model/NotificationModelTest.java`
83+
84+
- [ ] Test named placeholder rendering.
85+
- [ ] Test default template selection by sort.
86+
- [ ] Test failure record creation when the channel service throws.
87+
- [ ] Test retry rejects non-failed records.
88+
- [ ] Run `mvn -Dtest=NotificationServiceManagerTest,NotificationModelTest test`.
89+
- [ ] Run `mvn clean package -DskipTests`.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Notification Center Design
2+
3+
## Goal
4+
5+
Build a database-managed notification center for `base-sysadmin` so operators can manage notification scenes, channel templates, send records, and manual retries through APIs.
6+
7+
## Scope
8+
9+
The feature covers:
10+
11+
- Notification scenes, such as `LOGIN_CAPTCHA` or `ORDER_CREATED`.
12+
- Channel templates for each scene and channel, such as SMS and email.
13+
- Runtime send API based on `sceneCode` and optional `channel`.
14+
- Send records with template snapshots, arguments, status, message id, and failure reason.
15+
- Manual retry of failed records.
16+
17+
The first implementation does not include scheduled automatic retry. The table keeps `retry_count` and `next_retry_time` so automatic retry can be added later without changing the main data model.
18+
19+
## Data Model
20+
21+
`base_sys_notification_scene` stores a scene code, scene name, description, enabled flag, and audit columns.
22+
23+
`base_sys_notification_template` stores one template per scene and channel. It includes title, content, parameter schema text, sort order, enabled flag, and audit columns. A unique key on `scene_code + channel` prevents duplicate active channel definitions for a scene.
24+
25+
`base_sys_notification_record` stores every send attempt. It stores scene code, channel, target, template id, title snapshot, content snapshot, argument JSON, status, message id, failure reason, retry count, next retry time, sent time, and audit columns.
26+
27+
## API Design
28+
29+
Management APIs follow the existing `captcha` and `ratelimit` controller style:
30+
31+
- `GET /notification/scenes`
32+
- `GET /notification/scenes/enabled`
33+
- `GET /notification/scenes/{sceneCode}`
34+
- `POST /notification/scenes`
35+
- `PUT /notification/scenes/{sceneCode}`
36+
- `DELETE /notification/scenes/{sceneCode}`
37+
- `GET /notification/templates`
38+
- `GET /notification/templates/{id}`
39+
- `POST /notification/templates`
40+
- `PUT /notification/templates/{id}`
41+
- `DELETE /notification/templates/{id}`
42+
- `POST /notification/send`
43+
- `GET /notification/records`
44+
- `GET /notification/records/{id}`
45+
- `POST /notification/records/{id}/retry`
46+
47+
## Send Flow
48+
49+
The caller sends `target`, `sceneCode`, optional `channel`, and optional named `args`.
50+
51+
If `channel` is provided, the service uses the enabled template for that scene and channel. If `channel` is absent, it selects the first enabled template by `sort`.
52+
53+
Template rendering uses named placeholders such as `{code}`. Missing values are replaced with an empty string. The rendered content is passed to the existing channel service layer. The service records success or failure before returning.
54+
55+
## Compatibility
56+
57+
The existing enum template path is replaced as the main path by database templates. Existing channel implementations remain in place but gain content-based send support so runtime templates can be used without introducing provider-specific code.
58+
59+
## Validation And Errors
60+
61+
Scene code, scene name, channel, target, and template content are required. Disabled scenes or missing templates return clear argument errors. Unsupported channels return an argument error before any record is written.
62+
63+
Send failures are recorded with `FAILED` status and failure reason, then returned to the caller as a failed response. Manual retry only accepts records in `FAILED` status.
64+
65+
## Tests
66+
67+
Focused unit tests cover template rendering, default channel selection, send record creation, failed send retry behavior, and invalid scene/template errors. Model tests cover request and record defaults.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.opensabre.sysadmin.notification.dao;
2+
3+
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4+
import io.github.opensabre.sysadmin.notification.model.po.NotificationRecord;
5+
import org.apache.ibatis.annotations.Mapper;
6+
import org.springframework.stereotype.Repository;
7+
8+
@Repository
9+
@Mapper
10+
public interface NotificationRecordMapper extends BaseMapper<NotificationRecord> {
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.opensabre.sysadmin.notification.dao;
2+
3+
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4+
import io.github.opensabre.sysadmin.notification.model.po.NotificationScene;
5+
import org.apache.ibatis.annotations.Mapper;
6+
import org.springframework.stereotype.Repository;
7+
8+
@Repository
9+
@Mapper
10+
public interface NotificationSceneMapper extends BaseMapper<NotificationScene> {
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.opensabre.sysadmin.notification.dao;
2+
3+
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4+
import io.github.opensabre.sysadmin.notification.model.po.NotificationTemplateConfig;
5+
import org.apache.ibatis.annotations.Mapper;
6+
import org.springframework.stereotype.Repository;
7+
8+
@Repository
9+
@Mapper
10+
public interface NotificationTemplateMapper extends BaseMapper<NotificationTemplateConfig> {
11+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.github.opensabre.sysadmin.notification.enums;
2+
3+
public enum NotificationSendStatus {
4+
SUCCESS,
5+
FAILED
6+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.github.opensabre.sysadmin.notification.model.form;
2+
3+
import io.github.opensabre.sysadmin.notification.enums.NotificationType;
4+
import jakarta.validation.constraints.NotBlank;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.util.Map;
10+
11+
@Data
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
public class NotificationSendForm {
15+
16+
@NotBlank(message = "目标地址不能为空")
17+
private String target;
18+
19+
@NotBlank(message = "通知场景不能为空")
20+
private String sceneCode;
21+
22+
private NotificationType channel;
23+
24+
private Map<String, String> args;
25+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.github.opensabre.sysadmin.notification.model.po;
2+
3+
import com.baomidou.mybatisplus.annotation.TableName;
4+
import io.github.opensabre.persistence.entity.po.BasePo;
5+
import io.github.opensabre.sysadmin.notification.enums.NotificationSendStatus;
6+
import io.github.opensabre.sysadmin.notification.enums.NotificationType;
7+
import lombok.AllArgsConstructor;
8+
import lombok.Builder;
9+
import lombok.Data;
10+
import lombok.EqualsAndHashCode;
11+
import lombok.NoArgsConstructor;
12+
13+
import java.time.LocalDateTime;
14+
15+
@Data
16+
@Builder
17+
@NoArgsConstructor
18+
@AllArgsConstructor
19+
@TableName("base_sys_notification_record")
20+
@EqualsAndHashCode(callSuper = true)
21+
public class NotificationRecord extends BasePo {
22+
23+
private String sceneCode;
24+
25+
private NotificationType channel;
26+
27+
private String target;
28+
29+
private String templateId;
30+
31+
private String templateTitle;
32+
33+
private String templateContent;
34+
35+
private String argsJson;
36+
37+
private NotificationSendStatus status;
38+
39+
private String messageId;
40+
41+
private String failureReason;
42+
43+
private Integer retryCount;
44+
45+
private LocalDateTime nextRetryTime;
46+
47+
private LocalDateTime sentTime;
48+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.github.opensabre.sysadmin.notification.model.po;
2+
3+
import com.baomidou.mybatisplus.annotation.TableName;
4+
import io.github.opensabre.persistence.entity.po.BasePo;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Builder;
7+
import lombok.Data;
8+
import lombok.EqualsAndHashCode;
9+
import lombok.NoArgsConstructor;
10+
11+
@Data
12+
@Builder
13+
@NoArgsConstructor
14+
@AllArgsConstructor
15+
@TableName("base_sys_notification_scene")
16+
@EqualsAndHashCode(callSuper = true)
17+
public class NotificationScene extends BasePo {
18+
19+
private String sceneCode;
20+
21+
private String sceneName;
22+
23+
private String description;
24+
25+
private Boolean enabled;
26+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.github.opensabre.sysadmin.notification.model.po;
2+
3+
import com.baomidou.mybatisplus.annotation.TableName;
4+
import io.github.opensabre.persistence.entity.po.BasePo;
5+
import io.github.opensabre.sysadmin.notification.enums.NotificationType;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Data;
9+
import lombok.EqualsAndHashCode;
10+
import lombok.NoArgsConstructor;
11+
12+
@Data
13+
@Builder
14+
@NoArgsConstructor
15+
@AllArgsConstructor
16+
@TableName("base_sys_notification_template")
17+
@EqualsAndHashCode(callSuper = true)
18+
public class NotificationTemplateConfig extends BasePo {
19+
20+
private String sceneCode;
21+
22+
private NotificationType channel;
23+
24+
private String templateName;
25+
26+
private String title;
27+
28+
private String content;
29+
30+
private String paramSchema;
31+
32+
private Integer sort;
33+
34+
private Boolean enabled;
35+
}

0 commit comments

Comments
 (0)