JeecgBoot 手记
【本文档为最近使用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://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 未完待续。。。
版权声明: 本文为 InfoQ 作者【卧石漾溪】的原创文章。
原文链接:【http://xie.infoq.cn/article/3ba30f765a59168b0cb8f39eb】。文章转载请联系作者。
评论 (2 条评论)