公司用了这个叫做jeecg的快速开发框架,我不知道有多少公司在用这个框架,园子里有的可以吱一声。个人觉得这框架唯一优势就是可以让不会ssh的人也能进行开发,只要你会J2SE,有web后台发开经验即可。

框架的优劣这里不做说明,但是官方文档真的写的很粗糙,很多时候需要自己额外添加一些功能的时候会有一点无处下手的感觉。接触了一段时间后,也踩了不少的坑,现在记录一下,以飨读者。

jeecg版本:3.7.1

Tips

前端

  1. 权限管理设置中,按钮权限需要对相应的按钮设置OperateCode字段,然后在后台菜单管理-页面权限控制中配置相应的规则,接着去角色管理中分配权限,注意checkbox选中状态下为显示该按钮(此处与文档中描述的相反!)。
  2. dgToolBar中对应的funname中的方法(例如add、update),都在curdtools_zh-cn.js文件中,写新的方法时可以去那里面复制。
  3. 针对于<t:datagrid>中的显示,<t:dgCol/>中如果有表示状态的字段,数据库可能存int,而显示需要中文,可以使用dictionary属性,如果对应的中文直接添加在系统后台的数据字典中(系统管理-数据字典),则直接dictionary=[字典名称];如果数据库中存在代码表,则dictionary=[表名,编码,显示文本]
  4. 针对于表单中的显示,状态选择可以使用下拉控件<t:dictSelect>,其中typeGroupCode属性填写数据字典名称。
  5. 文件上传推荐使用<t:webUploader>控件,具体代码见Snippets。t:webUploader是h5的,兼容性较好。
  6. <t:formvalid>表单中,需要手动提交表单,需要一个id为btn_sub的按钮。
  7. 表单页面中,设置input设置disabled="disabled" 后,该元素的内容不会提交表单,如果需要提交,但不可编辑,请使用readonly="readonly"
  8. 使用<t:dgOpenOpt>时注意,默认的openModeOpenWin,需要为其设置width和height,否则报错;OpenTab时则不需要设置。
  9. jeecg所有封装的控件的urlfont属性为图标设置,可以更换Font Awesome中的所有图标。

后台

  1. SpringMVC路由默认采用param形式,即xxController.do?getList曾经一度想改成xx/getList,尝试多次后失败,事实证明代码关联太强,不推荐修改。
  2. 数据表设计中如果包含添加人,添加时间的可以直接使用jeecg指定字段(create_time,create_by,create_name,update_time,update_by,update_name等),jeecg自带aop绑定,更新时会 自动赋值。具体查看DataBaseConstant.java和HiberAspect.java
  3. GUI代码生成器中,若pk为uuid,主键生成策略选择uuid,若为自增的id,则选择identity。
  4. GUI代码生成器中,form风格个人推荐选择div风格,使用表格时,Validform会有坑。
  5. GUI代码生成器中,推荐使用一对一关系来建表,需要一对多等别的关系时,可以添加注解来实现(@OneToMany,@ManyToOne
  6. 路由的全局拦截器文件为AuthInterceptor.java和SignInterceptor.java,在里面添加系统的拦截规则。
  7. 后台可以配置过滤器来解决全局跨域问题。代码见Snippets。
  8. 清理jeecg自带版本号和logo信息,注意他的国际化内容,文字信息均存在数据表t_s_muti_lang中,无法直接在源代码中搜索到。
  9. 定时任务有bug,暂未解决,存在实例化多次的情况。
  10. 事务处理,添加注解@Transactional(rollbackFor=Exception.class)
  11. t:datagrid查询问题,对时间查询,如果时间格式是年月日+时分秒,则无法查询,需要修改代码文件,将原来的区间格式由xxxbegin1、xxxend2改成xxxbegin1、xxxend2
  12. t:datagrid 查询问题,如果使用多对多的关系进行查询,直接对字段添加query=true,如果关系多于二级则无法查询。例如放款表中有一个借款表外键,借款表有一个用户表的外键。在显示的时候,显示字段的field=borrow.user.name,那么,就算设置了query=true,查询也是无效的。

Snippets

1.跨域过滤器

public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
} @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.addHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Appkey");
filterChain.doFilter(servletRequest, servletResponse);
} @Override
public void destroy() {
}
}
<!-- web.xml中配置-->
<filter>
<filter-name>cors</filter-name>
<filter-class>cn.crenative.afloan.core.controller.CorsFilter</filter-class>
</filter> <filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

2.文件上传

    @RequestMapping(params = "doUpload", method = RequestMethod.POST)
@ResponseBody
public AjaxJson doUpload(MultipartHttpServletRequest request,
String path) {
logger.info("后台上传文件");
AjaxJson j = new AjaxJson();
String fileName = null;
String ctxPath = request.getSession().getServletContext().getRealPath(path);
File file = new File(ctxPath);
if (!file.exists()) {
file.mkdir();// 创建文件根目录
}
Map<String, MultipartFile> fileMap = request.getFileMap();
for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {
MultipartFile mf = entity.getValue();// 获取上传文件对象
fileName = mf.getOriginalFilename();// 获取文件名
String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String newFileName = df.format(new Date()) + "_" + new Random().nextInt(1000) + "." + fileExt;
String savePath = file.getPath() + "/" + newFileName;// 上传后的文件绝对路径
System.out.println("上传后路径:" + savePath);
File savefile = new File(savePath);
try {
// String imageUrl = "http://" + request.getServerName() + ":" + request.getLocalPort() + request.getContextPath() + path + "/" + newFileName;
String imageUrl = request.getContextPath() + path + "/" + newFileName;
logger.info("输出路径:" + imageUrl);
mf.transferTo(savefile);
j.setObj(imageUrl);
} catch (IOException e) {
e.printStackTrace();
}
}
j.setMsg("上传成功");
return j;
}
 <t:webUploader url="upload.do?doUpload&path=[相对路径]" name="[数据库字段]" extensions="" auto="true" pathValues="${后端set的attribute名称}"/>
<!-- eg:-->
<t:webUploader url="upload.do?doUpload&path=/upload/afloan/users/attachment" name="credentialPhoto" extensions="" auto="true" pathValues="${attachmentPage.credentialPhoto}"/>

3.dictSelect

  <t:dictSelect field="credentialType" type="select" defaultVal="${attachmentPage.credentialType}" typeGroupCode="attachment" hasLabel="false"/>

4.全局表单元素的隐藏

$(":input").attr("disabled", "true");
$('select').attr('disabled', true);

5.添加一个提示的窗口

 layer.open({
title: title, //弹窗title
content: content, //弹窗内容
icon: 7,
yes: function (index) {
//回调函数
},
btn: ['确定', '取消'],
btn2: function (index) {
layer.close(index);
}
});

6.选择datagrid中选中的行。

var rowsData = $('#' + id).datagrid('getSelections');
//获取具体的字段名,推荐第二种取值形式,如果使用一对多,name字段名可能长这样(user.id),使用第一种方式会报错
console.log(rowData[0].fieldname)
console.log(rowData[0]['fieldname')

7. 选择datagrid中选中的行

// 在方法中添加index,控件会自动添加选择的行号
<t:dgFunOpt title="删除" funname="deleteOne()" urlclass="ace_button" urlfont="fa-trash-o"/> function deleteOne(index) {
console.log(index);![Alt text](./popup.gif) var row = $("#usersList").datagrid('getData').rows[index];
}

8.添加一个新的标签页

//function addOneTab(subtitle, url, icon),该方法定义在curdtools_zh-cn.js中
function openAuditTab(id, mobile) {
addOneTab("用户" + mobile + "的档案", "userInfo?userInfo&mode=claim&userId=" + id);
}

9.popup,弹框选择相应的记录,并回调到父页面。

//设置表单内容
function setUser(obj, rowTag, selected) {
if (selected == '' || selected == null) {
alert("请选择");
return false;
} else {
var str = "";
var name = "";
var idNo = "";
$.each(selected, function (i, n) {
str += n.mobile;
name += n.realName;
idNo += n.idcardNo;
});
$("input[id='" + rowTag + ".mobile']").val(str);
$("input[id='" + rowTag + ".realName']").val(name);
$("input[id='" + rowTag + ".idcardNo']").val(idNo);
return true;
}
} /**
* 弹出popup窗口获取
* @param obj
* @param rowTag 行标记
* @param code 动态报表配置ID
*/
function selectUser(obj, rowTag) {
if (rowTag == null) {
alert("popup参数配置不全");
return;
}
console.log($('#mobile').val());
var inputClickUrl = basePath + "/users?userSelect";
if (typeof (windowapi) == 'undefined') { //页面弹出popup
$.dialog({
content: "url:" + inputClickUrl,
zIndex: getzIndex(),
lock: true,
title: "选择客户",
width: 1000,
height: 300,
cache: false,
ok: function () {
iframe = this.iframe.contentWindow;
var selected = iframe.getSelectRows(); //重要,此处获取行数据
return setUserMobile(obj, rowTag, selected);
},
cancelVal: '关闭',
cancel: true //为true等价于function(){}
});
} else { //popup内弹出popup
$.dialog({
content: "url:" + inputClickUrl,
zIndex: getzIndex(),
lock: true,
title: "选择客户",
width: 1000,
height: 300,
parent: windowapi, //设置弹出popup的openner
cache: false,
ok: function () {
iframe = this.iframe.contentWindow;
var selected = iframe.getSelectRows(); //重要,此处获取行数据
return setUserMobile(obj, rowTag, selected);
},
cancelVal: '关闭',
cancel: true //为true等价于function(){}
});
}
}
<!-- 方法绑定 -->
<input class="inputxt" onclick="selectUser(this,'user');" placeholder="点击选择客户" id="user.mobile"
name="appUser.mobile" value="${borrowInfoPage.appUser.mobile}"/>

10.一对多关系的使用

具体例子:借款订单(afl_borrow_info)中存在用户表(afl_user)外键,通过user_id关联。

@Entity
@Table(name = "afl_borrow_info", schema = "")
@DynamicUpdate(true)
@DynamicInsert(true)
@SuppressWarnings("serial")
public class BorrowInfoEntity implements java.io.Serializable {
private UsersEntity appUser; @ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
public UsersEntity getAppUser() {
return appUser;
} public void setAppUser(UsersEntity appUser) {
this.appUser = appUser;
}
}

关联之后,所有的查询(service层)和页面渲染(jsp),均不再使用user_id而是使用appUser.id,别的字段同理。

Jeecg踩坑不完全指南的更多相关文章

  1. 『OGG 02』Win7 配置 Oracle GoldenGate Adapter Java 踩坑指南

    上一文章 <__Win7 配置OGG(Oracle GoldenGate).docx>定下了 两个目标: 目标1: 给安装的Oracle_11g 创建 两个用户 admin 和 root ...

  2. Spring WebSocket踩坑指南

    Spring WebSocket踩坑指南 本次公司项目中需要在后台与安卓App间建立一个长连接,这里采用了Spring的WebSocket,协议为Stomp. 关于Stomp协议这里就不多介绍了,网上 ...

  3. Microsoft SQL Server on Linux 踩坑指南

    微软用 SQL Server 在 2016 年的时候搞了一个大新闻,宣传 Microsoft ❤️ Linux 打得一众软粉措手不及.但是这还是好事情,Linux 上也有好用的 SQL Server ...

  4. C# -- HttpWebRequest 和 HttpWebResponse 的使用 C#编写扫雷游戏 使用IIS调试ASP.NET网站程序 WCF入门教程 ASP.Net Core开发(踩坑)指南 ASP.Net Core Razor+AdminLTE 小试牛刀 webservice创建、部署和调用 .net接收post请求并把数据转为字典格式

    C# -- HttpWebRequest 和 HttpWebResponse 的使用 C# -- HttpWebRequest 和 HttpWebResponse 的使用 结合使用HttpWebReq ...

  5. 新书推荐《再也不踩坑的Kubernetes实战指南》

      <再也不踩坑的Kubernetes实战指南>终于出版啦.目前可以在京东.天猫购买,京东自营和当当网预计一个星期左右上架. 本书贴合生产环境经验,解决在初次使用或者是构建集群中的痛点,帮 ...

  6. 树莓派4B踩坑指南 - (15)搭建在线python IDE

    今天想在树莓派上自己搭一个在线的python IDE,于是找到了一篇教程--Fred913大神的从头开始制作OJ-在线IDE的搭建 自己尝试动手做了一下, 还是发现不少细节需要注意, 记录在此 如果不 ...

  7. 正则表达式 test 踩坑指南

    正则表达式 test 踩坑指南 test 只能使用一次,第二次返回的是错误结果! reg = /edg|edge/g; /edg|edge/g reg.test(`edg`) true reg.tes ...

  8. Taro 开发踩坑指南 (小程序,H5, RN)

    Taro 开发踩坑指南 (小程序,H5, RN) css taro 如何展示多行文本省略号 https://www.cnblogs.com/xgqfrms/p/12569057.html UI 设计稿 ...

  9. 小程序 & taro 踩坑指南

    小程序 & taro 踩坑指南 微信开发者工具, 不支持 react bug https://github.com/NervJS/taro/issues/5042 solution just ...

随机推荐

  1. 【SqlServer系列】表达式(expression)

    1   概述 本篇这文章主要概述SqlServer表达式. 2   具体内容 2.1  使用范围 SQL Server(2008开始) :Azure SQL数据库:Azure  SQL数据仓库:并行数 ...

  2. javascript中的事件Event

    一.事件流 1.事件流:描述的是从页面中接受事件的顺序 IE的事件流是事件冒泡流,Netscape的事件流是事件捕获流. 2.事件冒泡 IE的事件流叫做事件冒泡(event bubbing),即事件开 ...

  3. ASP.NET Core 使用 Hangfire 定时任务

    定时任务组件,除了 Hangfire 外,还有一个 Quarz.NET,不过 Hangfire .NET Core 支持的会更好些. ASP.NET Core 使用 Hangfire 很简单,首先,N ...

  4. Asp.Net Web API中使用Session,Cache和Application的几个方法

    在ASP.NET中,Web Api的控制器类派生于ApiController,该类与ASP.NET的Control类没有直接关系,因此不能像在Web MVC中直接使用HttpContext,Cache ...

  5. Platt SMO 和遗传算法优化 SVM

    机器学习算法实践:Platt SMO 和遗传算法优化 SVM 之前实现了简单的SMO算法来优化SVM的对偶问题,其中在选取α的时候使用的是两重循环通过完全随机的方式选取,具体的实现参考<机器学习 ...

  6. 【java】method.invoke(方法底层所属对象/null,new Object[]{实际参数})

    反射调方法时无论是静态/非静态,固定/可变参数,都有Object对象数组对参数进行包装. package com.tn.clas; import java.lang.reflect.Method; i ...

  7. C:数据结构与算法之单链表

    单链表相对于顺序表比较难理解,但是比较实用,单链表的插入,删除不需要移动数据元素,只需要一个指针来寻找所需要的元素,还有一个大优点就是不浪费空间,当你想要增加一个结点可以申请(malloc())一个结 ...

  8. 基于阿里云的MQTT远程控制

    好久没有写博客了,眼看自己的项目就要快做完了,先分享一下基于MQTT的远程控制,自己买了一个阿里的云端,然后在云端上安装了一个MQTT服务器,其实是一不小心买了两个,所以准备贡献出来一个供大家使用, ...

  9. Java NIO (二) 缓冲区(Buffer)

    缓冲区(Buffer):一个用于特定基本数据类型的容器,由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类. Java NIO 中的Buffer 主要用于和NIO中的通道(Ch ...

  10. Sublime Text 2 Plugin Installation

    For Package Control installation, see the Installation Guide.   To install Emmet(ex Zen Coding), do ...