增加系统环境变量功能

This commit is contained in:
wanggeng 2022-05-16 15:36:24 +08:00
parent 36ca8ca506
commit 7402291bf7
13 changed files with 865 additions and 1 deletions

View File

@ -0,0 +1,51 @@
package ink.wgink.common.controller.api.env;
import ink.wgink.annotation.CheckRequestBodyAnnotation;
import ink.wgink.common.base.DefaultBaseController;
import ink.wgink.common.pojo.dtos.env.EnvDTO;
import ink.wgink.common.pojo.vos.env.EnvListVO;
import ink.wgink.common.service.env.IEnvService;
import ink.wgink.interfaces.consts.ISystemConstant;
import ink.wgink.pojo.result.ErrorResult;
import ink.wgink.pojo.result.SuccessResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @ClassName: EnvController
* @Description: 环境变量
* @Author: wanggeng
* @Date: 2022/5/16 10:50
* @Version: 1.0
*/
@Api(tags = ISystemConstant.API_TAGS_SYSTEM_PREFIX + "环境变量")
@RestController
@RequestMapping(ISystemConstant.API_PREFIX + "/env")
public class EnvController extends DefaultBaseController {
@Autowired
private IEnvService envService;
@ApiOperation(value = "更新", notes = "更新")
@ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = ErrorResult.class)})
@PutMapping("update")
@CheckRequestBodyAnnotation
public SuccessResult update(@RequestBody EnvListVO envListVO) {
envService.update(envListVO);
return new SuccessResult();
}
@ApiOperation(value = "获取配置", notes = "获取配置接口")
@ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = ErrorResult.class)})
@GetMapping("list")
public List<EnvDTO> list() {
return envService.list();
}
}

View File

@ -0,0 +1,29 @@
package ink.wgink.common.controller.route.env;
import ink.wgink.common.base.DefaultBaseController;
import ink.wgink.interfaces.consts.ISystemConstant;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
/**
* @ClassName: EnvController
* @Description: 环境变量
* @Author: wanggeng
* @Date: 2022/5/16 10:50
* @Version: 1.0
*/
@Api(tags = ISystemConstant.ROUTE_TAGS_PREFIX + "环境变量")
@RestController
@RequestMapping(ISystemConstant.ROUTE_PREFIX + "/env")
public class EnvRouteController extends DefaultBaseController {
@GetMapping("update")
public ModelAndView update() {
ModelAndView modelAndView = new ModelAndView("env/update");
return modelAndView;
}
}

View File

@ -0,0 +1,64 @@
package ink.wgink.common.dao.env;
import ink.wgink.common.pojo.dtos.env.EnvDTO;
import ink.wgink.exceptions.RemoveException;
import ink.wgink.exceptions.SaveException;
import ink.wgink.exceptions.SearchException;
import ink.wgink.exceptions.UpdateException;
import ink.wgink.interfaces.init.IInitBaseTable;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* @ClassName: IEnvDao
* @Description: 环境变量
* @Author: wanggeng
* @Date: 2022/5/16 10:24
* @Version: 1.0
*/
@Repository
public interface IEnvDao extends IInitBaseTable {
/**
* 新增
*
* @param params
* @throws SaveException
*/
void save(Map<String, Object> params) throws SaveException;
/**
* 修改
*
* @param params
* @throws UpdateException
*/
void update(Map<String, Object> params) throws UpdateException;
/**
* 删除
*
* @throws RemoveException
*/
void delete() throws RemoveException;
/**
* 列表
*
* @return
* @throws SearchException
*/
List<EnvDTO> list() throws SearchException;
/**
* 详情
*
* @param envKey
* @return
* @throws SearchException
*/
EnvDTO get(String envKey) throws SearchException;
}

View File

@ -0,0 +1,81 @@
package ink.wgink.common.manager.env;
import ink.wgink.common.dao.env.IEnvDao;
import ink.wgink.common.pojo.Env;
import ink.wgink.common.pojo.dtos.env.EnvDTO;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @ClassName: EnvManager
* @Description: 环境变量管理
* @Author: wanggeng
* @Date: 2022/5/16 10:47
* @Version: 1.0
*/
public class EnvManager {
private static final Logger LOG = LoggerFactory.getLogger(EnvManager.class);
private static EnvManager envManager = EnvManagerBuilder.envManager;
private static Pattern ENV_VARIABLE = Pattern.compile("%[a-zA-Z\\d\\_\\-]+%");
private IEnvDao envDao;
private Env env = new Env();
public static EnvManager getInstance() {
return envManager;
}
/**
* 刷新环境变量
*/
public void refreshEnv() {
env.clear();
List<EnvDTO> envDTOS = envDao.list();
envDTOS.forEach(envDTO -> {
env.put(envDTO.getEnvKey(), envDTO.getEnvValue());
});
}
/**
* 获得环境变量
*
* @return
*/
public Env getEnv() {
return env;
}
public boolean isKeyExist(String key) {
return env.containsKey(key);
}
public String getValue(String key) {
String value = env.get(key);
value = value == null ? "" : value;
// 替换变量
Matcher matcher = ENV_VARIABLE.matcher(value);
while (matcher.find()) {
String variable = matcher.group();
String variableValue = env.get(variable.substring(1, variable.length() - 1));
if (StringUtils.isBlank(variableValue)) {
continue;
}
value = value.replaceFirst(variable, variableValue);
}
return value;
}
public void setEnvDao(IEnvDao envDao) {
this.envDao = envDao;
}
private static class EnvManagerBuilder {
public static final EnvManager envManager = new EnvManager();
}
}

View File

@ -0,0 +1,83 @@
package ink.wgink.common.pojo;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName: Env
* @Description: 环境变量
* @Author: wanggeng
* @Date: 2022/5/16 14:46
* @Version: 1.0
*/
public class Env implements Map<String, String> {
Map<String, String> envMap = new ConcurrentHashMap<>();
@Override
public int size() {
return envMap.size();
}
@Override
public boolean isEmpty() {
return envMap.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return envMap.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return envMap.containsValue(value);
}
@Override
public String get(Object key) {
return envMap.get(key);
}
@Override
public String put(String key, String value) {
return envMap.put(key, value);
}
@Override
public String remove(Object key) {
return envMap.remove(key);
}
@Override
public void putAll(Map<? extends String, ? extends String> m) {
envMap.putAll(m);
}
@Override
public void clear() {
envMap.clear();
}
@Override
public Set<String> keySet() {
return envMap.keySet();
}
@Override
public Collection<String> values() {
return envMap.values();
}
@Override
public Set<Entry<String, String>> entrySet() {
return envMap.entrySet();
}
@Override
public String toString() {
return envMap.toString();
}
}

View File

@ -0,0 +1,46 @@
package ink.wgink.common.pojo.dtos.env;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
/**
* @ClassName: EnvDTO
* @Description: 环境变量
* @Author: wanggeng
* @Date: 2022/5/16 10:36
* @Version: 1.0
*/
public class EnvDTO implements Serializable {
@ApiModelProperty(name = "envKey", value = "环境变量")
private String envKey;
@ApiModelProperty(name = "envExplain", value = "环境变量说明")
private String envExplain;
@ApiModelProperty(name = "envValue", value = "环境变量值")
private String envValue;
public String getEnvKey() {
return envKey == null ? "" : envKey.trim();
}
public void setEnvKey(String envKey) {
this.envKey = envKey;
}
public String getEnvExplain() {
return envExplain == null ? "" : envExplain.trim();
}
public void setEnvExplain(String envExplain) {
this.envExplain = envExplain;
}
public String getEnvValue() {
return envValue == null ? "" : envValue.trim();
}
public void setEnvValue(String envValue) {
this.envValue = envValue;
}
}

View File

@ -0,0 +1,40 @@
package ink.wgink.common.pojo.vos.env;
import ink.wgink.annotation.CheckListBeanAnnotation;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: EnvPO
* @Description: 环境变量
* @Author: wanggeng
* @Date: 2022/5/16 10:36
* @Version: 1.0
*/
@ApiModel
public class EnvListVO {
@ApiModelProperty(name = "envs", value = "环境变量列表")
@CheckListBeanAnnotation
private List<EnvVO> envs;
public List<EnvVO> getEnvs() {
return envs == null ? new ArrayList() : envs;
}
public void setEnvs(List<EnvVO> envs) {
this.envs = envs;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"envs\":")
.append(envs);
sb.append('}');
return sb.toString();
}
}

View File

@ -0,0 +1,59 @@
package ink.wgink.common.pojo.vos.env;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* @ClassName: EnvPO
* @Description: 环境变量
* @Author: wanggeng
* @Date: 2022/5/16 10:36
* @Version: 1.0
*/
@ApiModel
public class EnvVO {
@ApiModelProperty(name = "envKey", value = "环境变量")
private String envKey;
@ApiModelProperty(name = "envExplain", value = "环境变量说明")
private String envExplain;
@ApiModelProperty(name = "envValue", value = "环境变量值")
private String envValue;
public String getEnvKey() {
return envKey == null ? "" : envKey.trim();
}
public void setEnvKey(String envKey) {
this.envKey = envKey;
}
public String getEnvExplain() {
return envExplain == null ? "" : envExplain.trim();
}
public void setEnvExplain(String envExplain) {
this.envExplain = envExplain;
}
public String getEnvValue() {
return envValue == null ? "" : envValue.trim();
}
public void setEnvValue(String envValue) {
this.envValue = envValue;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"envKey\":\"")
.append(envKey).append('\"');
sb.append(",\"envExplain\":\"")
.append(envExplain).append('\"');
sb.append(",\"envValue\":\"")
.append(envValue).append('\"');
sb.append('}');
return sb.toString();
}
}

View File

@ -0,0 +1,31 @@
package ink.wgink.common.service.env;
import ink.wgink.common.pojo.dtos.env.EnvDTO;
import ink.wgink.common.pojo.vos.env.EnvListVO;
import java.util.List;
/**
* @ClassName: IEnvService
* @Description: 环境变量
* @Author: wanggeng
* @Date: 2022/5/16 10:48
* @Version: 1.0
*/
public interface IEnvService {
/**
* 更新
*
* @param envMap
*/
void update(EnvListVO envListVO);
/**
* 详情
*
* @return
*/
List<EnvDTO> list();
}

View File

@ -0,0 +1,52 @@
package ink.wgink.common.service.env.impl;
import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.common.dao.env.IEnvDao;
import ink.wgink.common.manager.env.EnvManager;
import ink.wgink.common.pojo.Env;
import ink.wgink.common.pojo.dtos.env.EnvDTO;
import ink.wgink.common.pojo.vos.env.EnvListVO;
import ink.wgink.common.pojo.vos.env.EnvVO;
import ink.wgink.common.service.env.IEnvService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
* @ClassName: EnvServiceImpl
* @Description: 环境变量
* @Author: wanggeng
* @Date: 2022/5/16 10:48
* @Version: 1.0
*/
@Service
public class EnvServiceImpl extends DefaultBaseService implements IEnvService {
@Autowired
private IEnvDao envDao;
@Override
public void update(EnvListVO envListVO) {
// 清空
envDao.delete();
// 新增
Map<String, Object> params = getHashMap(5);
for (EnvVO envVO : envListVO.getEnvs()) {
params.put("envKey", envVO.getEnvKey());
params.put("envExplain", envVO.getEnvExplain());
params.put("envValue", envVO.getEnvValue());
envDao.save(params);
}
// 刷新缓存
EnvManager.getInstance().refreshEnv();
}
@Override
public List<EnvDTO> list() {
Env env = EnvManager.getInstance().getEnv();
System.out.println(env.toString());
return envDao.list();
}
}

View File

@ -1,5 +1,7 @@
package ink.wgink.common.startup;
import ink.wgink.common.dao.env.IEnvDao;
import ink.wgink.common.manager.env.EnvManager;
import ink.wgink.interfaces.init.IInitBaseMenu;
import ink.wgink.interfaces.init.IInitBaseTable;
import ink.wgink.interfaces.init.IInitBaseUser;
@ -29,9 +31,11 @@ public class CommonStartup implements ApplicationRunner {
private IInitBaseMenu initBaseMenu;
@Autowired(required = false)
private IInitBaseUser initBaseUser;
private IEnvDao envDao;
public CommonStartup(ApplicationContext applicationContext) {
public CommonStartup(ApplicationContext applicationContext, IEnvDao envDao) {
this.applicationContext = applicationContext;
this.envDao = envDao;
}
@Override
@ -49,5 +53,10 @@ public class CommonStartup implements ApplicationRunner {
if (initBaseUser != null) {
initBaseUser.init();
}
// 初始化环境变量
EnvManager envManager = EnvManager.getInstance();
envManager.setEnvDao(envDao);
envManager.refreshEnv();
}
}

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="ink.wgink.common.dao.env.IEnvDao">
<cache flushInterval="3600000"/>
<resultMap id="envDTO" type="ink.wgink.common.pojo.dtos.env.EnvDTO">
<result column="env_key" property="envKey"/>
<result column="env_explain" property="envExplain"/>
<result column="env_value" property="envValue"/>
</resultMap>
<!-- 建表 -->
<update id="createTable">
CREATE TABLE IF NOT EXISTS `sys_env` (
`env_key` varchar(255) NOT NULL COMMENT '环境变量Key',
`env_explain` varchar(255) DEFAULT NULL COMMENT '环境变量说明',
`env_value` longtext COMMENT '环境变量值',
PRIMARY KEY (`env_key`),
UNIQUE KEY `env_key` (`env_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统环境变量';
</update>
<!-- 新增环境变量 -->
<insert id="save" parameterType="map" flushCache="true">
INSERT INTO sys_env(
env_key,
env_explain,
env_value
) VALUES(
#{envKey},
#{envExplain},
#{envValue}
)
</insert>
<!-- 删除环境变量 -->
<delete id="delete" parameterType="map" flushCache="true">
DELETE FROM sys_env
</delete>
<!-- 更新环境变量 -->
<update id="update" parameterType="map" flushCache="true">
UPDATE
sys_env
SET
env_value = #{envValue},
env_explain = #{envExplain}
WHERE
env_key = #{envKey}
</update>
<!-- 环境变量列表 -->
<select id="list" resultMap="envDTO" useCache="true">
SELECT
env_key,
env_explain,
env_value
FROM
sys_env
</select>
<!-- 获取环境变量 -->
<select id="get" parameterType="String" resultMap="envDTO" useCache="true">
SELECT
env_key,
env_explain,
env_value
FROM
sys_env
WHERE
env_key = #{_parameter}
</select>
</mapper>

View File

@ -0,0 +1,245 @@
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<base th:href="${#request.getContextPath() + '/'}">
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="assets/fonts/font-awesome/css/font-awesome.css"/>
<link rel="stylesheet" href="assets/layuiadmin/layui/css/layui.css" media="all">
<link rel="stylesheet" href="assets/layuiadmin/style/admin.css" media="all">
</head>
<body>
<div class="layui-fluid layui-anim layui-anim-fadein">
<form class="layui-form" lay-filter="dataForm">
<div class="layui-card">
<div class="layui-card-header">
<span class="layui-breadcrumb" lay-filter="breadcrumb" style="visibility: visible;">
<a href="javascript:void(0);"><cite>环境变量(参数名唯一且只能是字母、数字、下划线与英文横线的组合。可以通过 %变量名% 的方式相互引用。)</cite></a>
</span>
<button id="envPlusBtn" type="button" class="layui-btn layui-btn-xs" style="float: right; margin-top: 12px;">
<i class="fa fa-plus" aria-hidden="true"></i>
</button>
</div>
<div class="layui-card-body" style="padding: 15px;">
<div class="layui-form-item">
<table class="layui-table">
<colgroup>
<col width="250">
<col>
<col width="250">
<col width="60">
</colgroup>
<thead>
<tr>
<th>变量名</th>
<th>变量值</th>
<th>描述</th>
<th style="text-align: center;">操作</th>
</tr>
</thead>
<tbody id="envBody"></tbody>
</table>
</div>
</div>
</div>
<div class="layui-form-item layui-layout-admin">
<div class="layui-input-block">
<div class="layui-footer" style="left: 0;">
<button type="button" class="layui-btn" lay-submit lay-filter="submitForm">提交更新</button>
</div>
</div>
</div>
</form>
</div>
<script src="assets/layuiadmin/layui/layui.js"></script>
<script>
layui.config({
base: 'assets/layuiadmin/' //静态资源所在路径
}).extend({
index: 'lib/index' //主入口模块
}).use(['index', 'form', 'laydate', 'laytpl'], function(){
var $ = layui.$;
var form = layui.form;
var envs;
// 系统参数配置
function EnvsInit(idPrefix, classPrefix, envList) {
var envsArray = [];
var keyupSetTimeout;
function getTr(index, key, value, explain) {
return '<tr>' +
' <td>' +
' <input type="text" id="'+ idPrefix +'Key'+ index +'" placeholder="输入参数名" class="layui-input '+ classPrefix +'-key" value="'+ (key ? key : '') +'" data-index="'+ index +'">' +
' </td>' +
' <td>' +
' <input type="text" id="'+ idPrefix +'Value'+ index +'" placeholder="输入参数值" class="layui-input '+ classPrefix +'-value" value="'+ (value ? value : '') +'" data-index="'+ index +'">' +
' </td>' +
' <td>' +
' <input type="text" id="'+ idPrefix +'Explain'+ index +'" placeholder="输入描述" class="layui-input '+ classPrefix +'-explain" value="'+ (explain ? explain : '') +'" data-index="'+ index +'">' +
' </td>' +
' <td style="text-align: center;">' +
' <button type="button" class="layui-btn layui-btn-xs layui-btn-danger '+ classPrefix +'-remove-btn" data-index="'+ index +'">' +
' <i class="fa fa-times" aria-hidden="true"></i>' +
' </button>' +
' </td>' +
'</tr>';
}
function refreshTr() {
var trs = '';
for(var i = 0; i < envsArray.length; i++) {
var item = envsArray[i];
trs += getTr(i, item.envKey, item.envValue, item.envExplain);
}
$('#'+ idPrefix +'Body').empty();
$('#'+ idPrefix +'Body').append(trs);
}
function isKeyExist(index, key) {
if(!key) {
return false;
}
for(var i = 0, item; item = envsArray[i++];) {
if(key == item.envKey && index != (i - 1)) {
return true;
}
}
return false;
}
function isKeyEffective(key) {
if((/^[a-zA-Z0-9\_\-]+$/g.test(key))) {
return true;
}
return false;
}
$(document).on('keyup', '.'+ classPrefix +'-key', function() {
var self = this;
var index = this.dataset.index;
this.value = this.value.replace(/\s/g, '');
var value = this.value;
if(keyupSetTimeout) {
clearTimeout(keyupSetTimeout);
}
keyupSetTimeout = setTimeout(function() {
if(!isKeyEffective(value)) {
top.dialog.msg('参数名只能是字母、数字、下划线与英文横线组合');
self.focus();
self.value = '';
return;
}
if(isKeyExist(index, value)) {
top.dialog.msg('参数名重复');
self.focus();
self.value = '';
return;
} else {
envsArray[index].envKey = value;
}
}, 500);
});
$(document).on('keyup', '.'+ classPrefix +'-value', function() {
var index = this.dataset.index;
this.value = this.value.replace(/\s/g, '');
envsArray[index].envValue = this.value;
});
$(document).on('keyup', '.'+ classPrefix +'-explain', function() {
var index = this.dataset.index;
this.value = this.value.replace(/\s/g, '');
envsArray[index].envExplain = this.value;
});
$(document).on('click', '#'+ idPrefix +'PlusBtn', function() {
envsArray.push({
envKey: '',
envValue: '',
envExplain: '',
});
refreshTr();
})
$(document).on('click', '.'+ classPrefix +'-remove-btn', function() {
var index = this.dataset.index;
envsArray.splice(index, 1);
refreshTr();
})
this.init = function() {
if(!envList) {
return;
}
for(var i = 0, item; item = envList[i++];) {
envsArray.push({
envKey: item.envKey,
envValue: item.envValue,
envExplain: item.envExplain
})
}
refreshTr();
}
this.listEnvs = function() {
return envsArray;
}
};
// 初始化
function initData() {
var loadLayerIndex;
top.restAjax.get(top.restAjax.path('api/env/list', []), {}, null, function(code, data) {
envs = new EnvsInit('env', 'env', data);
envs.init();
}, function(code, data) {
top.dialog.msg(data.msg);
}, function() {
loadLayerIndex = top.dialog.msg(top.dataMessage.loading, {icon: 16, time: 0, shade: 0.3});
}, function() {
top.dialog.close(loadLayerIndex);
});
}
initData();
// 提交表单
form.on('submit(submitForm)', function(formData) {
top.dialog.confirm(top.dataMessage.commit, function(index) {
top.dialog.close(index);
var loadLayerIndex;
top.restAjax.put(top.restAjax.path('api/env/update', []), {
envs: envs.listEnvs()
}, null, function(code, data) {
var layerIndex = top.dialog.msg('更新成功', {
time: 0,
btn: [top.dataMessage.button.yes],
shade: 0.3,
yes: function(index) {
top.dialog.close(index);
window.location.reload();
}
});
}, function(code, data) {
top.dialog.msg(data.msg);
}, function() {
loadLayerIndex = top.dialog.msg(top.dataMessage.committing, {icon: 16, time: 0, shade: 0.3});
}, function() {
top.dialog.close(loadLayerIndex);
});
});
return false;
});
form.verify({
positiveNumber: function(value, item) {
if(value < 0) {
return '必须是正数';
}
}
});
});
</script>
</body>
</html>