虽然说是从0到1,但是程序员都知道,不可能重复造轮子。还是从ruoyi脚手架项目开始
一、ruoyi脚手架整体结构
1.1、原脚手架结构
ruoyi-admin 所有controller有关的接口全放在这里,也叫Web模块,特有逻辑1,必需的ruoyi-system 除此之外的domain、mapper、service等CRUD相关逻辑,特有逻辑2ruoyi-quartz 定时器,特有逻辑3ruoyi-generator 代码生成器,特有逻辑4ruoyi-common 公共组件,如工具类、公共用到的,公共逻辑1,必需的ruoyi-framework 框架运行所需要的一些类,公共逻辑2,必需的上述加黑字体的模块是主要模块,项目最终都会整合到 ruoyi-admin模块中,而ruoyi-admin是通过pom.xml把上述模块加入依赖进来的,各模块之间的依赖关系如下简图所示:
1.2、SQL表结构
重点的表主要是sys_ 开头的表:
sys_config :对应的系统中“系统管理/参数设置”sys_dept:对应系统中“系统管理/部门管理”sys_dict_data:字典明细表,对应系统中“系统管理/字典管理”sys_dict_type:字典类型表,对应系统中“系统管理/字典管理/字典类型”sys_job / sys_job_log:定时任务,对应系统中“系统监控/定时任务”sys_logininfor:登录信息,对应于系统的“系统监控/在线用户”和“日志管理/登录日志”sys_menu:菜单管理表,对应于系统的所有菜单值,还对应于“系统管理/菜单管理”sys_notice:通知信息,对应于“系统管理/通知公告”sys_oper_log:操作日志,对应于“日志管理/操作日志”sys_post:岗位管理表,对应“系统管理/岗位管理”sys_role:角色管理表,对应“系统管理/角色管理”sys_user:用户表,对应“系统管理/用户管理”sys_role_dept / sys_role_menu /sys_user_role : 角色与部门、菜单、用户的对应表sys_user_post:用户与岗位的对应表其中,用户相关的表的关系如下所示:
1.3 接口返回值
前后端分离的ruoyi框架共三个类型的返回值,分别是:
void:主要是导出或下载等不需要返回值的接口TableDataInfo:所返回的数据结构如下,主要是页面的分页列表数据
AjaxResult:所返回的数据结构如下,主要是基本添加、编辑、单个查询或删除等操作的返回值,一般是json格式数据
二、改造“通知公告”
目的:通过改造“通知公告”能够对系统有个基本了解。
目标:前端将“通知公告”拆分为两个页面:
A页面主要是显示所有用户发布的所有公告,并可查看详情,查看完毕后标识已读状态B页面主要是用户个人自己的公告编辑,发起流程并发布的编辑页面2.1 调整字典数据
通过前端页面调整字典数据,增加记录是否已读的sys_notice_isread数据字典类型,同时修改通知的类型sys_notice_status为流程类型、修改sys_notice_type的通知类型:
sys_notice_type:
sys_notice_status:
sys_notice_isread(新增):
2.2 增加用户与通知的关联SQL表
针对每个用户的已读信息,在SQL中予以存储之用,表名命名为sys_user_notice:
2.3 生成新增SQL的后台操作代码
使用代码生成器生成新增的SQL后台操作代码,如下图,代码生成器生成的代码包括前端VUE文件和后端main文件以及针对前端菜单显示的SQL三部分。其中因前端是改造原通知公告页面,故不使用vue和SQL文件,仅导入后端操作的main文件,如图标识所示部分:
2.4 改造“全员信息”页面
复制备份前端的views/system/notice下的index.vue,命名为create.vue作为编辑公共页面备用。
首先改造index如下:
2.4.1 去掉编辑功能
注释掉本页面中所有的“新增、修改、删除”的前端代码
2.4.2 增加序号和公告编号
2.4.3 操作中添加“查看”
同时添加点击“查看”后的详情页面:
v-for="mytype in dict.type.sys_notice_type" :key="dict.value" :type="mytype.raw.listClass" v-if="form.noticeType==mytype.value">{{ mytype.label}}
{{form.createTime}}
2.4.4 api和数据改造
api增加listPublishList路径,用以获取后端的所有发布的信息,index中改造如下:
import { listPublishNotice, getNotice,getNoticeAndRecord, delNotice, addNotice, updateNotice } from "@/api/system/notice";
api中改造如下:
// 查询所有发布的公告列表
export function listPublishNotice(query) {
return request({
url: '/system/notice/publishlist',
method: 'get',
params: query
})
}
index.vue中发生变动的数据如下:
dicts: ['sys_notice_status','sys_notice_isread', 'sys_notice_type'],
queryParams: {
pageNum: 1,
pageSize: 10,
noticeTitle: undefined,
createBy: undefined,
status: undefined,
orderByColumn:"create_time",
isAsc:"desc"
},
获取数据的方法变动如下:
/** 查询公告列表 */
getList() {
this.loading = true;
listPublishNotice(this.queryParams).then(response => {
this.noticeList = response.rows;
this.total = response.total;
this.loading = false;
});
},
增加打开详情信息的处理方法如下:
// 打开信息详情
openDetailDialog(id) {
this.openDetail = true;
this.loadingDetail = true;
getNoticeAndRecord(id).then(response => {
this.form = response.data;
this.openDetail = true;
this.loadingDetail = false;
});
},
// 取消按钮
closeDetail(id) {
this.titleDetail = "详情";
this.openDetail = false;
this.reset();
this.noticeList.forEach(element => {
if(element.noticeId == id) {
element.isRead = 1;
}
});
},
2.4.5 后端处理修改
SysNoticeCotroller中增加路由:
/**
* 获取发布类型通知公告列表
*/
@SaCheckPermission("system:notice:list")
@GetMapping("/publishlist")
public TableDataInfo
return noticeService.selectPageNoticePublishList(notice, pageQuery, getUserId());
}
在SysNoticeServerImpl中增加相关处理方法并添加接口:
/**
* 查询所有发布状态的公告
* @param notice
* @param pageQuery
* @return
*/
@Override
public TableDataInfo
LambdaQueryWrapper
.like(StringUtils.isNotBlank(notice.getNoticeTitle()), SysNotice::getNoticeTitle, notice.getNoticeTitle())
.eq(StringUtils.isNotBlank(notice.getNoticeType()), SysNotice::getNoticeType, notice.getNoticeType())
.like(StringUtils.isNotBlank(notice.getCreateBy()), SysNotice::getCreateBy, notice.getCreateBy())
.eq(SysNotice::getStatus, dictDataService.selectDictValue("sys_notice_status", "发布"));
Page
int i = PageQuery.DEFAULT_PAGE_NUM;
for( SysNotice one : page.getRecords()) {
one.setCreateBy(String.valueOf(sysUserService.selectUserByUserName(one.getCreateBy()).getNickName()));
SysUserNoticeVo isReadResult = sysUserNoticeService.queryByIdAndNoticeId(currentUserId, one.getNoticeId());
if(isReadResult != null){
one.setIsRead(Long.valueOf(dictDataService.selectDictValue("sys_notice_isread", "已读")));
} else {
one.setIsRead(Long.valueOf(dictDataService.selectDictValue("sys_notice_isread", "未读")));
}
one.setNoId(Long.valueOf((pageQuery.getPageNum()-1)*pageQuery.getPageSize()+i));
i++;
}
return TableDataInfo.build(page);
}
所有修改完毕后效果如下
2.5 改造“编辑公告”页面
针对上述2.4步复制出来的create.vue页面改造如下:
2.5.1 添加序号和流程发起
2.5.2 修改数据和方法
如2.4.4中改造“全员公告”的方式类似,分别修改api及本create.vue页面如下
// 查询当前用户的所有公告列表
export function listCurrUserNotice(query) {
return request({
url: '/system/notice/curruserlist',
method: 'get',
params: query
})
}
import { listCurrUserNotice,getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice";
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
noticeTitle: undefined,
createBy: undefined,
status: undefined,
orderByColumn:"create_time",
isAsc:"desc"
},
/** 查询公告列表 */
getList() {
this.loading = true;
listCurrUserNotice(this.queryParams).then(response => {
this.noticeList = response.rows;
this.total = response.total;
this.loading = false;
});
},
其中,流程操作中的“发起”按钮的操作方法暂未开发(20240304记录,待后续更新)
2.5.3 后端处理修改
分别修改后端对应的controller文件和service实现文件:
/**
* 获取当前用户的所有状态通知公告列表
*/
@SaCheckPermission("system:notice:list")
@GetMapping("/curruserlist")
public TableDataInfo
return noticeService.selectPageCurrUserList(notice, pageQuery, getUserId());
}
/**
* 查询当前用户的所有状态的公告
* @param notice
* @param pageQuery
* @param userId
* @return
*/
@Override
public TableDataInfo
LambdaQueryWrapper
.like(StringUtils.isNotBlank(notice.getNoticeTitle()), SysNotice::getNoticeTitle, notice.getNoticeTitle())
.eq(StringUtils.isNotBlank(notice.getNoticeType()), SysNotice::getNoticeType, notice.getNoticeType())
.eq(SysNotice::getCreateBy, sysUserService.selectUserById(userId).getUserName());
Page
int i = PageQuery.DEFAULT_PAGE_NUM;
for( SysNotice one : page.getRecords()) {
one.setNoId(Long.valueOf((pageQuery.getPageNum()-1)*pageQuery.getPageSize()+i));
i++;
}
return TableDataInfo.build(page);
}
所有改造完毕后的效果如下:
2.6 过程遇到的问题
2.6.1 jdk17下cglib报错
修改后端的service时,报如下错误,经查是 jdk17下cglib未更新所致
Unable to make protected final java.lang.Class java.lang.ClassLoader
在项目运行的中增加项目VM运行前的参数解决
--add-opens java.base/java.lang=ALL-UNNAMED
2.6.2 SQL update相关问题
因本次修改仿照其他sql表建立的sys_user_notice关联表,在实际修改service过程中发生了以下几类错误,后期如遇到可快速避免:
新增表无id,updateById或selectById时报错。此类问题可使用updateByBo之类解决,增加一个lamdaQueryWrapper的表达式,用Bo对象实现查询或更新默认的表都有create_by等系统默认字段,后端MybatisPlus的相关处理默认也是要操作这几个字段的,在尽量少更改代码的情况,在后期新建的SQL表中尽量考虑增加上这几个系统字段