anyline 入门
配置maven环境
如果需要私服参考这里>maven私服
通常情况下每个公司会有自己的*-starter或*-dependency来统一管理maven依赖版本号
如:用anyline-simple-dependency来定义所有外部依赖的版本号
anyline-simple-start-mvc-mysql继承anyline-simple-dependency并添加mvc与mysql的依赖,
有需要myql和mvc环境的项目继承自anyline-simple-start-mvc-mysql
常用示例
源码地址: https://gitee.com/anyline/anyline-simple
关于更新部分属性(列)、忽略部分属性(列)
添加到DataRow中但不需要参与更新(插入)
row.put("-NAME", "ZH");
添加了空值, 默认情况下不参与更新(插入) 如果需要强制参与更新(插入)
row.put("+NAME",null);
如果这样指定了更新(插入)列,则只会更新(插入)指定的列,其他列都不会参与更新(插入)
service.save(row,"NAME");
强制更新(插入)NAME,忽略CODE,其他列不受影响,按默认情况处理
service.save(row,"+NAME","-CODE");
插入所有列,更新所有值发生过变化的列
service.save(row);
关于自动检测表结构
在执行数据库操作时,许多参数是以String形式获取到的,无法参数识别数据类型,如url中的参数
还有一些数据类型在Java中没有对应关系,如xml/josn/几何图形等
而有些数据库在执行SQL时会执行强类型检测。
不像我们平时用的MySQL在执行时会进行隐式转换,无论什么类型只要能转换成功就可以执行。
而PostgreSQL则要求jdbc参数与表结构对应,如果在varchar列中执行int类型会失败。
要求开发人员在编码过程中记住表数据类型,或进行类型转换显示不合理,何况表结构有可能会变动。
可以开启表结构自动检测,在执行SQL前把参数转换成与表结构对应的类型
这样就可以像MySQL一样随意了
ConfigTable.IS_AUTO_CHECK_METADATA = true;可以参考anyline-simple-jdbc-postgresql
DataSet构造多级树型结构
表结构类似这样
| ID | BASE_ID | NAME |
| 1 | NULL | 中国 |
| 2 | 1 | 山东 |
| 3 | 2 | 济南 |
| 4 | 2 | 青岛 |
| 5 | 2 | 烟台 |
| 6 | 3 | 历下区 |
| 7 | 3 | 天桥区 |
| 8 | 4 | 市南区 |
| 9 | 4 | 城阳区 |
//先取出完整列表
DataSet set = service.querys("SYS_AREA");
//ID:主键 BASE_ID:表示上一级ID的列名
set.dispatchs(true,true, "ID:BASE_ID");
set.dispatchs("children",true,true, "ID:BASE_ID");
//执行完成后会把每个DataRow中存入当前DataRow的下一级
//这里会生成多个树型结构,一般需要根据ID取出最顶级的DataRow set.getRow("ID",1);
get(String key)与getString(String key)的区别
get(String key)与Map的get(String key)效果一样
getString(String key)支持表达式getString("${ID}-${NAME}")
关于分页参数
默认的分页参数名比较长:
public static String DEFAULT_KEY_PAGE_ROWS = "_anyline_page_rows" ; //设置每页显示多少条的key
public static String DEFAULT_KEY_PAGE_NO = "_anyline_page" ; //设置当前第几页的key
public static String DEFAULT_KEY_TOTAL_PAGE = "_anyline_total_page" ; //显示一共多少页的key
public static String DEFAULT_KEY_TOTAL_ROW = "_anyline_total_row" ; //显示一共多少条的key
public static String DEFAULT_KEY_SHOW_STAT = "_anyline_navi_show_stat" ; //设置是否显示统计数据的key
public static String DEFAULT_KEY_SHOW_JUMP = "_anyline_navi_show_jump" ; //设置是否显示页数跳转key
public static String DEFAULT_KEY_SHOW_VOL = "_anyline_navi_show_vol" ; //设置是否显示每页条数设置key
public static String DEFAULT_KEY_GUIDE = "_anyline_navi_guide" ; //设置分页样式的key
public static String DEFAULT_KEY_ID_FLAG = "_anyline_navi_conf_" ; //生成配置文件标识public String KEY_PAGE_ROWS = DEFAULT_KEY_PAGE_ROWS ; //设置每页显示多少条的key
public String KEY_PAGE_NO = DEFAULT_KEY_PAGE_NO ; //设置当前第几页的key
public String KEY_TOTAL_PAGE = DEFAULT_KEY_TOTAL_PAGE ; //显示一共多少页的key
public String KEY_TOTAL_ROW = DEFAULT_KEY_TOTAL_ROW ; //显示一共多少条的key
public String KEY_SHOW_STAT = DEFAULT_KEY_SHOW_STAT ; //设置是否显示统计数据的key
public String KEY_SHOW_JUMP = DEFAULT_KEY_SHOW_JUMP ; //设置是否显示页数跳转key
public String KEY_SHOW_VOL = DEFAULT_KEY_SHOW_VOL ; //设置是否显示每页条数设置key
public String KEY_GUIDE = DEFAULT_KEY_GUIDE ; //设置分页样式的key
public String KEY_ID_FLAG = DEFAULT_KEY_ID_FLAG ; //生成配置文件标识是为了防止跟业务参数重名,但一般项目不会写这长啰嗦的参数名可以在配置文件anyline-navi.xml中修改参数名
如
<!--当前第几页-->
<property key="KEY_PAGE_NO">page</property>或者直接写在Java中,一般写在启动类中
PageNaviConfig.DEFAULT_KEY_PAGE_NO = "page";
PageNaviConfig.DEFAULT_KEY_PAGE_ROWS = "size";
PageNaviConfig.DEFAULT_VAR_PAGE_MAX_VOL = 1000;
PageNaviConfig.DEFAULT_VAR_CLIENT_SET_VOL_ENABLE = true;
几个常用的配置参数
//DataRow的key大小写(默认大写)
DataRow.DEFAULT_KEY_KASE = KeyAdapter.KEY_CASE.LOWER;
//http分页参数-当前当前第几页(默认page)
PageNaviConfig.DEFAULT_KEY_PAGE_NO = "pageNum";
//http分页参数-每页多少条(默认vol)
PageNaviConfig.DEFAULT_KEY_PAGE_ROWS = "pageSize";
//是否允许前端设置每页多少条(默认false)
PageNaviConfig.DEFAULT_VAR_CLIENT_SET_VOL_ENABLE = true;
关于日期类的链式操作
String ymd = DateBuilder.init().addYear(1).addDay(-1).format("yyyy-MM-dd");
excel根据内容定位单元格
用户提交的excel内容经常不固定行列,而是需要根据单元格内容来确定表头位置或数据起止位置。
如根据内容中包含"结算日期"的单元格来确定当前行是表头,下一行是数据
根据内容中包含"会计年度"的单元格来确定当前数据年度。
int[] position = org.anyline.poi.excel.ExcelUtil.position(file.getInputStream(),"时间.*");
返回第1个包含"时间"的单元格位置(下标从0开始)
支持正则表达式,
可以根据返回的坐标来读取单元格内容
String value = ExcelUtil.value(File或InputStream, position[0], position[1]);
以上默认第0个Sheet页,可以指定Sheet下标或名称
IN条件下多种参数格式的接收
对于标准的url格式 /list?id=1&id=2
以及标准的json格式 {id:[1,2]}
可以通过condition("ID:[id]")的形式接收
对于非标准格式如 /list?id=1,2
可以通过condition("ID:[split(id)]")的形式接收
最终都是生成SQL WHERE ID IN(1,2)
DataSet拆分
有些情况下需要对DataSet分组处理。如:查询出2000个手机号,如果一短信平台一次只能发500个
List<DataSet> list = set.split(set.size()/500);
for(DataSet items:list) {
List<String> mobiles = items.getDistinctStrings("mobile");
//发送短信
}
从列式数据库中取出坐标数据,格式化成地图轨迹需要的数据
轨迹原始数据(保存在列式数据库或thingsboard平台上)
lng=[{"ts":1655007789001,"value":120.1}, {"ts":1655007759002,"value":120.2}],
lat=[{"ts":1655007789001,"value":36.1}, {"ts":1655007759002,"value":36.2}]
通过org.anyline.thingsboard.util.ThingsBoardClient.getTimeseries()取出列的DataSet结构:
[{"TS":1657707789001, "LNG":120.1, "LAT":36.1},
{"TS":1657707759002, "LNG":120.2, "LAT":36.2}]
DataSet转换成地图轨迹常用的格式:
{
//点位时间
time: [1657707789001, 1657707759002],
//点位坐标
path: [
[120.1, 36.1],
[120.2, 36.2],
]
}
取出时间 List<Long> times = set.getLongs("TS");
取出坐标 List<Double[]> points = set.getDoubleArrays("LNG", "LAT");
不同环境下的配置文件
以anyline-aliyun-sms为例,每个工具类都会对应一个配置类与默认实例化类
如SMSUtil对应SMSConfig与SMSBeanSMSConfig用来配置帐号密码等
SMSBean用来在系统启动中往Spring上下文中注入一个默认的SMSUtil实例
SMSUtil就是开发中常用的工具了,如发送短信、查询短信接收状态、创建短信模板等
其中SMSConfig中的变量可以通过多种方式设置
1.配置文件anyline-aliyun-sms.xml(根据SMSConfig中的静态变量CONFIG_NAME = "anyline-aliyun-sms.xml";)
这个配置文件中可以配置多组帐号密码,开发过程中根据需要生成针对不同帐号的util
一般这样配置
<configs>
<config key="default">帐号、密码等</config>
<config key="instance-oa">帐号、密码等</config>
<config key="instance-crm">帐号、密码等</config>
</configs>
SMSUtil.getInstance("instance-crm")的方式获取不同的util
2.如果帐号无限多、如开发一个SAAS平台,这时的帐号密码会由不同的租户或用户自己设置,数据通常要保存在数据中。在运行过程中根据用户环境来调用不同的util
在实例化util前可以通过
SMSConfig.register("用户编号", DataRow)的方式先注册,其中DataRow中的KEY与配置文件中的KEY相对应
SMSConfig中一般会提供多个register的重载
再通过SMSUtil.getInstance("用户编号")的方式获取util
3.对于一些简单的项目,不想使用配置文件的可以通过2的方式直接register方式注册一个
也可以设置SMSConfig中的静态变更 DEFAULT_配置文件中的KEY
如DEFAULT_ACCOUNT(对应配置文件中的ACCOUNT)、DEFAULT_PASSWORD(对应配置文件中的PASSWORD)
这样在系统启动后会在Spring上下文中默认注入一个SMSUtil实例
4.现有的项目配置文件中设置,参考SMSBean中的属性
@Value("${anyline.aliyun.sms.key:}") private String ACCESS_KEY;
这样在系统启动后会在Spring上下文中默认注入一个SMSUtil实例
5.nacos配置中心
需要添加依赖anyline-nacos
anyline-nacos本身也有配置文件用来指定NACOS配置中心地址以及namespace/group
可以通过anyline-nacos.xml配置文件设置
如果是spring boot项目则按spring boot方式来配置如 nacos.config.server-addr
如果是spring cloud项目则按spring cloud方式来配置如 spring.cloud.nacos.config.server-addr
配置好nacos后在nacos中根据 根据SMSConfig中的静态变量CONFIG_NAME = "anyline-aliyun-sms.xml" 命名nacos中的dataId
关于merge与join的区别
都是用来合并集合, 都有返回值
merge将结果保存到新集合中
join把其他参数合并到前一个参数中,如果前一个参数为null则创建新集合
关于几种OR条件查询的情况
//以下三种格式,只有cd取值成功时,条件才生效
//当cd=1,id=2时 WHERE CODE = 1 OR CODE =2
//当cd=null,id=2时 条件不生效
//当cd=1,id=null时 WHERE CODE = 1
service.querys("HR_USER", condition("CODE:cd|id"));
//当cd=1时 WHERE CODE =1 OR CODE = 9
//当cd=null时 条件不生效
service.querys("HR_USER", condition("CODE:cd|{9}"));
//当cd=1时 WHERE CODE =1 OR CODE IS NULL
//当cd=null时 条件不生效
service.querys("HR_USER", condition("CODE:cd|{NULL}"));//当type=1,dept=null时 WHERE TYPE_CODE = 1
//当type=1,dept=2时 WHERE TYPE_CODE =1 OR DEPT_ID =2
//当type=null,dept=2时 WHERE DEPT_ID = 2
service.querys("HR_USER", condition("TYPE_CODE:type|DEPT_ID:dept"));//依次取c1,c2的值,如果c1取值成功则忽略c2,如果都失败则取默认值9
service.querys("HR_USER", condition("CODE:c1:c2:{9}"));anyline加载service失败
经常是因为没有扫描org.anyline包
如果是springboot项目 需要添加注释
@ComponentScan(basePackages = {"org.anyline","org.anyboot"})
如果用了springboot注意扫描一下org.anyboot
如果是spring-mvc项目 需要添加配置
<context:component-scan base-package="org.anyline"></context:component-scan>
读取带表头表尾的excel
File file = new File(dir,"template_102.xlsx");
ExcelReader reader = ExcelReader.init()
.setFile(file) //文件位置
.setSheet(1) //读取第1个sheet(下标从0开始)
.setHead(0) //表头在第0行,如果没有表头,结果集以下标作为key
.setData(1) //数据从第1行开始
.setFoot(1) //到第1行结束(如果负数表示 表尾有多少行不需要读取)
;
DataSet set = reader.read();
log.warn(set.toJSON());关于DataRow parse xml与parse json区别
**json结构相对简单每一对key value可以直接存入DataRow **
{name:zhang,age:20}
对应:
row.put("name","zhang");
row.put("age",20);
但xml比json多了一个attribute,这个attribute不直接存入DataRow
<user id="1"><name>zhang</name></user>
对应:
user.attr("id","1");
user.put("name","zhang");
同样取值时也通过user.attr("id");
html中抽取标签时嵌套问题
从html中抽取多个标签,如需要抽取a标签和li标签
最简单的是抽取两次RegularUtil.fetchAllTag(html,"a")RegularUtil.fetchAllTag(html,"li")
但这样有个问题,两个标签的顺序会乱,
如果需要保持顺序可以通过RegularUtil.fetchAllTag(html,"a","li");
但是一定注意:这里的a有可能被包含在li内部,这时li中的a不会再单独抽取
html代码中抽取指定标签
//获取所有超链接(a标签)
/*
* 提取单标签+双标签
* 不区分大小写
* 0:全文 1:开始标签 2:标签name 3:标签体 (单标签时null) 4:结束标签 (单标签时null)
* 注意标签体有可能是HTML片段,而不是纯文本
*/
List<List<String>> list = RegularUtil.fetchAllTag(html,"a");
log.warn("标签数量:"+list.size());
for(List<String> item:list){
log.warn("全文:"+item.get(0));
log.warn("开始标签:"+item.get(1));
log.warn("标签名称:"+item.get(2));
log.warn("标签体:"+item.get(3));
log.warn("结束标签:"+item.get(4));
}
//抽取所有 a标签和li标签
//一定注意:这里的a有可能被包含在li内部,这时的a不会再抽取
list = RegularUtil.fetchAllTag(html,"a","li");从html源码中截取字符串
//放多情况下我们并不需要复杂的标签内容,只需要截取几个关键字
//如提取商品名称和商品价格,而这两个值有可能是根其他内容混在一块的
//如以下这段源码
String html ="<div class='title' data-product='1001'>商品名称(限时)</div>"
+"<div class='price'>一个货币符号:100.00</div>";
//这时可以通过字符串截取的方式提取出价格
//第0个参数:源数据
//第1个到倒数第2个参数:100.00(就是我们要提取的价格) 之前出现的关键字
//最后1个参数:100.00之后出现的第1个关键字
//参数顺序: 源码,k1,k2,k3,kn-1,内容,kn
String price = RegularUtil.cut(html, "price",":","</div>");
log.warn("价格:{}",price);
//许多情况下price有可能在源码中出现多次,这时需要多个关键字的组合来确认100.00的位置
html = DateUtil.format("yyyy-MM-dd")+ "<div class='title' data-product='1001'>商品名称(限时)</div>" +
"div class='src-price price'></div>" +
"<div class='price'>一个货币符号:100.00</div>元";
price = RegularUtil.cut(html,"src-price","price", "price",":","</div>");
log.warn("价格:{}",price);
//如果需要提取的内容在最后 如上面的单位:元
String unit = RegularUtil.cut(html,"src-price","price", "price",":","</div>", RegularUtil.TAG_END);
log.warn("单位:{}", unit);
//同样的如果需要提取的内容在最开始位置 如上面的日期
String ymd = RegularUtil.cut(html, RegularUtil.TAG_BEGIN, "<");
log.warn("日期:{}", ymd);导出复杂的表格需要借助TableBuilder先生成Table,再将Table导出到excel中
TableBuilder builder = TableBuilder.init()
.setDatas(set) //设置数据源
.setFields( //需要导出的列
"{num}(EMPLOYEE_NM)" //{num}表示序号,(DEPARTMENT_NM)表示根据哪一列计算序号,这里部门名称需要分组合并,所以num不是按行计算
,"DEPARTMENT_NM"
,"EMPLOYEE_NM"
,"YM"
,"BASE_PRICE")
.addUnion( //需要合并的列
"DEPARTMENT_NM" //如果部门名称相同则合并
,"EMPLOYEE_NM(DEPARTMENT_NM)"
,"YM(DEPARTMENT_NM)" //如果月份相同则合并,前提是部门已经合并
)
.setReplaceEmpty("/") //如果值为空则以/代替
.addIgnoreUnionValue("/") //不参与合并的值
.setCellBorder(true) //设置默认边框
.setMergeCellHorizontalAlign("center") //设置合并的列 水平对齐方式
.setMergeCellVerticalAlign("top") //设置合并的列 垂直对齐方式
.setEmptyCellHorizontalAlign("center") //设置空单元格 水平对齐方式(为空时有可能需要替换成其他值)
.setEmptyCellVerticalAlign("top") //设置空单元格 垂直对齐方式
.setHorizontalAlign("YM","center") //设置月份列 水平对齐方式
.setVerticalAlign("middle") //设置所有数据单元格 垂直对齐方式
.setLineHeight("50px") //设置数据区域行高
.setWidth("YM","200px") //设置月份列 宽度
;
Table table = builder.build();
File file = new File(dir, "export_table.xlsx");
ExcelUtil.export(file, table);最简单的导出一个列表,如果文件已存在,则在原文件内容基础上插入行
DataSet set = service.querys("V_HR_SALARY","YYYY:"+ (DateUtil.year()-1), "ORDER BY EMPLOYEE_ID, YM");
//最简单的导出一个列表,如果文件已存在,则在原文件内容基础上插入行
File file = new File(dir,"export_list.xlsx");
//1表示从第1行插入,如果原来文件有内容,则下移
//{num}表示第几行,下标从1开始
//这里支持复合KEY
ExcelUtil.export(file,1, set,"序号:{num}","部门:DEPARTMENT_NM","姓名:EMPLOYEE_NM","月份:YM","底薪:{BASE_PRICE}+{REWARD_PRICE}");
//如果表头、表尾格式比较复杂,可先创建模板,再根据模板导出
File template = new File(dir,"template.xlsx");//这里是一个模板文件
//根据模板导出时就不需要指定表头了,只要对应好顺序,并计算好从哪一行开始写入
if(template.exists()) {
ExcelUtil.export(template, file, 1, set, "{num}", "DEPARTMENT_NM", "EMPLOYEE_NM", "YM", "{BASE_PRICE}+{REWARD_PRICE}");
}读取合并单元格的excel
File file = new File(dir,"export_table.xlsx");
List list = ExcelUtil.read(file); //默认读取第0个sheet从第0行开始
list = ExcelUtil.read(file,1,3); //读取第1个sheet从第3行读取
//遇到合并单元格的,将拆分开未合并前的状态,拆分后补上每个单元格的值
//返回的是一个二维数组
//为了操作方便可以把返回值转换成DataSet,DataSet中的条目(DataRow)以excel列下标作为属性key
DataSet set = new DataSet(list);导出excel,多个sheet
//默认情况下导出的第0个sheet也就是sheet1
//如果要导出到多个sheet需要执行多次export导出到同一个文件,每次执行时指定sheet名称或下标
Table table = null;
File file = new File(dir, "export_sheet.xlsx");
ExcelUtil.export(file, "shet1", table);
ExcelUtil.export(file, "shet2", table);从人员列表中,查出所有不重复的部门名称
DataSet set = service.query("HR_USER");
set.distinct("DEPARTMENT_NM"); //这里返回的还是人员列表,但一个部门只返回一个
.concat("DEPARTMENT_NM"); //这里返回String并以逗号分隔:部门A,部门B
List<String> departments = set.getDistinctStrings("DEPARTMENT_NM"); //这里返回一个不重复的部门名称List
设置单元格默认边框
TableBuilder.init()..setCellBorder(true)设置所有空值单元格对齐方式
有些情况下,需要把空值替换成其他固定的符号如(/)
这时可以设置这些单元格的对齐方式
TableBuilder.init()
.setEmptyCellVerticalAlign("top")
.setEmptyCellHorizontalAlign("center")设置指定列的对齐方式
TableBuilder.init()
.setHorizontalAlign("CHECK_CODE", "center")
.setVerticalAlign("CHECK_CODE", "top")设置所有合并单元格的对齐方式
TableBuilder.init()
.setMergeCellHorizontalAlign("center") //设置合并单元格水平对齐方式
.setMergeCellVerticalAlign("center") //设置合并单元格垂直对齐方式导出EXCEL中的序号合并单元格
在导出excel时有可能不是每行一个序号,而是每组一个序号,如按部门分组,每个部门一个序号
| 1 | 人事部 | 张三 | 20 |
| 张三三 | 22 | ||
| 2 | 财务部 | 李四 | 25 |
| 王五 | 26 | ||
王五五 | 20 |
这时num需要部门列来合并单元格
TableBuilder builder = TableBuilder.init()
.setDatas(set) //设置数据源
.setFields("{num}(DEPARTMENT_NAME)"
,"DEPARTMENT_NAME"
,"USER_NAME"
,"USER_AGE") //设置需要导出的属性(列)
.addUnion("DEPARTMENT_NAME"); /设置需要合并行的列,如果年相同的合并,月相同的合并(前提是年相同)
File file = new File("模板地址");
ExcelUtil.export(file, "sheet名称", 2, builder.build()); //从第2行插入(根据表头行数)导出EXCEL中的序号
导出excel时如果每行需要一个序号可以用{num}来代替属性名,如
export(file, list, "序号:{num}","姓名:NAME","年龄:AGE")
| 1 | 张三 | 20 |
| 2 | 李四 | 22 |
| 3 | 王五 | 25 |
关于DataRow中get与getString的区别
DataRow中get是覆盖了父类Map的get
getString在get的基础上增加了复合KEY的支持,如getString("{ID}/{CODE}")
关于DataRow的复合KEY
许多情况下需要从DataRow中取多个值合并显示。如导出excel时地址列需要合并省市区详细地址
DataRow可以取多个值拼接,但DataSet则需要遍历,非常麻烦
DataRow提供了复合KEY取值的函数
如{ID:1,CODE:A01,NAME:张三}
row.getString("{ID}-{CODE}")可以取出 1-A01row.getString("编号:{CODE};姓名:{NAME}")可以取出 编号:A01;姓名:张三
需要注意的是如果其中一个KEY取值为null 或 KEY不存在则以""代替,而不是"null"
类似的在导出EXCEL时指定需要导出的列时也可以使用复合KEY
将所有列中的oldChar替换成newChar
将key列中的oldChar替换成newChar
public DataSet replace(String key, String oldChar, String newChar)
将所有列中的oldChar替换成newChar
public DataSet replace(String oldChar, String newChar)
替换DataSet,DataRow中所有空值(null,'')
将所有列中的空值替换成value
public DataSet replaceEmpty(String value)
al:checkbox 自定义value key与text key
根据集合数据源生成checkbox时,默认情况下会取集合中条目的ID值作为input的value值,取条目的NM值作为label的标签体
但数据源经常是从数据库中查询出来的,列中并不一定有ID和NM,也有可能是CODE,TITLE或其他情况
这时需要显式指定value key与text key
<al:checkbox data="${set }" valueKey="CODE" textKey="TITLE" />
往同一个excel中写多个sheet
public static boolean export(File file, String sheet, int rows, DataSet set, String ... configs)如果文件存在则在当前文件中插入数据,如果文件不存在则新创建文件
这里的sheet如果在file中已存在,则往这个sheet中插入数据,如果不存在则新创建sheet再继续插入数据,其他重载函数规则相同。
如
File file = new File();
export(file, "s1",0, set, "ID"); //这一行执行完成后在文件中有一个sheet(s1)
export(file, "s2",0, set, "ID");//这一行执行完后文件中有两个sheet(s1,s2)
关于al:number中的def与evl
def是指如果没有输入值,则取默认值,默认值同样会执行格式化与其他运算
evl/nvl是指如果结果为empt(null),则直接输出evl/nvl,不会执行格式化,不会执行运算
关于al:date中的nvl与dev
value与body为空的情况下
如果nvl=true则显示当前日期,如果nvl=false则不输出
如果nvl=其他值,则直接输出nvl
如果没有指定value,body,nvl都为空,同时def有值,则输入def
需要注意的是def与nvl=true时,日期都会参与格式化与运算(如增加一天),而nvl=其他值时,直接原样输出evl并不执行格式化与其他运算
关于分页后退保持页数
有一种典型的场景AJAX分页,
用户第一次打开列表页时默认显示第1页。
用户在列表页中切换到第2页后,在第2页中打开一个条目的明细。操作完明细后退。返回到到列表
这时列表页需要直接显示第2页数据。
如何区分是后退过来的还是页面刷新过来的。navi是在列表页中存放了一个隐藏的input在切换页数后修改这个input
刷新过来的页面这个input值为空,而后退过来的这个input值为2
根据浏览器加载机制,在页面加载完成之后才会给input赋值。所以在页面加载过程中取不到这个input值。
可以把需要取input值的代码放在setTimeout(function(){取值},0);
向压缩文件中追加文件
public static boolean append(File item, File zip, String dir, String comment)
public static boolean append(File item, File zip)替换压缩包中的文件
//zip压缩包中的item文件替换成content文件
public static void replace(File zip, File content, String item)
//zip压缩包中的item文件替换成content内容
public static void replace(File zip, String content, String item)读取压缩包中的文件内容
//读取zip压缩包中的item文件内容
public static InputStream read(File zip, String item)
以String格式返回
public static String read(File zip, String item, String encode)删除压缩包中的指定文件
删除zip中的item文件(含目录)
public static boolean remove(File zip, String item)
ZipUtil.remove(new File(”users.zip“),"user1.xml")
ZipUtil.remove(new File(”users.zip“),"list/user1.xml")压缩文件
把item文件压缩到zip文件中
如果item是一个目录,则递归item中所有文件
如果zip已存在则覆盖原文件
public static boolean zip(File item, File zip)
ZipUtil.zip(new File("D:\\users\\user.xml"), new File("D:\\user.zip"));
ZipUtil.zip(new File("D:\\users"), new File("D:\\user.zip"));
把item文件压缩到zip文件中的dir目录
public static boolean zip(File item, File zip, String dir)
public static boolean zip(Collection<file> items, File zip)
把items文件集合压缩到zip文件中,以key作为压缩目录
public static boolean zip(Map<string,file> items, File zip)
//替换zip压缩包中的item文件
public static void replace(File zip, File content, String item)
public static void replace(File zip, String content, String item)
//删除zip压缩包中的item文件
public static boolean remove(File zip, String item)
//读取zip压缩包中的item文件内容
public static InputStream read(File zip, String item)
public static String read(File zip, String item, String encode)
</string,file></file>更多常用示例在后文中有所展现,囿于文章篇幅所限,此处不过多做介绍!