parent
e7eb8cb30f
commit
5fe2eb98e7
@ -1,107 +1,77 @@
|
|||||||
# ---> Java
|
|
||||||
# Compiled class file
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Log file
|
# 查看更多 .gitignore 配置 -> https://help.github.com/articles/ignoring-files/
|
||||||
*.log
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
.flattened-pom.xml
|
||||||
|
|
||||||
# BlueJ files
|
### STS ###
|
||||||
*.ctxt
|
.apt_generated
|
||||||
|
.classpath
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
|
||||||
|
|
||||||
# Package Files #
|
|
||||||
*.jar
|
|
||||||
*.war
|
|
||||||
*.nar
|
|
||||||
*.ear
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
|
||||||
replay_pid*
|
|
||||||
|
|
||||||
# ---> Eclipse
|
|
||||||
.metadata
|
|
||||||
bin/
|
|
||||||
tmp/
|
|
||||||
*.tmp
|
|
||||||
*.bak
|
|
||||||
*.swp
|
|
||||||
*~.nib
|
|
||||||
local.properties
|
|
||||||
.settings/
|
|
||||||
.loadpath
|
|
||||||
.recommenders
|
|
||||||
|
|
||||||
# External tool builders
|
|
||||||
.externalToolBuilders/
|
|
||||||
|
|
||||||
# Locally stored "Eclipse launch configurations"
|
|
||||||
*.launch
|
|
||||||
|
|
||||||
# PyDev specific (Python IDE for Eclipse)
|
|
||||||
*.pydevproject
|
|
||||||
|
|
||||||
# CDT-specific (C/C++ Development Tooling)
|
|
||||||
.cproject
|
|
||||||
|
|
||||||
# CDT- autotools
|
|
||||||
.autotools
|
|
||||||
|
|
||||||
# Java annotation processor (APT)
|
|
||||||
.factorypath
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.class
|
||||||
|
target/*
|
||||||
|
|
||||||
# PDT-specific (PHP Development Tools)
|
### NetBeans ###
|
||||||
.buildpath
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
/build/
|
||||||
|
|
||||||
# sbteclipse plugin
|
|
||||||
.target
|
|
||||||
|
|
||||||
# Tern plugin
|
|
||||||
.tern-project
|
|
||||||
|
|
||||||
# TeXlipse plugin
|
### admin-web ###
|
||||||
.texlipse
|
|
||||||
|
|
||||||
# STS (Spring Tool Suite)
|
# dependencies
|
||||||
.springBeans
|
**/node_modules
|
||||||
|
|
||||||
# Code Recommenders
|
# roadhog-api-doc ignore
|
||||||
.recommenders/
|
/src/utils/request-temp.js
|
||||||
|
_roadhog-api-doc
|
||||||
|
|
||||||
# Annotation Processing
|
# production
|
||||||
.apt_generated/
|
/dist
|
||||||
.apt_generated_test/
|
/.vscode
|
||||||
|
|
||||||
# Scala IDE specific (Scala & Java development for Eclipse)
|
# misc
|
||||||
.cache-main
|
.DS_Store
|
||||||
.scala_dependencies
|
npm-debug.log*
|
||||||
.worksheet
|
yarn-error.log
|
||||||
|
|
||||||
# Uncomment this line if you wish to ignore the project description file.
|
/coverage
|
||||||
# Typically, this file would be tracked if it contains build/dependency configurations:
|
.idea
|
||||||
#.project
|
.image
|
||||||
|
.github
|
||||||
|
.gitee
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
*bak
|
||||||
|
.vscode
|
||||||
|
|
||||||
# ---> Maven
|
# visual studio code
|
||||||
target/
|
.history
|
||||||
pom.xml.tag
|
*.log
|
||||||
pom.xml.releaseBackup
|
|
||||||
pom.xml.versionsBackup
|
functions/mock
|
||||||
pom.xml.next
|
.temp/**
|
||||||
release.properties
|
|
||||||
dependency-reduced-pom.xml
|
# umi
|
||||||
buildNumber.properties
|
.umi
|
||||||
.mvn/timing.properties
|
.umi-production
|
||||||
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
|
|
||||||
.mvn/wrapper/maven-wrapper.jar
|
|
||||||
|
|
||||||
# Eclipse m2e generated files
|
|
||||||
# Eclipse Core
|
|
||||||
.project
|
|
||||||
# JDT-specific (Eclipse Java Development Tools)
|
|
||||||
.classpath
|
|
||||||
|
|
||||||
|
# screenshot
|
||||||
|
screenshot
|
||||||
|
.firebase
|
||||||
|
sessionStore
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2021 hake-cloud
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.biz.infra.logger.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "RPC 服务 - API 错误日志创建 Request DTO")
|
||||||
|
@Data
|
||||||
|
public class ApiErrorLogCreateReqDTO {
|
||||||
|
|
||||||
|
@Schema(description = "链路追踪编号", example = "89aca178-a370-411c-ae02-3f0d672be4ab")
|
||||||
|
private String traceId;
|
||||||
|
|
||||||
|
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||||
|
private Long userId;
|
||||||
|
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
private Integer userType;
|
||||||
|
@Schema(description = "应用名", requiredMode = Schema.RequiredMode.REQUIRED, example = "system-server")
|
||||||
|
@NotNull(message = "应用名不能为空")
|
||||||
|
private String applicationName;
|
||||||
|
|
||||||
|
@Schema(description = "请求方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "GET")
|
||||||
|
@NotNull(message = "http 请求方法不能为空")
|
||||||
|
private String requestMethod;
|
||||||
|
@Schema(description = "请求地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "/xxx/yyy")
|
||||||
|
@NotNull(message = "访问地址不能为空")
|
||||||
|
private String requestUrl;
|
||||||
|
@Schema(description = "请求参数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "请求参数不能为空")
|
||||||
|
private String requestParams;
|
||||||
|
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
|
||||||
|
@NotNull(message = "ip 不能为空")
|
||||||
|
private String userIp;
|
||||||
|
@Schema(description = "浏览器 UserAgent", requiredMode = Schema.RequiredMode.REQUIRED, example = "Mozilla/5.0")
|
||||||
|
@NotNull(message = "User-Agent 不能为空")
|
||||||
|
private String userAgent;
|
||||||
|
|
||||||
|
@Schema(description = "异常时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "异常时间不能为空")
|
||||||
|
private LocalDateTime exceptionTime;
|
||||||
|
@Schema(description = "异常名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "异常名不能为空")
|
||||||
|
private String exceptionName;
|
||||||
|
@Schema(description = "异常发生的类全名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "异常发生的类全名不能为空")
|
||||||
|
private String exceptionClassName;
|
||||||
|
@Schema(description = "异常发生的类文件", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "异常发生的类文件不能为空")
|
||||||
|
private String exceptionFileName;
|
||||||
|
@Schema(description = "异常发生的方法名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "异常发生的方法名不能为空")
|
||||||
|
private String exceptionMethodName;
|
||||||
|
@Schema(description = "异常发生的方法所在行", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "异常发生的方法所在行不能为空")
|
||||||
|
private Integer exceptionLineNumber;
|
||||||
|
@Schema(description = "异常的栈轨迹异常的栈轨迹", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "异常的栈轨迹不能为空")
|
||||||
|
private String exceptionStackTrace;
|
||||||
|
@Schema(description = "异常导致的根消息", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "异常导致的根消息不能为空")
|
||||||
|
private String exceptionRootCauseMessage;
|
||||||
|
@Schema(description = "异常导致的消息", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "异常导致的消息不能为空")
|
||||||
|
private String exceptionMessage;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* 针对 infra 模块的 api 包
|
||||||
|
*/
|
||||||
|
package cn.iocoder.hake.framework.common.biz.infra;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.biz.system.dict.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "RPC 服务 - 字典数据 Response DTO")
|
||||||
|
@Data
|
||||||
|
public class DictDataRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "字典标签", requiredMode = Schema.RequiredMode.REQUIRED, example = "哈客")
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
@Schema(description = "字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "iocoder")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
@Schema(description = "字典类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "sys_common_sex")
|
||||||
|
private String dictType;
|
||||||
|
|
||||||
|
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
private Integer status; // 参见 CommonStatusEnum 枚举
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.biz.system.logger.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Schema(name = "RPC 服务 - 系统操作日志 Create Request DTO")
|
||||||
|
@Data
|
||||||
|
public class OperateLogCreateReqDTO {
|
||||||
|
|
||||||
|
@Schema(description = "链路追踪编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "89aca178-a370-411c-ae02-3f0d672be4ab")
|
||||||
|
private String traceId;
|
||||||
|
|
||||||
|
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
|
||||||
|
@NotNull(message = "用户编号不能为空")
|
||||||
|
private Long userId;
|
||||||
|
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "2" )
|
||||||
|
@NotNull(message = "用户类型不能为空")
|
||||||
|
private Integer userType;
|
||||||
|
@Schema(description = "操作模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单")
|
||||||
|
@NotEmpty(message = "操作模块类型不能为空")
|
||||||
|
private String type;
|
||||||
|
@Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "创建订单")
|
||||||
|
@NotEmpty(message = "操作名不能为空")
|
||||||
|
private String subType;
|
||||||
|
@Schema(description = "操作模块业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "188")
|
||||||
|
@NotNull(message = "操作模块业务编号不能为空")
|
||||||
|
private Long bizId;
|
||||||
|
@Schema(description = "操作内容", requiredMode = Schema.RequiredMode.REQUIRED,
|
||||||
|
example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从哈客改成源码")
|
||||||
|
@NotEmpty(message = "操作内容不能为空")
|
||||||
|
private String action;
|
||||||
|
@Schema(description = "拓展字段", example = "{\"orderId\": \"1\"}")
|
||||||
|
private String extra;
|
||||||
|
|
||||||
|
@Schema(description = "请求方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "GET")
|
||||||
|
@NotEmpty(message = "请求方法名不能为空")
|
||||||
|
private String requestMethod;
|
||||||
|
@Schema(description = "请求地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "/order/get")
|
||||||
|
@NotEmpty(message = "请求地址不能为空")
|
||||||
|
private String requestUrl;
|
||||||
|
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
|
||||||
|
@NotEmpty(message = "用户 IP 不能为空")
|
||||||
|
private String userIp;
|
||||||
|
@Schema(description = "浏览器 UserAgent", requiredMode = Schema.RequiredMode.REQUIRED, example = "Mozilla/5.0")
|
||||||
|
@NotEmpty(message = "浏览器 UA 不能为空")
|
||||||
|
private String userAgent;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.biz.system.oauth2.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Schema(description = "RPC 服务 - OAuth2 访问令牌的校验 Response DTO")
|
||||||
|
@Data
|
||||||
|
public class OAuth2AccessTokenCheckRespDTO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
private Integer userType;
|
||||||
|
|
||||||
|
@Schema(description = "用户信息", example = "{\"nickname\": \"哈客\"}")
|
||||||
|
private Map<String, String> userInfo;
|
||||||
|
|
||||||
|
@Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "授权范围的数组", example = "user_info")
|
||||||
|
private List<String> scopes;
|
||||||
|
|
||||||
|
@Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private LocalDateTime expiresTime;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.biz.system.oauth2.dto;
|
||||||
|
|
||||||
|
import cn.iocoder.hake.framework.common.enums.UserTypeEnum;
|
||||||
|
import cn.iocoder.hake.framework.common.validation.InEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(description = "RPC 服务 - OAuth2 访问令牌创建 Request DTO")
|
||||||
|
@Data
|
||||||
|
public class OAuth2AccessTokenCreateReqDTO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||||
|
@NotNull(message = "用户编号不能为空")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@NotNull(message = "用户类型不能为空")
|
||||||
|
@InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
|
||||||
|
private Integer userType;
|
||||||
|
|
||||||
|
@Schema(description = "客户端编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "hakeyuanma")
|
||||||
|
@NotNull(message = "客户端编号不能为空")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@Schema(description = "授权范围的数组", example = "user_info")
|
||||||
|
private List<String> scopes;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.biz.system.oauth2.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "RPC 服务 - OAuth2 访问令牌的信息 Response DTO")
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class OAuth2AccessTokenRespDTO implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "访问令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "tudou")
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
@Schema(description = "刷新令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "haha")
|
||||||
|
private String refreshToken;
|
||||||
|
|
||||||
|
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1" )
|
||||||
|
private Integer userType;
|
||||||
|
|
||||||
|
@Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private LocalDateTime expiresTime;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* 针对 system 模块的 api 包
|
||||||
|
*/
|
||||||
|
package cn.iocoder.hake.framework.common.biz.system;
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.biz.system.permission.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Schema(description = "RPC 服务 - 部门的数据权限 Response DTO")
|
||||||
|
@Data
|
||||||
|
public class DeptDataPermissionRespDTO {
|
||||||
|
|
||||||
|
@Schema(description = "是否可查看全部数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||||
|
private Boolean all;
|
||||||
|
|
||||||
|
@Schema(description = "是否可查看自己的数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||||
|
private Boolean self;
|
||||||
|
|
||||||
|
@Schema(description = "可查看的部门编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 3]")
|
||||||
|
private Set<Long> deptIds;
|
||||||
|
|
||||||
|
public DeptDataPermissionRespDTO() {
|
||||||
|
this.all = false;
|
||||||
|
this.self = false;
|
||||||
|
this.deptIds = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可生成 T 数组的接口
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
public interface ArrayValuable<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 数组
|
||||||
|
*/
|
||||||
|
T[] array();
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.core;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key Value 的键值对
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class KeyValue<K, V> implements Serializable {
|
||||||
|
|
||||||
|
private K key;
|
||||||
|
private V value;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjUtil;
|
||||||
|
import cn.iocoder.hake.framework.common.core.ArrayValuable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用状态枚举
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum CommonStatusEnum implements ArrayValuable<Integer> {
|
||||||
|
|
||||||
|
ENABLE(0, "开启"),
|
||||||
|
DISABLE(1, "关闭");
|
||||||
|
|
||||||
|
public static final Integer[] ARRAYS = Arrays.stream(values()).map(CommonStatusEnum::getStatus).toArray(Integer[]::new);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态值
|
||||||
|
*/
|
||||||
|
private final Integer status;
|
||||||
|
/**
|
||||||
|
* 状态名
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEnable(Integer status) {
|
||||||
|
return ObjUtil.equal(ENABLE.status, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDisable(Integer status) {
|
||||||
|
return ObjUtil.equal(DISABLE.status, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import cn.iocoder.hake.framework.common.core.ArrayValuable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间间隔的枚举
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum DateIntervalEnum implements ArrayValuable<Integer> {
|
||||||
|
|
||||||
|
DAY(1, "天"),
|
||||||
|
WEEK(2, "周"),
|
||||||
|
MONTH(3, "月"),
|
||||||
|
QUARTER(4, "季度"),
|
||||||
|
YEAR(5, "年")
|
||||||
|
;
|
||||||
|
|
||||||
|
public static final Integer[] ARRAYS = Arrays.stream(values()).map(DateIntervalEnum::getInterval).toArray(Integer[]::new);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
private final Integer interval;
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateIntervalEnum valueOf(Integer interval) {
|
||||||
|
return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档地址
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum DocumentEnum {
|
||||||
|
|
||||||
|
REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"),
|
||||||
|
TENANT("https://doc.iocoder.cn", "SaaS 多租户文档");
|
||||||
|
|
||||||
|
private final String url;
|
||||||
|
private final String memo;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.enums;
|
||||||
|
|
||||||
|
import cn.iocoder.hake.framework.common.core.ArrayValuable;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端的枚举
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum TerminalEnum implements ArrayValuable<Integer> {
|
||||||
|
|
||||||
|
UNKNOWN(0, "未知"), // 目的:在无法解析到 terminal 时,使用它
|
||||||
|
WECHAT_MINI_PROGRAM(10, "微信小程序"),
|
||||||
|
WECHAT_WAP(11, "微信公众号"),
|
||||||
|
H5(20, "H5 网页"),
|
||||||
|
APP(31, "手机 App"),
|
||||||
|
;
|
||||||
|
|
||||||
|
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TerminalEnum::getTerminal).toArray(Integer[]::new);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端
|
||||||
|
*/
|
||||||
|
private final Integer terminal;
|
||||||
|
/**
|
||||||
|
* 终端名
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import cn.iocoder.hake.framework.common.core.ArrayValuable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局用户类型枚举
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum UserTypeEnum implements ArrayValuable<Integer> {
|
||||||
|
|
||||||
|
MEMBER(1, "会员"), // 面向 c 端,普通用户
|
||||||
|
ADMIN(2, "管理员"); // 面向 b 端,管理后台
|
||||||
|
|
||||||
|
public static final Integer[] ARRAYS = Arrays.stream(values()).map(UserTypeEnum::getValue).toArray(Integer[]::new);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
private final Integer value;
|
||||||
|
/**
|
||||||
|
* 类型名
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public static UserTypeEnum valueOf(Integer value) {
|
||||||
|
return ArrayUtil.firstMatch(userType -> userType.getValue().equals(value), UserTypeEnum.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer[] array() {
|
||||||
|
return ARRAYS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.pojo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(description = "分页结果")
|
||||||
|
@Data
|
||||||
|
public final class PageResult<T> implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private List<T> list;
|
||||||
|
|
||||||
|
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Long total;
|
||||||
|
|
||||||
|
public PageResult() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageResult(List<T> list, Long total) {
|
||||||
|
this.list = list;
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageResult(Long total) {
|
||||||
|
this.list = new ArrayList<>();
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> PageResult<T> empty() {
|
||||||
|
return new PageResult<>(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> PageResult<T> empty(Long total) {
|
||||||
|
return new PageResult<>(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.pojo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(description = "可排序的分页参数")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class SortablePageParam extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "排序字段")
|
||||||
|
private List<SortingField> sortingFields;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.util.collection;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.collection.IterUtil;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static cn.iocoder.hake.framework.common.util.collection.CollectionUtils.convertList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array 工具类
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
public class ArrayUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 object 和 newElements 合并成一个数组
|
||||||
|
*
|
||||||
|
* @param object 对象
|
||||||
|
* @param newElements 数组
|
||||||
|
* @param <T> 泛型
|
||||||
|
* @return 结果数组
|
||||||
|
*/
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> Consumer<T>[] append(Consumer<T> object, Consumer<T>... newElements) {
|
||||||
|
if (object == null) {
|
||||||
|
return newElements;
|
||||||
|
}
|
||||||
|
Consumer<T>[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length);
|
||||||
|
result[0] = object;
|
||||||
|
System.arraycopy(newElements, 0, result, 1, newElements.length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, V> V[] toArray(Collection<T> from, Function<T, V> mapper) {
|
||||||
|
return toArray(convertList(from, mapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> T[] toArray(Collection<T> from) {
|
||||||
|
if (CollectionUtil.isEmpty(from)) {
|
||||||
|
return (T[]) (new Object[0]);
|
||||||
|
}
|
||||||
|
return ArrayUtil.toArray(from, (Class<T>) IterUtil.getElementType(from.iterator()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T get(T[] array, int index) {
|
||||||
|
if (null == array || index >= array.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return array[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.util.collection;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set 工具类
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
public class SetUtils {
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> Set<T> asSet(T... objs) {
|
||||||
|
return CollUtil.newHashSet(objs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.util.date;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
|
|
||||||
|
import java.time.*;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间工具类
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
public class DateUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时区 - 默认
|
||||||
|
*/
|
||||||
|
public static final String TIME_ZONE_DEFAULT = "GMT+8";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 秒转换成毫秒
|
||||||
|
*/
|
||||||
|
public static final long SECOND_MILLIS = 1000;
|
||||||
|
|
||||||
|
public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
|
||||||
|
|
||||||
|
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 LocalDateTime 转换成 Date
|
||||||
|
*
|
||||||
|
* @param date LocalDateTime
|
||||||
|
* @return LocalDateTime
|
||||||
|
*/
|
||||||
|
public static Date of(LocalDateTime date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 将此日期时间与时区相结合以创建 ZonedDateTime
|
||||||
|
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
|
||||||
|
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
|
||||||
|
Instant instant = zonedDateTime.toInstant();
|
||||||
|
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||||
|
return Date.from(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 Date 转换成 LocalDateTime
|
||||||
|
*
|
||||||
|
* @param date Date
|
||||||
|
* @return LocalDateTime
|
||||||
|
*/
|
||||||
|
public static LocalDateTime of(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 转为时间戳
|
||||||
|
Instant instant = date.toInstant();
|
||||||
|
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||||
|
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Date addTime(Duration duration) {
|
||||||
|
return new Date(System.currentTimeMillis() + duration.toMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isExpired(LocalDateTime time) {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
return now.isAfter(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建指定时间
|
||||||
|
*
|
||||||
|
* @param year 年
|
||||||
|
* @param mouth 月
|
||||||
|
* @param day 日
|
||||||
|
* @return 指定时间
|
||||||
|
*/
|
||||||
|
public static Date buildTime(int year, int mouth, int day) {
|
||||||
|
return buildTime(year, mouth, day, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建指定时间
|
||||||
|
*
|
||||||
|
* @param year 年
|
||||||
|
* @param mouth 月
|
||||||
|
* @param day 日
|
||||||
|
* @param hour 小时
|
||||||
|
* @param minute 分钟
|
||||||
|
* @param second 秒
|
||||||
|
* @return 指定时间
|
||||||
|
*/
|
||||||
|
public static Date buildTime(int year, int mouth, int day,
|
||||||
|
int hour, int minute, int second) {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.set(Calendar.YEAR, year);
|
||||||
|
calendar.set(Calendar.MONTH, mouth - 1);
|
||||||
|
calendar.set(Calendar.DAY_OF_MONTH, day);
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
||||||
|
calendar.set(Calendar.MINUTE, minute);
|
||||||
|
calendar.set(Calendar.SECOND, second);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒
|
||||||
|
return calendar.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Date max(Date a, Date b) {
|
||||||
|
if (a == null) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
if (b == null) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return a.compareTo(b) > 0 ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
|
||||||
|
if (a == null) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
if (b == null) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return a.isAfter(b) ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否今天
|
||||||
|
*
|
||||||
|
* @param date 日期
|
||||||
|
* @return 是否
|
||||||
|
*/
|
||||||
|
public static boolean isToday(LocalDateTime date) {
|
||||||
|
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否昨天
|
||||||
|
*
|
||||||
|
* @param date 日期
|
||||||
|
* @return 是否
|
||||||
|
*/
|
||||||
|
public static boolean isYesterday(LocalDateTime date) {
|
||||||
|
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.util.json.databind;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于时间戳的 LocalDateTime 反序列化器
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
|
||||||
|
|
||||||
|
public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
|
// 将 Long 时间戳,转换为 LocalDateTime 对象
|
||||||
|
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.util.json.databind;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于时间戳的 LocalDateTime 序列化器
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
public class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
|
||||||
|
|
||||||
|
public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
|
// 将 LocalDateTime 对象,转换为 Long 时间戳
|
||||||
|
gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.util.spring;
|
||||||
|
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring 工具类
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
public class SpringUtils extends SpringUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为生产环境
|
||||||
|
*
|
||||||
|
* @return 是否生产环境
|
||||||
|
*/
|
||||||
|
public static boolean isProd() {
|
||||||
|
String activeProfile = getActiveProfile();
|
||||||
|
return Objects.equals("prod", activeProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.util.validation;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintViolation;
|
||||||
|
import javax.validation.ConstraintViolationException;
|
||||||
|
import javax.validation.Validation;
|
||||||
|
import javax.validation.Validator;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验工具类
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
public class ValidationUtils {
|
||||||
|
|
||||||
|
private static final Pattern PATTERN_MOBILE = Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[0,1,4-9])|(?:5[0-3,5-9])|(?:6[2,5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[0-3,5-9]))\\d{8}$");
|
||||||
|
|
||||||
|
private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
|
||||||
|
|
||||||
|
private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*");
|
||||||
|
|
||||||
|
public static boolean isMobile(String mobile) {
|
||||||
|
return StringUtils.hasText(mobile)
|
||||||
|
&& PATTERN_MOBILE.matcher(mobile).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isURL(String url) {
|
||||||
|
return StringUtils.hasText(url)
|
||||||
|
&& PATTERN_URL.matcher(url).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isXmlNCName(String str) {
|
||||||
|
return StringUtils.hasText(str)
|
||||||
|
&& PATTERN_XML_NCNAME.matcher(str).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void validate(Object object, Class<?>... groups) {
|
||||||
|
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||||
|
Assert.notNull(validator);
|
||||||
|
validate(validator, object, groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void validate(Validator validator, Object object, Class<?>... groups) {
|
||||||
|
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
||||||
|
if (CollUtil.isNotEmpty(constraintViolations)) {
|
||||||
|
throw new ConstraintViolationException(constraintViolations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.validation;
|
||||||
|
|
||||||
|
import cn.iocoder.hake.framework.common.core.ArrayValuable;
|
||||||
|
|
||||||
|
import javax.validation.Constraint;
|
||||||
|
import javax.validation.Payload;
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Target({
|
||||||
|
ElementType.METHOD,
|
||||||
|
ElementType.FIELD,
|
||||||
|
ElementType.ANNOTATION_TYPE,
|
||||||
|
ElementType.CONSTRUCTOR,
|
||||||
|
ElementType.PARAMETER,
|
||||||
|
ElementType.TYPE_USE
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@Constraint(
|
||||||
|
validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class}
|
||||||
|
)
|
||||||
|
public @interface InEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 实现 ArrayValuable 接口的类
|
||||||
|
*/
|
||||||
|
Class<? extends ArrayValuable<?>> value();
|
||||||
|
|
||||||
|
String message() default "必须在指定范围 {value}";
|
||||||
|
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.validation;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.iocoder.hake.framework.common.core.ArrayValuable;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintValidator;
|
||||||
|
import javax.validation.ConstraintValidatorContext;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class InEnumCollectionValidator implements ConstraintValidator<InEnum, Collection<?>> {
|
||||||
|
|
||||||
|
private List<?> values;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(InEnum annotation) {
|
||||||
|
ArrayValuable<?>[] values = annotation.value().getEnumConstants();
|
||||||
|
if (values.length == 0) {
|
||||||
|
this.values = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
this.values = Arrays.asList(values[0].array());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Collection<?> list, ConstraintValidatorContext context) {
|
||||||
|
if (list == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 校验通过
|
||||||
|
if (CollUtil.containsAll(values, list)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 校验不通过,自定义提示语句
|
||||||
|
context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
|
||||||
|
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
|
||||||
|
.replaceAll("\\{value}", CollUtil.join(list, ","))).addConstraintViolation(); // 重新添加错误提示语句
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.validation;
|
||||||
|
|
||||||
|
import cn.iocoder.hake.framework.common.core.ArrayValuable;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintValidator;
|
||||||
|
import javax.validation.ConstraintValidatorContext;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class InEnumValidator implements ConstraintValidator<InEnum, Object> {
|
||||||
|
|
||||||
|
private List<?> values;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(InEnum annotation) {
|
||||||
|
ArrayValuable<?>[] values = annotation.value().getEnumConstants();
|
||||||
|
if (values.length == 0) {
|
||||||
|
this.values = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
this.values = Arrays.asList(values[0].array());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Object value, ConstraintValidatorContext context) {
|
||||||
|
// 为空时,默认不校验,即认为通过
|
||||||
|
if (value == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 校验通过
|
||||||
|
if (values.contains(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 校验不通过,自定义提示语句
|
||||||
|
context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
|
||||||
|
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
|
||||||
|
.replaceAll("\\{value}", values.toString())).addConstraintViolation(); // 重新添加错误提示语句
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.validation;
|
||||||
|
|
||||||
|
import javax.validation.Constraint;
|
||||||
|
import javax.validation.Payload;
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Target({
|
||||||
|
ElementType.METHOD,
|
||||||
|
ElementType.FIELD,
|
||||||
|
ElementType.ANNOTATION_TYPE,
|
||||||
|
ElementType.CONSTRUCTOR,
|
||||||
|
ElementType.PARAMETER,
|
||||||
|
ElementType.TYPE_USE
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@Constraint(
|
||||||
|
validatedBy = MobileValidator.class
|
||||||
|
)
|
||||||
|
public @interface Mobile {
|
||||||
|
|
||||||
|
String message() default "手机号格式不正确";
|
||||||
|
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.validation;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.iocoder.hake.framework.common.util.validation.ValidationUtils;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintValidator;
|
||||||
|
import javax.validation.ConstraintValidatorContext;
|
||||||
|
|
||||||
|
public class MobileValidator implements ConstraintValidator<Mobile, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(Mobile annotation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||||
|
// 如果手机号为空,默认不校验,即校验通过
|
||||||
|
if (StrUtil.isEmpty(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 校验手机
|
||||||
|
return ValidationUtils.isMobile(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.validation;
|
||||||
|
|
||||||
|
import javax.validation.Constraint;
|
||||||
|
import javax.validation.Payload;
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Target({
|
||||||
|
ElementType.METHOD,
|
||||||
|
ElementType.FIELD,
|
||||||
|
ElementType.ANNOTATION_TYPE,
|
||||||
|
ElementType.CONSTRUCTOR,
|
||||||
|
ElementType.PARAMETER,
|
||||||
|
ElementType.TYPE_USE
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@Constraint(
|
||||||
|
validatedBy = TelephoneValidator.class
|
||||||
|
)
|
||||||
|
public @interface Telephone {
|
||||||
|
|
||||||
|
String message() default "电话格式不正确";
|
||||||
|
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package cn.iocoder.hake.framework.common.validation;
|
||||||
|
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.core.util.PhoneUtil;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintValidator;
|
||||||
|
import javax.validation.ConstraintValidatorContext;
|
||||||
|
|
||||||
|
public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(Telephone annotation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||||
|
// 如果手机号为空,默认不校验,即校验通过
|
||||||
|
if (CharSequenceUtil.isEmpty(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 校验手机
|
||||||
|
return PhoneUtil.isTel(value) || PhoneUtil.isPhone(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* 使用 Hibernate Validator 实现参数校验
|
||||||
|
*/
|
||||||
|
package cn.iocoder.hake.framework.common.validation;
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
<?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>hake-framework</artifactId>
|
||||||
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>hake-spring-boot-starter-biz-data-permission</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>数据权限</description>
|
||||||
|
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
|
<artifactId>hake-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Web 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
|
<artifactId>hake-spring-boot-starter-security</artifactId>
|
||||||
|
<optional>true</optional> <!-- 可选,如果使用 DeptDataPermissionRule 必须提供 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- DB 相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
|
<artifactId>hake-spring-boot-starter-mybatis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- RPC 远程调用相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
|
<artifactId>hake-spring-boot-starter-rpc</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test 测试相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
|
<artifactId>hake-spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package cn.iocoder.hake.framework.datapermission.config;
|
||||||
|
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.db.DataPermissionRuleHandler;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.rule.DataPermissionRule;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;
|
||||||
|
import cn.iocoder.hake.framework.mybatis.core.util.MyBatisUtils;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限的自动配置类
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
public class HakeDataPermissionAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DataPermissionRuleFactory dataPermissionRuleFactory(List<DataPermissionRule> rules) {
|
||||||
|
return new DataPermissionRuleFactoryImpl(rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DataPermissionRuleHandler dataPermissionRuleHandler(MybatisPlusInterceptor interceptor,
|
||||||
|
DataPermissionRuleFactory ruleFactory) {
|
||||||
|
// 创建 DataPermissionInterceptor 拦截器
|
||||||
|
DataPermissionRuleHandler handler = new DataPermissionRuleHandler(ruleFactory);
|
||||||
|
DataPermissionInterceptor inner = new DataPermissionInterceptor(handler);
|
||||||
|
// 添加到 interceptor 中
|
||||||
|
// 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定
|
||||||
|
MyBatisUtils.addInterceptor(interceptor, inner, 0);
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() {
|
||||||
|
return new DataPermissionAnnotationAdvisor();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package cn.iocoder.hake.framework.datapermission.config;
|
||||||
|
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.rpc.DataPermissionRequestInterceptor;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.rpc.DataPermissionRpcWebFilter;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
import static cn.iocoder.hake.framework.common.enums.WebFilterOrderEnum.TENANT_CONTEXT_FILTER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限针对 RPC 的自动配置类
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
@ConditionalOnClass(name = "feign.RequestInterceptor")
|
||||||
|
public class HakeDataPermissionRpcAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DataPermissionRequestInterceptor dataPermissionRequestInterceptor() {
|
||||||
|
return new DataPermissionRequestInterceptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean<DataPermissionRpcWebFilter> dataPermissionRpcFilter() {
|
||||||
|
FilterRegistrationBean<DataPermissionRpcWebFilter> registrationBean = new FilterRegistrationBean<>();
|
||||||
|
registrationBean.setFilter(new DataPermissionRpcWebFilter());
|
||||||
|
registrationBean.setOrder(TENANT_CONTEXT_FILTER - 1); // 顺序没有绝对的要求,在租户 Filter 前面稳妥点
|
||||||
|
return registrationBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package cn.iocoder.hake.framework.datapermission.config;
|
||||||
|
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import cn.iocoder.hake.framework.common.biz.system.permission.PermissionCommonApi;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.rule.dept.DeptDataPermissionRule;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
|
||||||
|
import cn.iocoder.hake.framework.security.core.LoginUser;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于部门的数据权限 AutoConfiguration
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
@ConditionalOnClass(LoginUser.class)
|
||||||
|
@ConditionalOnBean(value = {PermissionCommonApi.class, DeptDataPermissionRuleCustomizer.class})
|
||||||
|
public class HakeDeptDataPermissionAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DeptDataPermissionRule deptDataPermissionRule(PermissionCommonApi permissionApi,
|
||||||
|
List<DeptDataPermissionRuleCustomizer> customizers) {
|
||||||
|
// Cloud 专属逻辑:优先使用本地的 PermissionApi 实现类,而不是 Feign 调用
|
||||||
|
// 原因:在创建租户时,租户还没创建好,导致 Feign 调用获取数据权限时,报“租户不存在”的错误
|
||||||
|
try {
|
||||||
|
PermissionCommonApi permissionApiImpl = SpringUtil.getBean("permissionApiImpl", PermissionCommonApi.class);
|
||||||
|
if (permissionApiImpl != null) {
|
||||||
|
permissionApi = permissionApiImpl;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
// 创建 DeptDataPermissionRule 对象
|
||||||
|
DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi);
|
||||||
|
// 补全表配置
|
||||||
|
customizers.forEach(customizer -> customizer.customize(rule));
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package cn.iocoder.hake.framework.datapermission.core.aop;
|
||||||
|
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.annotation.DataPermission;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.aopalliance.aop.Advice;
|
||||||
|
import org.springframework.aop.Pointcut;
|
||||||
|
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
||||||
|
import org.springframework.aop.support.ComposablePointcut;
|
||||||
|
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link DataPermission} 注解的 Advisor 实现类
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class DataPermissionAnnotationAdvisor extends AbstractPointcutAdvisor {
|
||||||
|
|
||||||
|
private final Advice advice;
|
||||||
|
|
||||||
|
private final Pointcut pointcut;
|
||||||
|
|
||||||
|
public DataPermissionAnnotationAdvisor() {
|
||||||
|
this.advice = new DataPermissionAnnotationInterceptor();
|
||||||
|
this.pointcut = this.buildPointcut();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Pointcut buildPointcut() {
|
||||||
|
Pointcut classPointcut = new AnnotationMatchingPointcut(DataPermission.class, true);
|
||||||
|
Pointcut methodPointcut = new AnnotationMatchingPointcut(null, DataPermission.class, true);
|
||||||
|
return new ComposablePointcut(classPointcut).union(methodPointcut);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package cn.iocoder.hake.framework.datapermission.core.rule;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.annotation.DataPermission;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.aop.DataPermissionContextHolder;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认的 DataPermissionRuleFactoryImpl 实现类
|
||||||
|
* 支持通过 {@link DataPermissionContextHolder} 过滤数据权限
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DataPermissionRuleFactoryImpl implements DataPermissionRuleFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限规则数组
|
||||||
|
*/
|
||||||
|
private final List<DataPermissionRule> rules;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DataPermissionRule> getDataPermissionRules() {
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // mappedStatementId 参数,暂时没有用。以后,可以基于 mappedStatementId + DataPermission 进行缓存
|
||||||
|
public List<DataPermissionRule> getDataPermissionRule(String mappedStatementId) {
|
||||||
|
// 1. 无数据权限
|
||||||
|
if (CollUtil.isEmpty(rules)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
// 2. 未配置,则默认开启
|
||||||
|
DataPermission dataPermission = DataPermissionContextHolder.get();
|
||||||
|
if (dataPermission == null) {
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
// 3. 已配置,但禁用
|
||||||
|
if (!dataPermission.enable()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 已配置,只选择部分规则
|
||||||
|
if (ArrayUtil.isNotEmpty(dataPermission.includeRules())) {
|
||||||
|
return rules.stream().filter(rule -> ArrayUtil.contains(dataPermission.includeRules(), rule.getClass()))
|
||||||
|
.collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询
|
||||||
|
}
|
||||||
|
// 5. 已配置,只排除部分规则
|
||||||
|
if (ArrayUtil.isNotEmpty(dataPermission.excludeRules())) {
|
||||||
|
return rules.stream().filter(rule -> !ArrayUtil.contains(dataPermission.excludeRules(), rule.getClass()))
|
||||||
|
.collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询
|
||||||
|
}
|
||||||
|
// 6. 已配置,全部规则
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* 基于部门的数据权限规则
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
package cn.iocoder.hake.framework.datapermission.core.rule.dept;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
cn.iocoder.hake.framework.datapermission.config.HakeDataPermissionAutoConfiguration
|
||||||
|
cn.iocoder.hake.framework.datapermission.config.HakeDeptDataPermissionAutoConfiguration
|
||||||
|
cn.iocoder.hake.framework.datapermission.config.HakeDataPermissionRpcAutoConfiguration
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
package cn.iocoder.hake.framework.datapermission.core.aop;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.iocoder.hake.framework.datapermission.core.annotation.DataPermission;
|
||||||
|
import cn.iocoder.hake.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link DataPermissionAnnotationInterceptor} 的单元测试
|
||||||
|
*
|
||||||
|
* @author hake
|
||||||
|
*/
|
||||||
|
public class DataPermissionAnnotationInterceptorTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private DataPermissionAnnotationInterceptor interceptor;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private MethodInvocation methodInvocation;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
interceptor.getDataPermissionCache().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // 无 @DataPermission 注解
|
||||||
|
public void testInvoke_none() throws Throwable {
|
||||||
|
// 参数
|
||||||
|
mockMethodInvocation(TestNone.class);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Object result = interceptor.invoke(methodInvocation);
|
||||||
|
// 断言
|
||||||
|
assertEquals("none", result);
|
||||||
|
assertEquals(1, interceptor.getDataPermissionCache().size());
|
||||||
|
assertTrue(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // 在 Method 上有 @DataPermission 注解
|
||||||
|
public void testInvoke_method() throws Throwable {
|
||||||
|
// 参数
|
||||||
|
mockMethodInvocation(TestMethod.class);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Object result = interceptor.invoke(methodInvocation);
|
||||||
|
// 断言
|
||||||
|
assertEquals("method", result);
|
||||||
|
assertEquals(1, interceptor.getDataPermissionCache().size());
|
||||||
|
assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // 在 Class 上有 @DataPermission 注解
|
||||||
|
public void testInvoke_class() throws Throwable {
|
||||||
|
// 参数
|
||||||
|
mockMethodInvocation(TestClass.class);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Object result = interceptor.invoke(methodInvocation);
|
||||||
|
// 断言
|
||||||
|
assertEquals("class", result);
|
||||||
|
assertEquals(1, interceptor.getDataPermissionCache().size());
|
||||||
|
assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockMethodInvocation(Class<?> clazz) throws Throwable {
|
||||||
|
Object targetObject = clazz.newInstance();
|
||||||
|
Method method = targetObject.getClass().getMethod("echo");
|
||||||
|
when(methodInvocation.getThis()).thenReturn(targetObject);
|
||||||
|
when(methodInvocation.getMethod()).thenReturn(method);
|
||||||
|
when(methodInvocation.proceed()).then(invocationOnMock -> method.invoke(targetObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestMethod {
|
||||||
|
|
||||||
|
@DataPermission(enable = false)
|
||||||
|
public String echo() {
|
||||||
|
return "method";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataPermission(enable = false)
|
||||||
|
static class TestClass {
|
||||||
|
|
||||||
|
public String echo() {
|
||||||
|
return "class";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestNone {
|
||||||
|
|
||||||
|
public String echo() {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue