✨如何实现一行代码接入通用动态公告系统?

趁着周末闲隙,又给自己的项目加个小小的功能✨✨,想着如果每个项目都要维护自己的一套公告系统,不如构建一个公共的管理体系,让不同子系统进行接入,感兴趣的小伙伴可以参考下述构建思路,试着搭建一套属于自己的通用公告系统体系。

项目介绍

构建通用动态公告系统,后端管理员统一维护公告信息,对外提供接口根据域名或者其他参数配置构建通告联系(关联通知对象,例如根据域名区分子系统等)。前台通过引入通用公告SDK组件(请求调用后台接口获取通知,封装弹窗组件获取公告信息),不同子系统接入只需要一行代码的形式即可完成接入。

项目源码

发布效果

管理员后台创建消息通知,前端(自定义页面)刷新页面触发消息通知弹窗

模块设计

1.数据表设计

根据业务场景设计一个最基础的通知信息体(动态公告)

sql 复制代码
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实现:

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 installnpm run dev,启动项目初始化访问主页默认是如下页面

项目启动没有问题,此处则可进一步构建自己的SDK,先导入所需要的弹窗依赖sweetalert2

导入sweetalert2

复制代码
npm install sweetalert2

构建main.tsx(或者是main.jsx)具体看vite构建的时候选择基于的语法规则是什么

javascript 复制代码
# 原有模板生成
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进而实现最新消息的弹窗提醒。参考实现如下:

javascript 复制代码
# 实现参考(直接替换掉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文件,或者通过按钮组件触发加载

ini 复制代码
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项目发布

csharp 复制代码
# 确认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体积,提高网页加载速度

bash 复制代码
# 安装Terser插件
npm install terser --save-dev
​
# 使用Terser进行压缩的配置参考

默认打包方式参考:77958字节(磁盘上的82KB),考虑第三方库本身比较大,所以打包后可能差距并不是特别的大

方案2:将引入方式调整为引入最小的组件库sweet2alert.all.min.js(保证基础功能的基础上构建项目)

方案3:尝试引入其他体积更小的组件库(例如sweetalert1,但是目前该库版本已经不再维护且版本非常少),但是可以尝试将其引入并构建测试(在不影响基础功能的基础上可以使用即可,相应要调整sweetalert的属性API的弹窗语法规则)

相关推荐
曹牧5 分钟前
文档格式:OFD
java
布兰妮甜28 分钟前
从 0 搭建企业级 Vue3/Vite 脚手架(规范、eslint、husky、打包、环境变量全流程)
typescript·vue3·vite·脚手架·前端工程化
豆角焖肉30 分钟前
Maven进阶与搭建私服
java·maven
大不点wow41 分钟前
Java序列化与反序列化:让对象走出JVM
java·开发语言·jvm
止语Lab44 分钟前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活44 分钟前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
噢,我明白了1 小时前
Java中日期和字符串的处理
java·开发语言·日期
程序员天天困1 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn1 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
dkbnull1 小时前
Spring Boot请求处理组件对比详解
java·spring boot