first commit.
This commit is contained in:
27
plusone-system/plusone-system-application/pom.xml
Normal file
27
plusone-system/plusone-system-application/pom.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>plusone-system</artifactId>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>plusone-system-application</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-system-domain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-system-infrastructure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-application</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,61 @@
|
||||
package xyz.zhouxy.plusone.system.application.common.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.IWithOrderNumber;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
|
||||
|
||||
/**
|
||||
* 菜单工具类
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class MenuUtil {
|
||||
private MenuUtil() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建菜单树
|
||||
*
|
||||
* @param allMenus 菜单列表
|
||||
* @return 菜单树
|
||||
*/
|
||||
public static List<MenuViewObject> buildMenuTree(Collection<MenuViewObject> allMenus) {
|
||||
// 先排序,保证添加到 rootMenus 中的顺序,以及 addChild 添加的子菜单的顺序
|
||||
allMenus = allMenus.stream()
|
||||
.sorted(Comparator.comparing(IWithOrderNumber::getOrderNumber))
|
||||
.toList();
|
||||
|
||||
// 一级菜单
|
||||
List<MenuViewObject> rootMenus = new ArrayList<>();
|
||||
// key: 菜单 id; value: 菜单对象. 方便根据 id 查找相应对象。
|
||||
Map<Long, MenuViewObject> menuListMap = new HashMap<>();
|
||||
|
||||
for (var menu : allMenus) {
|
||||
// 添加 MENU_LIST 到 map 中,方便后面调用对象的方法
|
||||
if (menu.getType() == MenuType.MENU_LIST.ordinal()) {
|
||||
menuListMap.put(menu.getId(), menu);
|
||||
}
|
||||
// 一级菜单
|
||||
if (menu.getParentId() == 0) {
|
||||
rootMenus.add(menu);
|
||||
}
|
||||
}
|
||||
for (var menu : allMenus) {
|
||||
var parent = menuListMap.getOrDefault(menu.getParentId(), null);
|
||||
// 父菜单存在于 map 中,调用父菜单的 addChild 方法将当前菜单添加为父菜单的子菜单。
|
||||
if (parent != null) {
|
||||
parent.addChild(menu);
|
||||
}
|
||||
}
|
||||
|
||||
return rootMenus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package xyz.zhouxy.plusone.system.application.common.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
|
||||
public enum PrincipalType {
|
||||
EMAIL(RegexConsts.EMAIL),
|
||||
MOBILE_PHONE(RegexConsts.MOBILE_PHONE),
|
||||
USERNAME(RegexConsts.USERNAME)
|
||||
;
|
||||
|
||||
@Getter
|
||||
private final String regex;
|
||||
|
||||
PrincipalType(String regex) {
|
||||
this.regex = regex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package xyz.zhouxy.plusone.system.application.common.util;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import xyz.zhouxy.plusone.exception.InvalidInputException;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Principal;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 根据字面值,判断并生成 {@link Principal} 值对象。
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see Principal
|
||||
* @see Username
|
||||
* @see Email
|
||||
* @see MobilePhone
|
||||
* @see InvalidInputException
|
||||
*/
|
||||
public class PrincipalUtil {
|
||||
|
||||
private PrincipalUtil() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static PrincipalType getPrincipalType(@Nullable String principal) {
|
||||
if (principal == null) {
|
||||
throw new IllegalArgumentException("principal 不能为空");
|
||||
}
|
||||
PrincipalType[] principalTypes = PrincipalType.values();
|
||||
for (var principalType : principalTypes) {
|
||||
if (principal.matches(principalType.getRegex())) {
|
||||
return principalType;
|
||||
}
|
||||
}
|
||||
throw InvalidInputException.unsupportedPrincipalTypeException();
|
||||
}
|
||||
|
||||
public static Principal getPrincipal(@Nullable String principal) {
|
||||
PrincipalType principalType = getPrincipalType(principal);
|
||||
if (principalType == PrincipalType.EMAIL) {
|
||||
return Email.of(principal);
|
||||
}
|
||||
if (principalType == PrincipalType.MOBILE_PHONE) {
|
||||
return MobilePhone.of(principal);
|
||||
}
|
||||
if (principalType == PrincipalType.USERNAME) {
|
||||
return Username.of(principal);
|
||||
}
|
||||
throw InvalidInputException.unsupportedPrincipalTypeException();
|
||||
}
|
||||
|
||||
public static Principal getEmailOrMobilePhone(@Nullable String principal) {
|
||||
PrincipalType principalType = getPrincipalType(principal);
|
||||
if (principalType == PrincipalType.EMAIL) {
|
||||
return Email.of(principal);
|
||||
}
|
||||
if (principalType == PrincipalType.MOBILE_PHONE) {
|
||||
return MobilePhone.of(principal);
|
||||
}
|
||||
throw InvalidInputException.unsupportedPrincipalTypeException("输入邮箱地址或手机号");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package xyz.zhouxy.plusone.system.application.controller;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.service.AccountContextService;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* 账号查询本身相关信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("account")
|
||||
public class AccountContextController {
|
||||
|
||||
private final AccountContextService service;
|
||||
|
||||
public AccountContextController(AccountContextService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@GetMapping("info")
|
||||
public RestfulResult getAccountInfo() {
|
||||
adminAuthLogic.checkLogin();
|
||||
var result = service.getAccountInfo();
|
||||
return RestfulResult.success("查询成功", result);
|
||||
}
|
||||
|
||||
@GetMapping("menus")
|
||||
public RestfulResult getMenuTree() {
|
||||
adminAuthLogic.checkLogin();
|
||||
var result = service.getMenuTree();
|
||||
return RestfulResult.success("查询成功", result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package xyz.zhouxy.plusone.system.application.controller;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams;
|
||||
import xyz.zhouxy.plusone.system.application.service.AccountManagementService;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.CreateAccountCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.UpdateAccountCommand;
|
||||
import xyz.zhouxy.plusone.util.AssertResult;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* 账号管理
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("sys/account")
|
||||
public class AccountManagementController {
|
||||
|
||||
private final AccountManagementService service;
|
||||
|
||||
public AccountManagementController(AccountManagementService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public RestfulResult createAccount(@RequestBody @Valid CreateAccountCommand command) {
|
||||
adminAuthLogic.checkLogin();
|
||||
adminAuthLogic.checkPermission("sys-account-create");
|
||||
service.createAccount(command);
|
||||
return success();
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public RestfulResult deleteAccounts(@RequestBody List<Long> ids) {
|
||||
adminAuthLogic.checkLogin();
|
||||
adminAuthLogic.checkPermission("sys-account-delete");
|
||||
service.deleteAccounts(ids);
|
||||
return success();
|
||||
}
|
||||
|
||||
@PatchMapping("{id}")
|
||||
public RestfulResult updateAccountInfo(
|
||||
@PathVariable("id") Long id,
|
||||
@RequestBody @Valid UpdateAccountCommand command) {
|
||||
adminAuthLogic.checkLogin();
|
||||
adminAuthLogic.checkPermission("sys-account-update");
|
||||
service.updateAccountInfo(id, command);
|
||||
return success();
|
||||
}
|
||||
|
||||
@GetMapping("query")
|
||||
public RestfulResult queryAccountOverviewList(AccountQueryParams queryParams) {
|
||||
adminAuthLogic.checkLogin();
|
||||
adminAuthLogic.checkPermission("sys-account-list");
|
||||
var accountOverviewList = service.queryAccountOverviewList(queryParams);
|
||||
return success("查询成功", accountOverviewList);
|
||||
}
|
||||
|
||||
@GetMapping("{accountId}")
|
||||
public RestfulResult queryAccountDetails(@PathVariable("accountId") Long accountId) {
|
||||
adminAuthLogic.checkLogin();
|
||||
adminAuthLogic.checkPermission("sys-account-details");
|
||||
var accountDetails = service.queryAccountDetails(accountId);
|
||||
AssertResult.nonNull(accountDetails);
|
||||
return success("查询成功", accountDetails);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package xyz.zhouxy.plusone.system.application.controller;
|
||||
|
||||
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.service.AdminLoginService;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.LoginByOtpCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.LoginByPasswordCommand;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* Admin 账号登录
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("login")
|
||||
public class AdminLoginController {
|
||||
|
||||
private final AdminLoginService service;
|
||||
|
||||
public AdminLoginController(AdminLoginService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PostMapping("byPassword")
|
||||
public RestfulResult loginByPassword(@RequestBody LoginByPasswordCommand command) {
|
||||
var loginInfo = service.loginByPassword(command);
|
||||
return success("登录成功", loginInfo);
|
||||
}
|
||||
|
||||
@PostMapping("byOtp")
|
||||
public RestfulResult loginByOtp(@RequestBody LoginByOtpCommand command) {
|
||||
var loginInfo = service.loginByOtp(command);
|
||||
return success("登录成功", loginInfo);
|
||||
}
|
||||
|
||||
@GetMapping("sendOtp")
|
||||
public RestfulResult sendOtp(@RequestParam String principal) {
|
||||
service.sendOtp(principal);
|
||||
return success("发送成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package xyz.zhouxy.plusone.system.application.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.service.AdminLogoutService;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* Admin 账号登出
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("logout")
|
||||
public class AdminLogoutController {
|
||||
|
||||
private final AdminLogoutService service;
|
||||
|
||||
public AdminLogoutController(AdminLogoutService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public RestfulResult execute() {
|
||||
service.execute();
|
||||
return RestfulResult.success("注销成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package xyz.zhouxy.plusone.system.application.controller;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.query.params.DictQueryParams;
|
||||
import xyz.zhouxy.plusone.system.application.service.DictManagementService;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.CreateDictCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.UpdateDictCommand;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* 数据字典管理
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("sys/dict")
|
||||
public class DictManagementController {
|
||||
|
||||
private final DictManagementService service;
|
||||
|
||||
public DictManagementController(DictManagementService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public RestfulResult createDict(@RequestBody @Valid CreateDictCommand command) {
|
||||
adminAuthLogic.checkPermission("sys-dict-create");
|
||||
service.createDict(command);
|
||||
return success();
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public RestfulResult deleteDicts(@RequestBody List<Long> ids) {
|
||||
adminAuthLogic.checkPermission("sys-dict-delete");
|
||||
service.deleteDicts(ids);
|
||||
return success();
|
||||
}
|
||||
|
||||
@PatchMapping("{id}")
|
||||
public RestfulResult updateDict(
|
||||
@PathVariable("id") Long id,
|
||||
@RequestBody @Valid UpdateDictCommand command) {
|
||||
adminAuthLogic.checkPermission("sys-dict-update");
|
||||
service.updateDict(id, command);
|
||||
return success();
|
||||
}
|
||||
|
||||
@GetMapping("{dictId}")
|
||||
public RestfulResult findDictDetails(@PathVariable("dictId") Long dictId) {
|
||||
adminAuthLogic.checkPermission("sys-dict-details");
|
||||
var dictDetails = service.findDictDetails(dictId);
|
||||
return success("查询成功", dictDetails);
|
||||
}
|
||||
|
||||
@GetMapping("all")
|
||||
public RestfulResult loadAllDicts() {
|
||||
adminAuthLogic.checkPermissionAnd("sys-dict-list", "sys-dict-details");
|
||||
var dicts = service.loadAllDicts();
|
||||
return success("查询成功", dicts);
|
||||
}
|
||||
|
||||
@GetMapping("query")
|
||||
public RestfulResult queryDictOverviewList(@Valid DictQueryParams queryParams) {
|
||||
adminAuthLogic.checkPermission("sys-dict-list");
|
||||
var dicts = service.queryDictOverviewList(queryParams);
|
||||
return success("查询成功", dicts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package xyz.zhouxy.plusone.system.application.controller;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.service.MenuManagementService;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.CreateMenuCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.UpdateMenuCommand;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* 菜单管理
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("sys/menu")
|
||||
public class MenuManagementController {
|
||||
|
||||
private final MenuManagementService service;
|
||||
|
||||
public MenuManagementController(MenuManagementService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
// ==================== create ====================
|
||||
@PostMapping
|
||||
public RestfulResult createMenu(@RequestBody @Valid CreateMenuCommand command) {
|
||||
adminAuthLogic.checkPermission("sys-menu-create");
|
||||
service.createMenu(command);
|
||||
return success();
|
||||
}
|
||||
|
||||
// ==================== delete ====================
|
||||
@DeleteMapping("{id}")
|
||||
public RestfulResult deleteMenu(@PathVariable("id") Long id) {
|
||||
adminAuthLogic.checkPermission("sys-menu-delete");
|
||||
service.deleteMenu(id);
|
||||
return success();
|
||||
}
|
||||
|
||||
// ==================== update ====================
|
||||
@PatchMapping("{id}")
|
||||
public RestfulResult updateMenu(
|
||||
@PathVariable("id") Long id,
|
||||
@RequestBody @Valid UpdateMenuCommand command) {
|
||||
adminAuthLogic.checkPermission("sys-menu-update");
|
||||
service.updateMenu(id, command);
|
||||
return success();
|
||||
}
|
||||
|
||||
// ==================== query ====================
|
||||
@GetMapping("{id}")
|
||||
public RestfulResult findById(@PathVariable("id") Long id) {
|
||||
adminAuthLogic.checkPermission("sys-menu-details");
|
||||
var result = service.findById(id);
|
||||
return RestfulResult.success("查询成功", result);
|
||||
}
|
||||
|
||||
@GetMapping("queryByAccountId")
|
||||
public RestfulResult queryByAccountId(@RequestParam Long accountId) {
|
||||
adminAuthLogic.checkPermission("sys-menu-details");
|
||||
var result = service.queryByAccountId(accountId);
|
||||
return success("查询成功", result);
|
||||
}
|
||||
|
||||
@GetMapping("queryByRoleId")
|
||||
public RestfulResult queryByRoleId(@RequestParam Long roleId) {
|
||||
adminAuthLogic.checkPermission("sys-menu-details");
|
||||
var result = service.queryByRoleId(roleId);
|
||||
return success("查询成功", result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package xyz.zhouxy.plusone.system.application.controller;
|
||||
|
||||
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.service.RegisterAccountService;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.RegisterAccountCommand;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* 注册账号服务
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("register")
|
||||
public class RegisterAccountController {
|
||||
|
||||
private final RegisterAccountService service;
|
||||
|
||||
public RegisterAccountController(RegisterAccountService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public RestfulResult registerAccount(@RequestBody RegisterAccountCommand command) {
|
||||
service.registerAccount(command);
|
||||
return success("注册成功");
|
||||
}
|
||||
|
||||
@GetMapping("sendCode")
|
||||
public RestfulResult sendCode(@RequestParam String principal) {
|
||||
service.sendCode(principal);
|
||||
return success("发送成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package xyz.zhouxy.plusone.system.application.controller;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.query.params.RoleQueryParams;
|
||||
import xyz.zhouxy.plusone.system.application.service.RoleManagementService;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.CreateRoleCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.UpdateRoleCommand;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* 角色管理服务
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("sys/role")
|
||||
public class RoleManagementController {
|
||||
|
||||
private final RoleManagementService service;
|
||||
|
||||
public RoleManagementController(RoleManagementService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public RestfulResult createRole(@RequestBody @Valid CreateRoleCommand command) {
|
||||
adminAuthLogic.checkPermission("sys-role-create");
|
||||
service.createRole(command);
|
||||
return RestfulResult.success();
|
||||
}
|
||||
|
||||
@PatchMapping
|
||||
public RestfulResult updateRole(@RequestBody @Valid UpdateRoleCommand command) {
|
||||
adminAuthLogic.checkPermission("sys-role-update");
|
||||
service.updateRole(command);
|
||||
return RestfulResult.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
public RestfulResult delete(@PathVariable("id") Long id) {
|
||||
adminAuthLogic.checkPermission("sys-role-delete");
|
||||
service.delete(id);
|
||||
return RestfulResult.success();
|
||||
}
|
||||
|
||||
@GetMapping("exists")
|
||||
public RestfulResult exists(@RequestParam("id") Long id) {
|
||||
adminAuthLogic.checkPermissionOr("sys-role-list", "sys-role-details");
|
||||
var isExists = service.exists(id);
|
||||
return RestfulResult.success(isExists ? "存在" : "不存在", isExists);
|
||||
}
|
||||
|
||||
@GetMapping("{id}")
|
||||
public RestfulResult findById(@PathVariable("id") Long id) {
|
||||
adminAuthLogic.checkPermission("sys-role-details");
|
||||
var result = service.findById(id);
|
||||
return RestfulResult.success("查询成功", result);
|
||||
}
|
||||
|
||||
@GetMapping("query")
|
||||
public RestfulResult query(RoleQueryParams params) {
|
||||
adminAuthLogic.checkPermission("sys-role-list");
|
||||
var result = service.query(params);
|
||||
return RestfulResult.success("查询成功", result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package xyz.zhouxy.plusone.system.application.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import xyz.zhouxy.plusone.exception.PlusoneException;
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public class AccountLoginException extends PlusoneException {
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = -3040996790739138556L;
|
||||
|
||||
private static final int DEFAULT_ERR_CODE = 4030000;
|
||||
|
||||
private AccountLoginException() {
|
||||
super(DEFAULT_ERR_CODE, "用户登录异常");
|
||||
}
|
||||
|
||||
private AccountLoginException(int code, String message) {
|
||||
super(code, message);
|
||||
}
|
||||
|
||||
public static AccountLoginException accountNotExistException() {
|
||||
return new AccountLoginException(4030101, "用户账户不存在");
|
||||
}
|
||||
|
||||
public static AccountLoginException otpErrorException() {
|
||||
return new AccountLoginException(4030501, "验证码错误");
|
||||
}
|
||||
|
||||
public static AccountLoginException otpNotExistsException() {
|
||||
return new AccountLoginException(4030502, "验证码不存在或已过期");
|
||||
}
|
||||
|
||||
public static AccountLoginException passwordErrorException() {
|
||||
return new AccountLoginException(4030200, "用户密码错误");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package xyz.zhouxy.plusone.system.application.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import xyz.zhouxy.plusone.exception.PlusoneException;
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public class AccountRegisterException extends PlusoneException {
|
||||
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = 7580245181633370195L;
|
||||
|
||||
public AccountRegisterException() {
|
||||
this(4020000, "用户注册错误");
|
||||
}
|
||||
|
||||
public AccountRegisterException(String message) {
|
||||
this(4020000, message);
|
||||
}
|
||||
|
||||
public AccountRegisterException(Throwable cause) {
|
||||
super(4020000, cause);
|
||||
}
|
||||
|
||||
public AccountRegisterException(int code, String message) {
|
||||
super(code, message);
|
||||
}
|
||||
|
||||
public AccountRegisterException(int code, Throwable cause) {
|
||||
super(code, cause);
|
||||
}
|
||||
|
||||
public static AccountRegisterException emailOrMobilePhoneRequiredException() {
|
||||
return new AccountRegisterException(4020300, "邮箱和手机号应至少绑定一个");
|
||||
}
|
||||
|
||||
public static AccountRegisterException usernameAlreadyExists(String username) {
|
||||
return new AccountRegisterException(4020400, String.format("用户名 %s 已存在", username));
|
||||
}
|
||||
|
||||
public static AccountRegisterException emailAlreadyExists(String value) {
|
||||
return new AccountRegisterException(4020500, String.format("邮箱 %s 已存在", value));
|
||||
}
|
||||
|
||||
public static AccountRegisterException mobilePhoneAlreadyExists(String value) {
|
||||
return new AccountRegisterException(4020600, String.format("手机号 %s 已存在", value));
|
||||
}
|
||||
|
||||
public static AccountRegisterException codeErrorException() {
|
||||
return new AccountRegisterException(4020701, "校验码错误");
|
||||
}
|
||||
|
||||
public static AccountRegisterException codeNotExistsException() {
|
||||
return new AccountRegisterException(4020702, "校验码不存在或已过期");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package xyz.zhouxy.plusone.system.application.exception;
|
||||
|
||||
import xyz.zhouxy.plusone.exception.InvalidInputException;
|
||||
|
||||
public class UnsupportedMenuTypeException extends InvalidInputException {
|
||||
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = -769169844015637730L;
|
||||
|
||||
public UnsupportedMenuTypeException() {
|
||||
this("不支持的菜单类型");
|
||||
}
|
||||
|
||||
public UnsupportedMenuTypeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package xyz.zhouxy.plusone.system.application.exception.handler;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import cn.dev33.satoken.exception.DisableServiceException;
|
||||
import cn.dev33.satoken.exception.SameTokenInvalidException;
|
||||
import cn.dev33.satoken.exception.NotBasicAuthException;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
import cn.dev33.satoken.exception.NotSafeException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler;
|
||||
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||
|
||||
/**
|
||||
* Sa-Token 异常处理器
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class SaTokenExceptionHandler extends BaseExceptionHandler {
|
||||
|
||||
public SaTokenExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
|
||||
super(exceptionInfoHolder);
|
||||
set(NotPermissionException.class, 4030103, "会话未能通过权限认证", HttpStatus.FORBIDDEN);
|
||||
set(NotRoleException.class, 4030103, "会话未能通过角色认证", HttpStatus.FORBIDDEN);
|
||||
set(DisableServiceException.class, 4030202, "账号指定服务已被封禁", HttpStatus.FORBIDDEN);
|
||||
set(SameTokenInvalidException.class, 4030400, "提供的 Same-Token 无效", HttpStatus.UNAUTHORIZED);
|
||||
set(NotBasicAuthException.class, 4030000, "会话未能通过 Http Basic 认证", HttpStatus.UNAUTHORIZED);
|
||||
set(NotSafeException.class, 4020300, "会话未能通过二级认证", HttpStatus.UNAUTHORIZED);
|
||||
set(NotLoginException.class,
|
||||
4020400,
|
||||
e -> switch (((NotLoginException) e).getType()) {
|
||||
case NotLoginException.NOT_TOKEN -> "未提供 Token";
|
||||
case NotLoginException.INVALID_TOKEN -> "Token 无效";
|
||||
case NotLoginException.TOKEN_TIMEOUT -> "Token 已过期";
|
||||
case NotLoginException.BE_REPLACED -> "Token 已被顶下线";
|
||||
case NotLoginException.KICK_OUT -> "Token 已被踢下线";
|
||||
default -> "当前会话未登录";
|
||||
},
|
||||
HttpStatus.UNAUTHORIZED);
|
||||
set(SaTokenException.class, 4020300, "未通过身份认证或权限认证", HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@ExceptionHandler(SaTokenException.class)
|
||||
public ResponseEntity<RestfulResult> handleSaTokenException(SaTokenException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return buildExceptionResponse(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package xyz.zhouxy.plusone.system.application.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.AccountDetails;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.AccountOverview;
|
||||
import xyz.zhouxy.plusone.util.PageDTO;
|
||||
|
||||
/**
|
||||
* 账号信息查询器
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Mapper
|
||||
public interface AccountQueries {
|
||||
|
||||
default PageDTO<AccountOverview> queryAccountOverviewPage(AccountQueryParams queryParams) {
|
||||
List<AccountOverview> content = queryAccountOverview(queryParams);
|
||||
long total = count(queryParams);
|
||||
return PageDTO.of(content, total);
|
||||
}
|
||||
|
||||
List<AccountOverview> queryAccountOverview(AccountQueryParams queryParams);
|
||||
|
||||
long count(AccountQueryParams queryParams);
|
||||
|
||||
AccountDetails queryAccountDetails(Long accountId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package xyz.zhouxy.plusone.system.application.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import xyz.zhouxy.plusone.sql.SQL;
|
||||
import xyz.zhouxy.plusone.system.application.query.params.DictQueryParams;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.DictOverview;
|
||||
|
||||
/**
|
||||
* 数据字典查询器
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Component
|
||||
public class DictQueries {
|
||||
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
public DictQueries(NamedParameterJdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
public List<DictOverview> queryDictOverviewList(DictQueryParams queryParams) {
|
||||
String sql = new SQL()
|
||||
.SELECT("id", "dict_type", "dict_label",
|
||||
"created_by", "create_time", "updated_by", "update_time", "count")
|
||||
.FROM("view_sys_dict_overview")
|
||||
.WHERE_IF(queryParams.getDictType() != null, "dict_type LIKE '%:dictType%'")
|
||||
.WHERE_IF(queryParams.getDictLabel() != null, "dict_label LIKE '%:dictLabel%'")
|
||||
.toString();
|
||||
return this.jdbcTemplate
|
||||
.query(sql, new BeanPropertySqlParameterSource(queryParams), new BeanPropertyRowMapper<>(DictOverview.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package xyz.zhouxy.plusone.system.application.query;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public interface PermissionQueries {
|
||||
// TODO【添加】 权限信息查询器
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package xyz.zhouxy.plusone.system.application.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import xyz.zhouxy.plusone.sql.SQL;
|
||||
import xyz.zhouxy.plusone.system.application.query.params.RoleQueryParams;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.RoleOverview;
|
||||
|
||||
/**
|
||||
* 角色信息查询器
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Repository
|
||||
public class RoleQueries {
|
||||
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
|
||||
public RoleQueries(NamedParameterJdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 Role
|
||||
* <p>
|
||||
* <b> !!!注意:此方法内存在字符串拼接,勿在循环内使用。</b>
|
||||
* </p>
|
||||
*
|
||||
* @param params 查询参数
|
||||
* @return 查询结果
|
||||
*/
|
||||
public List<RoleOverview> query(RoleQueryParams params) {
|
||||
String b = new SQL()
|
||||
.SELECT("id")
|
||||
.FROM("sys_role")
|
||||
.WHERE_IF_NOT_NULL(params.getId(), "id = :id")
|
||||
.WHERE_IF_NOT_NULL(params.getName(), "name = :name")
|
||||
.WHERE_IF_NOT_NULL(params.getIdentifier(), "identifier = :identifier")
|
||||
.WHERE_IF_NOT_NULL(params.getStatus(), "status = :status")
|
||||
.WHERE_IF_NOT_NULL(params.getCreateTimeStart(), "create_time >= :createTimeStart")
|
||||
.WHERE_IF_NOT_NULL(params.getCreateTimeEnd(), "create_time < :createTimeEnd")
|
||||
.WHERE_IF_NOT_NULL(params.getUpdateTimeStart(), "update_time >= :updateTimeStart")
|
||||
.WHERE_IF_NOT_NULL(params.getUpdateTimeEnd(), "update_time < :updateTimeEnd")
|
||||
.LIMIT(params.getSize())
|
||||
.OFFSET(params.getOffset())
|
||||
.toString();
|
||||
var sql = """
|
||||
SELECT a.id AS id, a.name AS name, a.identifier AS identifier, a.status AS status
|
||||
FROM sys_role AS a, (
|
||||
""" + b + """
|
||||
) AS b
|
||||
WHERE a.id = b.id
|
||||
""";
|
||||
return this.jdbcTemplate
|
||||
.query(sql, new BeanPropertySqlParameterSource(params),
|
||||
new BeanPropertyRowMapper<>(RoleOverview.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package xyz.zhouxy.plusone.system.application.query.params;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.util.PagingAndSortingQueryParams;
|
||||
|
||||
/**
|
||||
* 账号信息查询参数
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@ToString
|
||||
public class AccountQueryParams extends PagingAndSortingQueryParams {
|
||||
|
||||
public AccountQueryParams() {
|
||||
super("id",
|
||||
"username",
|
||||
"email",
|
||||
"mobile_phone",
|
||||
"status",
|
||||
"nickname",
|
||||
"sex",
|
||||
"created_by",
|
||||
"create_time",
|
||||
"updated_by",
|
||||
"update_time");
|
||||
}
|
||||
|
||||
// TODO【添加】 注解参数校验
|
||||
private @Getter @Setter Long id;
|
||||
private @Getter @Setter String username;
|
||||
private @Getter @Setter String email;
|
||||
private @Getter @Setter String mobilePhone;
|
||||
private @Getter @Setter Integer status;
|
||||
private @Getter @Setter String nickname;
|
||||
private @Getter @Setter Integer sex;
|
||||
private @Getter @Setter Long createdBy;
|
||||
private @Getter @Setter LocalDate createTimeStart;
|
||||
private @Getter LocalDate createTimeEnd;
|
||||
private @Getter @Setter Long updatedBy;
|
||||
private @Getter @Setter LocalDate updateTimeStart;
|
||||
private @Getter LocalDate updateTimeEnd;
|
||||
private @Getter @Setter Long roleId;
|
||||
|
||||
public void setCreateTimeEnd(LocalDate createTimeEnd) {
|
||||
this.createTimeEnd = createTimeEnd.plusDays(1);
|
||||
}
|
||||
|
||||
public void setUpdateTimeEnd(LocalDate updateTimeEnd) {
|
||||
this.updateTimeEnd = updateTimeEnd.plusDays(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package xyz.zhouxy.plusone.system.application.query.params;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import xyz.zhouxy.plusone.util.PagingAndSortingQueryParams;
|
||||
|
||||
/**
|
||||
* 数据字典查询参数
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
@ToString(callSuper = true)
|
||||
public class DictQueryParams extends PagingAndSortingQueryParams {
|
||||
String dictType;
|
||||
String dictLabel;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package xyz.zhouxy.plusone.system.application.query.params;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.util.PagingAndSortingQueryParams;
|
||||
|
||||
/**
|
||||
* 角色信息查询参数
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@ToString(callSuper = true)
|
||||
public class RoleQueryParams extends PagingAndSortingQueryParams {
|
||||
private @Getter @Setter Long id;
|
||||
private @Getter @Setter String name;
|
||||
private @Getter @Setter String identifier;
|
||||
private @Getter @Setter Integer status;
|
||||
|
||||
private @Getter @Setter LocalDate createTimeStart;
|
||||
private @Getter LocalDate createTimeEnd;
|
||||
private @Getter @Setter LocalDate updateTimeStart;
|
||||
private @Getter LocalDate updateTimeEnd;
|
||||
|
||||
public void setCreateTimeEnd(LocalDate createTimeEnd) {
|
||||
this.createTimeEnd = createTimeEnd.plusDays(1);
|
||||
}
|
||||
|
||||
public void setUpdateTimeEnd(LocalDate updateTimeEnd) {
|
||||
this.updateTimeEnd = updateTimeEnd.plusDays(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package xyz.zhouxy.plusone.system.application.query.result;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 账号详细信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class AccountDetails {
|
||||
Long id;
|
||||
String username;
|
||||
String email;
|
||||
String mobilePhone;
|
||||
Integer status;
|
||||
Set<RoleOverview> roles;
|
||||
String nickname;
|
||||
String avatar;
|
||||
Integer sex;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package xyz.zhouxy.plusone.system.application.query.result;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 账号概述信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public class AccountOverview {
|
||||
Long id;
|
||||
String username;
|
||||
String email;
|
||||
String mobilePhone;
|
||||
Integer status;
|
||||
Set<String> roles;
|
||||
String nickname;
|
||||
String avatar;
|
||||
Integer sex;
|
||||
Long createdBy;
|
||||
LocalDateTime createTime;
|
||||
Long updatedBy;
|
||||
LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package xyz.zhouxy.plusone.system.application.query.result;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 数据字典概述信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class DictOverview {
|
||||
Long id;
|
||||
String dictType;
|
||||
String dictLabel;
|
||||
Integer count;
|
||||
Long createdBy;
|
||||
LocalDateTime createTime;
|
||||
Long updatedBy;
|
||||
LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package xyz.zhouxy.plusone.system.application.query.result;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 登录结果
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@ToString
|
||||
public class LoginInfoViewObject {
|
||||
private String token;
|
||||
private AccountDetails account;
|
||||
|
||||
public static LoginInfoViewObject of(String token, AccountDetails accountDetails) {
|
||||
return new LoginInfoViewObject(token, accountDetails);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package xyz.zhouxy.plusone.system.application.query.result;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import xyz.zhouxy.plusone.domain.IWithOrderNumber;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Action;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
|
||||
|
||||
/**
|
||||
* 菜单信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MenuViewObject implements IWithOrderNumber {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
Integer type;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
String typeName;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
Long id;
|
||||
@Getter
|
||||
@Setter
|
||||
Long parentId;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
String name;
|
||||
// 若 type 为 MENU_ITEM 且 path 以 http:// 或 https:// 开头则被识别为外链
|
||||
@Getter
|
||||
@Setter
|
||||
String path;
|
||||
@Getter
|
||||
@Setter
|
||||
String title;
|
||||
@Getter
|
||||
@Setter
|
||||
String icon;
|
||||
@Getter
|
||||
@Setter
|
||||
boolean hidden;
|
||||
@Getter
|
||||
@Setter
|
||||
int orderNumber;
|
||||
@Getter
|
||||
@Setter
|
||||
Integer status;
|
||||
@Getter
|
||||
@Setter
|
||||
String remarks;
|
||||
|
||||
// MENU_ITEM
|
||||
@Getter
|
||||
@Setter
|
||||
String component;
|
||||
@Getter
|
||||
@Setter
|
||||
Boolean cache;
|
||||
@Getter
|
||||
@Setter
|
||||
String resource;
|
||||
@Getter
|
||||
@Setter
|
||||
List<Action> actions;
|
||||
|
||||
// MENU_LIST
|
||||
List<MenuViewObject> children;
|
||||
|
||||
public void addChild(MenuViewObject child) {
|
||||
if (this.children == null) {
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
this.children.add(child);
|
||||
}
|
||||
|
||||
public void addChildren(Collection<MenuViewObject> children) {
|
||||
if (this.children == null) {
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
this.children.addAll(children);
|
||||
}
|
||||
|
||||
public static MenuViewObject of(Menu menu) {
|
||||
var viewObject = new MenuViewObject();
|
||||
viewObject.type = menu.getType().ordinal();
|
||||
viewObject.typeName = menu.getType().name();
|
||||
viewObject.id = menu.getId().orElseThrow();
|
||||
viewObject.parentId = menu.getParentId();
|
||||
viewObject.name = menu.getName();
|
||||
viewObject.path = menu.getPath();
|
||||
viewObject.title = menu.getTitle();
|
||||
viewObject.icon = menu.getIcon();
|
||||
viewObject.hidden = menu.isHidden();
|
||||
viewObject.orderNumber = menu.getOrderNumber();
|
||||
viewObject.status = menu.getStatus().getValue();
|
||||
viewObject.remarks = menu.getRemarks();
|
||||
if (viewObject.type == MenuType.MENU_ITEM.ordinal()) {
|
||||
viewObject.component = menu.getComponent();
|
||||
viewObject.cache = menu.getCache();
|
||||
viewObject.resource = menu.getResource();
|
||||
viewObject.actions = menu.getActions();
|
||||
}
|
||||
return viewObject;
|
||||
}
|
||||
|
||||
public List<MenuViewObject> getChildren() {
|
||||
return Objects.nonNull(this.children)
|
||||
? this.children
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(IWithOrderNumber::getOrderNumber))
|
||||
.toList()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package xyz.zhouxy.plusone.system.application.query.result;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 角色信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleOverview {
|
||||
// 角色 id
|
||||
Long id;
|
||||
// 角色名
|
||||
String name;
|
||||
// 标识符(安全框架校验权限所用)
|
||||
String identifier;
|
||||
Integer status;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.query.AccountQueries;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.AccountDetails;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject;
|
||||
|
||||
/**
|
||||
* 账号查询本身相关信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
public class AccountContextService {
|
||||
|
||||
private final AccountQueries accountQueries;
|
||||
private final MenuManagementService menuManagementService;
|
||||
|
||||
public AccountContextService(AccountQueries accountQueries, MenuManagementService menuManagementService) {
|
||||
this.accountQueries = accountQueries;
|
||||
this.menuManagementService = menuManagementService;
|
||||
}
|
||||
|
||||
public AccountDetails getAccountInfo() {
|
||||
long accountId = adminAuthLogic.getLoginIdAsLong();
|
||||
return accountQueries.queryAccountDetails(accountId);
|
||||
}
|
||||
|
||||
public List<MenuViewObject> getMenuTree() {
|
||||
long accountId = adminAuthLogic.getLoginIdAsLong();
|
||||
return menuManagementService.queryByAccountId(accountId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.exception.AccountRegisterException;
|
||||
import xyz.zhouxy.plusone.system.application.query.AccountQueries;
|
||||
import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.AccountDetails;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.AccountOverview;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.CreateAccountCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.UpdateAccountCommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.AccountInfo;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
import xyz.zhouxy.plusone.util.AssertResult;
|
||||
import xyz.zhouxy.plusone.util.PageDTO;
|
||||
|
||||
/**
|
||||
* 账号管理
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class AccountManagementService {
|
||||
|
||||
private final AccountRepository accountRepository;
|
||||
private final AccountQueries accountQueries;
|
||||
|
||||
public AccountManagementService(AccountRepository accountRepository, AccountQueries accountQueries) {
|
||||
this.accountRepository = accountRepository;
|
||||
this.accountQueries = accountQueries;
|
||||
}
|
||||
|
||||
public void createAccount(@Valid CreateAccountCommand command) {
|
||||
String username = command.getUsername();
|
||||
if (accountRepository.existsUsername(Username.of(username))) {
|
||||
throw AccountRegisterException.usernameAlreadyExists(username);
|
||||
}
|
||||
String email = command.getEmail();
|
||||
if (StringUtils.hasText(email) && accountRepository.existsEmail(Email.of(email))) {
|
||||
throw AccountRegisterException.emailAlreadyExists(email);
|
||||
}
|
||||
String mobilePhone = command.getMobilePhone();
|
||||
if (StringUtils.hasText(mobilePhone) && accountRepository.existsMobilePhone(MobilePhone.of(mobilePhone))) {
|
||||
throw AccountRegisterException.mobilePhoneAlreadyExists(mobilePhone);
|
||||
}
|
||||
Account account = Account.newInstance(
|
||||
username,
|
||||
email,
|
||||
mobilePhone,
|
||||
command.getPassword(),
|
||||
command.getPasswordConfirmation(),
|
||||
command.getStatus(),
|
||||
command.getRoleRefs(),
|
||||
AccountInfo.of(command.getNickname(), command.getAvatar(), command.getSex()),
|
||||
adminAuthLogic.getLoginIdAsLong());
|
||||
accountRepository.save(account);
|
||||
}
|
||||
|
||||
public void deleteAccounts(List<Long> ids) {
|
||||
Account accountToDelete;
|
||||
for (var id : ids) {
|
||||
accountToDelete = accountRepository.find(id);
|
||||
AssertResult.nonNull(accountToDelete);
|
||||
accountRepository.delete(accountToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateAccountInfo(Long id, @Valid UpdateAccountCommand command) {
|
||||
Assert.isTrue(Objects.equals(id, command.getId()), "参数错误: id 不匹配");
|
||||
Account account = accountRepository.find(id);
|
||||
AssertResult.nonNull(account, "该账号不存在");
|
||||
account.setAccountInfo(command.getNickname(), command.getAvatar(), command.getSex());
|
||||
account.setUpdatedBy(adminAuthLogic.getLoginIdAsLong());
|
||||
accountRepository.save(account);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public PageDTO<AccountOverview> queryAccountOverviewList(AccountQueryParams queryParams) {
|
||||
return accountQueries.queryAccountOverviewPage(queryParams);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public AccountDetails queryAccountDetails(@PathVariable("accountId") Long accountId) {
|
||||
var accountDetails = accountQueries.queryAccountDetails(accountId);
|
||||
AssertResult.nonNull(accountDetails);
|
||||
return accountDetails;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import xyz.zhouxy.plusone.exception.InvalidInputException;
|
||||
import xyz.zhouxy.plusone.system.application.common.util.PrincipalType;
|
||||
import xyz.zhouxy.plusone.system.application.common.util.PrincipalUtil;
|
||||
import xyz.zhouxy.plusone.system.application.exception.AccountLoginException;
|
||||
import xyz.zhouxy.plusone.system.application.query.AccountQueries;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.LoginInfoViewObject;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.LoginByOtpCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.LoginByPasswordCommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Principal;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
import xyz.zhouxy.plusone.validator.ValidateDto;
|
||||
|
||||
/**
|
||||
* Admin 账号登录
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class AdminLoginService {
|
||||
|
||||
private final AccountRepository accountRepository;
|
||||
private final AccountQueries accountQueries;
|
||||
private final MailAndSmsVerifyService mailAndSmsVerifyService;
|
||||
|
||||
AdminLoginService(AccountRepository accountRepository, AccountQueries accountQueries,
|
||||
MailAndSmsVerifyService mailAndSmsVerifyService) {
|
||||
this.accountRepository = accountRepository;
|
||||
this.accountQueries = accountQueries;
|
||||
this.mailAndSmsVerifyService = mailAndSmsVerifyService;
|
||||
}
|
||||
|
||||
@ValidateDto
|
||||
public LoginInfoViewObject loginByPassword(LoginByPasswordCommand command) {
|
||||
Principal principal = PrincipalUtil.getPrincipal(command.getPrincipal());
|
||||
Account account;
|
||||
if (principal instanceof Email) {
|
||||
account = accountRepository.findByEmail((Email) principal);
|
||||
} else if (principal instanceof MobilePhone) {
|
||||
account = accountRepository.findByMobilePhone((MobilePhone) principal);
|
||||
} else {
|
||||
account = accountRepository.findByUsername((Username) principal);
|
||||
}
|
||||
|
||||
if (account == null) {
|
||||
throw AccountLoginException.accountNotExistException();
|
||||
}
|
||||
@SuppressWarnings("null")
|
||||
boolean isPasswordCorrect = account.checkPassword(command.getPassword());
|
||||
if (!isPasswordCorrect) {
|
||||
throw AccountLoginException.passwordErrorException();
|
||||
}
|
||||
adminAuthLogic.login(account.getId().orElseThrow(), command.isRememberMe());
|
||||
|
||||
var accountDetails = accountQueries.queryAccountDetails(account.getId().orElseThrow());
|
||||
return LoginInfoViewObject.of(adminAuthLogic.getTokenValue(), accountDetails);
|
||||
}
|
||||
|
||||
@ValidateDto
|
||||
public LoginInfoViewObject loginByOtp(LoginByOtpCommand command) {
|
||||
String principal = command.getPrincipal();
|
||||
PrincipalType principalType = PrincipalUtil.getPrincipalType(principal);
|
||||
String otp = command.getOtp();
|
||||
boolean rememberMe = command.isRememberMe();
|
||||
|
||||
Account account;
|
||||
if (principalType == PrincipalType.EMAIL) {
|
||||
account = accountRepository.findByEmail(Email.of(principal));
|
||||
} else if (principalType == PrincipalType.MOBILE_PHONE) {
|
||||
account = accountRepository.findByMobilePhone(MobilePhone.of(principal));
|
||||
} else {
|
||||
throw InvalidInputException.unsupportedPrincipalTypeException("输入邮箱地址或手机号");
|
||||
}
|
||||
|
||||
if (account == null) {
|
||||
throw AccountLoginException.accountNotExistException();
|
||||
}
|
||||
mailAndSmsVerifyService.checkOtp(principal, otp);
|
||||
adminAuthLogic.login(account.getId().orElseThrow(), rememberMe);
|
||||
|
||||
var accountDetails = accountQueries.queryAccountDetails(account.getId().orElseThrow());
|
||||
return LoginInfoViewObject.of(adminAuthLogic.getTokenValue(), accountDetails);
|
||||
}
|
||||
|
||||
public void sendOtp(String principal) {
|
||||
Principal emailOrMobilePhone = PrincipalUtil.getEmailOrMobilePhone(principal);
|
||||
if (emailOrMobilePhone instanceof Email) {
|
||||
mailAndSmsVerifyService.sendOtpToEmail((Email) emailOrMobilePhone);
|
||||
} else {
|
||||
mailAndSmsVerifyService.sendOtpToMobilePhone((MobilePhone) emailOrMobilePhone);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Admin 账号登出
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
public class AdminLogoutService {
|
||||
|
||||
public void execute() {
|
||||
adminAuthLogic.checkLogin();
|
||||
adminAuthLogic.logout();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Action;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.MenuRepository;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.ActionRef;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.Role;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.RoleRepository;
|
||||
|
||||
@Service
|
||||
public class AuthService implements StpInterface {
|
||||
|
||||
private RoleRepository roleRepository;
|
||||
private MenuRepository menuRepository;
|
||||
|
||||
public AuthService(RoleRepository roleRepository, MenuRepository menuRepository) {
|
||||
this.roleRepository = roleRepository;
|
||||
this.menuRepository = menuRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
Collection<Role> roles = getRoleList(loginId);
|
||||
Set<Long> permissionIds = new HashSet<>();
|
||||
roles.forEach(role -> permissionIds.addAll(
|
||||
role.getPermissions()
|
||||
.stream()
|
||||
.map(ActionRef::actionId)
|
||||
.toList()));
|
||||
List<String> permValList = menuRepository.findPermissionsByIdIn(permissionIds)
|
||||
.stream().map(Action::value).toList();
|
||||
return permValList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
SaSession session = switch (loginType) {
|
||||
case ADMIN_LOGIN_TYPE -> adminAuthLogic.getSessionByLoginId(loginId);
|
||||
case USER_LOGIN_TYPE -> userAuthLogic.getSessionByLoginId(loginId);
|
||||
default -> StpUtil.getSessionByLoginId(loginId);
|
||||
};
|
||||
return session.get("RoleList",
|
||||
() -> getRoleList(loginId).stream().map(Role::getIdentifier).toList());
|
||||
}
|
||||
|
||||
private Collection<Role> getRoleList(Object loginId) {
|
||||
return roleRepository.findByAccountId(Long.valueOf((String) loginId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.query.DictQueries;
|
||||
import xyz.zhouxy.plusone.system.application.query.params.DictQueryParams;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.DictOverview;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.CreateDictCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.UpdateDictCommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.dict.Dict;
|
||||
import xyz.zhouxy.plusone.system.domain.model.dict.DictRepository;
|
||||
|
||||
/**
|
||||
* 数据字典管理
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class DictManagementService {
|
||||
|
||||
private final DictRepository dictRepository;
|
||||
private final DictQueries dictQueries;
|
||||
|
||||
public DictManagementService(DictRepository dictRepository, DictQueries dictQueries) {
|
||||
this.dictRepository = dictRepository;
|
||||
this.dictQueries = dictQueries;
|
||||
}
|
||||
|
||||
public void createDict(@Valid CreateDictCommand command) {
|
||||
var dictToSave = Dict.newInstance(command.getDictType(),
|
||||
command.getDictLabel(),
|
||||
command.getKeyLabelMap());
|
||||
dictRepository.save(dictToSave);
|
||||
}
|
||||
|
||||
public void deleteDicts(List<Long> ids) {
|
||||
Dict dictToDelete;
|
||||
for (Long id : ids) {
|
||||
dictToDelete = dictRepository.find(id);
|
||||
dictRepository.delete(dictToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDict(Long id, @Valid UpdateDictCommand command) {
|
||||
Assert.isTrue(Objects.equals(id, command.getId()), "id 不匹配");
|
||||
Dict dictToUpdate = dictRepository.find(command.getId());
|
||||
dictToUpdate.updateDict(command.getDictType(), command.getDictLabel(), command.getKeyLabelMap());
|
||||
dictRepository.save(dictToUpdate);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public Dict findDictDetails(Long dictId) {
|
||||
return dictRepository.find(dictId);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public List<Dict> loadAllDicts() {
|
||||
return dictRepository.findAll();
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public List<DictOverview> queryDictOverviewList(@Valid DictQueryParams queryParams) {
|
||||
return dictQueries.queryDictOverviewList(queryParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import xyz.zhouxy.plusone.mail.MailService;
|
||||
import xyz.zhouxy.plusone.sms.SmsService;
|
||||
import xyz.zhouxy.plusone.system.application.exception.AccountLoginException;
|
||||
import xyz.zhouxy.plusone.system.application.exception.AccountRegisterException;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
|
||||
/**
|
||||
* 邮箱和短信的验证服务
|
||||
*/
|
||||
@Service
|
||||
public class MailAndSmsVerifyService {
|
||||
private static final int CODE_LENGTH = 6;
|
||||
private final AccountRepository accountRepository;
|
||||
private final MailService mailService;
|
||||
private final SmsService smsService;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
public MailAndSmsVerifyService(AccountRepository accountRepository, MailService mailService, SmsService smsService,
|
||||
StringRedisTemplate redisTemplate) {
|
||||
this.accountRepository = accountRepository;
|
||||
this.mailService = mailService;
|
||||
this.smsService = smsService;
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送一次性密码到邮箱
|
||||
*
|
||||
* @param email 要求邮箱必须已注册
|
||||
*/
|
||||
public void sendOtpToEmail(Email email) {
|
||||
Assert.isTrue(accountRepository.existsEmail(email), "该邮箱未绑定任何帐号");
|
||||
var otp = generateCode();
|
||||
mailService.sendCodeMail(otp, email.value());
|
||||
redisTemplate.opsForValue().set("OTP-" + email.value(), otp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送一次性密码到手机号
|
||||
*
|
||||
* @param mobilePhone 要求手机号必须已注册
|
||||
*/
|
||||
public void sendOtpToMobilePhone(MobilePhone mobilePhone) {
|
||||
Assert.isTrue(accountRepository.existsMobilePhone(mobilePhone), "该手机号未绑定任何帐号");
|
||||
var otp = generateCode();
|
||||
smsService.sendCodeMessage(otp, mobilePhone.value());
|
||||
redisTemplate.opsForValue().set("OTP-" + mobilePhone.value(), otp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送校验码到邮箱
|
||||
*
|
||||
* @param email 要求邮箱必须未注册
|
||||
*/
|
||||
public void sendCodeToEmail(Email email) {
|
||||
Assert.isTrue(!accountRepository.existsEmail(email), "该邮箱未绑定任何帐号");
|
||||
var code = generateCode();
|
||||
mailService.sendCodeMail(code, email.value());
|
||||
redisTemplate.opsForValue().set("Code-" + email.value(), code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送校验码到手机号
|
||||
*
|
||||
* @param mobilePhone 要求手机号必须未注册
|
||||
*/
|
||||
public void sendCodeToMobilePhone(MobilePhone mobilePhone) {
|
||||
Assert.isTrue(!accountRepository.existsMobilePhone(mobilePhone), "该手机号未绑定任何帐号");
|
||||
var code = generateCode();
|
||||
smsService.sendCodeMessage(code, mobilePhone.value());
|
||||
redisTemplate.opsForValue().set("Code-" + mobilePhone.value(), code);
|
||||
}
|
||||
|
||||
public void checkOtp(String emailOrMobilePhone, String otp) {
|
||||
String key = "OTP-" + emailOrMobilePhone;
|
||||
String otpInRedis = redisTemplate.opsForValue().get(key);
|
||||
if (otpInRedis == null) {
|
||||
throw AccountLoginException.otpNotExistsException();
|
||||
}
|
||||
if (!Objects.equals(otpInRedis, otp)) {
|
||||
throw AccountLoginException.otpErrorException();
|
||||
}
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
public void checkCode(String emailOrMobilePhone, String code) {
|
||||
String key = "Code-" + emailOrMobilePhone;
|
||||
String codeInRedis = redisTemplate.opsForValue().get(key);
|
||||
if (codeInRedis == null) {
|
||||
throw AccountRegisterException.codeNotExistsException();
|
||||
}
|
||||
if (!Objects.equals(codeInRedis, code)) {
|
||||
throw AccountRegisterException.codeErrorException();
|
||||
}
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
private static String generateCode() {
|
||||
return RandomUtil.randomString(CODE_LENGTH);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.IWithOrderNumber;
|
||||
import xyz.zhouxy.plusone.system.application.exception.UnsupportedMenuTypeException;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.CreateMenuCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.UpdateMenuCommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.MenuConstructor;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.MenuRepository;
|
||||
import xyz.zhouxy.plusone.system.domain.service.MenuService;
|
||||
import xyz.zhouxy.plusone.util.AssertResult;
|
||||
|
||||
/**
|
||||
* 菜单管理
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class MenuManagementService {
|
||||
|
||||
private final MenuService menuService;
|
||||
private final MenuRepository menuRepository;
|
||||
|
||||
MenuManagementService(MenuService roleRepository, MenuRepository menuRepository) {
|
||||
this.menuService = roleRepository;
|
||||
this.menuRepository = menuRepository;
|
||||
}
|
||||
|
||||
// ==================== create ====================
|
||||
public void createMenu(@Valid CreateMenuCommand command) {
|
||||
Menu menuToInsert;
|
||||
switch (command.getMenuType()) {
|
||||
case MENU_LIST:
|
||||
menuToInsert = createMenuList(command);
|
||||
break;
|
||||
case MENU_ITEM:
|
||||
menuToInsert = createMenuItem(command);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedMenuTypeException();
|
||||
}
|
||||
menuRepository.save(menuToInsert);
|
||||
}
|
||||
|
||||
private Menu createMenuList(CreateMenuCommand command) {
|
||||
return MenuConstructor.newMenuList(
|
||||
command.getParentId(),
|
||||
command.getPath(),
|
||||
command.getName(),
|
||||
command.getTitle(),
|
||||
command.getIcon(),
|
||||
command.getHidden(),
|
||||
command.getOrderNumber(),
|
||||
command.getStatus(),
|
||||
command.getRemarks());
|
||||
}
|
||||
|
||||
private Menu createMenuItem(CreateMenuCommand command) {
|
||||
return MenuConstructor.newMenuItem(
|
||||
command.getParentId(),
|
||||
command.getPath(),
|
||||
command.getName(),
|
||||
command.getTitle(),
|
||||
command.getIcon(),
|
||||
command.getHidden(),
|
||||
command.getOrderNumber(),
|
||||
command.getStatus(),
|
||||
command.getComponent(),
|
||||
command.getResource(),
|
||||
command.getCache(),
|
||||
command.getRemarks());
|
||||
}
|
||||
|
||||
// ==================== delete ====================
|
||||
public void deleteMenu(Long id) {
|
||||
Menu menuToDelete = menuRepository.find(id);
|
||||
AssertResult.nonNull(menuToDelete);
|
||||
menuRepository.delete(menuToDelete);
|
||||
}
|
||||
|
||||
// ==================== update ====================
|
||||
public void updateMenu(Long id, @Valid UpdateMenuCommand command) {
|
||||
Assert.isTrue(Objects.equals(id, command.getId()), "id 不匹配");
|
||||
Menu menuToUpdate = menuRepository.find(command.getId());
|
||||
menuToUpdate.updateMenuInfo(
|
||||
command.getMenuType(),
|
||||
command.getParentId(),
|
||||
command.getPath(),
|
||||
command.getName(),
|
||||
command.getTitle(),
|
||||
command.getIcon(),
|
||||
command.getHidden(),
|
||||
command.getOrderNumber(),
|
||||
command.getStatus(),
|
||||
command.getComponent(),
|
||||
command.getResource(),
|
||||
command.getCache(),
|
||||
command.getRemarks());
|
||||
menuRepository.save(menuToUpdate);
|
||||
}
|
||||
|
||||
// ==================== query ====================
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public MenuViewObject findById(Long id) {
|
||||
var menu = menuRepository.find(id);
|
||||
return MenuViewObject.of(menu);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public List<MenuViewObject> queryByAccountId(Long accountId) {
|
||||
var menus = menuService.queryAllMenuListByAccountId(accountId);
|
||||
var menuViewObjects = menus.stream().map(MenuViewObject::of).toList();
|
||||
return buildMenuTree(menuViewObjects);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public List<MenuViewObject> queryByRoleId(Long roleId) {
|
||||
var menus = menuRepository.queryByRoleId(roleId);
|
||||
var menuViewObjects = menus.stream().map(MenuViewObject::of).toList();
|
||||
return buildMenuTree(menuViewObjects);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public List<MenuViewObject> buildMenuTree(List<MenuViewObject> menus) {
|
||||
List<MenuViewObject> rootMenus = menus
|
||||
.stream()
|
||||
.filter(menu -> Objects.equals(menu.getParentId(), 0L))
|
||||
.toList();
|
||||
|
||||
Map<Long, MenuViewObject> allMenus = new HashMap<>();
|
||||
for (var item : menus) {
|
||||
allMenus.put(item.getId(), item);
|
||||
}
|
||||
|
||||
for (MenuViewObject menu : menus) {
|
||||
long parentId = menu.getParentId();
|
||||
while (parentId != 0 && !allMenus.containsKey(parentId)) {
|
||||
MenuViewObject parent = findById(parentId);
|
||||
if (parent == null) {
|
||||
break;
|
||||
}
|
||||
allMenus.put(parent.getId(), parent);
|
||||
parentId = parent.getParentId();
|
||||
}
|
||||
}
|
||||
|
||||
for (var menu : allMenus.values()) {
|
||||
var parent = allMenus.getOrDefault(menu.getParentId(), null);
|
||||
if (parent != null) {
|
||||
parent.addChild(menu);
|
||||
}
|
||||
}
|
||||
|
||||
return rootMenus
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(IWithOrderNumber::getOrderNumber))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import xyz.zhouxy.plusone.exception.InvalidInputException;
|
||||
import xyz.zhouxy.plusone.system.application.common.util.PrincipalType;
|
||||
import xyz.zhouxy.plusone.system.application.common.util.PrincipalUtil;
|
||||
import xyz.zhouxy.plusone.system.application.exception.AccountRegisterException;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.RegisterAccountCommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.AccountInfo;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.AccountStatus;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Password;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 注册账号服务
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class RegisterAccountService {
|
||||
|
||||
private static final long DEFAULT_ROLE_ID = 1L;
|
||||
|
||||
private final AccountRepository accountRepository;
|
||||
private final MailAndSmsVerifyService verifyService;
|
||||
|
||||
public RegisterAccountService(AccountRepository accountRepository, MailAndSmsVerifyService verifyService) {
|
||||
this.accountRepository = accountRepository;
|
||||
this.verifyService = verifyService;
|
||||
}
|
||||
|
||||
public void registerAccount(RegisterAccountCommand command) {
|
||||
String username = command.getUsername();
|
||||
var existsUsername = accountRepository.existsUsername(Username.of(username));
|
||||
if (existsUsername) {
|
||||
throw AccountRegisterException.usernameAlreadyExists(username);
|
||||
}
|
||||
// 1. 确定是使用邮箱地址还是手机号进行注册
|
||||
String emailOrMobilePhone = command.getEmailOrMobilePhone();
|
||||
if (emailOrMobilePhone == null) {
|
||||
throw new IllegalArgumentException("邮箱地址或手机号不能为空");
|
||||
}
|
||||
PrincipalType principalType = PrincipalUtil.getPrincipalType(emailOrMobilePhone);
|
||||
|
||||
// 2. 确定该邮箱地址或手机号是否存在,比对校验码
|
||||
Email email = null;
|
||||
MobilePhone mobilePhone = null;
|
||||
boolean isExists;
|
||||
|
||||
if (principalType == PrincipalType.EMAIL) {
|
||||
email = Email.of(emailOrMobilePhone);
|
||||
isExists = accountRepository.existsEmail(email);
|
||||
if (isExists) {
|
||||
throw AccountRegisterException.emailAlreadyExists(email.value());
|
||||
}
|
||||
} else if (principalType == PrincipalType.MOBILE_PHONE) {
|
||||
mobilePhone = MobilePhone.of(emailOrMobilePhone);
|
||||
isExists = accountRepository.existsMobilePhone(mobilePhone);
|
||||
if (isExists) {
|
||||
throw AccountRegisterException.emailAlreadyExists(mobilePhone.value());
|
||||
}
|
||||
} else {
|
||||
throw InvalidInputException.unsupportedPrincipalTypeException();
|
||||
}
|
||||
|
||||
verifyService.checkCode(emailOrMobilePhone, command.getCode());
|
||||
|
||||
Account accountToSave = Account.register(
|
||||
Username.of(username),
|
||||
email,
|
||||
mobilePhone,
|
||||
Password.newPassword(command.getPassword(), command.getPasswordConfirmation()),
|
||||
AccountStatus.AVAILABLE,
|
||||
Set.of(DEFAULT_ROLE_ID),
|
||||
AccountInfo.of(command.getNickname(), command.getAvatar(), command.getSex()));
|
||||
accountRepository.save(accountToSave);
|
||||
}
|
||||
|
||||
public void sendCode(String principal) {
|
||||
PrincipalType principalType = PrincipalUtil.getPrincipalType(principal);
|
||||
if (principalType == PrincipalType.EMAIL) {
|
||||
verifyService.sendCodeToEmail(Email.of(principal));
|
||||
} else if (principalType == PrincipalType.MOBILE_PHONE) {
|
||||
verifyService.sendCodeToMobilePhone(MobilePhone.of(principal));
|
||||
} else {
|
||||
throw InvalidInputException.unsupportedPrincipalTypeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package xyz.zhouxy.plusone.system.application.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import xyz.zhouxy.plusone.system.application.query.RoleQueries;
|
||||
import xyz.zhouxy.plusone.system.application.query.params.RoleQueryParams;
|
||||
import xyz.zhouxy.plusone.system.application.query.result.RoleOverview;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.CreateRoleCommand;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.UpdateRoleCommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.MenuRepository;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.ActionRef;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.MenuRef;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.Role;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.RoleRepository;
|
||||
|
||||
/**
|
||||
* 角色管理服务
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class RoleManagementService {
|
||||
|
||||
private final RoleRepository _roleRepository;
|
||||
private final MenuRepository _menuRepository;
|
||||
private final RoleQueries _roleQueries;
|
||||
|
||||
public RoleManagementService(RoleRepository roleRepository, MenuRepository menuRepository,
|
||||
RoleQueries roleQueries) {
|
||||
_roleRepository = roleRepository;
|
||||
_menuRepository = menuRepository;
|
||||
_roleQueries = roleQueries;
|
||||
}
|
||||
|
||||
public void createRole(@Valid CreateRoleCommand command) {
|
||||
Set<MenuRef> menuRefs = _menuRepository.findByIdIn(command.getMenus())
|
||||
.stream()
|
||||
.map(menu -> new MenuRef(menu.getId().orElseThrow()))
|
||||
.collect(Collectors.toSet());
|
||||
Set<ActionRef> permissionRefs = _menuRepository.findPermissionsByIdIn(command.getPermissions())
|
||||
.stream()
|
||||
.map(permission -> new ActionRef(permission.getId().orElseThrow()))
|
||||
.collect(Collectors.toSet());
|
||||
Role roleToCreate = Role.newInstance(
|
||||
command.getName(),
|
||||
command.getIdentifier(),
|
||||
command.getStatus(),
|
||||
command.getRemarks(),
|
||||
menuRefs,
|
||||
permissionRefs);
|
||||
_roleRepository.save(roleToCreate);
|
||||
}
|
||||
|
||||
public void updateRole(@Valid UpdateRoleCommand command) {
|
||||
Long roleId = command.getId();
|
||||
Role roleToUpdate = _roleRepository.find(roleId);
|
||||
roleToUpdate.update(
|
||||
command.getName(),
|
||||
command.getIdentifier(),
|
||||
command.getStatus(),
|
||||
command.getRemarks(),
|
||||
Set.copyOf(_menuRepository.findByIdIn(command.getMenus())),
|
||||
Set.copyOf(_menuRepository.findPermissionsByIdIn(command.getPermissions())));
|
||||
_roleRepository.save(roleToUpdate);
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
Role role = _roleRepository.find(id);
|
||||
_roleRepository.delete(role);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public boolean exists(Long id) {
|
||||
return _roleRepository.exists(id);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public Role findById(Long id) {
|
||||
return _roleRepository.find(id);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public List<RoleOverview> query(RoleQueryParams params) {
|
||||
return _roleQueries.query(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import lombok.Data;
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.AccountStatus;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Sex;
|
||||
|
||||
/**
|
||||
* 创建账号命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class CreateAccountCommand implements ICommand {
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = RegexConsts.USERNAME, message = "用户名格式错误")
|
||||
String username;
|
||||
|
||||
@Email(message = "邮箱地址格式错误")
|
||||
String email;
|
||||
|
||||
@Pattern(regexp = RegexConsts.MOBILE_PHONE, message = "手机号格式错误")
|
||||
String mobilePhone;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = RegexConsts.PASSWORD, message = "密码不符合要求")
|
||||
String password;
|
||||
@NotBlank
|
||||
@Pattern(regexp = RegexConsts.PASSWORD, message = "密码不符合要求")
|
||||
String passwordConfirmation;
|
||||
|
||||
@NotNull
|
||||
AccountStatus status;
|
||||
|
||||
Set<Long> roleRefs;
|
||||
|
||||
@Pattern(regexp = RegexConsts.NICKNAME, message = "昵称格式错误")
|
||||
String nickname;
|
||||
|
||||
@NotBlank
|
||||
@URL
|
||||
String avatar;
|
||||
|
||||
Sex sex;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
|
||||
/**
|
||||
* 创建数据字典命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class CreateDictCommand implements ICommand {
|
||||
|
||||
@NotBlank
|
||||
String dictType;
|
||||
|
||||
@NotBlank
|
||||
String dictLabel;
|
||||
|
||||
@NotNull
|
||||
Map<Integer, String> keyLabelMap;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import xyz.zhouxy.plusone.constant.EntityStatus;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
|
||||
|
||||
/**
|
||||
* 创建菜单命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CreateMenuCommand implements ICommand {
|
||||
@NotNull
|
||||
private MenuType menuType;
|
||||
|
||||
@NotNull
|
||||
private Long parentId;
|
||||
|
||||
@NotBlank
|
||||
private String path;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@NotBlank
|
||||
private String title;
|
||||
|
||||
@NotBlank
|
||||
private String icon;
|
||||
|
||||
@NotNull
|
||||
private Boolean hidden;
|
||||
|
||||
@NotNull
|
||||
private Integer orderNumber;
|
||||
|
||||
@NotNull
|
||||
private EntityStatus status;
|
||||
|
||||
private String component;
|
||||
|
||||
private String resource;
|
||||
|
||||
@NotNull
|
||||
private Boolean cache;
|
||||
|
||||
private String remarks;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
import xyz.zhouxy.plusone.constant.EntityStatus;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
|
||||
/**
|
||||
* 创建角色命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class CreateRoleCommand implements ICommand {
|
||||
|
||||
@NotBlank
|
||||
String name;
|
||||
@NotBlank
|
||||
String identifier;
|
||||
|
||||
@NotNull
|
||||
EntityStatus status;
|
||||
String remarks;
|
||||
|
||||
Set<Long> menus;
|
||||
Set<Long> permissions;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import lombok.Data;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
|
||||
/**
|
||||
* 登录命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class LoginByOtpCommand implements ICommand {
|
||||
|
||||
String principal; // 邮箱地址 / 手机号
|
||||
String otp; // 密码
|
||||
boolean rememberMe; // 记住我
|
||||
|
||||
// 进入登陆界面时或刷新验证码时,前端发送图形验证码的请求,后端生成 captcha 并暂存到 redis 中,key 为 UUID,将图形和 uuid 响应给前端。
|
||||
// String uuid; // 校验码的 key
|
||||
// String captcha; // 校验码
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import lombok.Data;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
|
||||
/**
|
||||
* 登录命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class LoginByPasswordCommand implements ICommand {
|
||||
|
||||
String principal; // 用户名 / 邮箱地址 / 手机号
|
||||
String password; // 密码
|
||||
boolean rememberMe; // 记住我
|
||||
|
||||
// 进入登陆界面时或刷新验证码时,前端发送图形验证码的请求,后端生成 captcha 并暂存到 redis 中,key 为 UUID,将图形和 uuid 响应给前端。
|
||||
// String uuid; // 校验码的 key
|
||||
// String captcha; // 校验码
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import lombok.Data;
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Sex;
|
||||
|
||||
/**
|
||||
* 注册账号命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class RegisterAccountCommand implements ICommand {
|
||||
|
||||
@NotBlank
|
||||
String emailOrMobilePhone;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = RegexConsts.CAPTCHA)
|
||||
String code; // 校验码
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = RegexConsts.USERNAME)
|
||||
String username;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = RegexConsts.PASSWORD)
|
||||
String password;
|
||||
String passwordConfirmation;
|
||||
|
||||
@Pattern(regexp = RegexConsts.NICKNAME)
|
||||
String nickname;
|
||||
|
||||
@NotBlank
|
||||
@URL
|
||||
String avatar;
|
||||
|
||||
Sex sex;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import lombok.Data;
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Sex;
|
||||
|
||||
/**
|
||||
* 更新账号信息命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class UpdateAccountCommand implements ICommand {
|
||||
|
||||
@NotNull
|
||||
Long id;
|
||||
|
||||
@Pattern(regexp = RegexConsts.NICKNAME)
|
||||
String nickname;
|
||||
|
||||
@URL
|
||||
String avatar;
|
||||
|
||||
Sex sex;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
|
||||
/**
|
||||
* 更新数据字典命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class UpdateDictCommand implements ICommand {
|
||||
|
||||
@NotNull
|
||||
Long id;
|
||||
|
||||
@NotBlank
|
||||
String dictType;
|
||||
|
||||
@NotBlank
|
||||
String dictLabel;
|
||||
|
||||
@NotNull
|
||||
Map<Integer, String> keyLabelMap;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import xyz.zhouxy.plusone.constant.EntityStatus;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
|
||||
|
||||
/**
|
||||
* 更新菜单信息命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class UpdateMenuCommand implements ICommand {
|
||||
|
||||
@NotNull
|
||||
private Long id;
|
||||
|
||||
@NotNull
|
||||
private MenuType menuType;
|
||||
|
||||
@NotNull
|
||||
private Long parentId;
|
||||
|
||||
@NotBlank
|
||||
private String path;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@NotBlank
|
||||
private String title;
|
||||
|
||||
@NotBlank
|
||||
private String icon;
|
||||
|
||||
@NotNull
|
||||
private Boolean hidden;
|
||||
|
||||
@NotNull
|
||||
private Integer orderNumber;
|
||||
|
||||
@NotNull
|
||||
private EntityStatus status;
|
||||
|
||||
private String component;
|
||||
|
||||
private String resource;
|
||||
|
||||
@NotNull
|
||||
private Boolean cache;
|
||||
|
||||
private String remarks;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
import xyz.zhouxy.plusone.constant.EntityStatus;
|
||||
import xyz.zhouxy.plusone.domain.ICommand;
|
||||
|
||||
/**
|
||||
* 更新角色信息命令
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Data
|
||||
public class UpdateRoleCommand implements ICommand {
|
||||
|
||||
@NotNull
|
||||
Long id;
|
||||
|
||||
@NotBlank
|
||||
String name;
|
||||
|
||||
@NotBlank
|
||||
String identifier;
|
||||
|
||||
@NotNull
|
||||
EntityStatus status;
|
||||
|
||||
@NotBlank
|
||||
String remarks;
|
||||
|
||||
@NotNull
|
||||
Set<Long> menus;
|
||||
|
||||
@NotNull
|
||||
Set<Long> permissions;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command.validator;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.LoginByOtpCommand;
|
||||
import xyz.zhouxy.plusone.util.RegexUtil;
|
||||
import xyz.zhouxy.plusone.validator.BaseValidator;
|
||||
import xyz.zhouxy.plusone.validator.DtoValidator;
|
||||
|
||||
@Component
|
||||
@DtoValidator(LoginByOtpCommand.class)
|
||||
public class LoginByOtpCommandValidator extends BaseValidator<LoginByOtpCommand> {
|
||||
public LoginByOtpCommandValidator() {
|
||||
ruleFor(loginCommand -> {
|
||||
String principal = loginCommand.getPrincipal();
|
||||
return StringUtils.hasText(principal)
|
||||
&&
|
||||
RegexUtil.matchesOr(principal, RegexConsts.EMAIL, RegexConsts.MOBILE_PHONE);
|
||||
}, "输入邮箱地址或手机号");
|
||||
ruleFor(loginCommand -> {
|
||||
String otp = loginCommand.getOtp();
|
||||
return StringUtils.hasText(otp) && Pattern.matches(RegexConsts.CAPTCHA, otp);
|
||||
}, "验证码不符合要求");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package xyz.zhouxy.plusone.system.application.service.command.validator;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
import xyz.zhouxy.plusone.system.application.service.command.LoginByPasswordCommand;
|
||||
import xyz.zhouxy.plusone.util.RegexUtil;
|
||||
import xyz.zhouxy.plusone.validator.BaseValidator;
|
||||
import xyz.zhouxy.plusone.validator.DtoValidator;
|
||||
|
||||
@Component
|
||||
@DtoValidator(LoginByPasswordCommand.class)
|
||||
public class LoginByPasswordCommandValidator extends BaseValidator<LoginByPasswordCommand> {
|
||||
public LoginByPasswordCommandValidator() {
|
||||
ruleFor(loginCommand -> {
|
||||
String principal = loginCommand.getPrincipal();
|
||||
return StringUtils.hasText(principal)
|
||||
&&
|
||||
RegexUtil.matchesOr(principal, RegexConsts.USERNAME, RegexConsts.EMAIL, RegexConsts.MOBILE_PHONE, principal);
|
||||
}, "输入用户名、邮箱地址或手机号");
|
||||
ruleFor(loginCommand -> {
|
||||
String password = loginCommand.getPassword();
|
||||
return StringUtils.hasText(password)
|
||||
&&
|
||||
Pattern.matches(RegexConsts.PASSWORD, password);
|
||||
}, "密码格式不正确");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?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="xyz.zhouxy.plusone.system.application.query.AccountQueries">
|
||||
<!-- //////////////////// SELECT //////////////////// -->
|
||||
|
||||
<resultMap id="AccountOverview_ResultMap" type="xyz.zhouxy.plusone.system.application.query.result.AccountOverview">
|
||||
<id column="a_id" property="id" javaType="Long" />
|
||||
<result column="a_username" property="username" />
|
||||
<result column="a_email" property="email" />
|
||||
<result column="a_mobile_phone" property="mobilePhone" />
|
||||
<result column="a_status" property="status" />
|
||||
<result column="a_nickname" property="nickname" />
|
||||
<result column="a_avatar" property="avatar" />
|
||||
<result column="a_sex" property="sex" />
|
||||
<result column="a_created_by" property="createdBy" javaType="Long" />
|
||||
<result column="a_create_time" property="createTime" javaType="java.time.LocalDateTime" />
|
||||
<result column="a_updated_by" property="updatedBy" javaType="Long" />
|
||||
<result column="a_update_time" property="updateTime" javaType="java.time.LocalDateTime" />
|
||||
<collection property="roles" select="xyz.zhouxy.plusone.system.application.query.AccountQueries.getRoleNameListByAccount" column="a_id" />
|
||||
</resultMap>
|
||||
|
||||
<!-- List<AccountOverview> queryAccountOverview(AccountQueryParams queryParams); -->
|
||||
<select id="queryAccountOverview" resultMap="AccountOverview_ResultMap">
|
||||
SELECT
|
||||
a.id AS a_id,
|
||||
a.username AS a_username,
|
||||
a.email AS a_email,
|
||||
a.mobile_phone AS a_mobile_phone,
|
||||
a.avatar AS a_avatar,
|
||||
a.sex AS a_sex,
|
||||
a.nickname AS a_nickname,
|
||||
a.status AS a_status,
|
||||
a.created_by AS a_created_by,
|
||||
a.create_time AS a_create_time,
|
||||
a.updated_by AS a_updated_by,
|
||||
a.update_time AS a_update_time
|
||||
FROM
|
||||
sys_account AS a,
|
||||
(
|
||||
SELECT sa.id
|
||||
FROM sys_account sa
|
||||
<if test="roleId != null">
|
||||
JOIN sys_account_role AS sar ON sa.id = sar.account_id
|
||||
JOIN sys_role AS sr ON sr.id = sar.role_id
|
||||
</if>
|
||||
WHERE sa.deleted = 0
|
||||
<if test="id != null">
|
||||
AND sa.id = #{id}
|
||||
</if>
|
||||
<if test="username != null">
|
||||
AND sa.username = #{username}
|
||||
</if>
|
||||
<if test="email != null">
|
||||
AND sa.email = #{email}
|
||||
</if>
|
||||
<if test="mobilePhone != null">
|
||||
AND sa.mobile_phone = #{mobilePhone}
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND sa.status = #{status}
|
||||
</if>
|
||||
<if test="nickname != null">
|
||||
AND sa.nickname = #{nickname}
|
||||
</if>
|
||||
<if test="sex != null">
|
||||
AND sa.sex = #{sex}
|
||||
</if>
|
||||
<if test="createdBy != null">
|
||||
AND sa.created_by = #{createdBy}
|
||||
</if>
|
||||
<if test="createTimeStart != null">
|
||||
AND sa.create_time >= #{createTimeStart}
|
||||
</if>
|
||||
<if test="createTimeEnd != null">
|
||||
AND sa.create_time < #{createTimeEnd}
|
||||
</if>
|
||||
<if test="updatedBy != null">
|
||||
AND sa.updated_by = #{updatedBy}
|
||||
</if>
|
||||
<if test="updateTimeStart != null">
|
||||
AND sa.update_time >= #{updateTimeStart}
|
||||
</if>
|
||||
<if test="updateTimeEnd != null">
|
||||
AND sa.update_time < #{updateTimeEnd}
|
||||
</if>
|
||||
<if test="roleId != null">
|
||||
AND sar.role_id = #{roleId}
|
||||
AND sr.deleted = 0
|
||||
</if>
|
||||
<if test="orderBy != null">
|
||||
ORDER BY sa.${orderBy}, sa.id
|
||||
</if>
|
||||
<if test="orderBy == null">
|
||||
ORDER BY sa.id
|
||||
</if>
|
||||
LIMIT #{size} OFFSET #{offset}
|
||||
) b
|
||||
WHERE a.id = b.id
|
||||
</select>
|
||||
|
||||
<!--
|
||||
long count(SysAccountQuery queryParams);
|
||||
-->
|
||||
<select id="count" resultType="long">
|
||||
SELECT COUNT(*)
|
||||
FROM sys_account sa
|
||||
<if test="roleId != null">
|
||||
JOIN sys_account_role AS sar ON sa.id = sar.account_id
|
||||
JOIN sys_role AS sr ON sr.id = sar.role_id
|
||||
</if>
|
||||
WHERE sa.deleted = 0
|
||||
<if test="id != null">
|
||||
AND sa.id = #{id}
|
||||
</if>
|
||||
<if test="username != null">
|
||||
AND sa.username = #{username}
|
||||
</if>
|
||||
<if test="email != null">
|
||||
AND sa.email = #{email}
|
||||
</if>
|
||||
<if test="mobilePhone != null">
|
||||
AND sa.mobile_phone = #{mobilePhone}
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND sa.status = #{status}
|
||||
</if>
|
||||
<if test="nickname != null">
|
||||
AND sa.nickname = #{nickname}
|
||||
</if>
|
||||
<if test="sex != null">
|
||||
AND sa.sex = #{sex}
|
||||
</if>
|
||||
<if test="createdBy != null">
|
||||
AND sa.created_by = #{createdBy}
|
||||
</if>
|
||||
<if test="createTimeStart != null">
|
||||
AND sa.create_time >= #{createTimeStart}
|
||||
</if>
|
||||
<if test="createTimeEnd != null">
|
||||
AND sa.create_time < #{createTimeEnd}
|
||||
</if>
|
||||
<if test="updatedBy != null">
|
||||
AND sa.updated_by = #{updatedBy}
|
||||
</if>
|
||||
<if test="updateTimeStart != null">
|
||||
AND sa.update_time >= #{updateTimeStart}
|
||||
</if>
|
||||
<if test="updateTimeEnd != null">
|
||||
AND sa.update_time < #{updateTimeEnd}
|
||||
</if>
|
||||
<if test="roleId != null">
|
||||
AND sar.role_id = #{roleId}
|
||||
AND sr.deleted = 0
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="getRoleNameListByAccount" resultType="java.lang.String">
|
||||
SELECT r.name
|
||||
FROM sys_role AS r
|
||||
RIGHT JOIN sys_account_role AS ar ON r.id = ar.role_id
|
||||
WHERE ar.account_id = #{id} AND r.deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- AccountDetails queryAccountDetails(Long accountId); -->
|
||||
<resultMap id="RoleOverview_ResultMap" type="xyz.zhouxy.plusone.system.application.query.result.RoleOverview">
|
||||
<id column="id" property="id" javaType="Long" />
|
||||
<result column="name" property="name" />
|
||||
<result column="identifier" property="identifier" />
|
||||
<result column="status" property="status" />
|
||||
</resultMap>
|
||||
|
||||
<select id="getRoleOverviewListByAccount" resultMap="RoleOverview_ResultMap">
|
||||
SELECT r.id, r.name, r.identifier, r.status
|
||||
FROM sys_role AS r
|
||||
RIGHT JOIN sys_account_role AS ar ON r.id = ar.role_id
|
||||
WHERE ar.account_id = #{id} AND r.deleted = 0
|
||||
</select>
|
||||
|
||||
<resultMap id="AccountDetails_ResultMap" type="xyz.zhouxy.plusone.system.application.query.result.AccountDetails">
|
||||
<id column="id" property="id" javaType="Long" />
|
||||
<result column="username" property="username" />
|
||||
<result column="email" property="email" />
|
||||
<result column="mobile_phone" property="mobilePhone" />
|
||||
<result column="status" property="status" />
|
||||
<result column="nickname" property="nickname" />
|
||||
<result column="avatar" property="avatar" />
|
||||
<result column="sex" property="sex" />
|
||||
<collection property="roles" select="xyz.zhouxy.plusone.system.application.query.AccountQueries.getRoleOverviewListByAccount" column="id" />
|
||||
</resultMap>
|
||||
|
||||
<select id="queryAccountDetails" resultMap="AccountDetails_ResultMap">
|
||||
SELECT id, username, email, mobile_phone, avatar, sex, nickname, status
|
||||
FROM sys_account
|
||||
WHERE id = #{id} AND deleted = 0
|
||||
</select>
|
||||
</mapper>
|
||||
23
plusone-system/plusone-system-common/pom.xml
Normal file
23
plusone-system/plusone-system-common/pom.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>plusone-system</artifactId>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>plusone-system-common</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,20 @@
|
||||
package xyz.zhouxy.plusone.system.constant;
|
||||
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
|
||||
/**
|
||||
* 维护 StpLogic 的实例
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class AuthLogic {
|
||||
private AuthLogic() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static final String ADMIN_LOGIN_TYPE = "Admin";
|
||||
public static final StpLogic adminAuthLogic = new StpLogic(ADMIN_LOGIN_TYPE);
|
||||
|
||||
public static final String USER_LOGIN_TYPE = "User";
|
||||
public static final StpLogic userAuthLogic = new StpLogic(USER_LOGIN_TYPE);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package xyz.zhouxy.plusone.system.util;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import xyz.zhouxy.plusone.exception.PlusoneException;
|
||||
|
||||
/**
|
||||
* 密码工具类
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public final class PasswordUtil {
|
||||
private static final String SALT_BASE_STRING = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~`!@#$%^&*()_-+={}[]|\\:;\"',.<>?/";
|
||||
|
||||
/**
|
||||
* 将密码和随机盐混合,并进行哈希加密。
|
||||
*
|
||||
* @param password 密文密码
|
||||
* @param salt 随机盐
|
||||
* @return 哈希加密的结果
|
||||
*/
|
||||
@Nonnull
|
||||
public static String hashPassword(@Nonnull String password, @Nonnull String salt) {
|
||||
int length = salt.length();
|
||||
int i = length > 0 ? length / 2 : 0;
|
||||
var passwordWithSalt = salt.substring(0, i)
|
||||
+ password
|
||||
+ salt.substring(1);
|
||||
String sha512Hex = DigestUtil.sha512Hex(passwordWithSalt);
|
||||
if (sha512Hex == null) {
|
||||
throw new PlusoneException(9999999, "未知错误:哈希加密失败!");
|
||||
}
|
||||
return sha512Hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 24 位的字符串
|
||||
*
|
||||
* @return 生成的随机盐
|
||||
*/
|
||||
public static String generateRandomSalt() {
|
||||
return RandomUtil.randomString(SALT_BASE_STRING, 24);
|
||||
}
|
||||
|
||||
private PasswordUtil() {
|
||||
// 不允许实例化
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
}
|
||||
23
plusone-system/plusone-system-domain/pom.xml
Normal file
23
plusone-system/plusone-system-domain/pom.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-system</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>plusone-system-domain</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-domain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-system-common</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,33 @@
|
||||
package xyz.zhouxy.plusone.system.domain.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.DomainEvent;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 领域事件:创建账号事件
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@ToString(callSuper = true)
|
||||
public class AccountCreated extends DomainEvent {
|
||||
|
||||
private Username username;
|
||||
private Email email;
|
||||
private MobilePhone mobilePhone;
|
||||
|
||||
public AccountCreated(Account account) {
|
||||
this.username = account.getUsername();
|
||||
this.email = account.getEmail();
|
||||
this.mobilePhone = account.getMobilePhone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package xyz.zhouxy.plusone.system.domain.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.DomainEvent;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 领域事件:账号被锁定事件
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@ToString(callSuper = true)
|
||||
public class AccountLocked extends DomainEvent {
|
||||
|
||||
private Username username;
|
||||
private Email email;
|
||||
private MobilePhone mobilePhone;
|
||||
|
||||
public AccountLocked(Account account) {
|
||||
this.username = account.getUsername();
|
||||
this.email = account.getEmail();
|
||||
this.mobilePhone = account.getMobilePhone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package xyz.zhouxy.plusone.system.domain.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.DomainEvent;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 领域事件:账号密码被更改事件
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@ToString(callSuper = true)
|
||||
public class AccountPasswordChanged extends DomainEvent {
|
||||
|
||||
private Username username;
|
||||
private Email email;
|
||||
private MobilePhone mobilePhone;
|
||||
|
||||
public AccountPasswordChanged(Account account) {
|
||||
this.username = account.getUsername();
|
||||
this.email = account.getEmail();
|
||||
this.mobilePhone = account.getMobilePhone();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package xyz.zhouxy.plusone.system.domain.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.DomainEvent;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 领域事件:账号恢复事件
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@ToString(callSuper = true)
|
||||
public class AccountRecovered extends DomainEvent {
|
||||
|
||||
private Username username;
|
||||
|
||||
public AccountRecovered(Account account) {
|
||||
this.username = account.getUsername();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package xyz.zhouxy.plusone.system.domain.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.DomainEvent;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 领域事件:账号绑定角色事件
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@ToString(callSuper = true)
|
||||
public class AccountRolesBound extends DomainEvent {
|
||||
|
||||
private Username username;
|
||||
|
||||
public AccountRolesBound(Account account) {
|
||||
this.username = account.getUsername();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package xyz.zhouxy.plusone.system.domain.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.DomainEvent;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 领域事件:账号邮箱更改事件
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@ToString(callSuper = true)
|
||||
public class EmailChanged extends DomainEvent {
|
||||
|
||||
private Username username;
|
||||
private Email email;
|
||||
|
||||
public EmailChanged(Account account) {
|
||||
this.username = account.getUsername();
|
||||
this.email = account.getEmail();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package xyz.zhouxy.plusone.system.domain.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.DomainEvent;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 领域事件:账号手机号更改事件
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@ToString(callSuper = true)
|
||||
public class MobilePhoneChanged extends DomainEvent {
|
||||
|
||||
private Username username;
|
||||
private @Getter @Setter MobilePhone mobilePhone;
|
||||
|
||||
public MobilePhoneChanged(Account account) {
|
||||
this.username = account.getUsername();
|
||||
this.mobilePhone = account.getMobilePhone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package xyz.zhouxy.plusone.system.domain.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.DomainEvent;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Account;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||
|
||||
/**
|
||||
* 领域事件:账号用户名修改事件
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@ToString(callSuper = true)
|
||||
public class UsernameChanged extends DomainEvent {
|
||||
|
||||
private Username username;
|
||||
private Email email;
|
||||
private MobilePhone mobilePhone;
|
||||
|
||||
public UsernameChanged(Account account) {
|
||||
this.username = account.getUsername();
|
||||
this.email = account.getEmail();
|
||||
this.mobilePhone = account.getMobilePhone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.AggregateRoot;
|
||||
import xyz.zhouxy.plusone.domain.IWithVersion;
|
||||
import xyz.zhouxy.plusone.exception.UserOperationException;
|
||||
import xyz.zhouxy.plusone.system.domain.event.AccountCreated;
|
||||
import xyz.zhouxy.plusone.system.domain.event.AccountLocked;
|
||||
import xyz.zhouxy.plusone.system.domain.event.AccountPasswordChanged;
|
||||
import xyz.zhouxy.plusone.system.domain.event.AccountRecovered;
|
||||
import xyz.zhouxy.plusone.system.domain.event.AccountRolesBound;
|
||||
import xyz.zhouxy.plusone.system.domain.event.EmailChanged;
|
||||
import xyz.zhouxy.plusone.system.domain.event.MobilePhoneChanged;
|
||||
import xyz.zhouxy.plusone.system.domain.event.UsernameChanged;
|
||||
|
||||
/**
|
||||
* 聚合根:账号
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@ToString
|
||||
public class Account extends AggregateRoot<Long> implements IWithVersion {
|
||||
|
||||
// ===================== 字段 ====================
|
||||
private Long id;
|
||||
private @Getter Username username;
|
||||
private @Getter Email email;
|
||||
private @Getter MobilePhone mobilePhone;
|
||||
private Password password;
|
||||
private @Getter AccountStatus status;
|
||||
private @Getter AccountInfo accountInfo;
|
||||
private Set<Long> roleRefs = new HashSet<>();
|
||||
|
||||
private @Getter Long createdBy;
|
||||
private @Getter @Setter Long updatedBy;
|
||||
private @Getter long version;
|
||||
|
||||
public void setUsername(Username username) {
|
||||
this.username = username;
|
||||
addDomainEvent(new UsernameChanged(this));
|
||||
}
|
||||
|
||||
public void setEmail(Email email) {
|
||||
this.email = email;
|
||||
addDomainEvent(new EmailChanged(this));
|
||||
}
|
||||
|
||||
public void setMobilePhone(MobilePhone mobilePhone) {
|
||||
this.mobilePhone = mobilePhone;
|
||||
addDomainEvent(new MobilePhoneChanged(this));
|
||||
}
|
||||
|
||||
public void changePassword(String newPassword, String passwordConfirmation) {
|
||||
this.password = Password.newPassword(newPassword, passwordConfirmation);
|
||||
addDomainEvent(new AccountPasswordChanged(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定账号。如当前账号已锁定,则抛出 UserOperationException 异常
|
||||
*
|
||||
* @see UserOperationException
|
||||
*/
|
||||
public void lockAccount() {
|
||||
if (this.status == AccountStatus.LOCKED) {
|
||||
throw UserOperationException
|
||||
.invalidOperation(String.format("账号 %d 的状态为:%s,无法锁定", this.id, this.status.getName()));
|
||||
}
|
||||
this.status = AccountStatus.LOCKED;
|
||||
addDomainEvent(new AccountLocked(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复账号状态。如当前用户可用,则抛出 UserOperationException 异常
|
||||
*
|
||||
* @see UserOperationException
|
||||
*/
|
||||
public void recoveryAccount() {
|
||||
if (this.status == AccountStatus.AVAILABLE) {
|
||||
throw UserOperationException
|
||||
.invalidOperation(String.format("账号 %d 的状态为:%s,无法恢复", this.id, this.status.getName()));
|
||||
}
|
||||
this.status = AccountStatus.AVAILABLE;
|
||||
addDomainEvent(new AccountRecovered(this));
|
||||
}
|
||||
|
||||
public void setAccountInfo(AccountInfo accountInfo) {
|
||||
this.accountInfo = accountInfo;
|
||||
}
|
||||
|
||||
public void setAccountInfo(Nickname nickname, URL avatar, Sex sex) {
|
||||
this.accountInfo = AccountInfo.of(nickname, avatar, sex);
|
||||
}
|
||||
|
||||
public void setAccountInfo(String nickname, String avatar, Sex sex) {
|
||||
this.accountInfo = AccountInfo.of(nickname, avatar, sex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定角色
|
||||
*
|
||||
* @param roleRefs 角色 id 集合
|
||||
*/
|
||||
public void bindRoles(Set<Long> roleRefs) {
|
||||
this.roleRefs.clear();
|
||||
this.roleRefs.addAll(roleRefs);
|
||||
addDomainEvent(new AccountRolesBound(this));
|
||||
}
|
||||
|
||||
public boolean checkPassword(@Nonnull String password) {
|
||||
return this.password.check(password);
|
||||
}
|
||||
|
||||
// ===================== 实例化 ====================
|
||||
|
||||
Account(Long id,
|
||||
Username username,
|
||||
Email email,
|
||||
MobilePhone mobilePhone,
|
||||
Password password,
|
||||
AccountStatus status,
|
||||
AccountInfo accountInfo,
|
||||
Set<Long> roleRefs,
|
||||
Long createdBy,
|
||||
Long updatedBy,
|
||||
long version) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.email = email;
|
||||
this.mobilePhone = mobilePhone;
|
||||
this.password = password;
|
||||
this.status = status;
|
||||
this.accountInfo = accountInfo;
|
||||
this.bindRoles(roleRefs);
|
||||
this.createdBy = createdBy;
|
||||
this.updatedBy = updatedBy;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public static Account newInstance(
|
||||
Username username,
|
||||
Email email,
|
||||
MobilePhone mobilePhone,
|
||||
Password password,
|
||||
AccountStatus status,
|
||||
Set<Long> roleRefs,
|
||||
AccountInfo accountInfo,
|
||||
Long createdBy) {
|
||||
var newInstance = new Account(null, username, email, mobilePhone, password, status, accountInfo, roleRefs,
|
||||
createdBy, null, 0);
|
||||
newInstance.addDomainEvent(new AccountCreated(newInstance));
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
public static Account register(
|
||||
Username username,
|
||||
Email email,
|
||||
MobilePhone mobilePhone,
|
||||
Password password,
|
||||
AccountStatus status,
|
||||
Set<Long> roleRefs,
|
||||
AccountInfo accountInfo) {
|
||||
var newInstance = new Account(null, username, email, mobilePhone, password, status, accountInfo, roleRefs,
|
||||
0L, null, 0);
|
||||
newInstance.addDomainEvent(new AccountCreated(newInstance));
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
Account(Long id,
|
||||
String username,
|
||||
String email,
|
||||
String mobilePhone,
|
||||
Password password,
|
||||
AccountStatus status,
|
||||
AccountInfo accountInfo,
|
||||
Set<Long> roleRefs,
|
||||
Long createdBy,
|
||||
Long updatedBy,
|
||||
long version) {
|
||||
this(id, Username.of(username), Email.ofNullable(email), MobilePhone.ofNullable(mobilePhone),
|
||||
password, status, accountInfo, roleRefs, createdBy, updatedBy, version);
|
||||
}
|
||||
|
||||
public static Account newInstance(
|
||||
String username,
|
||||
String email,
|
||||
String mobilePhone,
|
||||
String password,
|
||||
String passwordConfirmation,
|
||||
AccountStatus status,
|
||||
Set<Long> roleRefs,
|
||||
AccountInfo accountInfo,
|
||||
long createdBy) {
|
||||
var newInstance = new Account(null, username, email, mobilePhone,
|
||||
Password.newPassword(password, passwordConfirmation), status, accountInfo, roleRefs,
|
||||
createdBy, null, 0);
|
||||
newInstance.addDomainEvent(new AccountCreated(newInstance));
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
public static Account register(
|
||||
String username,
|
||||
String email,
|
||||
String mobilePhone,
|
||||
Password password,
|
||||
AccountStatus status,
|
||||
Set<Long> roleRefs,
|
||||
AccountInfo accountInfo) {
|
||||
var newInstance = new Account(null, username, email, mobilePhone, password, status, accountInfo, roleRefs,
|
||||
0L, null, 0);
|
||||
newInstance.addDomainEvent(new AccountCreated(newInstance));
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getId() {
|
||||
return Optional.ofNullable(id);
|
||||
}
|
||||
|
||||
public Set<Long> getRoleIds() {
|
||||
return Set.copyOf(this.roleRefs);
|
||||
}
|
||||
|
||||
Password getPassword() {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.IValueObject;
|
||||
|
||||
/**
|
||||
* 账号详细信息
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@ToString
|
||||
public class AccountInfo implements IValueObject {
|
||||
|
||||
private final Nickname nickname;
|
||||
private final URL avatar;
|
||||
private final Sex sex;
|
||||
|
||||
private AccountInfo(Nickname nickname, URL avatar, Sex sex) {
|
||||
this.nickname = nickname;
|
||||
this.avatar = avatar;
|
||||
this.sex = Objects.nonNull(sex) ? sex : Sex.UNSET;
|
||||
}
|
||||
|
||||
public static AccountInfo of(Nickname nickname, URL avatar, Sex sex) {
|
||||
return new AccountInfo(nickname, avatar, sex);
|
||||
}
|
||||
|
||||
public static AccountInfo of(String nickname, String avatar, Sex sex) {
|
||||
URL avatarURL;
|
||||
try {
|
||||
avatarURL = Objects.nonNull(avatar) ? new URL(avatar) : null;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
return new AccountInfo(Nickname.ofNullable(nickname), avatarURL, Objects.nonNull(sex) ? sex : Sex.UNSET);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.IRepository;
|
||||
|
||||
/**
|
||||
* AccountRepository
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @see Account
|
||||
*/
|
||||
public interface AccountRepository extends IRepository<Account, Long> {
|
||||
|
||||
Collection<Account> findByRoleId(Long roleId);
|
||||
|
||||
Account findByEmail(Email email);
|
||||
|
||||
Account findByMobilePhone(MobilePhone mobilePhone);
|
||||
|
||||
Account findByUsername(Username username);
|
||||
|
||||
boolean existsUsername(Username username);
|
||||
|
||||
boolean existsEmail(Email email);
|
||||
|
||||
boolean existsMobilePhone(MobilePhone email);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import lombok.Getter;
|
||||
import xyz.zhouxy.plusone.domain.IValueObject;
|
||||
import xyz.zhouxy.plusone.util.Enumeration;
|
||||
import xyz.zhouxy.plusone.util.EnumerationValuesHolder;
|
||||
|
||||
/**
|
||||
* 账号状态
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
public class AccountStatus extends Enumeration<AccountStatus> implements IValueObject {
|
||||
|
||||
private AccountStatus(int value, String name) {
|
||||
super(value, name);
|
||||
}
|
||||
|
||||
public static final AccountStatus AVAILABLE = new AccountStatus(0, "账号正常");
|
||||
public static final AccountStatus LOCKED = new AccountStatus(1, "账号被锁定");
|
||||
|
||||
private static final EnumerationValuesHolder<AccountStatus> ENUMERATION_VALUES = new EnumerationValuesHolder<>(
|
||||
new AccountStatus[] { AVAILABLE, LOCKED });
|
||||
|
||||
public static AccountStatus of(int value) {
|
||||
return ENUMERATION_VALUES.get(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
|
||||
/**
|
||||
* 值对象:电子邮箱地址
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class Email extends Principal {
|
||||
|
||||
public static final String REGEX = RegexConsts.EMAIL;
|
||||
|
||||
private Email(String email) {
|
||||
super(REGEX);
|
||||
if (email == null) {
|
||||
throw new IllegalArgumentException("邮箱地址不能为空");
|
||||
}
|
||||
this.value = email;
|
||||
if (!isValid()) {
|
||||
throw new IllegalArgumentException("邮箱地址格式错误");
|
||||
}
|
||||
}
|
||||
|
||||
public static Email of(String email) {
|
||||
return new Email(email);
|
||||
}
|
||||
|
||||
public static Email ofNullable(String email) {
|
||||
return Objects.nonNull(email) ? new Email(email) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏后的数据
|
||||
*/
|
||||
public String safeValue() {
|
||||
return DesensitizedUtil.email(this.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.safeValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
|
||||
/**
|
||||
* 值对象:手机号码
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class MobilePhone extends Principal {
|
||||
|
||||
public static final String REGEX = RegexConsts.MOBILE_PHONE;
|
||||
|
||||
private MobilePhone(String mobilePhone) {
|
||||
super(REGEX);
|
||||
if (mobilePhone == null) {
|
||||
throw new IllegalArgumentException("手机号不能为空");
|
||||
}
|
||||
this.value = mobilePhone;
|
||||
if (!isValid()) {
|
||||
throw new IllegalArgumentException("手机号格式错误");
|
||||
}
|
||||
}
|
||||
|
||||
public static MobilePhone of(String mobilePhone) {
|
||||
return new MobilePhone(mobilePhone);
|
||||
}
|
||||
|
||||
public static MobilePhone ofNullable(String mobilePhone) {
|
||||
return Objects.nonNull(mobilePhone) ? new MobilePhone(mobilePhone) : null;
|
||||
}
|
||||
|
||||
public String safeValue() {
|
||||
return DesensitizedUtil.mobilePhone(this.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.safeValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
import xyz.zhouxy.plusone.domain.ValidatableStringRecord;
|
||||
|
||||
/**
|
||||
* 值对象:昵称
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class Nickname extends ValidatableStringRecord {
|
||||
|
||||
public static final String REGEX = RegexConsts.NICKNAME;
|
||||
|
||||
private Nickname(String value) {
|
||||
super(REGEX);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("昵称不能为空");
|
||||
}
|
||||
this.value = value;
|
||||
if (!isValid()) {
|
||||
throw new IllegalArgumentException("昵称格式错误");
|
||||
}
|
||||
}
|
||||
|
||||
public static Nickname of(String nickname) {
|
||||
return new Nickname(nickname);
|
||||
}
|
||||
|
||||
public static Nickname ofNullable(String nickname) {
|
||||
return Objects.nonNull(nickname) ? new Nickname(nickname) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
import xyz.zhouxy.plusone.domain.IValueObject;
|
||||
import xyz.zhouxy.plusone.exception.PlusoneException;
|
||||
import xyz.zhouxy.plusone.system.util.PasswordUtil;
|
||||
|
||||
/**
|
||||
* 值对象:加盐加密的密码
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class Password implements IValueObject {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile(RegexConsts.PASSWORD);
|
||||
private static final String DEFAULT_PASSWORD = "A1b2C3d4";
|
||||
|
||||
@Nonnull
|
||||
private final String passwordVal;
|
||||
@Nonnull
|
||||
private final String saltVal;
|
||||
|
||||
private Password(String password) {
|
||||
if (password == null) {
|
||||
throw new IllegalArgumentException("密码不能为空");
|
||||
}
|
||||
if (!PATTERN.matcher(password).matches()) {
|
||||
throw new IllegalArgumentException("密码格式不符合要求");
|
||||
}
|
||||
var salt = PasswordUtil.generateRandomSalt();
|
||||
if (salt == null) {
|
||||
throw new PlusoneException(9999999, "未知错误:生成随机盐失败");
|
||||
}
|
||||
this.saltVal = salt;
|
||||
this.passwordVal = PasswordUtil.hashPassword(password, salt);
|
||||
}
|
||||
|
||||
private Password(String password, String salt) {
|
||||
if (password == null || salt == null) {
|
||||
throw new IllegalArgumentException("password 和 salt 不能为空");
|
||||
}
|
||||
this.passwordVal = password;
|
||||
this.saltVal = salt;
|
||||
}
|
||||
|
||||
public static Password of(String password, String salt) {
|
||||
return new Password(password, salt);
|
||||
}
|
||||
|
||||
public static Password newPassword(String newPassword, String passwordConfirmation) {
|
||||
Assert.isTrue(Objects.equals(newPassword, passwordConfirmation), "两次输入的密码不一致");
|
||||
return newPassword(newPassword);
|
||||
}
|
||||
|
||||
public static Password newPassword(String newPassword) {
|
||||
return new Password(newPassword);
|
||||
}
|
||||
|
||||
public boolean check(String password) {
|
||||
if (password == null) {
|
||||
throw new IllegalArgumentException("password 不能为空");
|
||||
}
|
||||
Assert.hasText(password, "密码不能为空");
|
||||
return Objects.equals(this.passwordVal, PasswordUtil.hashPassword(password, this.saltVal));
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return passwordVal;
|
||||
}
|
||||
|
||||
public String getSalt() {
|
||||
return saltVal;
|
||||
}
|
||||
|
||||
public static Password newDefaultPassword() {
|
||||
return newPassword(DEFAULT_PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "********";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.ValidatableStringRecord;
|
||||
|
||||
/**
|
||||
* 账号标识符
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public abstract class Principal extends ValidatableStringRecord {
|
||||
protected Principal(String format) {
|
||||
super(format);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.IValueObject;
|
||||
import xyz.zhouxy.plusone.util.Enumeration;
|
||||
import xyz.zhouxy.plusone.util.EnumerationValuesHolder;
|
||||
|
||||
/**
|
||||
* 值对象:性别
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class Sex extends Enumeration<Sex> implements IValueObject {
|
||||
public static final Sex UNSET = new Sex(0, "未设置");
|
||||
public static final Sex MALE = new Sex(1, "男性");
|
||||
public static final Sex FEMALE = new Sex(2, "女性");
|
||||
|
||||
private Sex(int value, String name) {
|
||||
super(value, name);
|
||||
}
|
||||
|
||||
private static EnumerationValuesHolder<Sex> values = new EnumerationValuesHolder<>(new Sex[] {
|
||||
UNSET,
|
||||
MALE,
|
||||
FEMALE
|
||||
});
|
||||
|
||||
public static Sex of(int value) {
|
||||
return values.get(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||
|
||||
/**
|
||||
* 值对象:用户名
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class Username extends Principal {
|
||||
|
||||
public static final String REGEX = RegexConsts.USERNAME;
|
||||
|
||||
private Username(String username) {
|
||||
super(REGEX);
|
||||
if (username == null) {
|
||||
throw new IllegalArgumentException("用户名不能为空");
|
||||
}
|
||||
this.value = username;
|
||||
if (!isValid()) {
|
||||
throw new IllegalArgumentException("用户名格式错误");
|
||||
}
|
||||
}
|
||||
|
||||
public static Username of(String username) {
|
||||
return new Username(username);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.dict;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.AggregateRoot;
|
||||
import xyz.zhouxy.plusone.domain.IWithLabel;
|
||||
import xyz.zhouxy.plusone.domain.IWithVersion;
|
||||
|
||||
/**
|
||||
* 聚合根:数据字典
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@ToString
|
||||
public class Dict extends AggregateRoot<Long> implements IWithLabel, IWithVersion {
|
||||
|
||||
private Long id;
|
||||
private String dictType;
|
||||
private String dictLabel;
|
||||
|
||||
private Map<Integer, DictValue> values = new HashMap<>();
|
||||
|
||||
private long version;
|
||||
|
||||
// ==================== 领域逻辑 ====================
|
||||
|
||||
public void addValue(int key, String label) {
|
||||
if (this.values.containsKey(key)) {
|
||||
throw new IllegalArgumentException(String.format("字典 %s 已存在值:%d", dictType, key));
|
||||
}
|
||||
this.values.put(key, DictValue.of(key, label));
|
||||
}
|
||||
|
||||
public void removeValue(int key) {
|
||||
this.values.remove(key);
|
||||
}
|
||||
|
||||
public void updateDict(String dictType, String dictLabel, Map<Integer, String> keyLabelMap) {
|
||||
this.dictType = dictType;
|
||||
this.dictLabel = dictLabel;
|
||||
var valueKeys = this.values.keySet();
|
||||
for (Integer key : valueKeys) {
|
||||
if (!keyLabelMap.containsKey(key)) {
|
||||
this.values.remove(key);
|
||||
}
|
||||
}
|
||||
keyLabelMap.forEach((Integer key, String label) -> {
|
||||
var temp = this.values.get(key);
|
||||
if (temp != null) {
|
||||
temp.label = label;
|
||||
} else {
|
||||
this.values.put(key, DictValue.of(key, label));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 实例化 ====================
|
||||
|
||||
Dict(Long id, String dictType, String dictLabel, Set<DictValue> values, long version) {
|
||||
this.id = id;
|
||||
this.dictType = dictType;
|
||||
this.dictLabel = dictLabel;
|
||||
values.forEach(dictValue -> this.values.put(dictValue.key, dictValue));
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public static Dict newInstance(
|
||||
String dictType,
|
||||
String dictLabel) {
|
||||
return new Dict(null, dictType, dictLabel, Collections.emptySet(), 0);
|
||||
}
|
||||
|
||||
public static Dict newInstance(
|
||||
String dictType,
|
||||
String dictLabel,
|
||||
Set<DictValue> values) {
|
||||
return new Dict(null, dictType, dictLabel, values, 0);
|
||||
}
|
||||
|
||||
public static Dict newInstance(
|
||||
String dictType,
|
||||
String dictLabel,
|
||||
Map<Integer, String> keyLabelMap) {
|
||||
var values = buildDictValues(keyLabelMap);
|
||||
return new Dict(null, dictType, dictLabel, values, 0);
|
||||
}
|
||||
|
||||
private static Set<DictValue> buildDictValues(Map<Integer, String> keyLabelMap) {
|
||||
Set<DictValue> dictValues = new HashSet<>(keyLabelMap.size());
|
||||
keyLabelMap.forEach((Integer key, String label) -> dictValues.add(DictValue.of(key, label)));
|
||||
return dictValues;
|
||||
}
|
||||
|
||||
// ==================== getters ====================
|
||||
|
||||
@Override
|
||||
public Optional<Long> getId() {
|
||||
return Optional.ofNullable(id);
|
||||
}
|
||||
|
||||
public String getDictType() {
|
||||
return dictType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.dictLabel;
|
||||
}
|
||||
|
||||
public Set<DictValue> getValues() {
|
||||
return this.values.values().stream().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public int count() {
|
||||
return this.values.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.dict;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.IRepository;
|
||||
|
||||
public interface DictRepository extends IRepository<Dict, Long> {
|
||||
|
||||
List<Dict> findAll();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.dict;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.domain.IValueObject;
|
||||
|
||||
/**
|
||||
* 字典值
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Getter
|
||||
@ToString
|
||||
public class DictValue implements IValueObject {
|
||||
|
||||
final Integer key;
|
||||
String label;
|
||||
|
||||
// ==================== 实例化 ====================
|
||||
|
||||
private DictValue(int key, String label) {
|
||||
this.key = key;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public static DictValue of(int key, String label) {
|
||||
return new DictValue(key, label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DictValue other = (DictValue) obj;
|
||||
return Objects.equals(key, other.key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.menu;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import lombok.Getter;
|
||||
import xyz.zhouxy.plusone.domain.Entity;
|
||||
import xyz.zhouxy.plusone.domain.IWithLabel;
|
||||
|
||||
/**
|
||||
* 行为。
|
||||
* <p>
|
||||
* 一个 Menu 代表对应一个资源,Action 表示对该资源的行为,每个行为具有对应权限。
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*
|
||||
* @see Menu
|
||||
*/
|
||||
public class Action extends Entity<Long> implements IWithLabel {
|
||||
|
||||
Long id;
|
||||
String resource;
|
||||
@Getter
|
||||
String identifier;
|
||||
@Getter
|
||||
String label;
|
||||
|
||||
private Action(Long id, String resource, String identifier, String label) {
|
||||
this.id = id;
|
||||
this.resource = resource;
|
||||
this.identifier = identifier;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
static Action of(Long id, String resource, String identifier, String label) {
|
||||
return new Action(id, resource, identifier, label);
|
||||
}
|
||||
|
||||
static Action newInstance(String resource, String identifier, String label) {
|
||||
return new Action(null, resource, identifier, label);
|
||||
}
|
||||
|
||||
@JsonProperty("value")
|
||||
public String value() {
|
||||
return resource + identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getId() {
|
||||
return Optional.ofNullable(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.menu;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.constant.EntityStatus;
|
||||
import xyz.zhouxy.plusone.domain.AggregateRoot;
|
||||
import xyz.zhouxy.plusone.domain.IWithOrderNumber;
|
||||
import xyz.zhouxy.plusone.domain.IWithVersion;
|
||||
|
||||
/**
|
||||
* 聚合根:菜单
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*
|
||||
* @see Action
|
||||
* @see MenuConstructor
|
||||
*/
|
||||
@Getter
|
||||
@ToString
|
||||
public class Menu extends AggregateRoot<Long> implements IWithOrderNumber, IWithVersion {
|
||||
|
||||
MenuType type;
|
||||
|
||||
Long id;
|
||||
Long parentId;
|
||||
|
||||
String name;
|
||||
// 若 type 为 MENU_ITEM 且 path 以 http:// 或 https:// 开头则被识别为外链
|
||||
String path;
|
||||
String title;
|
||||
String icon;
|
||||
boolean hidden;
|
||||
int orderNumber;
|
||||
EntityStatus status;
|
||||
String remarks;
|
||||
|
||||
// MENU_ITEM
|
||||
String component;
|
||||
Boolean cache;
|
||||
String resource;
|
||||
private List<Action> actions;
|
||||
|
||||
private @Getter long version;
|
||||
|
||||
public void updateMenuInfo(
|
||||
MenuType type,
|
||||
long parentId,
|
||||
String name,
|
||||
String path,
|
||||
String title,
|
||||
String icon,
|
||||
boolean hidden,
|
||||
int orderNumber,
|
||||
EntityStatus status,
|
||||
String remarks,
|
||||
String component,
|
||||
boolean cache,
|
||||
String resource) {
|
||||
this.type = type;
|
||||
this.parentId = parentId;
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
this.title = title;
|
||||
this.icon = icon;
|
||||
this.hidden = hidden;
|
||||
this.orderNumber = orderNumber;
|
||||
this.status = status;
|
||||
this.component = component;
|
||||
this.resource = resource;
|
||||
this.cache = cache;
|
||||
this.remarks = remarks;
|
||||
}
|
||||
|
||||
public Menu addAction(String action, String label) {
|
||||
return addAction(Action.newInstance(this.resource, action, label));
|
||||
}
|
||||
|
||||
public void removeAction(Long actionId) {
|
||||
this.actions.removeIf(action -> Objects.equals(actionId, action.getId().orElseThrow()));
|
||||
}
|
||||
|
||||
public void removeAction(String identifier) {
|
||||
this.actions.removeIf(action -> Objects.equals(identifier, action.identifier));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getId() {
|
||||
return Optional.ofNullable(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrderNumber() {
|
||||
return this.orderNumber;
|
||||
}
|
||||
|
||||
private Menu addAction(Action action) {
|
||||
if (this.actions == null) {
|
||||
this.actions = new ArrayList<>(8);
|
||||
}
|
||||
this.actions.add(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
Menu(MenuType type, Long id, Long parentId, String name, String path, String title, String icon,
|
||||
boolean hidden, int orderNumber, EntityStatus status, String remarks, String component, Boolean cache,
|
||||
String resource, List<Action> actions, long version) {
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
this.parentId = parentId;
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.title = title;
|
||||
this.icon = icon;
|
||||
this.hidden = hidden;
|
||||
this.orderNumber = orderNumber;
|
||||
this.status = status;
|
||||
this.remarks = remarks;
|
||||
this.component = component;
|
||||
this.cache = cache;
|
||||
this.resource = resource;
|
||||
this.actions = actions;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public enum MenuType {
|
||||
MENU_LIST, MENU_ITEM;
|
||||
|
||||
@JsonValue
|
||||
public int value() {
|
||||
return ordinal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.menu;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import xyz.zhouxy.plusone.constant.EntityStatus;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
|
||||
|
||||
/**
|
||||
* 菜单构造器。生成新的 MenuList 或 MenuItem
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class MenuConstructor {
|
||||
|
||||
private MenuConstructor() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static Menu newMenuItem(
|
||||
long parentId,
|
||||
String path,
|
||||
String name,
|
||||
String title,
|
||||
String icon,
|
||||
boolean hidden,
|
||||
int orderNumber,
|
||||
EntityStatus status,
|
||||
String component,
|
||||
String resource,
|
||||
boolean cache,
|
||||
String remarks) {
|
||||
List<Action> actions = List.of(
|
||||
Action.newInstance(resource, "-query", "查询"),
|
||||
Action.newInstance(resource, "-details", "详情"),
|
||||
Action.newInstance(resource, "-add", "新增"),
|
||||
Action.newInstance(resource, "-update", "修改"),
|
||||
Action.newInstance(resource, "-delete", "删除"));
|
||||
return new Menu(MenuType.MENU_ITEM, null, parentId, name, path, title, icon, hidden, orderNumber, status,
|
||||
remarks, component, cache, resource, actions, 0L);
|
||||
}
|
||||
|
||||
public static Menu newMenuList(
|
||||
long parentId,
|
||||
String path,
|
||||
String name,
|
||||
String title,
|
||||
String icon,
|
||||
boolean hidden,
|
||||
int orderNumber,
|
||||
EntityStatus status,
|
||||
String remarks) {
|
||||
return new Menu(MenuType.MENU_LIST, null, parentId, name, path, title, icon, hidden, orderNumber, status,
|
||||
remarks, null, null, null, null, 0L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.menu;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.IRepository;
|
||||
|
||||
public interface MenuRepository extends IRepository<Menu, Long> {
|
||||
|
||||
Collection<Menu> findByIdIn(Collection<Long> ids);
|
||||
|
||||
Collection<Menu> queryByRoleId(Long roleId);
|
||||
|
||||
Collection<Action> findPermissionsByIdIn(Collection<Long> permissionIds);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.menu;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.IWithLabel;
|
||||
|
||||
public enum Target implements IWithLabel {
|
||||
BLANK("_blank"),
|
||||
PARENT("_parent"),
|
||||
SELF("_self"),
|
||||
TOP("_top"),
|
||||
|
||||
;
|
||||
|
||||
private final String label;
|
||||
|
||||
Target(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.permission;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.Getter;
|
||||
import xyz.zhouxy.plusone.domain.Entity;
|
||||
import xyz.zhouxy.plusone.domain.IWithLabel;
|
||||
import xyz.zhouxy.plusone.domain.IWithVersion;
|
||||
|
||||
/**
|
||||
* 行为
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class Action extends Entity<Long> implements IWithLabel, IWithVersion {
|
||||
|
||||
Long id;
|
||||
String resource;
|
||||
@Getter String identifier;
|
||||
@Getter String label;
|
||||
@Getter long version;
|
||||
|
||||
public Action(Long id, String resource, String identifier, String label, long version) {
|
||||
this.id = id;
|
||||
this.resource = resource;
|
||||
this.identifier = identifier;
|
||||
this.label = label;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
static Action newInstance(String resource, String identifier, String label) {
|
||||
return new Action(null, resource, identifier, label, 0L);
|
||||
}
|
||||
|
||||
static Action existingInstance(Long id, String resource, String action, String label, Long version) {
|
||||
return new Action(id, resource, action, label, version);
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return resource + identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getId() {
|
||||
return Optional.ofNullable(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.permission;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.Getter;
|
||||
import xyz.zhouxy.plusone.domain.AggregateRoot;
|
||||
import xyz.zhouxy.plusone.domain.IWithVersion;
|
||||
|
||||
/**
|
||||
* 权限
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public class Permission extends AggregateRoot<Long> implements IWithVersion {
|
||||
|
||||
private Long id;
|
||||
private @Getter String resource;
|
||||
|
||||
private List<Action> actions = new ArrayList<>(8);
|
||||
|
||||
private @Getter long version;
|
||||
|
||||
public Permission addAction(String action, String label) {
|
||||
return addAction(Action.newInstance(resource, action, label));
|
||||
}
|
||||
|
||||
public void removeAction(Long actionId) {
|
||||
this.actions.removeIf(action -> Objects.equals(actionId, action.getId().orElseThrow()));
|
||||
}
|
||||
|
||||
public void removeAction(String identifier) {
|
||||
this.actions.removeIf(action -> Objects.equals(identifier, action.identifier));
|
||||
}
|
||||
|
||||
// ==================== 实例化 ====================
|
||||
|
||||
public static Permission newInstance(String resource) {
|
||||
return new Permission(
|
||||
null, resource,
|
||||
List.of(Action.newInstance(resource, ":add", "添加"),
|
||||
Action.newInstance(resource, ":delete", "删除"),
|
||||
Action.newInstance(resource, ":update", "更改"),
|
||||
Action.newInstance(resource, ":query", "查询"),
|
||||
Action.newInstance(resource, ":details", "详情")),
|
||||
0);
|
||||
}
|
||||
|
||||
Permission(Long id, String resource, List<Action> actions, long version) {
|
||||
this.id = id;
|
||||
this.resource = resource;
|
||||
this.actions.addAll(actions);
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
// ==================== private ====================
|
||||
private Permission addAction(Action action) {
|
||||
this.actions.add(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
// ==================== getter ====================
|
||||
@Override
|
||||
public Optional<Long> getId() {
|
||||
return Optional.ofNullable(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Description
|
||||
|
||||
后期考虑菜单项与 Permission 绑定,使之与 Action 解耦,Permission 亦可以单独管理。
|
||||
@@ -0,0 +1,12 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.role;
|
||||
|
||||
/**
|
||||
* ActionRef
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public record ActionRef(Long actionId) {
|
||||
public static ActionRef of(Long actionId) {
|
||||
return new ActionRef(actionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.role;
|
||||
|
||||
/**
|
||||
* MenuRef
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
public record MenuRef(Long menuId) {
|
||||
public static MenuRef of(Long menuId) {
|
||||
return new MenuRef(menuId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.role;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import xyz.zhouxy.plusone.constant.EntityStatus;
|
||||
import xyz.zhouxy.plusone.domain.AggregateRoot;
|
||||
import xyz.zhouxy.plusone.domain.IWithVersion;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Action;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu;
|
||||
|
||||
/**
|
||||
* 聚合根:角色
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@ToString
|
||||
public class Role extends AggregateRoot<Long> implements IWithVersion {
|
||||
|
||||
private Long id;
|
||||
private @Getter String name;
|
||||
private @Getter String identifier;
|
||||
|
||||
private @Getter EntityStatus status;
|
||||
private @Getter String remarks;
|
||||
|
||||
private final Set<MenuRef> menus = new HashSet<>();
|
||||
private final Set<ActionRef> permissions = new HashSet<>();
|
||||
|
||||
private @Getter long version;
|
||||
|
||||
public void bindMenus(Set<MenuRef> menus) {
|
||||
this.menus.clear();
|
||||
this.menus.addAll(menus);
|
||||
}
|
||||
|
||||
public void bindPermissions(Set<ActionRef> permissions) {
|
||||
this.permissions.clear();
|
||||
this.permissions.addAll(permissions);
|
||||
}
|
||||
|
||||
public void update(
|
||||
String name,
|
||||
String identifier,
|
||||
EntityStatus status,
|
||||
String remarks,
|
||||
Set<Menu> menus,
|
||||
Set<Action> permissions) {
|
||||
this.name = name;
|
||||
this.identifier = identifier;
|
||||
this.status = status;
|
||||
this.remarks = remarks;
|
||||
bindMenus(menus.stream().map(menu -> new MenuRef(menu.getId().orElseThrow())).collect(Collectors.toSet()));
|
||||
bindPermissions(permissions
|
||||
.stream()
|
||||
.map(permission -> new ActionRef(permission.getId().orElseThrow()))
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
@Override
|
||||
public Optional<Long> getId() {
|
||||
return Optional.ofNullable(id);
|
||||
}
|
||||
|
||||
public Set<MenuRef> getMenus() {
|
||||
return Set.copyOf(menus);
|
||||
}
|
||||
|
||||
public Set<ActionRef> getPermissions() {
|
||||
return Set.copyOf(permissions);
|
||||
}
|
||||
|
||||
public static Role newInstance(
|
||||
String name,
|
||||
String identifier,
|
||||
EntityStatus status,
|
||||
String remarks,
|
||||
Set<MenuRef> menus,
|
||||
Set<ActionRef> permissions) {
|
||||
return new Role(null, name, identifier, status, remarks, menus, permissions, 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* @param id id
|
||||
* @param name 角色名
|
||||
* @param identifier 标识符
|
||||
* @param status 状态
|
||||
* @param remarks 备注
|
||||
* @param menus 菜单
|
||||
* @param permissions 权限
|
||||
* @param version 版本号
|
||||
*/
|
||||
Role(Long id, String name, String identifier, EntityStatus status, String remarks,
|
||||
Set<MenuRef> menus, Set<ActionRef> permissions, long version) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.identifier = identifier;
|
||||
this.status = status;
|
||||
this.remarks = remarks;
|
||||
bindMenus(menus);
|
||||
bindPermissions(permissions);
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.role;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import xyz.zhouxy.plusone.domain.IRepository;
|
||||
|
||||
public interface RoleRepository extends IRepository<Role, Long> {
|
||||
|
||||
Collection<Role> findByAccountId(Long accountId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package xyz.zhouxy.plusone.system.domain.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.MenuRepository;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.MenuRef;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.Role;
|
||||
import xyz.zhouxy.plusone.system.domain.model.role.RoleRepository;
|
||||
|
||||
/**
|
||||
* 领域服务:菜单服务
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Service
|
||||
public class MenuService {
|
||||
|
||||
private final RoleRepository roleRepository;
|
||||
private final MenuRepository menuRepository;
|
||||
|
||||
MenuService(RoleRepository roleRepository, MenuRepository menuRepository) {
|
||||
this.roleRepository = roleRepository;
|
||||
this.menuRepository = menuRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据账号 id 查询菜单列表(不形成树结构)
|
||||
* @param accountId
|
||||
* @return
|
||||
*/
|
||||
public Collection<Menu> queryAllMenuListByAccountId(Long accountId) {
|
||||
Collection<Role> roles = roleRepository.findByAccountId(accountId);
|
||||
Set<MenuRef> menuRefs = new HashSet<>();
|
||||
roles.forEach(role -> menuRefs.addAll(role.getMenus()));
|
||||
return menuRepository.findByIdIn(menuRefs.stream().map(MenuRef::menuId).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
class PasswordTests {
|
||||
|
||||
@Test
|
||||
void testNewDefaultPassword() {
|
||||
var pwd = Password.newDefaultPassword();
|
||||
log.debug("value -- {}; salt -- {}", pwd.value(), pwd.getSalt());
|
||||
assertTrue(pwd.check("A1b2C3d4"), "默认密码校验失败");
|
||||
}
|
||||
}
|
||||
23
plusone-system/plusone-system-infrastructure/pom.xml
Normal file
23
plusone-system/plusone-system-infrastructure/pom.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>plusone-system</artifactId>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>plusone-system-infrastructure</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-basic-infrastructure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xyz.zhouxy</groupId>
|
||||
<artifactId>plusone-system-domain</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,218 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import xyz.zhouxy.plusone.jdbc.JdbcRepositorySupport;
|
||||
import xyz.zhouxy.plusone.util.AssertResult;
|
||||
|
||||
/**
|
||||
* AccountRepository 实现类
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Repository
|
||||
public class AccountRepositoryImpl extends JdbcRepositorySupport<Account, Long> implements AccountRepository {
|
||||
|
||||
private final AccountRoleRefDAO accountRoleDAO;
|
||||
|
||||
public AccountRepositoryImpl(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
|
||||
super(namedParameterJdbcTemplate);
|
||||
this.accountRoleDAO = new AccountRoleRefDAO(namedParameterJdbcTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void doDelete(@Nonnull Account entity) {
|
||||
int i = this.jdbc.update("""
|
||||
UPDATE sys_account SET deleted = id, "version" = "version" + 1
|
||||
WHERE id = :id AND deleted = 0 AND "version" = :version
|
||||
""",
|
||||
new MapSqlParameterSource()
|
||||
.addValue("id", entity.getId().orElseThrow())
|
||||
.addValue("version", entity.getVersion()));
|
||||
AssertResult.update(i, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Account doFindById(@Nonnull Long id) {
|
||||
return queryForObject("""
|
||||
SELECT
|
||||
id, email, mobile_phone, username, "password", salt, avatar, sex, nickname, status,
|
||||
created_by, updated_by, "version"
|
||||
FROM sys_account
|
||||
WHERE id = :id AND deleted = 0
|
||||
""",
|
||||
new MapSqlParameterSource("id", id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account findByEmail(Email email) {
|
||||
return queryForObject("""
|
||||
SELECT
|
||||
id, email, mobile_phone, username, "password", salt, avatar, sex, nickname, status,
|
||||
created_by, updated_by, "version"
|
||||
FROM sys_account
|
||||
WHERE email = :email AND deleted = 0
|
||||
""",
|
||||
new MapSqlParameterSource("email", email.value()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account findByMobilePhone(MobilePhone mobilePhone) {
|
||||
return queryForObject("""
|
||||
SELECT
|
||||
id, email, mobile_phone, username, "password", salt, avatar, sex, nickname, status,
|
||||
created_by, updated_by, "version"
|
||||
FROM sys_account
|
||||
WHERE mobile_phone = :mobilePhone AND deleted = 0
|
||||
""",
|
||||
new MapSqlParameterSource("mobilePhone", mobilePhone.value()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account findByUsername(Username username) {
|
||||
return queryForObject("""
|
||||
SELECT
|
||||
id, email, mobile_phone, username, "password", salt, avatar, sex, nickname, status,
|
||||
created_by, updated_by, "version"
|
||||
FROM sys_account
|
||||
WHERE username = :username AND deleted = 0
|
||||
""",
|
||||
new MapSqlParameterSource("username", username.value()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(Long id) {
|
||||
return queryExists("SELECT 1 FROM sys_account WHERE id = :id AND deleted = 0 LIMIT 1",
|
||||
new MapSqlParameterSource("id", id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsUsername(Username username) {
|
||||
return queryExists("SELECT 1 FROM sys_account WHERE username = :username AND deleted = 0 LIMIT 1",
|
||||
new MapSqlParameterSource("username", username.value()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsEmail(Email email) {
|
||||
return queryExists("SELECT 1 FROM sys_account WHERE email = :email AND deleted = 0 LIMIT 1",
|
||||
new MapSqlParameterSource("email", email.value()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsMobilePhone(MobilePhone mobilePhone) {
|
||||
return queryExists("SELECT 1 FROM sys_account WHERE mobile_phone = :mobile_phone AND deleted = 0 LIMIT 1",
|
||||
new MapSqlParameterSource("mobile_phone", mobilePhone.value()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Account> findByRoleId(Long roleId) {
|
||||
return queryForList("""
|
||||
SELECT
|
||||
a.id, a.email, a.mobile_phone, a.username, a."password", a.salt,
|
||||
a.avatar, a.sex, a.nickname, a.status,
|
||||
a.created_by, a.updated_by, a."version"
|
||||
FROM sys_account a
|
||||
LEFT JOIN sys_account_role ar ON a.id = ar.account_id
|
||||
WHERE ar.role_id = :roleId AND a.deleted = 0
|
||||
""",
|
||||
new MapSqlParameterSource("roleId", roleId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Account doInsert(@Nonnull Account entity) {
|
||||
String sql = """
|
||||
INSERT INTO sys_account
|
||||
(id, email, mobile_phone, username, "password", salt, avatar, sex, nickname, status, created_by, create_time)
|
||||
VALUES
|
||||
(:id, :email, :mobilePhone, :username, :password, :salt, :avatar, :sex, :nickname, :status, :createdBy, :createTime)
|
||||
""";
|
||||
long id = IdUtil.getSnowflakeNextId();
|
||||
SqlParameterSource params = generateParamSource(id, entity);
|
||||
int i = jdbc.update(sql, params);
|
||||
AssertResult.update(i, 1);
|
||||
this.accountRoleDAO.insertAccountRoleRefs(id, entity.getRoleIds());
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Account doUpdate(@Nonnull Account entity) {
|
||||
String sql = """
|
||||
UPDATE sys_account
|
||||
SET "email" = :email,
|
||||
"mobile_phone" = :mobilePhone,
|
||||
"username" = :username,
|
||||
"password" = :password,
|
||||
"salt" = :salt,
|
||||
"avatar" = :avatar,
|
||||
"sex" = :sex,
|
||||
"nickname" = :nickname,
|
||||
"status" = :status,
|
||||
"updated_by" = :updatedBy,
|
||||
"update_time" = :updateTime,
|
||||
"version" = "version" + 1
|
||||
WHERE id = :id AND deleted = 0 AND "version" = :version
|
||||
""";
|
||||
SqlParameterSource params = generateParamSource(entity);
|
||||
int i = this.jdbc.update(sql, params);
|
||||
AssertResult.update(i, 1);
|
||||
this.accountRoleDAO.saveAccountRoleRefs(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Account mapRow(ResultSet rs) throws SQLException {
|
||||
long accountId = rs.getLong("id");
|
||||
AccountInfo accountInfo = AccountInfo.of(
|
||||
rs.getString("nickname"),
|
||||
rs.getString("avatar"),
|
||||
Sex.of(rs.getInt("sex")));
|
||||
return new Account(
|
||||
accountId,
|
||||
rs.getString("username"),
|
||||
rs.getString("email"),
|
||||
rs.getString("mobile_phone"),
|
||||
Password.of(rs.getString("password"), rs.getString("salt")),
|
||||
AccountStatus.of(rs.getInt("status")),
|
||||
accountInfo,
|
||||
this.accountRoleDAO.selectRoleIdsByAccountId(accountId),
|
||||
rs.getLong("created_by"),
|
||||
rs.getLong("updated_by"),
|
||||
rs.getLong("version"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final SqlParameterSource generateParamSource(Long id, @Nonnull Account entity) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
AccountInfo accountInfo = entity.getAccountInfo();
|
||||
return new MapSqlParameterSource()
|
||||
.addValue("id", id)
|
||||
.addValue("email", Objects.nonNull(entity.getEmail()) ? entity.getEmail().value() : null)
|
||||
.addValue("mobilePhone",
|
||||
Objects.nonNull(entity.getMobilePhone()) ? entity.getMobilePhone().value() : null)
|
||||
.addValue("username", entity.getUsername().value())
|
||||
.addValue("password", entity.getPassword().value())
|
||||
.addValue("salt", entity.getPassword().getSalt())
|
||||
.addValue("avatar", accountInfo.getAvatar().toString())
|
||||
.addValue("sex", accountInfo.getSex().getValue())
|
||||
.addValue("nickname",
|
||||
Objects.nonNull(accountInfo.getNickname()) ? accountInfo.getNickname().value() : null)
|
||||
.addValue("status", entity.getStatus().getValue())
|
||||
.addValue("createdBy", entity.getCreatedBy())
|
||||
.addValue("createTime", now)
|
||||
.addValue("updatedBy", entity.getUpdatedBy())
|
||||
.addValue("updateTime", now)
|
||||
.addValue("version", entity.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.account;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
|
||||
import xyz.zhouxy.plusone.util.AssertResult;
|
||||
import xyz.zhouxy.plusone.util.NumberUtil;
|
||||
|
||||
class AccountRoleRefDAO {
|
||||
private final NamedParameterJdbcTemplate jdbc;
|
||||
|
||||
AccountRoleRefDAO(NamedParameterJdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
Set<Long> selectRoleIdsByAccountId(Long accountId) {
|
||||
List<Long> roleRefs = this.jdbc.queryForList("""
|
||||
SELECT r.id FROM sys_role r RIGHT JOIN sys_account_role ar ON r.id = ar.role_id
|
||||
WHERE r.deleted = 0 AND ar.account_id = :accountId;
|
||||
""",
|
||||
new MapSqlParameterSource("accountId", accountId),
|
||||
Long.TYPE);
|
||||
return new HashSet<>(roleRefs);
|
||||
}
|
||||
|
||||
void clearAccountRoleRefs(Account entity) {
|
||||
var param = new MapSqlParameterSource("accountId", entity.getId().orElseThrow());
|
||||
this.jdbc.update("DELETE FROM sys_account_role WHERE account_id = :accountId", param);
|
||||
}
|
||||
|
||||
void insertAccountRoleRefs(Long accountId, Set<Long> roleRefs) {
|
||||
String sql = "INSERT INTO sys_account_role (account_id, role_id) VALUES (:accountId, :roleId)";
|
||||
MapSqlParameterSource[] batchArgs = roleRefs
|
||||
.stream()
|
||||
.map((Long roleId) -> new MapSqlParameterSource()
|
||||
.addValue("accountId", accountId)
|
||||
.addValue("roleId", roleId))
|
||||
.toArray(MapSqlParameterSource[]::new);
|
||||
int[] i = this.jdbc.batchUpdate(sql, batchArgs);
|
||||
AssertResult.update(roleRefs.size(), NumberUtil.sum(i));
|
||||
}
|
||||
|
||||
void saveAccountRoleRefs(Account entity) {
|
||||
Long accountId = entity.getId().orElseThrow();
|
||||
Set<Long> roleRefs = entity.getRoleIds();
|
||||
clearAccountRoleRefs(entity);
|
||||
insertAccountRoleRefs(accountId, roleRefs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.dict;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import xyz.zhouxy.plusone.jdbc.JdbcRepositorySupport;
|
||||
import xyz.zhouxy.plusone.util.AssertResult;
|
||||
|
||||
/**
|
||||
* DictRepository 实现类
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Repository
|
||||
public class DictRepositoryImpl extends JdbcRepositorySupport<Dict, Long> implements DictRepository {
|
||||
|
||||
private final DictValueDAO dictValueDAO;
|
||||
|
||||
public DictRepositoryImpl(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
|
||||
super(namedParameterJdbcTemplate);
|
||||
this.dictValueDAO = new DictValueDAO(namedParameterJdbcTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dict doFindById(@Nonnull Long id) {
|
||||
return queryForObject("SELECT id, dict_type, dict_label, \"version\" WHERE id = :id AND deleted = 0",
|
||||
new MapSqlParameterSource("id", id));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Dict doInsert(@Nonnull Dict entity) {
|
||||
long id = IdUtil.getSnowflakeNextId();
|
||||
int i = this.jdbc.update("""
|
||||
INSERT INTO sys_dict_type (dict_type, dict_label, create_time, created_by)
|
||||
VALUES (:dictType, :dictLabel, :createTime, :createdBy)
|
||||
""",
|
||||
generateParamSource(id, entity));
|
||||
AssertResult.update(i, 1);
|
||||
this.dictValueDAO.insertDictValues(id, entity);
|
||||
return find(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Dict doUpdate(@Nonnull Dict entity) {
|
||||
int i = this.jdbc.update("""
|
||||
UPDATE sys_dict_type
|
||||
SET dict_type = :dictType,
|
||||
dict_label = :dictLabel,
|
||||
update_time = :updateTime,
|
||||
updated_by = :updatedBy,
|
||||
"version" = "version" + 1
|
||||
WHERE id = :id AND deleted = 0 AND "version" = :version
|
||||
""",
|
||||
generateParamSource(entity));
|
||||
AssertResult.update(i, 1);
|
||||
this.dictValueDAO.updateDictValues(entity);
|
||||
return find(entity.getId().orElseThrow());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void doDelete(@Nonnull Dict entity) {
|
||||
int i = this.jdbc.update("""
|
||||
UPDATE sys_dict_type SET deleted = id, "version" = "version" + 1
|
||||
WHERE id = :id AND deleted = 0 AND "version" = :version
|
||||
""",
|
||||
generateParamSource(entity));
|
||||
AssertResult.update(i, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(Long id) {
|
||||
return queryExists("SELECT 1 FROM sys_dict_type WHERE id = :id AND deleted = 0 LIMIT 1",
|
||||
new MapSqlParameterSource("id", id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Dict> findAll() {
|
||||
return queryForList("SELECT id, dict_type, dict_label, \"version\" WHERE deleted = 0");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Dict mapRow(ResultSet rs) throws SQLException {
|
||||
long id = rs.getLong("id");
|
||||
return new Dict(
|
||||
id,
|
||||
rs.getString("dict_type"),
|
||||
rs.getString("dict_label"),
|
||||
this.dictValueDAO.selectDictValuesByDictId(id),
|
||||
rs.getLong("version"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final SqlParameterSource generateParamSource(Long id, @Nonnull Dict entity) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
long loginId = adminAuthLogic.getLoginIdAsLong();
|
||||
return new MapSqlParameterSource()
|
||||
.addValue("dictType", entity.getDictType())
|
||||
.addValue("dictLabel", entity.getLabel())
|
||||
.addValue("createTime", now)
|
||||
.addValue("createdBy", loginId)
|
||||
.addValue("updateTime", now)
|
||||
.addValue("updatedBy", loginId)
|
||||
.addValue("id", id)
|
||||
.addValue("version", entity.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.dict;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import xyz.zhouxy.plusone.util.AssertResult;
|
||||
import xyz.zhouxy.plusone.util.NumberUtil;
|
||||
|
||||
class DictValueDAO {
|
||||
private final NamedParameterJdbcTemplate jdbc;
|
||||
|
||||
DictValueDAO(NamedParameterJdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
void updateDictValues(Dict entity) {
|
||||
MapSqlParameterSource deleteParam = new MapSqlParameterSource("dictType", entity.getId().orElseThrow());
|
||||
this.jdbc.update("DELETE FROM sys_dict_value WHERE dict_type = :dictType", deleteParam);
|
||||
int i = insertDictValues(entity.getId().orElseThrow(), entity);
|
||||
AssertResult.update(i, entity.count());
|
||||
}
|
||||
|
||||
int insertDictValues(Long dictId, Dict entity) {
|
||||
if (Objects.isNull(dictId) || Objects.isNull(entity) || CollectionUtils.isEmpty(entity.getValues())) {
|
||||
return 0;
|
||||
}
|
||||
int[] i = this.jdbc.batchUpdate(
|
||||
"INSERT INTO sys_dict_value (dict_type, dict_key, label) VALUES (:dictType, :dictKey, :label)",
|
||||
entity.getValues().stream()
|
||||
.map(dictValue -> new MapSqlParameterSource()
|
||||
.addValue("dictType", dictId)
|
||||
.addValue("dictKey", dictValue.getKey())
|
||||
.addValue("label", dictValue.getLabel()))
|
||||
.toArray(SqlParameterSource[]::new));
|
||||
return NumberUtil.sum(i);
|
||||
}
|
||||
|
||||
Set<DictValue> selectDictValuesByDictId(long id) {
|
||||
return this.jdbc.queryForStream("""
|
||||
SELECT dict_key, label FROM sys_dict_value WHERE dict_type = :dictType
|
||||
""", new MapSqlParameterSource("dictType", id),
|
||||
(rs, rowNum) -> DictValue.of(rs.getInt("dict_key"), rs.getString("label")))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.menu;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import xyz.zhouxy.plusone.jdbc.JdbcEntityDaoSupport;
|
||||
|
||||
/**
|
||||
* {@link Action} 的数据访问对象
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
* @date 2022-10-31 12:31:49
|
||||
*/
|
||||
class ActionDAO extends JdbcEntityDaoSupport<Action, Long> {
|
||||
|
||||
ActionDAO(@Nonnull NamedParameterJdbcTemplate jdbc) {
|
||||
super(jdbc);
|
||||
}
|
||||
|
||||
void saveActions(Long menuId, List<Action> actions) {
|
||||
// 删除要删除的权限
|
||||
Collection<Long> ids = actions.stream()
|
||||
.filter(action -> action.getId().isPresent())
|
||||
.map(action -> action.getId().orElseThrow())
|
||||
.collect(Collectors.toSet());
|
||||
if (!ids.isEmpty()) {
|
||||
this.jdbc.update(
|
||||
"UPDATE sys_action SET deleted = id WHERE resource = :resource AND id NOT IN (:ids) AND deleted = 0",
|
||||
new MapSqlParameterSource()
|
||||
.addValue("resource", menuId)
|
||||
.addValue("ids", ids));
|
||||
}
|
||||
|
||||
// 更新存在的数据
|
||||
this.jdbc.batchUpdate("""
|
||||
UPDATE sys_action
|
||||
SET resource = :resource,
|
||||
identifier = :identifier,
|
||||
label = :label,
|
||||
update_time = :updateTime,
|
||||
updated_by = :updatedBy
|
||||
WHERE id = :id AND deleted = 0
|
||||
""",
|
||||
actions.stream()
|
||||
.filter(action -> action.getId().isPresent())
|
||||
.map(action -> generateParamSource(menuId, action))
|
||||
.toArray(MapSqlParameterSource[]::new));
|
||||
|
||||
// 插入新添加的数据
|
||||
this.jdbc.batchUpdate("""
|
||||
INSERT INTO sys_action
|
||||
(id, resource, identifier, "label", create_time, created_by)
|
||||
VALUES
|
||||
(:id, :resource, :identifier, :label, :createTime, :createdBy)
|
||||
""",
|
||||
actions.stream()
|
||||
.filter(action -> action.getId().isEmpty())
|
||||
.map(action -> generateParamSource(menuId, IdUtil.getSnowflakeNextId(), action))
|
||||
.toArray(MapSqlParameterSource[]::new));
|
||||
}
|
||||
|
||||
List<Action> selectActionsByMenuId(long menuId) {
|
||||
return queryForList("""
|
||||
SELECT a.id, m.resource, a.identifier, a.label
|
||||
FROM sys_action a
|
||||
JOIN (SELECT id, resource FROM sys_menu WHERE id = :menuId AND deleted = 0) m ON a.resource = m.id
|
||||
WHERE a.deleted = 0
|
||||
""", new MapSqlParameterSource("menuId", menuId));
|
||||
}
|
||||
|
||||
Collection<Action> selectActionsByIdIn(Collection<Long> actionIds) {
|
||||
if (Objects.isNull(actionIds) || actionIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return queryForList("""
|
||||
SELECT a.id, m.resource, a.identifier, a.label
|
||||
FROM sys_action a
|
||||
LEFT JOIN sys_menu m ON a.resource = m.id
|
||||
WHERE a.id IN (:actionIds) AND a.deleted = 0
|
||||
""", new MapSqlParameterSource("actionIds", actionIds));
|
||||
}
|
||||
|
||||
private SqlParameterSource generateParamSource(Long menuId, Action action) {
|
||||
return generateParamSource(menuId, action.getId().orElseThrow(), action);
|
||||
}
|
||||
|
||||
private SqlParameterSource generateParamSource(Long menuId, Long actionId, Action action) {
|
||||
long loginId = adminAuthLogic.getLoginIdAsLong();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
return new MapSqlParameterSource("id", actionId)
|
||||
.addValue("resource", menuId)
|
||||
.addValue("identifier", action.getIdentifier())
|
||||
.addValue("label", action.getLabel())
|
||||
.addValue("createTime", now)
|
||||
.addValue("createdBy", loginId)
|
||||
.addValue("updateTime", now)
|
||||
.addValue("updatedBy", loginId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Action mapRow(ResultSet rs) throws SQLException {
|
||||
return Action.of(
|
||||
rs.getLong("id"),
|
||||
rs.getString("resource"),
|
||||
rs.getString("identifier"),
|
||||
rs.getString("label"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package xyz.zhouxy.plusone.system.domain.model.menu;
|
||||
|
||||
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import xyz.zhouxy.plusone.constant.EntityStatus;
|
||||
import xyz.zhouxy.plusone.jdbc.JdbcRepositorySupport;
|
||||
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
|
||||
import xyz.zhouxy.plusone.util.AssertResult;
|
||||
import xyz.zhouxy.plusone.util.EnumUtil;
|
||||
|
||||
/**
|
||||
* MenuRepository 实现类
|
||||
*
|
||||
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||
*/
|
||||
@Repository
|
||||
public class MenuRepositoryImpl extends JdbcRepositorySupport<Menu, Long> implements MenuRepository {
|
||||
|
||||
private final ActionDAO actionDAO;
|
||||
|
||||
MenuRepositoryImpl(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
|
||||
super(namedParameterJdbcTemplate);
|
||||
this.actionDAO = new ActionDAO(namedParameterJdbcTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Menu doFindById(@Nonnull Long id) {
|
||||
return queryForObject("""
|
||||
SELECT
|
||||
id, parent_id, "type", "name", "path", title, icon, hidden, order_number, status, remarks,
|
||||
component, "cache", resource, "version"
|
||||
FROM sys_menu
|
||||
WHERE id = :id AND deleted = 0
|
||||
""",
|
||||
new MapSqlParameterSource("id", id));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Menu doInsert(@Nonnull Menu entity) {
|
||||
long id = IdUtil.getSnowflakeNextId();
|
||||
String sql = """
|
||||
INSERT INTO sys_menu (
|
||||
id, parent_id, "type", name, "path", title, icon, hidden, order_number, status, remarks,
|
||||
component, "cache", resource, create_time, created_by)
|
||||
VALUES
|
||||
(:id, :parentId, :type, :name, :path, :title, :icon, :hidden, :orderNumber, :status, :remarks,
|
||||
:component, :cache, :resource, :createTime, :createdBy)
|
||||
""";
|
||||
MapSqlParameterSource paramSource = generateParamSource(id, entity);
|
||||
int i = this.jdbc.update(sql, paramSource);
|
||||
AssertResult.update(i, 1);
|
||||
this.actionDAO.saveActions(id, entity.getActions());
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Menu doUpdate(@Nonnull Menu entity) {
|
||||
String sql = """
|
||||
UPDATE sys_menu
|
||||
SET "parent_id" = :parentId,
|
||||
"type" = :type,
|
||||
"name" = :name,
|
||||
"path" = :path,
|
||||
"title" = :title,
|
||||
"icon" = :icon,
|
||||
"hidden" = :hidden,
|
||||
"order_number" = :orderNumber,
|
||||
"status" = :status,
|
||||
"remarks" = :remarks,
|
||||
"component" = :component,
|
||||
"cache" = :cache,
|
||||
"resource" = :resource,
|
||||
"update_time" = :updateTime,
|
||||
"updated_by" = :updatedBy,
|
||||
"version" = "version" + 1
|
||||
WHERE id = :id AND deleted = 0 AND "version" = :version
|
||||
""";
|
||||
|
||||
// 更新菜单
|
||||
int i = this.jdbc.update(sql, generateParamSource(entity));
|
||||
AssertResult.update(i, 1);
|
||||
|
||||
// 保存权限
|
||||
Long id = entity.getId().orElseThrow();
|
||||
this.actionDAO.saveActions(id, entity.getActions());
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void doDelete(@Nonnull Menu entity) {
|
||||
int i = this.jdbc.update("""
|
||||
UPDATE sys_menu SET deleted = id, "version" = "version" + 1
|
||||
WHERE id = :id AND deleted = 0 AND "version" = :version
|
||||
""",
|
||||
new MapSqlParameterSource("id", entity.getId().orElseThrow())
|
||||
.addValue("version", entity.getVersion()));
|
||||
AssertResult.update(i, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(Long id) {
|
||||
return queryExists("SELECT 1 FROM sys_menu WHERE id = :id AND deleted = 0 LIMIT 1",
|
||||
new MapSqlParameterSource("id", id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Menu> findByIdIn(Collection<Long> ids) {
|
||||
if (Objects.isNull(ids) || ids.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return queryForList("""
|
||||
SELECT
|
||||
id, parent_id, "type", "name", "path", title, icon, hidden, order_number, status, remarks,
|
||||
component, "cache", resource, "version"
|
||||
FROM sys_menu
|
||||
WHERE id IN (:ids) AND deleted = 0
|
||||
""",
|
||||
new MapSqlParameterSource("ids", ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Action> findPermissionsByIdIn(Collection<Long> permissionIds) {
|
||||
return this.actionDAO.selectActionsByIdIn(permissionIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Menu> queryByRoleId(Long roleId) {
|
||||
return queryForList("""
|
||||
SELECT
|
||||
m.id, m.parent_id, m."type", m."name", m."path", m.title, m.icon, m.hidden, m.order_number,
|
||||
m.status, m.remarks, m.component, m."cache", m.resource, m."version"
|
||||
FROM sys_menu AS m
|
||||
LEFT JOIN sys_role_menu AS rm ON m.id = rm.menu_id
|
||||
WHERE rm.role_id = :roleId AND r.deleted = 0
|
||||
""",
|
||||
new MapSqlParameterSource("roleId", roleId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Menu mapRow(ResultSet rs) throws SQLException {
|
||||
long menuId = rs.getLong("id");
|
||||
return new Menu(
|
||||
EnumUtil.valueOf(MenuType.class, rs.getInt("type")),
|
||||
menuId,
|
||||
rs.getLong("parent_id"),
|
||||
rs.getString("name"),
|
||||
rs.getString("path"),
|
||||
rs.getString("title"),
|
||||
rs.getString("icon"),
|
||||
rs.getBoolean("hidden"),
|
||||
rs.getInt("order_number"),
|
||||
EntityStatus.of(rs.getInt("status")),
|
||||
rs.getString("remarks"),
|
||||
rs.getString("component"),
|
||||
rs.getBoolean("cache"),
|
||||
rs.getString("resource"),
|
||||
this.actionDAO.selectActionsByMenuId(menuId),
|
||||
rs.getLong("version"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final MapSqlParameterSource generateParamSource(Long id, @Nonnull Menu entity) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
long loginId = adminAuthLogic.getLoginIdAsLong();
|
||||
return new MapSqlParameterSource()
|
||||
.addValue("id", id)
|
||||
.addValue("parentId", entity.getParentId())
|
||||
.addValue("type", entity.getType().value())
|
||||
.addValue("name", entity.getName())
|
||||
.addValue("path", entity.getPath())
|
||||
.addValue("title", entity.getTitle())
|
||||
.addValue("icon", entity.getIcon())
|
||||
.addValue("hidden", entity.isHidden())
|
||||
.addValue("orderNumber", entity.getOrderNumber())
|
||||
.addValue("status", entity.getStatus().getValue())
|
||||
.addValue("remarks", entity.getRemarks())
|
||||
.addValue("component", entity.getComponent())
|
||||
.addValue("cache", entity.getCache())
|
||||
.addValue("resource", entity.getResource())
|
||||
.addValue("createTime", now)
|
||||
.addValue("createdBy", loginId)
|
||||
.addValue("updateTime", now)
|
||||
.addValue("updatedBy", loginId)
|
||||
.addValue("version", entity.getVersion());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user