浏览代码

支持多用户登录 https://gitee.com/ghi/dbsyncer/issues/I61G0O

AE86 2 年之前
父节点
当前提交
1ee5d37cf7

+ 3 - 2
dbsyncer-biz/src/main/java/org/dbsyncer/biz/UserService.java

@@ -48,12 +48,13 @@ public interface UserService {
     UserInfo getUserInfo(String currentUserName);
 
     /**
-     * 获取登录用户信息VO
+     * 获取登录用户信息VO(管理员可以查看所有用户,普通用户只能查看自己)
      *
      * @param currentUserName 登录用户
+     * @param username        查询的用户
      * @return
      */
-    UserInfoVo getUserInfoVo(String currentUserName);
+    UserInfoVo getUserInfoVo(String currentUserName, String username);
 
     /**
      * 获取所有用户信息VO(系统管理员可以查看所有用户,其他用户只能查看自己)

+ 39 - 14
dbsyncer-biz/src/main/java/org/dbsyncer/biz/impl/UserServiceImpl.java

@@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.util.Assert;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -61,7 +62,7 @@ public class UserServiceImpl implements UserService {
         UserInfo currentUser = userConfig.getUserInfo(params.get(UserService.CURRENT_USER_NAME));
         Assert.isTrue(null == currentUser || UserRoleEnum.isAdmin(currentUser.getRoleCode()), "No permission.");
         // 新用户合法性(用户不能重复)
-        Assert.isNull(userConfig.getUserInfo(username), "用户已存在");
+        Assert.isNull(userConfig.getUserInfo(username), "用户已存在,请换个账号");
         // 注册新用户
         userConfig.getUserInfoList().add(new UserInfo(username, nickname, SHA1Util.b64_sha1(password), UserRoleEnum.USER.getCode()));
 
@@ -90,20 +91,20 @@ public class UserServiceImpl implements UserService {
         // 用户昵称
         updateUser.setNickname(nickname);
         // 修改密码
-        if(StringUtil.isNotBlank(newPwd)){
+        if (StringUtil.isNotBlank(newPwd)) {
             // 修改自己的密码需要验证
-            if(self){
+            if (self) {
                 String oldPwd = params.get("oldPwd");
-                Assert.hasText(oldPwd, "The oldPwd is null.");
-                if(!StringUtil.equals(SHA1Util.b64_sha1(oldPwd), updateUser.getPassword())){
+                Assert.hasText(oldPwd, "旧密码不能为空.");
+                if (!StringUtil.equals(SHA1Util.b64_sha1(oldPwd), updateUser.getPassword())) {
                     logService.log(LogType.SystemLog.ERROR, String.format("[%s]修改密码失败", username));
-                    throw new BizException("修改密码失败");
+                    throw new BizException("修改密码失败.");
                 }
             }
             newPwd = SHA1Util.b64_sha1(newPwd);
             Assert.isTrue(!StringUtil.equals(newPwd, updateUser.getPassword()), "新旧密码不能完全一样.");
             updateUser.setPassword(newPwd);
-            logService.log(LogType.SystemLog.INFO, String.format("[%s]修改账号[%s]密码成功", currentUser.getUsername(), username));
+            logService.log(LogType.SystemLog.INFO, String.format("[%s]修改[%s]账号密码成功", currentUser.getUsername(), username));
         }
 
         return manager.editUserConfig(userConfig);
@@ -119,6 +120,9 @@ public class UserServiceImpl implements UserService {
         UserInfo currentUser = userConfig.getUserInfo(params.get(UserService.CURRENT_USER_NAME));
         Assert.isTrue(UserRoleEnum.isAdmin(currentUser.getRoleCode()), "No permission.");
 
+        // 不能删除自己
+        Assert.isTrue(!StringUtil.equals(currentUser.getUsername(), username), "不能删除自己.");
+
         // 删除用户
         UserInfo deleteUser = userConfig.getUserInfo(username);
         Assert.notNull(deleteUser, "用户已删除.");
@@ -133,13 +137,32 @@ public class UserServiceImpl implements UserService {
     }
 
     @Override
-    public UserInfoVo getUserInfoVo(String currentUserName) {
-        return convertUserInfo2Vo(getUserConfig().getUserInfo(currentUserName));
+    public UserInfoVo getUserInfoVo(String currentUserName, String username) {
+        // 管理员可以查看所有用户,普通用户只能查看自己
+        UserConfig userConfig = getUserConfig();
+        UserInfo currentUser = userConfig.getUserInfo(currentUserName);
+        boolean admin = null != currentUser && UserRoleEnum.isAdmin(currentUser.getRoleCode());
+        boolean self = null != currentUser && StringUtil.equals(currentUser.getUsername(), username);
+        Assert.isTrue(admin || self, "No permission.");
+
+        UserInfo userInfo = getUserConfig().getUserInfo(username);
+        return convertUserInfo2Vo(userInfo);
     }
 
     @Override
     public List<UserInfoVo> getUserInfoAll(String currentUserName) {
-        return getUserConfig().getUserInfoList().stream().map(user -> convertUserInfo2Vo(user)).collect(Collectors.toList());
+        // 系统管理员可以查看所有用户,其他用户只能查看自己
+        UserConfig userConfig = getUserConfig();
+        UserInfo currentUser = userConfig.getUserInfo(currentUserName);
+        boolean admin = null != currentUser && UserRoleEnum.isAdmin(currentUser.getRoleCode());
+        if(admin){
+            return getUserConfig().getUserInfoList().stream().map(user -> convertUserInfo2Vo(user)).collect(Collectors.toList());
+        }
+
+        List<UserInfoVo> list = new ArrayList<>();
+        UserInfo userInfo = userConfig.getUserInfo(currentUserName);
+        list.add(convertUserInfo2Vo(userInfo));
+        return list;
     }
 
     private UserConfig getUserConfig() {
@@ -164,10 +187,12 @@ public class UserServiceImpl implements UserService {
 
     private UserInfoVo convertUserInfo2Vo(UserInfo userInfo) {
         UserInfoVo userInfoVo = new UserInfoVo();
-        BeanUtils.copyProperties(userInfo, userInfoVo);
-        // 避免密码直接暴露
-        userInfoVo.setPassword("***");
-        userInfoVo.setRoleName(UserRoleEnum.getNameByCode(userInfo.getRoleCode()));
+        if (null != userInfo) {
+            BeanUtils.copyProperties(userInfo, userInfoVo);
+            // 避免密码直接暴露
+            userInfoVo.setPassword("***");
+            userInfoVo.setRoleName(UserRoleEnum.getNameByCode(userInfo.getRoleCode()));
+        }
         return userInfoVo;
     }
 

+ 4 - 0
dbsyncer-manager/src/main/java/org/dbsyncer/manager/command/PreloadCommand.java

@@ -38,4 +38,8 @@ public class PreloadCommand implements Command {
     public Object parseProjectGroup() {
         return parser.parseObject(json, ProjectGroup.class);
     }
+
+    public Object parseUserConfig() {
+        return parser.parseObject(json, UserConfig.class);
+    }
 }

+ 6 - 1
dbsyncer-manager/src/main/java/org/dbsyncer/manager/enums/CommandEnum.java

@@ -50,7 +50,12 @@ public enum CommandEnum {
     /**
      * 预加载ProjectGroup
      */
-    PRELOAD_PROJECT_GROUP(ConfigConstant.PROJECT_GROUP, true, (cmd) -> ((PreloadCommand) cmd).parseProjectGroup());
+    PRELOAD_PROJECT_GROUP(ConfigConstant.PROJECT_GROUP, true, (cmd) -> ((PreloadCommand) cmd).parseProjectGroup()),
+
+    /**
+     * 预加载用户配置
+     */
+    USER_CONFIG(ConfigConstant.USER_CONFIG, true, (cmd) -> ((PreloadCommand) cmd).parseUserConfig());
 
     private String modelType;
     private boolean preload;

+ 3 - 1
dbsyncer-manager/src/main/java/org/dbsyncer/manager/template/PreloadTemplate.java

@@ -90,7 +90,7 @@ public final class PreloadTemplate implements ApplicationListener<ContextRefresh
             return;
         }
 
-        // Load configs
+        // Load config
         reload(map, CommandEnum.PRELOAD_CONFIG);
         // Load connectors
         reload(map, CommandEnum.PRELOAD_CONNECTOR);
@@ -100,6 +100,8 @@ public final class PreloadTemplate implements ApplicationListener<ContextRefresh
         reload(map, CommandEnum.PRELOAD_META);
         // Load projectGroups
         reload(map, CommandEnum.PRELOAD_PROJECT_GROUP);
+        // Load userConfig
+        reload(map, CommandEnum.USER_CONFIG);
         launch();
     }
 

+ 27 - 2
dbsyncer-web/src/main/java/org/dbsyncer/web/controller/user/UserController.java

@@ -2,6 +2,8 @@ package org.dbsyncer.web.controller.user;
 
 import org.dbsyncer.biz.UserService;
 import org.dbsyncer.biz.vo.RestResult;
+import org.dbsyncer.biz.vo.UserInfoVo;
+import org.dbsyncer.common.util.StringUtil;
 import org.dbsyncer.web.controller.BaseController;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,15 +39,26 @@ public class UserController extends BaseController {
 
     @RequestMapping("")
     public String index(ModelMap model) {
-        model.put("currentUser", userService.getUserInfoVo(getUserName()));
+        model.put("currentUser", getUserInfoVo(null));
         model.put("users", userService.getUserInfoAll(getUserName()));
         return "user/user";
     }
 
+    @GetMapping("/page/add")
+    public String pageAdd(ModelMap model) {
+        return "user/add";
+    }
+
+    @GetMapping("/page/edit")
+    public String pageEdit(ModelMap model, String username) {
+        model.put("currentUser", getUserInfoVo(username));
+        return "user/edit";
+    }
+
     @GetMapping("/getUserInfo.json")
     @ResponseBody
     public RestResult getUserInfo() {
-        return RestResult.restSuccess(userService.getUserInfoVo(getUserName()));
+        return RestResult.restSuccess(getUserInfoVo(null));
     }
 
     @RequestMapping(value = "/add")
@@ -90,6 +103,18 @@ public class UserController extends BaseController {
         return params;
     }
 
+    /**
+     * 获取用户信息
+     *
+     * @param userName 穿空获取自己,否则获取指定用户
+     * @return
+     */
+    private UserInfoVo getUserInfoVo(String userName) {
+        String currentUserName = getUserName();
+        userName = StringUtil.isBlank(userName) ? currentUserName : userName;
+        return userService.getUserInfoVo(currentUserName, userName);
+    }
+
     private String getUserName() {
         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
         String username = authentication.getName();

+ 6 - 1
dbsyncer-web/src/main/resources/public/nav.html

@@ -24,7 +24,12 @@
                 </li>
             </ul>
             <ul class="nav navbar-nav navbar-right">
-                <li><a href="javascript:void(0);" id="nav_logout"> <i class ="fa fa-power-off"></i>注销</a></li>
+                <li class="dropdown">
+                    <a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown"><span id="currentUser"></span></a>
+                    <ul class="dropdown-menu">
+                        <li><a href="javascript:void(0);" id="nav_logout"> <i class ="fa fa-power-off"></i>注销</a></li>
+                    </ul>
+                </li>
             </ul>
         </div>
     </div>

+ 97 - 0
dbsyncer-web/src/main/resources/public/user/add.html

@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
+
+<div class="container-fluid">
+  <div class="container">
+    <form id="userAddForm" class="form-horizontal" role="form" method="post">
+      <!-- 标题 -->
+      <div class="row text-center">
+        <div class="page-header">
+          <h3>添加用户</h3>
+        </div>
+      </div>
+
+      <!-- 操作 -->
+      <div class="form-group">
+        <div class="col-md-10"></div>
+        <div class="col-md-2 text-right">
+          <button id="userSubmitBtn" type="button" class="btn btn-primary">
+            <span class="fa fa-save"></span>保存
+          </button>
+          <button id="userBackBtn" type="button" class="btn btn-default">
+            <span class="fa fa-reply"></span>返回
+          </button>
+        </div>
+      </div>
+
+      <!-- 配置 -->
+      <div class="row">
+        <div class="col-md-12">
+          <div class="panel panel-info">
+            <div class="panel-heading">
+              <h3 class="panel-title">用户配置</h3>
+            </div>
+
+            <div class="panel-body">
+              <!-- 账号 -->
+              <div class="form-group">
+                <label class="col-sm-2 control-label">账号 <strong class="driverVerifcateRequired">*</strong></label>
+                <div class="col-sm-10">
+                  <input class="form-control" name="username" type="text" maxlength="50" dbsyncer-valid="require" placeholder="账号"/>
+                </div>
+              </div>
+
+              <!-- 昵称 -->
+              <div class="form-group">
+                <label class="col-sm-2 control-label">昵称 <strong class="driverVerifcateRequired">*</strong></label>
+                <div class="col-sm-10">
+                  <input class="form-control" name="nickname" type="text" maxlength="50" dbsyncer-valid="require" placeholder="昵称"/>
+                </div>
+              </div>
+
+              <!-- 密码 -->
+              <div class="form-group">
+                <label class="col-sm-2 control-label">密码 <strong class="driverVerifcateRequired">*</strong></label>
+                <div class="col-sm-10">
+                  <input class="form-control" name="password" type="password" maxlength="50" dbsyncer-valid="require" placeholder="密码"/>
+                </div>
+              </div>
+
+            </div>
+          </div>
+        </div>
+      </div>
+
+    </form>
+  </div>
+</div>
+
+<script type="text/javascript">
+  // 跳转用户管理
+  function backUserIndexPage() {
+    doLoader("/user?refresh=" + new Date().getTime());
+  }
+
+  //保存
+  $("#userSubmitBtn").click(function () {
+    const $form = $("#userAddForm");
+    if ($form.formValidate() == true) {
+      const data = $form.serializeJson();
+      doPoster("/user/add", data, function (data) {
+        if (data.success == true) {
+          bootGrowl("新增用户成功!", "success");
+          backUserIndexPage();
+        } else {
+          bootGrowl(data.resultValue, "danger");
+        }
+      });
+    }
+  });
+
+  //返回
+  $("#userBackBtn").click(function () {
+    backUserIndexPage();
+  });
+</script>
+</html>

+ 105 - 0
dbsyncer-web/src/main/resources/public/user/edit.html

@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
+
+<div class="container-fluid">
+  <div class="container">
+    <form id="userEditForm" class="form-horizontal" role="form" method="post">
+      <!-- 标题 -->
+      <div class="row text-center">
+        <div class="page-header">
+          <h3>修改用户</h3>
+        </div>
+      </div>
+
+      <!-- 操作 -->
+      <div class="form-group">
+        <div class="col-md-10"></div>
+        <div class="col-md-2 text-right">
+          <button id="userSubmitBtn" type="button" class="btn btn-primary">
+            <span class="fa fa-save"></span>保存
+          </button>
+          <button id="userBackBtn" type="button" class="btn btn-default">
+            <span class="fa fa-reply"></span>返回
+          </button>
+        </div>
+      </div>
+
+      <!-- 配置 -->
+      <div class="row">
+        <div class="col-md-12">
+          <div class="panel panel-info">
+            <div class="panel-heading">
+              <h3 class="panel-title">用户配置</h3>
+            </div>
+
+            <div class="panel-body">
+              <!-- 账号 -->
+              <div class="form-group">
+                <label class="col-sm-2 control-label">账号 <strong class="driverVerifcateRequired">*</strong></label>
+                <div class="col-sm-10">
+                  <input class="form-control" name="username" type="text" maxlength="50" dbsyncer-valid="require" placeholder="账号" readonly="true" th:value="${currentUser?.username}"/>
+                </div>
+              </div>
+
+              <!-- 昵称 -->
+              <div class="form-group">
+                <label class="col-sm-2 control-label">昵称 <strong class="driverVerifcateRequired">*</strong></label>
+                <div class="col-sm-10">
+                  <input class="form-control" name="nickname" type="text" maxlength="50" dbsyncer-valid="require" placeholder="昵称" th:value="${currentUser?.nickname}"/>
+                </div>
+              </div>
+
+              <!-- 新密码 -->
+              <div class="form-group">
+                <label class="col-sm-2 control-label">新密码</label>
+                <div class="col-sm-10">
+                  <input class="form-control" name="newPwd" type="password" maxlength="50" placeholder="密码"/>
+                </div>
+              </div>
+
+              <!-- 旧密码 -->
+              <div class="form-group">
+                <label class="col-sm-2 control-label">旧密码</label>
+                <div class="col-sm-10">
+                  <input class="form-control" name="oldPwd" type="password" maxlength="50" placeholder="密码"/>
+                </div>
+              </div>
+
+            </div>
+          </div>
+        </div>
+      </div>
+
+    </form>
+  </div>
+</div>
+
+<script type="text/javascript">
+  // 跳转用户管理
+  function backUserIndexPage() {
+    doLoader("/user?refresh=" + new Date().getTime());
+  }
+
+  //保存
+  $("#userSubmitBtn").click(function () {
+    const $form = $("#userEditForm");
+    if ($form.formValidate() == true) {
+      const data = $form.serializeJson();
+      doPoster("/user/edit", data, function (data) {
+        if (data.success == true) {
+          bootGrowl("修改用户成功!", "success");
+          backUserIndexPage();
+        } else {
+          bootGrowl(data.resultValue, "danger");
+        }
+      });
+    }
+  });
+
+  //返回
+  $("#userBackBtn").click(function () {
+    backUserIndexPage();
+  });
+</script>
+</html>

+ 24 - 9
dbsyncer-web/src/main/resources/public/user/user.html

@@ -10,28 +10,43 @@
             </div>
         </div>
 
+        <!-- 管理员操作区域 -->
+        <div class="form-group" th:if="${currentUser?.roleCode eq 'admin'}">
+            <div class="row">
+                <div class="col-sm-10"></div>
+                <div class="col-md-2 text-right">
+                    <button id="addUserBtn" type="button" class="btn btn-primary">
+                        <span class="fa fa-plus"></span>添加([[${users?.size()} ?: 0]])
+                    </button>
+                </div>
+            </div>
+        </div>
+
         <!-- 操作 -->
         <div class="row">
             <div class="col-md-12">
                 <table class="table table-hover">
                     <thead>
                     <tr>
-                        <th>序号</th>
                         <th>用户</th>
-                        <th>昵称</th>
                         <th>角色</th>
-                        <th>密码</th>
                         <th>操作</th>
                     </tr>
                     </thead>
                     <tbody id="userList">
-                    <tr th:id="${u?.username}" th:each="u,state : ${users}">
-                        <td th:text="${state.count}"/>
-                        <td th:text="${u?.username}"/>
-                        <td th:text="${u?.nickname}"/>
+                    <tr th:each="u,state : ${users}">
+                        <td th:text="${u?.username + '(' + u?.nickname + ')'}"/>
                         <td th:text="${u?.roleName}"/>
-                        <td th:text="${u?.password}"/>
-                        <td th:text="修改"/>
+                        <td>
+                            <!-- 管理员或本人权限 -->
+                            <button th:if="${currentUser?.roleCode eq 'admin' or currentUser?.username eq u?.username}" type="button" th:id="${u?.username}" class="btn btn-primary editUserBtn">
+                                <span class="fa fa-pencil"></span>修改
+                            </button>
+                            <!-- 管理员且非本人权限 -->
+                            <button th:if="${currentUser?.roleCode eq 'admin' and currentUser?.username != u?.username}" type="button" th:id="${u?.username}" class="btn btn-default removeUserBtn">
+                                <span class="fa fa-times"></span>删除
+                            </button>
+                        </td>
                     </tr>
                     </tbody>
                 </table>

+ 7 - 0
dbsyncer-web/src/main/resources/static/js/index.js

@@ -1,6 +1,13 @@
 // ******************* 初始化 *****************************
 // 默认绑定菜单事件
 $(function () {
+    // 获取登录用户信息
+    doGetter("/user/getUserInfo.json",{}, function (data) {
+        if (data.success == true) {
+            const userDetail = data.resultValue.nickname + " (" + data.resultValue.roleName + ")";
+            $("#currentUser").html(userDetail);
+        }
+    });
 
     // 初始化版权信息
     doGetter("/index/version.json",{}, function (data) {

+ 38 - 19
dbsyncer-web/src/main/resources/static/js/user/index.js

@@ -1,24 +1,43 @@
-function submit(data) {
-    doPoster('/user/add', data, function (data) {
-        if (data.success == true) {
-            doPoster("/logout", null, function (data) {
-                location.href = $basePath;
-            });
-        } else {
-            bootGrowl(data.resultValue, "danger");
-            doLoader("/user");
-        }
+$(function () {
+    // 绑定添加用户按钮事件
+    $("#addUserBtn").click(function () {
+        doLoader("/user/page/add");
     });
-}
 
-$(function () {
-    //保存
-    $("#updateUserBtn").click(function () {
-        var $form = $("#configEditForm");
-        if ($form.formValidate() == true) {
-            var data = $form.serializeJson();
-            submit(data);
-        }
+    // 绑定修改用户按钮事件
+    $(".editUserBtn").click(function () {
+        doLoader("/user/page/edit?username=" + $(this).attr("id"));
+    });
+
+    // 绑定删除用户按钮事件
+    $(".removeUserBtn").click(function () {
+        const $username = $(this).attr("id");
+        // 确认框确认是否删除用户
+        BootstrapDialog.show({
+            title: "提示",
+            type: BootstrapDialog.TYPE_INFO,
+            message: "确认删除帐号?",
+            size: BootstrapDialog.SIZE_NORMAL,
+            buttons: [{
+                label: "确定",
+                action: function (dialog) {
+                    doPoster('/user/remove', {username: $username}, function (data) {
+                        if (data.success == true) {
+                            bootGrowl("删除用户成功!", "success");
+                        } else {
+                            bootGrowl(data.resultValue, "danger");
+                        }
+                        doLoader("/user?refresh=" + new Date().getTime());
+                    });
+                    dialog.close();
+                }
+            }, {
+                label: "取消",
+                action: function (dialog) {
+                    dialog.close();
+                }
+            }]
+        });
     });
 
 })