产口孵化新项目时,表中需要提前预置部份数据。如字典,角色、菜单、配置等。以达到快速布署的目的。这部份数据会随着某些因素的影响。如地区变化、系统定制方面。无法使用统一的一份sql。旧方案是在代码里面动态的作insert操作。在项目启动的时候初始化这部份数据。但玩着玩着失控了。主要是看不见、摸不着,不晓得往库里加了些什么数据。烽烟四起,四处报警。在此基础上进行了改良。使用了sql脚本+占位符的模式。并引入了初始化脚本和增量脚本的功能。将脚本执行情况记录到一张表中,防止重复执行。
代码结构:
代码如下:
@Component
@Slf4j
@ConditionalOnProperty(
prefix = GlobalConstant.CONFIG_PREFIX + "dependence", name = "enable-squirrel-execute", havingValue = "true")
public class SquirrelExecutor implements EasyApplicationRunner, SquirrelConstant {
@Autowired
private DataSource dataSource;
@Autowired
private AreaMapper areaMapper;
@Autowired
private DeployMapper deployMapper;
@Autowired
private SquirrelContextBuilder squirrelContextBuilder;
@Autowired
private MinioTemplate minioTemplate;
@SneakyThrows
@Override
public void doBusiness() {
SquirrelContext context = squirrelContextBuilder.buildSquirrelContext();
if (context.isStop()) {
return;
}
if (context.hasGlobalInit()) {
// 执行初始化操作
doAddScript(context);
} else {
// 做增量操作
doInitScript(context);
}
// 记录执行次数
recordExecuteTimes(context.getExecuteTimes());
}
public void doAddScript(SquirrelContext context) {
// 增量脚本
List<String> addFileNames = new ArrayList<>();
Map<String, String> addScripts = context.getAddScripts();
addScripts.forEach((fileName, script) -> {
if (!context.hasExecute(fileName)) {
executeScript(script, context);
addFileNames.add(fileName);
}
});
if (ZYListUtils.isNotEmptyList(addFileNames)) {
List<Deploy> addSigns = ZYListUtils.list2list(addFileNames, Deploy::new);
deployMapper.insertBatch(addSigns);
}
}
public void doInitScript(SquirrelContext context) {
// 从地区库中挑选出需要的地区保存地区
List<Area> areas = context.getInitAreas();
areaMapper.insertSplitBatch(areas, 200);
List<String> initScripts = context.getInitScripts();
for (String initScript : initScripts) {
executeScript(initScript, context);
}
// 标记已初始化
List<Deploy> signs = new ArrayList<>();
Deploy signInitDeploy = new Deploy(INIT_KEY);
signs.add(signInitDeploy);
// 标记当前增量脚本已执行
Map<String, String> addScripts = context.getAddScripts();
addScripts.forEach((fileName, script) -> {
Deploy signAddDeploy = new Deploy(fileName);
signs.add(signAddDeploy);
});
// 导入附件
// initAttachment();
deployMapper.insertBatch(signs);
}
private void executeScript(String script, SquirrelContext context) {
Reader reader = ScriptBuilder.of(script, context).buildExecuteReader();
if (null == reader) {
return;
}
try (Connection connection = dataSource.getConnection()) {
// 建表
ScriptRunner sr = new ScriptRunner(connection);
sr.setAutoCommit(true);
sr.setStopOnError(false);
sr.setSendFullScript(true);
sr.runScript(reader);
} catch (Exception e) {
e.printStackTrace();
log.warn("初始化系统表失败" + script);
}
}
private void recordExecuteTimes(int hasExecuteTimes) {
Deploy signTimes = new Deploy();
signTimes.setConfigKey(InitParam.EXECUTE_TIMES);
signTimes.setConfigValue(String.valueOf(hasExecuteTimes + 1));
if (hasExecuteTimes > 0) {
deployMapper.updateById(signTimes);
} else {
deployMapper.insert(signTimes);
}
}
public void initAttachment() {
SquirrelAttachment squirrelAttachment = new SquirrelAttachment();
squirrelAttachment.findAttachment();
if (squirrelAttachment.isEmpty()) {
return;
}
squirrelAttachment.forEach((fileName, fileInput) -> {
try (InputStream is = fileInput) {
FileWrapper fileWrapper = new FileWrapper();
fileWrapper.setNewFileName(fileName);
fileWrapper.setExtName(ZYFileUtils.extName(fileName));
minioTemplate.uploadInputStream(fileWrapper, is, MediaType.APPLICATION_OCTET_STREAM.toString());
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
public class SquirrelContextBuilder {
@Autowired
private AreaRepositoryMapper areaRepositoryMapper;
@Autowired
private DeployMapper deployMapper;
@Autowired
private DependenceProperties dependenceProperties;
public SquirrelContext buildSquirrelContext() {
SquirrelContext context = new SquirrelContext();
// 布署参数配置
List<Deploy> deploys = deployMapper.selectList(Wrappers.query());
InitParam initParam = new InitParam(deploys);
context.setInitParam(initParam);
Integer squirrelMaxExecuteTimes = dependenceProperties.getSquirrelMaxExecuteTimes();
int hasExecuteTimes = initParam.hasExecuteTimes();
// 为保证系统稳定,达到一次脚本初始化次数后将不会再执行。
if (hasExecuteTimes > squirrelMaxExecuteTimes) {
log.warn("已达脚本最大初始化次数");
context.setStop(true);
return context;
}
String rootAreaId = initParam.getRootAreaId();
if (ZYStrUtils.isNull(rootAreaId)) {
throw new LocalException("初始化地区根id不能为空");
}
AreaRepository rootArea = areaRepositoryMapper.get(rootAreaId);
if (null == rootArea) {
throw new LocalException("地区表尚未初始化");
}
// 地区层级
int areaLevel = initParam.getAreaLevel();
if (areaLevel == 0) {
throw new LocalException("地区层级未配置");
}
// 查询子级地区
List<AreaRepository> subAreas = findSubAreasByLevel(rootAreaId, rootArea, areaLevel);
List<AreaRepository> areaContainer = findSubAreasByLevel(rootAreaId, rootArea, 6);
Map<String, List<AreaRepository>> parentAreaContainer = new HashMap<>();
for (AreaRepository parentArea : areaContainer) {
String parentAreaId = parentArea.getId();
if (ZYTreeUtils.isRootTree(parentAreaId)) {
continue;
}
for (AreaRepository subArea : areaContainer) {
String subAreaParentIdId = subArea.getParentId();
if (subAreaParentIdId.equals(parentAreaId)) {
ZYMapUtils.flushMapList(parentAreaId, parentAreaContainer, subArea);
}
}
}
context.setScriptWrapper(new ScriptWrapper());
context.setParentAreaContainer(parentAreaContainer);
context.setRootArea(rootArea);
context.setSubArea(subAreas);
return context;
}
public List<AreaRepository> findSubAreasByLevel(String rootAreaId, AreaRepository rootArea, int areaLevel) {
List<AreaRepository> subAreas = new ArrayList<>();
subAreas.add(rootArea);
List<String> parentIds = Collections.singletonList(rootAreaId);
for (int i = 1; i < areaLevel; i++) {
List<AreaRepository> subAreaItems = findAreaByParentIds(parentIds);
if (ZYListUtils.isEmptyList(subAreaItems)) {
break;
}
parentIds = ZYListUtils.collectField(subAreaItems, AreaRepository::getId);
subAreas.addAll(subAreaItems);
}
return subAreas;
}
private List<AreaRepository> findAreaByParentIds(List<String> parentIds) {
if (ZYListUtils.isEmptyList(parentIds)) {
return Collections.emptyList();
}
LambdaQueryWrapper<AreaRepository> wrapper = Wrappers.lambdaQuery();
wrapper.in(AreaRepository::getParentId, parentIds);
return areaRepositoryMapper.selectList(wrapper);
}
}
public class ScriptWrapper implements SquirrelConstant {
List<String> initScripts = new ArrayList<>();
Map<String, String> addScripts = new HashMap<>();
public ScriptWrapper() {
Resource[] resources = null;
try {
resources = new PathMatchingResourcePatternResolver().getResources("xxx");
} catch (IOException e) {
log.warn("脚本文件读取失败");
}
if (null == resources) {
return;
}
for (Resource resource : resources) {
String filename = resource.getFilename();
if (null == filename) {
continue;
}
// 项目类型A的脚本
if (filename.startsWith(a)) {
if (!b) {
continue;
}
}
// 项目类型B的脚本
if (filename.startsWith(b)) {
if (!b) {
continue;
}
}
try (InputStream inputStream = resource.getInputStream()) {
String sqlScript = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
if (SquirrelConstant.isInitScript(filename)) {
initScripts.add(sqlScript);
} else if (SquirrelConstant.isAddScript(filename)) {
addScripts.put(filename, sqlScript);
}
} catch (IOException e) {
log.warn("脚本文件读取失败");
}
}
}
}
squirrel是松鼠的意思是,该组件寓意把初始化脚本初始化多多弄好。项目多时就有粮可吃。不用加班。好像解释有点牵强。那就是乱起的。