ref 升级脚手架依赖

This commit is contained in:
bootx
2025-08-21 12:48:17 +08:00
parent 1a3abea1e1
commit 6df7782ed2
107 changed files with 1419 additions and 6524 deletions

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-common</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>common-config</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-common</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>common-exception-handler</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-common</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>common-header-holder</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-common</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>common-jackson</artifactId>

View File

@@ -8,6 +8,7 @@ import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
/**
* jackson常用工具类封装
@@ -130,4 +131,24 @@ public class JacksonUtil {
throw new RuntimeException("json反序列化失败");
}
}
/**
* 对象转为map
*/
public Map<String, Object> parseObj(Object mchApply) {
return parseObj(mchApply,true);
}
@SuppressWarnings("unchecked")
public Map<String, Object> parseObj(Object mchApply, boolean ignoreNull) {
try {
if (ignoreNull) {
return ignoreNullObjectMapper.convertValue(mchApply, Map.class);
}
return objectMapper.convertValue(mchApply, Map.class);
} catch (IllegalArgumentException e) {
log.error(e.getMessage(), e);
throw new RuntimeException("json反序列化失败");
}
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-common</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>common-log</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-common</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>common-mybatis-plus</artifactId>
@@ -63,6 +63,17 @@
<artifactId>common-spring</artifactId>
<version>${bootx-platform.version}</version>
</dependency>
<!-- hutool 组件 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-db</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>

View File

@@ -1,8 +1,8 @@
package cn.bootx.platform.common.mybatisplus.handler;
import cn.bootx.platform.core.util.JsonUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
@@ -35,13 +35,13 @@ public class IntegerListTypeHandler extends AbstractJsonTypeHandler<List<Integer
@Override
public List<Integer> parse(String json) {
if (StrUtil.isNotBlank(json)){
return JsonUtil.toBean(json, new TypeReference<>() {}, false);
return JSONUtil.toBean(json, new TypeReference<>() {}, false);
}
return List.of();
}
@Override
public String toJson(List<Integer> obj) {
return JsonUtil.toJsonStr(obj);
return JSONUtil.toJsonStr(obj);
}
}

View File

@@ -3,6 +3,7 @@ package cn.bootx.platform.common.mybatisplus.handler;
import cn.bootx.platform.core.util.JsonUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
@@ -30,13 +31,13 @@ public class LongListTypeHandler extends AbstractJsonTypeHandler<List<Long>> {
@Override
public List<Long> parse(String json) {
if (StrUtil.isNotBlank(json)){
return JsonUtil.toBean(json, new TypeReference<>() {}, false);
return JSONUtil.toBean(json, new TypeReference<>() {}, false);
}
return List.of();
}
@Override
public String toJson(List<Long> obj) {
return JsonUtil.toJsonStr(obj);
return JSONUtil.toJsonStr(obj);
}
}

View File

@@ -39,6 +39,6 @@ public class StringListTypeHandler extends AbstractJsonTypeHandler<List<String>>
@Override
public String toJson(List<String> obj) {
return JsonUtil.toJsonStr(obj);
return JSONUtil.toJsonStr(obj);
}
}

View File

@@ -2,11 +2,13 @@ package cn.bootx.platform.common.mybatisplus.util;
import cn.bootx.platform.common.mybatisplus.function.ToResult;
import cn.bootx.platform.core.annotation.BigField;
import cn.bootx.platform.core.exception.BizException;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
@@ -20,7 +22,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.experimental.UtilityClass;
import org.apache.ibatis.reflection.property.PropertyNamer;
import javax.sql.DataSource;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -159,4 +164,17 @@ public class MpUtil {
return null;
}
/**
* 获取当前数据库类型, 别忘了关闭, 不然会连接池泄露
*/
public String getDbType(){
try {
try (Connection connection = SpringUtil.getBean(DataSource.class).getConnection()) {
return connection.getMetaData().getDatabaseProductName();
}
} catch (SQLException e) {
throw new BizException("获取数据库类型失败");
}
}
}

View File

@@ -0,0 +1,312 @@
package cn.bootx.platform.common.mybatisplus.util;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.handler.BeanHandler;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HtmlUtil;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.PropertyPlaceholderHelper;
import javax.sql.DataSource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 数据库工具类
*
* @author nn200433
* @date 2024-03-28 09:32:34
*/
@Slf4j
public class MybatisDbUtil {
private static final String PARAM_KEY_PRE = "ew.paramNameValuePairs.";
private static final String UPDATE_SET_PREFIX = "SET";
private static final String SQL_PREFIX_AND_SUFFIX = "'";
public static final String PLACEHOLDER_PREFIX = "#{";
private static final String PLACEHOLDER_SUFFIX = "}";
private static final PropertyPlaceholderHelper PLACEHOLDER_HELPER = new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX);
/**
* 物理删除
*
* @param tableClz 数据表实体类型
* @param ids 主键
* @return int 影响行数
* @author nn200433
*/
public static int delete(Class<?> tableClz, String... ids) {
final TableInfo tableInfo = getTableInfo(tableClz);
final String tableName = tableInfo.getTableName();
final String keyColumn = tableInfo.getKeyColumn();
Assert.notBlank(keyColumn, "{} 未设置 @TableId 注解!", tableClz);
int count = 0;
try {
count = Db.use(dataSource()).del(Entity.create(tableName).set(keyColumn, ids));
} catch (Exception e) {
log.error("", e);
}
return count;
}
/**
* 物理删除
*
* <p>
* 请使用{@link com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper}构造wrapper
* </p>
*
* @param wrapper 包装物
* @return int 影响行数
* @author nn200433
*/
public static <T> int delete(AbstractWrapper<T, ?, ?> wrapper) {
final Class<T> entityClass = (Class<T>) getEntityClass(wrapper);
final TableInfo tableInfo = getTableInfo(entityClass);
final String whereSql = wrapper.getCustomSqlSegment();
final String tableName = tableInfo.getTableName();
final Map<String, Object> paramNameValuePairs = wrapper.getParamNameValuePairs();
int count = 0;
try {
final String sql = getSql(SqlMethod.DELETE, tableName, StrUtil.EMPTY, StrUtil.EMPTY, whereSql, null, paramNameValuePairs);
count = Db.use(dataSource()).execute(sql);
} catch (Exception e) {
log.error("", e);
}
return count;
}
/**
* 通过ID查询数据忽略逻辑删除
*
* @param id 主键id值
* @param entityClass 返回的实体类型
* @return {@link T }
* @author nn200433
*/
public static <T> T selectById(Object id, Class<T> entityClass) {
final TableInfo tableInfo = getTableInfo(entityClass);
final String keyColumn = tableInfo.getKeyColumn();
final QueryWrapper<T> wrapper = new QueryWrapper<T>().eq(keyColumn, id);
final String tableName = tableInfo.getTableName();
final String whereSql = wrapper.getCustomSqlSegment();
final Map<String, Object> paramNameValuePairs = wrapper.getParamNameValuePairs();
T result = null;
try {
final String sql = getSql(SqlMethod.SELECT_BY_MAP, tableName, StrUtil.EMPTY, tableInfo.getAllSqlSelect(), whereSql, null, paramNameValuePairs);
result = Db.use(dataSource()).query(sql, new BeanHandler<T>(entityClass));
} catch (Exception e) {
log.error("", e);
}
return result;
}
/**
* 查询列表(忽略逻辑删除)
*
* <p>
* 请使用{@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}构造wrapper。
* <br/>
* 举例new LambdaQueryWrapper<SmsTemplate>(Entity.class).eq(Entity::getCode, "2");
* </p>
*
* @param wrapper 包装物
* @return {@link List }<{@link T }>
* @author nn200433
*/
public static <T> List<T> selectList(AbstractWrapper<T, ?, ?> wrapper) {
List<T> resultList = new ArrayList<T>();
final Class<T> entityClass = (Class<T>) getEntityClass(wrapper);
final TableInfo tableInfo = getTableInfo(entityClass);
final String whereSql = wrapper.getCustomSqlSegment();
final String columnSql = wrapper.getSqlSelect();
final String tableName = tableInfo.getTableName();
final Map<String, Object> paramNameValuePairs = wrapper.getParamNameValuePairs();
try {
final String sql = getSql(SqlMethod.SELECT_LIST, tableName, StrUtil.EMPTY, StrUtil.blankToDefault(columnSql, tableInfo.getAllSqlSelect()), whereSql, null, paramNameValuePairs);
resultList = Db.use(dataSource()).query(sql, entityClass);
} catch (Exception e) {
log.error("", e);
}
return resultList;
}
/**
* 更新(忽略逻辑删除)
*
* @param entity 实体类
* @return int 影响行数
* @author nn200433
*/
public static <T> int update(T entity) {
return update(entity, null);
}
/**
* 通过条件更新数据(忽略逻辑删除)
* <p>
* 请使用{@link com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper}构造wrapper
* </p>
*
* @param entity 实体类
* @param wrapper 包装物
* @return int 影响行数
* @author nn200433
*/
public static <T> int update(T entity, AbstractWrapper<T, ?, ?> wrapper) {
final TableInfo tableInfo = getTableInfo(entity.getClass());
final String tableName = tableInfo.getTableName();
if (null == wrapper) {
final String keyProperty = tableInfo.getKeyProperty();
Assert.notBlank(keyProperty, "{} 未设置 @TableId 注解!", keyProperty);
wrapper = new UpdateWrapper<T>(entity).eq(keyProperty, ReflectUtil.getFieldValue(entity, keyProperty));
}
final String whereSql = wrapper.getCustomSqlSegment();
final Map<String, Object> paramNameValuePairs = wrapper.getParamNameValuePairs();
int count = 0;
try {
final String sql = getSql(SqlMethod.UPDATE, tableName, StrUtil.EMPTY, tableInfo.getAllSqlSet(Boolean.TRUE, StrUtil.EMPTY), whereSql, BeanUtil.beanToMap(entity), paramNameValuePairs);
count = Db.use(dataSource()).execute(sql);
} catch (Exception e) {
log.error("", e);
}
return count;
}
/**
* 获取sql
*
* @param method SQL方法
* @param tableName 数据库表名称
* @param firstSql firtSql
* @param columnSql 字段sql
* @param whereSql where条件sql
* @param setMap set参数
* @param paramNameValuePairs where条件值
* @return {@link String }
* @author nn200433
*/
private static String getSql(SqlMethod method, String tableName, String firstSql, String columnSql, String whereSql,
Map<String, Object> setMap, Map<String, Object> paramNameValuePairs) {
final Map<String, Object> paramMap = new HashMap<String, Object>(paramNameValuePairs.size());
for (final Map.Entry<String, Object> entry : paramNameValuePairs.entrySet()) {
paramMap.put(PARAM_KEY_PRE + entry.getKey(), entry.getValue());
}
if (CollUtil.isNotEmpty(setMap)) {
paramMap.putAll(setMap);
}
// 害人不浅,包了这么标签
String originalSql = null;
switch (method) {
case DELETE:
originalSql = String.format(method.getSql(), tableName, whereSql, StrUtil.EMPTY);
break;
case UPDATE:
columnSql = StrUtil.addPrefixIfNot(columnSql, UPDATE_SET_PREFIX + StrUtil.SPACE);
columnSql = StrUtil.removeSuffix(columnSql, StrUtil.COMMA);
originalSql = String.format(method.getSql(), tableName, columnSql, whereSql, StrUtil.EMPTY);
break;
case SELECT_LIST:
// 判断个锤子OrderBy注解麻烦
originalSql = String.format(method.getSql(), firstSql, columnSql, tableName, whereSql, StrUtil.EMPTY, StrUtil.EMPTY);
break;
case SELECT_BY_MAP:
// 垃圾 SELECT_BY_ID ,限制那么多
originalSql = String.format(method.getSql(), columnSql, tableName, whereSql, StrUtil.EMPTY);
break;
default:
}
final String preSql = StrUtil.trim(HtmlUtil.cleanHtmlTag(originalSql));
return resolveParams(preSql, paramMap);
}
/**
* 从wrapper中尝试获取实体类型
*
* @param queryWrapper 条件构造器
* @param <T> 实体类型
* @return 实体类型
*/
private static <T> Class<T> getEntityClass(AbstractWrapper<T, ?, ?> queryWrapper) {
Class<T> entityClass = queryWrapper.getEntityClass();
if (entityClass == null) {
T entity = queryWrapper.getEntity();
if (entity != null) {
entityClass = (Class<T>) entity.getClass();
}
}
Assert.notNull(entityClass, "error: can not get entityClass from wrapper");
return entityClass;
}
/**
* 获取表信息,获取不到报错提示
*
* @param entityClass 实体类
* @param <T> 实体类型
* @return 对应表信息
*/
private static <T> TableInfo getTableInfo(Class<T> entityClass) {
return Optional.ofNullable(TableInfoHelper.getTableInfo(entityClass))
.orElseThrow(() -> ExceptionUtils.mpe("error: can not find TableInfo from Class: \"%s\".", entityClass.getName()));
}
/**
* 解析参数
*
* @param str 包含要替换的占位符的值
* @param param 参数
* @return @return {@link String }
* @author nn200433
*/
private static String resolveParams(String str, Map<String, Object> param) {
return PLACEHOLDER_HELPER.replacePlaceholders(str, key -> {
// final String v = MapUtil.(param, key);
final Object v = param.get(key);
if (null == v) {
return "null";
}
final Class<?> clz = v.getClass();
if (clz.equals(Date.class)) {
final Date vd = (Date) v;
return StrUtil.wrap(DateUtil.format(vd, DatePattern.NORM_DATETIME_FORMAT), SQL_PREFIX_AND_SUFFIX);
} else if (clz.equals(LocalDateTime.class)) {
final LocalDateTime vd = (LocalDateTime) v;
return StrUtil.wrap(DateUtil.formatLocalDateTime(vd), SQL_PREFIX_AND_SUFFIX);
}
return StrUtil.wrap(Convert.toStr(v), SQL_PREFIX_AND_SUFFIX);
});
}
/**
* 获取数据源
*
* @return {@link DataSource }
* @author nn200433
*/
private static DataSource dataSource() {
return SpringUtil.getBean(DataSource.class);
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-common</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>common-redis</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-common</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>common-spring</artifactId>

View File

@@ -1,6 +1,7 @@
package cn.bootx.platform.common.spring.configuration;
import cn.bootx.platform.core.util.JsonUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
@@ -39,7 +40,7 @@ public class AsyncExecutorConfiguration implements AsyncConfigurer {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
log.error("异步方法中发生异常,方法:{},参数:{},异常:{}", method.getName(), JsonUtil.toJsonStr(objects),
log.error("异步方法中发生异常,方法:{},参数:{},异常:{}", method.getName(), JSONUtil.toJsonStr(objects),
throwable.getMessage());
log.error("详细异常信息", throwable);
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-common</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>common-swagger</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 组件忽略注解
* 忽略租户数据隔离注解
* @author xxm
* @since 2024/6/25
*/

View File

@@ -13,7 +13,7 @@ import java.time.LocalDateTime;
*/
@Getter
@Setter
public class BaseResult {
public class BaseResult{
@Schema(description = "主键")
private Long id;

View File

@@ -8,7 +8,7 @@ import lombok.experimental.UtilityClass;
import java.util.Collection;
/**
* json工具类, 基于hutool的进行封装,
* json工具类, 基于hutool的进行封装, 仅用于与业务系统交互使用, 为了保持与SDK
* 对java8的LocalDateTime时间格式进行转换, 但无法处理LocalDate, LocalTime格式, 需要使用JacksonUtil进行处理
* @author xxm
* @since 2024/6/28
@@ -17,32 +17,6 @@ import java.util.Collection;
public class JsonUtil {
private final JSONConfig JSON_CONFIG = JSONConfig.create().setDateFormat(DatePattern.NORM_DATETIME_PATTERN);
/**
* 转换为实体
*/
public <T> T toBean(String json, Class<T> clazz){
JSONObject jsonObject = new JSONObject(json, JSON_CONFIG);
return JSONUtil.toBean(jsonObject, clazz);
}
/**
* 转换为实体
*/
public <T> T toBean(String json, TypeReference<T> reference){
JSON parse = JSONUtil.parse(json, JSON_CONFIG);
return parse.toBean(reference);
}
/**
* 转换为实体
*/
public <T> T toBean(String json, TypeReference<T> reference, boolean ignoreError){
JSONConfig jsonConfig = JSONConfig.create()
.setDateFormat(DatePattern.NORM_DATETIME_PATTERN)
.setIgnoreError(ignoreError);
JSON parse = JSONUtil.parse(json, jsonConfig);
return parse.toBean(reference);
}
/**
* 序列化为字符串
@@ -61,9 +35,10 @@ public class JsonUtil {
}
/**
* JSON字符串转JSONObject对象
* 转换为实体, 仅供处理验签时使用, 其他场景不要使用
*/
public JSONObject parseObj(String jsonStr){
return JSONUtil.parseObj(jsonStr, JSON_CONFIG);
public <T> T toBean(String json, TypeReference<T> reference) {
JSON parse = JSONUtil.parse(json, JSON_CONFIG);
return parse.toBean(reference);
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<modules>
<module>service-baseapi</module>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-service</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>service-baseapi</artifactId>

View File

@@ -0,0 +1,97 @@
package cn.bootx.platform.baseapi.controller.protocol;
import cn.bootx.platform.baseapi.param.protocol.UserProtocolParam;
import cn.bootx.platform.baseapi.param.protocol.UserProtocolQuery;
import cn.bootx.platform.baseapi.result.protocol.UserProtocolResult;
import cn.bootx.platform.baseapi.service.protocol.UserProtocolService;
import cn.bootx.platform.core.annotation.IgnoreAuth;
import cn.bootx.platform.core.annotation.RequestGroup;
import cn.bootx.platform.core.annotation.RequestPath;
import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.core.validation.ValidationGroup;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 用户协议
* @author xxm
* @since 2025/5/9
*/
@Validated
@Tag(name = "用户协议")
@RequestGroup(groupCode = "UserProtocol", groupName = "用户协议", moduleCode = "baseapi" )
@RestController
@RequestMapping("/user/protocol")
@RequiredArgsConstructor
public class UserProtocolController {
private final UserProtocolService userProtocolService;
@RequestPath("分页")
@Operation(summary = "分页")
@GetMapping("/page")
public Result<PageResult<UserProtocolResult>> page(PageParam pageParam, UserProtocolQuery query){
return Res.ok(userProtocolService.page(pageParam, query));
}
@RequestPath("新增")
@Operation(summary = "新增")
@PostMapping("/add")
public Result<Void> add(@RequestBody @Validated(ValidationGroup.add.class) UserProtocolParam param){
userProtocolService.add(param);
return Res.ok();
}
@RequestPath("修改")
@Operation(summary = "修改")
@PostMapping("/update")
public Result<Void> update(@RequestBody @Validated(ValidationGroup.edit.class) UserProtocolParam param){
userProtocolService.update(param);
return Res.ok();
}
@RequestPath("删除")
@Operation(summary = "删除")
@PostMapping("/delete")
public Result<Void> delete(@NotNull(message = "主键不可为空") Long id){
userProtocolService.delete(id);
return Res.ok();
}
@RequestPath("查询")
@Operation(summary = "查询")
@GetMapping("/findById")
public Result<UserProtocolResult> findById(@NotNull(message = "主键不可为空") Long id){
return Res.ok(userProtocolService.findById(id));
}
@IgnoreAuth
@Operation(summary = "查询默认协议")
@GetMapping("/findDefault")
public Result<UserProtocolResult> findDefault(@NotNull(message = "协议类型不可为空") String type){
return Res.ok(userProtocolService.findDefault(type));
}
@RequestPath("设置默认")
@Operation(summary = "设置默认")
@PostMapping("/setDefault")
public Result<Void> setDefault(@NotNull(message = "主键不可为空") Long id){
userProtocolService.setDefault(id);
return Res.ok();
}
@RequestPath("取消默认")
@Operation(summary = "取消默认")
@PostMapping("/cancelDefault")
public Result<Void> cancelDefault(@NotNull(message = "主键不可为空") Long id){
userProtocolService.cancelDefault(id);
return Res.ok();
}
}

View File

@@ -0,0 +1,21 @@
package cn.bootx.platform.baseapi.convert.protocol;
import cn.bootx.platform.baseapi.entity.protocol.UserProtocol;
import cn.bootx.platform.baseapi.param.protocol.UserProtocolParam;
import cn.bootx.platform.baseapi.result.protocol.UserProtocolResult;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
*用户协议管理
* @author xxm
* @since 2025/5/9
*/
@Mapper
public interface UserProtocolConvert {
UserProtocolConvert CONVERT = Mappers.getMapper(UserProtocolConvert.class);
UserProtocolResult toResult(UserProtocol userProtocol);
UserProtocol toEntity(UserProtocolParam param);
}

View File

@@ -2,6 +2,7 @@ package cn.bootx.platform.baseapi.dao.dict;
import cn.bootx.platform.baseapi.entity.dict.DictionaryItem;
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
import cn.bootx.platform.common.mybatisplus.base.MpRealDelEntity;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.core.rest.param.PageParam;
@@ -63,7 +64,11 @@ public class DictionaryItemManager extends BaseManager<DictionaryItemMapper, Dic
}
public void updateDictCode(Long dictId, String dictCode) {
lambdaUpdate().set(DictionaryItem::getDictCode, dictCode).eq(DictionaryItem::getDictId, dictId).update();
lambdaUpdate()
.set(DictionaryItem::getDictCode, dictCode)
.eq(DictionaryItem::getDictId, dictId)
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
public List<DictionaryItem> findAllByEnable(boolean enable) {

View File

@@ -30,21 +30,21 @@ public class SystemParamManager extends BaseManager<SystemParamMapper, SystemPar
* 根据键名获取键值
*/
public Optional<SystemParameter> findByKey(String key) {
return this.findByField(SystemParameter::getKey, key);
return this.findByField(SystemParameter::getParamKey, key);
}
/**
* key重复检查
*/
public boolean existsByKey(String key) {
return existedByField(SystemParameter::getKey, key);
return existedByField(SystemParameter::getParamKey, key);
}
/**
* key重复检查
*/
public boolean existsByKey(String key, Long id) {
return existedByField(SystemParameter::getKey, key, id);
return existedByField(SystemParameter::getParamKey, key, id);
}
/**
@@ -54,7 +54,7 @@ public class SystemParamManager extends BaseManager<SystemParamMapper, SystemPar
Page<SystemParameter> mpPage = MpUtil.getMpPage(pageParam);
return lambdaQuery().orderByDesc(MpIdEntity::getId)
.like(StrUtil.isNotBlank(param.getName()), SystemParameter::getName, param.getName())
.like(StrUtil.isNotBlank(param.getKey()), SystemParameter::getKey, param.getKey())
.like(StrUtil.isNotBlank(param.getKey()), SystemParameter::getParamKey, param.getKey())
.page(mpPage);
}

View File

@@ -0,0 +1,79 @@
package cn.bootx.platform.baseapi.dao.protocol;
import cn.bootx.platform.baseapi.entity.protocol.UserProtocol;
import cn.bootx.platform.baseapi.param.protocol.UserProtocolQuery;
import cn.bootx.platform.common.mybatisplus.base.MpRealDelEntity;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.common.mybatisplus.query.generator.QueryGenerator;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.core.rest.param.PageParam;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 用户协议管理
* @author xxm
* @since 2025/5/9
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class UserProtocolManager extends BaseManager<UserProtocolMapper, UserProtocol> {
/**
* 分页
*/
public Page<UserProtocol> page(PageParam pageParam, UserProtocolQuery query){
Page<UserProtocol> mpPage = MpUtil.getMpPage(pageParam, UserProtocol.class);
QueryWrapper<UserProtocol> generator = QueryGenerator.generator(query);
return this.page(mpPage,generator);
}
/**
* 根据分类查询默认协议
*/
public Optional<UserProtocol> findDefault(String type){
return this.lambdaQuery()
.eq(UserProtocol::getType,type)
.eq(UserProtocol::getDefaultProtocol,true)
.oneOpt();
}
/**
* 清除默认协议
*/
public void clearDefault(String type){
this.lambdaUpdate()
.eq(UserProtocol::getType,type)
.set(UserProtocol::getDefaultProtocol,false)
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
/**
* 设置默认协议
*/
public void setDefault(Long id){
this.lambdaUpdate()
.eq(UserProtocol::getId,id)
.set(UserProtocol::getDefaultProtocol,true)
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
/**
* 取消默认协议
*/
public void cancelDefault(Long id){
this.lambdaUpdate()
.eq(UserProtocol::getId,id)
.set(UserProtocol::getDefaultProtocol,false)
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
}

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.baseapi.dao.protocol;
import cn.bootx.platform.baseapi.entity.protocol.UserProtocol;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
*
* @author xxm
* @since 2025/5/9
*/
@Mapper
public interface UserProtocolMapper extends MPJBaseMapper<UserProtocol> {
}

View File

@@ -28,10 +28,10 @@ public class SystemParameter extends MpBaseEntity implements ToResult<SystemPara
private String name;
/** 参数键名 */
private String key;
private String paramKey;
/** 参数值 */
private String value;
private String paramValue;
/** 参数类型 */
private String type;

View File

@@ -0,0 +1,47 @@
package cn.bootx.platform.baseapi.entity.protocol;
import cn.bootx.platform.baseapi.convert.protocol.UserProtocolConvert;
import cn.bootx.platform.baseapi.param.protocol.UserProtocolParam;
import cn.bootx.platform.baseapi.result.protocol.UserProtocolResult;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.common.mybatisplus.function.ToResult;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 用户协议管理
* @author xxm
* @since 2025/5/9
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("base_user_protocol")
public class UserProtocol extends MpBaseEntity implements ToResult<UserProtocolResult> {
/** 名称 */
private String name;
/** 显示名称 */
private String showName;
/** 类型 */
private String type;
/** 默认协议 */
private Boolean defaultProtocol;
/** 内容 */
private String content;
@Override
public UserProtocolResult toResult() {
return UserProtocolConvert.CONVERT.toResult(this);
}
public static UserProtocol init(UserProtocolParam param) {
return UserProtocolConvert.CONVERT.toEntity(param);
}
}

View File

@@ -0,0 +1,44 @@
package cn.bootx.platform.baseapi.param.protocol;
import cn.bootx.platform.core.validation.ValidationGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 用户协议管理
* @author xxm
* @since 2025/5/9
*/
@Data
@Accessors(chain = true)
@Schema(title = "用户协议管理")
public class UserProtocolParam {
@Null(message = "Id需要为空", groups = ValidationGroup.add.class)
@NotNull(message = "Id不可为空", groups = ValidationGroup.edit.class)
@Schema(description = "主键")
private Long id;
/** 名称 */
@NotBlank(message = "名称不能为空")
@Schema(description = "名称")
private String name;
/** 显示名称 */
@NotBlank(message = "显示名称不能为空")
@Schema(description = "显示名称")
private String showName;
/** 类型 */
@NotBlank(message = "类型不能为空")
@Schema(description = "类型")
private String type;
/** 内容 */
@Schema(description = "内容")
private String content;
}

View File

@@ -0,0 +1,32 @@
package cn.bootx.platform.baseapi.param.protocol;
import cn.bootx.platform.common.mybatisplus.query.entity.SortParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 用户协议
* @author xxm
* @since 2025/5/11
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "用户协议")
public class UserProtocolQuery extends SortParam {
/** 名称 */
@Schema(description = "名称")
private String name;
/** 显示名称 */
@Schema(description = "显示名称")
private String showName;
/** 类型 */
@Schema(description = "类型")
private String type;
}

View File

@@ -0,0 +1,39 @@
package cn.bootx.platform.baseapi.result.protocol;
import cn.bootx.platform.core.result.BaseResult;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 用户协议管理
* @author xxm
* @since 2025/5/9
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "用户协议管理")
public class UserProtocolResult extends BaseResult {
/** 名称 */
@Schema(description = "名称")
private String name;
/** 显示名称 */
@Schema(description = "显示名称")
private String showName;
/** 类型 */
@Schema(description = "类型")
private String type;
/** 默认协议 */
@Schema(description = "默认协议")
private Boolean defaultProtocol;
/** 内容 */
@Schema(description = "内容")
private String content;
}

View File

@@ -36,7 +36,7 @@ public class SystemParamService {
*/
public void add(SystemParameterParam param) {
SystemParameter systemParameter = SystemParameter.init(param);
if (systemParamManager.existsByKey(systemParameter.getKey())) {
if (systemParamManager.existsByKey(systemParameter.getParamKey())) {
throw new BizException("key重复");
}
// 默认非内置
@@ -81,7 +81,7 @@ public class SystemParamService {
if (Objects.equals(param.getEnable(), false)) {
throw new BizException("该参数已停用");
}
return param.getValue();
return param.getParamValue();
}
/**

View File

@@ -0,0 +1,107 @@
package cn.bootx.platform.baseapi.service.protocol;
import cn.bootx.platform.baseapi.dao.protocol.UserProtocolManager;
import cn.bootx.platform.baseapi.entity.protocol.UserProtocol;
import cn.bootx.platform.baseapi.param.protocol.UserProtocolParam;
import cn.bootx.platform.baseapi.param.protocol.UserProtocolQuery;
import cn.bootx.platform.baseapi.result.protocol.UserProtocolResult;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.core.exception.BizException;
import cn.bootx.platform.core.exception.DataNotExistException;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户协议管理服务
* @author xxm
* @since 2025/5/9
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserProtocolService {
private final UserProtocolManager userProtocolManager;
/**
* 分页
*/
public PageResult<UserProtocolResult> page(PageParam pageParam, UserProtocolQuery query){
return MpUtil.toPageResult(userProtocolManager.page(pageParam,query));
}
/**
* 添加
*/
public void add(UserProtocolParam param){
var userProtocol = UserProtocol.init(param);
userProtocolManager.save(userProtocol);
}
/**
* 更新
*/
public void update(UserProtocolParam param){
var userProtocol = userProtocolManager.findById(param.getId())
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
BeanUtil.copyProperties(param, userProtocol, CopyOptions.create().ignoreNullValue());
userProtocolManager.updateById(userProtocol);
}
/**
* 删除
*/
public void delete(Long id){
// 默认不可被删除
var userProtocol = userProtocolManager.findById(id)
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
if (userProtocol.getDefaultProtocol()){
throw new BizException("默认协议不可删除");
}
userProtocolManager.deleteById(id);
}
/**
* 根据ID查询
*/
public UserProtocolResult findById(Long id){
return userProtocolManager.findById(id)
.map(UserProtocol::toResult)
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
}
/**
* 根据分类查询默认协议
*/
public UserProtocolResult findDefault(String type){
return userProtocolManager.findDefault(type)
.map(UserProtocol::toResult)
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
}
/**
* 设置默认协议
*/
@Transactional(rollbackFor = Exception.class)
public void setDefault(Long id){
var userProtocol = userProtocolManager.findById(id)
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
userProtocolManager.clearDefault(userProtocol.getType());
userProtocolManager.setDefault(id);
}
/**
* 取消默认协议
*/
public void cancelDefault(Long id){
var userProtocol = userProtocolManager.findById(id)
.orElseThrow(() -> new DataNotExistException("用户协议不存在"));
userProtocolManager.cancelDefault(id);
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-service</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>service-iam</artifactId>

View File

@@ -33,8 +33,10 @@ import java.util.Objects;
@SuppressWarnings("FieldCanBeLocal")
public class PasswordLoginHandler implements AbstractAuthentication {
@Getter
private final String ACCOUNT_PARAMETER = "account";
@Getter
private final String PASSWORD_PARAMETER = "password";
@Resource

View File

@@ -70,7 +70,7 @@ public class UserAdminController {
@RequestPath("重置密码")
@Operation(summary = "重置密码")
@PostMapping("/restartPassword")
@OperateLog(title = "重置密码", businessType = OperateLog.BusinessType.UPDATE, saveParam = true)
@OperateLog(title = "重置密码", businessType = OperateLog.BusinessType.UPDATE)
public Result<Void> restartPassword(@RequestBody @Validated RestartPwdParam param) {
userAdminService.restartPassword(param.getUserId(), param.getNewPassword());
return Res.ok();
@@ -79,7 +79,7 @@ public class UserAdminController {
@RequestPath("批量重置密码")
@Operation(summary = "批量重置密码")
@PostMapping("/restartPasswordBatch")
@OperateLog(title = "批量重置密码", businessType = OperateLog.BusinessType.UPDATE, saveParam = true)
@OperateLog(title = "批量重置密码", businessType = OperateLog.BusinessType.UPDATE)
public Result<Void> restartPasswordBatch(@RequestBody @Validated RestartPwdBatchParam param) {
userAdminService.restartPasswordBatch(param.getUserIds(), param.getNewPassword());
return Res.ok();

View File

@@ -1,6 +1,7 @@
package cn.bootx.platform.iam.dao.user;
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
import cn.bootx.platform.common.mybatisplus.base.MpRealDelEntity;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.common.mybatisplus.query.generator.QueryGenerator;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
@@ -74,7 +75,11 @@ public class UserInfoManager extends BaseManager<UserInfoMapper, UserInfo> {
}
public void setUpStatus(Long userId, String status) {
lambdaUpdate().eq(MpIdEntity::getId, userId).set(UserInfo::getStatus, status).update();
lambdaUpdate()
.eq(MpIdEntity::getId, userId)
.set(UserInfo::getStatus, status)
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
/**
@@ -86,6 +91,7 @@ public class UserInfoManager extends BaseManager<UserInfoMapper, UserInfo> {
.set(UserInfo::getStatus, status)
.set(UserInfo::getLastModifiedTime, LocalDateTime.now())
.set(UserInfo::getLastModifier, SecurityUtil.getUserIdOrDefaultId())
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
@@ -98,6 +104,7 @@ public class UserInfoManager extends BaseManager<UserInfoMapper, UserInfo> {
.set(UserInfo::getPassword, password)
.set(UserInfo::getLastModifiedTime, LocalDateTime.now())
.set(UserInfo::getLastModifier, SecurityUtil.getUserIdOrDefaultId())
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}

View File

@@ -1,6 +1,6 @@
package cn.bootx.platform.iam.entity.permission;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
import cn.bootx.platform.common.mybatisplus.function.ToResult;
import cn.bootx.platform.iam.convert.permission.PermPathConvert;
import cn.bootx.platform.iam.result.permission.PermPathResult;
@@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
@Data
@Accessors(chain = true)
@TableName("iam_perm_path")
public class PermPath extends MpCreateEntity implements ToResult<PermPathResult> {
public class PermPath extends MpIdEntity implements ToResult<PermPathResult> {
/** 上级编码 */
private String parentCode;

View File

@@ -2,10 +2,8 @@ package cn.bootx.platform.iam.handler;
import cn.bootx.platform.core.entity.UserDetail;
import cn.bootx.platform.iam.code.UserStatusEnum;
import cn.bootx.platform.iam.service.user.UserAdminService;
import cn.bootx.platform.starter.auth.authentication.UserInfoStatusCheck;
import cn.bootx.platform.starter.auth.configuration.AuthProperties;
import cn.bootx.platform.starter.auth.entity.AuthClient;
import cn.bootx.platform.starter.auth.entity.AuthInfoResult;
import cn.bootx.platform.starter.auth.entity.LoginAuthContext;
import cn.bootx.platform.starter.auth.exception.LoginFailureException;
@@ -24,7 +22,6 @@ import java.util.Objects;
@Component
@RequiredArgsConstructor
public class UserInfoStatusCheckImpl implements UserInfoStatusCheck {
private final UserAdminService userAdminService;
/**
* 检查用户状态
@@ -34,7 +31,6 @@ public class UserInfoStatusCheckImpl implements UserInfoStatusCheck {
@Override
public void check(AuthInfoResult authInfoResult, LoginAuthContext context) {
UserDetail userDetail = authInfoResult.getUserDetail();
AuthClient authClient = context.getAuthClient();
AuthProperties authProperties = context.getAuthProperties();
// 判断是否开启了超级管理员
if (!authProperties.isEnableAdmin() && userDetail.isAdmin()) {

View File

@@ -64,12 +64,12 @@ public class PermPathSyncService {
List<String> clientCodes = bootxConfigProperties.getClientCodes();
// 查询数据中的数据并转换为请求信息列表
for (String clientCode : clientCodes) {
sync(clientCode);
this.sync(clientCode);
}
} else {
// 分模块模式同步
String clientCode = clientCodeService.getClientCode();
sync(clientCode);
this.sync(clientCode);
}
}
@@ -82,7 +82,7 @@ public class PermPathSyncService {
// 查询是否包含所有
.filter(o -> o.isAllClient() || CollUtil.contains(o.getClientCodes(),clientCode))
.toList();
List<PermPath> permPaths = permPathManager.findAllByLeafAndClient(true,clientCode);
List<PermPath> permPaths = permPathManager.findAllByLeafAndClient(true, clientCode);
var requestPathMap = requestPathBos.stream()
.collect(Collectors.toMap(o -> o.getPath() + ":" + o.getMethod(), Function.identity()));
var permPathMap = permPaths.stream()
@@ -95,8 +95,8 @@ public class PermPathSyncService {
// 需要更新的数据
List<PermPath> updateData = this.getUpdateData(requestPathMap, permPathMap);
// 保存新增的
addData.forEach(o -> o.setClientCode(clientCode));
// 保存新增的, ID 由不会变更的终端编码+请求方式+请求路径进行
addData.forEach(o -> o.setClientCode(clientCode).setId(this.genPathId(o.getClientCode()+o.getMethod()+o.getPath())));
permPathManager.saveAll(addData);
// 更新存在的
permPathManager.updateAllById(updateData);
@@ -161,8 +161,7 @@ public class PermPathSyncService {
.map(permPathMap::get)
.peek(o -> {
RequestPathBo requestPathBo = requestPathMap.get(o.getPath() + ":" + o.getMethod());
o.setName(requestPathBo.getName())
.setParentCode(requestPathBo.getGroupCode());
o.setName(requestPathBo.getName()).setParentCode(requestPathBo.getGroupCode());
}).toList();
}
@@ -227,7 +226,7 @@ public class PermPathSyncService {
.stream()
.filter(pathKey -> {
HandlerMethod handlerMethod = map.get(pathKey);
return Objects.nonNull(handlerMethod.getMethodAnnotation(cn.bootx.platform.core.annotation.RequestPath.class))
return Objects.nonNull(handlerMethod.getMethodAnnotation(RequestPath.class))
&&Objects.nonNull(handlerMethod.getBeanType().getAnnotation(RequestGroup.class));
}).toList();
@@ -303,7 +302,7 @@ public class PermPathSyncService {
}
/**
* 给分组模块生成ID, 防止每次更新ID都会发生变化
* 给分组/模块/请求路径资源生成ID, 防止每次更新ID都会发生变化
*/
private long genPathId(String str) {
String s = SecureUtil.sha256(str);

View File

@@ -148,7 +148,7 @@ public class RoleQueryService {
// 获取关联的角色和子角色
List<RoleResult> unfold = TreeBuildUtil.unfold(tree, RoleResult::getChildren).stream()
.filter(role -> roleIds.contains(role.getId()))
.collect(Collectors.toList());;
.collect(Collectors.toList());
var list = new ArrayList<>(unfold);
// 将子孙级别的角色移除, 只保留根角色
for (var out : unfold) {

View File

@@ -152,7 +152,7 @@ public class RoleCodeService {
// 权限码列表
List<PermCode> permCodes = allPermCodes.stream()
.filter(PermCode::isLeaf)
.toList();;
.toList();
// 如果有有上级角色, 显示上级角色已分配的权限
if (Objects.nonNull(role.getPid())){
List<Long> codeIds = roleCodeManager.findAllByRole(role.getPid())

View File

@@ -47,7 +47,6 @@ public class UserAdminService {
private final AuthProperties authProperties;
/**
* 分页查询
*/
@@ -143,8 +142,7 @@ public class UserAdminService {
UserInfo userInfo = userInfoManager.findById(userId).orElseThrow(UserInfoNotExistsException::new);
// 新密码进行加密
newPassword = BCrypt.hashpw(newPassword);
userInfo.setPassword(newPassword);
userInfo.setPassword(BCrypt.hashpw(newPassword));
userInfoManager.updateById(userInfo);
}
@@ -154,8 +152,7 @@ public class UserAdminService {
@Transactional(rollbackFor = Exception.class)
public void restartPasswordBatch(List<Long> userIds, String newPassword){
// 新密码进行加密
String password = BCrypt.hashpw(newPassword);
userInfoManager.restartPasswordBatch(userIds,password);
userInfoManager.restartPasswordBatch(userIds,BCrypt.hashpw(newPassword));
}
/**

View File

@@ -98,13 +98,16 @@ public class UserInfoService {
public void updatePassword(String password, String newPassword) {
UserInfo userInfo = userInfoManager.findById(SecurityUtil.getUserId())
.orElseThrow(UserInfoNotExistsException::new);
UserInfo update=new UserInfo();
update.setId(userInfo.getId());
update.setVersion(userInfo.getVersion());
// 判断原密码是否正确
if (!BCrypt.checkpw(password, userInfo.getPassword())) {
throw new BizException("旧密码错误");
}
userInfo.setPassword(newPassword);
userInfoManager.updateById(userInfo);
newPassword=BCrypt.hashpw(newPassword,BCrypt.gensalt());
update.setPassword(newPassword);
userInfoManager.updateById(update);
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<modules>
<module>starter-auth</module>
@@ -27,7 +27,7 @@
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-core</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</dependency>
<!-- lombok -->
<dependency>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-starter</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>starter-audit-log</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-starter</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>starter-auth</artifactId>

View File

@@ -5,6 +5,7 @@ import cn.bootx.platform.starter.auth.entity.LoginAuthContext;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Objects;
/**
@@ -19,11 +20,12 @@ public interface AbstractAuthentication {
* 获取终端编码
*/
String getLoginType();
/**
* 获取用户状态检查接口的实现类
*/
default UserInfoStatusCheck getUserInfoStatusCheck() {
return SpringUtil.getBean(UserInfoStatusCheck.class);
default List<UserInfoStatusCheck> getUserInfoStatusCheck() {
return SpringUtil.getBeansOfType(UserInfoStatusCheck.class).values().stream().toList();
}
/**
@@ -62,7 +64,9 @@ public interface AbstractAuthentication {
// 添加用户信息到上下文中
context.setUserDetail(authInfoResult.getUserDetail());
// 检查用户信息和状态
this.getUserInfoStatusCheck().check(authInfoResult, context);
for (var userInfoStatusCheck : this.getUserInfoStatusCheck()) {
userInfoStatusCheck.check(authInfoResult, context);
}
// 认证后处理
this.authenticationAfter(authInfoResult, context);
return authInfoResult;

View File

@@ -11,7 +11,7 @@ import cn.bootx.platform.starter.auth.entity.LoginAuthContext;
public interface UserInfoStatusCheck {
/**
*
* 检查用户状态
* @param authInfoResult 认证返回结果
* @param context 登录认证上下文
*/

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-starter</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>starter-cache</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-starter</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>starter-delay-queue</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-starter</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>starter-file</artifactId>
@@ -25,11 +25,10 @@
<version>${x-file-storage.version}</version>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>${qcloud.version}</version>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>${aws-s3.version}</version>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>cn.bootx.platform</groupId>

View File

@@ -1,8 +1,12 @@
package cn.bootx.platform.starter.file.code;
import cn.bootx.platform.core.exception.BizException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.Objects;
/**
* 存储平台类型
*
@@ -12,22 +16,32 @@ import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum FilePlatformTypeEnum {
LOCAL("local"),
FTP("ftp"),
SFTP("sftp"),
WEB_DAV("web_dav"),
AMAZON("amazon"),
MINIO("minio"),
ALI("ali"),
HUAWEI("huawei"),
TENCENT("tencent"),
BAIDU("baidu"),
UPYUN("upyun"),
QINIU("qiniu"),
GOOGLE_CLOUD("google_cloud"),
FAST_DFS("fast_dfs"),
AZURE("azure");
LOCAL("local",false),
FTP("ftp",false),
SFTP("sftp",false),
WEB_DAV("web_dav",false),
// S3 存储, 现在系统只支持这一种方式
AMAZON_S3("amazon-s3",true),
MINIO("minio",true),
ALI("ali",true),
HUAWEI("huawei",true),
TENCENT("tencent",true),
BAIDU("baidu",true),
UPYUN("upyun",true),
QINIU("qiniu",true),
GOOGLE_CLOUD("google_cloud",true),
FAST_DFS("fast_dfs",true),
AZURE("azure",true);
private final String code;
/** 前端直传 */
private final boolean frontendUpload;
public static FilePlatformTypeEnum findByCode(String code){
return Arrays.stream(values())
.filter(e -> Objects.equals(e.code, code))
.findFirst()
.orElseThrow(() -> new BizException("不支持的类型"));
}
}

View File

@@ -1,30 +0,0 @@
package cn.bootx.platform.starter.file.configuration;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 文件上传配置
*
* @author xxm
* @since 2022/1/14
*/
@Data
@Accessors(chain = true)
@ConfigurationProperties(prefix = "bootx-platform.starter.file-upload")
public class FileUploadProperties {
/**
* 文件访问转发地址(当前后端服务地址或被代理后的地址), 流量会经过后端服务的转发
*/
private String forwardServerUrl = "http://127.0.0.1:9999";
/**
* 处理为 / 结尾
*/
public String getForwardServerUrl() {
return StrUtil.removeSuffix(forwardServerUrl, "/");
}
}

View File

@@ -1,19 +0,0 @@
package cn.bootx.platform.starter.file.configuration;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@Accessors(chain = true)
@ConfigurationProperties(prefix = "bootx-platform.starter.oss")
/***
* oss配置
*/
public class OssProperties {
private String filePath;
}

View File

@@ -8,20 +8,30 @@ import cn.bootx.platform.core.rest.Res;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.core.rest.result.Result;
import cn.bootx.platform.starter.file.param.FileUploadRequestParams;
import cn.bootx.platform.starter.file.param.UploadFileInfoParam;
import cn.bootx.platform.starter.file.param.UploadFileQuery;
import cn.bootx.platform.starter.file.result.FileUploadParamsResult;
import cn.bootx.platform.starter.file.result.UploadFileResult;
import cn.bootx.platform.starter.file.service.FileUploadService;
import org.dromara.core.trans.anno.TransMethodResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.core.trans.anno.TransMethodResult;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 文件上传
@@ -52,14 +62,14 @@ public class FIleUpLoadController {
@Operation(summary = "获取单条详情")
@GetMapping("/findById")
public Result<UploadFileResult> findById(@NotNull(message = "主键不可为空") Long id) {
return Res.ok(uploadService.findById(id));
return Res.ok(uploadService.findByUrl(id));
}
@IgnoreAuth
@Operation(summary = "根据URL获取单条详情")
@GetMapping("/findByUrl")
public Result<UploadFileResult> findById(@NotBlank(message = "文件URL不可为空") String url) {
return Res.ok(uploadService.findById(url));
return Res.ok(uploadService.findByUrl(url));
}
@Operation(summary = "删除")
@@ -71,31 +81,37 @@ public class FIleUpLoadController {
return Res.ok();
}
@IgnoreAuth
@Operation(summary = "上传")
@PostMapping("/upload")
public Result<UploadFileResult> local(@RequestPart MultipartFile file, String fileName) {
return Res.ok(uploadService.upload(file, fileName));
@IgnoreAuth(login = true)
@Operation(summary = "下载文件")
@GetMapping("/downloadByServer")
public ResponseEntity<byte[]> downloadByServer(String attachName) {
var bytes = uploadService.downloadAndCheck(attachName);
// 设置header信息
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", URLEncoder.encode(attachName, StandardCharsets.UTF_8));
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
}
@IgnoreAuth(login = true)
@Operation(summary = "获取前端直传参数")
@PostMapping("/getUploadParams")
public Result<FileUploadParamsResult> getUploadParams(@RequestBody @Valid FileUploadRequestParams params) {
return Res.ok(uploadService.getUploadParams(params));
}
@IgnoreAuth(login = true)
@Operation(summary = "前端直传文件信息保存")
@PostMapping("/saveFileInfo")
public Result<Void> saveFileInfo(@RequestBody @Valid UploadFileInfoParam param) {
uploadService.saveFileInfo(param);
return Res.ok();
}
@IgnoreAuth
@Operation(summary = "获取文件预览地址前缀(流量会经过后端)")
@GetMapping("/forward/getFilePreviewUrlPrefix")
public Result<String> getFilePreviewUrlPrefix() {
return Res.ok(uploadService.getServerFilePreviewUrlPrefix());
}
@IgnoreAuth
@Operation(summary = "预览文件(流量会经过后端)")
@GetMapping("/forward/preview/{id}")
public void preview(@PathVariable Long id, HttpServletResponse response) {
uploadService.preview(id, response);
}
@IgnoreAuth
@Operation(summary = "下载文件(流量会经过后端)")
@GetMapping("/forward/download/{id}")
public ResponseEntity<byte[]> download(@PathVariable Long id) {
return uploadService.download(id);
@Operation(summary = "前端直传文件预览/下载(不需要登录)")
@GetMapping("/download/{attachName}")
public void download(HttpServletResponse httpServletResponse, @Schema(description = "附件名") @PathVariable("attachName") String attachName) {
uploadService.ossDownload(httpServletResponse, attachName);
}
}

View File

@@ -13,6 +13,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -24,6 +25,7 @@ import java.util.List;
* @since 2024/8/12
*/
@Validated
@Deprecated
@RequestGroup(groupCode = "FilePlatform", groupName = "文件存储平台管理", moduleCode = "starter")
@Tag(name = "文件存储平台")
@RestController
@@ -31,6 +33,7 @@ import java.util.List;
@RequiredArgsConstructor
public class FilePlatformController {
private final FilePlatformService filePlatformService;
private final FileStorageService fileStorageService;
@IgnoreAuth
@Operation(summary = "列表")
@@ -63,4 +66,12 @@ public class FilePlatformController {
filePlatformService.setDefault(id);
return Res.ok();
}
@IgnoreAuth
@Operation(summary = "获取当前默认的文件上传存储平台")
@GetMapping("/getDefaultUpload")
public Result<String> getDefault(){
String defaultPlatform = fileStorageService.getProperties().getDefaultPlatform();
return Res.ok(defaultPlatform);
}
}

View File

@@ -1,36 +0,0 @@
package cn.bootx.platform.starter.file.controller;
import cn.bootx.platform.starter.file.param.FileUploadRequestParams;
import cn.bootx.platform.starter.file.result.FileUploadParamsResult;
import cn.bootx.platform.starter.file.service.OssService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@Tag(name = "对象存储")
@RequestMapping("/oss")
@AllArgsConstructor
public class OssController {
private final OssService ossService;
@Operation(summary = "获取上传参数")
@PostMapping("/getUploadParams")
public FileUploadParamsResult getUploadParams(@RequestBody @Valid FileUploadRequestParams params) {
return ossService.getUploadParams(params);
}
@Operation(summary = "文件预览/文件下载")
@GetMapping("/download/{attachName}")
public void download(HttpServletResponse httpServletResponse, @Schema(description = "附件名") @PathVariable("attachName") String attachName) {
ossService.download(httpServletResponse, attachName);
}
}

View File

@@ -1,6 +1,7 @@
package cn.bootx.platform.starter.file.convert;
import cn.bootx.platform.starter.file.entity.UploadFileInfo;
import cn.bootx.platform.starter.file.param.UploadFileInfoParam;
import cn.bootx.platform.starter.file.result.UploadFileResult;
import org.dromara.x.file.storage.core.FileInfo;
import org.mapstruct.Mapper;
@@ -19,6 +20,8 @@ public interface FileConvert {
UploadFileInfo convert(FileInfo in);
UploadFileInfo convert(UploadFileInfoParam in);
FileInfo toFileInfo(UploadFileInfo in);
UploadFileResult toResult(FileInfo in);

View File

@@ -6,13 +6,23 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
*
* 支付平台配置
* @author xxm
* @since 2024/8/12
*/
@Slf4j
@Deprecated
@Repository
@RequiredArgsConstructor
public class FilePlatformManager extends BaseManager<FilePlatformMapper, FilePlatform> {
/**
* 根据平台类型查询
*/
public Optional<FilePlatform> findByType(String platform) {
return findByField(FilePlatform::getType, platform);
}
}

View File

@@ -14,6 +14,8 @@ import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import lombok.experimental.FieldNameConstants;
import java.util.Objects;
/**
* 文件存储平台
* @author xxm
@@ -41,12 +43,20 @@ public class FilePlatform extends MpRealDelEntity implements ToResult<FilePlatfo
private boolean defaultPlatform;
/** 访问地址 */
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String url;
/** 前端直传 */
private Boolean frontendUpload;
public String getUrl() {
return StrUtil.removeSuffix(url, "/");
}
public Boolean getFrontendUpload() {
return Objects.equals(frontendUpload,true);
}
/**
* 转换
*/

View File

@@ -27,7 +27,7 @@ import java.time.LocalDateTime;
public class UploadFileInfo extends MpIdEntity implements ToResult<UploadFileResult> {
/**
* 文件访问地址
* 文件访问地址(存储在S3中的唯一名称)
*/
private String url;

View File

@@ -8,8 +8,6 @@ import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.recorder.DefaultFileRecorder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/**
* x.file.storage 文件上传信息储存
* @author xxm
@@ -27,7 +25,6 @@ public class FileDetailRecordHandler extends DefaultFileRecorder {
@Override
public boolean save(FileInfo fileInfo) {
UploadFileInfo uploadFileInfo = UploadFileInfo.init(fileInfo);
uploadFileInfo.setCreateTime(LocalDateTime.now());
uploadFileManager.save(uploadFileInfo);
fileInfo.setId(String.valueOf(uploadFileInfo.getId()));
return true;

View File

@@ -20,8 +20,11 @@ public class FilePlatformParam {
@Schema(description = "主键")
private Long id;
@NotBlank(message = "平台地址不得为空")
@Schema(description = "平台地址")
private String url;
/** 前端直传 */
@Schema(description = "前端直传")
private Boolean frontendUpload;
}

View File

@@ -14,7 +14,7 @@ public class FileUploadRequestParams {
private String fileName;
@Schema(description = "媒体类型 MEDIA_TYPE")
@NotBlank(message = "媒体类型不可为")
@NotBlank(message = "媒体类型不可为为空")
private String mediaType;
@Schema(description = "文件大小")

View File

@@ -0,0 +1,106 @@
package cn.bootx.platform.starter.file.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 上传文件信息
* @author xxm
* @since 2025/5/15
*/
@Data
@Accessors(chain = true)
@Schema(title = "上传文件信息")
public class UploadFileInfoParam {
/**
* 文件访问地址(名称)
*/
@Schema(description = "文件访问地址")
private String url;
/**
* 文件大小,单位字节
*/
@Schema(description = "文件大小,单位字节")
private Long size;
/**
* 文件名称
*/
@Schema(description = "文件名称")
private String filename;
/**
* 原始文件名
*/
@Schema(description = "原始文件名")
private String originalFilename;
/**
* 基础存储路径
*/
@Schema(description = "基础存储路径")
private String basePath;
/**
* 存储路径
*/
@Schema(description = "存储路径")
private String path;
/**
* 文件扩展名
*/
@Schema(description = "文件扩展名")
private String ext;
/**
* MIME 类型
*/
@Schema(description = "MIME 类型")
private String contentType;
/**
* 存储平台
*/
@Schema(description = "存储平台")
private String platform;
/**
* 缩略图访问路径
*/
@Schema(description = "缩略图访问路径")
private String thUrl;
/**
* 缩略图名称
*/
@Schema(description = "缩略图名称")
private String thFilename;
/**
* 缩略图大小,单位字节
*/
@Schema(description = "缩略图大小,单位字节")
private Long thSize;
/**
* 缩略图 MIME 类型
*/
@Schema(description = "缩略图 MIME 类型")
private String thContentType;
/**
* 文件所属对象id
*/
@Schema(description = "文件所属对象id")
private String objectId;
/**
* 文件所属对象类型,例如用户头像,评价图片
*/
@Schema(description = "文件所属对象类型,例如用户头像,评价图片")
private String objectType;
}

View File

@@ -36,4 +36,9 @@ public class FilePlatformResult extends BaseResult {
/** 访问地址 */
@Schema(description = "访问地址")
private String url;
/** 前端直传 */
@Schema(description = "前端直传")
private Boolean frontendUpload;
}

View File

@@ -2,16 +2,24 @@ package cn.bootx.platform.starter.file.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Map;
/**
* 文件上传参数
*/
@Schema(description = "文件上传参数")
@Data
@Accessors(chain = true)
public class FileUploadParamsResult {
@Schema(description = "文件上传地址")
private String url;
@Schema(description = "上传后文件名称")
private String attachName;
@Schema(description = "文件上传请求头,上传时放在请求头里")
private Map<String,String> headers;

View File

@@ -22,12 +22,12 @@ import java.util.List;
* @since 2024/8/12
*/
@Slf4j
@Deprecated
@Service
@RequiredArgsConstructor
public class FilePlatformService {
private final FilePlatformManager filePlatformManager;
/**
* 获取全部存储平台
*/
@@ -62,12 +62,14 @@ public class FilePlatformService {
.eq(FilePlatform::isDefaultPlatform, true)
.set(FilePlatform::getLastModifiedTime, LocalDateTime.now())
.set(MpRealDelEntity::getLastModifier, SecurityUtil.getUserIdOrDefaultId())
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
filePlatformManager.lambdaUpdate()
.eq(FilePlatform::getId, id)
.set(FilePlatform::getLastModifiedTime, LocalDateTime.now())
.set(MpRealDelEntity::getLastModifier, SecurityUtil.getUserIdOrDefaultId())
.set(FilePlatform::isDefaultPlatform, true)
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
}

View File

@@ -1,39 +1,37 @@
package cn.bootx.platform.starter.file.service;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.core.exception.BizException;
import cn.bootx.platform.core.exception.DataNotExistException;
import cn.bootx.platform.core.rest.param.PageParam;
import cn.bootx.platform.core.rest.result.PageResult;
import cn.bootx.platform.starter.file.configuration.FileUploadProperties;
import cn.bootx.platform.starter.file.convert.FileConvert;
import cn.bootx.platform.starter.file.dao.UploadFileManager;
import cn.bootx.platform.starter.file.entity.UploadFileInfo;
import cn.bootx.platform.starter.file.param.FileUploadRequestParams;
import cn.bootx.platform.starter.file.param.UploadFileInfoParam;
import cn.bootx.platform.starter.file.param.UploadFileQuery;
import cn.bootx.platform.starter.file.result.FileUploadParamsResult;
import cn.bootx.platform.starter.file.result.UploadFileResult;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.servlet.ServletOutputStream;
import cn.hutool.http.HttpUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.upload.UploadPretreatment;
import org.springframework.http.HttpHeaders;
import org.dromara.x.file.storage.core.constant.Constant;
import org.dromara.x.file.storage.core.presigned.GeneratePresignedUrlResult;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.io.IOException;
import java.util.Date;
/**
* 文件上传管理类
@@ -47,108 +45,134 @@ import java.time.LocalDateTime;
public class FileUploadService {
private final UploadFileManager uploadFileManager;
private final FileStorageService fileStorageService;
private final FileUploadProperties fileUploadProperties;
/**
* 分页
*/
public PageResult<UploadFileResult> page(PageParam pageParam, UploadFileQuery param) {
return MpUtil.toPageResult(uploadFileManager.page(pageParam,param));
return MpUtil.toPageResult(uploadFileManager.page(pageParam, param));
}
/**
* 获取单条详情
*/
public UploadFileResult findById(Long id){
public UploadFileResult findByUrl(Long id) {
return uploadFileManager.findById(id)
.map(UploadFileInfo::toResult)
.orElseThrow(DataNotExistException::new);
.orElseThrow(DataNotExistException::new);
}
/**
* 根据URL获取单条详情
*/
public UploadFileResult findById(String url){
public UploadFileResult findByUrl(String url) {
return uploadFileManager.findByUrl(url)
.map(UploadFileInfo::toResult)
.orElseThrow(DataNotExistException::new);
.orElseThrow(DataNotExistException::new);
}
/**
* 文件删除
*/
@Transactional(rollbackFor = Exception.class)
public void delete(Long id){
public void delete(Long id) {
UploadFileInfo uploadFileInfo = uploadFileManager.findById(id)
.orElseThrow(DataNotExistException::new);
fileStorageService.delete(FileConvert.CONVERT.toFileInfo(uploadFileInfo));
}
/**
* 文件上传
* @param file 文件
* @param fileName 文件名称
* 根据文件名称下载
*/
@Transactional(rollbackFor = Exception.class)
public UploadFileResult upload(@RequestPart MultipartFile file, String fileName) {
UploadPretreatment uploadPretreatment = fileStorageService.of(file);
if (StrUtil.isNotBlank(fileName)){
uploadPretreatment.setOriginalFilename(fileName);
public byte[] download(String attachName) {
String ossFileUrl = this.getOssFileUrl(attachName);
return HttpUtil.downloadBytes(ossFileUrl);
}
/**
* 根据存储的文件对象下载
*/
public byte[] download(FileInfo fileInfo) {
return HttpUtil.downloadBytes(this.getOssFileUrl(fileInfo.getUrl()));
}
/**
* 文件下载, 首先判断文件是否存在, 用在严格的场景中
*/
public byte[] downloadAndCheck(String attachName) {
FileInfo fileInfo = fileStorageService.getFileInfoByUrl(attachName);
if (fileInfo == null){
throw new DataNotExistException("文件不存在");
}
// 按年月日进行分目录
uploadPretreatment.setPath(LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy/MM/dd/"));
FileInfo upload = uploadPretreatment.upload();
return FileConvert.CONVERT.toResult(upload);
return HttpUtil.downloadBytes(this.getOssFileUrl(attachName));
}
/**
* 文件预览
* TODO url需要使用URl
* 获取直传上传参数
*/
@SneakyThrows
public void preview(Long id, HttpServletResponse response) {
FileInfo info = fileStorageService.getFileInfoByUrl(String.valueOf(id));
if (info == null){
log.warn("文件不存在");
return;
public FileUploadParamsResult getUploadParams(FileUploadRequestParams params) {
String fileExtension = FileUtil.extName(params.getFileName());
String attachName = IdUtil.fastSimpleUUID() + (StringUtils.isNotBlank(fileExtension) ? "." + fileExtension : "");
GeneratePresignedUrlResult uploadResult = fileStorageService
.generatePresignedUrl()
.setFilename(attachName) // 设置保存的文件名
.setMethod(Constant.GeneratePresignedUrl.Method.PUT) // 签名方法
.setExpiration(DateUtil.offsetMinute(new Date(), 10)) // 过期时间 10 分钟
.putHeaders(Constant.Metadata.CONTENT_TYPE, params.getMediaType())
.putHeaders(Constant.Metadata.CONTENT_LENGTH, String.valueOf(params.getFileSize()))
.generatePresignedUrl();
FileUploadParamsResult result = new FileUploadParamsResult();
result.setUrl(uploadResult.getUrl())
.setAttachName(attachName)
.setHeaders(uploadResult.getHeaders());
return result;
}
/**
* 保存前端直传的文件信息
*/
public void saveFileInfo(UploadFileInfoParam param) {
UploadFileInfo uploadFileInfo = FileConvert.CONVERT.convert(param);
// 扩展名
String fileExtension = FileUtil.extName(uploadFileInfo.getOriginalFilename());
uploadFileInfo.setExt(fileExtension);
// 平台
String defaultPlatform = fileStorageService.getProperties()
.getDefaultPlatform();
uploadFileInfo.setPlatform(defaultPlatform);
uploadFileManager.save(uploadFileInfo);
}
/**
* 获取oss直传文件下载链接
*/
public String getOssFileUrl(String attachName) {
// 系统中旧数据存储的地址是/开头, 为了兼容旧数据使用oss生成预签名链接的时候需要去掉
// 新数据存储格式就没有/开头
attachName = StrUtil.removePrefix(attachName, "/");
GeneratePresignedUrlResult downloadResult = fileStorageService
.generatePresignedUrl()
.setFilename(attachName) // 文件名
.setMethod(Constant.GeneratePresignedUrl.Method.GET) // 签名方法
.setExpiration(DateUtil.offsetMinute(new Date(), 10)) // 过期时间 10 分钟
.putResponseHeaders(
Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=" + attachName)
.generatePresignedUrl();
return downloadResult.getUrl();
}
/**
* 前端直传文件下载/预览
*/
public void ossDownload(HttpServletResponse httpServletResponse, String attachName) {
try {
httpServletResponse.sendRedirect(this.getOssFileUrl(attachName));
} catch (IOException e) {
log.error("下载文件失败", e);
httpServletResponse.setStatus(HttpStatus.NOT_FOUND.value());
throw new BizException(e.getMessage());
}
byte[] bytes = fileStorageService.download(info).bytes();
var is = new ByteArrayInputStream(bytes);
// 获取响应输出流
ServletOutputStream os = response.getOutputStream();
IoUtil.copy(is, os);
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, info.getContentType());
IoUtil.close(is);
IoUtil.close(os);
}
/**
* 文件下载
*/
@SneakyThrows
public ResponseEntity<byte[]> download(Long id) {
FileInfo fileInfo = fileStorageService.getFileInfoByUrl(String.valueOf(id));
byte[] bytes = fileStorageService.download(fileInfo).bytes();
// 设置header信息
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
String fileName = fileInfo.getOriginalFilename();
headers.setContentDispositionFormData("attachment", URLEncoder.encode(fileName, StandardCharsets.UTF_8));
return new ResponseEntity<>(bytes,headers,HttpStatus.OK);
}
/**
* 文件访问转发地址(当前后端服务地址或被代理后的地址), 流量会经过后端服务的转发
*/
public String getServerFilePreviewUrlPrefix() {
return this.getForwardServerUrl() + "/file/preview/";
}
/**
* 文件访问转发地址(当前后端服务地址或被代理后的地址), 流量会经过后端服务的转发
*/
private String getForwardServerUrl() {
return fileUploadProperties.getForwardServerUrl();
}
}

View File

@@ -1,88 +0,0 @@
package cn.bootx.platform.starter.file.service;
import cn.bootx.platform.starter.file.configuration.OssProperties;
import cn.bootx.platform.starter.file.param.FileUploadRequestParams;
import cn.bootx.platform.starter.file.result.FileUploadParamsResult;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.constant.Constant;
import org.dromara.x.file.storage.core.presigned.GeneratePresignedUrlResult;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Date;
/**
* @author yinxucun
*/
@Service
@Slf4j
public class OssService {
private final OssProperties ossProperties;
private final FileStorageService fileStorageService;
private static final Instant expirationTime = new Date().toInstant().atZone(ZoneId.of("Asia/Shanghai"))
.plusMinutes(10).toInstant();
public OssService(OssProperties ossProperties, FileStorageService fileStorageService) {
this.ossProperties = ossProperties;
this.fileStorageService = fileStorageService;
log.info("初始化OSS配置:{}", ossProperties);
}
public FileUploadParamsResult getUploadParams(FileUploadRequestParams params) {
return this.createSignedUrlForStringPut(params);
}
public void download(HttpServletResponse httpServletResponse, String attachName) {
GeneratePresignedUrlResult downloadResult = fileStorageService
.generatePresignedUrl()
.setPath(ossProperties.getFilePath()) // 文件路径
.setFilename(attachName) // 文件名,也可以换成缩略图的文件名
.setMethod(Constant.GeneratePresignedUrl.Method.GET) // 签名方法
.setExpiration(Date.from(expirationTime)) // 过期时间 10 分钟
.putResponseHeaders(
Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=" + attachName)
.generatePresignedUrl();
try {
httpServletResponse.sendRedirect(downloadResult.getUrl());
} catch (IOException e) {
log.error("下载文件失败", e);
httpServletResponse.setStatus(HttpStatus.NOT_FOUND.value());
throw new RuntimeException(e);
}
}
private FileUploadParamsResult createSignedUrlForStringPut(FileUploadRequestParams params) {
String fileExtension = FileUtil.extName(params.getFileName());
String attachName = IdUtil.fastSimpleUUID() + (StringUtils.isNotBlank(fileExtension) ? "." + fileExtension : "");
Date expirationTime = new Date(System.currentTimeMillis() + 600 * 1000);
GeneratePresignedUrlResult uploadResult = fileStorageService
.generatePresignedUrl()
.setPath(ossProperties.getFilePath()) // 设置路径
.setFilename(attachName) // 设置保存的文件名
.setMethod(Constant.GeneratePresignedUrl.Method.PUT) // 签名方法
.setExpiration(expirationTime) // 设置过期时间 10 分钟
.putHeaders(Constant.Metadata.CONTENT_TYPE, params.getMediaType())
.putHeaders(Constant.Metadata.CONTENT_LENGTH, String.valueOf(params.getFileSize()))
.generatePresignedUrl();
log.info("expirationTime:{},attachName:{}", DateUtil.format(expirationTime, "yyyy-MM-dd HH:mm:ss"), attachName);
log.info("生成上传预签名 URL 结果:{}", uploadResult);
FileUploadParamsResult result = new FileUploadParamsResult();
result.setUrl(uploadResult.getUrl());
result.setHeaders(uploadResult.getHeaders());
return result;
}
}

View File

@@ -0,0 +1,71 @@
package org.dromara.x.file.storage.core.platform;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.dromara.x.file.storage.core.FileStorageProperties.AmazonS3Config;
import java.util.Map;
/**
* 重写 Amazon S3 存储平台的 Client 工厂, 支持 withPathStyleAccessEnabled 配置
*/
@Getter
@Setter
@NoArgsConstructor
public class AmazonS3FileStorageClientFactory implements FileStorageClientFactory<AmazonS3> {
private String platform;
private String accessKey;
private String secretKey;
private String region;
private String endPoint;
private Map<String, Object> attr;
private volatile AmazonS3 client;
public AmazonS3FileStorageClientFactory(AmazonS3Config config) {
platform = config.getPlatform();
accessKey = config.getAccessKey();
secretKey = config.getSecretKey();
region = config.getRegion();
endPoint = config.getEndPoint();
attr = config.getAttr();
}
@Override
public AmazonS3 getClient() {
if (client == null) {
synchronized (this) {
if (client == null) {
AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard()
.withCredentials(
new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)));
if (StrUtil.isNotBlank(endPoint)) {
builder.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, region));
} else if (StrUtil.isNotBlank(region)) {
builder.withRegion(region);
}
// 使用路径访问样式
Boolean forcePathStyle = MapUtil.getBool(attr, "forcePathStyle", false);
builder.withPathStyleAccessEnabled(forcePathStyle);
client = builder.build();
}
}
}
return client;
}
@Override
public void close() {
if (client != null) {
client.shutdown();
client = null;
}
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform-starter</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
</parent>
<artifactId>starter-quartz</artifactId>

View File

@@ -7,13 +7,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<version>3.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>bootx-platform</artifactId>
<version>3.0.0.beta5</version>
<version>3.0.0</version>
<packaging>pom</packaging>
<description>基础脚手架服务</description>
@@ -25,18 +25,18 @@
</modules>
<properties>
<bootx-platform.version>3.0.0.beta5</bootx-platform.version>
<bootx-platform.version>3.0.0</bootx-platform.version>
<!-- 再高的的新版本与会knife4j 4.5冲突, 目前使用三方的knife4j依赖 -->
<springdoc.version>2.7.0</springdoc.version>
<hutool.version>5.8.31</hutool.version>
<hutool.version>5.8.39</hutool.version>
<bouncycastle.version>1.78.1</bouncycastle.version>
<knife4j.version>4.6.0</knife4j.version>
<mybatis-plus.version>3.5.9</mybatis-plus.version>
<mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
<mybatis-plus.version>3.5.12</mybatis-plus.version>
<mybatis-plus-join.version>1.5.4</mybatis-plus-join.version>
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
<lock4j.version>2.2.7</lock4j.version>
<x-file-storage.version>2.2.1</x-file-storage.version>
<minio.version>8.5.4</minio.version>
<x-file-storage.version>2.3.0</x-file-storage.version>
<aws-s3.version>1.12.429</aws-s3.version>
<qcloud.version>5.6.137</qcloud.version>
<sa-token.version>1.39.0</sa-token.version>
<justauth.version>1.16.6</justauth.version>