在前端点击打印后,需要根据参数值、模板、数据源
实现解析数据源、生成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
下面的示例代码与我们的约定略有不同,示例中默认有一个主表,而按我们的约定不需要区分主次都在数据源下的表中并列(所以15-47行是多余的)
Map<String, DataSet> values = new HashMap<>();
//业务库
AnylineService biz = ServiceProxy.service("biz");
//主表
values.put("master", biz.querys(template.getString("table_name"), "++id:"+pv));
//多数据源
DataSet ds = service.querys("t_ds_datasource", "template_id:"+template.getId());
if(ds.size() > 0) {
	//数据源关联的全部数据
	//所有的关联条件和过渡条件一次查出 避免重复查询数据库
	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类来实现提取占位符,操作表格等
如果没有循环输出的情况直接通过WDocument替换全局占位符即可,
因为这里涉及到样表循环输出所以需要定位需要循环的结构(行)在循环结构的上级(表)中插入行,
插入行时注意复制原表的字体格式,注意一般不是在最后追加,注意合并单元格,注意多层遍历
这里我们只演示简单的一层循环
//模板文件
WDocument doc = new WDocument(tmp_file);
List<WTable> tables = doc.tables();
for(WTable table:tables){
	List<WTr> trs = table.getTrs();
	for(int i=0; i<trs.size(); i++){
		WTr tr = trs.get(i);
		List<WTc> tcs = tr.getTcs();
		int static_qty = 0;
		int placeholder_qty = 0;
		List<String> tr_placeholders = new ArrayList<>(); //当前行的占位
		int template_index = 0;
		for(WTc tc:tcs){
			String txt = tc.getTexts();
			if(BasicUtil.isNotEmpty(txt)){
				List<String> td_placeholders = placeholders(txt);
				if(!td_placeholders.isEmpty()){
					placeholder_qty ++;
					tr_placeholders.addAll(td_placeholders);
				}else{
					static_qty ++;
				}
			}
		}
		if(placeholder_qty > static_qty){
			//多行
			WTr template_tr = tr; //以当前行为模板
			template_index =  trs.indexOf(template_tr);
			String tr_ds = null; //数据源
			for(String placeholder:tr_placeholders){
				if(!placeholder.startsWith("master")){
					tr_ds = placeholder.split(":")[0];
					if(values.containsKey(tr_ds)) {
						break;
					}
				}
			}
			DataSet for_datas = values.get(tr_ds); //数据集
			if(null != for_datas) {
				Map<String,String> keys = new HashMap<>(); //占位符与列之前对应 placeholder <> data.key
				List<String> placeholders = template_tr.placeholders();
				for (String placeholder : placeholders) {
					String[] ks = placeholder.split(":");
					String p_ds = ks[0];
					String p_tab = ks[1];
					String p_col = ks[2];
					String k = p_tab + "__" + p_col;
					if ("master".equalsIgnoreCase(p_ds)) {
						k = p_col;
					}
					keys.put(placeholder, k);
				 }
				//根据数据集添加行
				for (DataRow row : for_datas) {
					Map<String,String> replaces = new HashMap<>();
					for(String placeholder:placeholders){
						String key = keys.get(placeholder);
						String value = row.getString(key);
						if(null != value){
							//检测复选框
							String check = "□";
							String checked= "☑";
							value = value.replace(check, "<br/>"+check);
							value = value.replace(checked, "<br/>"+checked);
							if(value.startsWith("<br/>")){
								value = value.substring(6);
							}
						}
						replaces.put(placeholder, value);
					}
					WTr append_tr = template_tr.clone(true);
					append_tr.replace(replaces);
					table.insert(template_index++, append_tr);
				}
				table.remove(template_tr);//删除模板行
			}
		}
	}
}
List<String> placeholders = doc.placeholders();

//替换主表占位
for(String p:placeholders){
	//master:t_entrust:entrust_unitName
	String[] ks = p.split(":");
	String p_ds = ks[0];
	String p_tab = ks[1];
	String p_col = ks[2];
	DataSet set = values.get(p_ds);
	String v = "";
	String k = p_tab + "__" + p_col;
	if("master".equalsIgnoreCase(p_ds)){
		k = p_col;
	}
	if(null != set && !set.isEmpty()){
		DataRow row = set.getRow(0);
		v = row.getString(k);
	}
	System.out.println("[placeholder]"+p+"/"+k+"="+v);
	doc.replace(p, v);

}
doc.save();
doc.save()后就把最终的格式保存到了相关的word文件,下一步把文件提交给minio服务器,再把新的url交给前端预览就可以了 更复杂的场景就需要更复杂的约定规则,但是对于后台来说主要还是用到CofigStore,WDocument两个来类就可以了。