浏览代码

update AccountService

reghao 1 年之前
父节点
当前提交
72e0dd8f3b
共有 22 个文件被更改,包括 199 次插入415 次删除
  1. 6 12
      web/src/main/java/cn/reghao/devops/web/account/controller/HomeController.java
  2. 29 35
      web/src/main/java/cn/reghao/devops/web/account/controller/UserController.java
  3. 28 43
      web/src/main/java/cn/reghao/devops/web/account/controller/page/UserPageController.java
  4. 1 1
      web/src/main/java/cn/reghao/devops/web/account/model/dto/AccountProfile.java
  5. 2 1
      web/src/main/java/cn/reghao/devops/web/account/model/dto/AccountRole.java
  6. 5 4
      web/src/main/java/cn/reghao/devops/web/account/model/dto/CreateAccountDto.java
  7. 0 1
      web/src/main/java/cn/reghao/devops/web/account/service/AccountService.java
  8. 6 3
      web/src/main/java/cn/reghao/devops/web/account/service/AccountSessionService.java
  9. 7 8
      web/src/main/java/cn/reghao/devops/web/account/service/RoleService.java
  10. 5 22
      web/src/main/java/cn/reghao/devops/web/account/service/impl/AccountServiceImpl.java
  11. 6 29
      web/src/main/java/cn/reghao/devops/web/account/service/impl/RoleServiceImpl.java
  12. 9 19
      web/src/main/java/cn/reghao/devops/web/config/AppLifecycle.java
  13. 0 5
      web/src/main/java/cn/reghao/devops/web/config/AppProperties.java
  14. 0 167
      web/src/main/java/cn/reghao/devops/web/sys/service/FileService.java
  15. 17 0
      web/src/main/java/cn/reghao/devops/web/sys/service/SysMessageService.java
  16. 1 2
      web/src/main/resources/application.yml
  17. 1 1
      web/src/main/resources/templates/rbac/user/add.html
  18. 0 40
      web/src/main/resources/templates/rbac/user/edit.html
  19. 11 11
      web/src/main/resources/templates/rbac/user/index.html
  20. 11 8
      web/src/main/resources/templates/rbac/user/userinfo.html
  21. 2 2
      web/src/main/resources/templates/rbac/user/userpasswd.html
  22. 52 1
      web/src/test/java/cn/reghao/devops/web/account/AccountTest.java

+ 6 - 12
web/src/main/java/cn/reghao/devops/web/account/controller/AccountAuthController.java → web/src/main/java/cn/reghao/devops/web/account/controller/HomeController.java

@@ -7,7 +7,7 @@ import cn.reghao.devops.web.account.model.po.Menu;
 import cn.reghao.devops.web.account.model.po.User;
 import cn.reghao.devops.web.account.service.HomeService;
 import cn.reghao.devops.web.account.service.UserContext;
-import cn.reghao.devops.web.sys.db.repository.SysMessageRepository;
+import cn.reghao.devops.web.sys.service.SysMessageService;
 import cn.reghao.jutil.jdk.jvm.JVM;
 import cn.reghao.jutil.jdk.jvm.model.JvmInfo;
 import cn.reghao.jutil.jdk.machine.id.MachineIdLinux;
@@ -27,19 +27,19 @@ import java.util.Map;
 @Slf4j
 @Api(tags = "登录页和首页")
 @Controller
-public class AccountAuthController {
+public class HomeController {
     private final JVM jvm;
     private final MachineIdLinux machineId;
     private final AppVersion appVersion;
     private final HomeService homeService;
-    private final SysMessageRepository sysMessageRepository;
+    private final SysMessageService sysMessageService;
 
-    public AccountAuthController(HomeService homeService, SysMessageRepository sysMessageRepository) {
+    public HomeController(HomeService homeService, SysMessageService sysMessageService) {
         this.jvm = new JVM();
         this.machineId = new MachineIdLinux();
         this.appVersion = AppVersion.getVersion();
         this.homeService = homeService;
-        this.sysMessageRepository = sysMessageRepository;
+        this.sysMessageService = sysMessageService;
     }
     
     @GetMapping("/login")
@@ -57,13 +57,7 @@ public class AccountAuthController {
 
         boolean isAdmin = user.getRole().contains(RoleType.ROLE_ADMIN.name());
         if (isAdmin) {
-            int unreadTotal = sysMessageRepository.countByUnread(true);
-            String unreadMessage;
-            if (unreadTotal > 9) {
-                unreadMessage = "9+";
-            } else {
-                unreadMessage = String.valueOf(unreadTotal);
-            }
+            String unreadMessage = sysMessageService.getUnreadCount();
             model.addAttribute("unreadMessage", unreadMessage);
         }
 

+ 29 - 35
web/src/main/java/cn/reghao/devops/web/account/controller/UserController.java

@@ -16,7 +16,6 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.validation.constraints.NotNull;
-import java.util.List;
 
 /**
  * @author reghao
@@ -32,6 +31,26 @@ public class UserController {
         this.accountService = accountService;
     }
 
+    @ApiOperation(value = "修改个人头像")
+    @PostMapping(value = "/avatar", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String userAvatar(MultipartFile file) {
+        return WebResult.failWithMsg("接口未实现");
+    }
+
+    @ApiOperation(value = "修改个人信息")
+    @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String editUserInfo(@Validated AccountProfile accountProfile) {
+        accountService.updateAccountProfile(accountProfile);
+        return WebResult.success();
+    }
+
+    @ApiOperation(value = "修改个人密码")
+    @PostMapping(value = "/passwd/update", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    public String editPasswd(UpdatePasswordDto updatePasswordDto) {
+        return WebResult.failWithMsg("接口未实现");
+    }
+
     @PreAuthorize("hasRole('ROLE_ADMIN')")
     @ApiOperation(value = "创建用户")
     @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@@ -44,37 +63,18 @@ public class UserController {
     @ApiOperation(value = "批量创建用户")
     @PostMapping(value = "/batch", produces = MediaType.APPLICATION_JSON_VALUE)
     public String batchAdd(MultipartFile file) {
-        return WebResult.success();
-    }
-
-    @PostMapping(value = "/avatar", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String userAvatar(MultipartFile file) {
-        return WebResult.success();
-    }
-
-    @ApiOperation(value = "修改用户信息")
-    @PostMapping(value = "/modify", produces = MediaType.APPLICATION_JSON_VALUE)
-    @Deprecated
-    public String updateAccountProfile(@Validated AccountProfile accountProfile) {
-        accountService.updateAccountProfile(accountProfile);
-        return WebResult.success();
-    }
-
-    @ApiOperation(value = "修改用户信息")
-    @PostMapping(value = "/edit", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String editUserInfo(@Validated AccountProfile accountProfile) {
-        accountService.updateAccountProfile(accountProfile);
-        return WebResult.success();
+        return WebResult.failWithMsg("接口未实现");
     }
 
     @PreAuthorize("hasRole('ROLE_ADMIN')")
     @ApiOperation(value = "删除用户")
-    @DeleteMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    @DeleteMapping(value = "/delete/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
     public String deleteAccount(@PathVariable("id") Integer userId) {
         Result result = accountService.deleteAccount(userId);
         return WebResult.result(result);
     }
 
+    @PreAuthorize("hasRole('ROLE_ADMIN')")
     @ApiOperation(value = "修改用户密码")
     @PostMapping(value = "/passwd", produces = MediaType.APPLICATION_JSON_VALUE)
     public String modifyPassword(@NotNull Integer id, @NotNull String newPassword) {
@@ -82,12 +82,7 @@ public class UserController {
         return WebResult.success();
     }
 
-    @PostMapping(value = "/passwd/update", produces = MediaType.APPLICATION_JSON_VALUE)
-    @ResponseBody
-    public String editPasswd(UpdatePasswordDto updatePasswordDto) {
-        return WebResult.success();
-    }
-
+    @PreAuthorize("hasRole('ROLE_ADMIN')")
     @ApiOperation(value = "分配用户角色")
     @PostMapping(value = "/role", produces = MediaType.APPLICATION_JSON_VALUE)
     public String assignRole(@Validated AccountRole accountRole) {
@@ -95,12 +90,11 @@ public class UserController {
         return WebResult.success();
     }
 
-    // TODO 暂不启用本功能
+    @PreAuthorize("hasRole('ROLE_ADMIN')")
     @ApiOperation(value = "启用/禁用用户")
-    @PostMapping(value = "/status/{enable}", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String updateAccountStatus(@PathVariable("enable") Boolean enable,
-                                @RequestParam(value = "ids") List<Integer> userIds) {
-        userIds.forEach(userId -> accountService.updateAccountStatus(userId, enable));
-        return WebResult.success();
+    @PostMapping(value = "/status/{userId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String updateAccountStatus(@PathVariable("userId") Integer userId) {
+        //userIds.forEach(userId -> accountService.updateAccountStatus(userId, enable));
+        return WebResult.failWithMsg("接口未实现");
     }
 }

+ 28 - 43
web/src/main/java/cn/reghao/devops/web/account/controller/page/UserPageController.java

@@ -1,14 +1,13 @@
 package cn.reghao.devops.web.account.controller.page;
 
 import cn.reghao.devops.web.account.db.query.UserQuery;
-import cn.reghao.devops.web.account.model.po.Role;
 import cn.reghao.devops.web.account.model.po.User;
+import cn.reghao.devops.web.account.model.vo.RoleVO;
 import cn.reghao.devops.web.account.model.vo.UserVO;
 import cn.reghao.devops.web.account.service.AccountSessionService;
 import cn.reghao.devops.web.account.service.RoleService;
 import cn.reghao.devops.web.account.service.UserContext;
 import cn.reghao.devops.web.util.db.PageSort;
-import cn.reghao.jutil.jdk.converter.DateTimeConverter;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.data.domain.Page;
@@ -18,7 +17,6 @@ import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.*;
 
-import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -40,6 +38,20 @@ public class UserPageController {
         this.accountSessionService = accountSessionService;
     }
 
+    @ApiOperation(value = "个人信息页面")
+    @GetMapping("/profile")
+    public String userInfoPage(Model model) {
+        User user = UserContext.getUser();
+        model.addAttribute("user", user);
+        return "/rbac/user/userinfo";
+    }
+
+    @ApiOperation(value = "修改个人密码页面")
+    @GetMapping("/passwd/edit")
+    public String editPasswdPage(Model model) {
+        return "/rbac/user/userpasswd";
+    }
+
     @ApiOperation(value = "用户列表页面")
     @GetMapping
     public String userPage(@RequestParam(value = "screenName", required = false) String screenName, Model model) {
@@ -52,12 +64,12 @@ public class UserPageController {
             page = userQuery.getUserVOByPage(pageRequest);
         }
 
-        Map<Integer, LocalDateTime> map = accountSessionService.getLastRequest();
+        Map<Integer, String> map = accountSessionService.getLastAccess();
         page.getContent().forEach(userVO -> {
             int userId = userVO.getUserId();
-            LocalDateTime lastAccess = map.get(userId);
-            if (lastAccess != null) {
-                userVO.setLastAccess(DateTimeConverter.format(lastAccess));
+            String lastAccessStr = map.get(userId);
+            if (lastAccessStr != null) {
+                userVO.setLastAccess(lastAccessStr);
             }
         });
 
@@ -69,68 +81,41 @@ public class UserPageController {
     @ApiOperation(value = "新增用户页面")
     @GetMapping("/add")
     public String addUserPage(Model model) {
-        Set<Role> allRoles = new HashSet<>(roleService.getAllRoles());
-        Set<Role> userRoles = Collections.emptySet();
-
+        Set<RoleVO> allRoles = roleService.getAllRoles();
         model.addAttribute("allRoles", allRoles);
-        model.addAttribute("userRoles", userRoles);
+        model.addAttribute("userRoles", Collections.emptySet());
         return "/rbac/user/add";
     }
 
-    @ApiOperation(value = "用户信息编辑页面")
-    @GetMapping("/edit/{id}")
-    @Deprecated
-    public String editUserPage(@PathVariable("id") User user, Model model) {
-        Set<Role> allRoles = new HashSet<>(roleService.getAllRoles());
-        Set<Role> userRoles = roleService.getUserRoles(user.getId());
-
-        model.addAttribute("allRoles", allRoles);
-        model.addAttribute("userRoles", userRoles);
-        model.addAttribute("user", user);
-        return "/rbac/user/edit";
-    }
-
     @ApiOperation(value = "用户详细信息页面")
     @GetMapping("/detail/{id}")
     public String userDetailPage(@PathVariable("id") int id, Model model) {
         User user = userQuery.findById(id);
-        Set<Role> roles = roleService.getUserRoles(user.getId());
-        List<String> names = roles.stream().map(Role::getName).collect(Collectors.toList());
+        Set<RoleVO> roles = roleService.getUserRoles(user.getId());
+        List<String> names = roles.stream().map(RoleVO::getName).collect(Collectors.toList());
 
         model.addAttribute("roles", names.toString());
         model.addAttribute("user", user);
         return "/rbac/user/detail";
     }
 
-    @GetMapping("/profile")
-    public String userInfoPage(Model model) {
-        User user = UserContext.getUser();
-        model.addAttribute("user", user);
-        return "/rbac/user/userinfo";
-    }
-
-    @ApiOperation(value = "用户修改密码页面")
+    @ApiOperation(value = "重置用户密码页面")
     @GetMapping("/passwd/{id}")
     public String modifyPasswordPage(@PathVariable("id") Integer id, Model model) {
         model.addAttribute("id", id);
         return "/rbac/user/passwd";
     }
 
-    @GetMapping("/passwd/edit")
-    public String editPasswdPage(Model model) {
-        return "/rbac/user/editpasswd";
-    }
-
     @ApiOperation(value = "用户角色分配页面")
     @GetMapping("/role/{id}")
     public String assignRolePage(@PathVariable("id") User user, Model model) {
-        Set<Role> roles = new HashSet<>(roleService.getAllRoles());
+        Set<RoleVO> allRoles = roleService.getAllRoles();
         int userId = user.getId();
-        Set<Role> authRoles = roleService.getUserRoles(userId);
+        Set<RoleVO> userRoles = roleService.getUserRoles(userId);
 
         model.addAttribute("id", userId);
-        model.addAttribute("list", roles);
-        model.addAttribute("authRoles", authRoles);
+        model.addAttribute("list", allRoles);
+        model.addAttribute("authRoles", userRoles);
         return "/rbac/user/role";
     }
 }

+ 1 - 1
web/src/main/java/cn/reghao/devops/web/account/model/dto/AccountProfile.java

@@ -13,7 +13,7 @@ import java.io.Serializable;
 public class AccountProfile implements Serializable {
     private static final long serialVersionUID = 1L;
     private Integer userId;
-    @NotBlank(message = "用户名不能为空白字符串")
+    @NotBlank(message = "显示名不能为空白字符串")
     private String screenName;
     private String mobile;
     private String email;

+ 2 - 1
web/src/main/java/cn/reghao/devops/web/account/model/dto/AccountRole.java

@@ -4,6 +4,7 @@ import cn.reghao.devops.web.account.model.po.Role;
 import lombok.Data;
 
 import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
 import java.io.Serializable;
 import java.util.Set;
 
@@ -15,6 +16,6 @@ import java.util.Set;
 public class AccountRole implements Serializable {
     private static final long serialVersionUID = 1L;
     private Integer userId;
-    @NotNull(message = "用户角色不能为 NULL")
+    @Size(min = 1, max = 3, message = "用户可拥有 1~3 个角色")
     private Set<Role> roles;
 }

+ 5 - 4
web/src/main/java/cn/reghao/devops/web/account/model/dto/CreateAccountDto.java

@@ -4,7 +4,8 @@ import lombok.Getter;
 import lombok.Setter;
 
 import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.Set;
 
 /**
  * @author reghao
@@ -19,13 +20,13 @@ public class CreateAccountDto {
     private String screenName;
     @NotBlank(message = "必须指定登录密码")
     private String password;
-    @NotNull(message = "必须选择用户角色")
-    private Integer roleId;
+    @Size(min = 1, max = 3, message = "用户可拥有 1~3 个角色")
+    private Set<Integer> roleId;
 
     public CreateAccountDto(String username, String password, int roleId) {
         this.username = username;
         this.screenName = username;
         this.password = password;
-        this.roleId = roleId;
+        this.roleId = Set.of(roleId);
     }
 }

+ 0 - 1
web/src/main/java/cn/reghao/devops/web/account/service/AccountService.java

@@ -10,7 +10,6 @@ import cn.reghao.jutil.jdk.result.Result;
  * @date 2020-06-19 16:36:53
  */
 public interface AccountService {
-    void initAccount();
     Result createAccount(CreateAccountDto createAccountDto);
     void updateAccountPassword(Integer userId, String newPassword);
     void updateAccountProfile(AccountProfile accountProfile);

+ 6 - 3
web/src/main/java/cn/reghao/devops/web/account/service/AccountSessionService.java

@@ -2,6 +2,7 @@ package cn.reghao.devops.web.account.service;
 
 import cn.reghao.devops.web.account.db.repository.UserRepository;
 import cn.reghao.devops.web.account.model.po.User;
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
 import org.springframework.security.core.session.SessionInformation;
 import org.springframework.security.core.session.SessionRegistry;
 import org.springframework.stereotype.Service;
@@ -24,15 +25,17 @@ public class AccountSessionService {
         this.userRepository = userRepository;
     }
 
-    public Map<Integer, LocalDateTime> getLastRequest() {
-        Map<Integer, LocalDateTime> map = new HashMap<>();
+    // TODO 配置中使用 session.persistent 后, 应用在重启后不能获取到 getLastRequest
+    public Map<Integer, String> getLastAccess() {
+        Map<Integer, String> map = new HashMap<>();
         for (Object principal : sessionRegistry.getAllPrincipals()) {
             String username = (String) principal;
             User user = userRepository.findByUsername(username);
             for (SessionInformation sessionInfo : sessionRegistry.getAllSessions(principal, false)) {
                 Date date = sessionInfo.getLastRequest();
                 LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
-                map.put(user.getId(), localDateTime);
+                String localDateTimeStr = DateTimeConverter.format(localDateTime);
+                map.put(user.getId(), localDateTimeStr);
             }
         }
 

+ 7 - 8
web/src/main/java/cn/reghao/devops/web/account/service/RoleService.java

@@ -17,28 +17,27 @@ import java.util.Set;
  * @date 2020-06-19 16:36:53
  */
 public interface RoleService {
-    void initRole();
     void addOrUpdate(RoleDto roleDto);
     Result deleteRole(Integer roleId);
     void setRoleMenus(Integer roleId, Set<Menu> menus);
     Role getByName(String name);
     Page<RoleVO> getByPage(PageRequest pageRequest, String name);
     RoleVO getRoleVOById(Integer roleId);
-    List<Role> getAllRoles();
+    Set<RoleVO> getAllRoles();
     /**
-     * 获取拥有角色的用户
+     * 获取用户拥有角色
      *
      * @param
      * @return
-     * @date 2021-07-14 下午2:57
+     * @date 2021-09-10 下午11:12
      */
-    List<User> getRoleUsers(Integer roleId);
+    Set<RoleVO> getUserRoles(Integer userId);
     /**
-     * 获取用户拥有角色
+     * 获取拥有角色的用户
      *
      * @param
      * @return
-     * @date 2021-09-10 下午11:12
+     * @date 2021-07-14 下午2:57
      */
-    Set<Role> getUserRoles(Integer userId);
+    List<User> getRoleUsers(Integer roleId);
 }

+ 5 - 22
web/src/main/java/cn/reghao/devops/web/account/service/impl/AccountServiceImpl.java

@@ -43,29 +43,11 @@ public class AccountServiceImpl implements AccountService {
         this.accountSessionService = accountSessionService;
     }
 
-    @Override
-    public void initAccount() {
-        List<User> list = userRepository.findAll(PageRequest.of(0, 1)).getContent();
-        if (list.isEmpty()) {
-            String username = RandomString.getString(3).toLowerCase(Locale.ROOT);
-            username = "admin";
-            String password = RandomString.getString(10);
-
-            Role role = roleRepository.findByName(RoleType.ROLE_ADMIN.name());
-            if (role != null) {
-                int roleId = role.getId();
-                CreateAccountDto createAccountDto = new CreateAccountDto(username, password, roleId);
-                createAccount(createAccountDto);
-                log.info("初始化完成, 帐号和密码分别是 {} 和 {}", username, password);
-            }
-        }
-    }
-
     @Override
     public Result createAccount(CreateAccountDto createAccountDto) {
-        int roleId = createAccountDto.getRoleId();
-        Role role = roleRepository.findById(roleId).orElse(null);
-        if (role == null) {
+        Set<Integer> roleIds = createAccountDto.getRoleId();
+        List<Role> roles = roleRepository.findAllById(roleIds);
+        if (roles.isEmpty()) {
             return Result.fail("role 不存在");
         }
 
@@ -76,7 +58,8 @@ public class AccountServiceImpl implements AccountService {
             String salt = RandomString.getSalt(64);
             String encodedPassword = passwordEncoder.encode(password + salt);
 
-            user = new User(username, encodedPassword, salt, Set.of(role.getName()));
+            Set<String> userRoles = roles.stream().map(Role::getName).collect(Collectors.toSet());
+            user = new User(username, encodedPassword, salt, userRoles);
             userRepository.save(user);
             return Result.success();
         }

+ 6 - 29
web/src/main/java/cn/reghao/devops/web/account/service/impl/RoleServiceImpl.java

@@ -1,9 +1,7 @@
 package cn.reghao.devops.web.account.service.impl;
 
-import cn.reghao.devops.web.account.db.repository.MenuRepository;
 import cn.reghao.devops.web.account.db.repository.RoleRepository;
 import cn.reghao.devops.web.account.db.repository.UserRepository;
-import cn.reghao.devops.web.account.model.constant.RoleType;
 import cn.reghao.devops.web.account.model.dto.RoleDto;
 import cn.reghao.devops.web.account.model.po.Menu;
 import cn.reghao.devops.web.account.model.po.Role;
@@ -33,33 +31,13 @@ import java.util.stream.Collectors;
 @Service
 public class RoleServiceImpl implements RoleService {
     private final RoleRepository roleRepository;
-    private final MenuRepository menuRepository;
     private final UserRepository userRepository;
 
-    public RoleServiceImpl(RoleRepository roleRepository, MenuRepository menuRepository, UserRepository userRepository) {
+    public RoleServiceImpl(RoleRepository roleRepository, UserRepository userRepository) {
         this.roleRepository = roleRepository;
-        this.menuRepository = menuRepository;
         this.userRepository = userRepository;
     }
 
-    public void initRole() {
-        List<Role> roleList = roleRepository.findAll();
-        if (roleList.isEmpty()) {
-            roleList = new ArrayList<>();
-            for (RoleType roleType : RoleType.values()) {
-                roleList.add(new Role(roleType.name(), roleType.getDesc()));
-            }
-            roleRepository.saveAll(roleList);
-
-            List<Menu> menus = menuRepository.findAll();
-            if (!menus.isEmpty()) {
-                Role role = roleRepository.findByName(RoleType.ROLE_ADMIN.name());
-                role.setMenus(new HashSet<>(menus));
-                roleRepository.save(role);
-            }
-        }
-    }
-
     @Override
     public void addOrUpdate(RoleDto roleDto) {
         String name = String.format("ROLE_%s", roleDto.getName().toUpperCase(Locale.ROOT));
@@ -129,10 +107,9 @@ public class RoleServiceImpl implements RoleService {
     }
 
     @Override
-    public List<Role> getAllRoles() {
-        PageRequest pageRequest = PageRequest.of(0, 20);
-        Page<Role> page = roleRepository.findAll(pageRequest);
-        return page.getContent();
+    public Set<RoleVO> getAllRoles() {
+        PageRequest pageRequest = PageRequest.of(0, 100);
+        return roleRepository.findAll(pageRequest).stream().map(RoleVO::new).collect(Collectors.toSet());
     }
 
     /**
@@ -170,7 +147,7 @@ public class RoleServiceImpl implements RoleService {
     }
 
     @Override
-    public Set<Role> getUserRoles(Integer userId) {
+    public Set<RoleVO> getUserRoles(Integer userId) {
         User user = userRepository.findById(userId).orElse(null);
         if (user == null) {
             return Collections.emptySet();
@@ -184,6 +161,6 @@ public class RoleServiceImpl implements RoleService {
                 .map(UserAuthority::getAuthority)
                 .collect(Collectors.toList());
         Specification<Role> spec = ((root, query, criteriaBuilder) -> root.get("name").in(roles));
-        return new HashSet<>(roleRepository.findAll(spec));
+        return roleRepository.findAll(spec).stream().map(RoleVO::new).collect(Collectors.toSet());
     }
 }

+ 9 - 19
web/src/main/java/cn/reghao/devops/web/config/AppLifecycle.java

@@ -1,18 +1,15 @@
 package cn.reghao.devops.web.config;
 
-import cn.reghao.devops.web.account.service.AccountService;
 import cn.reghao.devops.web.mgr.builds.service.BuildDirService;
 import cn.reghao.devops.web.mgr.machine.service.MachineService;
-import cn.reghao.devops.web.account.service.RoleService;
 import cn.reghao.devops.web.mgr.builds.db.repository.CompilerConfigRepository;
 import cn.reghao.devops.web.mgr.builds.model.po.CompilerConfig;
 import cn.reghao.devops.web.mgr.app.service.bd.BuildStat;
+import cn.reghao.devops.web.sys.service.SysMessageService;
 import cn.reghao.devops.web.ws.handler.LogHandler;
 import cn.reghao.devops.web.mgr.build.model.constant.CompileType;
 import cn.reghao.devops.web.mgr.log.Appenders;
 import cn.reghao.devops.web.mgr.log.LoggerConfig;
-import cn.reghao.devops.web.sys.db.repository.SysMessageRepository;
-import cn.reghao.devops.web.sys.model.po.SysMessage;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.DisposableBean;
 import org.springframework.boot.ApplicationArguments;
@@ -32,23 +29,18 @@ import java.util.List;
 public class AppLifecycle implements ApplicationRunner, DisposableBean {
     private final LogHandler logHandler;
     private final CompilerConfigRepository compilerConfigRepository;
-    private final RoleService roleService;
-    private final AccountService accountService;
-    private final SysMessageRepository sysMessageRepository;
+    private final SysMessageService sysMessageService;
     private final BuildStat buildStat;
     private final MachineService machineService;
-    private BuildDirService buildDirService;
-    private AppProperties appProperties;
+    private final BuildDirService buildDirService;
+    private final AppProperties appProperties;
 
     public AppLifecycle(LogHandler logHandler, CompilerConfigRepository compilerConfigRepository,
-                        RoleService roleService, AccountService accountService, SysMessageRepository sysMessageRepository,
-                        BuildStat buildStat, MachineService machineService, BuildDirService buildDirService,
-                        AppProperties appProperties) {
+                        SysMessageService sysMessageService, BuildStat buildStat, MachineService machineService,
+                        BuildDirService buildDirService, AppProperties appProperties) {
         this.logHandler = logHandler;
         this.compilerConfigRepository = compilerConfigRepository;
-        this.roleService = roleService;
-        this.accountService = accountService;
-        this.sysMessageRepository = sysMessageRepository;
+        this.sysMessageService = sysMessageService;
         this.buildStat = buildStat;
         this.machineService = machineService;
         this.buildDirService = buildDirService;
@@ -69,8 +61,6 @@ public class AppLifecycle implements ApplicationRunner, DisposableBean {
     }
 
     private void initSys() throws IOException {
-        roleService.initRole();
-        accountService.initAccount();
         initBuildDir();
         initLogConfig();
         checkCompilerConfig();
@@ -124,9 +114,9 @@ public class AppLifecycle implements ApplicationRunner, DisposableBean {
             String homePath = compilerConfig.getHomePath();
             File file = new File(homePath);
             if (!file.exists()) {
+                String title = "系统异常";
                 String errMsg = String.format("编译配置 %s 的编译器 %s 不存在", name, homePath);
-                SysMessage sysMessage = new SysMessage("系统异常", errMsg);
-                sysMessageRepository.save(sysMessage);
+                sysMessageService.putSysMessage(title, errMsg);
             }
         }
     }

+ 0 - 5
web/src/main/java/cn/reghao/devops/web/config/AppProperties.java

@@ -14,10 +14,5 @@ import org.springframework.context.annotation.Configuration;
 @Configuration
 @ConfigurationProperties(prefix = "app")
 public class AppProperties {
-    private String ossEndpoint;
-    private String cookiePath;
-    private String cacheDir;
     private String baseDir;
-    private String storeDir;
-    private String indexDir;
 }

+ 0 - 167
web/src/main/java/cn/reghao/devops/web/sys/service/FileService.java

@@ -1,167 +0,0 @@
-package cn.reghao.devops.web.sys.service;
-
-import cn.reghao.devops.web.config.AppProperties;
-import cn.reghao.devops.web.sys.db.repository.DiskFileRepository;
-import cn.reghao.devops.web.sys.model.po.DiskFile;
-import cn.reghao.jutil.jdk.security.DigestUtil;
-import cn.reghao.jutil.web.ServletUtil;
-import org.apache.commons.io.FileUtils;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Sort;
-import org.springframework.stereotype.Service;
-import org.springframework.web.multipart.MultipartFile;
-
-import javax.annotation.PostConstruct;
-import javax.servlet.http.HttpServletResponse;
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.util.List;
-import java.util.UUID;
-
-/**
- * @author reghao
- * @date 2024-01-19 21:29:24
- */
-@Service
-public class FileService {
-    // 1MiB
-    private final int bufSize = 1024*1024;
-    private final AppProperties appProperties;
-    private final DiskFileRepository diskFileRepository;
-
-    public FileService(DiskFileRepository diskFileRepository, AppProperties appProperties) {
-        this.diskFileRepository = diskFileRepository;
-        this.appProperties = appProperties;
-    }
-
-    @PostConstruct
-    public void initLocalStore() throws IOException {
-        String storeDir = appProperties.getStoreDir();
-        File dir1 = new File(storeDir);
-        if (!dir1.exists()) {
-            FileUtils.forceMkdir(dir1);
-        }
-    }
-
-    public void getFile(String objectName) throws IOException {
-        DiskFile diskFile = diskFileRepository.findByObjectName(objectName);
-        if (diskFile == null) {
-            writeResponse(HttpServletResponse.SC_NOT_FOUND);
-            return;
-        }
-
-        String absolutePath = diskFile.getAbsolutePath();
-        String contentType = diskFile.getContentType();
-        long size = diskFile.getSize();
-        HttpServletResponse response = ServletUtil.getResponse();
-        response.setStatus(HttpServletResponse.SC_OK);
-        response.setContentType(contentType);
-        response.setContentLengthLong(size);
-
-        OutputStream outputStream = response.getOutputStream();
-        writeResponse(outputStream, absolutePath, 0, size);
-    }
-
-    private void writeResponse(int statusCode) throws IOException {
-        HttpServletResponse response = ServletUtil.getResponse();
-        response.setStatus(statusCode);
-        OutputStream outputStream = response.getOutputStream();
-        outputStream.flush();
-        outputStream.close();
-    }
-
-    private void writeResponse(OutputStream outputStream, String absolutePath, long start, long end) throws IOException {
-        RandomAccessFile raf = new RandomAccessFile(absolutePath, "r");
-        raf.seek(start);
-
-        long len = end-start+1;
-        if (len < bufSize) {
-            int len1 = (int) len;
-            byte[] buf1 = new byte[len1];
-            int readLen1 = raf.read(buf1, 0, len1);
-            outputStream.write(buf1, 0, readLen1);
-        } else {
-            byte[] buf = new byte[bufSize];
-            long totalRead = 0;
-            int readLen;
-            while ((readLen = raf.read(buf, 0, bufSize)) != -1) {
-                outputStream.write(buf, 0, readLen);
-                totalRead += readLen;
-
-                long left = len - totalRead;
-                if (left < bufSize) {
-                    int left1 = (int) left;
-                    byte[] buf1 = new byte[left1];
-                    int readLen1 = raf.read(buf1, 0, left1);
-                    outputStream.write(buf1, 0, readLen1);
-                    break;
-                }
-            }
-        }
-
-        outputStream.flush();
-        outputStream.close();
-        raf.close();
-    }
-
-    public String putFile(MultipartFile file) throws Exception {
-        long size = file.getSize();
-        String filename = file.getOriginalFilename();
-        String suffix = getSuffix(filename);
-
-        String objectId = UUID.randomUUID().toString().replace("-", "");;
-        String objectName;
-        if (suffix.isBlank()) {
-            objectName = String.format("file/%s", objectId);
-        } else {
-            objectName = String.format("file/%s%s", objectId, suffix);
-        }
-
-        String contentId = UUID.randomUUID().toString().replace("-", "");
-        File savedFile = saveFile(file.getInputStream(), contentId, suffix);
-        String contentType = Files.probeContentType(Path.of(savedFile.getAbsolutePath()));
-        String sha256sum = DigestUtil.sha256sum(savedFile.getAbsolutePath());
-
-        List<DiskFile> diskFiles = diskFileRepository.findBySha256sum(sha256sum);
-        DiskFile diskFile;
-        if (!diskFiles.isEmpty()) {
-            DiskFile existFile = diskFiles.get(0);
-            diskFile = new DiskFile(objectName, objectId, existFile, filename);
-            FileUtils.deleteQuietly(savedFile);
-        } else {
-            diskFile = new DiskFile(objectName, objectId, savedFile.getAbsolutePath(), sha256sum, filename, contentType, size);
-        }
-
-        diskFileRepository.save(diskFile);
-        return "/" + objectName;
-    }
-
-    private File saveFile(InputStream inputStream, String contentId, String suffix) throws IOException {
-        String absolutePath = String.format("%s/%s%s", appProperties.getStoreDir(), contentId, suffix);
-        File file = new File(absolutePath);
-        if (file.exists()) {
-            throw new IOException(absolutePath + " exist");
-        }
-
-        Files.copy(inputStream, Path.of(absolutePath), StandardCopyOption.REPLACE_EXISTING);
-        return file;
-    }
-
-    private String getSuffix(String filename) {
-        if (filename == null) {
-            return "";
-        }
-
-        int idx = filename.lastIndexOf(".");
-        return idx == -1 ? "" : filename.substring(idx);
-    }
-
-    public Page<DiskFile> getDiskFiles(int pageNumber) {
-        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
-        PageRequest pageRequest = PageRequest.of(pageNumber-1, 12, sort);
-        return diskFileRepository.findAll(pageRequest);
-    }
-}

+ 17 - 0
web/src/main/java/cn/reghao/devops/web/sys/service/SysMessageService.java

@@ -27,6 +27,23 @@ public class SysMessageService {
         }
     }
 
+    public String getUnreadCount() {
+        int unreadTotal = sysMessageRepository.countByUnread(true);
+        String unreadMessage;
+        if (unreadTotal > 9) {
+            unreadMessage = "9+";
+        } else {
+            unreadMessage = String.valueOf(unreadTotal);
+        }
+
+        return unreadMessage;
+    }
+
+    public void putSysMessage(String title, String content) {
+        SysMessage sysMessage = new SysMessage(title, content);
+        sysMessageRepository.save(sysMessage);
+    }
+
     public SysMessage getSysMessage(int id) {
         SysMessage sysMessage = sysMessageRepository.findById(id).orElse(null);
         if (sysMessage != null) {

+ 1 - 2
web/src/main/resources/application.yml

@@ -49,5 +49,4 @@ spring:
     servlet:
       content-type: text/html
 app:
-  baseDir: /opt/data/devopsdata
-  storeDir: ${app.basedir}/file
+  baseDir: /opt/data/devopsdata

+ 1 - 1
web/src/main/resources/templates/rbac/user/add.html

@@ -11,7 +11,7 @@
             </div>
         </div>
         <div class="layui-form-item">
-            <label class="layui-form-label required">用户名</label>
+            <label class="layui-form-label required">显示名</label>
             <div class="layui-input-inline">
                 <input class="layui-input" type="text" name="screenName" placeholder="请输入用户名">
             </div>

+ 0 - 40
web/src/main/resources/templates/rbac/user/edit.html

@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<html xmlns:th="http://www.thymeleaf.org">
-<head th:replace="/common/template :: header(~{::title},~{::link},~{::style})"></head>
-<body>
-<div class="layui-form timo-compile">
-    <form th:action="@{/api/rbac/user/modify}">
-        <input type="hidden" name="userId" th:value="${user.id}"/>
-        <div class="layui-form-item">
-            <label class="layui-form-label required">登录名</label>
-            <div class="layui-input-inline">
-                <input class="layui-input" type="text" name="username"  placeholder="请输入登录名" readonly="true" th:value="${user.username}">
-            </div>
-        </div>
-        <div class="layui-form-item">
-            <label class="layui-form-label required">用户名</label>
-            <div class="layui-input-inline">
-                <input class="layui-input" type="text" name="screenName" placeholder="请输入用户名" th:value="${user.screenName}">
-            </div>
-        </div>
-        <div class="layui-form-item">
-            <label class="layui-form-label">手机号码</label>
-            <div class="layui-input-inline">
-                <input class="layui-input" type="text" name="mobile" placeholder="请输入手机号码" th:value="${user.mobile}">
-            </div>
-        </div>
-        <div class="layui-form-item">
-            <label class="layui-form-label">邮箱</label>
-            <div class="layui-input-inline">
-                <input class="layui-input" type="text" name="email" placeholder="请输入邮箱" th:value="${user.email}">
-            </div>
-        </div>
-        <div class="layui-form-item timo-finally">
-            <button class="layui-btn ajax-submit"><i class="fa fa-check-circle"></i> 保存</button>
-            <button class="layui-btn btn-secondary close-popup"><i class="fa fa-times-circle"></i> 关闭</button>
-        </div>
-    </form>
-</div>
-<script th:replace="/common/template :: script"></script>
-</body>
-</html>

+ 11 - 11
web/src/main/resources/templates/rbac/user/index.html

@@ -43,39 +43,39 @@
                 <thead>
                 <tr>
                     <th data-field="username">登录名</th>
-                    <th data-field="screenName">用户名</th>
+                    <th data-field="screenName">显示名</th>
                     <th data-field="createDate">创建时间</th>
                     <th data-field="lastAccess">最近访问</th>
                     <th>状态</th>
                     <th>分配角色</th>
-                    <th>修改密码</th>
+                    <th>重置密码</th>
                     <th>操作</th>
                 </tr>
                 </thead>
                 <tbody>
                 <tr th:each="item:${list}">
                     <td th:text="${item.username}">登录名</td>
-                    <td th:text="${item.screenName}">用户名</td>
+                    <td th:text="${item.screenName}">显示名</td>
                     <td th:text="${item.createTime}">创建时间</td>
                     <td th:text="${item.lastAccess}">最近访问</td>
                     <td th:text="${item.status}">状态</td>
                     <td>
-                        <a class="open-popup" data-title="分配角色"
-                           th:attr="data-url=@{'/rbac/user/role/'+${item.userId}}" data-size="640,480"
+                        <a class="open-popup"
+                           th:attr="data-title=@{'为 ' + ${item.screenName} + ' 分配角色'},data-url=@{'/rbac/user/role/'+${item.userId}}" data-size="640,480"
                            href="#">设置</a>
                     </td>
                     <td>
-                        <a class="open-popup" data-title="修改密码"
-                           th:attr="data-url=@{'/rbac/user/passwd/'+${item.userId}}" data-size="640,480"
-                           href="#">修改</a>
+                        <a class="open-popup"
+                           th:attr="data-title=@{'重置 ' + ${item.screenName} + ' 的密码'},data-url=@{'/rbac/user/passwd/'+${item.userId}}" data-size="480,320"
+                           href="#">重置</a>
                     </td>
                     <td>
                         <a class="open-popup" data-title="详细信息" th:attr="data-url=@{'/rbac/user/detail/'+${item.userId}}"
                            data-size="640,480" href="#">详细</a>
-                        <a class="ajax-delete" th:attr="data-msg='确定要禁用帐号 '+ ${item.username}"
-                           th:href="@{'/api/rbac/user/' + ${item.userId}}">禁用</a>
+                        <a class="ajax-post" th:attr="data-msg='确定要禁用帐号 '+ ${item.username}"
+                           th:href="@{'/api/rbac/user/status/' + ${item.userId}}">禁用</a>
                         <a class="ajax-delete" th:attr="data-msg='确定要删除帐号 '+ ${item.username}"
-                           th:href="@{'/api/rbac/user/' + ${item.userId}}">删除</a>
+                           th:href="@{'/api/rbac/user/delete' + ${item.userId}}">删除</a>
                     </td>
                 </tr>
                 </tbody>

+ 11 - 8
web/src/main/resources/templates/rbac/user/userinfo.html

@@ -12,22 +12,25 @@
         <ul class="detail-info">
             <li>账号:<span th:text="${user.username}" th:title="${user.username}"></span></li>
             <li>昵称:<span th:text="${user.screenName}" th:title="${user.screenName}"></span></li>
-            <li>性别:<span th:text="${user.gender}"
-                         th:title="${user.gender}"></span></li>
+            <li>性别:
+                <span th:if="${user.gender} eq 0">女</span>
+                <span th:if="${user.gender} eq 1">男</span>
+                <span th:if="${user.gender} eq 2">未知</span>
+            </li>
             <li>电话:<span th:text="${user.mobile}" th:title="${user.mobile}"></span></li>
             <li>邮箱:<span th:text="${user.email}" th:title="${user.email}"></span></li>
         </ul>
     </div>
-    <form class="user-edit" th:action="@{/api/rbac/user/edit}">
+    <form class="user-edit" th:action="@{/api/rbac/user/update}">
         <input type="hidden" name="userId" th:value="${user.id}"/>
         <div class="layui-form-item">
-            <label class="layui-form-label">用户昵称</label>
+            <label class="layui-form-label">显示名</label>
             <div class="layui-input-inline">
                 <input class="layui-input" type="text" name="screenName" placeholder="请输入用户昵称" th:value="${user.screenName}">
             </div>
         </div>
         <div class="layui-form-item">
-            <label class="layui-form-label">电话号码</label>
+            <label class="layui-form-label">手机号</label>
             <div class="layui-input-inline">
                 <input class="layui-input" type="text" name="mobile" placeholder="请输入电话号码" th:value="${user.mobile}">
             </div>
@@ -41,9 +44,9 @@
         <div class="layui-form-item">
             <label class="layui-form-label">选择性别</label>
             <div class="layui-input-inline">
-                <input type="radio" name="gender" value="1" title="男" checked><div class="layui-unselect layui-form-radio layui-form-radioed"><i class="layui-anim layui-icon"></i><div>男</div></div>
-                <input type="radio" name="gender" value="2" title="女" th:checked="${user.gender} eq 2"><div class="layui-unselect layui-form-radio"><i class="layui-anim layui-icon"></i><div>女</div></div>
-                <input type="radio" name="gender" value="3" title="未知" th:checked="${user.gender} eq 3"><div class="layui-unselect layui-form-radio"><i class="layui-anim layui-icon"></i><div>未知</div></div>
+                <input type="radio" name="gender" value="0" title="女" checked><div class="layui-unselect layui-form-radio layui-form-radioed"><i class="layui-anim layui-icon"></i><div>男</div></div>
+                <input type="radio" name="gender" value="1" title="男" th:checked="${user.gender} eq 1"><div class="layui-unselect layui-form-radio"><i class="layui-anim layui-icon"></i><div>女</div></div>
+                <input type="radio" name="gender" value="2" title="未知" th:checked="${user.gender} eq 2"><div class="layui-unselect layui-form-radio"><i class="layui-anim layui-icon"></i><div>未知</div></div>
             </div>
         </div>
         <div class="layui-form-item timo-finally">

+ 2 - 2
web/src/main/resources/templates/rbac/user/editpasswd.html → web/src/main/resources/templates/rbac/user/userpasswd.html

@@ -6,7 +6,7 @@
 <div class="layui-form timo-compile">
     <form th:action="@{/api/rbac/user/passwd/update}">
         <div class="layui-form-item">
-            <label class="layui-form-label">原来密码</label>
+            <label class="layui-form-label">密码</label>
             <div class="layui-input-inline">
                 <input class="layui-input" type="password" name="original" placeholder="请输入原来密码">
             </div>
@@ -20,7 +20,7 @@
         <div class="layui-form-item">
             <label class="layui-form-label">确认密码</label>
             <div class="layui-input-inline">
-                <input class="layui-input" type="password" name="confirm" placeholder="再一次输入密码">
+                <input class="layui-input" type="password" name="confirm" placeholder="再一次输入密码">
             </div>
         </div>
         <div class="layui-form-item timo-finally">

+ 52 - 1
web/src/test/java/cn/reghao/devops/web/account/AccountTest.java

@@ -1,15 +1,29 @@
 package cn.reghao.devops.web.account;
 
 import cn.reghao.devops.web.WebApplication;
+import cn.reghao.devops.web.account.db.repository.MenuRepository;
+import cn.reghao.devops.web.account.db.repository.RoleRepository;
+import cn.reghao.devops.web.account.db.repository.UserRepository;
+import cn.reghao.devops.web.account.model.constant.RoleType;
+import cn.reghao.devops.web.account.model.dto.CreateAccountDto;
+import cn.reghao.devops.web.account.model.po.Menu;
+import cn.reghao.devops.web.account.model.po.Role;
+import cn.reghao.devops.web.account.model.po.User;
 import cn.reghao.devops.web.account.service.AccountService;
+import cn.reghao.jutil.jdk.security.RandomString;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.domain.PageRequest;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.junit4.SpringRunner;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
 /**
  * @author reghao
  * @date 2022-08-17 15:53:04
@@ -21,8 +35,45 @@ import org.springframework.test.context.junit4.SpringRunner;
 public class AccountTest {
     @Autowired
     AccountService accountService;
+    @Autowired
+    UserRepository userRepository;
+    @Autowired
+    RoleRepository roleRepository;
+    @Autowired
+    MenuRepository menuRepository;
+
+    @Test
+    public void initRole() {
+        List<Role> roleList = roleRepository.findAll();
+        if (roleList.isEmpty()) {
+            roleList = new ArrayList<>();
+            for (RoleType roleType : RoleType.values()) {
+                roleList.add(new Role(roleType.name(), roleType.getDesc()));
+            }
+            roleRepository.saveAll(roleList);
+
+            List<Menu> menus = menuRepository.findAll();
+            if (!menus.isEmpty()) {
+                Role role = roleRepository.findByName(RoleType.ROLE_ADMIN.name());
+                role.setMenus(new HashSet<>(menus));
+                roleRepository.save(role);
+            }
+        }
+    }
 
     @Test
-    public void createUserAccount() {
+    public void initAccount() {
+        List<User> list = userRepository.findAll(PageRequest.of(0, 1)).getContent();
+        if (list.isEmpty()) {
+            String username = "admin";
+            String password = RandomString.getString(10);
+            Role role = roleRepository.findByName(RoleType.ROLE_ADMIN.name());
+            if (role != null) {
+                int roleId = role.getId();
+                CreateAccountDto createAccountDto = new CreateAccountDto(username, password, roleId);
+                accountService.createAccount(createAccountDto);
+                log.info("初始化完成, 帐号和密码分别是 {} 和 {}", username, password);
+            }
+        }
     }
 }