实现解析数据源、生成SQL、读取数据源、解析word模板、识别占位符与约定标识符、识别模板样式(字体尺寸颜色等)、填充数据。
用来保存前端配置的表结构大概这样
前端提交的配置数据类似这样【完整格式参考】
{ template:1 //模板 datasources:[{ //数据源 name:'数据源名称' tables:[{ table:'hr_department' //数据源-关联表名 join:'left' //连接方式(inner:内连接, left:左连接 ,right:右连接) relations:[{ //关联条件 join:'and' //关联方式(可选and,or,ors) column:'id' //列名 compare:10 //比较运算符 value:null //常量值或变量(二选一) relattion_table:'hr_employee' //比较表名(二选一) relattion_column:'department_id' //比较列名 }] //end-relattions }] //end-tables ,conditions:[{ //过滤条件 join:'and' table:'hr_employee' column:'type_id' compare:10 value:100 //常量值或变量 }]//end-conditions ,havings:[{ //分组过滤条件 join:'and' method:'sum' table:'hr_employee' column:'type_id' compare:10 value:100 //常量值或变量 }]//end-havings ,orders:[{ //排序 table:'hr_employee' column:'type_id' type:'ASC' }] ,groups:[{ //分组 table:'hr_employee' column:'type_id' }] }] //end-datasources }把以上参数保存到各自的表中。
关于于常量值或变量
常量就不用说了配置时直接提供值
变量分内置变量和配置变量
内置变量应该提交约定好
一般有两类
1.后台约定或数据库约定,如{id}或{pk}表示主表的主键
2.前端约定,一般固定在url参数中附带,如打印时url中要带一个id=123参数来定位要打印哪行数据, {param.id}就表示那个123
配置变量比较灵活,与前端约定的区别是,这里的变量名一般由用户指定
在填充模板时,前端会提交一个参数用来定位数据,如id=123
接下来根据参数123和配置项从数据库中提取数据。
1.组装SQL
2.执行SQL获取结果集(如果只有一个数据库源(库),这一步就执行一个SQL就完事了)
3.解析word中的占位符,根据占位符从结果集中提取数据
4.替换占位答生成新的word
组装SQL的细节比较多,主要是关联条件与过滤条件的组合,这里我们通过一个ConfigStore来实现,参考【ConfigStore】
Map<String, DataSet> values = new HashMap<>(); //多个数据源 DataSet ds = service.querys("t_ds_datasource", "template_id:"+template.getId()); if(!ds.isEmpty()) { //数据源关联的全部数据 //所有的关联条件和过渡条件一次查出 避免重复查询数据库 DataSet all_tables = service.querys("t_ds_datasource_table", new DefaultConfigStore().in("DATASOURCE_ID", ds.getStrings("ID"))); DataSet all_relations = service.querys("t_ds_datasource_table_relation", new DefaultConfigStore().in("DATASOURCE_ID", ds.getStrings("ID"))); DataSet all_filters = service.querys("t_ds_datasource_filter", new DefaultConfigStore().in("DATASOURCE_ID", ds.getStrings("ID"))); for(DataRow datasource:ds){ //每个 数据组组装一条SQL 返回一个结果集 ConfigStore configs = new DefaultConfigStore(); String masterTableName = datasource.getString("TABLE_NAME"); String masterTableAlias = datasource.getString( "TABLE_ALIAS"); if(BasicUtil.isEmpty(masterTableAlias)){ masterTableAlias = masterTableName; } //TableBuilder实现多表关联 org.anyline.data.param.TableBuilder builder = org.anyline.data.param.TableBuilder.init(masterTableName +" AS "+ masterTableAlias); //需要查询的列 List<String> columns = new ArrayList<>(); DataSet tables = all_tables.getRows("DATASOURCE_ID", datasource.getId()); if(tables.size() == 0){ //如果只有一个表直接查*省了查询表结构的过程 columns.add("*"); }else{ //如果有多个表,一般会出现列重名的情况,所以需要一个前缀来区分(这里用表名或别名__) List<String> cols = service.columns(masterTableName); for(String col:cols){ columns.add(masterTableName+"."+col + " AS " + masterTableAlias + "__" + col); } for(DataRow table:tables){ String tableName = table.getString( "NAME"); //有别名的用别名,没有别名的用原表名 String tableAlias = table.getString( "ALIAS", "NAME"); cols = biz.columns(tableName); for(String col:cols){ //添加前缀 避免列重名 columns.add(tableName+"."+col + " AS " + tableAlias + "__" + col); } } } //每个表参考主表重复上面的过程 for(DataRow table:tables){ String condition = null; String tableName = table.getString("NAME"); String tableAlias = table.getString( "ALIAS"); if(BasicUtil.isEmpty(tableAlias)){ tableAlias = tableName; } //最关键的ConfigStore需要熟悉 ConfigStore conditions = new DefaultConfigStore(); //与数据源和表相关的 关联条件 DataSet relations = all_relations.getRows("DATASOURCE_ID", datasource.getId(), "TABLE_ID", table.getId()); //关联条件 for(DataRow relation:relations){ String columnName = relation.getString("COLUMN_NAME"); String value = relation.getString("VAL"); String join = relation.getString("JOIN_CODE"); Compare compare = compare(relation.getInt("COMPARE_CODE", 10)); String relationTableName = relation.getString("RELATION_TABLE_NAME"); String relationTableAlias = relation.getString("RELATION_TABLE_ALIAS", "RELATION_TABLE_NAME"); String relationColumnNmae = relation.getString("RELATION_COLUMN_NAME"); if(BasicUtil.isEmpty(value)) { //两个表之间关联 conditions.condition(join, compare, concat(tableAlias, columnName) , "${"+concat(relationTableAlias, relationColumnNmae)+"}"); }else{ //常量值 conditions.condition(join, compare, concat(tableAlias, columnName), value); } } //生成ON关联条件 ConditionChain chain = conditions.getConfigChain().createAutoConditionChain(); condition = chain.getRunText(false, null, RuntimeHolder.getRuntime()); //把占位值添加到SQL主体中 List<RunValue> vals = chain.getRunValues(); for(RunValue runValue:vals){ configs.addStaticValue(runValue.getValue()); } //left join/right join Join.TYPE join = Join.TYPE.valueOf(table.getString("JOIN_CODE").toUpperCase()); builder.join(join, tableName, condition); } //过滤条件 DataSet filters = all_filters.getRows("DATASOURCE_ID", datasource.getId()); for(DataRow filter:filters){ String value = filter.getString("VAL"); String join = filter.getString("JOIN_CODE"); String tableName = filter.getString("TABLE_NAME"); String columnName = filter.getString("COLUMN_NAME"); Compare compare = compare(filter.getInt("COMPARE_CODE", 10)); configs.condition(join, compare, concat(tableName, columnName), value); } configs.columns(columns); configs.and(masterTableAlias+".id", pv); DataSet set = biz.querys(builder.build(), configs); values.put(datasource.getCode(), set); } }
现在所有的结果集已经放在了values中,
下一步解析word中的占位符,
主要通过WDocument类来实现提取占位符,操作表格等
更复杂的情况参考【aol标签】
//模板文件 WDocument doc = new WDocument(tmp_file); doc.variable("数据源名称", "查询结果集"); doc.variables(values);//或者放一个map doc.save();doc.save()后就把最终的格式保存到了相关的word文件,下一步把文件提交给minio服务器,再把新的url交给前端预览就可以了 更复杂的场景就需要更复杂的约定规则,但是对于后台来说主要还是用到CofigStore,WDocument两个来类就可以了。