開箱即用的 GoWind Admin|風(fēng)行,企業(yè)級前后端一體中后臺框架:站內(nèi)信
在企業(yè)級后臺管理系統(tǒng)中,站內(nèi)信是核心溝通組件之一,承擔(dān)著系統(tǒng)通知、用戶互動(dòng)、業(yè)務(wù)提醒等關(guān)鍵場景需求?;?Go 語言微服務(wù)框架 Kratos 構(gòu)建的 Go Wind Admin,將站內(nèi)信模塊封裝為「開箱即用」的標(biāo)準(zhǔn)化組件,無需從零開發(fā)即可快速集成,大幅降低開發(fā)成本。
本文將從功能價(jià)值、技術(shù)設(shè)計(jì)、實(shí)操使用、擴(kuò)展場景四個(gè)維度,全面解析 Go Wind Admin 站內(nèi)信模塊。
一、Go Wind Admin 與站內(nèi)信的核心價(jià)值
1.1 Go Wind Admin 定位
Go Wind Admin 是基于B站 Kratos 微服務(wù)框架開發(fā)的企業(yè)級后臺管理系統(tǒng)解決方案,內(nèi)置用戶管理、權(quán)限控制、日志審計(jì)、配置中心等核心模塊,支持 Go 生態(tài)主流技術(shù)棧(GORM、Redis、ProtoBuf 等),主打「低代碼集成」與「高擴(kuò)展性」,適用于中小團(tuán)隊(duì)快速搭建后臺系統(tǒng)。
1.2 站內(nèi)信功能的核心場景
站內(nèi)信作為系統(tǒng)內(nèi)「非實(shí)時(shí)但可靠」的溝通載體,核心解決以下問題:
- 系統(tǒng)通知:如賬號狀態(tài)變更(禁用 / 啟用)、權(quán)限調(diào)整、訂單審核結(jié)果等業(yè)務(wù)通知;
- 用戶互動(dòng):如管理員向指定用戶發(fā)送定向提醒、用戶間基于系統(tǒng)的留言溝通;
- 消息追溯:所有消息持久化存儲,支持歷史查詢,滿足審計(jì)與問題排查需求;
- 低干擾觸達(dá):區(qū)別于短信 / 郵件的外部推送,站內(nèi)信僅在系統(tǒng)內(nèi)展示,避免用戶信息過載。
二、站內(nèi)信核心技術(shù)設(shè)計(jì)
Go Wind Admin 站內(nèi)信模塊遵循「簡潔可靠、易于擴(kuò)展」的設(shè)計(jì)原則,核心分為數(shù)據(jù)模型與業(yè)務(wù)邏輯兩層。
2.1 數(shù)據(jù)模型設(shè)計(jì)(Postgresql)
CREATE TABLE public.internal_messages (
id bigint generated by default as identity primary key COMMENT 'id',
created_at timestamp with time zone COMMENT '創(chuàng)建時(shí)間',
updated_at timestamp with time zone COMMENT '更新時(shí)間',
deleted_at timestamp with time zone COMMENT '刪除時(shí)間',
created_by bigint COMMENT '創(chuàng)建者ID',
updated_by bigint COMMENT '更新者ID',
deleted_by bigint COMMENT '刪除者ID',
tenant_id bigint COMMENT '租戶ID',
title varchar COMMENT '消息標(biāo)題',
content varchar COMMENT '消息內(nèi)容',
sender_id bigint COMMENT '發(fā)送者用戶ID',
category_id bigint COMMENT '分類ID',
status varchar default 'DRAFT'::character varying COMMENT '消息狀態(tài)',
type varchar default 'NOTIFICATION'::character varying COMMENT '消息類型'
) COMMENT '站內(nèi)信消息表';
CREATE TABLE public.internal_message_recipients (
id bigint generated by default as identity primary key COMMENT 'id',
created_at timestamp with time zone COMMENT '創(chuàng)建時(shí)間',
updated_at timestamp with time zone COMMENT '更新時(shí)間',
deleted_at timestamp with time zone COMMENT '刪除時(shí)間',
tenant_id bigint COMMENT '租戶ID',
message_id bigint COMMENT '站內(nèi)信內(nèi)容ID',
recipient_user_id bigint COMMENT '接收者用戶ID',
status varchar COMMENT '消息狀態(tài)',
received_at timestamp with time zone COMMENT '消息到達(dá)用戶收件箱的時(shí)間',
read_at timestamp with time zone COMMENT '用戶閱讀消息的時(shí)間'
) COMMENT '站內(nèi)信消息用戶接收信息表';
CREATE TABLE public.internal_message_categories (
id bigint generated by default as identity primary key COMMENT 'id',
created_at timestamp with time zone COMMENT '創(chuàng)建時(shí)間',
updated_at timestamp with time zone COMMENT '更新時(shí)間',
deleted_at timestamp with time zone COMMENT '刪除時(shí)間',
created_by bigint COMMENT '創(chuàng)建者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 '圖標(biāo)URL',
parent_id bigint
constraint internal_message_categories_in_8a268228b9922ecb0c6e7d2099d6aa98
references public.internal_message_categories
on delete set null
COMMENT '父節(jié)點(diǎn)ID'
) COMMENT '站內(nèi)信消息分類表';
目前,站內(nèi)信功能只設(shè)計(jì)了三張表,用于系統(tǒng)通知。
在 Go Wind Admin 中,數(shù)據(jù)模型已通過 Ent的Schema 進(jìn)行了定義,開發(fā)者可直接調(diào)用:
// 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("站內(nèi)信消息表"),
}
}
// Fields of the InternalMessage.
func (InternalMessage) Fields() []ent.Field {
return []ent.Field{
field.String("title").
Comment("消息標(biāo)題").
Optional().
Nillable(),
field.String("content").
Comment("消息內(nèi)容").
Optional().
Nillable(),
field.Uint32("sender_id").
Comment("發(fā)送者用戶ID").
Optional().
Nillable(),
field.Uint32("category_id").
Comment("分類ID").
Optional().
Nillable(),
field.Enum("status").
Comment("消息狀態(tài)").
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 核心業(yè)務(wù)邏輯
Go Wind Admin 已封裝站內(nèi)信全生命周期邏輯,核心流程如下:
- 參數(shù)校驗(yàn)
- 數(shù)據(jù)組裝
- 將站內(nèi)信消息存入數(shù)據(jù)庫;
- 將站內(nèi)信消息分發(fā)給用戶的收件箱;
- 通過SSE通知前端。
核心代碼片段(發(fā)送邏輯):
// app/admin/service/internal/service/internal_message_service.go
// SendMessage 發(fā)送消息
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 向客戶端發(fā)送通知消息
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 接口設(shè)計(jì)與使用
Go Wind Admin 站內(nèi)信模塊提供 RESTful 風(fēng)格 API,基于 ProtoBuf 定義接口規(guī)范,支持跨語言調(diào)用。
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";
// 站內(nèi)信消息管理服務(wù)
service InternalMessageService {
// 查詢站內(nèi)信消息列表
rpc ListMessage(pagination.PagingRequest) returns (internal_message.service.v1.ListInternalMessageResponse) {
option (google.api.http) = {
get: "/admin/v1/internal-message/messages"
};
}
// 查詢站內(nèi)信消息詳情
rpc GetMessage(internal_message.service.v1.GetInternalMessageRequest) returns (internal_message.service.v1.InternalMessage) {
option (google.api.http) = {
get: "/admin/v1/internal-message/messages/{id}"
};
}
// 更新站內(nèi)信消息
rpc UpdateMessage(internal_message.service.v1.UpdateInternalMessageRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
put: "/admin/v1/internal-message/messages/{data.id}"
body: "*"
};
}
// 刪除站內(nèi)信消息
rpc DeleteMessage(internal_message.service.v1.DeleteInternalMessageRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/admin/v1/internal-message/messages/{id}"
};
}
// 發(fā)送消息
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: "*"
};
}
// 將通知標(biāo)記為已讀
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 調(diào)用示例(curl)
(1)發(fā)送系統(tǒng)通知
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": "賬號權(quán)限更新",
"content": "您的賬號已添加「訂單審核」權(quán)限,生效時(shí)間:2024-10-01",
}'
響應(yīng)結(jié)果:
{
"messageId": 0
}
四、前端對接
在站內(nèi)信功能中,「實(shí)時(shí)性」是提升用戶體驗(yàn)的關(guān)鍵 —— 用戶無需刷新頁面,就能即時(shí)收到新消息提醒。這段代碼基于 SSE(Server-Sent Events,服務(wù)器發(fā)送事件) 實(shí)現(xiàn)前端實(shí)時(shí)通知接收,適配 Go Wind 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);
}
五、總結(jié)與展望
Go Wind Admin 站內(nèi)信模塊通過「標(biāo)準(zhǔn)化數(shù)據(jù)模型 + 封裝核心邏輯 + 開放 API 接口」,實(shí)現(xiàn)了「開箱即用」的特性,開發(fā)者無需關(guān)注底層存儲與流程設(shè)計(jì),僅需通過 API 即可快速集成。目前模塊已支持消息發(fā)送、讀取、過期清理等基礎(chǔ)功能,未來將進(jìn)一步優(yōu)化:
- 新增消息撤回功能(支持發(fā)送后 N 分鐘內(nèi)撤回);
- 支持消息標(biāo)簽(如「重要」「工作」),提升篩選效率;
- 集成消息搜索(基于 Elasticsearch),支持全文檢索。
若你在使用過程中遇到問題,可通過 Go Wind Admin 官方 GitHub 提交 Issue,或參與社區(qū)討論獲取支持。