前言
鉴于自己最近参加一个比赛,整区块链应用,因此部分核心数据需要往区块链网络的数据库中存储。用的区块链框架为FISCO-BCOS。FISCO-BCOS官方提供一一个预编译合约,名叫Table.sol (CRUD合约),能让我们通过CRUD合约像使用一般关系型数据库中的增删改查操作,从而对数据进行增删改查。
但,问题是,这个合约虽然提供了数据库一样的操作,并没有像Java一样的映射框架,就算用Table.sol来操作数据上链,这个代码量也非常大。
所以啊,我就想到之前我研究到的一个项目ry-vue-plus,有个代码生成工具,通过数据库连接->进而生成一般的crud代码、以及vue文件、domain、service、mapper之类的。在此基础上,对合约也设置一个模版,对数据库表 —— 合约的映射。
ry-vue-plus采用的模版引擎为velocity。
以下,只是记录如何整合自定义模版配置。以及设计对模版引擎的介绍。
1.什么是模版引擎?
模板引擎是构建动态内容的重要工具,特别适用于生成HTML、邮件内容、报告和其他文本文档。
简单来讲,能够让我们通过定义模板来生成动态内容。模板通常包含静态内容以及特殊语法或占位符,这些占位符可以在运行时被动态数据替换
2.Velocity 简介
Velocity是一个强大的模板引擎,它具有简单易用的语法和灵活性。
Velocity是一个用于生成文本输出的模板引擎。它是一种轻量级的、开源的工具,最初由Apache开发。Velocity的主要特点包括:
简单的语法: Velocity使用简单的模板语法,易于学习和使用。
- 灵活性: Velocity允许在模板中嵌套、迭代和使用条件语句,以便生成复杂的输出。
- 模板重用: 模板可以被多次重用,从而减少了重复代码。
- 可扩展性: Velocity可以与Java代码集成,允许在模板中调用Java方法。
- 广泛支持: Velocity可以用于生成HTML、XML、JSON等多种文本格式。
开发
吆西,知道他是个啥东西了,直接在ry-vue-plus原有的配置上进行代码开发。
找到代码生成核心
废话少说,他的代码生成在GenTableServiceImpl.java类中。核心代码在这里。
......
/**
* 查询表信息并生成代码
*/
private void generatorCode(Long tableId, ZipOutputStream zip) {
// 查询表信息
GenTable table = baseMapper.selectGenTableById(tableId);
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
menuIds.add(identifierGenerator.nextId(null).longValue());
}
table.setMenuIds(menuIds);
// 设置主键列信息
setPkColumn(table);
VelocityInitializer.initVelocity();
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
for (String template : templates) {
// 渲染模板
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, Constants.UTF8);
tpl.merge(context, sw);
// TODO 将模版的内容替换成实际渲染的内容
try {
// 添加到zip
zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString());
IoUtil.close(sw);
zip.flush();
zip.closeEntry();
} catch (IOException e) {
log.error("渲染模板失败,表名:" + table.getTableName(), e);
}
}
}
......
第二个框中定义代码,将变量和那个模版渲染成那个实际的文件。
我们在模版文件中占位符使用的变量,就是在VelocityContext context = VelocityUtils.prepareContext(table);这行代码得出来的。
我们调试context变量里面的数据,发现就是这些字段吧,可以在模版文件中通过占位符的形式访问。
现在,我们知道这个模版中的占位符的数据是如何来的,然就是把我们自定义的模版添加到他的代码里面。
添加我们自己的模版
如下图位置。
在VelocityUtils.java 文件中,修改如下代码。定义如下,我们获取模版列表才能获取到。
在这个VelocityUtils.java的getFileName方法内。定义文件打包的路径。
定制我们自己的模版
仿写
在GenTableServiceImpl.java的generatorCode方法内。
我们在上述知道context存储定义模版要使用的变量,调试到这里。以及参考项目已经定义好的模版使用,写在我们自己的模版中。
使用自定义方法
嗯,怎么讲,它里面有些样式可以用他的语法做出来,但是嫌麻烦,因此直接在模版文件调用java自定义方法,来处理数据得出我们想要的。
还是在GenTableServiceImpl.java 文件的generatorCode方法内,如下位置,可以添加我们自定义类。从而在模版文件就可以调用这个类中的方法了。
对应的静态类方法:
模版文件中的使用:
storage.sol.vm
pragma solidity ^0.4.25;
pragma experimental ABIEncoderV2;
import "./TableTools.sol";
#set($primayKey = $velocityUtil.getPrimaryKey($columns))
/// @title ${functionName}Storage
/// @author ${author}
/// @notice 操作${functionName}
contract ${ClassName}Storage is TableTools{
string constant ${ClassName}_Table = "${businessName}_table";
string constant ${ClassName}_Fields = "$velocityUtil.generateFiledList($columns)";
Bean tb_${businessName};
event InsertResult(int8);
constructor() public {
// 初始化表结构
initTableStruct(tb_${businessName}, ${ClassName}_Table,NOMINAL_PRIMARYKEY,${ClassName}_Fields);
}
/*
* 查询全部${functionName}列表
*
* @return ${functionName}列表
*/
function queryList() public view returns(string memory) {
int _code;
string memory _res;
string[] memory _strs = new string[](0);
(_code,_res) = selectOneRecordByCondToJson(tb_${businessName}, COMMON_VALUE, _strs);
require(_code == 1,"query failed!");
return _res;
}
/*
* 根据主键查询${functionName}
*
* @return ${functionName}列表
*/
function queryById(string memory _id) public view returns (string memory) {
int8 _code;
string memory _res;
string[] memory _conditionPair = new string[](2);
_conditionPair[0] = "${primayKey}";
_conditionPair[1] = _id;
(_code,_res) = selectOneRecordByCondToJson(tb_${businessName}, COMMON_VALUE,_conditionPair); // : 自定义字段查询
require(_code == 1,"query failed!");
return _res;
}
/*
* 根据查询条件、分页查询${functionName}列表
*
* @param _conditionPair 查询条件
* @param _pageNum 起始页数
* @param _pageSize 起始页数
* @return ${functionName}列表
*/
function queryList(string[] _conditionPair,int _pageNum,int _pageSize) public view returns(string memory) {
string memory _res;int8 _code;
Page memory _page = Page(_pageNum, _pageSize);
(_code,_res) = selectOneRecordByCondToJson(tb_${businessName}, COMMON_VALUE,_conditionPair,_page);
require(_code == 1,"query failed!");
return _res;
}
/*
* 新增${functionName}
*
* @param fieldsStr 字段列表字符串
* @return 新增结果
*/
function insert(string fieldsStr) public returns(bool) {
string[] memory fields = getFieldsArray(fieldsStr);
require(fields.length == $velocityUtil.getColumnsLength($columns),"The list length is wrong!");
require(!_isIDExist(fields[0]),"insert info primaryKey exist!");
int8 _code = insertOneRecord(tb_${businessName},COMMON_VALUE,fieldsStr, true);
emit InsertResult(_code);
bool end = _code == 1;
require(end,"insert failed!");
return end;
}
/*
* 根据主键值更新${functionName}信息
*
* @param fieldsStr 字段列表字符串
* @return 新增结果
*/
function updateById(string memory _fieldsStr) public returns(bool) {
string[] memory fields = getFieldsArray(_fieldsStr);
string[] memory _conditionPair = new string[](2);
_conditionPair[0] = "${primayKey}";
_conditionPair[1] = fields[0];
return update(_fieldsStr,_conditionPair);
}
/*
* 根据条件更新${functionName}信息
*
* @param _fieldsStr 新字段列表字符串
* @param _conditionPair 条件
* @return 修改结果
*/
function update(string memory _fieldsStr,string[] memory _conditionPair) public returns(bool) {
int8 _code = updateOneRecordByCond(tb_${businessName}, COMMON_VALUE,_fieldsStr,_conditionPair);
bool end = _code == 1;
require(end,"update failed!");
return end;
}
/*
* 根据主键值列表批量删除${functionName}信息
*
* @param _id 主键值
* @param _id 主键值
* @return 删除结果
*/
function deleteWithValidByIds(string[] memory _ids,bool isValid) public returns(bool) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
int8 _code;
for (uint j = 0; j < _ids.length; j++) {
require(_isIDExist(_ids[j]),"remove failed!");
}
for (uint i = 0; i < _ids.length; i++) {
string[] memory _conditionPair = new string[](2);
_conditionPair[0] = "${primayKey}";
_conditionPair[1] = _ids[i];
_code = remove(COMMON_VALUE,Test_Table,_conditionPair);
}
return _code == 1;
}
function deleteByIds(string[] memory _ids) public returns(bool) {
return deleteWithValidByIds(_ids,false);
}
/*
* 根据主键值判断信息是否存在
*
* @param _id 主键值
* @return 是否存在
*/
function _isIDExist(string memory _id) internal view returns(bool) {
string[] memory _conditionPair = new string[](2);
_conditionPair[0] = "${primayKey}";
_conditionPair[1] = _id;
(int _code,) = selectOneRecordByCondToJson(tb_${businessName}, COMMON_VALUE,_conditionPair);
return _code == 1;
}
}
测试
导入数据表,点击生成之后。
打开解压好的文件,成功。