开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:站内信
- 2025-12-27 湖南
本文字数:7298 字
阅读完需:约 24 分钟
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:站内信
在企业级后台管理系统中,站内信是核心沟通组件之一,承担着系统通知、用户互动、业务提醒等关键场景需求。基于 Go 语言微服务框架 Kratos 构建的 Go Wind Admin,将站内信模块封装为「开箱即用」的标准化组件,无需从零开发即可快速集成,大幅降低开发成本。
本文将从功能价值、技术设计、实操使用、扩展场景四个维度,全面解析 Go Wind Admin 站内信模块。
一、Go Wind Admin 与站内信的核心价值
1.1 Go Wind Admin 定位
Go Wind Admin 是基于 B 站 Kratos 微服务框架开发的企业级后台管理系统解决方案,内置用户管理、权限控制、日志审计、配置中心等核心模块,支持 Go 生态主流技术栈(GORM、Redis、ProtoBuf 等),主打「低代码集成」与「高扩展性」,适用于中小团队快速搭建后台系统。
1.2 站内信功能的核心场景
站内信作为系统内「非实时但可靠」的沟通载体,核心解决以下问题:
系统通知:如账号状态变更(禁用 / 启用)、权限调整、订单审核结果等业务通知;
用户互动:如管理员向指定用户发送定向提醒、用户间基于系统的留言沟通;
消息追溯:所有消息持久化存储,支持历史查询,满足审计与问题排查需求;
低干扰触达:区别于短信 / 邮件的外部推送,站内信仅在系统内展示,避免用户信息过载。
二、站内信核心技术设计
Go Wind 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 '站内信消息分类表';
目前,站内信功能只设计了三张表,用于系统通知。
在 Go Wind Admin 中,数据模型已通过 Ent 的 Schema 进行了定义,开发者可直接调用:
// app/admin/service/internal/data/ent/schema/internal_message.gopackage 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 核心业务逻辑
Go Wind 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 接口设计与使用
Go Wind Admin 站内信模块提供 RESTful 风格 API,基于 ProtoBuf 定义接口规范,支持跨语言调用。
3.1 核心 API 列表(Proto 定义)
// api/protos/admin/service/v1/i_internal_message.protosyntax = "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,服务器发送事件) 实现前端实时通知接收,适配 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);}
五、总结与展望
Go Wind Admin 站内信模块通过「标准化数据模型 + 封装核心逻辑 + 开放 API 接口」,实现了「开箱即用」的特性,开发者无需关注底层存储与流程设计,仅需通过 API 即可快速集成。目前模块已支持消息发送、读取、过期清理等基础功能,未来将进一步优化:
新增消息撤回功能(支持发送后 N 分钟内撤回);
支持消息标签(如「重要」「工作」),提升筛选效率;
集成消息搜索(基于 Elasticsearch),支持全文检索。
若你在使用过程中遇到问题,可通过 Go Wind Admin 官方 GitHub 提交 Issue,或参与社区讨论获取支持。
项目代码
参考资料
版权声明: 本文为 InfoQ 作者【喵个咪】的原创文章。
原文链接:【http://xie.infoq.cn/article/3bcb5567d10282ed9d3bcba6c】。文章转载请联系作者。
喵个咪
还未添加个人签名 2025-12-16 加入
还未添加个人简介







评论