实现解析数据源、生成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.selects("t_ds_datasource", "template_id:"+template.getId());
if(!ds.isEmpty()) {
//数据源关联的全部数据
//所有的关联条件和过渡条件一次查出 避免重复查询数据库
DataSet all_tables = service.selects("t_ds_datasource_table", new DefaultConfigStore().in("DATASOURCE_ID", ds.getStrings("ID")));
DataSet all_relations = service.selects("t_ds_datasource_table_relation", new DefaultConfigStore().in("DATASOURCE_ID", ds.getStrings("ID")));
DataSet all_filters = service.selects("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.selects(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两个来类就可以了。