接上篇Rocket-api优化方案(二)---数据源管理
一、设计目标
-
解决目前系统尚未做登陆的安全设置,同时不能新增其他的系统用户。
-
通过对用户权限的管理解决数据源安全管控的问题。
-
支持离线计算分析任务需要的定时任务。
-
需要将开发的项目与开发人员、数据源形成关联。
二、实现步骤
(1)一般来说都会有众多的项目都需要在此平台上进行开发,如果不对这些项目加以管理的话,会导致开发效率降低,而且也并不知道所编写的接口是服务的哪个项目,显的杂乱无章。
(2)在后台管理新增项目管理页面,对所服务的项目可视化展示
页面展示项目名称、路径、开发人员和所分配的数据源
(3)编写添加项目的页面,同时开发人员和数据源需要动态获取,为了方便管理员刚新增好开发人员和数据源就能立马对其进行分配调度,且开发人员与数据源都可多选。
(4) 点击添加,将所填写的信息存库,开发人员和数据源以唯一id存入项目表中,方便与开发人员表和数据源表进行绑定。
如果有多选,id以英文逗号隔开存入表中
(5)页面展示项目列表,需要对开发人员id和数据源id进行逻辑处理,把id和名称对应上,方便阅读。
public List<ApiDirectory> getProjectList(){
// 获取项目列表并过滤
List<ApiDirectory> apiDirectories = dataSourceManager.getStoreApiDataSource().listByEntity(ApiDirectory.builder().service(rocketApiProperties.getServiceName()).build());
List<ApiDirectory> apiDirectoryList = apiDirectories.stream().filter(item -> item.getParentId() == null || item.getParentId().equals("")).collect(Collectors.toList());
// 获取开发人员、数据源列表,形成关联
List<ApiDeveloper> userList = getUserList();
List<ApiConfig> configList = getConfigList();
// 获取到的项目列表循环逻辑处理
for (ApiDirectory apiDirectory : apiDirectoryList) {
String developerId = apiDirectory.getDeveloperId();
String configId = apiDirectory.getConfigId();
// 开发人员关联逻辑处理
if (developerId.contains(",")){
String[] split = developerId.split(",");
List<String> list = new ArrayList<>();
for (String s : split) {
for (ApiDeveloper apiDeveloper : userList) {
if (s.equals(apiDeveloper.getId())){
list.add(apiDeveloper.getName());
}
}
}
developerId = list.toString();
}else {
for (ApiDeveloper apiDeveloper : userList) {
if (developerId.equals(apiDeveloper.getId())){
developerId = apiDeveloper.getName();
}
}
}
// 数据源关联逻辑处理
if (configId.contains(",")){
String[] split = configId.split(",");
List<String> list = new ArrayList<>();
for (String s : split) {
for (ApiConfig apiConfig : configList) {
if (s.equals(apiConfig.getId())){
list.add(apiConfig.getConfigContext().split("@")[1]);
}
}
}
configId = list.toString();
}else {
for (ApiConfig apiConfig : configList) {
if (configId.equals(apiConfig.getId())){
configId = apiConfig.getConfigContext().split("@")[1];
}
}
}
apiDirectory.setDeveloperId(developerId);
apiDirectory.setConfigId(configId);
}
return apiDirectoryList;
}
粘贴部分逻辑处理代码供参考
(6)最后页面展示效果如下,开发人员和数据源多选,就以逗号隔开展示(后期可以优化)
(7)在新增项目时将此项目分配给某个或者几个开发人员,是为了让此开发人员在登录账号时,只能看到给他分配的项目和数据源,其他的未给他分配的项目和数据源不可对其操作。所以在开发人员登录此系统时,需要根据开发人员的唯一id进行判断他所需要开发的有哪些项目,并且这些项目含有哪些数据源。
(8)该系统的在用户登录的同时,会发送一个请求获取项目表中的所有目录,对我们来说需要改进,可以对发送这个请求的用户进行判断他的身份,如果是管理员,则显示所有项目,如果是开发人员,则根据开发人员id获取对应的项目。
/**
* 目录查询
* @return
*/
@GetMapping("/directory/list")
public ApiResult directoryList(HttpServletRequest request){
ApiDeveloper user = (ApiDeveloper) request.getSession().getAttribute("user");
String id = user.getId();
Integer grade = user.getGrade();
List<ApiDirectory> apiDirectoryList = apiInfoService.loadDirectoryList().stream()
.sorted(Comparator.comparing(ApiDirectory::getName).thenComparing(ApiDirectory::getPath))
.collect(Collectors.toList());
if (grade == 2){
// 运用迭代器删除与当前用户无关的项目
Iterator<ApiDirectory> iterator = apiDirectoryList.iterator();
while (iterator.hasNext()){
ApiDirectory apiDirectory = iterator.next();
if (apiDirectory.getParentId() == null || apiDirectory.getParentId() == ""){
if (apiDirectory.getDeveloperId().contains(id)){
}else {
iterator.remove();
}
}
}
}
return ApiResult.success(apiDirectoryList);
}
在这里进行逻辑处理时遇到一个坑,我的思路是先判断发送这个请求的用户身份,如果是管理员则直接返回所有数据,如果是开发人员,先获取所有directory目录数据,通过字段处理判断获得项目数据(项目和目录放在同一张表中,目录属于项目),然后遍历项目数据,删除项目表中不包含当前开发人员id的数据,刚开始通过增强for循环遍历判断删除数据,会抛一个ConcurrentModificationException异常
是因为java语法支持增强for,但到了jvm中会将其反编译成迭代器Iterator进行运行,ArrayList的父类AbstractList中有一个域modCount,每次对集合进行删除或增加时,都是modCount++。而增强for循环的背后实现原理就是迭代器Iterator,迭代ArrayList的Iterator中有一个变量expectedModCount,该变量会初始化和modCount相等,在后面每一次删除或者新增时会判断这两个值是否相等,只有当modCount==expectedModCount时,才会不抛出异常。而使用增强for循环删除元素时,只对了modCount进行modCount++,并没有对expectedModCount进行赋值,导致不相等抛出上面所出现的异常。解决是将增强for循环换成迭代器Iterator,运行迭代器里的remove()方法对集合元素进行删除,就不会出现问题。
(9)这样当开发人员登录账号时就只能看到自己所要开发的项目
以李四为例,给他分配的项目有日志查询和测试两个,他登录账号后,也只显示两个项目。
(10)给项目分配完开发人员,还要给项目分配数据源,这样在开发人员登录账号开发项目时,只能用到给此项目分配的数据源,其他的数据源一律操作不了。
(11)因为我们此系统上加上了一个登录页面,登录成功后才会跳转到接口编辑页面,在跳转的同时,系统从数据源的表中获取到了所有的数据源,并展示在页面上,我们需要通过登录用户的身份来判别,通过从session作用域中获取到当前用户信息。
function loadApiList(isDb,callback) {
//拉取目录信息
$.getJSON(loadApiListUrl+"?isDb="+(isDb?true:false),function (data) {
data = unpackResult(data);
gdata.apiList = data.data;
$.getJSON(directoryListUrl,function (data) {
data = unpackResult(data);
gdata.directoryList = data.data;
buildApiTree(gdata.directoryList,gdata.apiList,"collapsed");
loadCurrApi();
if (callback){
callback();
}
})
})
}
@GetMapping("/smart-api")
public String index(Model model, HttpServletRequest request){
ApiDeveloper apiDeveloper = (ApiDeveloper) request.getSession().getAttribute("user");
if (apiDeveloper.getGrade() == 1){
model.addAttribute("dataSourceList",dataSourceManager.getDialectMap().keySet());
}else {
List<ApiDirectory> apiDirectories = dataSourceManager.getStoreApiDataSource().listByEntity(ApiDirectory.builder().service(rocketApiProperties.getServiceName()).build());
List<ApiDirectory> apiDirectoryList = apiDirectories.stream().filter(item -> item.getParentId() == null || item.getParentId() == "").filter(item -> item.getDeveloperId().contains(apiDeveloper.getId())).collect(Collectors.toList());
List<ApiConfig> apiConfigs = dataSourceManager.getStoreApiDataSource().listByEntity(ApiConfig.builder().service(rocketApiProperties.getServiceName()).build());
Set<String> set = new HashSet<>();
for (ApiDirectory apiDirectory : apiDirectoryList) {
for (ApiConfig apiConfig : apiConfigs) {
JSONObject dataJson = JSON.parseObject(apiConfig.getConfigContext());
if (apiDirectory.getConfigId().contains(dataJson.getString("id"))){
set.add(dataJson.getString("name"));
}
}
}
set.add("mysql");
model.addAttribute("dataSourceList",set);
}
model.addAttribute("service", rocketApiProperties.getServiceName());
model.addAttribute("title", rocketApiProperties.getServiceTitle());
model.addAttribute("configEnabled", rocketApiProperties.isConfigEnabled());
model.addAttribute("version", PackageUtils.getVersion());
if (request.getRequestURI().endsWith("/")){
return "redirect:"+ rocketApiProperties.getBaseRegisterPath();
}
return "rocketapi/api-index";
}
(12)在用户登录后只能看到自己所开发的项目和项目所分配的数据源,这样在有更多的项目和数据源时,会很好的进行区分和管理,同时如果需要将开发人员账号所开发的项目进行迁移到别的开发账户,在管理员的后台管理也是可以做到的。 到此项目管理开发结束,请前往第四章定时任务管理。