Compare commits

...

11 Commits

31 changed files with 553 additions and 462 deletions

View File

@@ -1,3 +1,22 @@
## [v2.13.4](https://github.com/continew-org/continew-starter/compare/v2.13.3...v2.13.4) (2025-07-26)
### ✨ 新特性
- 【cache/redisson】RedisUtils 新增 Hash 常用操作方法hSet/hGet/hGetAll/hExists/hDel(Gitee#77@kiki1373639299) ([8676f9b](https://github.com/continew-org/continew-starter/commit/8676f9b5912914f2bc1025d7695e2b11136bad20))
- 【extension/crud】CRUD API 新增 DICT字典列表下拉选项等场景、DICT_TREE字典树列表树型结构下拉选项等场景 ([ecabda6](https://github.com/continew-org/continew-starter/commit/ecabda6aecc70fdb49eebd2dcde5bddd63fe337b))
- 【security/crypto】新增密码编码器配置由原 security/password 模块融合) ([0ba365d](https://github.com/continew-org/continew-starter/commit/0ba365dabc3c18a7b07eaec05c17a8848642e78f)) ([49c804a](https://github.com/continew-org/continew-starter/commit/49c804ac9e577ccc4019319c812ebcdf20ef5ad6))
- 【cache/redisson】新增 RedisLockUtils Redisson 分布式锁工具类 (Gitee#78@lishuyanla) ([48783db](https://github.com/continew-org/continew-starter/commit/48783db422525548d7eec5caba788f8ab53d7bec))
### 💎 功能优化
- 【cache/redisson】移除 RedisQueueUtils 类 ([e5354b7](https://github.com/continew-org/continew-starter/commit/e5354b765d27f4ba853865fa6290f706f14a990c))
- 【extension/crud】优化 CRUD API 自动配置代码EnableCrudRestController => EnableCrudApi ([ca33851](https://github.com/continew-org/continew-starter/commit/ca33851fbd92f145229844c464a0cf1edbf7b9c7)) ([1fdb029](https://github.com/continew-org/continew-starter/commit/1fdb0291d20975e667232f866e3605712b29f8a9))
- 【cache/redisson】移除 RedisUtils 中的 Lock 相关工具方法(统一使用 RedisLockUtils ([cff4f02](https://github.com/continew-org/continew-starter/commit/cff4f02d966836fd291c268d0e44b0047e35d053))
### 🐛 问题修复
- 【security/crypto】修复 构造默认加密上下文时缺失默认加密器 导致找不到加密器的问题 (Gitee#76@lishuyanla) ([d0eddcb](https://github.com/continew-org/continew-starter/commit/d0eddcb9f73282578df6ebb6b8c3d41fe164abd0))
## [v2.13.3](https://github.com/continew-org/continew-starter/compare/v2.13.2...v2.13.3) (2025-07-22)
### ✨ 新特性

View File

@@ -167,9 +167,9 @@ continew-starter
│ └─ continew-starter-data-mfMyBatis Flex
├─ continew-starter-security安全模块
│ ├─ continew-starter-security-crypto加密字段加解密
│ ├─ continew-starter-security-xssXSS 过滤)
│ ├─ continew-starter-security-mask脱敏JSON 数据脱敏)
─ continew-starter-security-password密码编码器
─ continew-starter-security-xssXSS 过滤
│ └─ continew-starter-security-sensitivewords敏感词
├─ continew-starter-ratelimiter限流模块
├─ continew-starter-idempotent幂等模块
├─ continew-starter-trace链路追踪模块

View File

@@ -13,7 +13,7 @@
<description>ContiNew Starter BOM</description>
<properties>
<revision>2.13.3</revision>
<revision>2.13.4</revision>
</properties>
<dependencyManagement>
@@ -104,12 +104,6 @@
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 密码编码器 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-security-password</artifactId>
<version>${revision}</version>
</dependency>
<!-- 安全模块 - 加密 -->
<dependency>
<groupId>top.continew.starter</groupId>

View File

@@ -0,0 +1,192 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.cache.redisson.util;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.core.util.SpringUtils;
import java.util.concurrent.TimeUnit;
/**
* Redisson 分布式锁工具类
*
* @author lishuyan
* @since 2.13.4
*/
public class RedisLockUtils implements AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(RedisLockUtils.class);
/**
* 默认锁过期时间(毫秒)
*/
private static final long DEFAULT_EXPIRE_TIME = 10000L;
/**
* 默认获取锁超时时间(毫秒)
*/
private static final long DEFAULT_TIMEOUT = 5000L;
/**
* Redisson 客户端
*/
private static volatile RedissonClient CLIENT;
/**
* 锁实例
*/
private final RLock lock;
/**
* 是否成功获取锁
*/
private boolean isLocked;
/**
* 获取Redisson客户端实例
*
* @return RedissonClient实例
*/
private static RedissonClient getClient() {
if (CLIENT == null) {
synchronized (RedisLockUtils.class) {
if (CLIENT == null) {
CLIENT = SpringUtils.getBean(RedissonClient.class, false);
}
}
}
return CLIENT;
}
/**
* 私有构造函数,防止外部实例化
*/
private RedisLockUtils(RLock lock, long expireTime, long timeout, TimeUnit unit) {
this.lock = lock;
try {
this.isLocked = lock.tryLock(timeout, expireTime, unit);
if (isLocked) {
log.debug("获取锁成功key: {}", lock.getName());
} else {
log.debug("获取锁失败key: {}", lock.getName());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取锁过程中被中断key: {}", lock.getName(), e);
}
}
/**
* 尝试获取锁(启用看门狗自动续期机制)
*
* @param key 锁的键
* @param timeout 获取锁的超时时间
* @param unit 时间单位
* @return LockUtils 实例
*/
public static RedisLockUtils tryLockWithWatchdog(String key, long timeout, TimeUnit unit) {
RLock lock = getClient().getLock(key);
// 传入-1表示使用看门狗机制
return new RedisLockUtils(lock, -1, timeout, unit);
}
/**
* 尝试获取锁(启用看门狗自动续期机制,默认时间单位为毫秒)
*
* @param key 锁的键
* @param timeout 获取锁的超时时间(单位:毫秒)
* @return LockUtils 实例
*/
public static RedisLockUtils tryLockWithWatchdog(String key, long timeout) {
return tryLockWithWatchdog(key, timeout, TimeUnit.MILLISECONDS);
}
/**
* 尝试获取锁(启用看门狗自动续期机制,使用默认超时时间)
*
* @param key 锁的键
* @return LockUtils 实例
*/
public static RedisLockUtils tryLockWithWatchdog(String key) {
return tryLockWithWatchdog(key, DEFAULT_TIMEOUT);
}
/**
* 尝试获取锁
*
* @param key 锁的键
* @param expireTime 锁的过期时间
* @param timeout 获取锁的超时时间
* @param unit 时间单位
* @return LockUtils 实例
*/
public static RedisLockUtils tryLock(String key, long expireTime, long timeout, TimeUnit unit) {
RLock lock = getClient().getLock(key);
return new RedisLockUtils(lock, expireTime, timeout, unit);
}
/**
* 尝试获取锁(默认时间单位为毫秒)
*
* @param key 锁的键
* @param expireTime 锁的过期时间(单位:毫秒)
* @param timeout 获取锁的超时时间(单位:毫秒)
* @return LockUtils 实例
*/
public static RedisLockUtils tryLock(String key, long expireTime, long timeout) {
return tryLock(key, expireTime, timeout, TimeUnit.MILLISECONDS);
}
/**
* 尝试获取锁(使用默认过期时间和超时时间)
*
* @param key 锁的键
* @return LockUtils 实例
*/
public static RedisLockUtils tryLock(String key) {
return tryLock(key, DEFAULT_EXPIRE_TIME, DEFAULT_TIMEOUT);
}
/**
* 检查是否成功获取锁
*
* @return true成功false失败
*/
public boolean isLocked() {
return isLocked;
}
/**
* 释放锁
*/
@Override
public void close() {
if (isLocked && lock.isHeldByCurrentThread()) {
try {
lock.unlockAsync().get();
log.debug("释放锁成功key: {}", lock.getName());
} catch (Exception e) {
log.error("释放锁失败key: {}", lock.getName(), e);
}
} else {
log.debug("锁未被当前线程持有无需释放key: {}", lock.getName());
}
}
}

View File

@@ -1,146 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.cache.redisson.util;
import cn.hutool.extra.spring.SpringUtil;
import org.redisson.api.*;
import java.util.concurrent.TimeUnit;
/**
* Redis 队列工具类
*
* @author Charles7c
* @since 2.12.1
*/
public class RedisQueueUtils {
private static final RedissonClient CLIENT = SpringUtil.getBean(RedissonClient.class);
private RedisQueueUtils() {
}
/**
* 获取 Redisson 客户端实例
*
* @return Redisson 客户端实例
*/
public static RedissonClient getClient() {
return CLIENT;
}
/**
* 获取阻塞队列实例
*
* @param key 键
* @return 阻塞队列实例
*/
public static <T> RBlockingQueue<T> getBlockingQueue(String key) {
return CLIENT.getBlockingQueue(key);
}
/**
* 添加元素到阻塞队列尾部
* <p>
* 如果队列已满,则返回 false
* </p>
*
* @param key 键
* @param value 值
* @return true添加成功false添加失败
*/
public static <T> boolean addBlockingQueueData(String key, T value) {
RBlockingQueue<T> queue = getBlockingQueue(key);
return queue.offer(value);
}
/**
* 获取并移除阻塞队列头部元素
* <p>
* 如果队列为空,则返回 null
* </p>
*
* @param key 键
* @return 队列头部元素,如果队列为空,则返回 null
*/
public static <T> T getBlockingQueueData(String key) {
RBlockingQueue<T> queue = getBlockingQueue(key);
return queue.poll();
}
/**
* 删除阻塞队列中的指定元素
*
* @param key 键
* @param value 值
* @return true删除成功false删除失败
*/
public static <T> boolean removeBlockingQueueData(String key, T value) {
RBlockingQueue<T> queue = getBlockingQueue(key);
return queue.remove(value);
}
/**
* 获取延迟队列实例
*
* @param key 键
* @return 延迟队列实例
*/
public static <T> RDelayedQueue<T> getDelayedQueue(String key) {
RBlockingQueue<T> blockingQueue = getBlockingQueue(key);
return CLIENT.getDelayedQueue(blockingQueue);
}
/**
* 添加元素到延迟队列
*
* @param key 键
* @param value 值
* @param delay 延迟时间
* @param unit 时间单位
*/
public static <T> void addDelayedQueueData(String key, T value, long delay, TimeUnit unit) {
RDelayedQueue<T> delayedQueue = getDelayedQueue(key);
delayedQueue.offer(value, delay, unit);
}
/**
* 获取并移除延迟队列头部元素
* <p>
* 如果队列为空,则返回 null
* </p>
*
* @param key 键
* @return 队列头部元素,如果队列为空,则返回 null
*/
public static <T> T getDelayedQueueData(String key) {
RDelayedQueue<T> delayedQueue = getDelayedQueue(key);
return delayedQueue.poll();
}
/**
* 移除延迟队列中的指定元素
*
* @param key 键
* @param value 值
* @return true移除成功false移除失败
*/
public static <T> boolean removeDelayedQueueData(String key, T value) {
RDelayedQueue<T> delayedQueue = getDelayedQueue(key);
return delayedQueue.remove(value);
}
}

View File

@@ -25,8 +25,8 @@ import top.continew.starter.core.constant.StringConstants;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.Map;
import java.util.function.Consumer;
/**
* Redis 工具类
@@ -103,7 +103,7 @@ public class RedisUtils {
/**
* 设置缓存
* <p>如果键不存在,则不设置</p>
*
*
* @param key 键
* @param value 值
* @return true设置成功false设置失败
@@ -116,7 +116,7 @@ public class RedisUtils {
/**
* 设置缓存
* <p>如果键不存在,则不设置</p>
*
*
* @param key 键
* @param value 值
* @param duration 过期时间
@@ -262,6 +262,75 @@ public class RedisUtils {
return CLIENT.getKeys().getKeysStream(options).toList();
}
/**
* 设置 Hash 中指定字段的值
*
* @param key Hash 键
* @param field 字段
* @param value 值
* @author KAI
* @since 2.13.4
*/
public static <T> void hSet(String key, String field, T value) {
RMap<String, T> map = CLIENT.getMap(key);
map.put(field, value);
}
/**
* 获取 Hash 中指定字段的值
*
* @param key Hash 键
* @param field 字段
* @return 值
* @author KAI
* @since 2.13.4
*/
public static <T> T hGet(String key, String field) {
RMap<String, T> map = CLIENT.getMap(key);
return map.get(field);
}
/**
* 获取整个 Hash 的所有字段值
*
* @param key Hash 键
* @return Map
* @author KAI
* @since 2.13.4
*/
public static <T> Map<String, T> hGetAll(String key) {
RMap<String, T> map = CLIENT.getMap(key);
return map.readAllMap();
}
/**
* 判断 Hash 中是否存在指定字段
*
* @param key Hash 键
* @param field 字段
* @return true存在false不存在
* @author KAI
* @since 2.13.4
*/
public static boolean hExists(String key, String field) {
RMap<String, ?> map = CLIENT.getMap(key);
return map.containsKey(field);
}
/**
* 删除 Hash 中指定字段
*
* @param key Hash 键
* @param fields 字段数组
* @return 删除成功的字段数量
* @author KAI
* @since 2.13.4
*/
public static long hDel(String key, String... fields) {
RMap<String, ?> map = CLIENT.getMap(key);
return map.fastRemove(fields);
}
/**
* 添加元素到 ZSet 中
*
@@ -452,77 +521,45 @@ public class RedisUtils {
}
/**
* 尝试获取锁
* 发布消息
*
* @param key
* @param expireTime 锁过期时间(单位:毫秒)
* @param timeout 获取锁超时时间(单位:毫秒)
* @return true成功false失败
* @since 2.7.2
* @param name 主题名称
* @param msg 发送数据
* @param consumer 自定义处理
* @author lishuyan
* @since 2.13.4
*/
public static boolean tryLock(String key, long expireTime, long timeout) {
return tryLock(key, expireTime, timeout, TimeUnit.MILLISECONDS);
public static <T> void publish(String name, T msg, Consumer<T> consumer) {
RTopic topic = CLIENT.getTopic(name);
topic.publish(msg);
consumer.accept(msg);
}
/**
* 释放锁
* 发布消息
*
* @param key 键
* @return true释放成功false释放失败
* @since 2.7.2
* @param name 主题名称
* @param msg 发送数据
* @author lishuyan
* @since 2.13.4
*/
public static boolean unlock(String key) {
RLock lock = getLock(key);
return unlock(lock);
public static <T> void publish(String name, T msg) {
RTopic topic = CLIENT.getTopic(name);
topic.publish(msg);
}
/**
* 尝试获取锁
* 订阅消息
*
* @param key
* @param expireTime 锁过期时间
* @param timeout 获取锁超时时间
* @param unit 时间单位
* @return true成功false失败
* @since 2.7.2
* @param name 主题名称
* @param clazz 消息类型
* @param consumer 自定义处理
* @author lishuyan
* @since 2.13.4
*/
public static boolean tryLock(String key, long expireTime, long timeout, TimeUnit unit) {
RLock lock = getLock(key);
try {
return lock.tryLock(timeout, expireTime, unit);
} catch (InterruptedException e) {
return false;
}
}
/**
* 释放锁
*
* @param lock 锁实例
* @return true释放成功false释放失败
* @since 2.7.2
*/
public static boolean unlock(RLock lock) {
if (lock.isHeldByCurrentThread()) {
try {
lock.unlockAsync().get();
return true;
} catch (ExecutionException | InterruptedException e) {
return false;
}
}
return false;
}
/**
* 获取锁实例
*
* @param key 键
* @return 锁实例
* @since 2.7.2
*/
public static RLock getLock(String key) {
return CLIENT.getLock(key);
public static <T> void subscribe(String name, Class<T> clazz, Consumer<T> consumer) {
RTopic topic = CLIENT.getTopic(name);
topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
}
/**

View File

@@ -54,11 +54,6 @@ public class PropertiesConstants {
*/
public static final String SECURITY = CONTINEW_STARTER + StringConstants.DOT + "security";
/**
* 安全-密码编解码配置
*/
public static final String SECURITY_PASSWORD = SECURITY + StringConstants.DOT + "password";
/**
* 安全-加/解密配置
*/

View File

@@ -14,7 +14,7 @@
<properties>
<!-- Project Version -->
<revision>2.13.3</revision>
<revision>2.13.4</revision>
<!-- Core Framework Versions -->
<spring-boot.version>3.3.12</spring-boot.version>

View File

@@ -39,5 +39,5 @@ public @interface CrudRequestMapping {
/**
* API 列表
*/
Api[] api() default {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE, Api.EXPORT};
Api[] api() default {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.BATCH_DELETE, Api.EXPORT, Api.DICT};
}

View File

@@ -17,13 +17,13 @@
package top.continew.starter.extension.crud.annotation;
import org.springframework.context.annotation.Import;
import top.continew.starter.extension.crud.autoconfigure.CrudApiAutoConfiguration;
import top.continew.starter.extension.crud.autoconfigure.CrudRequestMappingAutoConfiguration;
import top.continew.starter.extension.crud.autoconfigure.CrudRestControllerAutoConfiguration;
import java.lang.annotation.*;
/**
* CRUD REST Controller 启用注解
* CRUD API 启用注解
*
* @author Charles7c
* @since 1.2.0
@@ -31,5 +31,5 @@ import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CrudRequestMappingAutoConfiguration.class, CrudRestControllerAutoConfiguration.class})
public @interface EnableCrudRestController {}
@Import({CrudRequestMappingAutoConfiguration.class, CrudApiAutoConfiguration.class})
public @interface EnableCrudApi {}

View File

@@ -28,16 +28,16 @@ import top.continew.starter.extension.crud.aop.CrudApiAnnotationAdvisor;
import top.continew.starter.extension.crud.aop.CrudApiAnnotationInterceptor;
/**
* CRUD REST Controller 自动配置
* CRUD API 自动配置
*
* @author Charles7c
* @since 2.7.5
*/
@AutoConfiguration
@EnableConfigurationProperties(CrudProperties.class)
public class CrudRestControllerAutoConfiguration {
public class CrudApiAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(CrudRestControllerAutoConfiguration.class);
private static final Logger log = LoggerFactory.getLogger(CrudApiAutoConfiguration.class);
/**
* CRUD API 注解通知
@@ -59,6 +59,6 @@ public class CrudRestControllerAutoConfiguration {
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Extension-CRUD REST Controller' completed initialization.");
log.debug("[ContiNew Starter] - Auto Configuration 'Extension-CRUD API' completed initialization.");
}
}

View File

@@ -17,7 +17,6 @@
package top.continew.starter.extension.crud.autoconfigure;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@@ -34,7 +33,6 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider;
* @since 1.0.0
*/
@Configuration
@EnableConfigurationProperties(CrudProperties.class)
public class CrudRequestMappingAutoConfiguration extends DelegatingWebMvcConfiguration {
/**

View File

@@ -34,6 +34,7 @@ import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.req.IdsReq;
import top.continew.starter.extension.crud.model.resp.IdResp;
import top.continew.starter.extension.crud.model.resp.BasePageResp;
import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.service.CrudService;
import top.continew.starter.extension.crud.validation.CrudValidationGroup;
@@ -187,4 +188,32 @@ public abstract class AbstractCrudController<S extends CrudService<L, D, Q, C>,
public void export(@Valid Q query, @Valid SortQuery sortQuery, HttpServletResponse response) {
baseService.export(query, sortQuery, response);
}
/**
* 查询字典列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典列表信息
*/
@CrudApi(Api.DICT)
@Operation(summary = "查询字典列表", description = "查询字典列表(下拉选项等场景)")
@GetMapping("/dict")
public List<LabelValueResp> dict(@Valid Q query, @Valid SortQuery sortQuery) {
return baseService.dict(query, sortQuery);
}
/**
* 查询字典树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典树列表信息
*/
@CrudApi(Api.DICT_TREE)
@Operation(summary = "查询字典树列表", description = "查询树型结构字典列表(树型结构下拉选项等场景)")
@GetMapping("/dict/tree")
public List<Tree<Long>> dictTree(@Valid Q query, @Valid SortQuery sortQuery) {
return baseService.tree(query, sortQuery, true);
}
}

View File

@@ -68,4 +68,14 @@ public enum Api {
* 批量删除
*/
BATCH_DELETE,
/**
* 字典列表(下拉选项等场景)
*/
DICT,
/**
* 字典树列表(树型结构下拉选项等场景)
*/
DICT_TREE
}

View File

@@ -93,17 +93,6 @@ public interface CrudService<L, D, Q, C> {
*/
D get(Long id);
/**
* 查询字典列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典列表信息
* @since 2.1.0
* @see top.continew.starter.extension.crud.annotation.DictModel
*/
List<LabelValueResp> listDict(@Valid Q query, @Valid SortQuery sortQuery);
/**
* 创建
*
@@ -137,4 +126,15 @@ public interface CrudService<L, D, Q, C> {
* @param response 响应对象
*/
void export(@Valid Q query, @Valid SortQuery sortQuery, HttpServletResponse response);
/**
* 查询字典列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典列表信息
* @since 2.1.0
* @see top.continew.starter.extension.crud.annotation.DictModel
*/
List<LabelValueResp> dict(@Valid Q query, @Valid SortQuery sortQuery);
}

View File

@@ -19,9 +19,11 @@ package top.continew.starter.extension.crud.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
@@ -33,11 +35,13 @@ import org.springframework.transaction.annotation.Transactional;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.TreeUtils;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.core.util.validation.ValidationUtils;
import top.continew.starter.data.base.BaseMapper;
import top.continew.starter.data.service.impl.ServiceImpl;
import top.continew.starter.data.util.QueryWrapperHelper;
import top.continew.starter.excel.util.ExcelUtils;
import top.continew.starter.extension.crud.annotation.DictModel;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.autoconfigure.CrudProperties;
import top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties;
@@ -48,8 +52,7 @@ import top.continew.starter.extension.crud.model.resp.LabelValueResp;
import top.continew.starter.extension.crud.model.resp.PageResp;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.function.Function;
/**
@@ -135,11 +138,6 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
return detail;
}
@Override
public List<LabelValueResp> listDict(Q query, SortQuery sortQuery) {
return List.of();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long create(C req) {
@@ -175,6 +173,44 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
ExcelUtils.export(list, "导出数据", detailClass, response);
}
@Override
public List<LabelValueResp> dict(Q query, SortQuery sortQuery) {
DictModel dictModel = entityClass.getDeclaredAnnotation(DictModel.class);
CheckUtils.throwIfNull(dictModel, "请添加并配置 @DictModel 字典结构信息");
List<L> list = this.list(query, sortQuery);
// 解析映射
List<LabelValueResp> respList = new ArrayList<>(list.size());
String labelKey = dictModel.labelKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictModel.labelKey(), StringConstants.DOT, true)
: dictModel.labelKey();
String valueKey = dictModel.valueKey().contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(dictModel.valueKey(), StringConstants.DOT, true)
: dictModel.valueKey();
List<String> extraFieldNames = Arrays.stream(dictModel.extraKeys())
.map(extraKey -> extraKey.contains(StringConstants.DOT)
? CharSequenceUtil.subAfter(extraKey, StringConstants.DOT, true)
: extraKey)
.map(CharSequenceUtil::toCamelCase)
.toList();
for (L entity : list) {
LabelValueResp<Object> labelValueResp = new LabelValueResp<>();
labelValueResp.setLabel(Convert.toStr(ReflectUtil.getFieldValue(entity, CharSequenceUtil
.toCamelCase(labelKey))));
labelValueResp.setValue(ReflectUtil.getFieldValue(entity, CharSequenceUtil.toCamelCase(valueKey)));
respList.add(labelValueResp);
if (CollUtil.isEmpty(extraFieldNames)) {
continue;
}
// 额外数据
Map<String, Object> extraMap = MapUtil.newHashMap(dictModel.extraKeys().length);
for (String extraFieldName : extraFieldNames) {
extraMap.put(extraFieldName, ReflectUtil.getFieldValue(entity, extraFieldName));
}
labelValueResp.setExtra(extraMap);
}
return respList;
}
/**
* 查询列表
*

View File

@@ -141,7 +141,42 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
}
@Override
public List<LabelValueResp> listDict(Q query, SortQuery sortQuery) {
@Transactional(rollbackFor = Exception.class)
public Long create(C req) {
this.beforeCreate(req);
T entity = BeanUtil.copyProperties(req, super.getEntityClass());
baseMapper.insert(entity);
this.afterCreate(req, entity);
return entity.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(C req, Long id) {
this.beforeUpdate(req, id);
T entity = this.getById(id);
BeanUtil.copyProperties(req, entity, CopyOptions.create().ignoreNullValue());
baseMapper.updateById(entity);
this.afterUpdate(req, entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(List<Long> ids) {
this.beforeDelete(ids);
baseMapper.deleteByIds(ids);
this.afterDelete(ids);
}
@Override
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
List<D> list = this.list(query, sortQuery, this.getDetailClass());
list.forEach(this::fill);
ExcelUtils.export(list, "导出数据", this.getDetailClass(), response);
}
@Override
public List<LabelValueResp> dict(Q query, SortQuery sortQuery) {
DictModel dictModel = super.getEntityClass().getDeclaredAnnotation(DictModel.class);
CheckUtils.throwIfNull(dictModel, "请添加并配置 @DictModel 字典结构信息");
List<L> list = this.list(query, sortQuery);
@@ -178,41 +213,6 @@ public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D,
return respList;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long create(C req) {
this.beforeCreate(req);
T entity = BeanUtil.copyProperties(req, super.getEntityClass());
baseMapper.insert(entity);
this.afterCreate(req, entity);
return entity.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(C req, Long id) {
this.beforeUpdate(req, id);
T entity = this.getById(id);
BeanUtil.copyProperties(req, entity, CopyOptions.create().ignoreNullValue());
baseMapper.updateById(entity);
this.afterUpdate(req, entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(List<Long> ids) {
this.beforeDelete(ids);
baseMapper.deleteByIds(ids);
this.afterDelete(ids);
}
@Override
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
List<D> list = this.list(query, sortQuery, this.getDetailClass());
list.forEach(this::fill);
ExcelUtils.export(list, "导出数据", this.getDetailClass(), response);
}
/**
* 获取当前列表信息类型
*

View File

@@ -16,19 +16,18 @@
<description>ContiNew Starter 安全模块 - 加密</description>
<dependencies>
<!-- 安全模块 - 密码编码器 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-security-password</artifactId>
<optional>true</optional>
</dependency>
<!-- Hutool 加密解密模块(封装 JDK 中加密解密算法) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
<!-- Spring Security 附带的一个密码加密库 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>

View File

@@ -25,11 +25,20 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.security.crypto.enums.PasswordEncoderAlgorithm;
import top.continew.starter.security.crypto.mybatis.MyBatisDecryptInterceptor;
import top.continew.starter.security.crypto.mybatis.MyBatisEncryptInterceptor;
import top.continew.starter.security.crypto.util.EncryptHelper;
import top.continew.starter.security.crypto.util.PasswordEncoderUtil;
import java.util.HashMap;
import java.util.Map;
/**
* 加/解密自动配置
@@ -69,6 +78,31 @@ public class CryptoAutoConfiguration {
return new MyBatisDecryptInterceptor();
}
/**
* 密码编码器配置
*
* @see DelegatingPasswordEncoder
* @see PasswordEncoderFactories
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO + ".password-encoder", name = PropertiesConstants.ENABLED, havingValue = "true")
public PasswordEncoder passwordEncoder() {
PasswordEncoderProperties passwordEncoderProperties = properties.getPasswordEncoder();
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(PasswordEncoderAlgorithm.BCRYPT.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.BCRYPT));
encoders.put(PasswordEncoderAlgorithm.SCRYPT.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.SCRYPT));
encoders.put(PasswordEncoderAlgorithm.PBKDF2.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.PBKDF2));
encoders.put(PasswordEncoderAlgorithm.ARGON2.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.ARGON2));
PasswordEncoderAlgorithm algorithm = passwordEncoderProperties.getAlgorithm();
CheckUtils.throwIf(PasswordEncoderUtil.getEncoder(algorithm) == null, "不支持的加密算法: {}", algorithm);
return new DelegatingPasswordEncoder(algorithm.name().toLowerCase(), encoders);
}
@PostConstruct
public void postConstruct() {
EncryptHelper.init(properties);

View File

@@ -17,6 +17,7 @@
package top.continew.starter.security.crypto.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.security.crypto.enums.Algorithm;
@@ -55,6 +56,12 @@ public class CryptoProperties {
*/
private String privateKey;
/**
* 密码编码器配置
*/
@NestedConfigurationProperty
private PasswordEncoderProperties passwordEncoder;
public boolean isEnabled() {
return enabled;
}
@@ -94,4 +101,12 @@ public class CryptoProperties {
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
public PasswordEncoderProperties getPasswordEncoder() {
return passwordEncoder;
}
public void setPasswordEncoder(PasswordEncoderProperties passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
}

View File

@@ -14,19 +14,16 @@
* limitations under the License.
*/
package top.continew.starter.security.password.autoconfigure;
package top.continew.starter.security.crypto.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.security.password.enums.PasswordEncoderAlgorithm;
import top.continew.starter.security.crypto.enums.PasswordEncoderAlgorithm;
/**
* 密码编码配置属性
* 密码编码配置属性
*
* @author Jasmine
* @since 1.3.0
*/
@ConfigurationProperties(PropertiesConstants.SECURITY_PASSWORD)
public class PasswordEncoderProperties {
/**

View File

@@ -19,7 +19,8 @@ package top.continew.starter.security.crypto.encryptor;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
import top.continew.starter.security.password.autoconfigure.PasswordEncoderProperties;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.continew.starter.security.crypto.autoconfigure.PasswordEncoderProperties;
/**
* 密码编码器加/解密处理器
@@ -37,7 +38,7 @@ import top.continew.starter.security.password.autoconfigure.PasswordEncoderPrope
public class PasswordEncoderEncryptor extends AbstractEncryptor {
private final PasswordEncoder passwordEncoder = SpringUtil.getBean(PasswordEncoder.class);
private final PasswordEncoderProperties properties = SpringUtil.getBean(PasswordEncoderProperties.class);
private final CryptoProperties properties = SpringUtil.getBean(CryptoProperties.class);
public PasswordEncoderEncryptor(CryptoContext context) {
super(context);
@@ -46,7 +47,7 @@ public class PasswordEncoderEncryptor extends AbstractEncryptor {
@Override
public String encrypt(String plaintext) {
// 如果已经是加密格式,直接返回
if (properties.getAlgorithm().getPattern().matcher(plaintext).matches()) {
if (properties.getPasswordEncoder().getAlgorithm().getPattern().matcher(plaintext).matches()) {
return plaintext;
}
return passwordEncoder.encode(plaintext);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.security.password.enums;
package top.continew.starter.security.crypto.enums;
import java.util.regex.Pattern;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.security.password.exception;
package top.continew.starter.security.crypto.exception;
import top.continew.starter.core.exception.BaseException;

View File

@@ -21,8 +21,8 @@ import cn.hutool.core.util.ReflectUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.continew.starter.security.crypto.autoconfigure.CryptoContext;
import top.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.continew.starter.security.crypto.encryptor.IEncryptor;
import top.continew.starter.security.crypto.enums.Algorithm;
@@ -68,14 +68,14 @@ public class EncryptHelper {
* 计算 CryptoContext 对象的hashCode作为缓存中的key通过hashCode查询缓存中存在则直接返回不存在则创建并缓存
* </p>
*
* @param encryptContext 加密执行者需要的相关配置参数
* @param cryptoContext 加密执行者需要的相关配置参数
* @return 加密执行者
*/
public static IEncryptor registerAndGetEncryptor(CryptoContext encryptContext) {
int key = encryptContext.hashCode();
return ENCRYPTOR_CACHE.computeIfAbsent(key, k -> encryptContext.getEncryptor().equals(IEncryptor.class)
? ReflectUtil.newInstance(encryptContext.getAlgorithm().getEncryptor(), encryptContext)
: ReflectUtil.newInstance(encryptContext.getEncryptor(), encryptContext));
public static IEncryptor registerAndGetEncryptor(CryptoContext cryptoContext) {
int key = cryptoContext.hashCode();
return ENCRYPTOR_CACHE.computeIfAbsent(key, k -> cryptoContext.getEncryptor().equals(IEncryptor.class)
? ReflectUtil.newInstance(cryptoContext.getAlgorithm().getEncryptor(), cryptoContext)
: ReflectUtil.newInstance(cryptoContext.getEncryptor(), cryptoContext));
}
/**
@@ -104,8 +104,8 @@ public class EncryptHelper {
}
String ciphertext = value;
try {
CryptoContext encryptContext = buildEncryptContext(fieldEncrypt);
IEncryptor encryptor = registerAndGetEncryptor(encryptContext);
CryptoContext cryptoContext = buildCryptoContext(fieldEncrypt);
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
ciphertext = encryptor.encrypt(ciphertext);
} catch (Exception e) {
log.warn("加密失败,请检查加密配置,处理加密字段异常:{}", e.getMessage(), e);
@@ -125,8 +125,8 @@ public class EncryptHelper {
}
String ciphertext = value;
try {
CryptoContext encryptContext = buildEncryptContext();
IEncryptor encryptor = registerAndGetEncryptor(encryptContext);
CryptoContext cryptoContext = buildCryptoContext();
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
ciphertext = encryptor.encrypt(ciphertext);
} catch (Exception e) {
log.warn("加密失败,请检查加密配置,处理加密字段异常:{}", e.getMessage(), e);
@@ -147,8 +147,8 @@ public class EncryptHelper {
}
String plaintext = value;
try {
CryptoContext encryptContext = buildEncryptContext(fieldEncrypt);
IEncryptor encryptor = registerAndGetEncryptor(encryptContext);
CryptoContext cryptoContext = buildCryptoContext(fieldEncrypt);
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
plaintext = encryptor.decrypt(plaintext);
} catch (Exception e) {
log.warn("解密失败,请检查加密配置,处理解密字段异常:{}", e.getMessage(), e);
@@ -168,8 +168,8 @@ public class EncryptHelper {
}
String plaintext = value;
try {
CryptoContext encryptContext = buildEncryptContext();
IEncryptor encryptor = registerAndGetEncryptor(encryptContext);
CryptoContext cryptoContext = buildCryptoContext();
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
plaintext = encryptor.decrypt(plaintext);
} catch (Exception e) {
log.warn("解密失败,请检查加密配置,处理解密字段异常:{}", e.getMessage(), e);
@@ -183,24 +183,24 @@ public class EncryptHelper {
* @param fieldEncrypt 字段加密注解
* @return 加密上下文
*/
private static CryptoContext buildEncryptContext(FieldEncrypt fieldEncrypt) {
CryptoContext encryptContext = new CryptoContext();
encryptContext.setAlgorithm(fieldEncrypt.value() == Algorithm.DEFAULT
private static CryptoContext buildCryptoContext(FieldEncrypt fieldEncrypt) {
CryptoContext cryptoContext = new CryptoContext();
cryptoContext.setAlgorithm(fieldEncrypt.value() == Algorithm.DEFAULT
? defaultProperties.getAlgorithm()
: fieldEncrypt.value());
encryptContext.setEncryptor(fieldEncrypt.encryptor().equals(IEncryptor.class)
cryptoContext.setEncryptor(fieldEncrypt.encryptor().equals(IEncryptor.class)
? IEncryptor.class
: fieldEncrypt.encryptor());
encryptContext.setPassword(fieldEncrypt.password().isEmpty()
cryptoContext.setPassword(fieldEncrypt.password().isEmpty()
? defaultProperties.getPassword()
: fieldEncrypt.password());
encryptContext.setPrivateKey(fieldEncrypt.privateKey().isEmpty()
cryptoContext.setPrivateKey(fieldEncrypt.privateKey().isEmpty()
? defaultProperties.getPrivateKey()
: fieldEncrypt.privateKey());
encryptContext.setPublicKey(fieldEncrypt.publicKey().isEmpty()
cryptoContext.setPublicKey(fieldEncrypt.publicKey().isEmpty()
? defaultProperties.getPublicKey()
: fieldEncrypt.publicKey());
return encryptContext;
return cryptoContext;
}
/**
@@ -208,12 +208,13 @@ public class EncryptHelper {
*
* @return 加密上下文
*/
private static CryptoContext buildEncryptContext() {
CryptoContext encryptContext = new CryptoContext();
encryptContext.setAlgorithm(defaultProperties.getAlgorithm());
encryptContext.setPassword(defaultProperties.getPassword());
encryptContext.setPrivateKey(defaultProperties.getPrivateKey());
encryptContext.setPublicKey(defaultProperties.getPublicKey());
return encryptContext;
private static CryptoContext buildCryptoContext() {
CryptoContext cryptoContext = new CryptoContext();
cryptoContext.setAlgorithm(defaultProperties.getAlgorithm());
cryptoContext.setEncryptor(IEncryptor.class);
cryptoContext.setPassword(defaultProperties.getPassword());
cryptoContext.setPrivateKey(defaultProperties.getPrivateKey());
cryptoContext.setPublicKey(defaultProperties.getPublicKey());
return cryptoContext;
}
}

View File

@@ -14,15 +14,15 @@
* limitations under the License.
*/
package top.continew.starter.security.password.util;
package top.continew.starter.security.crypto.util;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import top.continew.starter.security.password.enums.PasswordEncoderAlgorithm;
import top.continew.starter.security.password.exception.PasswordEncodeException;
import top.continew.starter.security.crypto.enums.PasswordEncoderAlgorithm;
import top.continew.starter.security.crypto.exception.PasswordEncodeException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

View File

@@ -1,25 +0,0 @@
<?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>top.continew.starter</groupId>
<artifactId>continew-starter-security</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-security-password</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 安全模块 - 密码编码器</description>
<dependencies>
<!-- Spring Security 附带的一个密码加密库 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,87 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.security.password.autoconfigure;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import top.continew.starter.core.util.validation.CheckUtils;
import top.continew.starter.security.password.enums.PasswordEncoderAlgorithm;
import top.continew.starter.security.password.util.PasswordEncoderUtil;
import java.util.HashMap;
import java.util.Map;
/**
* 密码编码器自动配置
*
* <p>
* 密码配置类,默认编解码器使用的是 BCryptPasswordEncoder <br />
* 编码后的密码是遵循一定规则的 {idForEncode}encodePassword前缀 {} 包含了编码的方式再拼接上该方式编码后的密码串。<br />
* 可以添加自定义的编解码,也可以修改默认的编解码器,只需修改默认的 encodingId。<br />
* 优点:如果有一天我们对密码编码规则进行替换或者轮转,现有的用户不会受到影响,只要修改 DelegatingPasswordEncoder 的 idForEncode 即可。
* </p>
*
* @author Jasmine
* @author Charles7c
* @since 1.3.0
*/
@AutoConfiguration
@EnableConfigurationProperties(PasswordEncoderProperties.class)
@PropertySource(value = "classpath:default-password.yml", factory = GeneralPropertySourceFactory.class)
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_PASSWORD, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class PasswordEncoderAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(PasswordEncoderAutoConfiguration.class);
/**
* 密码编码器
*
* @see DelegatingPasswordEncoder
* @see PasswordEncoderFactories
*/
@Bean
public PasswordEncoder passwordEncoder(PasswordEncoderProperties properties) {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(PasswordEncoderAlgorithm.BCRYPT.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.BCRYPT));
encoders.put(PasswordEncoderAlgorithm.SCRYPT.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.SCRYPT));
encoders.put(PasswordEncoderAlgorithm.PBKDF2.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.PBKDF2));
encoders.put(PasswordEncoderAlgorithm.ARGON2.name().toLowerCase(), PasswordEncoderUtil
.getEncoder(PasswordEncoderAlgorithm.ARGON2));
PasswordEncoderAlgorithm algorithm = properties.getAlgorithm();
CheckUtils.throwIf(PasswordEncoderUtil.getEncoder(algorithm) == null, "不支持的加密算法: {}", algorithm);
return new DelegatingPasswordEncoder(algorithm.name().toLowerCase(), encoders);
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Security-PasswordEncoder' completed initialization.");
}
}

View File

@@ -1 +0,0 @@
top.continew.starter.security.password.autoconfigure.PasswordEncoderAutoConfiguration

View File

@@ -1,6 +0,0 @@
--- ### 安全配置:密码编码器配置
continew-starter.security:
password:
enabled: true
# 默认启用的编码器算法默认BCrypt 加密算法)
algorithm: BCRYPT

View File

@@ -16,11 +16,10 @@
<description>ContiNew Starter 安全模块</description>
<modules>
<module>continew-starter-security-password</module>
<module>continew-starter-security-mask</module>
<module>continew-starter-security-crypto</module>
<module>continew-starter-security-sensitivewords</module>
<module>continew-starter-security-mask</module>
<module>continew-starter-security-xss</module>
<module>continew-starter-security-sensitivewords</module>
</modules>
<dependencies>