跳至主要內容

CMS-动态公告(公告系统管理)

holic-x...大约 9 分钟项目itc

CMS-动态公告(公告系统管理)

项目介绍

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

项目源码

发布效果

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

image-20240510094318549

模块设计

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 根据提示初始化项目,然后选择一个第三方弹窗库实现效果,参考网站BootCDNopen in new window,可以选择一个体积比较小、样式精美的组件库,此处选用swaeetalert2open in new window

image-20240501154506034

npm create vite@latest,创建一个react项目,配置细节参考Vite官方文档open in new window

​ 创建完成更新依赖并启动项目:npm installnpm run dev,启动项目初始化访问主页默认是如下页面

image-20240501155848426

​ 项目启动没有问题,此处则可进一步构建自己的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文件

image-20240509173906528

​ 方式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查看是否正常请求后端接口:确认接口返回信息是否正常

image-20240509175343384

通知弹窗确认

​ 接口默认请求获取指定域名最近的一条通知记录(可以设定通知记录的开启/关闭状态,进而控制通知信息是否要触达用户前端),如果存在通知信息,则加载数据

前端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默认配置了私有,需要移除相关配置

image-20240509191209401

​ 发布成功之后,登录官网open in new window查看发布信息(点击右侧头像=》查看packages)

image-20240509191310100

image-20240510084551805

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的弹窗语法规则)

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3