first commit.
This commit is contained in:
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"), "默认密码校验失败");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user