47 Commits

Author SHA1 Message Date
68f6ce5b8f Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-07-07 15:34:45 +08:00
bd42da6777 优化 MenuViewObject。 2023-07-07 15:33:49 +08:00
c4e7b8fd26 格式化代码。 2023-06-29 02:03:24 +08:00
e34efdfc56 去除 TODO 注释。 2023-06-29 01:44:28 +08:00
7077b84317 修改 MenuType。 2023-06-29 01:44:06 +08:00
79466d180f 优化代码。 2023-06-29 01:43:43 +08:00
dcb86f9100 标识过时方法。 2023-06-29 01:43:08 +08:00
e972899fbb IWithCode 相关接口的使用调整。 2023-06-29 01:41:55 +08:00
aa7f10008f NumberUtil 改为 Numbers。 2023-06-29 01:39:43 +08:00
b32dac3b8a ValidatableStringRecord 移动到 plusone-commons 并优化。 2023-06-29 01:37:35 +08:00
2f07ce0e5f Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-06-29 01:32:42 +08:00
30f67dfa18 领域事件相关建模。 2023-06-29 01:28:14 +08:00
87942d708a 正则相关的操作由 RegexUtil 负责。 2023-06-29 01:27:14 +08:00
d05fcbfc9c 更新 Spring Boot 版本。 2023-06-29 01:26:39 +08:00
363460fe96 调整枚举类。 2023-06-29 01:26:17 +08:00
f83a05e429 改用 ValueSet 的静态工厂方法。 2023-05-16 17:41:29 +08:00
92896a0345 Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-05-16 17:39:21 +08:00
ac3fdae2d0 改用 ValueSet 的静态工厂方法。 2023-05-16 17:39:09 +08:00
8dcf8eadac 修改注释。 2023-05-16 17:38:37 +08:00
39d8eac578 删除多余的导入。 2023-04-28 23:42:15 +08:00
3858aa16e1 更新 Spring Boot 版本。 2023-04-28 23:41:15 +08:00
4d3d0f7cc7 commit. 2023-04-20 21:29:23 +08:00
17b5b55d59 添加些许规约。 2023-04-19 19:24:00 +08:00
30f29bb4b3 更改 Password 实现。 2023-04-19 06:05:02 +08:00
104bffb0e8 Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-04-16 04:33:31 +08:00
62607dc0a4 plusone-exception-handler 的代码调整。 2023-04-16 00:54:59 +08:00
fe03b6da4d 静态工厂方法添加 @StaticFactoryMethod 注解。 2023-04-16 00:16:11 +08:00
0f145e383e 重构 FastDFSFile。 2023-04-16 00:08:36 +08:00
89584d2a48 重构代码。 2023-04-16 00:08:35 +08:00
f3017e90c0 修改错别字。 2023-04-16 00:08:35 +08:00
f1d16808c6 添加常量。 2023-04-16 00:08:35 +08:00
8460b9da29 修改部分 jsr305 注解的使用。 2023-04-16 00:08:04 +08:00
19fc97362c 更改 Exception Handler 版本。 2023-04-16 00:06:36 +08:00
d91f818c96 Merge remote-tracking branch 'origin/main' into main 2023-04-15 22:06:58 +08:00
65d77f35c1 静态工厂方法添加 @StaticFactoryMethod 注解。 2023-04-15 14:01:46 +08:00
b14c03fc32 静态工厂方法添加 @StaticFactoryMethod 注解。 2023-04-15 14:00:20 +08:00
ba34fa4a2b 重构 FastDFSFile。 2023-04-15 13:38:00 +08:00
b015f5d1c4 重构代码。 2023-04-15 13:37:44 +08:00
6335fa03b1 Merge branch 'dev' of http://zhouxy.xyz:3000/ZhouXY108/plusone-admin into dev 2023-04-15 12:26:02 +08:00
5215fded9c 修改错别字。 2023-04-06 16:21:54 +08:00
c30cca8f6a 添加常量。 2023-04-04 18:53:38 +08:00
ea7a8ee6a0 Merge pull request '整合多次更改' (#1) from dev into main
Reviewed-on: http://zhouxy.xyz:3000/ZhouXY108/plusone-admin/pulls/1
2023-03-29 18:54:03 +08:00
33d3d86393 Merge branch 'dev' into main 2023-02-25 00:32:34 +08:00
1b551eeb4c Merge branch 'dev' into main 2023-02-17 15:57:05 +08:00
1611bf38e8 Merge branch 'dev' into main 2023-01-28 17:37:12 +08:00
17273b0bc6 Merge pull request 'dev' (#3) from dev into master
Reviewed-on: http://zhouxy.xyz:3000/ZhouXY108/plusone-admin/pulls/3
2022-12-17 22:55:06 +08:00
2d02899372 Merge pull request 'dev' (#1) from dev into master
Reviewed-on: http://zhouxy.xyz:3000/ZhouXY108/plusone-admin/pulls/1
2022-12-10 03:46:53 +08:00
71 changed files with 638 additions and 310 deletions

View File

@@ -19,3 +19,10 @@
目前项目还没完成,开发中……
相关的文档和介绍完善中……
## 编码规约
### 关于 null
1. 方法默认参数不为 `null`**可以**在 Javadoc 中对参数进行说明,如“...(must not be {@code null}[ or empty string])”。方法内部**必须**做参数校验,如使用 Assert 等工具类。
2. 如果参数允许为空,**必须**为参数添加 `@Nullable` 注解,并在 Javadoc 中对参数进行说明,如“...(may be {@code null})”。
3. 除极特殊的情况,方法默认**不返回 `null`**,如可能返回一个对象表示值的缺失,则**必须**使用 `Optional`**绝不在返回类型为 Optional 的方法中返回 `null`**。
4. 在极其特殊的情况下,方法需要返回 `null`**必须**给方法加上 `@Nullable` 注解,并在 Javadoc 中详细说明。

View File

@@ -1,10 +1,13 @@
package xyz.zhouxy.plusone.exception.handler;
import javax.annotation.Nonnull;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler.ExceptionInfoHolder;
import xyz.zhouxy.plusone.commons.exception.handler.AllExceptionHandler;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler.ExceptionInfoHolder;
/**
* AllExceptionHandlerConfig
@@ -14,7 +17,7 @@ import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler.ExceptionInfoHo
public class AllExceptionHandlerConfig {
@Bean
AllExceptionHandler getAllExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
AllExceptionHandler getAllExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
return new AllExceptionHandler(exceptionInfoHolder);
}
}

View File

@@ -1,5 +1,7 @@
package xyz.zhouxy.plusone.exception.handler;
import javax.annotation.Nonnull;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@@ -11,6 +13,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.commons.util.RestfulResult;
/**
@@ -40,7 +43,7 @@ import xyz.zhouxy.plusone.commons.util.RestfulResult;
@Order(Ordered.LOWEST_PRECEDENCE - 1)
@Slf4j
public class DefaultExceptionHandler extends BaseExceptionHandler {
public DefaultExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
public DefaultExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
super(exceptionInfoHolder);
set(IllegalArgumentException.class, 4010000, "格式错误", HttpStatus.FORBIDDEN);
set(DataAccessException.class, 6030000, "数据库错误", HttpStatus.INTERNAL_SERVER_ERROR, true);

View File

@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.commons.util.RestfulResult;
import xyz.zhouxy.plusone.exception.SysException;
@@ -15,7 +16,7 @@ import xyz.zhouxy.plusone.exception.SysException;
@Slf4j
public class SysExceptionHandler extends BaseExceptionHandler {
public SysExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
public SysExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
super(exceptionInfoHolder);
}

View File

@@ -49,7 +49,7 @@
<dependency>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-exception-handler</artifactId>
<version>0.0.8-SNAPSHOT</version>
<version>0.0.9-SNAPSHOT</version>
</dependency>
</dependencies>

View File

@@ -2,6 +2,8 @@ package xyz.zhouxy.plusone.constant;
public class ErrorCodeConsts {
public static final int DEFAULT_ERROR_CODE = 9999999;
public static final int DEFAULT_SYS_ERROR_CODE = 5000000;
public static final int DEFAULT_BIZ_ERROR_CODE = 4000000;
private ErrorCodeConsts() {
throw new IllegalStateException("Utility class");

View File

@@ -1,9 +1,5 @@
package xyz.zhouxy.plusone.util;
import java.util.Objects;
import java.util.regex.Pattern;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -27,17 +23,4 @@ public class PlusoneStrUtil {
return StringUtils.hasLength(value) ? value : EMPTY_STR;
}
public static String checkString(String value, String regex, String message) {
Assert.notNull(value, message);
Assert.isTrue(Pattern.matches(regex, value), message);
return value;
}
public static String checkStringNullable(String value, String regex, String message) {
return Objects.nonNull(value) ? checkString(value, regex, message) : null;
}
public static String checkStringOrDefault(String value, String regex, String message) {
return StringUtils.hasText(value) ? checkString(value, regex, message) : "";
}
}

View File

@@ -1,6 +1,6 @@
package xyz.zhouxy.plusone.util;
import java.util.concurrent.ThreadLocalRandom;
import java.security.SecureRandom;
public final class RandomUtil {
private RandomUtil() {
@@ -8,7 +8,7 @@ public final class RandomUtil {
}
public static String randomStr(char[] sourceCharacters, int length) {
ThreadLocalRandom random = ThreadLocalRandom.current();
SecureRandom random = new SecureRandom();
char[] result = new char[length];
for (int i = 0; i < length; i++) {
result[i] = sourceCharacters[random.nextInt(sourceCharacters.length)];

View File

@@ -21,15 +21,12 @@ public final class EntityStatus extends Enumeration<EntityStatus> {
public static final EntityStatus AVAILABLE = new EntityStatus(0, "正常");
public static final EntityStatus DISABLED = new EntityStatus(1, "禁用");
private static final ValueSet<EntityStatus> VALUE_SET = new ValueSet<>(
AVAILABLE, DISABLED);
private static final ValueSet<EntityStatus> VALUE_SET = ValueSet.of(AVAILABLE, DISABLED);
@Nonnull
public static EntityStatus of(int id) {
return VALUE_SET.get(id);
}
@Nonnull
public static Collection<EntityStatus> constants() {
return VALUE_SET.getValues();
}

View File

@@ -17,8 +17,8 @@ import lombok.Getter;
*/
@Getter
public abstract class DomainEvent {
private String identifier;
private long happenedAt;
private final String identifier;
private final long happenedAt;
protected DomainEvent() {
this.identifier = UUID.randomUUID().toString();

View File

@@ -5,6 +5,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import xyz.zhouxy.plusone.commons.annotation.Overridable;
/**
* 实体
*
@@ -26,7 +28,29 @@ public abstract class Entity<ID extends Serializable> {
public abstract Optional<ID> getId();
protected void addDomainEvent(DomainEvent domainEvent) {
protected final void addDomainEvent(DomainEvent domainEvent) {
domainEvents.add(domainEvent);
}
public final List<DomainEvent> getDomainEvents() {
return List.copyOf(domainEvents);
}
@Override
@Overridable
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
@Overridable
public int hashCode() {
return super.hashCode();
}
@Override
@Overridable
public String toString() {
return super.toString();
}
}

View File

@@ -0,0 +1,8 @@
package xyz.zhouxy.plusone.domain;
public interface IDomainEventPublisher {
void publish(DomainEvent domainEvent);
<T extends DomainEvent> void subsrcibe(IDomainEventSubscriber<T> subscriber);
}

View File

@@ -0,0 +1,11 @@
package xyz.zhouxy.plusone.domain;
/**
* 领域事件消费者
*/
public interface IDomainEventSubscriber<T extends DomainEvent> {
void handleEvent(final T event);
Class<T> subscribedToEventType();
}

View File

@@ -1,41 +0,0 @@
package xyz.zhouxy.plusone.domain;
import java.util.Optional;
import java.util.regex.Pattern;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* 带校验的字符串值对象
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
public abstract class ValidatableStringRecord implements IValueObject {
protected String value;
protected final Pattern format;
protected ValidatableStringRecord(Pattern format) {
this.format = format;
}
@JsonIgnore
protected boolean isValid() {
return format.matcher(value).matches();
}
@JsonValue
public String value() {
return value;
}
@Override
public String toString() {
return value;
}
public static String getValueOrNull(Optional<? extends ValidatableStringRecord> s) {
return s.isPresent() ? s.get().value() : null;
}
}

View File

@@ -5,7 +5,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import xyz.zhouxy.plusone.constant.ErrorCodeConsts;
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler.ExceptionInfoHolder;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler.ExceptionInfoHolder;
@Configuration
public class PlusoneExceptionHandlerConfig {

View File

@@ -3,6 +3,9 @@ package xyz.zhouxy.plusone.jdbc;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.lang.Nullable;
import xyz.zhouxy.plusone.commons.exception.IWithCode;
import xyz.zhouxy.plusone.commons.exception.IWithIntCode;
/**
* 扩展了 {@link BeanPropertySqlParameterSource},在将 POJO 转换为
* {@link org.springframework.jdbc.core.namedparam.SqlParameterSource} 时,
@@ -25,8 +28,14 @@ public class BeanPropertyParamSource extends BeanPropertySqlParameterSource {
@Nullable
public Object getValue(String paramName) throws IllegalArgumentException {
Object value = super.getValue(paramName);
if (value instanceof Enum) {
return ((Enum<?>) value).ordinal();
if (value instanceof Enum<?> e) {
if (value instanceof IWithCode<?> c) {
return c.getCode();
}
if (value instanceof IWithIntCode c) {
return c.getCode();
}
return e.ordinal();
}
return value;
}

View File

@@ -63,10 +63,12 @@ public abstract class JdbcEntityDaoSupport<T extends Entity<ID>, ID extends Seri
protected abstract T mapRow(ResultSet rs) throws SQLException;
@Deprecated
protected void setRowMapper(@Nonnull RowMapper<T> rowMapper) {
this.rowMapper = rowMapper;
}
@Deprecated
protected void setResultSetExtractor(@Nonnull ResultSetExtractor<Optional<T>> resultSetExtractor) {
this.resultSetExtractor = resultSetExtractor;
}

View File

@@ -7,8 +7,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import xyz.zhouxy.plusone.spring.SpringContextHolder;
/**
* 全局单例,由 Spring 装配好的对象。
* 可通过静态方法获取 Spring 容器中的 {@link JdbcTemplate} 和
* 通过静态方法获取 Spring 容器中的 {@link JdbcTemplate} 和
* {@link NamedParameterJdbcTemplate} 对象。
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>

View File

@@ -8,6 +8,9 @@ import javax.annotation.Nonnull;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import com.google.common.base.Preconditions;
import xyz.zhouxy.plusone.commons.annotation.Overridable;
import xyz.zhouxy.plusone.domain.AggregateRoot;
import xyz.zhouxy.plusone.domain.IRepository;
@@ -19,35 +22,33 @@ public abstract class JdbcRepositorySupport<T extends AggregateRoot<ID>, ID exte
super(namedParameterJdbcTemplate);
}
@Overridable
protected abstract void doDelete(@Nonnull T entity);
@Overridable
protected abstract Optional<T> doFindById(@Nonnull ID id);
@Overridable
protected abstract T doInsert(@Nonnull T entity);
@Overridable
protected abstract T doUpdate(@Nonnull T entity);
@Override
public final void delete(T entity) {
if (entity == null) {
throw new IllegalArgumentException("Cannot delete null.");
}
Preconditions.checkArgument(entity != null, "Cannot delete null.");
doDelete(entity);
}
@Override
public final Optional<T> find(ID id) {
if (id == null) {
throw new IllegalArgumentException("Id cannot be null.");
}
Preconditions.checkArgument(id != null, "Id cannot be null.");
return doFindById(id);
}
@Override
public final T save(T entity) {
if (entity == null) {
throw new IllegalArgumentException("Cannot save null.");
}
Preconditions.checkArgument(entity != null, "Cannot save null.");
return entity.getId().isPresent() ? doUpdate(entity) : doInsert(entity);
}

View File

@@ -16,7 +16,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.util.CollectionUtils;
import xyz.zhouxy.plusone.commons.util.NumberUtil;
import xyz.zhouxy.plusone.commons.util.Numbers;
import xyz.zhouxy.plusone.exception.DataOperationResultException;
public abstract class PlusoneJdbcDaoSupport {
@@ -88,19 +88,19 @@ public abstract class PlusoneJdbcDaoSupport {
protected final long batchUpdate(String sql, SqlParameterSource[] batchArgs) {
int[] i = this.jdbc.batchUpdate(sql, batchArgs);
return NumberUtil.sum(i);
return Numbers.sum(i);
}
protected final <T> long batchUpdate(String sql, Stream<T> c,
@Nonnull Function<T, SqlParameterSource> paramSourceBuilder) {
int[] i = this.jdbc.batchUpdate(sql, buildSqlParameterSourceArray(c, paramSourceBuilder));
return NumberUtil.sum(i);
return Numbers.sum(i);
}
protected final <T> long batchUpdate(String sql, Collection<T> c,
@Nonnull Function<T, SqlParameterSource> paramSourceBuilder) {
int[] i = this.jdbc.batchUpdate(sql, buildSqlParameterSourceArray(c, paramSourceBuilder));
return NumberUtil.sum(i);
return Numbers.sum(i);
}
protected static final <T> void assertResultEquals(T result, T expectedValue) {

View File

@@ -8,7 +8,7 @@ import java.sql.SQLException;
*
* <p>
* 通过在 {@link #map(ResultSet)} 中配置 {@link ResultSet} 到对象的映射,
* 可将 {@link #rowMapper(ResultSet, int)} 的方法用,
* 可将 {@link #rowMapper(ResultSet, int)} 的方法用,
* 直接当成 {@link org.springframework.jdbc.core.RowMapper} 对象传给
* {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}
* 的查询方法,

View File

@@ -1,8 +1,17 @@
package xyz.zhouxy.plusone.oss;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.csource.common.MyException;
import org.csource.common.NameValuePair;
@@ -14,7 +23,11 @@ import org.csource.fastdfs.TrackerServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.Files;
import lombok.Getter;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.exception.SysException;
public class FastDFSUtil {
@@ -44,7 +57,7 @@ public class FastDFSUtil {
* @throws FastDFSException
*/
public String[] upload(FastDFSFile file) throws FastDFSException {
logger.info("File Name: {}, File Length: {}", file.getName(), file.getContent().length);
logger.info("File Name: {}, File Length: {}", file.getFileName(), file.getContent().length);
NameValuePair[] metaList = new NameValuePair[1];
metaList[0] = new NameValuePair("author", file.getAuthor());
@@ -63,7 +76,7 @@ public class FastDFSUtil {
uploadResults[0], uploadResults[1], System.currentTimeMillis() - startTime);
} catch (IOException e) {
throw new FastDFSException("IO Exception when uploadind the file:" + file.getName(), e);
throw new FastDFSException("IO Exception when uploadind the file:" + file.getFileName(), e);
} catch (MyException e) {
throw new FastDFSException(e);
}
@@ -109,18 +122,72 @@ public class FastDFSUtil {
}
}
@Getter
public static final class FastDFSFile {
private String name;
private byte[] content;
private String ext;
private String md5;
private String author;
@Getter
private final String fileName;
private final byte[] content;
@Getter
private final String ext;
@Getter
private final String md5;
@Getter
private final String author;
public FastDFSFile(String name, byte[] content, String ext) {
this.name = name;
private FastDFSFile(@Nonnull File file, @Nullable String author) throws IOException {
this.fileName = file.getName();
this.content = Files.toByteArray(file);
this.ext = Files.getFileExtension(fileName);
this.md5 = md5Hex(content);
this.author = author;
}
private FastDFSFile(@Nonnull String fileName, @Nonnull byte[] content, @Nullable String author) {
this.fileName = fileName;
this.content = content;
this.ext = ext;
this.ext = Files.getFileExtension(fileName);
this.md5 = md5Hex(content);
this.author = author;
}
@Nonnull
@StaticFactoryMethod(FastDFSFile.class)
public static FastDFSFile of(@Nonnull File file) throws IOException {
return new FastDFSFile(file, null);
}
@Nonnull
@StaticFactoryMethod(FastDFSFile.class)
public static FastDFSFile of(@Nonnull File file, @Nullable String author) throws IOException {
return new FastDFSFile(file, author);
}
@Nonnull
@StaticFactoryMethod(FastDFSFile.class)
public static FastDFSFile of(@Nonnull String fileName, @Nonnull byte[] content) {
return new FastDFSFile(fileName, content, null);
}
@Nonnull
@StaticFactoryMethod(FastDFSFile.class)
public static FastDFSFile of(@Nonnull String fileName, @Nonnull byte[] content, @Nullable String author) {
return new FastDFSFile(fileName, content, author);
}
public byte[] getContent() {
return Arrays.copyOf(content, content.length);
}
@Nonnull
private static String md5Hex(byte[] data) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(data);
byte[] result = messageDigest.digest();
var sha512Hex = new BigInteger(1, result).toString(16);
return Objects.requireNonNull(sha512Hex);
} catch (NoSuchAlgorithmException e) {
throw new SysException(e);
}
}
}
}

View File

@@ -1,13 +1,15 @@
package xyz.zhouxy.plusone.validator;
import javax.annotation.Nonnull;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
@RestControllerAdvice
public class InvalidInputExceptionHandler extends BaseExceptionHandler {
public InvalidInputExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
public InvalidInputExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
super(exceptionInfoHolder);
set(InvalidInputException.class, InvalidInputException.ERROR_CODE, "无效的用户输入");
}

View File

@@ -3,14 +3,10 @@ package xyz.zhouxy.plusone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import lombok.extern.slf4j.Slf4j;
@SpringBootApplication
@Slf4j
public class PlusoneApplication {
public static void main(String[] args) {
log.debug("Plusone started!");
SpringApplication.run(PlusoneApplication.class, args);
}

View File

@@ -1,12 +1,11 @@
package xyz.zhouxy.plusone;
import java.io.FileInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.annotation.Resource;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@@ -24,10 +23,7 @@ class FastDFSTests {
@Test
void testOSS() throws FileNotFoundException, IOException, FastDFSException {
try (FileInputStream in = new FileInputStream("D:\\ZhouXY\\Desktop\\666.png");) {
byte[] content = IOUtils.toByteArray(in);
String[] upload = fastDFSUtil.upload(new FastDFSFile("666.png", content, "png"));
String[] upload = fastDFSUtil.upload(FastDFSFile.of(new File("D:\\ZhouXY\\Desktop\\666.png")));
log.info(String.join("/", upload));
}
}
}

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.exception;
package xyz.zhouxy.plusone.system.application.common.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.exception;
package xyz.zhouxy.plusone.system.application.common.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.exception;
package xyz.zhouxy.plusone.system.application.common.exception;
import xyz.zhouxy.plusone.validator.InvalidInputException;

View File

@@ -1,14 +1,12 @@
package xyz.zhouxy.plusone.system.application.exception.handler;
import javax.annotation.Nonnull;
package xyz.zhouxy.plusone.system.application.common.exception.handler;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.system.application.exception.AccountLoginException;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.commons.util.RestfulResult;
import xyz.zhouxy.plusone.system.application.common.exception.AccountLoginException;
@RestControllerAdvice
public class AccountLoginExceptionHandler extends BaseExceptionHandler {
@@ -18,7 +16,7 @@ public class AccountLoginExceptionHandler extends BaseExceptionHandler {
}
@ExceptionHandler({ AccountLoginException.class })
public ResponseEntity<RestfulResult> handleException(@Nonnull Exception e) {
public ResponseEntity<RestfulResult> handleException(Exception e) {
return buildExceptionResponse(e);
}
}

View File

@@ -1,4 +1,6 @@
package xyz.zhouxy.plusone.system.application.exception.handler;
package xyz.zhouxy.plusone.system.application.common.exception.handler;
import javax.annotation.Nonnull;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -14,7 +16,7 @@ import cn.dev33.satoken.exception.NotSafeException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.exception.SameTokenInvalidException;
import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.commons.exception.handler.BaseExceptionHandler;
import xyz.zhouxy.plusone.commons.util.RestfulResult;
/**
@@ -27,7 +29,7 @@ import xyz.zhouxy.plusone.commons.util.RestfulResult;
public class SaTokenExceptionHandler extends BaseExceptionHandler {
public SaTokenExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
public SaTokenExceptionHandler(@Nonnull ExceptionInfoHolder exceptionInfoHolder) {
super(exceptionInfoHolder);
set(NotPermissionException.class, 4030103, "会话未能通过权限认证", HttpStatus.FORBIDDEN);
set(NotRoleException.class, 4030103, "会话未能通过角色认证", HttpStatus.FORBIDDEN);

View File

@@ -0,0 +1,138 @@
package xyz.zhouxy.plusone.system.application.common.model;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.OptionalDouble;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import xyz.zhouxy.plusone.exception.BizException;
public class AuthenticationInfo<T> {
/** token 值 */
private final String token;
/** 此 token 对应的LoginId未登录时不会构建此类的实例 */
private final T loginId;
/** 账号类型 */
private final String loginType;
/** 登录设备类型 */
private final String deviceType;
/** 自定义数据 */
@Nonnull
private final Map<String, Object> metadata;
private AuthenticationInfo(String token, T loginId, String loginType, String deviceType,
@Nonnull Map<String, Object> metadata) {
this.token = token;
this.loginId = loginId;
this.loginType = loginType;
this.deviceType = deviceType;
this.metadata = metadata;
}
public String getToken() {
return token;
}
public T getLoginId() {
return loginId;
}
public String getLoginType() {
return loginType;
}
public String getDeviceType() {
return deviceType;
}
private Object getObjFromMetadata(String key) {
if (this.metadata.containsKey(key)) {
return this.metadata.get(key);
}
throw new BizException(String.format("不存在 key 为 \"%s\"的元数据。", key));
}
public OptionalDouble getMetadataAsDouble(String key) {
Object valObj = getObjFromMetadata(key);
if (valObj instanceof Double val) {
return OptionalDouble.of(val.doubleValue());
}
return OptionalDouble.empty();
}
public OptionalLong getMetadataAsLong(String key) {
Object valObj = getObjFromMetadata(key);
if (valObj instanceof Long val) {
return OptionalLong.of(val.longValue());
}
return OptionalLong.empty();
}
public OptionalInt getMetadataAsInt(String key) {
Object valObj = getObjFromMetadata(key);
if (valObj instanceof Integer val) {
return OptionalInt.of(val.intValue());
}
return OptionalInt.empty();
}
public Optional<String> getMetadataAsString(String key) {
Object valObj = getObjFromMetadata(key);
if (valObj instanceof String val) {
return Optional.of(val);
}
return Optional.empty();
}
@SuppressWarnings("unchecked")
public <C> Optional<C> getMetadata(String key, Class<C> type) {
Object valObj = getObjFromMetadata(key);
if (valObj == null || !type.isAssignableFrom(valObj.getClass())) {
return Optional.empty();
}
return Optional.of((C) valObj);
}
// ==========================================
// builder
public static <T> Builder<T> builder(String token, T loginId, String loginType, String deviceType) {
return new Builder<>(token, loginId, loginType, deviceType);
}
public static final class Builder<T> {
private final String token;
private final T loginId;
private final String loginType;
private final String deviceType;
@Nonnull
private final Map<String, Object> meta = new ConcurrentHashMap<>();
private Builder(String token, T loginId, String loginType, String deviceType) {
this.token = token;
this.loginId = loginId;
this.loginType = loginType;
this.deviceType = deviceType;
}
public <C> Builder<T> putMetadata(String key, @Nullable C value) {
this.meta.put(key, value);
return this;
}
public AuthenticationInfo<T> build() {
return new AuthenticationInfo<>(token, loginId, loginType, deviceType, meta);
}
}
}

View File

@@ -0,0 +1,21 @@
package xyz.zhouxy.plusone.system.application.common.model;
public class PlusoneContext {
private static final ThreadLocal<AuthenticationInfo<Long>> context = new ThreadLocal<>();
public static void setContext(AuthenticationInfo<Long> value) {
context.set(value);
}
public static AuthenticationInfo<Long> getContext() {
return context.get();
}
public static void remove() {
context.remove();
}
private PlusoneContext() {
throw new IllegalStateException("Utility class");
}
}

View File

@@ -2,6 +2,8 @@ package xyz.zhouxy.plusone.system.application.query;
import java.util.List;
import javax.annotation.Nonnull;
import org.apache.ibatis.annotations.Mapper;
import xyz.zhouxy.plusone.commons.util.PageDTO;
@@ -23,6 +25,7 @@ public interface AccountQueries {
return PageDTO.of(content, total);
}
@Nonnull
List<AccountOverview> queryAccountOverview(AccountQueryParams queryParams);
long count(AccountQueryParams queryParams);

View File

@@ -12,7 +12,7 @@ import xyz.zhouxy.plusone.commons.util.PagingAndSortingQueryParams;
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
@ToString
@ToString(callSuper = true)
public class AccountQueryParams extends PagingAndSortingQueryParams {
public AccountQueryParams() {
@@ -29,7 +29,6 @@ public class AccountQueryParams extends PagingAndSortingQueryParams {
"update_time");
}
// TODO【添加】 注解参数校验
private @Getter @Setter Long id;
private @Getter @Setter String username;
private @Getter @Setter String email;

View File

@@ -2,9 +2,10 @@ package xyz.zhouxy.plusone.system.application.query.result;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -81,18 +82,18 @@ public class MenuViewObject implements IWithOrderNumber {
List<Action> actions;
// MENU_LIST
List<MenuViewObject> children;
SortedSet<MenuViewObject> children;
public void addChild(MenuViewObject child) {
if (this.children == null) {
this.children = new ArrayList<>();
this.children = new TreeSet<>();
}
this.children.add(child);
}
public void addChildren(Collection<MenuViewObject> children) {
if (this.children == null) {
this.children = new ArrayList<>();
this.children = new TreeSet<>();
}
this.children.addAll(children);
}
@@ -121,11 +122,33 @@ public class MenuViewObject implements IWithOrderNumber {
}
public List<MenuViewObject> getChildren() {
return Objects.nonNull(this.children)
? this.children
.stream()
.sorted(Comparator.comparing(IWithOrderNumber::getOrderNumber))
.toList()
: null;
return this.children == null || this.children.isEmpty()
? Collections.emptyList()
: new ArrayList<>(this.children);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MenuViewObject other = (MenuViewObject) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}

View File

@@ -8,9 +8,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cn.dev33.satoken.stp.StpLogic;
import xyz.zhouxy.plusone.system.application.common.exception.AccountLoginException;
import xyz.zhouxy.plusone.system.application.common.exception.UnsupportedPrincipalTypeException;
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.AccountDetails;
import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject;

View File

@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import xyz.zhouxy.plusone.commons.util.PageDTO;
import xyz.zhouxy.plusone.exception.DataNotExistException;
import xyz.zhouxy.plusone.system.application.exception.AccountRegisterException;
import xyz.zhouxy.plusone.system.application.common.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;
@@ -94,7 +94,6 @@ public class AccountManagementService {
Account account = accountRepository.find(id)
.orElseThrow(() -> new DataNotExistException("该账号不存在"));
account.setAccountInfo(command.getNickname(), command.getAvatar(), Sex.of(command.getSex()));
account.setUpdatedBy(adminAuthLogic.getLoginIdAsLong());
accountRepository.save(account);
}

View File

@@ -6,10 +6,10 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cn.hutool.core.lang.Assert;
import xyz.zhouxy.plusone.system.application.common.exception.AccountLoginException;
import xyz.zhouxy.plusone.system.application.common.exception.UnsupportedPrincipalTypeException;
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;

View File

@@ -9,8 +9,8 @@ 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.application.common.exception.AccountLoginException;
import xyz.zhouxy.plusone.system.application.common.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;

View File

@@ -16,7 +16,7 @@ import org.springframework.util.Assert;
import xyz.zhouxy.plusone.constant.EntityStatus;
import xyz.zhouxy.plusone.domain.IWithOrderNumber;
import xyz.zhouxy.plusone.exception.DataNotExistException;
import xyz.zhouxy.plusone.system.application.exception.UnsupportedMenuTypeException;
import xyz.zhouxy.plusone.system.application.common.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;

View File

@@ -5,10 +5,10 @@ import java.util.Set;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xyz.zhouxy.plusone.system.application.common.exception.AccountRegisterException;
import xyz.zhouxy.plusone.system.application.common.exception.UnsupportedPrincipalTypeException;
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;

View File

@@ -0,0 +1,20 @@
package xyz.zhouxy.plusone.system.application.web.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
HandlerInterceptor[] interceptors;
@Override
public void addInterceptors(InterceptorRegistry registry) {
for (var interceptor : interceptors) {
registry.addInterceptor(interceptor);
}
}
}

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.controller;
package xyz.zhouxy.plusone.system.application.web.controller;
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.controller;
package xyz.zhouxy.plusone.system.application.web.controller;
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
import static xyz.zhouxy.plusone.commons.util.RestfulResult.success;

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.controller;
package xyz.zhouxy.plusone.system.application.web.controller;
import static xyz.zhouxy.plusone.commons.util.RestfulResult.success;

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.controller;
package xyz.zhouxy.plusone.system.application.web.controller;
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
import static xyz.zhouxy.plusone.commons.util.RestfulResult.success;

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.controller;
package xyz.zhouxy.plusone.system.application.web.controller;
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
import static xyz.zhouxy.plusone.commons.util.RestfulResult.success;

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.controller;
package xyz.zhouxy.plusone.system.application.web.controller;
import static xyz.zhouxy.plusone.commons.util.RestfulResult.success;

View File

@@ -1,4 +1,4 @@
package xyz.zhouxy.plusone.system.application.controller;
package xyz.zhouxy.plusone.system.application.web.controller;
import javax.validation.Valid;
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;

View File

@@ -0,0 +1,32 @@
package xyz.zhouxy.plusone.system.application.web.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.system.application.common.model.PlusoneContext;
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;;
@Slf4j
@Order(1)
@Component
public class HttpContextInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.info("" + adminAuthLogic.isLogin());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
PlusoneContext.remove();
log.info("拦截器清理 ThreadLocal防止内存泄漏");
}
}

View File

@@ -0,0 +1,7 @@
/**
* 拦截器
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
* @since 1.0
*/
package xyz.zhouxy.plusone.system.application.web.interceptor;

View File

@@ -1,15 +1,12 @@
package xyz.zhouxy.plusone.system.util;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.annotation.Nonnull;
import org.springframework.util.Assert;
import com.google.common.hash.Hashing;
import lombok.extern.slf4j.Slf4j;
import xyz.zhouxy.plusone.constant.ErrorCodeConsts;
import xyz.zhouxy.plusone.exception.BizException;
import xyz.zhouxy.plusone.util.RandomUtil;
/**
@@ -17,7 +14,6 @@ import xyz.zhouxy.plusone.util.RandomUtil;
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
@Slf4j
public final class PasswordUtil {
private static final char[] SALT_BASE_CHAR_ARRAY = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~`!@#$%^&*()_-+={}[]|\\:;\"',.<>?/"
.toCharArray();
@@ -29,18 +25,18 @@ public final class PasswordUtil {
* @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 = sha512Hex(passwordWithSalt);
if (sha512Hex == null) {
throw new BizException(ErrorCodeConsts.DEFAULT_ERROR_CODE, "未知错误:哈希加密失败!");
}
return sha512Hex;
public static String hashPassword(String password, String salt) {
Assert.notNull(password, "Password must not be null");
Assert.notNull(salt, "Salt must not be null");
return Hashing.sha512().newHasher()
.putInt(Arrays.hashCode(salt.toCharArray()))
.putString(password, StandardCharsets.UTF_8)
.putInt(password.length())
.putBoolean(password.length() % 2 == 0)
.putString(salt, StandardCharsets.UTF_8)
.putInt(Arrays.hashCode(password.toCharArray()))
.hash()
.toString();
}
/**
@@ -56,16 +52,4 @@ public final class PasswordUtil {
// 不允许实例化
throw new IllegalStateException("Utility class");
}
private static String sha512Hex(String data) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
messageDigest.update(data.getBytes(StandardCharsets.UTF_8));
byte[] result = messageDigest.digest();
return new BigInteger(1, result).toString(16);
} catch (NoSuchAlgorithmException e) {
log.error("{}", e);
}
return null;
}
}

View File

@@ -6,6 +6,7 @@ import java.util.Optional;
import java.util.Set;
import lombok.ToString;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.domain.AggregateRoot;
import xyz.zhouxy.plusone.domain.IWithVersion;
import xyz.zhouxy.plusone.exception.UserOperationException;
@@ -164,6 +165,7 @@ public class Account extends AggregateRoot<Long> implements IWithVersion {
return newInstance;
}
@StaticFactoryMethod(Account.class)
public static Account register(
Username username,
Email email,
@@ -193,6 +195,7 @@ public class Account extends AggregateRoot<Long> implements IWithVersion {
password, status, accountInfo, roleRefs, createdBy, updatedBy, version);
}
@StaticFactoryMethod(Account.class)
public static Account newInstance(
String username,
String email,
@@ -210,6 +213,7 @@ public class Account extends AggregateRoot<Long> implements IWithVersion {
return newInstance;
}
@StaticFactoryMethod(Account.class)
public static Account register(
String username,
String email,
@@ -265,10 +269,6 @@ public class Account extends AggregateRoot<Long> implements IWithVersion {
return Optional.ofNullable(updatedBy);
}
public void setUpdatedBy(long updatedBy) {
this.updatedBy = updatedBy;
}
@Override
public long getVersion() {
return this.version;

View File

@@ -23,7 +23,7 @@ public final class AccountStatus extends Enumeration<AccountStatus> implements I
public static final AccountStatus AVAILABLE = new AccountStatus(0, "账号正常");
public static final AccountStatus LOCKED = new AccountStatus(1, "账号被锁定");
private static final ValueSet<AccountStatus> VALUE_SET = new ValueSet<>(
private static final ValueSet<AccountStatus> VALUE_SET = ValueSet.of(
AVAILABLE,
LOCKED);
@@ -31,7 +31,6 @@ public final class AccountStatus extends Enumeration<AccountStatus> implements I
return VALUE_SET.get(id);
}
@Nonnull
public static Collection<AccountStatus> constants() {
return VALUE_SET.getValues();
}

View File

@@ -3,7 +3,11 @@ package xyz.zhouxy.plusone.system.domain.model.account;
import java.util.Objects;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import cn.hutool.core.util.DesensitizedUtil;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.commons.annotation.ValueObject;
import xyz.zhouxy.plusone.commons.constant.PatternConsts;
/**
@@ -11,38 +15,28 @@ import xyz.zhouxy.plusone.commons.constant.PatternConsts;
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
public class Email extends Principal {
@ValueObject
public final class Email extends Principal {
public static final Pattern REGEX = PatternConsts.EMAIL;
private Email(String email) {
super(REGEX);
if (email == null) {
throw new IllegalArgumentException("邮箱地址不能为空");
}
this.value = email;
if (!isValid()) {
throw new IllegalArgumentException("邮箱地址格式错误");
}
super(email, REGEX);
}
@StaticFactoryMethod(Email.class)
public static Email of(String email) {
return new Email(email);
}
@StaticFactoryMethod(Email.class)
@Nullable
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();
public String safeValue() {
return DesensitizedUtil.email(value());
}
}

View File

@@ -3,7 +3,11 @@ package xyz.zhouxy.plusone.system.domain.model.account;
import java.util.Objects;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import cn.hutool.core.util.DesensitizedUtil;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.commons.annotation.ValueObject;
import xyz.zhouxy.plusone.commons.constant.PatternConsts;
/**
@@ -11,35 +15,28 @@ import xyz.zhouxy.plusone.commons.constant.PatternConsts;
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
@ValueObject
public class MobilePhone extends Principal {
public static final Pattern REGEX = PatternConsts.MOBILE_PHONE;
private MobilePhone(String mobilePhone) {
super(REGEX);
if (mobilePhone == null) {
throw new IllegalArgumentException("手机号不能为空");
}
this.value = mobilePhone;
if (!isValid()) {
throw new IllegalArgumentException("手机号格式错误");
}
super(mobilePhone, REGEX);
}
@StaticFactoryMethod(MobilePhone.class)
public static MobilePhone of(String mobilePhone) {
return new MobilePhone(mobilePhone);
}
@StaticFactoryMethod(MobilePhone.class)
@Nullable
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();
public String safeValue() {
return DesensitizedUtil.mobilePhone(this.value());
}
}

View File

@@ -3,33 +3,31 @@ package xyz.zhouxy.plusone.system.domain.model.account;
import java.util.Objects;
import java.util.regex.Pattern;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.commons.annotation.ValueObject;
import xyz.zhouxy.plusone.commons.constant.PatternConsts;
import xyz.zhouxy.plusone.domain.ValidatableStringRecord;
import xyz.zhouxy.plusone.commons.domain.ValidatableStringRecord;
/**
* 值对象:昵称
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
@ValueObject
public class Nickname extends ValidatableStringRecord {
public static final Pattern REGEX = PatternConsts.NICKNAME;
private Nickname(String value) {
super(REGEX);
if (value == null) {
throw new IllegalArgumentException("昵称不能为空");
}
this.value = value;
if (!isValid()) {
throw new IllegalArgumentException("昵称格式错误");
}
super(value, REGEX);
}
@StaticFactoryMethod(Nickname.class)
public static Nickname of(String nickname) {
return new Nickname(nickname);
}
@StaticFactoryMethod(Nickname.class)
public static Nickname ofNullable(String nickname) {
return Objects.nonNull(nickname) ? new Nickname(nickname) : null;
}

View File

@@ -3,14 +3,11 @@ 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.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.commons.constant.PatternConsts;
import xyz.zhouxy.plusone.constant.ErrorCodeConsts;
import xyz.zhouxy.plusone.domain.IValueObject;
import xyz.zhouxy.plusone.exception.BizException;
import xyz.zhouxy.plusone.system.util.PasswordUtil;
/**
@@ -23,43 +20,35 @@ public class Password implements IValueObject {
private static final Pattern PATTERN = PatternConsts.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 BizException(ErrorCodeConsts.DEFAULT_ERROR_CODE, "未知错误:生成随机盐失败");
}
Assert.notNull(password, "密码不能为空");
Assert.isTrue(PATTERN.matcher(password).matches(), "密码格式不符合要求");
String salt = PasswordUtil.generateRandomSalt();
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 不能为空");
}
Assert.isTrue(password != null && salt != null, "password 和 salt 不能为空");
this.passwordVal = password;
this.saltVal = salt;
}
@StaticFactoryMethod(Password.class)
public static Password of(String password, String salt) {
return new Password(password, salt);
}
@StaticFactoryMethod(Password.class)
public static Password newPassword(String newPassword, String passwordConfirmation) {
Assert.isTrue(Objects.equals(newPassword, passwordConfirmation), "两次输入的密码不一致");
return newPassword(newPassword);
}
@StaticFactoryMethod(Password.class)
public static Password newPassword(String newPassword) {
return new Password(newPassword);
}
@@ -80,6 +69,7 @@ public class Password implements IValueObject {
return saltVal;
}
@StaticFactoryMethod(Nickname.class)
public static Password newDefaultPassword() {
return newPassword(DEFAULT_PASSWORD);
}

View File

@@ -2,15 +2,23 @@ package xyz.zhouxy.plusone.system.domain.model.account;
import java.util.regex.Pattern;
import xyz.zhouxy.plusone.domain.ValidatableStringRecord;
import xyz.zhouxy.plusone.commons.domain.ValidatableStringRecord;
import xyz.zhouxy.plusone.domain.IValueObject;
/**
* 账号标识符
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
public abstract class Principal extends ValidatableStringRecord {
protected Principal(Pattern format) {
super(format);
public abstract class Principal extends ValidatableStringRecord implements IValueObject {
protected Principal(String principal, Pattern pattern) {
super(principal, pattern);
}
protected abstract String safeValue();
@Override
public final String toString() {
return this.safeValue();
}
}

View File

@@ -4,6 +4,7 @@ import java.util.Collection;
import javax.annotation.Nonnull;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.commons.util.Enumeration;
import xyz.zhouxy.plusone.domain.IValueObject;
@@ -24,14 +25,13 @@ public final class Sex extends Enumeration<Sex> implements IValueObject {
super(id, name);
}
private static final ValueSet<Sex> VALUE_SET = new ValueSet<>(UNSET, MALE, FEMALE);
private static final ValueSet<Sex> VALUE_SET = ValueSet.of(UNSET, MALE, FEMALE);
@Nonnull
@StaticFactoryMethod(Sex.class)
public static Sex of(int value) {
return VALUE_SET.get(value);
}
@Nonnull
public static Collection<Sex> constants() {
return VALUE_SET.getValues();
}

View File

@@ -2,6 +2,8 @@ package xyz.zhouxy.plusone.system.domain.model.account;
import java.util.regex.Pattern;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.commons.annotation.ValueObject;
import xyz.zhouxy.plusone.commons.constant.PatternConsts;
/**
@@ -9,22 +11,23 @@ import xyz.zhouxy.plusone.commons.constant.PatternConsts;
*
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
*/
@ValueObject
public class Username extends Principal {
public static final Pattern REGEX = PatternConsts.USERNAME;
private Username(String username) {
super(REGEX);
if (username == null) {
throw new IllegalArgumentException("用户名不能为空");
}
this.value = username;
if (!isValid()) {
throw new IllegalArgumentException("用户名格式错误");
}
super(username, REGEX);
}
@StaticFactoryMethod(Username.class)
public static Username of(String username) {
return new Username(username);
}
@Override
protected String safeValue() {
// 不需要脱敏。
return value();
}
}

View File

@@ -8,6 +8,7 @@ import java.util.Optional;
import java.util.Set;
import lombok.ToString;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.domain.AggregateRoot;
import xyz.zhouxy.plusone.domain.IWithLabel;
import xyz.zhouxy.plusone.domain.IWithVersion;
@@ -70,12 +71,14 @@ public class Dict extends AggregateRoot<Long> implements IWithLabel, IWithVersio
this.version = version;
}
@StaticFactoryMethod(Dict.class)
public static Dict newInstance(
String dictType,
String dictLabel) {
return new Dict(null, dictType, dictLabel, Collections.emptySet(), 0);
}
@StaticFactoryMethod(Dict.class)
public static Dict newInstance(
String dictType,
String dictLabel,
@@ -83,6 +86,7 @@ public class Dict extends AggregateRoot<Long> implements IWithLabel, IWithVersio
return new Dict(null, dictType, dictLabel, values, 0);
}
@StaticFactoryMethod(Dict.class)
public static Dict newInstance(
String dictType,
String dictLabel,

View File

@@ -4,6 +4,7 @@ import java.util.Objects;
import lombok.Getter;
import lombok.ToString;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.domain.IValueObject;
/**
@@ -25,6 +26,7 @@ public class DictValue implements IValueObject {
this.label = label;
}
@StaticFactoryMethod(DictValue.class)
public static DictValue of(int key, String label) {
return new DictValue(key, label);
}

View File

@@ -9,6 +9,7 @@ import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
import lombok.ToString;
import xyz.zhouxy.plusone.commons.exception.IWithIntCode;
import xyz.zhouxy.plusone.constant.EntityStatus;
import xyz.zhouxy.plusone.domain.AggregateRoot;
import xyz.zhouxy.plusone.domain.IWithOrderNumber;
@@ -129,12 +130,28 @@ public class Menu extends AggregateRoot<Long> implements IWithOrderNumber, IWith
this.version = version;
}
public enum MenuType {
MENU_LIST, MENU_ITEM;
public enum MenuType implements IWithIntCode {
MENU_LIST(0), MENU_ITEM(1);
private final int code;
private MenuType(int code) {
this.code = code;
}
@JsonValue
public int value() {
return ordinal();
@Override
public int getCode() {
return code;
}
public static MenuType valueOf(int code) {
for (MenuType value : values()) {
if (value.code == code) {
return value;
}
}
throw new EnumConstantNotPresentException(MenuType.class, Integer.toString(code));
}
}
}

View File

@@ -2,6 +2,7 @@ package xyz.zhouxy.plusone.system.domain.model.menu;
import java.util.List;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.constant.EntityStatus;
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
@@ -16,6 +17,7 @@ public class MenuConstructor {
throw new IllegalStateException("Utility class");
}
@StaticFactoryMethod(Menu.class)
public static Menu newMenuItem(
long parentId,
String path,
@@ -39,6 +41,7 @@ public class MenuConstructor {
remarks, component, cache, resource, actions, 0L);
}
@StaticFactoryMethod(Menu.class)
public static Menu newMenuList(
long parentId,
String path,

View File

@@ -3,6 +3,7 @@ package xyz.zhouxy.plusone.system.domain.model.permission;
import java.util.Optional;
import lombok.Getter;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.domain.Entity;
import xyz.zhouxy.plusone.domain.IWithLabel;
import xyz.zhouxy.plusone.domain.IWithVersion;
@@ -20,7 +21,7 @@ public class Action extends Entity<Long> implements IWithLabel, IWithVersion {
@Getter String label;
@Getter long version;
public Action(Long id, String resource, String identifier, String label, long version) {
private Action(Long id, String resource, String identifier, String label, long version) {
this.id = id;
this.resource = resource;
this.identifier = identifier;
@@ -28,10 +29,12 @@ public class Action extends Entity<Long> implements IWithLabel, IWithVersion {
this.version = version;
}
@StaticFactoryMethod(Action.class)
static Action newInstance(String resource, String identifier, String label) {
return new Action(null, resource, identifier, label, 0L);
}
@StaticFactoryMethod(Action.class)
static Action existingInstance(Long id, String resource, String action, String label, Long version) {
return new Action(id, resource, action, label, version);
}

View File

@@ -6,6 +6,7 @@ import java.util.Objects;
import java.util.Optional;
import lombok.Getter;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.domain.AggregateRoot;
import xyz.zhouxy.plusone.domain.IWithVersion;
@@ -37,6 +38,7 @@ public class Permission extends AggregateRoot<Long> implements IWithVersion {
// ==================== 实例化 ====================
@StaticFactoryMethod(Permission.class)
public static Permission newInstance(String resource) {
return new Permission(
null, resource,

View File

@@ -14,7 +14,8 @@ import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.stereotype.Repository;
import cn.hutool.core.util.IdUtil;
import static xyz.zhouxy.plusone.domain.ValidatableStringRecord.getValueOrNull;
import xyz.zhouxy.plusone.commons.domain.ValidatableStringRecord;
import xyz.zhouxy.plusone.commons.util.OptionalUtil;
import xyz.zhouxy.plusone.jdbc.JdbcRepositorySupport;
/**
@@ -196,23 +197,35 @@ public class AccountRepositoryImpl extends JdbcRepositorySupport<Account, Long>
@Override
protected final SqlParameterSource generateParamSource(Long id, @Nonnull Account entity) {
LocalDateTime now = LocalDateTime.now();
AccountInfo accountInfo = entity.getAccountInfo();
final LocalDateTime now = LocalDateTime.now();
final AccountInfo accountInfo = entity.getAccountInfo();
final Optional<String> email = entity.getEmail().map(ValidatableStringRecord::value);
final Optional<String> mobilePhone = entity.getMobilePhone().map(ValidatableStringRecord::value);
final String username = entity.getUsername().value();
final String password = entity.getPassword().value();
final String salt = entity.getPassword().getSalt();
final Optional<String> avatar = accountInfo.getAvatar().map(Object::toString);
final int sex = accountInfo.getSex().getId();
final Optional<String> nickname = accountInfo.getNickname().map(ValidatableStringRecord::value);
final int status = entity.getStatus().getId();
final Optional<Long> createdBy = entity.getCreatedBy();
final Optional<Long> updatedBy = entity.getUpdatedBy();
final long version = entity.getVersion();
return new MapSqlParameterSource()
.addValue("id", id)
.addValue("email", getValueOrNull(entity.getEmail()))
.addValue("mobilePhone", getValueOrNull(entity.getMobilePhone()))
.addValue("username", entity.getUsername().value())
.addValue("password", entity.getPassword().value())
.addValue("salt", entity.getPassword().getSalt())
.addValue("avatar", accountInfo.getAvatar().toString())
.addValue("sex", accountInfo.getSex().getId())
.addValue("nickname", getValueOrNull(accountInfo.getNickname()))
.addValue("status", entity.getStatus().getId())
.addValue("createdBy", entity.getCreatedBy())
.addValue("email", OptionalUtil.orElseNull(email))
.addValue("mobilePhone", OptionalUtil.orElseNull(mobilePhone))
.addValue("username", username)
.addValue("password", password)
.addValue("salt", salt)
.addValue("avatar", OptionalUtil.orElseNull(avatar))
.addValue("sex", sex)
.addValue("nickname", OptionalUtil.orElseNull(nickname))
.addValue("status", status)
.addValue("createdBy", OptionalUtil.orElseNull(createdBy))
.addValue("createTime", now)
.addValue("updatedBy", entity.getUpdatedBy())
.addValue("updatedBy", OptionalUtil.orElseNull(updatedBy))
.addValue("updateTime", now)
.addValue("version", entity.getVersion());
.addValue("version", version);
}
}

View File

@@ -7,6 +7,7 @@ import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
@@ -94,13 +95,12 @@ public class DictRepositoryImpl extends JdbcRepositorySupport<Dict, Long> implem
@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"));
final long id = rs.getLong("id");
final String dictType = rs.getString("dict_type");
final String dictLabel = rs.getString("dict_label");
final Set<DictValue> values = this.dictValueDAO.selectDictValuesByDictId(id);
final long version = rs.getLong("version");
return new Dict(id, dictType, dictLabel, values, version);
}
@Override

View File

@@ -17,7 +17,6 @@ import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;
import cn.hutool.core.util.IdUtil;
import xyz.zhouxy.plusone.commons.util.EnumUtil;
import xyz.zhouxy.plusone.constant.EntityStatus;
import xyz.zhouxy.plusone.exception.DataNotExistException;
import xyz.zhouxy.plusone.jdbc.JdbcRepositorySupport;
@@ -156,7 +155,7 @@ public class MenuRepositoryImpl extends JdbcRepositorySupport<Menu, Long> implem
protected final Menu mapRow(ResultSet rs) throws SQLException {
long menuId = rs.getLong("id");
return new Menu(
EnumUtil.valueOf(MenuType.class, rs.getInt("type")),
MenuType.valueOf(rs.getInt("type")),
menuId,
rs.getLong("parent_id"),
rs.getString("name"),
@@ -181,7 +180,7 @@ public class MenuRepositoryImpl extends JdbcRepositorySupport<Menu, Long> implem
return new MapSqlParameterSource()
.addValue("id", id)
.addValue("parentId", entity.getParentId())
.addValue("type", entity.getType().value())
.addValue("type", entity.getType().getCode())
.addValue("name", entity.getName())
.addValue("path", entity.getPath())
.addValue("title", entity.getTitle())

View File

@@ -25,7 +25,7 @@
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring-boot.version>2.7.10</spring-boot.version>
<spring-boot.version>2.7.13</spring-boot.version>
<sa-token.version>1.34.0</sa-token.version>
<hutool.version>5.8.16</hutool.version>
<mybatis-starter.version>3.0.1</mybatis-starter.version>