开箱即用的GO后台管理系统 Kratos Admin - 站内信

首页 编程分享 PHP丨JAVA丨OTHER 正文

喵个咪 转载 编程分享 2025-11-08 22:03:58

简介 开箱即用的GO后台管理系统 Kratos Admin - 站内信 在企业级后台管理系统中,站内信是核心沟通组件之一,承担着系统通知、用户互动、业务提醒等关键场景需求。基于 Go 语言微服务框架 Kra


开箱即用的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 已封装站内信全生命周期逻辑,核心流程如下:​

  1. 参数校验
  2. 数据组装
  3. 将站内信消息存入数据库;
  4. 将站内信消息分发给用户的收件箱;
  5. 通过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,或参与社区讨论获取支持。

项目代码

参考资料

转载链接:https://juejin.cn/post/7569183054154514470


Tags:


本篇评论 —— 揽流光,涤眉霜,清露烈酒一口话苍茫。


    声明:参照站内规则,不文明言论将会删除,谢谢合作。


      最新评论




ABOUT ME

Blogger:袅袅牧童 | Arkin

Ido:PHP攻城狮

WeChat:nnmutong

Email:nnmutong@icloud.com

标签云