若依源码阅读

ry-ui.js

在goods.html、add.html、edit.html中,到处可见如:

onclick="$.table.search()"
onclick="$.table.exportExcel()"

等代码

这些代码从哪里来?与什么相关?

【学着写项目】开源脚手架 【若依】 的学习,深度阅读开源项目_哔哩哔哩_bilibili

整体框架

image-20210810152039170

表格封装

image-20210810152700462

image-20210810152752366

image-20210810152821721

table.init

image-20210810153226141

修改一个练练手

从BootStrap Table事件里面选择一个API方法

onClickCell

  • jQuery 事件: click-cell.bs.table

  • 参数: field, value, row, $element

  • 详情:

    用户单击一个单元格时触发,参数包含:

    • field: 与单击的单元格对应的字段名称。
    • value: 与单击的单元格对应的数据值。
    • row: 与单击的行对应的记录。
    • $element: td元素。

修改

在哪里修改代码呢?

查看ry-ui.js代码

image-20210810161012328

发现了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)

20210810-163655-044

发现问题

image-20210810171740283

发现过程

类比用户管理

image-20210810171839030

找到/system/user

image-20210810171952843

这里的注解

@Controller
@RequestMapping("/system/user")

的作用是指明这个类的作用是控制/system/user页面的

ruoyi-admin/src/main/java/com/ruoyi/web/controller/ErpGoodsController.java

的注解里面却是

image-20210810172312255

原本是/system/goods

不知道什么时候被改掉了

改回来再看看

又变成了500

image-20210810172523140

原来

prefix也没有改过来

image-20210810172807351

但是还是报错

根本原因在于

正确的应该是

package com.ruoyi.system.controller;

但是不知道为什么,错误地变成了

package com.ruoyi.web.controller;

引起了这场错误,非常可惜。

改过来之后,就好了

edit方法

image-20210811085120364

$.common.isEmpty(id)

判断id是否为空

var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0];

获取表格的第一行

$.common.isEmpty(row)

判断行是否为空

查看页面

image-20210811092704095

点击修改时,如果没有选择任何一项,就会弹出“请至少选择一条记录”的弹窗

uniqueId

  • 属性:data-unique-id

  • 类型:String

  • 详情:

    为每一行指示唯一的标识符。

  • 默认:undefined

uniqueId

image-20210811093433536

uniqueId是bootstrap-table初始化的时候,从options里面获取的

而options里面没有设置uniqueId,所以uniqueId是未定义的,不用管

直接跳到

else语句

else {
    $.modal.open("修改" + table.options.modalName, $.operate.editUrl(id));
}

editUrl

image-20210811095245251

var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId);

如果uniqueId为空,则令id为表格的第一列,否则,令id为uniqueId

selectFirstColumns

image-20210811100229734

调试

image-20210811100937912

var rows = $.map(
    $("#" + table.options.id).bootstrapTable('getSelections'), 
    function (row) {return $.common.getItemField(row, table.options.columns[1].field);}
);

rows是一个映射数组

查看options的columns

image-20210811101037033

$.common.getItemField(row, table.options.columns[1].field);

获取的是options的id字段

最后返回id字段

open

edit调用open

$.modal.open("修改" + table.options.modalName, $.operate.editUrl(id));

没有传递callback参数

所以会跳转到open的这个流程

image-20210811102343087

在该流程中,会自己设置一个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方法,查看

image-20210811103550151

发送前,产生一个处理中的弹窗

 beforeSend: function () {
                        $.modal.loading("正在处理中,请稍后...");
                        $.modal.disable();
                    },

后端返回结果后,执行success方法

success: function(result) {
                        if (typeof callback == "function") {
                            callback(result);
                        }
                        $.operate.successCallback(result);
                    }

成功执行回调函数

image-20210811104505841

检查结果码

if (result.code == web_status.SUCCESS)

若修改成功,则在父窗口弹出一个成功弹窗,并刷新表格数据

20210811-105144-010

parent.$.modal.msgSuccess(result.msg);
parent.$.table.refresh();

goods/list页面

image-20210811110121241

这是商品页面,我们需要看看他是怎么加载的

image-20210811110253252

这里封装的很严实

要到startPage方法里面看看

startPage

//ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java

image-20210811110541142

调试

image-20210811111139480

buildPageRequest

再到

getPageDomain

image-20210811111907175

servletUtils

其中调用了

ServletUtils.getParameterToInt

image-20210811112003571

ServletUtils是若依的工具类

getParameterToInt

从请求里面获取参数

getRequest().getParameter(name)

再点击getReuqest,看看如何获取请求

getRequest

image-20210811112448576

调用了getRequestAttributes

进入这个方法看看

getRequestAttributes

image-20210811112535885

其实RequestContextHolder.getRequestAttributes()

就是Spring的方法了

在任何地方调用该方法,都可以获得请求

TableDataInfo

为什么要封装一个TableDataInfo

image-20210811113245339

前端需要这些数据

image-20210811114031393

{
"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
}

后端就封装这些数据

image-20210811114234535

总结

实际上startPage就是把PageHelper又封装了一遍

让我们写起来更加方便

注意

image-20210811114508108

startPage()方法后面必须紧紧跟着查询方法

因为获取请求后,必须按照最新的请求进行查询

不然隔了一个请求,查询的就不是我们需要的数据了

BaseEntity

image-20210812080931512

注意最后一个请求参数是Map类型

可以存放很多参数

是一个参数表

Mapper

这里的date_format,里面调用了params

image-20210812080809514

如果有新的参数,需要作为参数传到Mapper里面,那么可以使用params

BaseController

image-20210812081848560

DateUtils

image-20210812083401395

日期工具类

登录日志

登录日志界面

image-20210812083737317

该日志记录的是登录信息 logininfor

F12 查看网络请求

20210812-083839-006

查看logininfor:list

image-20210812084408918

这个list与之前的goods/list完全类似,都是分页显示

  startPage();
  List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
  return getDataTable(list);
  1. 获取分页请求

  2. 根据请求,查找数据

  3. 封装并返回数据

登录授权过程

image-20210812085858296

登录信息是在登录时获取的

ajaxLogin方法中

subject.login(token)

这里和realm的认证授权有关

//ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java

image-20210812092936975

//ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java

image-20210812092300908

此处遇到任何问题,都是记录日志然后抛出异常

AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists")));
        

AsyncManager是类

me()是方法

类调用方法,说明me()是AsyncManager类的静态方法

image-20210812104940239

private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");

这个是获取bean的方法

image-20210812152539184

这个是单例模式中的饿汉式,做起来方便

//ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java

image-20210812105431466

//ruoyi-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java

image-20210812110227838

此处使用了注解

@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

image-20210812153834495

总结

为什么系统日志用异步方法做呢?

因为主线程的业务逻辑本身已经很复杂了,像这种可以稍后处理的日志记录功能,放在主线程中完成,会对主线程业务造成一定的延迟。而放在异步方法中执行,就可以减轻主线程的压力,加大主线程的实时性。

操作日志

操作日志界面

image-20210812155043311

操作日志应该是通过AOP实现的

//ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java

image-20210812163819084

后置通知

handleLog方法

image-20210812164648587

// 获得注解
            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));

定时任务

定时任务页面

image-20210812171104806

定义一个任务

image-20210812170958666

添加定时任务

image-20210812171245984

任务执行异常

image-20210812171448180

因为忘记加@Component注解了

加上

image-20210812171548260

执行成功

image-20210812171930474

定时任务的具体实现

//ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java

image-20210813081232057

新增任务

先检测Cron 表达式是否正确、是否为RMI调用、是否为https//调用

然后再调用toAjax(jobService.insertJob(job))

把任务插入到任务调度队列里面

posted @ 2021-08-13 10:27  lucky_doog  阅读(1483)  评论(0编辑  收藏  举报