This commit is contained in:
2026-08-12 18:23:57 +08:00
parent bac46a0f54
commit ae40d3f173
14 changed files with 339 additions and 9 deletions
@@ -0,0 +1,38 @@
package com.baiyue.iam.notification.dto;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
public final class NotificationDtos {
private NotificationDtos() { }
public enum RecipientType { USER, MEMBERSHIP, TENANT_ROLE, TENANT_ALL, PLATFORM_ROLE }
public record CreateNotificationRequest(
@NotNull Long tenantId,
@NotBlank @Size(max = 64) String templateCode,
@NotNull RecipientType recipientType,
@Size(max = 1000) List<Long> recipientIds,
@Size(max = 64) String roleCode,
@NotNull @Size(max = 32) Map<String, String> variables,
@Future LocalDateTime scheduledAt,
@NotBlank @Size(min = 8, max = 128) String idempotencyKey,
@Size(max = 128) String sourceEventId) { }
public record NotificationSummary(Long id, String category, String title, String status,
LocalDateTime sentAt, LocalDateTime readAt) { }
public record NotificationDetail(Long id, String category, String title, String body,
String status, String sourceType, String sourceId,
LocalDateTime sentAt, LocalDateTime readAt) { }
public record NotificationTaskResponse(Long id, String taskNo, String status, int recipientCount,
LocalDateTime scheduledAt, LocalDateTime sentAt,
boolean duplicated) { }
}
@@ -0,0 +1,29 @@
package com.baiyue.iam.notification.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter @Setter
@TableName("iam_notification_inbox")
public class NotificationInbox {
@TableId private Long id;
private Long taskId;
private Long recipientId;
private Long tenantId;
private Long membershipId;
private String appCode;
private String category;
private String title;
private String body;
private String sourceType;
private String sourceId;
private String status;
private LocalDateTime sentAt;
private LocalDateTime readAt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,26 @@
package com.baiyue.iam.notification.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter @Setter
@TableName("iam_notification_recipient")
public class NotificationRecipient {
@TableId private Long id;
private Long taskId;
private Long tenantId;
private Long membershipId;
private Long userId;
private String displayName;
private String appCode;
private String channel;
private String status;
private String failureReason;
private LocalDateTime deliveredAt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,32 @@
package com.baiyue.iam.notification.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter @Setter
@TableName("iam_notification_task")
public class NotificationTask {
@TableId private Long id;
private String taskNo;
private Long tenantId;
private Long templateId;
private String templateCode;
private String recipientType;
private String variablesJson;
private String idempotencyKey;
private String sourceEventId;
private String sourceEventType;
private String status;
private Integer recipientCount;
private Integer attemptCount;
private LocalDateTime scheduledAt;
private LocalDateTime sentAt;
private String lastError;
private Long createdBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,28 @@
package com.baiyue.iam.notification.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter @Setter
@TableName("iam_notification_template")
public class NotificationTemplate {
@TableId private Long id;
private Long tenantId;
private String templateCode;
private String templateName;
private String category;
private String channel;
private String titleTemplate;
private String bodyTemplate;
private String variableWhitelistJson;
private String status;
private Integer version;
private Long createdBy;
private Long updatedBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,5 @@
package com.baiyue.iam.notification.mapper;
import com.baiyue.iam.notification.entity.NotificationInbox;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface NotificationInboxMapper extends BaseMapper<NotificationInbox> { }
@@ -0,0 +1,5 @@
package com.baiyue.iam.notification.mapper;
import com.baiyue.iam.notification.entity.NotificationRecipient;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface NotificationRecipientMapper extends BaseMapper<NotificationRecipient> { }
@@ -0,0 +1,5 @@
package com.baiyue.iam.notification.mapper;
import com.baiyue.iam.notification.entity.NotificationTask;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface NotificationTaskMapper extends BaseMapper<NotificationTask> { }
@@ -0,0 +1,5 @@
package com.baiyue.iam.notification.mapper;
import com.baiyue.iam.notification.entity.NotificationTemplate;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface NotificationTemplateMapper extends BaseMapper<NotificationTemplate> { }
@@ -0,0 +1,113 @@
openapi: 3.0.3
info:
title: IAM Notification Center API
version: 1.0.0
description: First release is in-app notifications only. There is no clear-read/delete operation in this release.
servers:
- url: /api
tags:
- name: Notification inbox
- name: Platform notification administration
paths:
/{app}/v1/notifications:
get:
tags: [Notification inbox]
summary: List current user's inbox
parameters:
- {name: app, in: path, required: true, schema: {type: string, enum: [platform, merchant]}}
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 20}}
- {name: status, in: query, schema: {type: string, enum: [UNREAD, READ]}}
- {name: category, in: query, schema: {type: string, maxLength: 64}}
responses:
'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/InboxPageResponse'}}}}
/{app}/v1/notifications/unread-count:
get:
tags: [Notification inbox]
summary: Count current user's unread inbox messages
parameters:
- {name: app, in: path, required: true, schema: {type: string, enum: [platform, merchant]}}
responses:
'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/UnreadCountResponse'}}}}
/{app}/v1/notifications/{id}:
get:
tags: [Notification inbox]
summary: Get one inbox message
parameters:
- {name: app, in: path, required: true, schema: {type: string, enum: [platform, merchant]}}
- {name: id, in: path, required: true, schema: {type: integer, format: int64}}
responses:
'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/InboxDetailResponse'}}}}
/{app}/v1/notifications/{id}/read:
put:
tags: [Notification inbox]
summary: Mark one message as read (idempotent)
parameters:
- {name: app, in: path, required: true, schema: {type: string, enum: [platform, merchant]}}
- {name: id, in: path, required: true, schema: {type: integer, format: int64}}
responses:
'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/InboxDetailResponse'}}}}
/{app}/v1/notifications/read-all:
put:
tags: [Notification inbox]
summary: Mark all current-user messages as read (idempotent)
parameters:
- {name: app, in: path, required: true, schema: {type: string, enum: [platform, merchant]}}
responses:
'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/ReadAllResponse'}}}}
/platform/v1/notifications:
post:
tags: [Platform notification administration]
summary: Create an in-app notification task
responses:
'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/TaskResponse'}}}}
/platform/v1/notification-templates:
get:
tags: [Platform notification administration]
summary: List templates
parameters: [{name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}, {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 20}}, {name: status, in: query, schema: {type: string, enum: [DRAFT, ACTIVE, DISABLED]}}]
responses: {'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/TemplatePageResponse'}}}}}
post:
tags: [Platform notification administration]
summary: Create a DRAFT template
responses: {'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/TemplateResponse'}}}}}
/platform/v1/notification-templates/{id}:
put:
tags: [Platform notification administration]
summary: Edit a DRAFT template only
responses: {'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/TemplateResponse'}}}}}
/platform/v1/notification-templates/{id}/status:
put:
tags: [Platform notification administration]
summary: Activate or disable a template; only DRAFT->ACTIVE or ACTIVE->DISABLED
responses: {'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/TemplateResponse'}}}}}
/platform/v1/notification-tasks:
get:
tags: [Platform notification administration]
summary: List tasks
parameters: [{name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}, {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 20}}, {name: status, in: query, schema: {type: string, enum: [CREATED, SCHEDULED, DISPATCHING, RETRYING, SENT, DEAD, CANCELLED]}}]
responses: {'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/TaskPageResponse'}}}}}
/platform/v1/notification-tasks/{id}/cancel:
put:
tags: [Platform notification administration]
summary: Cancel CREATED or SCHEDULED task
responses: {'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/TaskResponse'}}}}}
/platform/v1/notification-tasks/{id}/retry:
put:
tags: [Platform notification administration]
summary: Retry RETRYING or DEAD task
responses: {'200': {description: OK, content: {application/json: {schema: {$ref: '#/components/schemas/TaskResponse'}}}}}
components:
schemas:
PageMeta: {type: object, required: [page, size, total, pages], properties: {page: {type: integer, format: int64}, size: {type: integer, format: int64}, total: {type: integer, format: int64}, pages: {type: integer, format: int64}}}
InboxItem: {type: object, properties: {id: {type: integer, format: int64}, category: {type: string}, title: {type: string}, status: {type: string, enum: [UNREAD, READ]}, sentAt: {type: string, format: date-time}, readAt: {type: string, format: date-time, nullable: true}}}
InboxDetail: {allOf: [{$ref: '#/components/schemas/InboxItem'}, {type: object, properties: {body: {type: string}, sourceType: {type: string}, sourceId: {type: string}}}]}
InboxPageResponse: {type: object, properties: {code: {type: string, example: '200'}, data: {type: object, properties: {records: {type: array, items: {$ref: '#/components/schemas/InboxItem'}}, page: {type: integer}, size: {type: integer}, total: {type: integer}, pages: {type: integer}}}}}
InboxDetailResponse: {type: object, properties: {code: {type: string, example: '200'}, data: {$ref: '#/components/schemas/InboxDetail'}}}
UnreadCountResponse: {type: object, properties: {code: {type: string, example: '200'}, data: {type: object, properties: {unreadCount: {type: integer, format: int64}}}}}
ReadAllResponse: {type: object, properties: {code: {type: string, example: '200'}, data: {type: object, properties: {updatedCount: {type: integer, format: int64}}}}}
CreateNotificationRequest: {type: object, required: [tenantId, templateCode, recipientType, variables, idempotencyKey], properties: {tenantId: {type: integer, format: int64, minimum: 0}, templateCode: {type: string, maxLength: 64}, recipientType: {type: string, enum: [USER, MEMBERSHIP, TENANT_ROLE, TENANT_ALL, PLATFORM_ROLE]}, recipientIds: {type: array, maxItems: 1000, items: {type: integer, format: int64}}, roleCode: {type: string, maxLength: 64}, variables: {type: object, additionalProperties: {type: string}}, scheduledAt: {type: string, format: date-time, nullable: true}, idempotencyKey: {type: string, minLength: 8, maxLength: 128}, sourceEventId: {type: string, maxLength: 128, nullable: true}}}
TemplateResponse: {type: object, properties: {id: {type: integer, format: int64}, templateCode: {type: string}, templateName: {type: string}, category: {type: string}, channel: {type: string, enum: [IN_APP]}, titleTemplate: {type: string}, bodyTemplate: {type: string}, variableWhitelist: {type: array, items: {type: string}}, status: {type: string, enum: [DRAFT, ACTIVE, DISABLED]}, version: {type: integer}}}
TemplatePageResponse: {type: object, properties: {code: {type: string, example: '200'}, data: {type: object, properties: {records: {type: array, items: {$ref: '#/components/schemas/TemplateResponse'}}, page: {type: integer}, size: {type: integer}, total: {type: integer}, pages: {type: integer}}}}}
TaskResponse: {type: object, properties: {id: {type: integer, format: int64}, taskNo: {type: string}, status: {type: string, enum: [CREATED, SCHEDULED, DISPATCHING, RETRYING, SENT, DEAD, CANCELLED]}, recipientCount: {type: integer}, scheduledAt: {type: string, format: date-time, nullable: true}, sentAt: {type: string, format: date-time, nullable: true}, duplicated: {type: boolean}}}
TaskPageResponse: {type: object, properties: {code: {type: string, example: '200'}, data: {type: object, properties: {records: {type: array, items: {$ref: '#/components/schemas/TaskResponse'}}, page: {type: integer}, size: {type: integer}, total: {type: integer}, pages: {type: integer}}}}}