写点什么

JeecgBoot 手记

用户头像
卧石漾溪
关注
发布于: 2020 年 08 月 24 日

 

【本文档为最近使用JeecgBoot遇到的问题整理而成的,算是我在对这个框架使用的过程中,整理出来的认为比较重要的部分】

 

Jeecg Boot 2.2.1:

后端技术:  SpringBoot_2.1.3.RELEASE 

            Mybatis-plus_3.1.2 

            Shiro_1.4.0 

           Jwt_3.7.0  

           Swagger-ui 

           Redis

前端技术: Ant-design-vue + Vue + Webpack

其他技术: Druid(数据库连接池)、Logback(日志工具) 、poi(Excel工具)、Quartz(定时任务)、lombok(简化代码)

 

官网:http://www.jeecg.com/

在线演示: http://boot.jeecg.com

前端组件文档(Ant-design-vue):https://www.antdv.com/docs/vue/introduce-cn/

视频教程:

https://www.bilibili.com/video/BV1Y541147m1

https://www.bilibili.com/video/BV1zJ411t7FG

https://www.bilibili.com/video/BV1mt411N7r8

 

*工作流部分集成参考的是如下工程,在此基础上做了改进:

https://gitee.com/happy-panda/jeecg-boot-activiti

 

1      项目运行步骤

-前端项目运行步骤:

       a. 使用Idea导入ant-design-jeecg-vue前端项目

       b. 在Terminal中执行命令 yarn install 下载项目依赖

       c. 配置前端项目访问端口

ant-design-vue-jeecg\vue.config.js(71行)

       d. 配置后台接口地址(开发时):

ant-design-vue-jeecg\vue.config.js(82行)

       e. 前端项目运行:

在项目根目录package.json 上鼠标右键选择Show npm Scripts

             



              最后双击serve即可启动前端项目服务。

       f. 前端项目访问:

              http://localhost:3000

 

-后端项目运行步骤:

       a. 使用Idea导入jeecg-boot后端项目

       b. 创建数据库并执行sql脚本:

              jeecg-boot\db\jeecgboot-mysql-5.7.sql

       c. 修改数据库及Redis连接:

jeecg-boot\jeecg-boot-module-system\src\main\resources\application-dev.yml(130行)

       d. 修改附件上传目录:

jeecg-boot\jeecg-boot-module-system\src\main\resources\application-dev.yml(175行)

 

       e. 配置后端项目访问端口和访问路径配置:

jeecg-boot\jeecg-boot-module-system\src\main\resources\application-dev.yml(2行)

       f. 修改代码生成的数据库连接:

jeecg-boot\jeecg-boot-module-system\src\main\resources\jeecg\jeecg_database.properties

       g. 启动Redis服务

       h. 后端项目运行:

              运行jeecg-boot\jeecg-boot-module-system\

src\main\java\org\jeecg\JeecgApplication.java

       i.后端项目访问:

              http://localhost:8080/jeecg-boot

 

2      项目部署步骤

-后端部署:

 

修改数据库及Redis连接:

jeecg-boot\jeecg-boot-module-system\src\main\resources\application-prod.yml(130行)

 

       修改附件上传目录:

jeecg-boot\jeecg-boot-module-system\src\main\resources\application-prod.yml(175行)

 

       切换配置为线上配置:

jeecg-boot\jeecg-boot-module-system\src\main\resources\application.yml

将active: dev修改为active: prod

 

       (这步应该不用做,本来就有了,没有的话再加即可)修改后端项目jeecg-boot\jeecg-boot-module-system\pom.xml加上打包插件:

                     <plugin>

                            <groupId>org.springframework.boot</groupId>

                            <artifactId>spring-boot-maven-plugin</artifactId>

                     </plugin>

 

       首先执行下jeecg-boot-parent的install 操作:

             

 

       然后执行下jeecg-boot-parent的package 操作打jar包:

       

              会在jeecg-boot\jeecg-boot-module-system\target目录中生成jar文件:

jeecg-boot-module-system-2.2.1.jar

 

jar启动后端项目:

              java -jar jeecg-boot-module-system-2.2.1.jar

 

如果需要在启动是动态指定内存大小、配置文件、端口,可使用如下命令启动:

java -jar -Xms258m -Xmx258m -XX:PermSize=512M -XX:MaxPermSize=512m jeecg-boot-module-system-2.2.1.jar --spring.profiles.active=prod --server.port=8080

 

后台启动命令:

nohup java -jar -Xms258m -Xmx258m -XX:PermSize=512M -XX:MaxPermSize=512m jeecg-boot-module-system-2.2.1.jar --spring.profiles.active=prod --server.port=8093 > nohup.log 2&>1 &

 

注意:

       jar运行后端项目发现虽然前台可以正常访问后端接口,但不能访问接口文档页面,

http://localhost:8080/jeecg-boot,这是因为prod运行的生产环境下禁止了接口文档的访问:

jeecg-boot\jeecg-boot-module-system\src\main\resources\application-prod.yml(最后一行):





-前端部署:

 

       修改后台接口服务地址:

ant-design-vue-jeecg\public\index.html(245行)

       build前端项目:

             

       将ant-design-vue-jeecg\dist目录中的文件复制到Nginx根目录下的自定义文件夹中(我这里文件夹名叫sp):

             

       修改Nginx的conf/nginx.conf文件:

              http {

    include       mime.types;

    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

 

    server {

        listen       3000;

        server_name  localhost;

#后台服务配置,配置了这个location便可以通过http://域名/jeecg-boot/xxxx 访问            

              location ^~ /jeecg-boot {

                     proxy_pass              http://127.0.0.1:8080/jeecg-boot/;

                     proxy_set_header        Host 127.0.0.1;

                     proxy_set_header        X-Real-IP $remote_addr;

                     proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;

              }

              #解决Router(mode: 'history')模式下,刷新路由地址不能找到页面的问题

              location / {

                     root   sp;

                     index  index.html index.htm;

                     if (!-e $request_filename) {

                            rewrite ^(.*)$ /index.html?s=$1 last;

                            break;

                     }

              }

       }

}

 

注意:

       上面配置中的4个加粗的配置说明:

       3000:Nginx端口,也就是前端项目访问的端口。

       localhost:Nginx访问地址。

       sp:因为我把前端打包后dist目录中的项目文件放在了Nginx根目录下的

sp文件夹内,所以这个配置的是前端项目所在的位置。

       http://127.0.0.1:8080/jeecg-boot/这个表示后端接口访问URL的代理。

 

3      创建自己的业务子模块步骤

以创建jeecg-boot-module-mytest子模块为例:

a.     jeecg-boot -> 鼠标右键 -> New -> Module -> Maven



b.    在jeecg-boot-module-mytest模块中的pom.xml中添加:

<artifactId>jeecg-boot-module-mytest</artifactId>

<version>1.0.0</version>

    <dependencies>

        <dependency>

            <groupId>org.jeecgframework.boot</groupId>

            <artifactId>jeecg-boot-base-common</artifactId>

        </dependency>

    </dependencies>

 

c.     根目录pom.xml模块节点modules添加业务模块(有了就不用加了哦):

 <module>jeecg-boot-module-mytest</module>

d.    jeecg-boot-module-system目录pom.xml添加模块依赖:

<dependency>

       <groupId>org.jeecgframework.boot</groupId>

       <artifactId>jeecg-boot-module-mytest</artifactId>

       <version>1.0.0</version>

</dependency>

 

4      代码生成配置

 

a.     自定义业务表的名称前缀不能是下面列出的,否则“导入数据库表”时显示不出来哦。

为了系统安全,防止有些前缀的表导入进行破坏,jeecg平台针对以下前缀表做了过滤,所以在导入表单的时候,会看不到这些表。

所以自定义业务表不能是以下前缀的:

"act_",

"ext_act_",

"design_",

"onl_",

"sys_",

"qrtz_"

 

b.自定义业务表必须包含以下字段,否则生成后的代码功能有问题:

              ALTER TABLE `表名`

              ADD COLUMN `create_by`  varchar(32) NULL COMMENT '创建人',

              ADD COLUMN `create_time`  datetime NULL COMMENT '创建时间' AFTER `create_by`,

              ADD COLUMN `update_by`  varchar(32) NULL COMMENT '修改人' AFTER `create_time`,

              ADD COLUMN `update_time`  datetime NULL COMMENT '修改时间' AFTER `update_by`,

              ADD COLUMN `sys_org_code`  varchar(255) NULL COMMENT '创建人所属部门',

              ADD COLUMN `del_flag`  varchar(1) NULL COMMENT '0表示未删除,1表示删除' AFTER `update_time`;

 

注意:

业务表主键必须叫id!类型可用varchar(32),不知道为何bigint都不行!

具体规范参考:http://doc.jeecg.com/1624723

 

c.代码生成的数据库配置是独立的,在后端项目

jeecg-boot\jeecg-boot-module-system\src\main\resources\jeecg\jeecg_database.properties中配置。

 

d.代码生成相关路径可在

jeecg-boot\jeecg-boot-module-system\src\main\resources\jeecg\jeecg_config.properties中配置。

              project_path:代码生成文件的存放路径

              bussi_package:代码所在的业务包名

 

5      代码生成步骤

a.在数据库中创建业务表,注意最好带上表和字段的描述信息。



b.在线开发->Online表单开发菜单页面中点击“导入数据库表”按钮,在弹出框中选择你创建的业务表,然后点“生成表单”按钮

 

c.在列表中可看到刚才添加的记录,点右侧“编辑”按钮可弹出具体表单信息维护页面,根据具体业务做调整。

注意:

a.     记得去掉表中以下字段选中的勾,不然会显示在表单页面上:



b.    数据字典的使用配置:







 

d.在列表中点击“代码生成”按钮,在弹出的页面记得填入报名(子模块包名哦),然后点“生成代码”按钮即可生成代码文件。

 

注意:有时候会弹出“请先同步数据库”这种提示,明明已经同步过了的,此时只需要刷新下页面,然后重新去生成即可)

 

e.后端代码部署:生成的代码除了vue文件夹,其他的放入指定的后端项目目录即可;

 

注意:要手工Build一下项目,然后重新启动,不知为何有时候Build一次还不行!?前台访问会提示找不到资源。。。。。。)

 

f.前端代码部署:vue文件夹中的文件放入前端项目views目录中即可。

(可手动在views中创建个模块对应的文件夹,放vue文件夹中的文件,文件的路径其实对应了菜单配置时的组件路径)

 

6      单表代码生成问题

a.单表生成的代码在列表页面展示时,F12控制台会报以下错误:



解决方法:

1.修改已生成的代码:

生成的前端代码modules文件夹中的  实体名Modal.vue文件(第8行)代码是:

:okButtonProps="{ class:{'jee-hidden': disablesubmit} }"

改为

:okButtonProps="{ class:{'jee-hidden': disableSubmit} }"

这样在列表页面加载时,F12控制台里就不会错了。

 

2.【推荐方案】修改代码生成的模板:

在文件

jeecg-boot\jeecg-boot-module-system\src\main\resources\jeecg

\code-template-online\default\one\java\${bussiPackage}\${entityPackage}

\vue\modules\${entityName}Modal.vuei(第18行),

 

:okButtonProps="{ class:{'jee-hidden': disablesubmit} }"

改为

:okButtonProps="{ class:{'jee-hidden': disableSubmit} }"

这样以后单表代码生成就不会再有这个问题了。

 

b.编辑后发现create_time字段的日期值时分秒丢失变为00:00:00的情况:

       解决方法:

1.修改已生成的代码:

              找到业务实体类的private java.util.Date createTime;这一行,

              修改其上的注解为:

              /**创建时间*/

              @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")

           @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")

           @ApiModelProperty(value = "创建时间")

 

2.修改代码生成中实体类对应的模板即可

      

7      树代码生成

-树代码生成需要业务表中包含父节点ID字段pid varchar(50)和是否有子节点字段has_child varchar(5)。



-树节点代码生成配置:







-树代码生成问题:

 

树代码生成后Controller中会报错:



解决方法:

1.修改已生成的代码:

需要手动改一下Controller中出错的LvTestTree实体类名(比如改为DemoTree)以及方法前注释中的@param后面的参数名(比如改为demoTree)。

 

2.修改代码生成的模板:

       在文件jeecg-boot\jeecg-boot-module-system\src\main\resources\jeecg\

code-template-online\default\tree\java\${bussiPackage}\${entityPackage}\controller\

${entityName}Controller.javai(第90行):

@param testTree

改为

@param ${entityName?uncap_first}

 

(第100行):

LvTestTree

改为

${entityName}

 

这样以后树代码生成就不会再有这个问题了。

 

8      主子表代码生成

主子表代码生成配置如下:

 

-主表配置:

      

 

-子表配置:







9      基于del_flag字段的逻辑删除

1.业务表添加del_flag字段

       2.修改业务表Controller的list方法,查询方法执行前添加以下代码:

              queryWrapper.eq("del_flag","0");

       3. 修改业务表Controller的delete方法,修改如下:

UpdateWrapper<业务实体类名> updateWrapper =

new UpdateWrapper<业务实体类名>();

              updateWrapper.set("del_flag","1");

              updateWrapper.eq("id",id);

              demoStudent2Service.update(updateWrapper);

              return Result.ok("删除成功!");

 

10  前端项目IP修改

打包发布时一共有两处需要修改:

a.     前端项目ant-design-vue-jeecg\vue.config.js(82行)

【vue.config.js现在不用改了,改了也没用,原因如下:】



       b.前端项目ant-design-vue-jeecg\public\index.html(245行)

 

11  菜单配置

a.一级菜单配置:





b.二级菜单配置:           

                    

 

注意:

a.     前端组件:

即为前端项目views文件夹下对应模块的vue组件访问路径(比如student/DemoStudentList.vue组件就配置为student/ DemoStudentList即可,不带.vue扩展名哦!)

b.    菜单路径:

感觉就是一个菜单对应功能页面的标识而已,只要系统中菜单的菜单路径不要重复就行;尽量起有意义的和模块相关的名称吧!

 

前端组件配置说明:

1、非叶子菜单(即没有下级的菜单)配置固定 前端组件layouts/RouteView

2、普通的叶子菜单(即具体的页面) 配置相对于src/views目录的路径例如src/views/jeecg/helloworld.vue 这个页面,前端组件为 jeecg/helloworld



3、需要跳转到第三页面的菜单 前端组件固定为:layouts/IframePageView,比如跳转百度:https://www.baidu.com



4、java后台请求的菜单需要以{{ window._CONFIG['domianURL'] }}开头5、配置外网打开的链接请求地址需要以http开头或者{{ window._CONFIG['domianURL'] }}开头



菜单路径配置说明:

1、非叶子菜单(即没有下级的菜单),URL配置规则:按照功能模块定义的关键根路径即可,不能重复,需以“/”开头

2、普通的叶子菜单(即具体的页面),URL和前端组件配置保持一致即可,需在前端组件值前加“/”

3、需要跳转到第三方页面的菜单,菜单路径配置第三方跳转的地址即可,例如http://www.baidu.com

 

12  后端接口配置不登录直接访问

修改配置文件:org.jeecg.config.ShiroConfig的方法shiroFilter,排除你的请求:



13  前端页面配置不登录直接访问

修改ant-design-jeecg-vue/src/config/router.config.js:

在底部constantRouterMap配置里面加上你要访问的路由配置



14  如何获取登录用户信息

-后端获取:

LoginUser sysUser = (LoginUser)SecurityUtils.getSubject().getPrincipal();

 

通过token获取用户信息方法:

org.jeecg.common.system.util.JwtUtil.getUsername(token)

 

-前端获取:

import store from '@/store/'

 

store.getters.userInfo

 

15  TOKEN超时时间修改

设置org.jeecg.common.system.util.JwtUtil类中的EXPIRE_TIME值。



16  列表like模糊查询设置

-设置某个字段的模糊查询:

默认查询条件是全匹配,想实现模糊查询需求在查询值的前后加: *;

示例:

       //手动添加模糊查询(业务Controller的list方法中)

       if(StringUtils.isNotBlank(demoStudent3.getStudentName())){

                     demoStudent3.setStudentName("*"+demoStudent3.getStudentName()+"*");

       }

       QueryWrapper<DemoStudent3> queryWrapper =

QueryGenerator.initQueryWrapper(demoStudent3, req.getParameterMap());

 

-全局修改所有字符串查询的模式为模糊查询:

后端项目找到

/jeecg-boot-base-common

/src/main/java/org/jeecg/common/system/query/QueryGenerator.java文件,

找到installMplus方法,将164行~167行的代码解注销即可。

 

17  CAS单点登录对接

-后端项目修改:

application-dev.yml(发布环境是application-prod.yml) 最下面修改cas的prefixUrl配置:

cas:

  prefixUrl: http://localhost:8090/cas   

以上完成Jeecg Boot后端对接CAS所有操作,启动项目即可。

 

注意:

       http://localhost:8090/cas是我本机测试用的单点登录CAS Server的访问地址。

 

-前端项目修改:

1、public/index.html页面增加CAS服务地址配置:

该地址与后端对接CAS时配置的地址一致。

  <script>

    window._CONFIG = {};

    window._CONFIG['casPrefixUrl'] = 'http://localhost:8090/cas';

  </script>

 

2、 src/store/modules/user.js文件中增加验证登录方法:

登出方法改造:

// 登出

    Logout({ commit, state }) {

      return new Promise((resolve) => {

        let logoutToken = state.token;

        commit('SET_TOKEN', '')

        commit('SET_PERMISSIONLIST', [])

        Vue.ls.remove(ACCESS_TOKEN)

        //console.log('logoutToken: '+ logoutToken)

        logout(logoutToken).then(() => {

          resolve()

        }).catch(() => {

          resolve()

        })

      })

    },

 

改造为:

 

// 登出

    Logout({ commit, state }) {

      return new Promise((resolve) => {

        let logoutToken = state.token;

        commit('SET_TOKEN', '')

        commit('SET_PERMISSIONLIST', [])

        Vue.ls.remove(ACCESS_TOKEN)

        //console.log('logoutToken: '+ logoutToken)

        logout(logoutToken).then(() => {

          var sevice = "http://"+window.location.host+"/";

          var serviceUrl = encodeURIComponent(sevice);

          window.location.href = window._CONFIG['casPrefixUrl']+"/logout?service="+serviceUrl;

          //resolve()

        }).catch(() => {

          resolve()

        })

      })

},

 

3、改造src/main.js代码,增加如下代码:

(1) 引入js

import SSO from '@/cas/sso.js'

 

(2)改造下面代码:

new Vue({

    router,

    store,

    mounted () {

      store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true))

      store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme))

      store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout))

      store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader))

      store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))

      store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))

      store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))

      store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak))

      store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor))

      store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))

      store.commit('SET_MULTI_PAGE',Vue.ls.get(DEFAULT_MULTI_PAGE,true))

    },

    render: h => h(App)

  }).$mount('#app')

 

改造为:

 

SSO.init(() => {

  main();

});

function main() {

  new Vue({

    router,

    store,

    mounted () {

      store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true))

      store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme))

      store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout))

      store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader))

      store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar))

      store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth))

      store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader))

      store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak))

      store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor))

      store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))

      store.commit('SET_MULTI_PAGE',Vue.ls.get(DEFAULT_MULTI_PAGE,true))

    },

    render: h => h(App)

  }).$mount('#app')

}

 

4、组织机构登录切换 src/components/tools/UserMenu.vue 增加 mounted()方法:

mounted(){

      let depart = this.userInfo().orgCode;

      if(!depart){

        this.updateCurrentDepart();

      }

  },

 

以上步骤完成后,CAS对接完成,启动前端项目访问即可。

 

18  Activiti工作流整合

可直接下载或参考:https://gitee.com/happy-panda/jeecg-boot-activiti

 

18.1 申请流程后查看进度或历史的时候,会出现Redis报错

jeecg-boot\jeecg-boot-module-activiti\src\main\

java\org\jeecg\modules\activiti\web\ActTaskController.java(historicFlow方法,265行)

        中的代码:

              String owner = sysBaseAPI.getUserByName(htv.getOwner()).getRealname();

assignees.add(new Assignee(assignee+"(受"+owner+"委托)", true));

        修改为:

if(StringUtils.isNotBlank(htv.getOwner())){

         String owner =

sysBaseAPI.getUserByName(htv.getOwner()).getRealname();

assignees.add(new Assignee(assignee+"(受"+owner+"委托)", true));

    }

18.2 流程表单配置说明

-工作流表单是在“已发布模型”->“编辑”页面选择出来的,如何添加新的可供选择的表单?

ant-design-vue-jeecg\src\views\activiti\mixins\activitiMixin.js

(allFormComponent计算属性)可配置节点可以选择的表单组件;

 

注意:

a. “已发布模型”->“编辑”页面中选择的页面组件会作为流程申请时的表单页;

b. 如果没有在流程节点上配置表单,则上一步所选的页面组件会作为所有流程节点的通用待办处理页面。

 

-默认的表单在审批过程中只能使用同一个表单,如何动态获取流程图节点上绑定的表单?

a.直接在流程图节点上配置要绑定的表单组件地址;

b. 在ant-design-vue-jeecg\src\views\activiti\mixins\activitiMixin.js

(allFormComponentExt计算属性)配置节点上对应的表单组件即可。

注意:

如果在流程图某个节点上手动设置了表单组件,则该节点的待办处理页面以流程图节点设置的表单为准;

对于没有在流程图上设置表单的节点,则以在“已发布模型”->“编辑”页面设置的节点通用表单组件为准。

 

18.3 流程变量设置及业务回调参数设置

 默认的集成没有提供流程变量设置的方法。。。

这里我做了修改,jeecg-boot\jeecg-boot-module-activiti\src\main\

java\org\jeecg\modules\activiti\web\ActTaskController.java这个类中的pass方法已添加了完成工作项时流程变量的提交。

页面提交到pass方法时可用如下代码设置流程变量(flowVarsJsonStr参数):

 

/*审批页面-通过审批按钮点击时的方法*/

passTask() {

     this.$emit('passTask',{flowVarsJsonStr:{pass:1}})

}

 

/*审批页面-打回修改按钮点击时的方法*/

backTaskToEidt() {

this.$emit('passTask',{flowVarsJsonStr:{pass:0},modalTaskTitle:"打回修改"})

}

 

     /*打回修改页面最终的提交*/

this.$emit('passTask',

{

flowVarsJsonStr:{},

modalTaskTitle:"业务信息修改提交",

spflag:'0',

businessCallbackUrl:url,

businessCallbackParams:formData,

businessCallbackMethod:'post',

businessCallbackParamType:'params'

})

 

-完整回调参数说明:

flowVarsJsonStr:保存流程变量的参数对象。

modalTaskTitle:可设置弹出框的自定义标题。

spflag:设置流程待办提交页面时是否显示审批输入框(0:隐藏 1:显示)。

businessCallbackUrl:流程提交时的业务回调地址。

(不传则表示不需要业务回调接口)

businessCallbackParams:流程提交时的业务回调接口参数。

(如果回调接口不需要参数,可不传)

businessCallbackMethod:流程提交时的业务回调接口访问方法。

(post,get,put,delete等,默认不传是post提交)

       businessCallbackParamType:业务回调接口参数发送类型

(params 或 data,默认不传是params)。

      

                     *params类型对应后台接口@RequestParam接收的参数;

                     *data类型对应后台接口@RequestBody接收的参数。

      

 

*业务回调使用场景:

某个节点待办处理完后需要执行业务表状态的更新,或者需要修改业务表信息等。

18.4 流程示例

两个示例如下:

1.     用一个通用页面来处理业务申请表单填写、流程节点待办处理等功能(适用于简单流程):

参考:ant-design-vue-jeecg\src\views\activiti\form\demoForm.vue

 

2.     将业务申请表单填写页面以及流程审批、打回修改、通知等流程节点的待办处理页面分开(适用于复杂流程):

 

-代码参考:

业务申请表单填写页面:    ant-design-vue-jeecg\src\views\flowtest\demoForm;

流程审批待办处理页面:    ant-design-vue-jeecg\src\views\flowtest\approveForm;

流程打回修改页面:           ant-design-vue-jeecg\src\views\flowtest\editForm;

流程通知页面:                  ant-design-vue-jeecg\src\views\flowtest\viewForm;

 

19  部门管理功能注意事项

部门管理功能页面,添加下级部门或部门权限设置前需要在左侧先选中部门,注意,这个选中不是打勾!



这样是不行滴!

需要点部门名称才能选中!



20  用户管理功能部门选择的问题

1.用户管理部门选择时出现找不到add方法的问题:

这个错误会导致不能正确弹出部门选择页面,修改方案如下:

jeecg-boot-activiti\ant-design-vue-jeecg\src\views\system\modules\DepartWindow.vue

删除41行import userModal from './UserModal'

删除45行userModal,

 

2.编辑时如果曾经选择过部门,则打开部门选择弹框后直接点“确定”按钮,会发现原先选择的部门也会丢失的情况:



解决方法:

jeecg-boot-activiti\ant-design-vue-jeecg\src\views\system\modules\DepartWindow.vue

增加如下方法,将原先选中的部门对象放入确定按钮回调的方法参数数组中:

//递归查询当页面打开时默认选中的所有节点

      getInitSelectedNodes(array){

        for(let i in array){

          let data=array[i];

          let depart = {key:"",value:"",title:""};

          depart.key = data.value;

          depart.value = data.value;

          depart.title = data.title;

          if(this.checkedKeys.includes(depart.key)) {

            this.departList.push(depart);

          }

          if(data.children){

            this.getInitSelectedNodes(data.children) //自己调用自己

          }

        }

      },

 

同时在queryDepartTree()方法中添加以上方法的调用:

this.getInitSelectedNodes(res.result);

 

21  数据权限配置

1.     配置菜单数据规则:



只允许查看自己创建的:



只允许查看自己部门创建的:





2.     角色权限配置:





3.     修改后台业务Controller的list方法,在方法前添加权限注解:



      

注意:

注释属性pageComponent的值必须和前台菜单配置的组件地址一致!

 

22  按钮级权限配置

1.     菜单管理中的业务功能菜单下添加按钮权限:



2.     在页面上需要控制按钮级权限的组件上设置v-has属性:



注意:

v-has属性的值表示权限标识字符串,需要用’单引号引起来哦!

       

3.     角色管理中进行按钮级权限的授权:





23  设置form中的控件不可编辑



24  vue页面中获取访问token

先引入依赖:

import { ACCESS_TOKEN } from "@/store/mutation-types"

import Vue from 'vue'

 

再获取token:

created() {

      const token= Vue.ls.get(ACCESS_TOKEN);

      console.log(token);

}

 

说明:

       Vue.ls是用到了Vue的Vue-ls插件,用于从Vue上下文中使用本地Storage、会话Storage和内存Storage,封装了本地储存的方法。

25  this.$nextTick()的使用

this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

-使用示例:

 

this.$nextTick(()=>{

          //do something......

})

 

-详解:







26  高级自定义查询





list页面添加高级查询的步骤如下:

a.     在列表页面export default {上方添加高级查询所需选择的字段数组:

//高级查询modal需要参数,可设置不同的字段类型进行查询

  const superQueryFieldList=[

    {

      type: "string",

      value: "studentName",

      text: "学生姓名"

    }, {

      type: "int",

      value: "studentAge",

      text: "年龄"

    }, {

      type: "date",

      value: "studentBirthday",

      text: "生日"

    },

    {

      type: 'select',

      value: 'studentHobby',

      text: '爱好',

      dictCode: 'like'

    }

  ]

 

b.    添加高级查询组件的引用:

import JSuperQuery from '@/components/jeecg/JSuperQuery.vue';

export default {

name: "XXXList",

components: {

  JSuperQuery

},

              ……

 

c.     页面操作按钮区域添加查询组件:

<!-- 高级查询区域 -->

<j-super-query :fieldList="fieldList" ref="superQueryModal"

@handleSuperQuery="handleSuperQuery"></j-super-query>

 

d.    页面data中需要定义三个属性:

                   fieldList:superQueryFieldList,

                    superQueryFlag:false,

       superQueryParams:""

 

原框架高级查询的问题:

       2.2.1版本框架默认的高级查询条件保存是存放到localstore中的;

其中key的值格式大致为:JSuperQuerySaved_ /student2/DemoStudent2List

默认采用固定前缀 + 当前页面路由地址作为key的,

这样做有个问题是不同用户在同一台机器上登录后,这个模块的高级查询条件都是一样的(就是并没有做用户的区分)。

解决方法:

              修改ant-design-vue-jeecg\src\components\jeecg\JSuperQuery.vue文件:

              1.添加引用import store from '@/store/'

              2.修改fullSaveCode()方法为:

              fullSaveCode() {

        let saveCode = this.saveCode

        if (saveCode == null || saveCode === '') {

          saveCode = '/'+store.getters.userInfo.username+this.$route.fullPath

        }else{

          saveCode = '/'+store.getters.userInfo.username+saveCode

        }

        return this.saveCodeBefore + saveCode

      },

 

注意:

a.     JSuperQuery.vue中默认saveCode属性值为null,代表以当前路由全路径为区分Code,如果需要指定为自定义code,只需要在列表页面引入高级查询组件时,传入saveCode属性值即可,比如:

<!-- 高级查询区域 -->

<j-super-query :fieldList="fieldList" ref="superQueryModal"

@handleSuperQuery="handleSuperQuery" :saveCode="'/student2/DemoStudent2List'"></j-super-query>

 

b.    如果有数据字典下拉选项查询字段的话,可在superQueryFieldList中做如下配置:

{

      type: 'select',

      value: 'studentHobby',

      text: '爱好',

      dictCode: 'like'

},

 

c.     如果有自定义下拉选项查询的话,可在superQueryFieldList中做如下配置:

{

type: 'select',

value: 'isDbSynch',

text: '同步状态',

options: [

    {label: "已同步",value: "Y"},

    {label: "未同步",value: "N"}

]

},

 

参考文档:http://doc.jeecg.com/1524928

 

27  附件上传后获取文件大小的问题

-如何开启文件上传大小的回显:

<j-upload v-model="fileList" :returnUrl="false"></j-upload>

 

-默认组件回显文件大小的问题:

ant-design-vue-jeecg\src\components\jeecg\JUpload.vue组件中默认情况下只会回显最后一次上传的文件大小,这样如果连续上传多个文件时,会出现前面上传过的文件大小为undifined的情况。

 

解决方式:

修改JUpload.vue组件中的handleChange方法,修改286行else{}中的代码为

 

//returnUrl为false时返回文件名称、文件路径及文件大小

//YX Add 修改size文件大小获取逻辑(原组件只能获取到最后一次上传的文件size!)

//为了获取之前上传过的文件大小,这里先对之前上传过的文件信息进行备份

            let newFileListOldAry = JSON.parse(JSON.stringify(this.newFileList));

 

            let newFileListOldMap = [];

            if(newFileListOldAry) {

              newFileListOldAry.map(function (e, item) {

                newFileListOldMap[e.filePath] = e;

              });

            }

            this.newFileList = [];

 

            for(var a=0;a<fileList.length;a++){

              if(fileList[a].status === 'done' ) {

                let sizeVal;//YX Add

                if(newFileListOldMap[fileList[a].response.message]){

                  sizeVal = newFileListOldMap[fileList[a].response.message].fileSize;

                }else{

                  sizeVal = fileList[a].size;

                }

 

                var fileJson = {

                  fileName:fileList[a].name,

                  filePath:fileList[a].response.message,

                  fileSize:sizeVal

                };

 

                this.newFileList.push(fileJson);

              }else{

                return;

              }

            }

            this.$emit('change', this.newFileList);

 

28  系统日志相关

-JeecgBoot系统日志分为:登录日志、操作日志、数据日志

       a.登录日志(sys_log表):系统自己处理的,只要登录就会记录登录日志。

      

b.操作日志(sys_log表):

              有以下两种方法可添加操作日志:

1.Controller添加 @AutoLog(value = "这里是操作说明", operateType =

CommonConstant.OPERATE_TYPE_3)

              2.使用系统共通业务API:

                     //注入依赖

                     @Autowired

                     private ISysBaseAPI sysBaseAPI;

                     //调用日志添加API

sysBaseAPI.addLog("这里是操作说明" ,CommonConstant.LOG_TYPE_2, 2);

 

       c.数据日志(sys_data_log表):

可使用sysDataLogService.addDataLog(tableName, dataId, dataContent);方法添加数据日志。

注意:

       在自定义module中是不能直接使用sysDataLogService的!

因为sysDataLogService是jeecg-boot-module-system中提供的,而自定义module不能直接在pom.xml中添加对jeecg-boot-module-system的依赖!

       (详见官方文档:http://doc.jeecg.com/1273938

解决方式:

       通过在org.jeecg.common.system.api.ISysBaseAPI和实现类

org.jeecg.modules.system.service.impl.SysBaseApiImpl中添加所需API方法,然后在自定义module中注入ISysBaseAPI即可。

下面是我添加的对数据日志操作的API方法:

1.org.jeecg.common.system.api.ISysBaseAPI中添加:

/**

        * YX Add

        * 数据日志添加

        * @param tableName 表名

        * @param dataId 数据ID

        * @param dataContent 数据内容(JSON字符串)

        */

       void addDataLog(String tableName, String dataId, String dataContent);

 

2.org.jeecg.modules.system.service.impl.SysBaseApiImpl中添加:

      

       //YX Add

       @Resource

       private ISysDataLogService sysDataLogService;

 

//YX Add

       @Override

       public void addDataLog(String tableName, String dataId,

String dataContent){

              sysDataLogService.addDataLog(tableName, dataId, dataContent);

       }

 

3.在需要记录数据日志的module中的Controller类的add方法和edit方法里添加:

//添加数据日志

              String tableName = "demo_student2";

              String dataId = demoStudent2.getId();

              String dataContent = JSON.toJSONString(demoStudent2);

              sysBaseAPI.addDataLog(tableName, dataId, dataContent);

 

*备注:

       -数据日志记录是分版本的,它记录了每次修改的数据历史;

-不同版本的数据ID相同的记录可以进行比较,很直观的展现不同版本数据的差异。

 

29  QueryWrapper子查询:关联其他表做where查询条件

比如:

queryWrapper.inSql("sys_org_code","select org_code from sys_depart where org_code = 'A01'");

 

就表示添加以下子查询条件:

sys_org_code in (select org_code from sys_depart where org_code = 'A01')

 

30  QueryWrapper实现join关联查询

示例关联查询如下:

 

select a.*,b.depart_name as sysOrgName from demo_student2 a left join sys_depart b on a.sys_org_code = b.org_code

 

a.     在业务实体类中添加关联表中要查询的字段:

@TableField(exist = false)

@ApiModelProperty(value = "所属部门名称")

private java.lang.String sysOrgName;

 

b.    在业务Mapper中添加join关联查询的方法:

 

@Select("select a.*,b.depart_name as sysOrgName from demo_student2 a left join sys_depart b on a.sys_org_code = b.org_code ${ew.customSqlSegment}")

IPage<DemoStudent2> selectPage2(Page<DemoStudent2> page,

@Param("ew") Wrapper<DemoStudent2> queryWrapper);

       注意:

              ${ew.customSqlSegment}是指QueryWrapper自动拼接的参数。

 

c.     在业务Service接口和实现类中添加查询方法:

 

-业务Service接口中添加:

IPage<DemoStudent2> selectPage2(Page<DemoStudent2> page,

@Param("ew") Wrapper<DemoStudent2> queryWrapper);

 

-业务Service接口实现类中添加:

@Override

public IPage<DemoStudent2> selectPage2(Page<DemoStudent2> page,

Wrapper<DemoStudent2> queryWrapper) {

return this.getBaseMapper().selectPage2(page, queryWrapper);

}

 

d.    业务Controller查询方法中调用关联查询方法:

IPage<DemoStudent2> pageList =

demoStudent2Service.selectPage2(page, queryWrapper);

 

注意:如果需要在列表页面添加针对关联表字段的查询,需要做以下修改】:

 

a.业务Controller查询方法中修改:

 

-在初始化queryWrapper对象前添加:

String sysOrgName = demoStudent2.getSysOrgName();//先获取关联表要查询的字段的值

if (StrUtil.isNotBlank(sysOrgName)) {

        demoStudent2.setSysOrgName("");

//这里先把该查询值置为空字符串,为的是初始化QueryWrapper时不会自动生成针对该字段的查询

        }

 

-在queryWrapper对象初始化后添加:

if (StrUtil.isNotBlank(sysOrgName)) {

               queryWrapper.eq("b.depart_name", sysOrgName);

        }

 

b.前端页面查询区域添加对关联表查询字段的显示:

<a-input placeholder="请输入所属部门名称"

v-model="queryParam.sysOrgName"></a-input>

 

31  工作流待办处理获取下个节点参与者的BUG

修改前的代码里,对于有分支的流程,需要根据流程变量(比如pass==0条件)判断并获取下个节点参与者列表,这个原代码并没有把pass的值从前台传入到后台接口,而是直接查询业务表数据传给判断分支的逻辑方法了。。。。。。(坑爹啊,咳咳),下面的修改是根据前台传入流程变量,来判断和获取下个节点参与者,这样就对了。

 

修改步骤:

1.修改

jeecg-boot\jeecg-boot-module-activiti\src\main\java\org\jeecg\modules\activiti\web\ActivitiProcessController.java

(319行)的getNextNode()方法为:

 

@RequestMapping(value = "/getNextNode", method = RequestMethod.GET)

    @ApiOperation(value = "通过当前节点定义id获取下一个节点")

public Result getNextNode(

@ApiParam("流程定义id")  String procDefId,

       @ApiParam("当前节点定义id")  String currActId,

    @ApiParam("流程变量") @RequestParam(required = false) String flowVarsJsonStr){

        //YX Add 添加流程变量的设置

        Map<String, Object> flowVars = JsonUtil.jsonStrToMap(flowVarsJsonStr);

        ProcessNodeVo node = actZprocessService.getNextNode(procDefId, currActId, flowVars);

        return Result.ok(node);

}

 

2.修改

jeecg-boot\jeecg-boot-module-activiti\src\main\java\org\jeecg\modules\activiti\service\Impl\ActZprocessServiceImpl.java(345行)的getNextNode()方法为:

 

public ProcessNodeVo getNextNode(String procDefId, String currActId,Map<String, Object> vals) {

。。。。。。

/*流程定义Id*/

String procInsId = "";

/*定义变量*/

//YX Update 修改流程变量

//Map<String, Object> vals = Maps.newHashMap();

List<ActBusiness> actBbyProcDefId = actBusinessService.findByProcDefId(procDefId);

if (CollUtil.isNotEmpty(actBbyProcDefId)){

ActBusiness actBusiness = actBbyProcDefId.get(0);

//vals =

actBusinessService.getApplyForm(actBusiness.getTableId(), actBusiness.getTableName());

procInsId = actBusiness.getProcInstId();

}

。。。。。。

}

 

3.修改ant-design-vue-jeecg\src\views\activiti\todoManage.vue待办页面的

passTask(v,userParams)方法,在获取下个节点参与者时传入流程变量:

this.getAction(this.url.getNextNode,{procDefId:v.procDefId,

currActId:v.key, flowVarsJsonStr:userParams.flowVarsJsonStr}

 

32  Activiti部署Linux后工作流查看流程图乱码

 

代码里配置的Activiti相关字体用的都是宋体,而Linux中如果没有宋体则需要安装宋体(simsun.ttc)。

 

安装Linux系统字体步骤:

 

1.安装字体库:

yum -y install fontconfig

这时在/usr/share目录就可以看到fonts和fontconfig目录了(之前是没有的)

 

2.执行yum -y group info fonts

 

3.上传simsun.ttc到/usr/share/fonts/mswfonts目录中(mswfonts为自定义目录)

 

4.使字体生效:

fc-cache -fv

 

5.检测安装结果:

执行 fc-list 或 fc-list :lang=zh-cn,查看字体列表中是否包含所安装的字体信息;若测试不生效,尝试重启服务器即可。

 

*注意:

待办列表和已办列表查看流程图虽然调用的是同一个方法,但对于已结束的流程,代码里是直接获取流程原图的;对于未结束的流程,是通过API获取高亮实时流程图的。

ProcessDiagramGenerator diagramGenerator =

processEngineConfiguration.getProcessDiagramGenerator();

            inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivities, highLightedFlows, "宋体", "宋体", "宋体",null, 1.0);

 

33  添加IE浏览器兼容性配置

在ant-design-vue-jeecg\public\index.html中引入:

<script src="/cdn/babel-polyfill/polyfill_7_2_5.js"></script>

即可。

 

34  全局覆盖的ant-design-vue组件样式

a. 创建用于覆盖原有样式的自定义assets/less/resetAntd.less文件,内容如下:

@import '~ant-design-vue/dist/antd.less';

/*下面是以覆盖table标题行样式为例*/

.ant-table-thead > tr > th {

  color: rgba(0, 0, 0, 0.85);

  font-weight: 500;

  text-align: left;

  background: red;

  border-bottom: 1px solid #e8e8e8;

  transition: background 0.3s ease;

}

 

b. ant-design-vue-jeecg\src\main.js中做如下修改:

 

//注释掉原有组件less样式的引用

//import 'ant-design-vue/dist/antd.less';

 

//下面是引入自定义需要覆盖的ant-design-vue样式的方式

import './assets/less/resetAntd.less'

35  Activiti多人会签的实现

-默认的用户任务节点(单实例节点)就算设置多个参与者,他们直接也是竞争关系,就是说只要其中一个参与者处理了待办,流程节点就往下走了。

 

-而会签用户任务节点(多实例节点)主要是参与者有多个,这些参与者必须全部完成待办(或部分人员完成待办,这个看具体规则)时,流程节点才会往下走。

 

比如有如下流程:



-流程图多实例会签节点设置

 





-流程监听器设置









上面选择的流程监听器代码如下:

package org.jeecg.modules.activiti.listener;

 

import org.activiti.engine.delegate.DelegateExecution;

import org.activiti.engine.delegate.ExecutionListener;

 

import java.util.ArrayList;

import java.util.Arrays;

import java.util.List;

 

/**

 * 自定义监听器,用于节点多实例处理逗号分隔的参与者字符串为List

 */

public class MyExecutionListener implements ExecutionListener {

 

    // 保存流程中用到的会签参与者集合变量的名称集合,应尽量保持不同流程中会签参与者集合变量名的统一,如果会签参与者集合变量名不同,请在下面的List中设置即可。

    private static List<String> hqVariables = new ArrayList<>();

    static{

        hqVariables.add("assigneeList");

    }

 

    @Override

    public void notify(DelegateExecution delegateExecution) throws Exception {

        for(String temp : hqVariables){

            String assigneeList = String.valueOf(delegateExecution.getVariable(temp));

            if(assigneeList != null){

                // 根据逗号分割并以数组形式重新设置进去

                delegateExecution.setVariable(temp, Arrays.asList(assigneeList.split(",")));

            }

        }

 

    }

}

 

-注意:

 

1.因为是通过流程全局变量设置的会签节点参与者,所以在系统中已发布模型-》节点设置中的会签节点参与者设置就失效了,流程执行真正的参与者还是由全局变量的参与者确定:



2. 流程监听器中的静态集合变量hqVariables存放的是项目中不同流程中会签节点的集合(多实例)属性设置的全局变量名,应尽量保证项目中不同流程的会签节点该变量名一致;

如果真的遇到特殊情况,比如一个流程有多个不同的会签节点时,将不同的变量名称配置到hqVariables初始化static块中即可。

 

36  未完待续。。。

 

发布于: 2020 年 08 月 24 日阅读数: 407
用户头像

卧石漾溪

关注

还未添加个人签名 2020.05.04 加入

还未添加个人简介

评论 (2 条评论)

发布
用户头像
CAS是如何集成的呢jeecgboot可以做为SSO服务器吗
2020 年 09 月 04 日 17:09
回复
文档里有啊,我就是按文档里的步骤实现JeecgBoot和Cas Server的对接的
2020 年 09 月 17 日 18:30
回复
没有更多了
JeecgBoot手记