add
This commit is contained in:
25
qiaoba-common/pom.xml
Normal file
25
qiaoba-common/pom.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>qiaoba-boot</artifactId>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qiaoba-common</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>qiaoba-common-base</module>
|
||||
<module>qiaoba-common-datasource</module>
|
||||
<module>qiaoba-common-web</module>
|
||||
<module>qiaoba-common-doc</module>
|
||||
<module>qiaoba-common-redis</module>
|
||||
<module>qiaoba-common-poi</module>
|
||||
</modules>
|
||||
|
||||
<description>qiaoba-common: 新建的common统一放在此模块下面</description>
|
||||
|
||||
</project>
|
30
qiaoba-common/qiaoba-common-base/pom.xml
Normal file
30
qiaoba-common/qiaoba-common-base/pom.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>qiaoba-common</artifactId>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qiaoba-common-base</artifactId>
|
||||
|
||||
<description>通用-基础模块</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,33 @@
|
||||
package com.qiaoba.common.base.code;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 系统配置错误code
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/12 13:34
|
||||
*/
|
||||
@Getter
|
||||
public enum ConfigErrorCode {
|
||||
|
||||
/**
|
||||
* 系统内置-禁止删除
|
||||
*/
|
||||
SYS_NOT_ALLOW_DELETE(5020, "系统内置不允许删除!"),
|
||||
|
||||
/**
|
||||
* Key已存在
|
||||
*/
|
||||
KEY_EXIST(5021, "参数键名已存在, 操作失败!");
|
||||
|
||||
private final Integer code;
|
||||
private final String msg;
|
||||
|
||||
ConfigErrorCode(Integer code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.qiaoba.common.base.code;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 数据源错误信息
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/27 9:22
|
||||
*/
|
||||
@Getter
|
||||
public enum DatasourceErrorCode {
|
||||
|
||||
/**
|
||||
* 未找到
|
||||
*/
|
||||
NOT_FIND(50401, "未找到数据源信息 (可能未设置使用)"),
|
||||
|
||||
/**
|
||||
* 连接错误
|
||||
*/
|
||||
CONNECT_ERROR(50402, "数据源无法连接"),
|
||||
|
||||
/**
|
||||
* 切换模式错误
|
||||
*/
|
||||
SWITCH_SCHEMA_ERROR(50403, "切换模式错误"),
|
||||
|
||||
/**
|
||||
* 创建表错误
|
||||
*/
|
||||
CREATE_TABLE_ERROR(50404, "创建表错误"),
|
||||
|
||||
/**
|
||||
* 创建模式/库错误
|
||||
*/
|
||||
CREATE_SCHEMA_ERROR(50405, "创建模式/库错误"),
|
||||
|
||||
/**
|
||||
* 初始化数据错误
|
||||
*/
|
||||
INIT_DATA_ERROR(50406, "初始化数据错误"),
|
||||
|
||||
/**
|
||||
* schema 不存在
|
||||
*/
|
||||
SCHEMA_NOT_EXIST_ERROR(50407, "SCHEMA: {} 不存在");
|
||||
|
||||
private final Integer code;
|
||||
private final String msg;
|
||||
|
||||
DatasourceErrorCode(Integer code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.qiaoba.common.base.code;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 字典错误code
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/12 13:34
|
||||
*/
|
||||
@Getter
|
||||
public enum DictErrorCode {
|
||||
|
||||
/**
|
||||
* 类型已存在
|
||||
*/
|
||||
TYPE_EXIST(5030, "类型[{}]已存在, 不允许新增或修改!"),
|
||||
|
||||
/**
|
||||
* 存在数据
|
||||
*/
|
||||
HAS_DATA(5031, "字典[{}]已绑定数据, 请解绑后删除!");
|
||||
|
||||
private final Integer code;
|
||||
private final String msg;
|
||||
|
||||
DictErrorCode(Integer code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.qiaoba.common.base.code;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 租户错误code
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/12 13:33
|
||||
*/
|
||||
@Getter
|
||||
public enum TenantErrorCode {
|
||||
|
||||
/**
|
||||
* 未找到
|
||||
*/
|
||||
NOT_FIND(5010, "未找到租户信息"),
|
||||
|
||||
/**
|
||||
* 禁用
|
||||
*/
|
||||
DISABLE(5011, "租户被禁用"),
|
||||
|
||||
/**
|
||||
* 过期
|
||||
*/
|
||||
EXPIRE(5012, "租户已过期"),
|
||||
|
||||
/**
|
||||
* 租户模式不存在
|
||||
*/
|
||||
MODE_NOT_FIND(50103, "未找到租户模式"),
|
||||
|
||||
/**
|
||||
* 已初始化过
|
||||
*/
|
||||
INITIALIZED(50104, "已初始化过"),
|
||||
|
||||
/**
|
||||
* 租户名称已存在
|
||||
*/
|
||||
TENANT_NAME_EXIST(50105, "公司名称已存在");
|
||||
|
||||
private final Integer code;
|
||||
private final String msg;
|
||||
|
||||
TenantErrorCode(Integer code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.qiaoba.common.base.code;
|
||||
|
||||
/**
|
||||
* 用户错误code
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/12 13:34
|
||||
*/
|
||||
public enum UserErrorCode {
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package com.qiaoba.common.base.constant;
|
||||
|
||||
/**
|
||||
* BaseConstant
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 15:37:43
|
||||
*/
|
||||
public class BaseConstant {
|
||||
|
||||
/**
|
||||
* UTF-8 字符集
|
||||
*/
|
||||
public static final String UTF8 = "UTF-8";
|
||||
|
||||
/**
|
||||
* 默认的字符拼接/切割符号: ','(英文逗号)
|
||||
*/
|
||||
public static final String DEFAULT_SPLIT_STR = ",";
|
||||
|
||||
/**
|
||||
* 竖线拼接符号: '|'(英文竖线)
|
||||
*/
|
||||
public static final String LINE_JOIN_STR = "|";
|
||||
|
||||
/**
|
||||
* 冒号拼接符号: ':'(英文冒号)
|
||||
*/
|
||||
public static final String COLON_JOIN_STR = ":";
|
||||
|
||||
/**
|
||||
* 中划线拼接符号: '-'
|
||||
*/
|
||||
public static final String HYPHEN_JOIN_STR = "-";
|
||||
|
||||
/**
|
||||
* 树的key的命名
|
||||
*/
|
||||
public static final String TREE_KEY_NAME = "label";
|
||||
|
||||
/**
|
||||
* 默认的父ID = 0
|
||||
*/
|
||||
public static final String DEFAULT_PARENT_ID_VALUE = "0";
|
||||
|
||||
/**
|
||||
* http请求
|
||||
*/
|
||||
public static final String HTTP = "http://";
|
||||
|
||||
/**
|
||||
* https请求
|
||||
*/
|
||||
public static final String HTTPS = "https://";
|
||||
|
||||
/**
|
||||
* 资源映射路径 前缀
|
||||
*/
|
||||
public static final String RESOURCE_PREFIX = "/resource";
|
||||
|
||||
/**
|
||||
* 资源映射路径 正则
|
||||
*/
|
||||
public static final String RESOURCE_PATTERN = RESOURCE_PREFIX + "/**";
|
||||
|
||||
/**
|
||||
* 是
|
||||
*/
|
||||
public static final String YES = "Y";
|
||||
|
||||
/**
|
||||
* 否
|
||||
*/
|
||||
public static final String NO = "N";
|
||||
|
||||
/**
|
||||
* 处理失败 个数 0
|
||||
*/
|
||||
public static final Integer HANDLE_ERROR = 0;
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.qiaoba.common.base.constant;
|
||||
|
||||
/**
|
||||
* 配置常量
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 15:37:43
|
||||
*/
|
||||
public class ConfigConstant {
|
||||
|
||||
/**
|
||||
* 参数管理 cache key 前缀
|
||||
*/
|
||||
public static final String SYS_CONFIG_KEY_PREFIX = "sys_config:";
|
||||
|
||||
/**
|
||||
* 参数配置-Token有效期
|
||||
*/
|
||||
public static final String TOKEN_EXPIRE_TIME_KEY = SYS_CONFIG_KEY_PREFIX + "sys.token.expireTime";
|
||||
|
||||
/**
|
||||
* 参数配置-允许同时在线
|
||||
*/
|
||||
public static final String ALLOW_BOTH_ONLINE_KEY = SYS_CONFIG_KEY_PREFIX + "sys.account.allowBothOnline";
|
||||
|
||||
/**
|
||||
* 参数配置-系统注册开关
|
||||
*/
|
||||
public static final String REGISTER_ON_OFF_KEY = SYS_CONFIG_KEY_PREFIX + "sys.account.registerUser";
|
||||
|
||||
/**
|
||||
* 参数配置-验证码开关
|
||||
*/
|
||||
public static final String CAPTCHA_ON_OFF_KEY = SYS_CONFIG_KEY_PREFIX + "sys.account.captchaOnOff";
|
||||
|
||||
/**
|
||||
* 参数配置-黑名单开关
|
||||
*/
|
||||
public static final String BLACKLIST_ON_OFF_KEY = SYS_CONFIG_KEY_PREFIX + "sys.account.blacklistOnOff";
|
||||
|
||||
/**
|
||||
* 参数配置-黑名单过期时间(拉黑时间), 单位:分钟
|
||||
*/
|
||||
public static final String BLACKLIST_EXPIRE_TIME_KEY = SYS_CONFIG_KEY_PREFIX + "sys.account.blacklistExpireTime";
|
||||
|
||||
/**
|
||||
* 参数配置-登陆最大错误次数
|
||||
*/
|
||||
public static final String LOGIN_ERROR_MAX_COUNT_KEY = SYS_CONFIG_KEY_PREFIX + "sys.account.loginErrorMaxCount";
|
||||
|
||||
/**
|
||||
* 参数配置-登录日志开关
|
||||
*/
|
||||
public static final String LOGIN_LOG_ON_OFF_KEY = SYS_CONFIG_KEY_PREFIX + "sys.login.loginLogOnOff";
|
||||
|
||||
/**
|
||||
* 开
|
||||
*/
|
||||
public static final String COMMON_ON_VALUE = "true";
|
||||
|
||||
/**
|
||||
* 关
|
||||
*/
|
||||
public static final String COMMON_OFF_VALUE = "false";
|
||||
|
||||
/**
|
||||
* 默认最大允许错误次数
|
||||
*/
|
||||
public static final Integer DEFAULT_LOGIN_ERROR_MAX_COUNT = 5;
|
||||
|
||||
/**
|
||||
* 默认最大黑名单过期时间(单位分钟)
|
||||
*/
|
||||
public static final Long DEFAULT_MAX_BLACKLIST_EXPIRE_TIME = 30L;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.qiaoba.common.base.constant;
|
||||
|
||||
/**
|
||||
* 字典常量
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 15:37:43
|
||||
*/
|
||||
public class DictConstant {
|
||||
|
||||
/**
|
||||
* 字典 redis key
|
||||
*/
|
||||
public static final String SYS_DICT_KEY = "sys_dict:";
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.qiaoba.common.base.constant;
|
||||
|
||||
/**
|
||||
* 菜单常量
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 15:37:43
|
||||
*/
|
||||
public class MenuConstant {
|
||||
|
||||
/**
|
||||
* 是否菜单外链(是)
|
||||
*/
|
||||
public static final String YES_FRAME = "1";
|
||||
|
||||
/**
|
||||
* 是否菜单外链(否)
|
||||
*/
|
||||
public static final String NO_FRAME = "0";
|
||||
|
||||
/**
|
||||
* 菜单类型(目录)
|
||||
*/
|
||||
public static final String TYPE_DIR = "M";
|
||||
|
||||
/**
|
||||
* 菜单类型(菜单)
|
||||
*/
|
||||
public static final String TYPE_MENU = "C";
|
||||
|
||||
/**
|
||||
* 菜单类型(按钮)
|
||||
*/
|
||||
public static final String TYPE_BUTTON = "F";
|
||||
|
||||
/**
|
||||
* Layout组件标识
|
||||
*/
|
||||
public final static String LAYOUT = "Layout";
|
||||
|
||||
/**
|
||||
* ParentView组件标识
|
||||
*/
|
||||
public final static String PARENT_VIEW = "ParentView";
|
||||
|
||||
/**
|
||||
* InnerLink组件标识
|
||||
*/
|
||||
public final static String INNER_LINK = "InnerLink";
|
||||
|
||||
/**
|
||||
* 不重定向
|
||||
*/
|
||||
public final static String NO_REDIRECT = "noRedirect";
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.qiaoba.common.base.constant;
|
||||
|
||||
/**
|
||||
* 租户常量
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 15:37:43
|
||||
*/
|
||||
public class TenantConstant {
|
||||
|
||||
/**
|
||||
* 租户 key 前缀
|
||||
*/
|
||||
public static final String TENANT_KEY_PREFIX = "tenant_";
|
||||
|
||||
/**
|
||||
* 租户信息 Redis Key
|
||||
*/
|
||||
public static final String TENANT_INFO_KEY_PREFIX = "tenant_info:";
|
||||
|
||||
/**
|
||||
* header 租户 key
|
||||
*/
|
||||
public static final String HEADER_KEY_TENANT = "tenant";
|
||||
|
||||
/**
|
||||
* 系统默认租户ID
|
||||
*/
|
||||
public static final String DEFAULT_TENANT_ID = "1";
|
||||
|
||||
/**
|
||||
* 登陆入口-获取正常的租户-Uri
|
||||
*/
|
||||
public static final String LOGIN_TENANT_LIST_URI = "/tenant/normal-list";
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package com.qiaoba.common.base.context;
|
||||
|
||||
/**
|
||||
* 全局上下文对象
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/5/22 17:24
|
||||
*/
|
||||
public class BaseContext {
|
||||
|
||||
/**
|
||||
* 数据库类型
|
||||
*/
|
||||
private static final ThreadLocal<String> DATABASE_TYPE_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* SCHEMA
|
||||
*/
|
||||
private static final ThreadLocal<Boolean> SCHEMA_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private static final ThreadLocal<String> TENANT_ID_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 数据源
|
||||
*/
|
||||
private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 获取上下文中数据库类型
|
||||
*/
|
||||
public static String getDatabaseType() {
|
||||
return DATABASE_TYPE_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上下文中数据库类型
|
||||
*/
|
||||
public static void setDatabaseType(String type) {
|
||||
DATABASE_TYPE_HOLDER.set(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文中租户ID
|
||||
*/
|
||||
public static String getTenantId() {
|
||||
return TENANT_ID_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上下文中租户ID
|
||||
*/
|
||||
public static void setTenantId(String tenantId) {
|
||||
TENANT_ID_HOLDER.set(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除上下文中租户ID
|
||||
*/
|
||||
public static void clearTenantId() {
|
||||
TENANT_ID_HOLDER.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文中的数据源
|
||||
*/
|
||||
public static String getDataSource() {
|
||||
return DATASOURCE_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上下文中的数据源
|
||||
*/
|
||||
public static void setDataSource(String dataSource) {
|
||||
DATASOURCE_HOLDER.set(dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是 Schema模式
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public static Boolean isSchemaMode() {
|
||||
return SCHEMA_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否是 Schema模式
|
||||
*
|
||||
* @param isSchema 是/否
|
||||
*/
|
||||
public static void setSchema(boolean isSchema) {
|
||||
SCHEMA_HOLDER.set(isSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有的ThreadLocal
|
||||
*/
|
||||
public static void clearAllHolder() {
|
||||
// 清除上下文中数据源
|
||||
DATASOURCE_HOLDER.remove();
|
||||
// 清除上下文中租户ID
|
||||
TENANT_ID_HOLDER.remove();
|
||||
// 清除上下文中数据库类型
|
||||
DATABASE_TYPE_HOLDER.remove();
|
||||
// 清除上下文中 SCHEMA
|
||||
SCHEMA_HOLDER.remove();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.qiaoba.common.base.entity;
|
||||
|
||||
import com.qiaoba.common.base.validate.AddGroup;
|
||||
import com.qiaoba.common.base.validate.EditGroup;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* BaseEntity
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 15:37:43
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class BaseEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private String createUser;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private String updateUser;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@Size(max = 500, message = "备注不能超过{max}个字符", groups = {AddGroup.class, EditGroup.class})
|
||||
private String remark;
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.qiaoba.common.base.entity;
|
||||
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基础分页封装对象
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 15:37:43
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class BasePage<T> implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
public long total;
|
||||
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
public List<T> rows;
|
||||
|
||||
/**
|
||||
* 消息状态码
|
||||
*/
|
||||
public int code;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
public String msg;
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*
|
||||
* @param list 列表数据
|
||||
* @param total 总记录数
|
||||
*/
|
||||
public BasePage(List<T> list, long total) {
|
||||
this.rows = list;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
|
||||
public static <T> BasePage<T> build(List<T> list) {
|
||||
BasePage<T> basePage = new BasePage<>();
|
||||
basePage.setCode(HttpStatus.HTTP_OK);
|
||||
basePage.setMsg("查询成功");
|
||||
basePage.setRows(list);
|
||||
basePage.setTotal(list.size());
|
||||
return basePage;
|
||||
}
|
||||
|
||||
public static <T> BasePage<T> build(Long total, List<T> list) {
|
||||
BasePage<T> basePage = new BasePage<>();
|
||||
basePage.setCode(HttpStatus.HTTP_OK);
|
||||
basePage.setMsg("查询成功");
|
||||
basePage.setRows(list);
|
||||
basePage.setTotal(total);
|
||||
return basePage;
|
||||
}
|
||||
|
||||
public static <T> BasePage<T> build() {
|
||||
BasePage<T> basePage = new BasePage<>();
|
||||
basePage.setCode(HttpStatus.HTTP_OK);
|
||||
basePage.setMsg("查询成功");
|
||||
return basePage;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.qiaoba.common.base.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 数据权限
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/5/23 9:36
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class DataScopeParam implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String dataScope;
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.qiaoba.common.base.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BaseEnum
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 15:34:59
|
||||
*/
|
||||
@Getter
|
||||
public enum BaseEnum {
|
||||
|
||||
// 是
|
||||
YES("1", "是"),
|
||||
// 否
|
||||
NO("0", "否"),
|
||||
// 正常
|
||||
NORMAL("1", "正常"),
|
||||
// 不正常
|
||||
ABNORMAL("0", "不正常");
|
||||
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
BaseEnum(String code, String info) {
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.qiaoba.common.base.enums;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.qiaoba.common.base.exception.ServiceException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 数据库类型
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/5/22 16:41
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DataBaseEnum {
|
||||
|
||||
// ip/port/database
|
||||
|
||||
/**
|
||||
* MySQL
|
||||
*/
|
||||
MY_SQL("MySQL",
|
||||
"jdbc:mysql://{}:{}/{}?databaseTerm=SCHEMA&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true",
|
||||
"com.mysql.cj.jdbc.Driver",
|
||||
"SELECT 1"),
|
||||
|
||||
/**
|
||||
* PostgreSQL
|
||||
*/
|
||||
POSTGRE_SQL("PostgreSQL",
|
||||
"jdbc:postgresql://{}:{}/{}?currentSchema={}&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true",
|
||||
"org.postgresql.Driver",
|
||||
"SELECT 1"),
|
||||
|
||||
/**
|
||||
* SQL Server
|
||||
*/
|
||||
SQL_SERVER("Microsoft SQL Server",
|
||||
"jdbc:sqlserver://localhost:1433;DatabaseName={};SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true",
|
||||
"com.microsoft.sqlserver.jdbc.SQLServerDriver",
|
||||
"SELECT 1");
|
||||
|
||||
private final String type;
|
||||
private final String url;
|
||||
private final String driver;
|
||||
private final String checkSql;
|
||||
|
||||
|
||||
public static String getDriver(String type) {
|
||||
for (DataBaseEnum dataBaseEnum : values()) {
|
||||
if (dataBaseEnum.getType().equals(type)) {
|
||||
return dataBaseEnum.driver;
|
||||
}
|
||||
}
|
||||
throw new ServiceException(StrUtil.format("未找到数据库驱动, Type: {}", type));
|
||||
}
|
||||
|
||||
public static String getUrl(String type, String ip, String port, String dbName, String schema) {
|
||||
for (DataBaseEnum dataBaseEnum : values()) {
|
||||
if (dataBaseEnum.getType().equals(type)) {
|
||||
return StrUtil.format(dataBaseEnum.url, ip, port, dbName, schema);
|
||||
}
|
||||
}
|
||||
throw new ServiceException(StrUtil.format("未找到数据库Url, Type: {}", type));
|
||||
}
|
||||
|
||||
public static String getCheckSql(String type) {
|
||||
for (DataBaseEnum dataBaseEnum : values()) {
|
||||
if (dataBaseEnum.getType().equals(type)) {
|
||||
return dataBaseEnum.checkSql;
|
||||
}
|
||||
}
|
||||
throw new ServiceException(StrUtil.format("未找到数据库checkSql, Type: {}", type));
|
||||
}
|
||||
|
||||
public static String getIp(String url, String driver) {
|
||||
for (DataBaseEnum dataBaseEnum : values()) {
|
||||
if (dataBaseEnum.getDriver().equals(driver)) {
|
||||
url = url.replaceFirst(url.substring(0, dataBaseEnum.url.indexOf(StrUtil.EMPTY_JSON)), "");
|
||||
return url.split(StrUtil.COLON)[0];
|
||||
}
|
||||
}
|
||||
throw new ServiceException(StrUtil.format("未找到数据库IP, Driver: {}", driver));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.qiaoba.common.base.exception;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 自定义 业务异常
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021-08-31
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class ServiceException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private Integer errorCode;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
public ServiceException(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ServiceException(Integer code, String message) {
|
||||
this.message = message;
|
||||
this.errorCode = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制台不打印自定义错误的堆栈信息, 提高性能
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.qiaoba.common.base.order;
|
||||
|
||||
/**
|
||||
* 过滤器执行顺序
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/7/6 9:22
|
||||
*/
|
||||
public interface FilterOrder {
|
||||
|
||||
/**
|
||||
* 动态数据源过滤器-执行顺序(最高)
|
||||
*/
|
||||
int DYNAMIC_DATASOURCE_FILTER_ORDER = -10000;
|
||||
|
||||
/**
|
||||
* 在线用户过滤器
|
||||
*/
|
||||
int ONLINE_USER_FILTER_ORDER = -9999;
|
||||
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
package com.qiaoba.common.base.result;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* 通用返回
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021-08-31
|
||||
*/
|
||||
public class AjaxResult extends HashMap<String, Object> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
public static final String CODE_TAG = "code";
|
||||
|
||||
/**
|
||||
* 返回内容
|
||||
*/
|
||||
public static final String MSG_TAG = "msg";
|
||||
|
||||
/**
|
||||
* 数据对象
|
||||
*/
|
||||
public static final String DATA_TAG = "data";
|
||||
|
||||
/**
|
||||
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
|
||||
*/
|
||||
public AjaxResult() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化一个新创建的 AjaxResult 对象
|
||||
*
|
||||
* @param code 状态码
|
||||
* @param msg 返回内容
|
||||
*/
|
||||
public AjaxResult(int code, String msg) {
|
||||
super.put(CODE_TAG, code);
|
||||
super.put(MSG_TAG, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化一个新创建的 AjaxResult 对象
|
||||
*
|
||||
* @param code 状态码
|
||||
* @param msg 返回内容
|
||||
* @param data 数据对象
|
||||
*/
|
||||
public AjaxResult(int code, String msg, Object data) {
|
||||
super.put(CODE_TAG, code);
|
||||
super.put(MSG_TAG, msg);
|
||||
super.put(DATA_TAG, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回成功消息
|
||||
*
|
||||
* @return 成功消息
|
||||
*/
|
||||
public static AjaxResult success() {
|
||||
return AjaxResult.success("操作成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回成功数据
|
||||
*
|
||||
* @return 成功消息
|
||||
*/
|
||||
public static AjaxResult success(Object data) {
|
||||
return AjaxResult.success("操作成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回成功消息
|
||||
*
|
||||
* @param msg 返回内容
|
||||
* @return 成功消息
|
||||
*/
|
||||
public static AjaxResult success(String msg) {
|
||||
return AjaxResult.success(msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回成功消息
|
||||
*
|
||||
* @param msg 返回内容
|
||||
* @param data 数据对象
|
||||
* @return 成功消息
|
||||
*/
|
||||
public static AjaxResult success(String msg, Object data) {
|
||||
return new AjaxResult(200, msg, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回错误消息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static AjaxResult error() {
|
||||
return AjaxResult.error("操作失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回错误消息
|
||||
*
|
||||
* @param msg 返回内容
|
||||
* @return 警告消息
|
||||
*/
|
||||
public static AjaxResult error(String msg) {
|
||||
return AjaxResult.error(msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回错误消息
|
||||
*
|
||||
* @param msg 返回内容
|
||||
* @param data 数据对象
|
||||
* @return 警告消息
|
||||
*/
|
||||
public static AjaxResult error(String msg, Object data) {
|
||||
return new AjaxResult(500, msg, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回错误消息
|
||||
*
|
||||
* @param code 状态码
|
||||
* @param msg 返回内容
|
||||
* @return 警告消息
|
||||
*/
|
||||
public static AjaxResult error(int code, String msg) {
|
||||
return new AjaxResult(code, msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方便链式调用
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 数据对象
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult put(String key, Object value) {
|
||||
super.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static AjaxResult toAjax(int result) {
|
||||
return result > 0 ? AjaxResult.success() : AjaxResult.error();
|
||||
}
|
||||
|
||||
public static AjaxResult rateLimit() {
|
||||
return new AjaxResult(300, "您访问速度过快,系统繁忙!");
|
||||
}
|
||||
|
||||
public static AjaxResult noPermissionResult(String uri) {
|
||||
return new AjaxResult(401, "无 " + uri + " 路径访问权限");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.qiaoba.common.base.util;
|
||||
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 注解Util
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/21 9:53
|
||||
*/
|
||||
public class AnnotationUtil extends cn.hutool.core.annotation.AnnotationUtil {
|
||||
|
||||
public static boolean hasAnnotation(String referenceMethod, Class<? extends Annotation> annotation) {
|
||||
// com.qiaoba.common.base.util.AnnotationUtil.hasAnnotation
|
||||
StringBuilder sb = new StringBuilder(referenceMethod);
|
||||
int index = sb.lastIndexOf(".");
|
||||
// com.qiaoba.common.base.util.AnnotationUtil
|
||||
String clazzName = sb.substring(0, index);
|
||||
// hasAnnotation
|
||||
String methodName = sb.substring(index + 1, sb.length());
|
||||
Class<?> clazz = ClassUtil.loadClass(clazzName);
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (methodName.equals(method.getName())) {
|
||||
return hasAnnotation(method, annotation);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.qiaoba.common.base.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ArrayUtil
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/5/22 16:41
|
||||
*/
|
||||
public class ArrayUtil extends cn.hutool.core.util.ArrayUtil {
|
||||
|
||||
public static boolean containsValueIgnoreCase(CharSequence[] array, CharSequence value) {
|
||||
if (Objects.nonNull(array)) {
|
||||
for (CharSequence charSequence : array) {
|
||||
if (StrUtil.containsIgnoreCase(value, charSequence)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.qiaoba.common.base.util;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.base.enums.DataBaseEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 数据库工具类
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/5/22 16:41
|
||||
*/
|
||||
@Slf4j
|
||||
public class DatabaseUtil {
|
||||
|
||||
/**
|
||||
* 根据数据库类型,处理find_in_set函数
|
||||
*
|
||||
* @param var1 参数1
|
||||
* @param var2 参数2
|
||||
* @return 处理后的sql
|
||||
*/
|
||||
public static String handleFindInSet(Object var1, String var2) {
|
||||
String databaseType = BaseContext.getDatabaseType();
|
||||
log.debug("租户ID: [{}], 数据源类型: {}", BaseContext.getTenantId(), databaseType);
|
||||
String var = Convert.toStr(var1);
|
||||
if (DataBaseEnum.SQL_SERVER.getType().equals(databaseType)) {
|
||||
// charindex(',100,' , ',0,100,101,') <> 0
|
||||
return "charindex('," + var + ",' , ','+" + var2 + "+',') <> 0";
|
||||
} else if (DataBaseEnum.POSTGRE_SQL.getType().equals(databaseType)) {
|
||||
// (select position(',100,' in ',0,100,101,')) <> 0
|
||||
return "(select position('," + var + ",' in ','||" + var2 + "||',')) <> 0";
|
||||
}
|
||||
// find_in_set(100 , '0,100,101')
|
||||
return "find_in_set(" + var + " , " + var2 + ") <> 0";
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.qiaoba.common.base.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 对象工具类
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/5/18 14:59
|
||||
*/
|
||||
public class ObjectUtil {
|
||||
|
||||
/**
|
||||
* 判断对象所有属性是否都为空
|
||||
*
|
||||
* @param obj obj
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isAllFieldNull(Object obj) {
|
||||
// 得到类对象
|
||||
Class clazz = (Class) obj.getClass();
|
||||
//得到属性集合
|
||||
Field[] fs = clazz.getDeclaredFields();
|
||||
boolean flag = true;
|
||||
//遍历属性
|
||||
for (Field f : fs) {
|
||||
try {
|
||||
// 设置属性是可以访问的(私有的也可以)
|
||||
f.setAccessible(true);
|
||||
// 得到此属性的值
|
||||
Object val = f.get(obj);
|
||||
// 只要有1个属性不为空,那么就不是所有的属性值都为空
|
||||
if (val != null) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.qiaoba.common.base.validate;
|
||||
|
||||
/**
|
||||
* 新增-分组校验
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-5-30 10:14:28
|
||||
*/
|
||||
public interface AddGroup {
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.qiaoba.common.base.validate;
|
||||
|
||||
/**
|
||||
* 修改-分组校验
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-5-30 10:14:28
|
||||
*/
|
||||
public interface EditGroup {
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.qiaoba.common.base.validate;
|
||||
|
||||
/**
|
||||
* 查询-分组校验
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-5-30 10:14:28
|
||||
*/
|
||||
public interface QueryGroup {
|
||||
}
|
43
qiaoba-common/qiaoba-common-datasource/pom.xml
Normal file
43
qiaoba-common/qiaoba-common-datasource/pom.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>qiaoba-common</artifactId>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qiaoba-common-datasource</artifactId>
|
||||
|
||||
<description>通用-数据源模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Druid -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- Mysql -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<!-- PgSql -->
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<!-- mybatis plus -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<artifactId>qiaoba-common-redis</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,16 @@
|
||||
package com.qiaoba.common.database.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 只查询一条数据
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/21 10:10
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface SelectOneRow {
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
package com.qiaoba.common.database.config;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.alibaba.druid.pool.DruidPooledConnection;
|
||||
import com.qiaoba.common.base.constant.TenantConstant;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.base.enums.BaseEnum;
|
||||
import com.qiaoba.common.database.context.BackupDatasourceContext;
|
||||
import com.qiaoba.common.database.context.DynamicDataSourceContext;
|
||||
import com.qiaoba.common.database.context.PrimaryDatasourceContext;
|
||||
import com.qiaoba.common.database.context.TenantDbTypeContext;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import com.qiaoba.common.database.monitor.NotOnlineDatasourceMonitor;
|
||||
import com.qiaoba.common.database.properties.DataSourceProperties;
|
||||
import com.qiaoba.common.database.service.DynamicDatasourceService;
|
||||
import com.qiaoba.common.database.util.DatasourceUtil;
|
||||
import com.qiaoba.common.database.util.JdbcUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.annotation.Resource;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 多数据源配置
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/15 0015 下午 16:43
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class DynamicDataSourceConfig {
|
||||
|
||||
/**
|
||||
* Bean 必须叫 dataSource
|
||||
*/
|
||||
@Resource
|
||||
private DynamicDataSourceContext dataSource;
|
||||
@Resource
|
||||
private DataSourceProperties dataSourceProperties;
|
||||
@Resource
|
||||
private DynamicDatasourceService dynamicDatasourceService;
|
||||
|
||||
/**
|
||||
* 数据源加载完成
|
||||
*/
|
||||
public static Boolean COMPLETE_LOAD_DATASOURCE = false;
|
||||
|
||||
/**
|
||||
* 租户 ids
|
||||
*/
|
||||
public static List<String> TENANT_IDS = ListUtil.toList(TenantConstant.DEFAULT_TENANT_ID);
|
||||
|
||||
|
||||
/**
|
||||
* 把DynamicDataSourceContext 纳入容器管理,其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象,并调用freshDataSource方法
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 加载系统默认数据源
|
||||
initDefault();
|
||||
// 加载租户数据源
|
||||
initTenant();
|
||||
COMPLETE_LOAD_DATASOURCE = true;
|
||||
}
|
||||
|
||||
public static DruidDataSource getPrimaryDataSource(String tenantId) {
|
||||
return (DruidDataSource) PrimaryDatasourceContext.get(tenantId);
|
||||
}
|
||||
|
||||
private void initDefault() {
|
||||
List<DynamicDataSource> dataSources = dataSourceProperties.getDataSources();
|
||||
for (int i = 0; i < dataSources.size(); i++) {
|
||||
// 0索引作为主数据源
|
||||
Object dataSource = DatasourceUtil.buildDataSource(TenantConstant.DEFAULT_TENANT_ID, dataSources.get(i));
|
||||
if (Objects.isNull(dataSource)) {
|
||||
// 默认的主数据源挂了
|
||||
// 加入到错误数据源Map 等待重试
|
||||
NotOnlineDatasourceMonitor.addErrorDatasource(TenantConstant.DEFAULT_TENANT_ID, dataSources.get(i));
|
||||
dataSources.remove(i);
|
||||
} else {
|
||||
addPrimaryMap(TenantConstant.DEFAULT_TENANT_ID, dataSource);
|
||||
dataSources.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (CollUtil.isEmpty(PrimaryDatasourceContext.getAll())) {
|
||||
log.error("主系统配置数据源全部无效, 请检查 yml 中相关配置");
|
||||
}
|
||||
// 其他数据源备用
|
||||
addBackupMap(TenantConstant.DEFAULT_TENANT_ID, dataSources);
|
||||
// 刷新数据源
|
||||
this.dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
|
||||
}
|
||||
|
||||
private void initTenant() {
|
||||
setDefaultSetting();
|
||||
Map<String, List<DynamicDataSource>> tenantDatasource = dynamicDatasourceService.loadAllTenantDatasource();
|
||||
for (String tenantId : tenantDatasource.keySet()) {
|
||||
List<DynamicDataSource> dataSources = tenantDatasource.get(tenantId);
|
||||
for (int i = 0; i < dataSources.size(); i++) {
|
||||
DynamicDataSource dynamicDataSource = dataSources.get(i);
|
||||
if (BaseEnum.YES.getCode().equals(dynamicDataSource.getIsPrimary())) {
|
||||
Object dataSource = DatasourceUtil.buildDataSource(dataSources.get(i).getTenantId(), dataSources.get(i));
|
||||
if (Objects.isNull(dataSource)) {
|
||||
// 默认的主数据源挂了
|
||||
// 加入到错误数据源Map 等待重试
|
||||
NotOnlineDatasourceMonitor.addErrorDatasource(tenantId, dataSources.get(i));
|
||||
// 在数据源集合中删除, 防止将错误的数据源加载到备用数据源中
|
||||
dataSources.remove(i);
|
||||
} else {
|
||||
addPrimaryMap(tenantId, dataSource);
|
||||
// 去除主要数据源,剩下皆为备用数据源
|
||||
dataSources.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 备用数据源
|
||||
addBackupMap(tenantId, dataSources);
|
||||
TENANT_IDS.add(tenantId);
|
||||
}
|
||||
BaseContext.clearAllHolder();
|
||||
// 刷新数据源
|
||||
dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 程序关闭后,要释放数据源连接池
|
||||
*/
|
||||
@PreDestroy
|
||||
public void close() {
|
||||
Set<Map.Entry<Object, Object>> entries = PrimaryDatasourceContext.getAll().entrySet();
|
||||
for (Map.Entry<Object, Object> entry : entries) {
|
||||
DruidDataSource dataSource = (DruidDataSource) entry.getValue();
|
||||
IoUtil.close(dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
private void addPrimaryMap(String tenantId, Object dataSource) {
|
||||
if (Objects.nonNull(dataSource)) {
|
||||
try {
|
||||
PrimaryDatasourceContext.set(tenantId, dataSource);
|
||||
// 将数据源的类型保存
|
||||
DruidPooledConnection connection = ((DruidDataSource) dataSource).getConnection();
|
||||
TenantDbTypeContext.set(tenantId, connection.getMetaData().getDatabaseProductName());
|
||||
// 归还 connection
|
||||
IoUtil.close(connection);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void addBackupMap(String tenantId, List<DynamicDataSource> dataSources) {
|
||||
if (CollUtil.isNotEmpty(dataSources)) {
|
||||
BackupDatasourceContext.set(tenantId, dataSources);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDefaultSetting() {
|
||||
BaseContext.setDataSource(TenantConstant.DEFAULT_TENANT_ID);
|
||||
BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
|
||||
BaseContext.setDatabaseType(TenantDbTypeContext.getDefault());
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.qiaoba.common.database.config;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
|
||||
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.database.interceptor.SchemaInterceptor;
|
||||
import com.qiaoba.common.database.interceptor.SelectOneRowInterceptor;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.StringValue;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MybatisPlusConfig
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/5/8 11:18
|
||||
*/
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
/**
|
||||
* 不需要拼接租户ID的表 租户表/租户数据源表
|
||||
*/
|
||||
private static final List<String> IGNORE_TABLES = ListUtil.toList("sys_tenant", "sys_tenant_datasource", "generator_table", "generator_table_column");
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new SchemaInterceptor());
|
||||
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
return new StringValue(BaseContext.getTenantId());
|
||||
}
|
||||
|
||||
// 不拼接租户ID
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
return IGNORE_TABLES.contains(tableName);
|
||||
}
|
||||
}));
|
||||
interceptor.addInnerInterceptor(new SelectOneRowInterceptor());
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用网卡信息绑定雪花生成器
|
||||
* 防止集群雪花ID重复
|
||||
*/
|
||||
@Bean
|
||||
public IdentifierGenerator idGenerator() {
|
||||
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package com.qiaoba.common.database.context;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 备用(未在使用)数据源
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/28 10:36
|
||||
*/
|
||||
public class BackupDatasourceContext {
|
||||
|
||||
/**
|
||||
* 备用数据源
|
||||
*/
|
||||
private static Map<String, List<DynamicDataSource>> BACKUP_DATASOURCE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* 获取租户备用数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 数据源集合
|
||||
*/
|
||||
public static List<DynamicDataSource> get(String tenantId) {
|
||||
return BACKUP_DATASOURCE_MAP.get(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置租户备用数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param list 数据源集合
|
||||
*/
|
||||
public static void set(String tenantId, List<DynamicDataSource> list) {
|
||||
BACKUP_DATASOURCE_MAP.put(tenantId, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加租户备用数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param dataSource 数据源
|
||||
*/
|
||||
public static void addBackupMap(String tenantId, DynamicDataSource dataSource) {
|
||||
List<DynamicDataSource> dataSourceList = get(tenantId);
|
||||
if (CollUtil.isEmpty(dataSourceList)) {
|
||||
set(tenantId, ListUtil.toList(dataSource));
|
||||
} else {
|
||||
dataSourceList.add(dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除租户备用数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param dataSourceId 数据源ID
|
||||
*/
|
||||
public static void delBackupMap(String tenantId, String dataSourceId) {
|
||||
List<DynamicDataSource> dataSourceList = get(tenantId);
|
||||
if (CollUtil.isEmpty(dataSourceList)) {
|
||||
for (DynamicDataSource dynamicDataSource : dataSourceList) {
|
||||
if (dataSourceId.equals(dynamicDataSource.getDatasourceId())) {
|
||||
dataSourceList.remove(dynamicDataSource);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改租户备用数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param dataSource 数据源
|
||||
*/
|
||||
public static void updateBackupMap(String tenantId, DynamicDataSource dataSource) {
|
||||
List<DynamicDataSource> dataSourceList = get(tenantId);
|
||||
if (CollUtil.isEmpty(dataSourceList)) {
|
||||
for (DynamicDataSource dynamicDataSource : dataSourceList) {
|
||||
if (dataSource.getDatasourceId().equals(dynamicDataSource.getDatasourceId())) {
|
||||
dataSourceList.remove(dynamicDataSource);
|
||||
dataSourceList.add(dataSource);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.qiaoba.common.database.context;
|
||||
|
||||
import com.qiaoba.common.base.constant.TenantConstant;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态数据源上下文对象
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/15 0015 下午 16:43
|
||||
*/
|
||||
public class DynamicDataSourceContext extends AbstractRoutingDataSource {
|
||||
|
||||
|
||||
/**
|
||||
* 设置默认数据源、全部数据源,及刷新
|
||||
*/
|
||||
public void freshDataSource(Map<Object, Object> targetDataSources) {
|
||||
//默认数据源 (determineCurrentLookupKey 如果找不到就使用默认数据源)
|
||||
super.setDefaultTargetDataSource(targetDataSources.get(TenantConstant.DEFAULT_TENANT_ID));
|
||||
//设置全部数据源
|
||||
super.setTargetDataSources(targetDataSources);
|
||||
//刷新(即把targetDataSources刷到resolvedDataSources中去,resolvedDataSources才是我们真正存放数据源的map)
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object determineCurrentLookupKey() {
|
||||
//获取当前指定的数据源
|
||||
return BaseContext.getDataSource();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.qiaoba.common.database.context;
|
||||
|
||||
import com.qiaoba.common.base.constant.TenantConstant;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 主要(正在使用)数据源
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/28 10:36
|
||||
*/
|
||||
public class PrimaryDatasourceContext {
|
||||
|
||||
/**
|
||||
* 主要数据源
|
||||
*/
|
||||
private static Map<Object, Object> PRIMARY_DATASOURCE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
public static Map<Object, Object> getAll() {
|
||||
return PRIMARY_DATASOURCE_MAP;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户主要数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 数据源
|
||||
*/
|
||||
public static Object get(String tenantId) {
|
||||
return PRIMARY_DATASOURCE_MAP.get(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认(主)主要数据源
|
||||
*
|
||||
* @return 数据源
|
||||
*/
|
||||
public static Object getDefault() {
|
||||
return PRIMARY_DATASOURCE_MAP.get(TenantConstant.DEFAULT_TENANT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置租户主要数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param datasource 数据源
|
||||
*/
|
||||
public static void set(String tenantId, Object datasource) {
|
||||
PRIMARY_DATASOURCE_MAP.put(tenantId, datasource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
public static void remove(String tenantId) {
|
||||
PRIMARY_DATASOURCE_MAP.remove(tenantId);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package com.qiaoba.common.database.context;
|
||||
|
||||
import com.qiaoba.common.base.constant.TenantConstant;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 租户数据源类型
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/28 10:25
|
||||
*/
|
||||
public class TenantDbTypeContext {
|
||||
|
||||
/**
|
||||
* 租户数据源类型
|
||||
*/
|
||||
private static Map<String, String> TENANT_DATASOURCE_TYPE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取租户数据源类型
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 数据源类型
|
||||
*/
|
||||
public static String get(String tenantId) {
|
||||
return TENANT_DATASOURCE_TYPE_MAP.get(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置租户数据源类型
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param dbType 数据源类型
|
||||
*/
|
||||
public static void set(String tenantId, String dbType) {
|
||||
TENANT_DATASOURCE_TYPE_MAP.put(tenantId, dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认(主)数据源类型
|
||||
*
|
||||
* @return 数据源类型
|
||||
*/
|
||||
public static String getDefault() {
|
||||
return TENANT_DATASOURCE_TYPE_MAP.get(TenantConstant.DEFAULT_TENANT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认(主)数据源类型
|
||||
*
|
||||
* @param dbType 数据源类型
|
||||
*/
|
||||
public static void set(String dbType) {
|
||||
set(TenantConstant.DEFAULT_TENANT_ID, dbType);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package com.qiaoba.common.database.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 动态数据源实体
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-06-13 20:24:31
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DynamicDataSource {
|
||||
|
||||
/**
|
||||
* 数据源ID
|
||||
*/
|
||||
private String datasourceId;
|
||||
|
||||
/**
|
||||
* 主要数据源
|
||||
*/
|
||||
private String isPrimary;
|
||||
|
||||
/**
|
||||
* 数据库-url
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 数据库-username
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 数据库-password
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 数据库-驱动
|
||||
*/
|
||||
private String driver;
|
||||
|
||||
/**
|
||||
* 连接池-初始化大小
|
||||
*/
|
||||
private Integer initialSize;
|
||||
|
||||
/**
|
||||
* 连接池-最小空闲线程数
|
||||
*/
|
||||
private Integer minIdle;
|
||||
|
||||
/**
|
||||
* 连接池-最大连接池数量
|
||||
*/
|
||||
private Integer maxActive;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.qiaoba.common.database.entity;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 分页查询实体类
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 20:33:43
|
||||
*/
|
||||
@Data
|
||||
public class PageQuery implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 分页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
|
||||
/**
|
||||
* 当前页数
|
||||
*/
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 当前记录起始索引 默认值
|
||||
*/
|
||||
public static final int DEFAULT_PAGE_NUM = 1;
|
||||
|
||||
/**
|
||||
* 每页显示记录数 默认值 默认查全部
|
||||
*/
|
||||
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
|
||||
|
||||
public <T> Page<T> build() {
|
||||
Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
|
||||
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
|
||||
if (pageNum <= 0) {
|
||||
pageNum = DEFAULT_PAGE_NUM;
|
||||
}
|
||||
return new Page<>(pageNum, pageSize);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.qiaoba.common.database.entity;
|
||||
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.qiaoba.common.base.entity.BasePage;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Mybatis-plus 分页封装对象
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-23 15:37:43
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@SuppressWarnings("unchecked")
|
||||
public class TableDataInfo<T> extends BasePage {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static <T> TableDataInfo<T> build(IPage<T> page) {
|
||||
TableDataInfo<T> rspData = new TableDataInfo<>();
|
||||
rspData.setCode(HttpStatus.HTTP_OK);
|
||||
rspData.setMsg("查询成功");
|
||||
rspData.setRows(page.getRecords());
|
||||
rspData.setTotal(page.getTotal());
|
||||
return rspData;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.qiaoba.common.database.factory;
|
||||
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
||||
import com.qiaoba.common.database.context.DynamicDataSourceContext;
|
||||
import com.qiaoba.common.database.properties.DataSourceProperties;
|
||||
import com.qiaoba.common.database.properties.TenantSchema;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* 动态数据源工厂
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/15 0015 下午 16:43
|
||||
*/
|
||||
@Configuration
|
||||
public class DynamicDataSourceFactory {
|
||||
|
||||
@Value("${spring.application.name}")
|
||||
private String schemaPrefix;
|
||||
|
||||
@PostConstruct
|
||||
public void setSchemaPrefix() {
|
||||
TenantSchema.setSchemaPrefix(schemaPrefix);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DynamicDataSourceContext dataSource() {
|
||||
return new DynamicDataSourceContext();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DynamicDataSourceConfig dynamicDataSourceConfig() {
|
||||
return new DynamicDataSourceConfig();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSourceProperties defaultDataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.qiaoba.common.database.interceptor;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.database.context.TenantDbTypeContext;
|
||||
import com.qiaoba.common.database.properties.TenantSchema;
|
||||
import com.qiaoba.common.database.util.DbUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* SchemaInterceptor
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-25 22:48:43
|
||||
*/
|
||||
@Slf4j
|
||||
public class SchemaInterceptor implements InnerInterceptor {
|
||||
|
||||
@Override
|
||||
public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) {
|
||||
|
||||
// SCHEMA 模式
|
||||
if (Objects.nonNull(BaseContext.isSchemaMode()) && BaseContext.isSchemaMode()) {
|
||||
try {
|
||||
DbUtil.setSchema(TenantDbTypeContext.getDefault(), conn, TenantSchema.getSchema(BaseContext.getTenantId()));
|
||||
} catch (SQLException e) {
|
||||
log.error("切换SCHEMA失败, 原因: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.qiaoba.common.database.interceptor;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.DialectFactory;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.DialectModel;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
|
||||
import com.qiaoba.common.base.util.AnnotationUtil;
|
||||
import com.qiaoba.common.database.annotation.SelectOneRow;
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.ParameterMapping;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 查询一条
|
||||
* 因为Oracle 不支持 limit 1 写法, 故调用MP的分页方言处理
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/21 8:53
|
||||
*/
|
||||
public class SelectOneRowInterceptor implements InnerInterceptor {
|
||||
|
||||
@Override
|
||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
|
||||
if (AnnotationUtil.hasAnnotation(ms.getId(), SelectOneRow.class)) {
|
||||
String buildSql = boundSql.getSql();
|
||||
IDialect dialect = DialectFactory.getDialect(JdbcUtils.getDbType(executor));
|
||||
DialectModel model = dialect.buildPaginationSql(buildSql, 0, 1);
|
||||
replaceSql(ms, boundSql, model);
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceSql(MappedStatement ms, BoundSql boundSql, DialectModel model) {
|
||||
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
|
||||
final Configuration configuration = ms.getConfiguration();
|
||||
List<ParameterMapping> mappings = mpBoundSql.parameterMappings();
|
||||
Map<String, Object> additionalParameter = mpBoundSql.additionalParameters();
|
||||
model.consumers(mappings, configuration, additionalParameter);
|
||||
mpBoundSql.sql(model.getDialectSql());
|
||||
mpBoundSql.parameterMappings(mappings);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package com.qiaoba.common.database.monitor;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import com.baomidou.lock.LockInfo;
|
||||
import com.baomidou.lock.LockTemplate;
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
||||
import com.qiaoba.common.database.context.BackupDatasourceContext;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import com.qiaoba.common.database.util.JdbcUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 不在线的数据源监控
|
||||
* 尝试连接,如果连接成功,加入到备用数据源
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/26 10:46
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class NotOnlineDatasourceMonitor {
|
||||
|
||||
/**
|
||||
* 错误数据源(连接中断)
|
||||
* <p>
|
||||
* key: 租户ID
|
||||
* value: 错误数据源 list
|
||||
*/
|
||||
public static Map<String, List<DynamicDataSource>> ERROR_DATASOURCE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private static final String LOCK_KEY = "lock4j:notOnlineDatasourceMonitor";
|
||||
|
||||
@Resource
|
||||
private LockTemplate lockTemplate;
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 10s 运行一次
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 项目启动时加载数据源还未完成
|
||||
if (!DynamicDataSourceConfig.COMPLETE_LOAD_DATASOURCE) {
|
||||
return;
|
||||
}
|
||||
// 没有暂时失联的数据源, 直接结束
|
||||
if (CollUtil.isEmpty(ERROR_DATASOURCE_MAP)) {
|
||||
return;
|
||||
}
|
||||
// expire = -1 锁自动续期, 防止数据源过多或异常等待, 超过默认锁 30s
|
||||
final LockInfo lockInfo = lockTemplate.lock(LOCK_KEY, -1, 1000);
|
||||
//申请锁失败 说明集群中其他设备正在执行监控
|
||||
if (null == lockInfo) {
|
||||
return;
|
||||
}
|
||||
//申请锁成功
|
||||
try {
|
||||
log.trace("开始-[错误数据源重试]-线程, 时间: {}", new Date());
|
||||
tryConnect();
|
||||
log.trace("结束-[错误数据源重试]-线程, 时间: {}", new Date());
|
||||
} finally {
|
||||
// 释放锁
|
||||
lockTemplate.releaseLock(lockInfo);
|
||||
}
|
||||
|
||||
}
|
||||
}, 0, 10 * 1000);
|
||||
}
|
||||
|
||||
private void tryConnect() {
|
||||
Set<String> tenantIds = ERROR_DATASOURCE_MAP.keySet();
|
||||
for (String tenantId : tenantIds) {
|
||||
List<DynamicDataSource> errorDatasourceList = ERROR_DATASOURCE_MAP.get(tenantId);
|
||||
for (int i = 0; i < errorDatasourceList.size(); i++) {
|
||||
DynamicDataSource errorDatasource = errorDatasourceList.get(i);
|
||||
// 说明连接成功
|
||||
boolean check = JdbcUtil.checkConnect(errorDatasource.getDriver(), errorDatasource.getUrl(), errorDatasource.getUsername(), errorDatasource.getPassword());
|
||||
if (check) {
|
||||
log.info("数据源重连成功, Url: {}", errorDatasource.getUrl());
|
||||
// 从errorMap中删除
|
||||
errorDatasourceList.remove(errorDatasource);
|
||||
if (CollUtil.isEmpty(errorDatasourceList)) {
|
||||
ERROR_DATASOURCE_MAP.remove(tenantId);
|
||||
}
|
||||
// 加入到备用Map中
|
||||
BackupDatasourceContext.addBackupMap(tenantId, errorDatasource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void addErrorDatasource(String tenantId, DynamicDataSource dataSource) {
|
||||
List<DynamicDataSource> errorDataSourceList = NotOnlineDatasourceMonitor.ERROR_DATASOURCE_MAP.get(tenantId);
|
||||
if (CollUtil.isEmpty(errorDataSourceList)) {
|
||||
NotOnlineDatasourceMonitor.ERROR_DATASOURCE_MAP.put(tenantId, ListUtil.toList(dataSource));
|
||||
} else {
|
||||
errorDataSourceList.add(dataSource);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
package com.qiaoba.common.database.monitor;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
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.constant.TenantConstant;
|
||||
import com.qiaoba.common.base.enums.DataBaseEnum;
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
||||
import com.qiaoba.common.database.context.BackupDatasourceContext;
|
||||
import com.qiaoba.common.database.context.PrimaryDatasourceContext;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import com.qiaoba.common.database.service.DynamicDatasourceService;
|
||||
import com.qiaoba.common.database.util.DatasourceUtil;
|
||||
import com.qiaoba.common.database.util.JdbcUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 在线的数据源监控
|
||||
* 尝试连接, 如果连接不成功, 替换可用数据源, 将失败数据源加入到错误数据源, 等待重试
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/26 10:46
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class OnlineDatasourceMonitor {
|
||||
|
||||
|
||||
private static final String LOCK_KEY = "lock4j:onlineDatasourceMonitor";
|
||||
private static Map<String, String> WAIT_UPDATE_DATASOURCE_STATUS = new ConcurrentHashMap<>();
|
||||
private static Map<String, List<String>> WAIT_ADD_ERROR_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
@Resource
|
||||
private LockTemplate lockTemplate;
|
||||
@Resource
|
||||
private DynamicDatasourceService dynamicDatasourceService;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 1s 运行一次
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 项目启动时加载数据源还未完成
|
||||
if (!DynamicDataSourceConfig.COMPLETE_LOAD_DATASOURCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// expire = -1 锁自动续期, 防止数据源过多或异常等待, 超过默认锁 30s
|
||||
final LockInfo lockInfo = lockTemplate.lock(LOCK_KEY, -1, 1000);
|
||||
//申请锁失败 说明集群中其他设备正在执行监控
|
||||
if (null == lockInfo) {
|
||||
return;
|
||||
}
|
||||
//申请锁成功
|
||||
try {
|
||||
log.trace("开始运行数据源监控线程, 时间: {}", new Date());
|
||||
tryConnect();
|
||||
log.trace("结束运行数据源监控线程, 时间: {}", new Date());
|
||||
} finally {
|
||||
// 释放锁
|
||||
lockTemplate.releaseLock(lockInfo);
|
||||
}
|
||||
|
||||
}
|
||||
}, 0, 1000);
|
||||
}
|
||||
|
||||
private void tryConnect() {
|
||||
for (String tenantId : DynamicDataSourceConfig.TENANT_IDS) {
|
||||
Object primary = PrimaryDatasourceContext.get(tenantId);
|
||||
if (Objects.isNull(primary)) {
|
||||
// 说明初始化主要数据源的时候出错
|
||||
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
|
||||
// 切换备用数据源为主数据源
|
||||
backToPrimary(tenantId);
|
||||
continue;
|
||||
}
|
||||
|
||||
DruidDataSource dataSource = (DruidDataSource) primary;
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = dataSource.getConnection();
|
||||
if (JdbcUtil.checkConnect(connection)) {
|
||||
// 说明数据源正常
|
||||
log.trace("租户[{}]-目前主数据源正常, 无需切换数据源", tenantId);
|
||||
// 系统默认主数据源 处理任务
|
||||
if (TenantConstant.DEFAULT_TENANT_ID.equals(tenantId)) {
|
||||
handleUpdateDatasourceStatus();
|
||||
handleErrorDatasource();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
|
||||
// 主数据源异常 切换备用数据源
|
||||
if (!backToPrimary(tenantId)) {
|
||||
// 备用切换失败
|
||||
// 关闭原有异常数据源
|
||||
IoUtil.close(dataSource);
|
||||
// 在数据源Map中删除
|
||||
PrimaryDatasourceContext.remove(tenantId);
|
||||
}
|
||||
// 将原有异常数据源加入到错误数据源Map, 等待重试
|
||||
addErrorDatasource(tenantId, dataSource);
|
||||
|
||||
} catch (SQLException e) {
|
||||
|
||||
} finally {
|
||||
IoUtil.close(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换备用数据源为主数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
private Boolean backToPrimary(String tenantId) {
|
||||
// 备用数据源
|
||||
List<DynamicDataSource> dataSources = BackupDatasourceContext.get(tenantId);
|
||||
if (CollUtil.isEmpty(dataSources)) {
|
||||
log.error("租户:[{}]切换备用数据源失败, 原因: 没有备用数据源", tenantId);
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer backupIndex = null;
|
||||
|
||||
for (int i = 0; i < dataSources.size(); i++) {
|
||||
Object dynamicDataSource = DatasourceUtil.buildDataSource(tenantId, dataSources.get(i));
|
||||
// 不是空,说明备用数据源有用
|
||||
if (Objects.nonNull(dynamicDataSource)) {
|
||||
DatasourceUtil.changePrimaryDatasource(tenantId, dynamicDataSource);
|
||||
backupIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Objects.nonNull(backupIndex)) {
|
||||
// 切换成功
|
||||
DynamicDataSource dynamicDataSource = dataSources.get(backupIndex);
|
||||
// 更改数据库中该数据源为主要数据源
|
||||
if (Objects.nonNull(dynamicDataSource.getTenantId()) && !TenantConstant.DEFAULT_TENANT_ID.equals(dynamicDataSource.getTenantId())) {
|
||||
// 添加到待处理任务中
|
||||
WAIT_UPDATE_DATASOURCE_STATUS.put(dynamicDataSource.getTenantId(), dynamicDataSource.getDatasourceId());
|
||||
}
|
||||
// 备用数据源集合删除该数据源
|
||||
dataSources.remove((int) backupIndex);
|
||||
log.info("租户:[{}]切换备用数据源成功, 现主数据ID: {}", tenantId, dynamicDataSource.getDatasourceId());
|
||||
return true;
|
||||
} else {
|
||||
log.error("租户:[{}]切换备用数据源失败, 原因: 备用数据源均无效", tenantId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUpdateDatasourceStatus() {
|
||||
Set<String> keys = WAIT_UPDATE_DATASOURCE_STATUS.keySet();
|
||||
for (String key : keys) {
|
||||
try {
|
||||
log.info("开始更新数据库中租户数据源状态, 租户ID: {}", key);
|
||||
// 防止更新过程中主数据挂了
|
||||
dynamicDatasourceService.changePrimaryDatasource(key, WAIT_UPDATE_DATASOURCE_STATUS.get(key));
|
||||
// 处理完成 删除任务
|
||||
WAIT_UPDATE_DATASOURCE_STATUS.remove(key);
|
||||
log.info("更新数据库中租户数据源状态完成, 租户ID: {}", key);
|
||||
} catch (Exception e) {
|
||||
log.error("更新数据库中租户数据源状态未完成, 租户ID: {}, 失败原因: {}", key, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleErrorDatasource() {
|
||||
Set<String> tenantIds = WAIT_ADD_ERROR_MAP.keySet();
|
||||
for (String tenantId : tenantIds) {
|
||||
List<String> ipList = WAIT_ADD_ERROR_MAP.get(tenantId);
|
||||
for (String ip : ipList) {
|
||||
NotOnlineDatasourceMonitor.addErrorDatasource(tenantId, dynamicDatasourceService.selectByIp(tenantId, ip));
|
||||
}
|
||||
WAIT_ADD_ERROR_MAP.remove(tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
private void addErrorDatasource(String tenantId, DruidDataSource dataSource) {
|
||||
// 主系统
|
||||
if (TenantConstant.DEFAULT_TENANT_ID.equals(tenantId)) {
|
||||
DynamicDataSource dynamicDataSource = new DynamicDataSource();
|
||||
dynamicDataSource.setPassword(dataSource.getPassword());
|
||||
dynamicDataSource.setUsername(dataSource.getUsername());
|
||||
dynamicDataSource.setUrl(dataSource.getUrl());
|
||||
dynamicDataSource.setDriver(dataSource.getDriverClassName());
|
||||
dynamicDataSource.setTenantId(tenantId);
|
||||
dynamicDataSource.setInitialSize(dataSource.getInitialSize());
|
||||
dynamicDataSource.setMaxActive(dataSource.getMaxActive());
|
||||
dynamicDataSource.setMinIdle(dataSource.getMinIdle());
|
||||
NotOnlineDatasourceMonitor.addErrorDatasource(tenantId, dynamicDataSource);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 普通租户
|
||||
String ip = DataBaseEnum.getIp(dataSource.getUrl(), dataSource.getDriverClassName());
|
||||
List<String> errorIpList = WAIT_ADD_ERROR_MAP.get(tenantId);
|
||||
if (CollUtil.isEmpty(errorIpList)) {
|
||||
WAIT_ADD_ERROR_MAP.put(tenantId, CollUtil.toList(ip));
|
||||
} else {
|
||||
errorIpList.add(ip);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.qiaoba.common.database.properties;
|
||||
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
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
|
||||
@EnableConfigurationProperties
|
||||
public class DataSourceProperties {
|
||||
|
||||
private List<DynamicDataSource> dataSources;
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.qiaoba.common.database.properties;
|
||||
|
||||
import com.qiaoba.common.base.constant.BaseConstant;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 租户Schema
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/29 8:53
|
||||
*/
|
||||
@Component
|
||||
public class TenantSchema {
|
||||
|
||||
|
||||
private static String schemaPrefix;
|
||||
|
||||
public static void setSchemaPrefix(String name) {
|
||||
schemaPrefix = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户schema生成逻辑
|
||||
* schemaPrefix + '-' + tenantId
|
||||
* eg: schema = abc-2
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return schema
|
||||
*/
|
||||
public static String getSchema(String tenantId) {
|
||||
return schemaPrefix + BaseConstant.HYPHEN_JOIN_STR + tenantId;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.qiaoba.common.database.service;
|
||||
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态数据源接口
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/9 13:15
|
||||
*/
|
||||
public interface DynamicDatasourceService {
|
||||
|
||||
/**
|
||||
* 加载所有租户的数据源
|
||||
*
|
||||
* @return 数据源集合 key: tenantId | value: list
|
||||
*/
|
||||
Map<String, List<DynamicDataSource>> loadAllTenantDatasource();
|
||||
|
||||
/**
|
||||
* 改变主数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param datasourceId 数据源ID
|
||||
*/
|
||||
void changePrimaryDatasource(String tenantId, String datasourceId);
|
||||
|
||||
/**
|
||||
* 通过IP查询
|
||||
*
|
||||
* @param tenantId tenantId
|
||||
* @param ip ip
|
||||
* @return obj
|
||||
*/
|
||||
DynamicDataSource selectByIp(String tenantId, String ip);
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package com.qiaoba.common.database.util;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.alibaba.druid.pool.DruidPooledConnection;
|
||||
import com.qiaoba.common.database.context.DynamicDataSourceContext;
|
||||
import com.qiaoba.common.database.context.PrimaryDatasourceContext;
|
||||
import com.qiaoba.common.database.context.TenantDbTypeContext;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* DatasourceUtil
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/7/13 0013 下午 16:20
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DatasourceUtil {
|
||||
|
||||
@Resource
|
||||
private DynamicDataSourceContext dataSource;
|
||||
|
||||
private static DynamicDataSourceContext dynamicDataSourceContext;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
dynamicDataSourceContext = dataSource;
|
||||
}
|
||||
|
||||
public static void changePrimaryDatasource(String tenantId, Object datasource) {
|
||||
PrimaryDatasourceContext.set(tenantId, datasource);
|
||||
// 将数据源的类型保存
|
||||
DruidPooledConnection connection = null;
|
||||
try {
|
||||
connection = ((DruidDataSource) datasource).getConnection();
|
||||
TenantDbTypeContext.set(tenantId, connection.getMetaData().getDatabaseProductName());
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
// 归还 connection
|
||||
IoUtil.close(connection);
|
||||
}
|
||||
dynamicDataSourceContext.freshDataSource(PrimaryDatasourceContext.getAll());
|
||||
}
|
||||
|
||||
public static Object buildDataSource(String tenantId, DynamicDataSource dynamicDataSource) {
|
||||
log.debug("正在创建数据源DataSource, 租户: {}", tenantId);
|
||||
boolean connect = JdbcUtil.checkConnect(dynamicDataSource.getDriver(), dynamicDataSource.getUrl(), dynamicDataSource.getUsername(), dynamicDataSource.getPassword());
|
||||
if (!connect) {
|
||||
log.error("租户: {} 数据源连接失败, Url: {}", tenantId, dynamicDataSource.getUrl());
|
||||
return null;
|
||||
}
|
||||
DruidDataSource dataSource = new DruidDataSource();
|
||||
dataSource.setUrl(dynamicDataSource.getUrl());
|
||||
dataSource.setUsername(dynamicDataSource.getUsername());
|
||||
dataSource.setPassword(dynamicDataSource.getPassword());
|
||||
dataSource.setDriverClassName(dynamicDataSource.getDriver());
|
||||
dataSource.setInitialSize(dynamicDataSource.getInitialSize());
|
||||
dataSource.setMinIdle(dynamicDataSource.getMinIdle());
|
||||
dataSource.setMaxActive(dynamicDataSource.getMaxActive());
|
||||
|
||||
try {
|
||||
dataSource.addFilters("stat");
|
||||
// wall 防火墙 切勿开启, 开启后 导入SQL 会失败
|
||||
// dataSource.addFilters("wall")
|
||||
// 初始化数据源
|
||||
dataSource.init();
|
||||
return dataSource;
|
||||
} catch (Exception e) {
|
||||
IoUtil.close(dataSource);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package com.qiaoba.common.database.util;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.qiaoba.common.base.enums.DataBaseEnum;
|
||||
import org.apache.ibatis.jdbc.ScriptRunner;
|
||||
import org.postgresql.jdbc.PgConnection;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/28 8:52
|
||||
*/
|
||||
public class DbUtil {
|
||||
|
||||
/**
|
||||
* PgSQL 查询 schema 是否存在
|
||||
*/
|
||||
private static final String PGSQL_CHECK_SCHEMA_EXIST_SQL = "SELECT count(1) FROM information_schema.schemata WHERE schema_name = '{}'";
|
||||
|
||||
|
||||
public static void setSchema(String dbType, Connection conn, String schema) throws SQLException {
|
||||
|
||||
if (DataBaseEnum.MY_SQL.getType().equals(dbType)) {
|
||||
conn.setSchema(schema);
|
||||
} else if (DataBaseEnum.POSTGRE_SQL.getType().equals(dbType)) {
|
||||
conn.unwrap(PgConnection.class).setSchema(schema);
|
||||
} else if (DataBaseEnum.SQL_SERVER.getType().equals(dbType)) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean checkSchema(String dbType, Connection conn, String schema) throws Exception {
|
||||
if (DataBaseEnum.POSTGRE_SQL.getType().equals(dbType)) {
|
||||
Statement statement = null;
|
||||
try {
|
||||
statement = conn.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(StrUtil.format(PGSQL_CHECK_SCHEMA_EXIST_SQL, schema));
|
||||
resultSet.next();
|
||||
return resultSet.getInt(1) > 0;
|
||||
|
||||
} finally {
|
||||
IoUtil.close(statement);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void runScript(Connection conn, String filePath) throws IOException {
|
||||
ClassPathResource resource = new ClassPathResource(filePath);
|
||||
File file = resource.getFile();
|
||||
FileReader reader = new FileReader(file);
|
||||
ScriptRunner scriptRunner = new ScriptRunner(conn);
|
||||
scriptRunner.setSendFullScript(true);
|
||||
scriptRunner.runScript(reader);
|
||||
reader.close();
|
||||
}
|
||||
|
||||
public static void runSql(Connection conn, String sql) throws SQLException {
|
||||
Statement statement = null;
|
||||
try {
|
||||
statement = conn.createStatement();
|
||||
statement.execute(sql);
|
||||
} finally {
|
||||
IoUtil.close(statement);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.qiaoba.common.database.util;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.qiaoba.common.base.enums.DataBaseEnum;
|
||||
import com.qiaoba.common.base.exception.ServiceException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* JdbcUtil
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/15 0015 下午 16:43
|
||||
*/
|
||||
@Slf4j
|
||||
public class JdbcUtil {
|
||||
|
||||
/**
|
||||
* 检查数据源是否可以连接
|
||||
*
|
||||
* @param driver 数据库驱动
|
||||
* @param url url
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @return true = 是
|
||||
*/
|
||||
public static boolean checkConnect(String driver, String url, String username, String password) {
|
||||
Connection conn = null;
|
||||
try {
|
||||
Class.forName(driver);
|
||||
//建立连接
|
||||
conn = DriverManager.getConnection(url, username, password);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.debug(e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
IoUtil.close(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据源连接可用性
|
||||
*
|
||||
* @param conn 数据源
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean checkConnect(Connection conn) {
|
||||
Statement stmt = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
// 允许 2s 延时
|
||||
stmt.setQueryTimeout(2);
|
||||
rs = stmt.executeQuery(DataBaseEnum.getCheckSql(conn.getMetaData().getDatabaseProductName()));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} finally {
|
||||
IoUtil.close(rs);
|
||||
IoUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
public static Connection getConnection(String driver, String url, String username, String password) {
|
||||
try {
|
||||
Class.forName(driver);
|
||||
return DriverManager.getConnection(url, username, password);
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException(StrUtil.format("数据源连接失败,错误: {}", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public static Connection connection(String driver, String url, String username, String password) throws Exception {
|
||||
Class.forName(driver);
|
||||
return DriverManager.getConnection(url, username, password);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
{
|
||||
"groups": [],
|
||||
"properties": [
|
||||
{
|
||||
"name": "qiaoba.dataSources",
|
||||
"type": "java.util.List<com.qiaoba.common.database.entity.DynamicDataSource>",
|
||||
"sourceType": "com.qiaoba.common.database.properties.DataSourceProperties"
|
||||
},
|
||||
{
|
||||
"name": "qiaoba.datasources.driver",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "com.qiaoba.common.database.properties.DataSourceProperties"
|
||||
},
|
||||
{
|
||||
"name": "qiaoba.datasources.url",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "com.qiaoba.common.database.properties.DataSourceProperties"
|
||||
},
|
||||
{
|
||||
"name": "qiaoba.datasources.username",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "com.qiaoba.common.database.properties.DataSourceProperties"
|
||||
},
|
||||
{
|
||||
"name": "qiaoba.datasources.password",
|
||||
"type": "java.lang.String",
|
||||
"sourceType": "com.qiaoba.common.database.properties.DataSourceProperties"
|
||||
},
|
||||
{
|
||||
"name": "qiaoba.datasources.initial-size",
|
||||
"type": "java.lang.Integer",
|
||||
"sourceType": "com.qiaoba.common.database.properties.DataSourceProperties"
|
||||
},
|
||||
{
|
||||
"name": "qiaoba.datasources.min-idle",
|
||||
"type": "java.lang.Integer",
|
||||
"sourceType": "com.qiaoba.common.database.properties.DataSourceProperties"
|
||||
},
|
||||
{
|
||||
"name": "qiaoba.datasources.max-active",
|
||||
"type": "java.lang.Integer",
|
||||
"sourceType": "com.qiaoba.common.database.properties.DataSourceProperties"
|
||||
}
|
||||
],
|
||||
"hints": []
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.qiaoba.common.database.factory.DynamicDataSourceFactory,\
|
||||
com.qiaoba.common.database.util.DatasourceUtil,\
|
||||
com.qiaoba.common.database.config.MybatisPlusConfig
|
26
qiaoba-common/qiaoba-common-doc/pom.xml
Normal file
26
qiaoba-common/qiaoba-common-doc/pom.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>qiaoba-common</artifactId>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qiaoba-common-doc</artifactId>
|
||||
|
||||
<description>通用-文档(Knife4j)模块</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
1
qiaoba-common/qiaoba-common-doc/src/main/java/.gitkeep
Normal file
1
qiaoba-common/qiaoba-common-doc/src/main/java/.gitkeep
Normal file
@ -0,0 +1 @@
|
||||
null not found
|
@ -0,0 +1 @@
|
||||
null not found
|
29
qiaoba-common/qiaoba-common-poi/pom.xml
Normal file
29
qiaoba-common/qiaoba-common-poi/pom.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>qiaoba-common</artifactId>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qiaoba-common-poi</artifactId>
|
||||
|
||||
<description>通用-poi(Excel, Word, Pdf等)模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- easypoi -->
|
||||
<dependency>
|
||||
<groupId>cn.afterturn</groupId>
|
||||
<artifactId>easypoi-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- Java Servlet -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,39 @@
|
||||
package com.qiaoba.common.poi.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Excel导出设置对象
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/25 0025 上午 9:15
|
||||
*/
|
||||
@Data
|
||||
public class ExcelSetting implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String EXCEL_FIELD_SHOW_FLAG = "1";
|
||||
|
||||
private Long id;
|
||||
|
||||
private String className;
|
||||
|
||||
private String fieldId;
|
||||
|
||||
private String fieldName;
|
||||
|
||||
private String width;
|
||||
|
||||
private Integer sort;
|
||||
|
||||
private String show;
|
||||
|
||||
private String dateFormat;
|
||||
|
||||
private String replace;
|
||||
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package com.qiaoba.common.poi.util;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.ExcelImportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.ImportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.qiaoba.common.poi.model.ExcelSetting;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Excel 工具类
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/25 0025 上午 9:15
|
||||
*/
|
||||
public class ExcelUtil {
|
||||
|
||||
/**
|
||||
* 默认导出
|
||||
*
|
||||
* @param list 数据
|
||||
* @param pojoClass java类
|
||||
* @param fileName 文件名
|
||||
* @param response response
|
||||
*/
|
||||
public static void exportExcel(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response) {
|
||||
defaultExport(list, pojoClass, fileName, new ExportParams(fileName, fileName, ExcelType.XSSF), response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义导出
|
||||
*
|
||||
* @param list 数据
|
||||
* @param entities 字段设置
|
||||
* @param fileName 文件名
|
||||
* @param response response
|
||||
*/
|
||||
public static void exportExcel(List<?> list, List<ExcelExportEntity> entities, String fileName, HttpServletResponse response) {
|
||||
exportBySettings(list, entities, fileName, new ExportParams(fileName, fileName, ExcelType.XSSF), response);
|
||||
}
|
||||
|
||||
private static void exportBySettings(List<?> list, List<ExcelExportEntity> entities, String fileName, ExportParams exportParams, HttpServletResponse response) {
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list);
|
||||
downLoadExcel(fileName, response, workbook);
|
||||
}
|
||||
|
||||
private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, ExportParams exportParams, HttpServletResponse response) {
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
|
||||
downLoadExcel(fileName, response, workbook);
|
||||
}
|
||||
|
||||
private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) {
|
||||
try {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setHeader("content-Type", "application/vnd.ms-excel");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));
|
||||
workbook.write(response.getOutputStream());
|
||||
IOUtils.closeQuietly(workbook);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> importExcel(MultipartFile file, Class<T> pojoClass) throws IOException {
|
||||
return importExcel(file.getInputStream(), 1, 1, pojoClass);
|
||||
}
|
||||
|
||||
private static <T> List<T> importExcel(InputStream inputStream, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws IOException {
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
ImportParams params = new ImportParams();
|
||||
params.setTitleRows(titleRows);
|
||||
params.setHeadRows(headerRows);
|
||||
params.setSaveUrl("/excel/");
|
||||
try {
|
||||
return ExcelImportUtil.importExcel(inputStream, pojoClass, params);
|
||||
} catch (NoSuchElementException e) {
|
||||
throw new IOException("excel文件不能为空");
|
||||
} catch (Exception e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ExcelExportEntity> createExcelEntry(List<ExcelSetting> settings) {
|
||||
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||
for (ExcelSetting setting : settings) {
|
||||
// 去除隐藏
|
||||
if (!ExcelSetting.EXCEL_FIELD_SHOW_FLAG.equals(setting.getShow())) {
|
||||
continue;
|
||||
}
|
||||
ExcelExportEntity entity = new ExcelExportEntity(setting.getFieldName(), setting.getFieldId());
|
||||
entity.setWidth(Double.valueOf(setting.getWidth()));
|
||||
String replace = setting.getReplace();
|
||||
if (StrUtil.isNotBlank(replace) && replace.contains(",")) {
|
||||
entity.setReplace(replace.split(","));
|
||||
}
|
||||
entity.setFormat(setting.getDateFormat());
|
||||
entity.setOrderNum(setting.getSort());
|
||||
entities.add(entity);
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
}
|
58
qiaoba-common/qiaoba-common-redis/pom.xml
Normal file
58
qiaoba-common/qiaoba-common-redis/pom.xml
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>qiaoba-common</artifactId>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qiaoba-common-redis</artifactId>
|
||||
|
||||
<description>通用-Redis模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- lock4j -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-data-30</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--redisson-->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-data-30</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-data-27</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<artifactId>qiaoba-common-base</artifactId>
|
||||
</dependency>
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,71 @@
|
||||
package com.qiaoba.common.redis.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Redis 基础配置
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/2 0003 下午 17:44
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureBefore(RedisAutoConfiguration.class)
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisSerializer<Object> serializer = redisSerializer();
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setValueSerializer(serializer);
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashValueSerializer(serializer);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisSerializer<Object> redisSerializer() {
|
||||
//创建JSON序列化器
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
//实体类存在子类继承父类,必须设置
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
//必须设置,否则无法将JSON转化为对象,会转化成Map类型
|
||||
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
serializer.setObjectMapper(objectMapper);
|
||||
return serializer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
|
||||
//设置Redis缓存有效期为1天
|
||||
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1));
|
||||
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,403 @@
|
||||
package com.qiaoba.common.redis.service;
|
||||
|
||||
import com.qiaoba.common.base.entity.BasePage;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 自定义Redis接口
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/2 0002 下午 17:41
|
||||
*/
|
||||
public interface RedisService {
|
||||
|
||||
/**
|
||||
* 保存为 Set
|
||||
*
|
||||
* @param key key
|
||||
* @param value value
|
||||
* @param time 存活时间 单位秒
|
||||
*/
|
||||
void set(String key, Object value, long time);
|
||||
|
||||
/**
|
||||
* 保存为 Set, 永久
|
||||
*
|
||||
* @param key key
|
||||
* @param value value
|
||||
*/
|
||||
void set(String key, Object value);
|
||||
|
||||
/**
|
||||
* 通过 key 获取值
|
||||
*
|
||||
* @param key key
|
||||
* @return Object
|
||||
*/
|
||||
Object get(String key);
|
||||
|
||||
/**
|
||||
* 删除 key
|
||||
*
|
||||
* @param key key
|
||||
* @return Boolean
|
||||
*/
|
||||
Boolean del(String key);
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param keys keys
|
||||
*/
|
||||
void del(Collection<String> keys);
|
||||
|
||||
|
||||
/**
|
||||
* 设置过期时间
|
||||
*
|
||||
* @param key key
|
||||
* @param time 存活时间 单位秒
|
||||
* @return Boolean
|
||||
*/
|
||||
Boolean expire(String key, long time);
|
||||
|
||||
/**
|
||||
* 获取过期时间
|
||||
*
|
||||
* @param key key
|
||||
* @return long 单位秒
|
||||
*/
|
||||
Long getExpire(String key);
|
||||
|
||||
/**
|
||||
* 判断是否有该属性
|
||||
*
|
||||
* @param key key
|
||||
* @return Boolean
|
||||
*/
|
||||
Boolean hasKey(String key);
|
||||
|
||||
/**
|
||||
* 按 delta 递增
|
||||
* <p>
|
||||
* value = value + delta
|
||||
*
|
||||
* @param key key
|
||||
* @param delta 值
|
||||
* @return Long 操作后的值
|
||||
*/
|
||||
Long incr(String key, long delta);
|
||||
|
||||
/**
|
||||
* 按 delta 递减
|
||||
* <p>
|
||||
* value = value - delta
|
||||
*
|
||||
* @param key key
|
||||
* @param delta 值
|
||||
* @return Long 操作后的值
|
||||
*/
|
||||
Long decr(String key, long delta);
|
||||
|
||||
/**
|
||||
* 获取Hash结构中的属性
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @return Object
|
||||
*/
|
||||
Object hGet(String key, String hashKey);
|
||||
|
||||
/**
|
||||
* 向Hash结构中放入一个属性
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @param value value
|
||||
* @param time 存活时间 单位秒
|
||||
* @return Boolean
|
||||
*/
|
||||
Boolean hSet(String key, String hashKey, Object value, long time);
|
||||
|
||||
/**
|
||||
* 向Hash结构中放入一个属性 永久
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @param value value
|
||||
*/
|
||||
void hSet(String key, String hashKey, Object value);
|
||||
|
||||
/**
|
||||
* 直接获取整个Hash结构
|
||||
*
|
||||
* @param key key
|
||||
* @return Map<Object, Object>
|
||||
*/
|
||||
Map<Object, Object> hGetAll(String key);
|
||||
|
||||
/**
|
||||
* 获取整个hash结构的大小
|
||||
*
|
||||
* @param key key
|
||||
* @return size
|
||||
*/
|
||||
Long hSize(String key);
|
||||
|
||||
/**
|
||||
* 直接设置整个Hash结构
|
||||
*
|
||||
* @param key key
|
||||
* @param map map
|
||||
* @param time 存活时间 单位秒
|
||||
* @return Boolean
|
||||
*/
|
||||
Boolean hSetAll(String key, Map<String, Object> map, long time);
|
||||
|
||||
/**
|
||||
* 直接设置整个Hash结构
|
||||
*
|
||||
* @param key key
|
||||
* @param map map
|
||||
*/
|
||||
void hSetAll(String key, Map<String, ?> map);
|
||||
|
||||
/**
|
||||
* 删除Hash结构中的属性
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
*/
|
||||
void hDel(String key, Object... hashKey);
|
||||
|
||||
/**
|
||||
* 判断Hash结构中是否有该属性
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @return Boolean
|
||||
*/
|
||||
Boolean hHasKey(String key, String hashKey);
|
||||
|
||||
/**
|
||||
* Hash结构中属性递增
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @param delta 数值
|
||||
* @return 操作后最终的值
|
||||
*/
|
||||
Long hIncr(String key, String hashKey, Long delta);
|
||||
|
||||
/**
|
||||
* Hash结构中属性递减
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @param delta 数值
|
||||
* @return 操作后最终的值
|
||||
*/
|
||||
Long hDecr(String key, String hashKey, Long delta);
|
||||
|
||||
/**
|
||||
* 获取Set结构
|
||||
*
|
||||
* @param key key
|
||||
* @return Set<Object>
|
||||
*/
|
||||
Set<Object> sMembers(String key);
|
||||
|
||||
/**
|
||||
* 向Set结构中添加属性
|
||||
*
|
||||
* @param key key
|
||||
* @param values values
|
||||
* @return 被添加到集合中的新元素的数量,不包括被忽略的元素
|
||||
*/
|
||||
Long sAdd(String key, Object... values);
|
||||
|
||||
/**
|
||||
* 向Set结构中添加属性
|
||||
*
|
||||
* @param key key
|
||||
* @param time 存活时间 单位秒
|
||||
* @param values values
|
||||
* @return 被添加到集合中的新元素的数量,不包括被忽略的元素
|
||||
*/
|
||||
Long sAdd(String key, long time, Object... values);
|
||||
|
||||
/**
|
||||
* 是否为Set中的属性
|
||||
*
|
||||
* @param key key
|
||||
* @param value value
|
||||
* @return Boolean
|
||||
*/
|
||||
Boolean sIsMember(String key, Object value);
|
||||
|
||||
/**
|
||||
* 获取Set结构的长度
|
||||
*
|
||||
* @param key key
|
||||
* @return Set结构的长度
|
||||
*/
|
||||
Long sSize(String key);
|
||||
|
||||
/**
|
||||
* 删除Set结构中的属性
|
||||
*
|
||||
* @param key key
|
||||
* @param values values
|
||||
* @return 被成功移除的元素的数量,不包括被忽略的元素
|
||||
*/
|
||||
Long sRemove(String key, Object... values);
|
||||
|
||||
/**
|
||||
* 获取List结构中的属性
|
||||
*
|
||||
* @param key key
|
||||
* @param start 开始索引
|
||||
* @param end 结束索引
|
||||
* @return List<Object>
|
||||
*/
|
||||
List<Object> lRange(String key, long start, long end);
|
||||
|
||||
/**
|
||||
* 获取List结构的长度
|
||||
*
|
||||
* @param key key
|
||||
* @return list.size
|
||||
*/
|
||||
Long lSize(String key);
|
||||
|
||||
/**
|
||||
* 根据索引获取List中的属性
|
||||
*
|
||||
* @param key key
|
||||
* @param index 索引
|
||||
* @return Object
|
||||
*/
|
||||
Object lIndex(String key, long index);
|
||||
|
||||
/**
|
||||
* 向List结构中添加属性
|
||||
*
|
||||
* @param key key
|
||||
* @param value value
|
||||
* @return 执行命令后,列表的长度
|
||||
*/
|
||||
Long lPush(String key, Object value);
|
||||
|
||||
/**
|
||||
* 向List结构中添加属性
|
||||
*
|
||||
* @param key key
|
||||
* @param value value
|
||||
* @param time 存活时间 单位秒
|
||||
* @return 执行命令后,列表的长度
|
||||
*/
|
||||
Long lPush(String key, Object value, long time);
|
||||
|
||||
/**
|
||||
* 向List结构中批量添加属性
|
||||
*
|
||||
* @param key key
|
||||
* @param values values
|
||||
* @return 执行命令后,列表的长度
|
||||
*/
|
||||
Long lPushAll(String key, Object... values);
|
||||
|
||||
/**
|
||||
* 向List结构中批量添加属性
|
||||
*
|
||||
* @param key key
|
||||
* @param time 存活时间 单位秒
|
||||
* @param values values
|
||||
* @return 执行命令后,列表的长度
|
||||
*/
|
||||
Long lPushAll(String key, Long time, Object... values);
|
||||
|
||||
/**
|
||||
* 从List结构中移除属性
|
||||
*
|
||||
* @param key key
|
||||
* @param count 数量
|
||||
* count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
|
||||
* count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
|
||||
* count = 0 : 移除表中所有与 VALUE 相等的值。
|
||||
* @param value value
|
||||
* @return 被移除元素的数量
|
||||
*/
|
||||
Long lRemove(String key, long count, Object value);
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param key key
|
||||
* @param pageNumber pageNumber
|
||||
* @param pageSize pageSize
|
||||
* @return // List<clazz>
|
||||
*/
|
||||
BasePage getPageList(String key, long pageNumber, long pageSize);
|
||||
|
||||
/**
|
||||
* 模糊查询所有key
|
||||
*
|
||||
* @param key key
|
||||
* @return keys
|
||||
*/
|
||||
Collection<String> getKeys(String key);
|
||||
|
||||
/**
|
||||
* get object
|
||||
*
|
||||
* @param key key
|
||||
* @param clazz clazz
|
||||
* @param <T> T
|
||||
* @return clazz
|
||||
*/
|
||||
<T> T getObject(String key, Class<T> clazz);
|
||||
|
||||
|
||||
/**
|
||||
* get list
|
||||
*
|
||||
* @param key key
|
||||
* @param clazz clazz
|
||||
* @param <T> T
|
||||
* @return // List<clazz>
|
||||
*/
|
||||
<T> List<T> getObjectList(String key, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* hGetObject
|
||||
*
|
||||
* @param key key
|
||||
* @param hashKey hashKey
|
||||
* @param clazz clazz
|
||||
* @param <T> T
|
||||
* @return clazz
|
||||
*/
|
||||
<T> T hGetObject(String key, String hashKey, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* key添加租户前缀
|
||||
*
|
||||
* @param key key
|
||||
* @return newKey
|
||||
*/
|
||||
String addTenantPrefix(String key);
|
||||
|
||||
/**
|
||||
* key去除租户前缀
|
||||
*
|
||||
* @param key key
|
||||
* @return newKey
|
||||
*/
|
||||
String removeTenantPrefix(String key);
|
||||
}
|
@ -0,0 +1,276 @@
|
||||
package com.qiaoba.common.redis.service.impl;
|
||||
|
||||
|
||||
import com.qiaoba.common.base.constant.BaseConstant;
|
||||
import com.qiaoba.common.base.constant.TenantConstant;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
import com.qiaoba.common.base.entity.BasePage;
|
||||
import com.qiaoba.common.redis.service.RedisService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 自定义Redis接口实现类
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/2 0003 下午 17:44
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class RedisServiceImpl implements RedisService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Override
|
||||
public void set(String key, Object value, long time) {
|
||||
redisTemplate.opsForValue().set(addTenantPrefix(key), value, time, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String key, Object value) {
|
||||
redisTemplate.opsForValue().set(addTenantPrefix(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String key) {
|
||||
return redisTemplate.opsForValue().get(addTenantPrefix(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean del(String key) {
|
||||
return redisTemplate.delete(addTenantPrefix(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void del(Collection<String> keys) {
|
||||
List<String> list = keys.stream().map(key -> key = addTenantPrefix(key)).collect(Collectors.toList());
|
||||
redisTemplate.delete(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean expire(String key, long time) {
|
||||
return redisTemplate.expire(key, time, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getExpire(String key) {
|
||||
return redisTemplate.getExpire(addTenantPrefix(key), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean hasKey(String key) {
|
||||
return redisTemplate.hasKey(addTenantPrefix(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long incr(String key, long delta) {
|
||||
return redisTemplate.opsForValue().increment(addTenantPrefix(key), delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long decr(String key, long delta) {
|
||||
return redisTemplate.opsForValue().increment(addTenantPrefix(key), -delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object hGet(String key, String hashKey) {
|
||||
return redisTemplate.opsForHash().get(addTenantPrefix(key), hashKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean hSet(String key, String hashKey, Object value, long time) {
|
||||
String newKey = addTenantPrefix(key);
|
||||
redisTemplate.opsForHash().put(newKey, hashKey, value);
|
||||
return expire(newKey, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hSet(String key, String hashKey, Object value) {
|
||||
redisTemplate.opsForHash().put(addTenantPrefix(key), hashKey, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> hGetAll(String key) {
|
||||
return redisTemplate.opsForHash().entries(addTenantPrefix(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long hSize(String key) {
|
||||
return redisTemplate.opsForHash().size(addTenantPrefix(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean hSetAll(String key, Map<String, Object> map, long time) {
|
||||
String newKey = addTenantPrefix(key);
|
||||
redisTemplate.opsForHash().putAll(newKey, map);
|
||||
return expire(newKey, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hSetAll(String key, Map<String, ?> map) {
|
||||
redisTemplate.opsForHash().putAll(addTenantPrefix(key), map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hDel(String key, Object... hashKey) {
|
||||
redisTemplate.opsForHash().delete(addTenantPrefix(key), hashKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean hHasKey(String key, String hashKey) {
|
||||
return redisTemplate.opsForHash().hasKey(addTenantPrefix(key), hashKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long hIncr(String key, String hashKey, Long delta) {
|
||||
return redisTemplate.opsForHash().increment(addTenantPrefix(key), hashKey, delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long hDecr(String key, String hashKey, Long delta) {
|
||||
return redisTemplate.opsForHash().increment(addTenantPrefix(key), hashKey, -delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Object> sMembers(String key) {
|
||||
return redisTemplate.opsForSet().members(addTenantPrefix(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long sAdd(String key, Object... values) {
|
||||
return redisTemplate.opsForSet().add(addTenantPrefix(key), values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long sAdd(String key, long time, Object... values) {
|
||||
String newKey = addTenantPrefix(key);
|
||||
Long count = redisTemplate.opsForSet().add(newKey, values);
|
||||
expire(newKey, time);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean sIsMember(String key, Object value) {
|
||||
return redisTemplate.opsForSet().isMember(addTenantPrefix(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long sSize(String key) {
|
||||
return redisTemplate.opsForSet().size(addTenantPrefix(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long sRemove(String key, Object... values) {
|
||||
return redisTemplate.opsForSet().remove(addTenantPrefix(key), values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> lRange(String key, long start, long end) {
|
||||
return redisTemplate.opsForList().range(addTenantPrefix(key), start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long lSize(String key) {
|
||||
return redisTemplate.opsForList().size(addTenantPrefix(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object lIndex(String key, long index) {
|
||||
return redisTemplate.opsForList().index(addTenantPrefix(key), index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long lPush(String key, Object value) {
|
||||
return redisTemplate.opsForList().rightPush(addTenantPrefix(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long lPush(String key, Object value, long time) {
|
||||
String newKey = addTenantPrefix(key);
|
||||
Long index = redisTemplate.opsForList().rightPush(newKey, value);
|
||||
expire(newKey, time);
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long lPushAll(String key, Object... values) {
|
||||
return redisTemplate.opsForList().rightPushAll(addTenantPrefix(key), values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long lPushAll(String key, Long time, Object... values) {
|
||||
String newKey = addTenantPrefix(key);
|
||||
Long count = redisTemplate.opsForList().rightPushAll(newKey, values);
|
||||
expire(newKey, time);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long lRemove(String key, long count, Object value) {
|
||||
return redisTemplate.opsForList().remove(addTenantPrefix(key), count, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public BasePage getPageList(String key, long pageNumber, long pageSize) {
|
||||
//获取数据总量
|
||||
Long totalCount = redisTemplate.opsForSet().size(key);
|
||||
if (Objects.isNull(totalCount)) {
|
||||
return new BasePage(new ArrayList(), 0);
|
||||
}
|
||||
//获取当前要取分页数据的游标位置
|
||||
long start = (pageNumber - 1) * pageSize;
|
||||
long end = start + pageSize - 1;
|
||||
//获取当前页分页数据
|
||||
List<Object> list = redisTemplate.opsForList().range(key, start, end);
|
||||
return new BasePage(list, totalCount);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<String> getKeys(String key) {
|
||||
return redisTemplate.keys(addTenantPrefix(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getObject(String key, Class<T> clazz) {
|
||||
return (T) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> getObjectList(String key, Class<T> clazz) {
|
||||
return (List<T>) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T hGetObject(String key, String hashKey, Class<T> clazz) {
|
||||
return (T) hGet(key, hashKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String addTenantPrefix(String key) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(TenantConstant.TENANT_KEY_PREFIX).append(BaseContext.getTenantId()).append(BaseConstant.COLON_JOIN_STR).append(key);
|
||||
log.debug("拼接后的RedisKey: {}", sb.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String removeTenantPrefix(String key) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(TenantConstant.TENANT_KEY_PREFIX).append(BaseContext.getTenantId()).append(BaseConstant.COLON_JOIN_STR);
|
||||
log.debug("去除拼接后的RedisKey: {}", key.replace(sb.toString(), ""));
|
||||
return key.replace(sb.toString(), "");
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.qiaoba.common.redis.config.RedisConfig,\
|
||||
com.qiaoba.common.redis.service.impl.RedisServiceImpl
|
||||
|
||||
|
30
qiaoba-common/qiaoba-common-web/pom.xml
Normal file
30
qiaoba-common/qiaoba-common-web/pom.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>qiaoba-common</artifactId>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qiaoba-common-web</artifactId>
|
||||
|
||||
<description>通用-Web模块(有controller的模块必须引入此模块)</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<artifactId>qiaoba-common-base</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,86 @@
|
||||
package com.qiaoba.common.web.advice;
|
||||
|
||||
|
||||
import com.qiaoba.common.base.exception.ServiceException;
|
||||
import com.qiaoba.common.base.result.AjaxResult;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/15 0015 下午 16:43
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class ExceptionAdvice {
|
||||
|
||||
/**
|
||||
* 非自定义异常
|
||||
*
|
||||
* @param e Exception
|
||||
* @return AjaxResult
|
||||
*/
|
||||
@ExceptionHandler(value = Exception.class)
|
||||
@ResponseBody
|
||||
public AjaxResult error(Exception e) {
|
||||
e.printStackTrace();
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义异常, 控制台不打印
|
||||
*
|
||||
* @param e ServiceException
|
||||
* @return AjaxResult
|
||||
*/
|
||||
@ExceptionHandler(ServiceException.class)
|
||||
@ResponseBody
|
||||
public AjaxResult handlerServiceException(ServiceException e) {
|
||||
return Objects.isNull(e.getErrorCode()) ? AjaxResult.error(e.getMessage()) : AjaxResult.error(e.getErrorCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验
|
||||
*
|
||||
* @param e MethodArgumentNotValidException
|
||||
* @return AjaxResult
|
||||
*/
|
||||
@ExceptionHandler({MethodArgumentNotValidException.class})
|
||||
@ResponseBody
|
||||
public AjaxResult handlerValidException(MethodArgumentNotValidException e) {
|
||||
return AjaxResult.error(Objects.isNull(e.getBindingResult().getFieldError()) ? e.getMessage() : e.getBindingResult().getFieldError().getDefaultMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验
|
||||
*
|
||||
* @param e BindException
|
||||
* @return AjaxResult
|
||||
*/
|
||||
@ExceptionHandler({BindException.class})
|
||||
@ResponseBody
|
||||
public AjaxResult handlerValidException(BindException e) {
|
||||
return AjaxResult.error(Objects.isNull(e.getBindingResult().getFieldError()) ? e.getMessage() : e.getBindingResult().getFieldError().getDefaultMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验
|
||||
*
|
||||
* @param e ConstraintViolationException
|
||||
* @return AjaxResult
|
||||
*/
|
||||
@ExceptionHandler({ConstraintViolationException.class})
|
||||
@ResponseBody
|
||||
public AjaxResult handlerValidException(ConstraintViolationException e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.qiaoba.common.web.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* 全局跨域配置
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/15 0015 下午 16:43
|
||||
*/
|
||||
@Configuration
|
||||
public class GlobalCorsConfig {
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
// 设置访问源地址
|
||||
config.addAllowedOriginPattern("*");
|
||||
// 设置访问源请求头
|
||||
config.addAllowedHeader("*");
|
||||
// 设置访问源请求方法
|
||||
config.addAllowedMethod("*");
|
||||
// 有效期 半小时
|
||||
config.setMaxAge(1800L);
|
||||
// 添加映射路径,拦截一切请求
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
// 返回新的CorsFilter
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.qiaoba.common.web.config;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import com.qiaoba.common.web.util.IpUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 离线IP设置
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/5/25 16:32
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class IpConfig {
|
||||
|
||||
@PostConstruct
|
||||
public void dbToCache() {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
ClassPathResource classPathResource = new ClassPathResource("ip2region.xdb");
|
||||
inputStream = classPathResource.getInputStream();
|
||||
IpUtil.setSearcher(Searcher.newWithBuffer(IoUtil.read(inputStream).toByteArray()));
|
||||
log.info("加载IP离线库到内存成功");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("加载IP离线库到内存失败, 请联系管理员!");
|
||||
} finally {
|
||||
IoUtil.close(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void close() {
|
||||
IpUtil.closeSearcher();
|
||||
log.info("IP离线库已关闭");
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.qiaoba.common.web.util;
|
||||
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* IP工具类
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/5/25 15:49
|
||||
*/
|
||||
@Slf4j
|
||||
public class IpUtil {
|
||||
|
||||
private static Searcher searcher;
|
||||
|
||||
public static void setSearcher(Searcher searcher) {
|
||||
IpUtil.searcher = searcher;
|
||||
}
|
||||
|
||||
public static String getIp(HttpServletRequest request) {
|
||||
return ServletUtil.getClientIP(request);
|
||||
}
|
||||
|
||||
public static String getIpAddr(String ip) {
|
||||
try {
|
||||
return searcher.search(ip);
|
||||
} catch (Exception e) {
|
||||
return "error ip";
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeSearcher() {
|
||||
try {
|
||||
searcher.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.qiaoba.common.web.util;
|
||||
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.qiaoba.common.base.constant.BaseConstant;
|
||||
import com.qiaoba.common.base.result.AjaxResult;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* ResponseUtil
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-25 22:48:43
|
||||
*/
|
||||
public class ResponseUtil {
|
||||
|
||||
public static void response(HttpServletResponse response, String msg) throws IOException {
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.setContentType(ContentType.JSON.getValue());
|
||||
response.setCharacterEncoding(BaseConstant.UTF8);
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(msg);
|
||||
writer.close();
|
||||
}
|
||||
|
||||
public static void errorAuth(HttpServletResponse response, Integer code, String msg) throws IOException {
|
||||
AjaxResult result = AjaxResult.error(code, msg);
|
||||
response(response, JSONUtil.toJsonStr(result));
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.qiaoba.common.web.util;
|
||||
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
/**
|
||||
* UriUtil
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-06-11 12:01:18
|
||||
*/
|
||||
public class UriUtil {
|
||||
|
||||
private static final AntPathMatcher MATCH = new AntPathMatcher();
|
||||
|
||||
private UriUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 通配符匹配
|
||||
*
|
||||
* @param pattern 通配符
|
||||
* @param path uri
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean match(String pattern, String path) {
|
||||
return MATCH.match(pattern, path);
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.qiaoba.common.web.advice.ExceptionAdvice,\
|
||||
com.qiaoba.common.web.config.IpConfig,\
|
||||
com.qiaoba.common.web.config.GlobalCorsConfig
|
||||
|
||||
|
BIN
qiaoba-common/qiaoba-common-web/src/main/resources/ip2region.xdb
Normal file
BIN
qiaoba-common/qiaoba-common-web/src/main/resources/ip2region.xdb
Normal file
Binary file not shown.
Reference in New Issue
Block a user