趁着周末闲隙,又给自己的项目加个小小的功能✨✨,想着如果每个项目都要维护自己的一套公告系统,不如构建一个公共的管理体系,让不同子系统进行接入,感兴趣的小伙伴可以参考下述构建思路,试着搭建一套属于自己的通用公告系统体系。
项目介绍
构建通用动态公告系统,后端管理员统一维护公告信息,对外提供接口根据域名或者其他参数配置构建通告联系(关联通知对象,例如根据域名区分子系统等)。前台通过引入通用公告SDK组件(请求调用后台接口获取通知,封装弹窗组件获取公告信息),不同子系统接入只需要一行代码的形式即可完成接入。
项目源码
- 动态公告前端SDK:项目源码、NPM发布
- 动态公告后端:参考itc-platform的itc-platform-backend工程的
Notification
模块
发布效果
管理员后台创建消息通知,前端(自定义页面)刷新页面触发消息通知弹窗
模块设计
1.数据表设计
根据业务场景设计一个最基础的通知信息体(动态公告)
create table notification
(
id bigint comment 'id' primary key,
title varchar(255) not null comment '公告标题',
content varchar(2048) not null comment '公告内容',
startTime datetime null comment '开始时间',
endTime datetime null comment '结束时间',
status tinyint default 0 not null comment '0: 关闭,1: 启用',
domain varchar(255) null comment '域名',
createTime datetime default CURRENT_TIMESTAMP null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP,
creater bigint null comment '创建者',
updater bigint null comment '修改者',
isDelete tinyint default 0 not null comment '是否删除'
);
2.实现说明
动态公告核心构建思路:
- 后台:对内维护公告信息,对外提供调用接口获取最新的公告信息
- 前台:通过引入自定义SDK,调用弹窗信息
动态公告引入概念:构建一个通用的公告系统,可以为每个系统接入相应的通知体系(设定相应的域名或者引入租户概念区分子系统),通过指定域名等必要参数获取到相应通知信息。提供公告系统后台管理模块,进行公告信息维护。前台则通过构建前端SDK的模式提供给各个系统使用,子系统只需要接入相应的JS,触发页面刷新或者提供相应的组件按钮触发即可获取通知内容
-
后台:通知/公告信息管理(按照现有项目模板构建公告的CRUD操作)
- 实现公告信息管理(基础的CRUD操作)
- 对外暴露一个接口用于获取通知信息(根据场景灵活适配),交由前端SDK进行调用触发
-
前台:
- 抽离公共的实现,前端SDK构建=》调用后台接口、获取通知弹窗
构建步骤
后台实现
后台管理部分按照数据表结构完成基础的CRUD操作,随后提供一个获取通知的接口(例如此处实现为根据domain获取最新的通知信息)
参考Mapper层的SQL实现:
SELECT
nt.id,
nt.title,
nt.content,
nt.startTime,
nt.creater,
nt.updater,
nt.domain,
nt.isDelete,
nt.createTime,
nt.updateTime,
cu.userName "createrUserName" ,
cu.userAccount "createrUserAccount",
uu.userName "updaterUserName",
uu.userAccount "updaterUserAccount"
FROM
notification nt
LEFT JOIN `user` cu ON cu.id = nt.creater
LEFT JOIN `user` uu ON uu.id = nt.updater
where nt.domain = #{domain}
order by nt.updateTime desc limit 1
前台实现(构建前端SDK)
创建vite项目进行构建:npm create vite@latest
根据提示初始化项目,然后选择一个第三方弹窗库实现效果,参考网站BootCDN,可以选择一个体积比较小、样式精美的组件库,此处选用swaeetalert2
npm create vite@latest
,创建一个react项目,配置细节参考Vite官方文档
创建完成更新依赖并启动项目:npm install
、npm run dev
,启动项目初始化访问主页默认是如下页面
项目启动没有问题,此处则可进一步构建自己的SDK,先导入所需要的弹窗依赖sweetalert2
导入sweetalert2
npm install sweetalert2
构建main.tsx(或者是main.jsx)具体看vite构建的时候选择基于的语法规则是什么
# 原有模板生成
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
此处需注意,方法定义和方法触发,如果说默认加载js的时候触发弹窗,则在此处定义中调用一次方法进行弹窗显示alertNotification();
,如果加载js后发现没有弹窗显示(F12刷新页面确认是否触发请求接口,如果没有请求则说明方法没有触发,进一步检查js定义)
此处在实现的时候本地将通知id+时间戳进行存储,用于避免一些通知重复弹窗。实现核心是在页面加载的时候从后端获取最新更新的通知信息,然后使用SweetAlert2进行弹窗显示,并将通知ID和更新时间存储到本地的localStrorage中,以避免重复提示。考虑到公告更新后localStrorage不会更新的问题,将key设置为id+时间戳(updateTIme)的格式,进而使得每次更新公告之后,js会请求校验然后更新key进而实现最新消息的弹窗提醒。参考实现如下:
# 实现参考(直接替换掉main.tsx)
import Swal from "sweetalert2";
function alertNotification() {
/**
* 后端地址(本地)
*/
// const BACKEND_HOST_LOCAL = "http://localhost:8101";
/**
* 后端地址(线上)
*/
// const BACKEND_HOST_PROD = "http://xxx.xxx";
function getNotificationVoUsingGet(params) {
const url = `http://localhost:8101/api/admin/cms/notification/getNotificationVOByDomain`;
return fetch(url + "?" + new URLSearchParams(params))
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => data)
.catch((error) => {
// 处理错误
console.error("Fetch request error:", error);
throw error;
});
}
const fetchNotification = function (domain) {
// 发起请求获取通知信息的逻辑
getNotificationVoUsingGet({ domain })
.then((response) => {
const data = response.data;
const id = data.id;
const updateTime = data.updateTime;
if (
!localStorage.getItem(id + updateTime) &&
data.title &&
data.content
) {
// 使用 SweetAlert2 显示弹窗
Swal.fire({
title: data.title,
text: data.content,
icon: "info",
confirmButtonText: "知道了",
});
// 存储到 localStorage
localStorage.setItem(id + updateTime, "id");
}
})
.catch((error) => {
console.error("Fetch request error:", error);
});
};
const url = new URL(location.href);
const domain = url.hostname;
fetchNotification(domain);
}
alertNotification();
SDK项目打包
通过npm run build方式打包项目,然后将生成的dist/assets/下的js文件进行上传(例如上传到腾讯云COS或者其他云存储进行测试)
PS:如果打包提示一些语法的问题,可能是eslint强校验导致,新手可以关闭强校验(修改tsconfig.json的strict:false进行关闭),或者严格规范代码编写的语法规则。打包成功之后可以看到生成js文件(dist/assets文件夹下的js),然后将其上传到云进行测试
子项目引用
方式1(全局配置):react项目,通过配置config/config.ts文件,在headScripts中加载js文件
方式2(局部加载):在指定的页面,通过useEffect设定在页面初始化的时候加载js文件,或者通过按钮组件触发加载
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://cos.holic-x.com/publish/index-D523rTEB.js';
script.defer = true;
document.head.appendChild(script);
}, []);
方式3(触发加载)todo:例如定义一个获取通知的按钮,在页面自动触发加载节点信息(此处需要前端SDK配合封装,对外提供一个可触发的方法,目前现有的实现是直接加载js节点触发弹窗效果)
子项目启动访问
此处触发的条件是通过解析域名去获取参数信息,可以查看请求接口的内容,然后对照要实现的接口。例如此处前端请求js,访问后台接口(根据域名获取通知信息)
测试后台接口:http://localhost:8101/api/admin/cms/notification/getNotificationVOByDomain?domain=localhost
前端加载JS查看是否正常请求后端接口:确认接口返回信息是否正常
通知弹窗确认
接口默认请求获取指定域名最近的一条通知记录(可以设定通知记录的开启/关闭状态,进而控制通知信息是否要触达用户前端),如果存在通知信息,则加载数据
前端SDK发布
目前实现是将sdk放到云存储上,但为了可以更方便地使用sdk,此处可以将sdk发布到npm,随后确认发布版本。
然后通过引入线上地址在浏览器中进行查看,也可在项目中直接引用发布的地址接入sdk
SDK项目发布
# 确认npm的镜像源
npm config get registry
# 切换默认镜像源
npm config set registry https://registry.npmjs.org/
# 登陆(输入npm login指令提示跳转页面,然后注册、登录账号即可)
npm login
# 注册成功返回到terminal,进入到要发布的项目目录
npm publish
# 确认发布信息,如果发布不成功则进一步确认提示
此处发布不成功,是因为package.json默认配置了私有,需要移除相关配置
发布成功之后,登录官网查看发布信息(点击右侧头像=》查看packages)
SDK优化
基于上述的步骤,已经可以初步完成一个公共的通告系统框架核心,但是如果说这个前端SDK需要提供给外部使用,则要进一步优化SDK的构建方式,尽量在不影响基础功能的前提下,将打包体积进行压缩,提升用户体验。
方案1:引入Terser(强力压缩插件):JavaScript解析器、转换器和压缩工具,可以用于压缩、混淆、美化JavaScript代码。在项目中使用Terser一般是为了减小JavaScript体积,提高网页加载速度
# 安装Terser插件
npm install terser --save-dev
# 使用Terser进行压缩的配置参考
默认打包方式参考:77958字节(磁盘上的82KB),考虑第三方库本身比较大,所以打包后可能差距并不是特别的大
方案2:将引入方式调整为引入最小的组件库sweet2alert.all.min.js(保证基础功能的基础上构建项目)
方案3:尝试引入其他体积更小的组件库(例如sweetalert1,但是目前该库版本已经不再维护且版本非常少),但是可以尝试将其引入并构建测试(在不影响基础功能的基础上可以使用即可,相应要调整sweetalert的属性API的弹窗语法规则)