This commit is contained in:
2023-06-25 17:22:05 +08:00
parent 3fd3a09888
commit caeae9127f
51 changed files with 235 additions and 632 deletions

View File

@ -16,6 +16,8 @@
<module>qiaoba-auth</module>
</modules>
<description>qiaoba-boot 多租户管理系统</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
@ -39,7 +41,7 @@
<druid.version>1.2.17</druid.version>
<!-- HuTool工具包 -->
<hutool.version>5.8.18</hutool.version>
<!-- lock4j -->
<!-- Lock4j -->
<lock4j.version>2.2.5</lock4j.version>
<!-- mysql驱动 -->
<mysql-connector.version>8.0.33</mysql-connector.version>
@ -75,9 +77,10 @@
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- Lock4j -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<version>${lock4j.version}</version>
</dependency>
<!-- easy poi -->

View File

@ -11,6 +11,9 @@
<artifactId>qiaoba-apis</artifactId>
<packaging>pom</packaging>
<description>api模块, 用于抽取共用部分, 供其他模块调用</description>
<modules>
<module>qiaoba-api-system</module>
<module>qiaoba-api-job</module>

View File

@ -37,7 +37,7 @@ public interface SysMenuApiService {
* @param param 条件
* @return 菜单列表
*/
List<SysMenuVo> selectVoList(SysMenuParam param);
List<SysMenu> selectList(SysMenuParam param);
/**
* 查询详细

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-application</artifactId>
<description>总打包模块, 需要打包的模块, 引入到该模块中即可</description>
<dependencies>
<dependency>
<groupId>com.qiaoba</groupId>

View File

@ -37,4 +37,4 @@ mybatis-plus:
logging:
level:
com.qiaoba: debug #开发环境输出sql日志
com.qiaoba: trace #开发环境输出sql日志

View File

@ -28,7 +28,7 @@ spring:
# 数据库索引
database: 0
# 密码
password:
#password:
# 连接超时时间
timeout: 10s
lettuce:

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-auth</artifactId>
<description>鉴权模块</description>
<dependencies>
<dependency>
<groupId>com.qiaoba</groupId>

View File

@ -10,8 +10,7 @@ import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
public @interface DataScope {
/**
* 部门表的别名
*/

View File

@ -11,7 +11,6 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
/**
* 退出处理器

View File

@ -24,8 +24,8 @@ public interface OnlineUserService {
/**
* 删除(强退)
*
* @param username 登录账号
* @param deviceSn 设备号
* @param username 登录账号
* @param deviceSn 设备号
* @param deleteOwn 是否是删除自己
*/
void deleteOne(String username, String deviceSn, Boolean deleteOwn);
@ -66,7 +66,7 @@ public interface OnlineUserService {
/**
* 分页查询列表
*
* @param pageNum pageNum
* @param pageNum pageNum
* @param pageSize pageSize
* @param username username
* @return list

View File

@ -20,5 +20,6 @@
<module>qiaoba-common-poi</module>
</modules>
<description>qiaoba-common: 新建的common统一放在此模块下面</description>
</project>

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-common-base</artifactId>
<description>通用-基础模块</description>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-common-datasource</artifactId>
<description>通用-数据源模块</description>
<dependencies>
<!-- Druid -->
<dependency>
@ -38,7 +40,7 @@
</dependency>
<dependency>
<groupId>com.qiaoba</groupId>
<artifactId>qiaoba-common-web</artifactId>
<artifactId>qiaoba-common-redis</artifactId>
</dependency>
</dependencies>

View File

@ -1,16 +1,11 @@
package com.qiaoba.common.database.interceptors;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.qiaoba.common.base.context.BaseContext;
import com.qiaoba.common.database.handlers.schema.SchemaHandlerFactory;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.springframework.beans.factory.annotation.Value;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Objects;
/**
@ -23,7 +18,6 @@ import java.util.Objects;
public class SchemaInterceptor implements InnerInterceptor {
@Override
public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) {

View File

@ -1,308 +0,0 @@
package com.qiaoba.common.database.mapper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.qiaoba.common.web.utils.BeanCopyUtil;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 自定义 Mapper 接口, 实现 自定义扩展
*
* @param <M> mapper 泛型
* @param <T> table 泛型
* @param <V> vo 泛型
* @author ailanyin
* @since 2023-04-23 17:20:32
*/
@SuppressWarnings("unchecked")
public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
/**
* currentVoClass
*
* @return Vo
*/
default Class<V> currentVoClass() {
return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 2);
}
/**
* currentModelClass
*
* @return model
*/
default Class<T> currentModelClass() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1);
}
/**
* currentMapperClass
*
* @return mapper
*/
default Class<M> currentMapperClass() {
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0);
}
/**
* 查询所有
*
* @return all lst
*/
default List<T> selectList() {
return this.selectList(new QueryWrapper<>());
}
/**
* 批量新增
*
* @param entityList list
* @return 结果
*/
default boolean insertBatch(Collection<T> entityList) {
return Db.saveBatch(entityList);
}
/**
* 批量更新
*
* @param entityList list
* @return 结果
*/
default boolean updateBatchById(Collection<T> entityList) {
return Db.updateBatchById(entityList);
}
/**
* 批量插入或更新
*
* @param entityList list
* @return 结果
*/
default boolean insertOrUpdateBatch(Collection<T> entityList) {
return Db.saveOrUpdateBatch(entityList);
}
/**
* 批量插入(包含限制条数)
*
* @param entityList list
* @param batchSize 最大调数
* @return 结果
*/
default boolean insertBatch(Collection<T> entityList, int batchSize) {
return Db.saveBatch(entityList, batchSize);
}
/**
* 批量更新(包含限制条数)
*
* @param entityList list
* @param batchSize 最大调数
* @return 结果
*/
default boolean updateBatchById(Collection<T> entityList, int batchSize) {
return Db.updateBatchById(entityList, batchSize);
}
/**
* 批量插入或更新(包含限制条数)
*
* @param entityList list
* @param batchSize 最大调数
* @return 结果
*/
default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
return Db.saveOrUpdateBatch(entityList, batchSize);
}
/**
* 插入或更新
*
* @param entity entity
* @return 结果
*/
default boolean insertOrUpdate(T entity) {
return Db.saveOrUpdate(entity);
}
/**
* 查询Vo by id
*
* @param id 主键
* @return Vo
*/
default V selectVoById(Serializable id) {
return selectVoById(id, this.currentVoClass());
}
/**
* 根据 ID 查询
*
* @param id 主键
* @param voClass Vo
* @param <C> 泛型
* @return Vo
*/
default <C> C selectVoById(Serializable id, Class<C> voClass) {
T obj = this.selectById(id);
if (ObjectUtil.isNull(obj)) {
return null;
}
return BeanCopyUtil.copy(obj, voClass);
}
/**
* 通过Ids 查询 list
*
* @param idList ids
* @return list
*/
default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
return selectVoBatchIds(idList, this.currentVoClass());
}
/**
* 通过Ids 查询 list
*
* @param idList ids
* @param voClass Vo
* @param <C> 泛型
* @return list
*/
default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
List<T> list = this.selectBatchIds(idList);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanCopyUtil.copyList(list, voClass);
}
/**
* selectVoByMap
*
* @param map map
* @return Vo
*/
default List<V> selectVoByMap(Map<String, Object> map) {
return selectVoByMap(map, this.currentVoClass());
}
/**
* selectVoByMap
*
* @param map map
* @param voClass Vo
* @param <C> 泛型
* @return Vo
*/
default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
List<T> list = this.selectByMap(map);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanCopyUtil.copyList(list, voClass);
}
/**
* 根据条件查询一条记录
*
* @param wrapper 条件
* @return Vo
*/
default V selectVoOne(Wrapper<T> wrapper) {
return selectVoOne(wrapper, this.currentVoClass());
}
/**
* 根据条件查询一条记录
*
* @param wrapper 条件
* @param voClass Vo
* @param <C> 泛型
* @return Vo
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
T obj = this.selectOne(wrapper);
if (ObjectUtil.isNull(obj)) {
return null;
}
return BeanCopyUtil.copy(obj, voClass);
}
/**
* 根据条件查询所有
*
* @param wrapper 条件
* @return list
*/
default List<V> selectVoList(Wrapper<T> wrapper) {
return selectVoList(wrapper, this.currentVoClass());
}
/**
* 根据条件查询所有
*
* @param wrapper 条件
* @param voClass Vo
* @param <C> 泛型
* @return list vo
*/
default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
List<T> list = this.selectList(wrapper);
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return BeanCopyUtil.copyList(list, voClass);
}
/**
* 分页查询Vo
*
* @param page 分页对象
* @param wrapper 条件
* @param <P> 泛型
* @return IPage
*/
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
return selectVoPage(page, wrapper, this.currentVoClass());
}
/**
* 分页查询Vo
*
* @param page 分页对象
* @param wrapper 条件
* @param voClass Vo
* @param <C> 泛型
* @param <P> 泛型
* @return IPage
*/
default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
IPage<T> pageData = this.selectPage(page, wrapper);
IPage<C> voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal());
if (CollUtil.isEmpty(pageData.getRecords())) {
return (P) voPage;
}
voPage.setRecords(BeanCopyUtil.copyList(pageData.getRecords(), voClass));
return (P) voPage;
}
}

View File

@ -2,6 +2,8 @@ package com.qiaoba.common.database.monitor;
import cn.hutool.core.io.IoUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.qiaoba.common.base.constants.TenantConstant;
import com.qiaoba.common.base.enums.DataBaseEnum;
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
@ -34,12 +36,16 @@ public class DatasourceConnectionMonitor {
private DynamicDataSourceConfig dynamicDataSourceConfig;
@Resource
private DynamicDatasourceService dynamicDatasourceService;
@Resource
private LockTemplate lockTemplate;
private static Map<String, String> WAIT_UPDATE_DATASOURCE_STATUS = new ConcurrentHashMap<>();
private static final String LOCK_KEY = "lock4j:datasourceConnectionMonitor";
@PostConstruct
public void init() {
// 1s运行一次
// 1s 运行一次
new Timer().schedule(new TimerTask() {
@Override
public void run() {
@ -47,47 +53,77 @@ public class DatasourceConnectionMonitor {
if (!DynamicDataSourceConfig.COMPLETE_LOAD_DATASOURCE) {
return;
}
log.trace("开始运行数据源监控线程, 时间: {}", new Date());
for (String tenantId : DynamicDataSourceConfig.TENANT_IDS) {
Object primary = DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.get(tenantId);
if (Objects.isNull(primary)) {
// 说明初始化主要数据源的时候出错
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
// 切换备用数据源
changePrimary(tenantId);
} else {
DruidDataSource dataSource = (DruidDataSource) primary;
try {
Connection connection = dataSource.getConnection();
if (check(connection, tenantId)) {
// 说明数据源正常
log.trace("租户[{}]-目前主数据源正常, 无需切换数据源", tenantId);
// 主数据 处理任务
if (TenantConstant.DEFAULT_TENANT_ID.equals(tenantId)) {
handleJob();
}
IoUtil.close(connection);
continue;
}
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
IoUtil.close(connection);
// 主数据源异常 切换备用数据源
if (changePrimary(tenantId)) {
// 备用切换成功, 关闭原有异常数据源
IoUtil.close(dataSource);
}
} catch (SQLException e) {
//e.printStackTrace();
}
}
// expire = -1 锁自动续期, 防止数据源过多或异常等待, 超过默认锁 30s
final LockInfo lockInfo = lockTemplate.lock(LOCK_KEY, -1, 1000);
//申请锁失败 说明集群中其他设备正在执行监控
if (null == lockInfo) {
return;
}
//申请锁成功
try {
// 执行监控
datasourceConnectionMonitor();
} finally {
// 释放锁
lockTemplate.releaseLock(lockInfo);
}
log.trace("结束运行数据源监控线程, 时间: {}", new Date());
}
}, 0, 1000);
}
/**
* 核心监控内容
*/
private void datasourceConnectionMonitor() {
log.trace("开始运行数据源监控线程, 时间: {}", new Date());
for (String tenantId : DynamicDataSourceConfig.TENANT_IDS) {
Object primary = DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.get(tenantId);
if (Objects.isNull(primary)) {
// 说明初始化主要数据源的时候出错
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
// 切换备用数据源
changePrimary(tenantId);
} else {
DruidDataSource dataSource = (DruidDataSource) primary;
Connection connection = null;
try {
connection = dataSource.getConnection();
if (check(connection, tenantId)) {
// 说明数据源正常
log.trace("租户[{}]-目前主数据源正常, 无需切换数据源", tenantId);
// 主数据 处理任务
if (TenantConstant.DEFAULT_TENANT_ID.equals(tenantId)) {
handleJob();
}
continue;
}
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
// 主数据源异常 切换备用数据源
if (changePrimary(tenantId)) {
// 备用切换成功, 关闭原有异常数据源
IoUtil.close(dataSource);
}
} catch (SQLException e) {
} finally {
IoUtil.close(connection);
}
}
}
log.trace("结束运行数据源监控线程, 时间: {}", new Date());
}
/**
* 检查数据源连接可用性
*
* @param conn 数据源
* @param tenantId 租户ID
* @return 结果
*/
private Boolean check(Connection conn, String tenantId) {
Statement stmt = null;
ResultSet rs = null;

View File

@ -8,7 +8,13 @@ import org.springframework.stereotype.Component;
import java.util.List;
/**
* DataSourceProperties
*
* @author ailanyin
* @version 1.0
* @since 2022-09-22 04:20:28
*/
@Component
@ConfigurationProperties(prefix = "qiaoba")
@Data

View File

@ -5,7 +5,8 @@ import cn.hutool.core.util.StrUtil;
import com.qiaoba.common.base.exceptions.ServiceException;
import lombok.extern.slf4j.Slf4j;
import java.sql.*;
import java.sql.Connection;
import java.sql.DriverManager;
/**
* JdbcUtil

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-common-doc</artifactId>
<description>通用-文档(Knife4j)模块</description>
<dependencies>
<dependency>
<groupId>com.github.xiaoymin</groupId>
@ -21,4 +23,4 @@
<artifactId>springdoc-openapi-ui</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-common-poi</artifactId>
<description>通用-poi(Excel, Word, Pdf等)模块</description>
<dependencies>
<!-- easypoi -->
<dependency>

View File

@ -11,11 +11,13 @@
<artifactId>qiaoba-common-redis</artifactId>
<description>通用-Redis模块</description>
<dependencies>
<!-- lock4j -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.qiaoba</groupId>

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-common-web</artifactId>
<description>通用-Web模块(有controller的模块必须引入此模块)</description>
<dependencies>
<dependency>
<groupId>com.qiaoba</groupId>

View File

@ -1,227 +0,0 @@
package com.qiaoba.common.web.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.cglib.core.Converter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* bean深拷贝工具(基于 cglib 性能优异)
* <p>
* 重点 cglib 不支持 拷贝到链式对象
* 例如: 源对象 拷贝到 目标(链式对象)
* 请区分好`浅拷贝`和`深拷贝`再做使用
*
* @author ailanyin
* @version 1.0
* @since 2023-04-23 15:34:59
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanCopyUtil {
/**
* 单对象基于class创建拷贝
*
* @param source 数据来源实体
* @param desc 描述对象 转换后的对象
* @return desc
*/
public static <T, V> V copy(T source, Class<V> desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
final V target = ReflectUtil.newInstanceIfPossible(desc);
return copy(source, target);
}
/**
* 单对象基于对象创建拷贝
*
* @param source 数据来源实体
* @param desc 转换后的对象
* @return desc
*/
public static <T, V> V copy(T source, V desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
BeanCopier beanCopier = BeanCopierCache.INSTANCE.get(source.getClass(), desc.getClass(), null);
beanCopier.copy(source, desc, null);
return desc;
}
/**
* 列表对象基于class创建拷贝
*
* @param sourceList 数据来源实体列表
* @param desc 描述对象 转换后的对象
* @return desc
*/
public static <T, V> List<V> copyList(List<T> sourceList, Class<V> desc) {
if (ObjectUtil.isNull(sourceList)) {
return null;
}
if (CollUtil.isEmpty(sourceList)) {
return CollUtil.newArrayList();
}
return toList(sourceList, source -> {
V target = ReflectUtil.newInstanceIfPossible(desc);
copy(source, target);
return target;
});
}
/**
* bean拷贝到map
*
* @param bean 数据来源实体
* @return map对象
*/
@SuppressWarnings("unchecked")
public static <T> Map<String, Object> copyToMap(T bean) {
if (ObjectUtil.isNull(bean)) {
return null;
}
return BeanMap.create(bean);
}
/**
* map拷贝到bean
*
* @param map 数据来源
* @param beanClass bean类
* @return bean对象
*/
public static <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(beanClass)) {
return null;
}
T bean = ReflectUtil.newInstanceIfPossible(beanClass);
return mapToBean(map, bean);
}
/**
* map拷贝到bean
*
* @param map 数据来源
* @param bean bean对象
* @return bean对象
*/
public static <T> T mapToBean(Map<String, Object> map, T bean) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(bean)) {
return null;
}
BeanMap.create(bean).putAll(map);
return bean;
}
/**
* map拷贝到map
*
* @param map 数据来源
* @param clazz 返回的对象类型
* @return map对象
*/
public static <T, V> Map<String, V> mapToMap(Map<String, T> map, Class<V> clazz) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(clazz)) {
return null;
}
Map<String, V> copyMap = new LinkedHashMap<>(map.size());
map.forEach((k, v) -> copyMap.put(k, copy(v, clazz)));
return copyMap;
}
/**
* BeanCopier属性缓存<br>
* 缓存用于防止多次反射造成的性能问题
*
* @author Looly
* @since 5.4.1
*/
public enum BeanCopierCache {
/**
* BeanCopier属性缓存单例
*/
INSTANCE;
private final SimpleCache<String, BeanCopier> cache = new SimpleCache<>();
/**
* 获得类与转换器生成的key在{@link BeanCopier}的Map中对应的元素
*
* @param srcClass 源Bean的类
* @param targetClass 目标Bean的类
* @param converter 转换器
* @return Map中对应的BeanCopier
*/
public BeanCopier get(Class<?> srcClass, Class<?> targetClass, Converter converter) {
final String key = genKey(srcClass, targetClass, converter);
return cache.get(key, () -> BeanCopier.create(srcClass, targetClass, converter != null));
}
/**
* 获得类与转换器生成的key
*
* @param srcClass 源Bean的类
* @param targetClass 目标Bean的类
* @param converter 转换器
* @return 属性名和Map映射的key
*/
private String genKey(Class<?> srcClass, Class<?> targetClass, Converter converter) {
final StringBuilder key = StrUtil.builder()
.append(srcClass.getName()).append('#').append(targetClass.getName());
if (null != converter) {
key.append('#').append(converter.getClass().getName());
}
return key.toString();
}
}
/**
* 将collection转化为List集合但是两者的泛型不同<br>
* <B>{@code Collection<E> ------> List<T> } </B>
*
* @param collection 需要转化的集合
* @param function collection中的泛型转化为list泛型的lambda表达式
* @param <E> collection中的泛型
* @param <T> List中的泛型
* @return 转化后的list
*/
private static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) {
if (CollUtil.isEmpty(collection)) {
return CollUtil.newArrayList();
}
return collection
.stream()
.map(function)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}

View File

@ -1,7 +1,6 @@
package com.qiaoba.common.web.utils;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.qiaoba.common.base.result.AjaxResult;

View File

@ -20,4 +20,6 @@
<module>qiaoba-module-demo</module>
</modules>
<description>qiaoba-modules: 新建的模块, 统一放在这个模块下面</description>
</project>

View File

@ -11,6 +11,10 @@
<artifactId>qiaoba-module-demo</artifactId>
<description>
示例模块
此模块主要用于典型案例的demo, 切勿引入到 qiaoba-application 模块中
</description>
<dependencies>
<dependency>
<groupId>com.qiaoba</groupId>
@ -24,5 +28,9 @@
<groupId>com.qiaoba</groupId>
<artifactId>qiaoba-common-web</artifactId>
</dependency>
<dependency>
<groupId>com.qiaoba</groupId>
<artifactId>qiaoba-common-doc</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@ -0,0 +1,18 @@
package com.qiaoba.module.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* DemoApplication
*
* @author ailanyin
* @version 1.0
* @since 2023/6/25 8:52
*/
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

View File

@ -2,22 +2,34 @@ package com.qiaoba.module.demo.controller;
import com.baomidou.lock.annotation.Lock4j;
import com.qiaoba.common.base.result.AjaxResult;
import com.qiaoba.module.demo.service.Lock4jService;
import com.qiaoba.module.demo.service.DemoLock4jService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Demo-Lock4j web层
*
* @author ailanyin
* @version 1.0
* @since 2022-09-22 04:20:28
*/
@RestController
@RequestMapping("/demo/lock4j")
@RequiredArgsConstructor
public class Lock4jController {
@Tag(name = "Demo-Lock4j")
@Slf4j
public class DemoLock4jController {
private final Lock4jService lock4jService;
private final DemoLock4jService demoLock4JService;
@GetMapping("/api-lock")
public AjaxResult apiLock() {
lock4jService.apiLock();
log.info("开始执行 apiLock, 当前线程{}", Thread.currentThread().getName());
demoLock4JService.apiLock();
return AjaxResult.success();
}
@ -29,9 +41,10 @@ public class Lock4jController {
* @return AjaxResult
*/
@GetMapping("/annotation-lock")
@Lock4j(keys = {"#accountId"}, expire = 20000, acquireTimeout = 10000)
@Lock4j(name = "annotationLock", acquireTimeout = 1000)
public AjaxResult annotationLock() {
lock4jService.annotationLock();
log.info("开始执行 annotationLock, 当前线程{}", Thread.currentThread().getName());
demoLock4JService.annotationLock();
return AjaxResult.success();
}
}

View File

@ -1,6 +1,13 @@
package com.qiaoba.module.demo.service;
public interface Lock4jService {
/**
* Demo-Lock4j 业务层
*
* @author ailanyin
* @version 1.0
* @since 2022-09-22 04:20:28
*/
public interface DemoLock4jService {
/**
* 分布式锁-编程式加锁

View File

@ -4,15 +4,22 @@ import cn.hutool.core.thread.ThreadUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.qiaoba.common.base.exceptions.ServiceException;
import com.qiaoba.module.demo.service.Lock4jService;
import com.qiaoba.module.demo.service.DemoLock4jService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* Demo-Lock4j 业务层实现
*
* @author ailanyin
* @version 1.0
* @since 2022-09-22 04:20:28
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class Lock4jServiceImpl implements Lock4jService {
public class DemoLock4jServiceImpl implements DemoLock4jService {
private final LockTemplate lockTemplate;
@ -20,15 +27,16 @@ public class Lock4jServiceImpl implements Lock4jService {
public void apiLock() {
//... 各种不需要上锁的操作
String lockKey = "lock4j:apiLock";
final LockInfo lockInfo = lockTemplate.lock(lockKey);
// expire = -1 锁自动续期
final LockInfo lockInfo = lockTemplate.lock(lockKey, -1, 1000);
//申请锁失败
if (null == lockInfo) {
throw new ServiceException("业务处理中,请稍后再试...");
}
//申请锁成功
try {
ThreadUtil.sleep(10 * 1000);
log.info("执行 apiLock, 当前线程{}", Thread.currentThread().getName());
ThreadUtil.sleep(30 * 1000);
log.info("执行完毕 apiLock, 当前线程{}", Thread.currentThread().getName());
} finally {
lockTemplate.releaseLock(lockInfo);
}
@ -38,6 +46,6 @@ public class Lock4jServiceImpl implements Lock4jService {
public void annotationLock() {
// 休眠 10s
ThreadUtil.sleep(10 * 1000);
log.info("执行 apiLock, 当前线程{}", Thread.currentThread().getName());
log.info("执行完毕 annotationLock, 当前线程{}", Thread.currentThread().getName());
}
}

View File

@ -0,0 +1,15 @@
server:
port: 8080
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
api-docs:
path: /v3/api-docs
group-configs:
- group: 'Demo'
paths-to-match: '/**'
packages-to-scan: com.qiaoba.module.demo.controller

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-module-file</artifactId>
<description>文件模块</description>
<dependencies>
<dependency>
<groupId>com.qiaoba</groupId>

View File

@ -11,5 +11,6 @@
<artifactId>qiaoba-module-job</artifactId>
<description>定时任务模块</description>
</project>
</project>

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-module-monitor</artifactId>
<description>监控模块</description>
<dependencies>
<dependency>
<groupId>com.qiaoba</groupId>

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-module-system</artifactId>
<description>系统模块</description>
<dependencies>
<dependency>
<groupId>com.qiaoba</groupId>

View File

@ -41,7 +41,7 @@ public class SysDictTypeController {
@PostMapping("/export")
public void export(HttpServletResponse response, SysDictTypeParam param) throws Exception {
List<SysDictType> list = sysDictTypeService.selectList(param);
ExcelUtil.exportExcel(list, SysDictType.class, "字典类型" , response);
ExcelUtil.exportExcel(list, SysDictType.class, "字典类型", response);
}
@PreAuthorize("hasAuthority('system:dict:query')")

View File

@ -2,7 +2,6 @@ package com.qiaoba.module.system.controller;
import com.qiaoba.api.system.entity.SysMenu;
import com.qiaoba.api.system.entity.param.SysMenuParam;
import com.qiaoba.api.system.entity.vo.SysMenuVo;
import com.qiaoba.common.base.result.AjaxResult;
import com.qiaoba.module.system.service.SysMenuService;
import io.swagger.v3.oas.annotations.Operation;
@ -32,7 +31,7 @@ public class SysMenuController {
@GetMapping("/list")
@Operation(summary = "获取列表")
public AjaxResult list(SysMenuParam param) {
List<SysMenuVo> menus = sysMenuService.selectVoList(param);
List<SysMenu> menus = sysMenuService.selectList(param);
return AjaxResult.success(menus);
}
@ -67,7 +66,7 @@ public class SysMenuController {
@GetMapping("/tree-select")
@Operation(summary = "构建菜单树")
public AjaxResult treeSelect(SysMenuParam param) {
List<SysMenuVo> menus = sysMenuService.selectVoList(param);
List<SysMenu> menus = sysMenuService.selectList(param);
return AjaxResult.success(sysMenuService.buildMenuTree(menus));
}
@ -76,7 +75,7 @@ public class SysMenuController {
public AjaxResult roleMenuTreeSelect(@PathVariable("roleId") String roleId) {
AjaxResult ajax = AjaxResult.success();
ajax.put("checkedKeys", sysMenuService.selectMenuIdsByRoleId(roleId));
ajax.put("menus", sysMenuService.buildMenuTree(sysMenuService.selectVoList(new SysMenuParam())));
ajax.put("menus", sysMenuService.buildMenuTree(sysMenuService.selectList(new SysMenuParam())));
return ajax;
}
}

View File

@ -1,9 +1,9 @@
package com.qiaoba.module.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qiaoba.api.system.entity.SysMenu;
import com.qiaoba.api.system.entity.vo.SysMenuVo;
import com.qiaoba.common.database.annotations.SelectOneRow;
import com.qiaoba.common.database.mapper.BaseMapperPlus;
import java.util.List;
import java.util.Set;
@ -15,7 +15,7 @@ import java.util.Set;
* @version 1.0
* @since 2023/5/5 11:36
*/
public interface SysMenuMapper extends BaseMapperPlus<SysMenuMapper, SysMenu, SysMenuVo> {
public interface SysMenuMapper extends BaseMapper<SysMenu> {
/**
* 校验菜单名称是否唯一

View File

@ -1,8 +1,6 @@
package com.qiaoba.module.system.service;
import com.qiaoba.api.system.entity.SysUser;
import com.qiaoba.api.system.entity.dto.LoginDto;
import com.qiaoba.common.base.result.AjaxResult;
import java.util.Map;

View File

@ -1,6 +1,7 @@
package com.qiaoba.module.system.service;
import cn.hutool.core.lang.tree.Tree;
import com.qiaoba.api.system.entity.SysMenu;
import com.qiaoba.api.system.entity.vo.RouterVo;
import com.qiaoba.api.system.entity.vo.SysMenuVo;
import com.qiaoba.api.system.service.SysMenuApiService;
@ -24,7 +25,7 @@ public interface SysMenuService extends SysMenuApiService {
* @param menus 菜单列表
* @return 下拉树结构列表
*/
List<Tree<String>> buildMenuTree(List<SysMenuVo> menus);
List<Tree<String>> buildMenuTree(List<SysMenu> menus);
/**
* 根据角色ID查询菜单树信息

View File

@ -56,8 +56,8 @@ public class SysMenuServiceImpl implements SysMenuService {
}
@Override
public List<SysMenuVo> selectVoList(SysMenuParam param) {
return sysMenuMapper.selectVoList(param2Wrapper((param)));
public List<SysMenu> selectList(SysMenuParam param) {
return sysMenuMapper.selectList(param2Wrapper((param)));
}
@Override
@ -81,7 +81,7 @@ public class SysMenuServiceImpl implements SysMenuService {
}
@Override
public List<Tree<String>> buildMenuTree(List<SysMenuVo> menus) {
public List<Tree<String>> buildMenuTree(List<SysMenu> menus) {
TreeNodeConfig config = TreeNodeConfig.DEFAULT_CONFIG.setNameKey(BaseConstant.TREE_KEY_NAME);
return TreeUtil.build(menus, BaseConstant.DEFAULT_PARENT_ID_VALUE, config, (menu, tree) ->
tree.setId(menu.getMenuId())

View File

@ -11,7 +11,6 @@ import com.qiaoba.api.system.entity.dto.DataScopeDto;
import com.qiaoba.api.system.entity.dto.SysRoleDto;
import com.qiaoba.api.system.entity.param.SysRoleParam;
import com.qiaoba.auth.utils.SecurityUtil;
import com.qiaoba.common.base.constants.BaseConstant;
import com.qiaoba.common.base.exceptions.ServiceException;
import com.qiaoba.common.database.entity.PageQuery;
import com.qiaoba.common.database.entity.TableDataInfo;

View File

@ -1,8 +1,8 @@
package com.qiaoba.module.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.qiaoba.api.system.entity.SysUser;
import com.qiaoba.api.auth.service.SysUserDetailsApiService;
import com.qiaoba.api.system.entity.SysUser;
import com.qiaoba.auth.constants.SecurityConstant;
import com.qiaoba.auth.entity.LoginUser;
import com.qiaoba.auth.entity.SecurityUser;

View File

@ -14,7 +14,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

View File

@ -103,7 +103,7 @@ public class SysUserServiceImpl implements SysUserService {
}
@Override
@DataScope(userAlias = "u",deptAlias = "t2")
@DataScope(userAlias = "u", deptAlias = "t2")
public TableDataInfo<SysUserVo> selectVoPageList(SysUserParam param, PageQuery pageQuery) {
Page<SysUserVo> page = sysUserMapper.selectVoPageList(pageQuery.build(), param);
return TableDataInfo.build(page);

View File

@ -12,8 +12,9 @@
</select>
<select id="selectByIds" resultType="com.qiaoba.api.system.entity.SysConfig">
select config_id, config_name, config_key, config_value, config_type, create_user, create_time, update_user, update_time, remark
from sys_config where config_id in
select config_id, config_name, config_key, config_value, config_type, create_user, create_time, update_user,
update_time, remark
from sys_config where config_id in
<foreach item="configId" collection="list" open="(" separator="," close=")">
#{configId}
</foreach>

View File

@ -11,6 +11,8 @@
<artifactId>qiaoba-module-tenant</artifactId>
<description>租户模块</description>
<dependencies>
<dependency>
<groupId>com.qiaoba</groupId>

View File

@ -8,6 +8,13 @@ import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Service;
/**
* 项目启动完成后-将主库中的租户信息同步到 Redis 中
*
* @author ailanyin
* @version 1.0
* @since 2022-09-22 04:20:28
*/
@Service
@RequiredArgsConstructor
public class SysTenantRunner implements ApplicationRunner {

View File

@ -72,7 +72,7 @@ public interface SysTenantDatasourceService {
/**
* 将租户下的除datasourceId之外的其他数据源设置为备用
*
* @param tenantId tenantId
* @param tenantId tenantId
* @param excludeId 排除Id
*/
void setBackupDatasourceExcludeId(String tenantId, String excludeId);

View File

@ -4,9 +4,6 @@ import com.qiaoba.common.database.entity.PageQuery;
import com.qiaoba.common.database.entity.TableDataInfo;
import com.qiaoba.module.tenant.entity.SysTenant;
import com.qiaoba.module.tenant.entity.param.SysTenantParam;
import com.qiaoba.module.tenant.entity.vo.TenantSettingVo;
import java.sql.Connection;
/**
* 租户管理 服务层

View File

@ -62,7 +62,7 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic
@Override
public void setBackupDatasourceExcludeId(String tenantId, String excludeId) {
sysTenantDatasourceMapper.setBackupDatasourceExcludeId(tenantId,excludeId,BaseEnum.NO.getCode());
sysTenantDatasourceMapper.setBackupDatasourceExcludeId(tenantId, excludeId, BaseEnum.NO.getCode());
}
private QueryWrapper<SysTenantDatasource> paramToWrapper(SysTenantDatasourceParam param) {