若依源码阅读
ry-ui.js
在goods.html、add.html、edit.html中,到处可见如:
onclick="$.table.search()"
onclick="$.table.exportExcel()"
等代码
这些代码从哪里来?与什么相关?
【学着写项目】开源脚手架 【若依】 的学习,深度阅读开源项目_哔哩哔哩_bilibili
整体框架
表格封装
table.init
修改一个练练手
从BootStrap Table事件里面选择一个API方法
onClickCell
-
jQuery 事件:
click-cell.bs.table
-
参数:
field, value, row, $element
-
详情:
用户单击一个单元格时触发,参数包含:
field
: 与单击的单元格对应的字段名称。value
: 与单击的单元格对应的数据值。row
: 与单击的行对应的记录。$element
: td元素。
修改
在哪里修改代码呢?
查看ry-ui.js代码
发现了onClickCell
注意到
onClickCell: options.onClickCell
里面有options
于是回到options定义的地方
//ruoyi-admin/src/main/resources/templates/system/goods/goods.html
$(function() {
var options = {
url: prefix + "/list",
createUrl: prefix + "/add",
updateUrl: prefix + "/edit/{id}",
removeUrl: prefix + "/remove",
exportUrl: prefix + "/export",
modalName: "商品",
columns: [{
checkbox: true
},
{
field: 'id',
title: 'id',
visible: false
},
{
field: 'goodsName',
title: '商品名称'
},
{
field: 'goodsPrice',
title: '商品价格'
},
{
field: 'gmtCreate',
title: '创建日期'
},
{
title: '操作',
align: 'center',
formatter: function(value, row, index) {
var actions = [];
actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.id + '\')"><i class="fa fa-edit"></i>编辑</a> ');
actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a>');
return actions.join('');
}
}]
};
$.table.init(options);
});
添加一个函数
onClickCell: function(field, value, row, $element){
console.log(field)
console.log(value)
console.log(row)
console.log($element)
},
再次运行项目
出问题了(404)
发现问题
发现过程
类比用户管理
找到/system/user
这里的注解
@Controller
@RequestMapping("/system/user")
的作用是指明这个类的作用是控制/system/user页面的
而
ruoyi-admin/src/main/java/com/ruoyi/web/controller/ErpGoodsController.java
的注解里面却是
原本是/system/goods
不知道什么时候被改掉了
改回来再看看
又变成了500
原来
prefix也没有改过来
但是还是报错
根本原因在于
正确的应该是
package com.ruoyi.system.controller;
但是不知道为什么,错误地变成了
package com.ruoyi.web.controller;
引起了这场错误,非常可惜。
改过来之后,就好了
edit方法
$.common.isEmpty(id)
判断id是否为空
var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0];
获取表格的第一行
$.common.isEmpty(row)
判断行是否为空
查看页面
点击修改时,如果没有选择任何一项,就会弹出“请至少选择一条记录”的弹窗
uniqueId
-
属性:
data-unique-id
-
类型:
String
-
详情:
为每一行指示唯一的标识符。
-
默认:
undefined
uniqueId
uniqueId是bootstrap-table初始化的时候,从options里面获取的
而options里面没有设置uniqueId,所以uniqueId是未定义的,不用管
直接跳到
else语句
else {
$.modal.open("修改" + table.options.modalName, $.operate.editUrl(id));
}
editUrl
var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId);
如果uniqueId为空,则令id为表格的第一列,否则,令id为uniqueId
selectFirstColumns
调试
var rows = $.map(
$("#" + table.options.id).bootstrapTable('getSelections'),
function (row) {return $.common.getItemField(row, table.options.columns[1].field);}
);
rows是一个映射数组
查看options的columns
$.common.getItemField(row, table.options.columns[1].field);
获取的是options的id字段
最后返回id字段
open
edit调用open
$.modal.open("修改" + table.options.modalName, $.operate.editUrl(id));
没有传递callback参数
所以会跳转到open的这个流程
在该流程中,会自己设置一个callback
iframeWin.contentWindow.submitHandler(index, layero);
这个submitHandler是提交
在edit.html里面有定义
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/edit", $('#form-user-edit').serialize());
}
}
save
submitHandler()里面调用了$.operate.save(prefix + "/edit", $('#form-user-edit').serialize());
方法
跳转到save方法,查看
发送前,产生一个处理中的弹窗
beforeSend: function () {
$.modal.loading("正在处理中,请稍后...");
$.modal.disable();
},
后端返回结果后,执行success方法
success: function(result) {
if (typeof callback == "function") {
callback(result);
}
$.operate.successCallback(result);
}
成功执行回调函数
检查结果码
if (result.code == web_status.SUCCESS)
若修改成功,则在父窗口弹出一个成功弹窗,并刷新表格数据
parent.$.modal.msgSuccess(result.msg);
parent.$.table.refresh();
goods/list页面
这是商品页面,我们需要看看他是怎么加载的
这里封装的很严实
要到startPage方法里面看看
startPage
//ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java
调试
到
buildPageRequest
再到
getPageDomain
servletUtils
其中调用了
ServletUtils.getParameterToInt
ServletUtils是若依的工具类
getParameterToInt
从请求里面获取参数
getRequest().getParameter(name)
再点击getReuqest,看看如何获取请求
getRequest
调用了getRequestAttributes
进入这个方法看看
getRequestAttributes
其实RequestContextHolder.getRequestAttributes()
就是Spring的方法了
在任何地方调用该方法,都可以获得请求
TableDataInfo
为什么要封装一个TableDataInfo
前端需要这些数据
{
"total":1,
"rows": [{"searchValue":null,"createBy":null,"createTime":null,"updateBy":null,"updateTime":null,"remark":null,"params":{},"id":7,"goodsName":"华为P50搭载麒麟芯片","goodsPrice":7999.00,"gmtCreate":"2021-07-28"}],
"code":0,
"msg":null
}
后端就封装这些数据
总结
实际上startPage就是把PageHelper又封装了一遍
让我们写起来更加方便
注意
startPage()方法后面必须紧紧跟着查询方法
因为获取请求后,必须按照最新的请求进行查询
不然隔了一个请求,查询的就不是我们需要的数据了
BaseEntity
注意最后一个请求参数是Map类型
可以存放很多参数
是一个参数表
Mapper
这里的date_format,里面调用了params
如果有新的参数,需要作为参数传到Mapper里面,那么可以使用params
BaseController
DateUtils
日期工具类
登录日志
登录日志界面
该日志记录的是登录信息 logininfor
F12 查看网络请求
查看logininfor:list
这个list与之前的goods/list完全类似,都是分页显示
startPage();
List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
return getDataTable(list);
-
获取分页请求
-
根据请求,查找数据
-
封装并返回数据
登录授权过程
登录信息是在登录时获取的
ajaxLogin方法中
subject.login(token)
这里和realm的认证授权有关
//ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java
//ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java
此处遇到任何问题,都是记录日志然后抛出异常
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists")));
AsyncManager是类
me()是方法
类调用方法,说明me()是AsyncManager类的静态方法
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
这个是获取bean的方法
这个是单例模式中的饿汉式,做起来方便
//ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java
//ruoyi-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java
此处使用了注解
@Bean(name = "scheduledExecutorService")
//ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java
AsyncManager.me().execute(
AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists"))
);
里面返回的就是个task
AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists"))
AsyncFactory
总结
为什么系统日志用异步方法做呢?
因为主线程的业务逻辑本身已经很复杂了,像这种可以稍后处理的日志记录功能,放在主线程中完成,会对主线程业务造成一定的延迟。而放在异步方法中执行,就可以减轻主线程的压力,加大主线程的实时性。
操作日志
操作日志界面
操作日志应该是通过AOP实现的
//ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
后置通知
handleLog方法
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null)
{
return;
}
// 获取当前的用户
SysUser currentUser = ShiroUtils.getSysUser();
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = ShiroUtils.getIp();
operLog.setOperIp(ip);
// 返回参数
if (StringUtils.isNotNull(jsonResult))
{
operLog.setJsonResult(StringUtils.substring(JSON.marshal(jsonResult), 0, 2000));
}
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (currentUser != null)
{
operLog.setOperName(currentUser.getLoginName());
if (StringUtils.isNotNull(currentUser.getDept())
&& StringUtils.isNotEmpty(currentUser.getDept().getDeptName()))
{
operLog.setDeptName(currentUser.getDept().getDeptName());
}
}
if (e != null)
{
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog);
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
定时任务
定时任务页面
定义一个任务
添加定时任务
任务执行异常
因为忘记加@Component注解了
加上
执行成功
定时任务的具体实现
//ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java
新增任务
先检测Cron 表达式是否正确、是否为RMI调用、是否为https//调用
然后再调用toAjax(jobService.insertJob(job))
把任务插入到任务调度队列里面
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步