开箱即用的GO后台管理系统 Kratos Admin - 站内信
在企业级后台管理系统中,站内信是核心沟通组件之一,承担着系统通知、用户互动、业务提醒等关键场景需求。基于 Go 语言微服务框架 Kratos 构建的 Kratos Admin,将站内信模块封装为「开箱即用」的标准化组件,无需从零开发即可快速集成,大幅降低开发成本。
本文将从功能价值、技术设计、实操使用、扩展场景四个维度,全面解析 Kratos Admin 站内信模块。
一、Kratos Admin 与站内信的核心价值
1.1 Kratos Admin 定位
Kratos Admin 是基于B站 Kratos 微服务框架开发的企业级后台管理系统解决方案,内置用户管理、权限控制、日志审计、配置中心等核心模块,支持 Go 生态主流技术栈(GORM、Redis、ProtoBuf 等),主打「低代码集成」与「高扩展性」,适用于中小团队快速搭建后台系统。
1.2 站内信功能的核心场景
站内信作为系统内「非实时但可靠」的沟通载体,核心解决以下问题:
- 系统通知:如账号状态变更(禁用 / 启用)、权限调整、订单审核结果等业务通知;
- 用户互动:如管理员向指定用户发送定向提醒、用户间基于系统的留言沟通;
- 消息追溯:所有消息持久化存储,支持历史查询,满足审计与问题排查需求;
- 低干扰触达:区别于短信 / 邮件的外部推送,站内信仅在系统内展示,避免用户信息过载。
二、站内信核心技术设计
Kratos Admin 站内信模块遵循「简洁可靠、易于扩展」的设计原则,核心分为数据模型与业务逻辑两层。
2.1 数据模型设计(Postgresql)
CREATE TABLE public.internal_messages (
id bigint generated by default as identity primary key COMMENT 'id',
created_at timestamp with time zone COMMENT '创建时间',
updated_at timestamp with time zone COMMENT '更新时间',
deleted_at timestamp with time zone COMMENT '删除时间',
created_by bigint COMMENT '创建者ID',
updated_by bigint COMMENT '更新者ID',
deleted_by bigint COMMENT '删除者ID',
tenant_id bigint COMMENT '租户ID',
title varchar COMMENT '消息标题',
content varchar COMMENT '消息内容',
sender_id bigint COMMENT '发送者用户ID',
category_id bigint COMMENT '分类ID',
status varchar default 'DRAFT'::character varying COMMENT '消息状态',
type varchar default 'NOTIFICATION'::character varying COMMENT '消息类型'
) COMMENT '站内信消息表';
CREATE TABLE public.internal_message_recipients (
id bigint generated by default as identity primary key COMMENT 'id',
created_at timestamp with time zone COMMENT '创建时间',
updated_at timestamp with time zone COMMENT '更新时间',
deleted_at timestamp with time zone COMMENT '删除时间',
tenant_id bigint COMMENT '租户ID',
message_id bigint COMMENT '站内信内容ID',
recipient_user_id bigint COMMENT '接收者用户ID',
status varchar COMMENT '消息状态',
received_at timestamp with time zone COMMENT '消息到达用户收件箱的时间',
read_at timestamp with time zone COMMENT '用户阅读消息的时间'
) COMMENT '站内信消息用户接收信息表';
CREATE TABLE public.internal_message_categories (
id bigint generated by default as identity primary key COMMENT 'id',
created_at timestamp with time zone COMMENT '创建时间',
updated_at timestamp with time zone COMMENT '更新时间',
deleted_at timestamp with time zone COMMENT '删除时间',
created_by bigint COMMENT '创建者ID',
updated_by bigint COMMENT '更新者ID',
deleted_by bigint COMMENT '删除者ID',
is_enabled boolean default true COMMENT '是否启用',
sort_order integer default 0 COMMENT '排序顺序,值越小越靠前',
remark varchar COMMENT '备注',
tenant_id bigint COMMENT '租户ID',
name varchar COMMENT '名称',
code varchar COMMENT '编码',
icon_url varchar COMMENT '图标URL',
parent_id bigint
constraint internal_message_categories_in_8a268228b9922ecb0c6e7d2099d6aa98
references public.internal_message_categories
on delete set null
COMMENT '父节点ID'
) COMMENT '站内信消息分类表';
目前,站内信功能只设计了三张表,用于系统通知。
在 Kratos Admin 中,数据模型已通过 Ent的Schema 进行了定义,开发者可直接调用:
// app/admin/service/internal/data/ent/schema/internal_message.go
package schema
import (
"entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"github.com/tx7do/go-utils/entgo/mixin"
)
// InternalMessage holds the schema definition for the InternalMessage entity.
type InternalMessage struct {
ent.Schema
}
func (InternalMessage) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{
Table: "internal_messages",
Charset: "utf8mb4",
Collation: "utf8mb4_bin",
},
entsql.WithComments(true),
schema.Comment("站内信消息表"),
}
}
// Fields of the InternalMessage.
func (InternalMessage) Fields() []ent.Field {
return []ent.Field{
field.String("title").
Comment("消息标题").
Optional().
Nillable(),
field.String("content").
Comment("消息内容").
Optional().
Nillable(),
field.Uint32("sender_id").
Comment("发送者用户ID").
Optional().
Nillable(),
field.Uint32("category_id").
Comment("分类ID").
Optional().
Nillable(),
field.Enum("status").
Comment("消息状态").
NamedValues(
"Draft", "DRAFT",
"Published", "PUBLISHED",
"Scheduled", "SCHEDULED",
"Revoked", "REVOKED",
"Archived", "ARCHIVED",
"Deleted", "DELETED",
).
Default("DRAFT").
Optional().
Nillable(),
field.Enum("type").
Comment("消息类型").
NamedValues(
"Notification", "NOTIFICATION",
"Private", "PRIVATE",
"Group", "GROUP",
).
Default("NOTIFICATION").
Optional().
Nillable(),
}
}
// Mixin of the InternalMessage.
func (InternalMessage) Mixin() []ent.Mixin {
return []ent.Mixin{
mixin.AutoIncrementId{},
mixin.TimeAt{},
mixin.OperatorID{},
mixin.TenantID{},
}
}
2.2 核心业务逻辑
Kratos Admin 已封装站内信全生命周期逻辑,核心流程如下:
- 参数校验
- 数据组装
- 将站内信消息存入数据库;
- 将站内信消息分发给用户的收件箱;
- 通过SSE通知前端。
核心代码片段(发送逻辑):
// app/admin/service/internal/service/internal_message_service.go
// SendMessage 发送消息
func (s *InternalMessageService) SendMessage(ctx context.Context, req *internalMessageV1.SendMessageRequest) (*internalMessageV1.SendMessageResponse, error) {
// 获取操作人信息
operator, err := auth.FromContext(ctx)
if err != nil {
return nil, err
}
now := time.Now()
var msg *internalMessageV1.InternalMessage
if msg, err = s.internalMessageRepo.Create(ctx, &internalMessageV1.CreateInternalMessageRequest{
Data: &internalMessageV1.InternalMessage{
Title: req.Title,
Content: trans.Ptr(req.GetContent()),
Status: trans.Ptr(internalMessageV1.InternalMessage_PUBLISHED),
Type: trans.Ptr(req.GetType()),
CategoryId: req.CategoryId,
CreatedBy: trans.Ptr(operator.GetUserId()),
CreatedAt: timeutil.TimeToTimestamppb(&now),
},
}); err != nil {
s.log.Errorf("create internal message failed: %s", err)
return nil, err
}
if req.GetTargetAll() {
users, err := s.userRepo.List(ctx, &pagination.PagingRequest{NoPaging: trans.Ptr(true)})
if err != nil {
s.log.Errorf("send message failed, list users failed, %s", err)
} else {
for _, user := range users.Items {
_ = s.sendNotification(ctx, msg.GetId(), user.GetId(), operator.GetUserId(), &now, msg.GetTitle(), msg.GetContent())
}
}
} else {
if req.RecipientUserId != nil {
_ = s.sendNotification(ctx, msg.GetId(), req.GetRecipientUserId(), operator.GetUserId(), &now, msg.GetTitle(), msg.GetContent())
} else {
if len(req.TargetUserIds) != 0 {
for _, uid := range req.TargetUserIds {
_ = s.sendNotification(ctx, msg.GetId(), uid, operator.GetUserId(), &now, msg.GetTitle(), msg.GetContent())
}
}
}
}
return &internalMessageV1.SendMessageResponse{
MessageId: msg.GetId(),
}, nil
}
// sendNotification 向客户端发送通知消息
func (s *InternalMessageService) sendNotification(ctx context.Context, messageId uint32, recipientUserId uint32, senderUserId uint32, now *time.Time, title, content string) error {
recipient := &internalMessageV1.InternalMessageRecipient{
MessageId: trans.Ptr(messageId),
RecipientUserId: trans.Ptr(recipientUserId),
Status: trans.Ptr(internalMessageV1.InternalMessageRecipient_SENT),
CreatedBy: trans.Ptr(senderUserId),
CreatedAt: timeutil.TimeToTimestamppb(now),
Title: trans.Ptr(title),
Content: trans.Ptr(content),
}
var err error
var entity *internalMessageV1.InternalMessageRecipient
if entity, err = s.internalMessageRecipientRepo.Create(ctx, recipient); err != nil {
s.log.Errorf("send message failed, send to user failed, %s", err)
return err
}
recipient.Id = entity.Id
recipientJson, _ := json.Marshal(recipient)
recipientStreamIds := s.userToken.GetAccessToken(ctx, recipientUserId)
for _, streamId := range recipientStreamIds {
s.sseServer.Publish(ctx, sse.StreamID(streamId), &sse.Event{
ID: []byte(uuid.New().String()),
Data: recipientJson,
Event: []byte("notification"),
})
}
return nil
}
三、API 接口设计与使用
Kratos Admin 站内信模块提供 RESTful 风格 API,基于 ProtoBuf 定义接口规范,支持跨语言调用。
3.1 核心 API 列表(Proto 定义)
// api/protos/admin/service/v1/i_internal_message.proto
syntax = "proto3";
package admin.service.v1;
import "gnostic/openapi/v3/annotations.proto";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
import "pagination/v1/pagination.proto";
import "internal_message/service/v1/internal_message.proto";
// 站内信消息管理服务
service InternalMessageService {
// 查询站内信消息列表
rpc ListMessage(pagination.PagingRequest) returns (internal_message.service.v1.ListInternalMessageResponse) {
option (google.api.http) = {
get: "/admin/v1/internal-message/messages"
};
}
// 查询站内信消息详情
rpc GetMessage(internal_message.service.v1.GetInternalMessageRequest) returns (internal_message.service.v1.InternalMessage) {
option (google.api.http) = {
get: "/admin/v1/internal-message/messages/{id}"
};
}
// 更新站内信消息
rpc UpdateMessage(internal_message.service.v1.UpdateInternalMessageRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
put: "/admin/v1/internal-message/messages/{data.id}"
body: "*"
};
}
// 删除站内信消息
rpc DeleteMessage(internal_message.service.v1.DeleteInternalMessageRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/admin/v1/internal-message/messages/{id}"
};
}
// 发送消息
rpc SendMessage(internal_message.service.v1.SendMessageRequest) returns (internal_message.service.v1.SendMessageResponse) {
option (google.api.http) = {
post: "/admin/v1/internal-message/send"
body: "*"
};
}
// 获取用户的收件箱列表 (通知类)
rpc ListUserInbox(pagination.PagingRequest) returns (internal_message.service.v1.ListUserInboxResponse) {
option (google.api.http) = {
get: "/admin/v1/internal-message/inbox"
};
}
// 删除用户收件箱中的通知记录
rpc DeleteNotificationFromInbox(internal_message.service.v1.DeleteNotificationFromInboxRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/admin/v1/internal-message/inbox/delete"
body: "*"
};
}
// 将通知标记为已读
rpc MarkNotificationAsRead(internal_message.service.v1.MarkNotificationAsReadRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/admin/v1/internal-message/read"
body: "*"
};
}
// 撤销某条消息
rpc RevokeMessage(internal_message.service.v1.RevokeMessageRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/admin/v1/internal-message/revoke"
body: "*"
};
}
}
3.2 API 调用示例(curl)
(1)发送系统通知
curl -X POST http://127.0.0.1:8000/api/v1/internal-message/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {admin_token}" \
-d '{
"type": "NOTIFICATION",
"recipientUserId": 0,
"conversationId": 0,
"categoryId": 0,
"targetAll": true,
"title": "账号权限更新",
"content": "您的账号已添加「订单审核」权限,生效时间:2024-10-01",
}'
响应结果:
{
"messageId": 0
}
四、前端对接
在站内信功能中,「实时性」是提升用户体验的关键 —— 用户无需刷新页面,就能即时收到新消息提醒。这段代码基于 SSE(Server-Sent Events,服务器发送事件) 实现前端实时通知接收,适配 Kratos Admin 后端的推送能力。
// apps/admin/src/layouts/basic.vue
function handleSseNotification(
data: InternalMessageRecipient,
event: MessageEvent,
) {
console.log('SSE', event, data);
if (!hasMessage(data)) {
notifications.value.unshift(convertInternalMessageRecipient(data));
}
}
function initSseClient() {
const targetSseUrl = `${import.meta.env.VITE_GLOB_SSE_URL}?stream=${encodeURIComponent(accessStore.accessToken)}`;
const sseClient = new SSEClient({
url: targetSseUrl,
withCredentials: false,
});
sseClient.connect();
sseClient.on<InternalMessageRecipient>('notification', handleSseNotification);
}
五、总结与展望
Kratos Admin 站内信模块通过「标准化数据模型 + 封装核心逻辑 + 开放 API 接口」,实现了「开箱即用」的特性,开发者无需关注底层存储与流程设计,仅需通过 API 即可快速集成。目前模块已支持消息发送、读取、过期清理等基础功能,未来将进一步优化:
- 新增消息撤回功能(支持发送后 N 分钟内撤回);
- 支持消息标签(如「重要」「工作」),提升筛选效率;
- 集成消息搜索(基于 Elasticsearch),支持全文检索。
若你在使用过程中遇到问题,可通过 Kratos Admin 官方 GitHub 提交 Issue,或参与社区讨论获取支持。
喵个咪 
![[爱了]](/js/img/d1.gif)
![[尴尬]](/js/img/d16.gif)