Commit 910f201b authored by 胡伟's avatar 胡伟

初始

parents
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/
# Other files and folders
.settings/
# Executables
*.swf
*.air
*.ipa
*.apk
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.
.idea/
/project-base.iml
/.idea/
/*/*.iml
/*/*/*.iml
/target/
/*/target/
/*/*/target/
/*/*/*/target/
/logs/
木兰宽松许可证, 第2版
木兰宽松许可证, 第2版
2020年1月 http://license.coscl.org.cn/MulanPSL2
您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:
0. 定义
“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
“法人实体”是指提交贡献的机构及其“关联实体”。
“关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
1. 授予版权许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
2. 授予专利许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
3. 无商标许可
“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
4. 分发限制
您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
5. 免责声明与责任限制
“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
6. 语言
“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
条款结束
如何将木兰宽松许可证,第2版,应用到您的软件
如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
3, 请将如下声明文本放入每个源文件的头部注释中。
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
Mulan Permissive Software License,Version 2
Mulan Permissive Software License,Version 2 (Mulan PSL v2)
January 2020 http://license.coscl.org.cn/MulanPSL2
Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
0. Definition
Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
Contribution means the copyrightable work licensed by a particular Contributor under this License.
Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
Legal Entity means the entity making a Contribution and all its Affiliates.
Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
1. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
2. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
3. No Trademark License
No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
4. Distribution Restriction
You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
5. Disclaimer of Warranty and Limitation of Liability
THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
6. Language
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
END OF THE TERMS AND CONDITIONS
How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software
To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:
i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package;
iii Attach the statement to the appropriate annotated syntax at the beginning of each source file.
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
# project-base
#### 介绍
项目结构基础模板
<?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>common</artifactId>
<groupId>com.ssyf</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>core</artifactId>
<dependencies>
</dependencies>
</project>
\ No newline at end of file
package com.ssyf.common.core.constants;
/**
* 系统常量
* @author huwei
* @date 2022/7/6 15:19
*/
public class SystemConstants {
/**
* 时间格式
*/
public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* 时间格式
*/
public static final String DATE_FORMAT = "yyyy-MM-dd";
/**
* 时区
*/
public static final String DATE_TIME_TIMEZONE = "GMT+8";
/**
* 项目根包
*/
public static final String PROJECT_BASE_PACKAGE = "com.ssyf";
/**
* 数据分隔符号
*/
public static final String DATA_SEPARATOR = ",";
}
package com.ssyf.common.core.domain.query;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 基础查询类
* @author huwei
* @date 2023/3/5 10:27
*/
@Data
@Accessors(chain = true)
public class BaseQuery {
/**
* 排序值
*/
private List<SortQuery> sorts;
}
package com.ssyf.common.core.domain.query;
import lombok.Data;
import lombok.experimental.Accessors;
/**
*
* @author huwei
* @date 2023/8/14 14:12
*/
@Data
@Accessors(chain = true)
public class KeyQuery<T> {
private T key;
}
package com.ssyf.common.core.domain.query;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
*
* @author huwei
* @date 2023/3/5 10:24
*/
@Data
@Accessors(chain = true)
public class KeysQuery<T> {
private List<T> list;
}
package com.ssyf.common.core.domain.query;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
*
* @author huwei
* @date 2023/3/5 10:24
*/
@Data
@Accessors(chain = true)
public class ListQuery<T> {
private List<T> list;
}
package com.ssyf.common.core.domain.query;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 分页查询参数类
* @author huwei
* @date 2022/7/19 21:27
*/
@Data
@Accessors(chain = true)
public class PageQuery extends BaseQuery {
/**
* 页码
*/
private Integer pageNum = 1;
/**
* 页展示数量
*/
private Integer pageSize = 20;
}
package com.ssyf.common.core.domain.query;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 排序参数
* @author huwei
* @date 2023/1/26 16:21
*/
@Data
@Accessors(chain = true)
public class SortQuery {
/**
* 排序字段名
*/
private String name;
/**
* 正序
*/
private boolean desc;
}
package com.ssyf.common.core.enums;
/**
* 比较
* @author huwei
* @date 2023/2/9 9:56
*/
public enum MathEnum {
/**
* 等于
*/
eq(1)
/**
* 不包含
*/
,nin(2)
/**
* 小于
*/
,lt(3)
/**
* 小于等于
*/
,lte(4)
/**
* 大于
*/
,gt(5)
/**
* 大于等于
*/
,gte(6)
/**
* 不等于
*/
,ne(7)
/**
* 包含
*/
,in(8)
/**
* 左右模糊匹配
*/
,like(9)
/**
* 右模糊
*/
,likeRight(10)
/**
* 左模糊
*/
,likeLeft(11)
/**
* 为空
*/
,isNull(12)
/**
* 不为空
*/
,isNotNull(13);
private int value;
MathEnum(int value){
this.value = value;
}
public int getValue(){
return this.value;
}
}
package com.ssyf.common.core.enums;
/**
* 状态账户枚举;
* 1 正常;2 冻结;3 已锁定;4 已注销;5 待审核;6 异常;
* @author huwei
* @date 2022/12/1 16:36
*/
public enum StatusAccountEnum {
/**
* 正常;
* 表示事件被创建并记录在系统中,等待进一步处理。
*/
normal(1 ,"正常"),
/**
* 已冻结;
* 表示事件处于等待状态,等待被分配给处理人员或处理流程
*/
freeze(2 ,"已冻结"),
/**
* 已锁定;
* 表示事件正在进行处理中,处理人员正在处理事件。
*/
locked(3 ,"已锁定"),
/**
* 已注销;
* 表示事件处理暂时被挂起,可能是需要等待其他条件满足或需要进一步调查。
*/
cancel(4 ,"挂起"),
/**
* 待审核;
* 表示事件已经被处理完成,处理人员完成了相应的处理工作。
*/
audit(5 ,"待审核"),
/**
* 异常;
* 表示事件处理完结,所有相关工作已完成,事件可以被归档或关闭。
*/
error(6 ,"异常");
private Integer value;
private String notes;
StatusAccountEnum(int value , String notes){
this.value = value;
this.notes = notes;
}
public Integer getValue() {
return this.value;
}
public String getNotes(){
return this.notes;
}
public static boolean isNormal(int value){
return normal.getValue().equals(value);
}
}
package com.ssyf.common.core.enums;
/**
* 状态通用枚举
* @author huwei
* @date 2022/12/1 16:36
*/
public enum StatusCommonEnum {
disabled(0) ,enabled(1);
private Integer value;
StatusCommonEnum(int value){
this.value = value;
}
public Integer getValue() {
return this.value;
}
public static boolean isDisabled(int value){
return StatusCommonEnum.disabled.getValue() == value;
}
public static boolean isEnabled(int value){
return StatusCommonEnum.enabled.getValue() == value;
}
}
package com.ssyf.common.core.enums;
/**
* 状态删除枚举
* @author huwei
* @date 2022/11/14 12:58
*/
public enum StatusDeleteMarkEnum {
/**
* 已被删除
*/
delete(1)
/**
* 未删除
*/
,exist(0);
private int value;
StatusDeleteMarkEnum(int value){
this.value = value;
}
public int getValue(){
return this.value;
}
public static boolean isDelete(int value){
return StatusDeleteMarkEnum.delete.getValue() == value;
}
public static boolean isExist(int value){
return StatusDeleteMarkEnum.exist.getValue() == value;
}
}
package com.ssyf.common.core.enums;
/**
* 事件处理状态;
* 1 创建;2 等待;3 处理中;4 挂起;5 已处理;6 已完结;7 退回;8 撤销;9 关闭;
* @author huwei
* @date 2022/12/1 16:36
*/
public enum StatusEventEnum {
/**
* 创建;
* 表示事件被创建并记录在系统中,等待进一步处理。
*/
created(1 ,"创建"),
/**
* 等待;
* 表示事件处于等待状态,等待被分配给处理人员或处理流程
*/
waiting(2 ,"等待"),
/**
* 处理中;
* 表示事件正在进行处理中,处理人员正在处理事件。
*/
processing(3 ,"处理中"),
/**
* 挂起;
* 表示事件处理暂时被挂起,可能是需要等待其他条件满足或需要进一步调查。
*/
pending(4 ,"挂起"),
/**
* 已处理;
* 表示事件已经被处理完成,处理人员完成了相应的处理工作。
*/
processed(5 ,"已处理"),
/**
* 已完结;
* 表示事件处理完结,所有相关工作已完成,事件可以被归档或关闭。
*/
finish(6 ,"已完结"),
/**
* 退回;
* 表示事件需要退回给之前的处理人员或重新分配给其他人员处理。
*/
back(7 ,"退回"),
/**
* 撤销;
* 表示事件被撤销,可能是因为误报或其他原因导致事件无需处理。
*/
revocation(8 ,"撤销"),
/**
* 关闭;
* 表示事件被正式关闭,所有相关工作已经完成,事件处理流程结束。
*/
closed(9 ,"关闭");
private Integer value;
private String notes;
StatusEventEnum(int value , String notes){
this.value = value;
this.notes = notes;
}
public Integer getValue() {
return this.value;
}
public String getNotes(){
return this.notes;
}
}
package com.ssyf.common.core.func;
/**
* 无参数回调
* @author huwei
* @date 2023/2/23 14:50
*/
@FunctionalInterface
public interface Callback {
/**
* 回调
*/
void call();
}
package com.ssyf.common.core.func;
/**
* 有参数回调
* @author huwei
* @date 2023/2/23 14:50
*/
@FunctionalInterface
public interface CallbackP<P> {
/**
* 回调
*/
void call(P p);
}
package com.ssyf.common.core.func;
/**
* 有参数回调
* @author huwei
* @date 2023/2/23 14:50
*/
@FunctionalInterface
public interface CallbackR<R> {
/**
* 回调
*/
R call();
}
package com.ssyf.common.core.func;
/**
* 有参数回调
* @author huwei
* @date 2023/2/23 14:50
*/
@FunctionalInterface
public interface CallbackRP<R,P> {
/**
* 回调
*/
R call(P p);
}
package com.ssyf.common.core.func;
import java.io.Serializable;
import java.util.function.Function;
/**
*
* @author huwei
* @date 2022/9/9 16:06
*/
public interface MyFunction<T ,R> extends Function<T, R>, Serializable {
}
package com.ssyf.common.core.helper;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ClassUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 文件工具类
*
* @author huwei
* @date 2023/1/28 16:31
*/
public class ResourceHelper {
/**
* 类文件后缀
*/
public static final String CLASS_SUFFIX = ".class";
/**
* 加载指定文件
* @param resourcePathPattern 文件路径
* @return
*/
public static Resource[] loadByPathPattern(String resourcePathPattern) throws IOException {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
String pattern = resourcePathPattern;
if(!resourcePathPattern.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)){
pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resourcePathPattern;
}
return resourcePatternResolver.getResources(pattern);
}
/**
* 加载指定文件
* @param packageName 包路径,示例:com.ssyf
* @return
* @throws IOException
*/
public static Resource[] loadInPackage(String packageName) throws IOException {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
String pattern = ClassUtils.convertClassNameToResourcePath(packageName) + "/**/*" + CLASS_SUFFIX;
if(!pattern.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)){
pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + pattern;
}
return resourcePatternResolver.getResources(pattern);
}
/**
* 加载指定包路径下的类
* @param packageName 包路径,示例:com.ssyf
* @return
* @throws IOException
*/
public static List<Class<?>> loadClassInPackage(String packageName) throws Exception {
List<Class< ? >> result = new ArrayList();
Resource[] resources = loadInPackage(packageName);
for (Resource resource : resources) {
String tempPath = resource.getURL().getPath().replace("/",".").replace(CLASS_SUFFIX ,"");
String classPackageName = tempPath.substring(tempPath.indexOf(packageName));
result.add(Class.forName(classPackageName));
}
return result;
}
}
package com.ssyf.common.core.util;
/**
* 断言工具
* @author huwei
* @date 2022/7/19 20:45
*/
public class AssertUtil {
public static final void isTrue(boolean state , String message){
isTrue(state ,new RuntimeException(message));
}
public static final void isTrue(boolean state , RuntimeException exception){
if(!state){
throw exception;
}
}
public static final void isFalse(boolean state , String message){
isTrue(!state ,message);
}
public static final void isFalse(boolean state , RuntimeException exception){
isTrue(!state ,exception);
}
public static final void isNull(Object object , String message){
isTrue(object == null ,message);
}
public static final void isNull(Object object , RuntimeException exception){
isTrue(object == null ,exception);
}
public static final void isNotNull(Object object , String message){
isTrue(object != null ,message);
}
public static final void isNotNull(Object object , RuntimeException exception){
isTrue(object != null ,exception);
}
}
package com.ssyf.common.core.util;
/**
* 转换工具
* @author huwei
* @date 2022/7/21 7:24
*/
public class ConvertUtil {
/**
* 62进制基本字符
*/
private static final String SYSTEM62_STRING = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/**
* 转换为62进制
* @author huwei
* @date 2022/7/21 7:26
*/
public static final String toSystem62(int num){
StringBuffer sb = new StringBuffer();
int temp = num;
int len = SYSTEM62_STRING.length();
while (temp >= 0){
int mod = temp % len;
sb.append(SYSTEM62_STRING.charAt(mod));
if(temp < len){
break;
}
temp = temp / len;
}
return sb.reverse().toString();
}
/**
* 62进制转换为10进制
* @param str
* @return
*/
public static final int system62ToInt(String str){
int res = 0;
for(int i = 0 ;i < str.length() ;++i){
int idx = SYSTEM62_STRING.indexOf(str.charAt(i));
if(idx == -1){
throw new RuntimeException("["+str+"]不是62为进制字符串");
}
if(i < str.length() - 1){
res += idx * Math.pow(SYSTEM62_STRING.length() ,str.length() - i - 1);
}else{
res += idx;
}
}
return res;
}
}
package com.ssyf.common.core.util;
import cn.hutool.core.util.IdUtil;
/**
* 唯一编码短id
*
* @author huwei
* @date 2022/7/20 22:53
*/
public class IdShortUtil {
/**
* 基础输出字符
*/
private static final String[] BASE_CHARS = new String[]{"a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z"};
/**
* 8为uuid字符
*
* @return
*/
public static String uuid8() {
StringBuffer shortBuffer = new StringBuffer();
String uuid = IdUtil.fastSimpleUUID();
for (int i = 0; i < 8; i++) {
String str = uuid.substring(i * 4, i * 4 + 4);
int x = Integer.parseInt(str, 16);
shortBuffer.append(BASE_CHARS[x % BASE_CHARS.length]);
}
return shortBuffer.toString();
}
}
package com.ssyf.common.core.util;
import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.lang.func.LambdaUtil;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
/**
*
* @author huwei
* @date 2022/12/13 10:28
*/
public class LambdaEnhanceUtil {
/**
* 获取字段名称
* @param fn
* @return
*/
public static <T> String getFieldName(Function<T ,?> fn) {
// 从function取出序列化方法
Method writeReplaceMethod;
try {
writeReplaceMethod = fn.getClass().getDeclaredMethod("writeReplace");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
// 从序列化方法取出序列化的lambda信息
boolean isAccessible = writeReplaceMethod.isAccessible();
writeReplaceMethod.setAccessible(true);
SerializedLambda serializedLambda;
try {
serializedLambda = (SerializedLambda) writeReplaceMethod.invoke(fn);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
writeReplaceMethod.setAccessible(isAccessible);
// 从lambda信息取出method、field、class等
String fieldName = serializedLambda.getImplMethodName();
if(fieldName.startsWith("get")){
fieldName = fieldName.substring("get".length());
}else if(fieldName.startsWith("is")){
fieldName = fieldName.substring("is".length());
}
return fieldName.replaceFirst(fieldName.charAt(0) + "", (fieldName.charAt(0) + "").toLowerCase());
}
/**
* 获取字段名
* @param fns
* @return
*/
public static <T> String[] getFieldNames(Function<T, ?>...fns){
String[] result = new String[fns.length];
for (int i = 0; i < fns.length; i++) {
result[i] = getFieldName(fns[i]);
}
return result;
}
/**
* 获取字段名
* @param fns
* @return
*/
public static <T> List<String> getFieldNameList(Function<T ,?>...fns){
List<String> result = new ArrayList<>();
for (int i = 0; i < fns.length; i++) {
result.add(getFieldName(fns[i]));
}
return result;
}
/**
* 获取字段名
* @param fns
* @param <T>
* @return
*/
public static <T> String[] getFieldNames(Func1<T, ?>...fns){
String[] result = new String[fns.length];
for (int i = 0; i < fns.length; i++) {
result[i] = LambdaUtil.getFieldName(fns[i]);
}
return result;
}
/**
* 获取字段名
* @param fns
* @return
*/
public static <T> List<String> getFieldNameList(Func1<T ,?>...fns){
List<String> result = new ArrayList<>();
for (int i = 0; i < fns.length; i++) {
result.add(LambdaUtil.getFieldName(fns[i]));
}
return result;
}
}
package com.ssyf.common.core.util;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 经纬度点位工具
*
* @author Administrator
* @date 2019/8/13
* 电子围栏计算
*/
public class MapRangeUtil {
/**
* 地球平均半径(单位:米)
*/
private static final double EARTH_AVG_RADIUS = 6371000;
/**
* 经纬度转化为弧度(rad)
*
* @param d 经度/纬度
*/
private static double rad(double d) {
return d * Math.PI / 180.0;
}
/**
* 方法三:(基于googleMap中的算法得到两经纬度之间的距离,计算精度与谷歌地图的距离精度差不多。)
*
* @param longitude1 第一点的经度
* @param latitude1 第一点的纬度
* @param longitude2 第二点的经度
* @param latitude2 第二点的纬度
* @return 返回的距离,单位m
*/
public static double calculateHaversine(double longitude1, double latitude1, double longitude2, double latitude2) {
double radLat1 = rad(latitude1);
double radLat2 = rad(latitude2);
double a = radLat1 - radLat2;
double b = rad(longitude1) - rad(longitude2);
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
s = s * EARTH_AVG_RADIUS;
s = Math.round(s * 10000d) / 10000d;
return s;
}
/**
* 判断点是否坐落于多边形内
* 注意::: 多边形的坐标点必须依次顺序传入
*
* @param point
* @param polygonPoints
* @return
*/
public static boolean isInPolygon(Point2D.Double point, Point2D.Double[] polygonPoints) {
List<Point2D.Double> polygonPointList = new ArrayList<>();
for (int i = 0; i < polygonPoints.length; ++i) {
polygonPointList.add(polygonPoints[i]);
}
return isInPolygon(point, polygonPointList);
}
public static boolean isInPolygon(Point2D.Double point, List<Point2D.Double> polygonPointList) {
if (polygonPointList.size() < 3) {
return false;
}
List<Double> xs = new ArrayList<>(polygonPointList.size());
List<Double> ys = new ArrayList<>(polygonPointList.size());
for (int i = 0; i < polygonPointList.size(); ++i) {
xs.add(polygonPointList.get(i).getX());
ys.add(polygonPointList.get(i).getY());
}
//排序
Collections.sort(xs);
Collections.sort(ys);
if (point.getX() < xs.get(0).doubleValue() || point.getX() > xs.get(xs.size() - 1).doubleValue()) {
return false;
}
if (point.getY() < ys.get(0).doubleValue() || point.getY() > ys.get(ys.size() - 1).doubleValue()) {
return false;
}
int i, j;
boolean c = false;
for (i = 0, j = polygonPointList.size() - 1; i < polygonPointList.size(); j = i++) {
if ((polygonPointList.get(i).getY() >= point.getY()) != (polygonPointList.get(j).getY() >= point.getY()) &&
(point.getX() <= (polygonPointList.get(j).getX() - polygonPointList.get(i).getX()) * (point.getY() - polygonPointList.get(i).getY()) / (polygonPointList.get(j).getY() - polygonPointList.get(i).getY()) + polygonPointList.get(i).getX())) {
c = !c;
}
}
return c;
}
}
\ No newline at end of file
package com.ssyf.common.core.util;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author: huwei
* @date: 2020/6/3 10:47
* @version: 1.0.0
*/
public class MapUtil {
public static <K, V> MapBuilder builder() {
return new MapBuilder<K, V>();
}
public static class MapBuilder<K, V> {
Map<K, V> map;
{
this.map = new LinkedHashMap<>();
}
public MapBuilder elem(K key, V value) {
this.map.put(key, value);
return this;
}
public MapBuilder elem(Map<K, V> more) {
this.map.putAll(more);
return this;
}
public MapBuilder put(K key, V value) {
this.map.put(key, value);
return this;
}
public MapBuilder putAll(Map<K, V> more) {
this.map.putAll(more);
return this;
}
public Map<K, V> build() {
return map;
}
}
}
package com.ssyf.common.core.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author: huwei
* @date: 2020/6/30 9:38
* @version: 1.0.0
*/
public class PatternUtil {
/**
* 是否为url
* @param url
* @return
*/
public static boolean isUrl(String url){
return Inner.URL_PATTERN.matcher(url).find();
}
/**
* 获取url的域名
* @param url
* @return
*/
public static String getUrlDomain(String url){
Matcher matcher = Inner.URL_DOMAIN_PATTERN.matcher(url);
if(matcher.find()){
return matcher.group();
}
return null;
}
/**
*
* @param mail
* @return
*/
public static boolean isMail(String mail){
return Inner.EMAIL_PATTERN.matcher(mail).find();
}
/**
* 是否为手机
* @param phone
* @return
*/
public static boolean isPhone(String phone){
return Inner.PHONE_PATTERN.matcher(phone).find();
}
private static final class Inner {
/**
* url地址
*/
private static final Pattern URL_PATTERN = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]$");
/**
* url域名
*/
private static final Pattern URL_DOMAIN_PATTERN = Pattern.compile("(http|https)://(www.)?(\\w+(\\.)?)+");
/**
* 手机号码
*/
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3456789]\\d{9}$");
/**
* 邮箱地址
*/
private static final Pattern EMAIL_PATTERN = Pattern.compile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?");
}
}
package com.ssyf.common.core.util;
import cn.hutool.core.date.DateUtil;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
/**
* 流程编码生成工具
* @author huwei
* @date 2023/2/17 14:53
*/
public class SerialNumberUtil {
/**
* 同时间点流水号控制map
*/
private static Map<String, AtomicInteger> sameTimeSerialMap = new ConcurrentHashMap<>();
public static String create(){
return create(null ,"yyyyMMddHHmmss" ,4);
}
/**
* 生成流水号;
* 号码整体长度等于:前缀长度 + 格式化日期的长度 + 流水号的长度;
* @param prefix 前缀
* @param datetimeFormat 日期格式化样式
* @param serialLength 流水号长度
* @return
*/
public static String create(String prefix , String datetimeFormat , int serialLength){
String millisKey = DateUtil.format(new Date() ,datetimeFormat);
Lock lock = SimpleLockerUtil.getLocker(millisKey);
lock.lock();
try{
AtomicInteger atomicInteger = sameTimeSerialMap.get(millisKey);
if(atomicInteger == null){
atomicInteger = new AtomicInteger(0);
sameTimeSerialMap.put(millisKey ,atomicInteger);
}
int serial = atomicInteger.incrementAndGet();
StringBuilder serialFormatCode = new StringBuilder();
for(int i = 0 ;i < serialLength ;++i){
serialFormatCode.append("0");
}
StringBuilder result = new StringBuilder();
result.append(prefix != null ? prefix : "");
result.append(millisKey);
result.append(new DecimalFormat(serialFormatCode.toString()).format(serial));
int count = SimpleLockerUtil.getReferenceCount(millisKey);
if(count <= 0){
sameTimeSerialMap.remove(millisKey);
}
return result.toString();
}finally {
lock.unlock();
}
}
}
package com.ssyf.common.core.util;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 简单锁工具
* @author huwei
* @date 2022/11/13 9:20
*/
public class SimpleLockerUtil {
private static final Map<String,LockerInfo> lockerMap = new HashMap<>();
private static final Lock locker = new ReentrantLock();
/**
* 获取锁对象
* @param key
* @return
*/
public static Lock getLocker(String key){
locker.lock();
try{
LockerInfo lockerInfo = lockerMap.get(key);
if(lockerInfo == null){
lockerInfo = new LockerInfo(key);
lockerMap.put(key ,lockerInfo);
}else{
lockerInfo.increReference();
}
return lockerInfo.getLocker();
}finally {
locker.unlock();
}
}
/**
* 获取锁对象
* @param key
* @param domains 域,防止相同key获取同一锁对象造成的同步问题
* @return
*/
public static Lock getLocker(String key , String... domains){
return getLocker(wrapKey(key, domains));
}
public static int getReferenceCount(String key){
LockerInfo lockInfo = lockerMap.get(key);
if(lockInfo != null){
return lockInfo.getReferenceCount();
}
return 0;
}
/**
* 释放锁对象
* @param key
*/
private static void releaseLocker(String key){
locker.lock();
try{
LockerInfo lockerInfo = lockerMap.get(key);
if(lockerInfo != null){
lockerInfo.decreReference();
if(lockerInfo.getReferenceCount().intValue() <= 0){
lockerMap.remove(key);
}
}
}finally {
locker.unlock();
}
}
private static String wrapKey(String key , String... domains) {
StringBuffer stringBuilder = new StringBuffer();
for(String domain : domains){
stringBuilder.append(domain);
}
return stringBuilder.append(key).toString();
}
/**
* 自动引用锁类
* @author huwei
* @date 2022/11/13 10:18
*/
public static class AutoReferenceLocker extends ReentrantLock {
private String key;
AutoReferenceLocker(String key){
this.key = key;
}
@Override
public void unlock() {
super.unlock();
//释放
releaseLocker(key);
}
public int getReferenceCount(){
return SimpleLockerUtil.getReferenceCount(key);
}
}
/**
* 锁信息类
* @author huwei
* @date 2022/11/13 10:18
*/
@Data
static class LockerInfo {
ReentrantLock locker;
Integer referenceCount;
LockerInfo(String key){
this.locker = new AutoReferenceLocker(key);
this.referenceCount = 1;
}
void increReference(){
this.referenceCount++;
}
void decreReference(){
this.referenceCount--;
}
}
}
package com.ssyf.common.core.util;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import java.util.Map;
/**
*
* @author huwei
* @date 2023/2/12 13:29
*/
public class TypeEnhanceUtil {
public static boolean isNumber(Object value){
Class<?> valueClass = value.getClass();
if (valueClass.isAssignableFrom(byte.class) || valueClass.isAssignableFrom(Byte.class)) {
return true;
}
if (valueClass.isAssignableFrom(short.class) || valueClass.isAssignableFrom(Short.class)) {
return true;
}
if (valueClass.isAssignableFrom(int.class) || valueClass.isAssignableFrom(Integer.class)) {
return true;
}
if (valueClass.isAssignableFrom(long.class) || valueClass.isAssignableFrom(Long.class)) {
return true;
}
if (valueClass.isAssignableFrom(float.class) || valueClass.isAssignableFrom(Float.class)) {
return true;
}
if (valueClass.isAssignableFrom(double.class) || valueClass.isAssignableFrom(Double.class)) {
return true;
}
return false;
}
public static boolean isBoolean(Object object){
return object.getClass().isAssignableFrom(boolean.class) || object.getClass().isAssignableFrom(Boolean.class);
}
public static boolean isString(Object object){
return object.getClass().isAssignableFrom(String.class);
}
public static boolean isChar(Object object){
return object.getClass().isAssignableFrom(char.class) || object.getClass().isAssignableFrom(Character.class);
}
public static boolean isBaseType(Object object){
return isNumber(object) || isBoolean(object) || isChar(object) || isString(object);
}
public static String getStringValue(Object value){
if(value == null){
return "";
}
final String jsonValueKey = "value";
String json = JSONUtil.toJsonStr(MapUtil.builder().put(jsonValueKey ,value).build());
return JSONUtil.toBean(json , Map.class).get(jsonValueKey).toString();
}
public static Long toLongDefaultNull(Object value){
return toLong(value ,null);
}
public static Long toLong(Object value ,Long defaultValue){
try {
return Long.parseLong(value.toString());
}catch (Exception e){}
return defaultValue;
}
/**
* 值比较
* @param obj
* @param other
* @return
*/
public static boolean isValueEquals(Object obj , Object other){
if(obj == other){
return true;
}
if(obj == null || other == null){
return false;
}
if(obj.toString().equals(other.toString())){
return true;
}
return getStringValue(obj).equals(getStringValue(other));
}
}
<?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>common</artifactId>
<groupId>com.ssyf</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mybaits</artifactId>
<dependencies>
<!-- Mysql Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid-spring-boot-starter</artifactId>-->
<!-- <version>1.1.9</version>-->
<!-- </dependency>-->
<!-- Pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>com.ssyf</groupId>
<artifactId>core</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.ssyf.orm.mysql.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.ssyf.common.core.constants.SystemConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
* 数据库配置
* @author huwei
* @date 2022/7/6 11:00
*/
@Slf4j
@Configuration
@ConditionalOnProperty(prefix = "spring.datasource.base" ,name = "jdbcUrl")
@MapperScan(basePackages = BaseDatasourceConfig.MAPPER_PATH, sqlSessionFactoryRef = "baseSqlSessionFactory")
public class BaseDatasourceConfig {
/**
* mapper所在路径
*/
public static final String MAPPER_PATH = SystemConstants.PROJECT_BASE_PACKAGE + ".**.mapper";
/**
* xml所在路径
*/
public static final String XML_PATH = "classpath*:".concat(SystemConstants.PROJECT_BASE_PACKAGE.replace(".","/")).concat("/**/*.xml");
@Primary
@Bean(name = "base")
@ConfigurationProperties(prefix = "spring.datasource.base")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "baseSqlSessionFactory")
public SqlSessionFactory datasource1SqlSessionFactory(@Qualifier("base") DataSource datasource)
throws Exception {
log.info("mybaits mapper扫描路径:".concat(MAPPER_PATH));
log.info("mybaits xml扫描路径:".concat(XML_PATH));
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(datasource);
// 设置mybatis的xml所在位置
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(XML_PATH));
bean.setPlugins(paginationInterceptor());
return bean.getObject();
}
/**
* 分页插件
* paginationInterceptor.setLimit(你的最大单页限制数量,默认 50 条,小于 0 如 -1 不受限制);
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setLimit(50);
return paginationInterceptor;
}
}
\ No newline at end of file
package com.ssyf.orm.mysql.helper;
import cn.hutool.core.lang.func.Func1;
import com.ssyf.common.core.util.EntityUtil;
import com.github.pagehelper.PageInfo;
import java.util.List;
/**
* 分页结果帮助类
* @author huwei
* @date 2023/1/29 14:45
*/
public class PageEnhanceHelper {
public static <T ,R> PageInfo<R> switchPageInfoIgnore(PageInfo<T> pageInfo , Class<R> cls , Func1<R ,?>... ignoreFunc1s){
List<R> list = EntityUtil.copyProperties(pageInfo.getList() ,cls ,ignoreFunc1s);
PageInfo<R> result = new PageInfo<>(list);
result.setTotal(pageInfo.getTotal());
result.setPageNum(pageInfo.getPageNum());
result.setPageSize(pageInfo.getPageSize());
return result;
}
/**
* 将分页数据转换为指定类型的数据
* @param pageInfo
* @param cls
* @param <T>
* @param <R>
* @return
*/
public static <T ,R> PageInfo<R> switchPageInfo(PageInfo<T> pageInfo ,Class<R> cls){
return switchPageInfoIgnore(pageInfo, cls);
}
/**
* 获取开始下标
* @param pageNum
* @param pageSize
* @return
*/
public static long getStartIndex(long pageNum ,long pageSize){
return (pageNum - 1 ) * pageSize;
}
}
<?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>bom-bmiss-robot</artifactId>
<groupId>com.ssyf</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>security</module>
<module>web</module>
<module>swagger</module>
<module>mybaits</module>
<module>test</module>
</modules>
</project>
\ No newline at end of file
## 证书
### 自定义证书秘钥
默认实现获取ClientInfo的dys字段信息,详情参见:{@link com.ssyf.common.security.handler.impl.CertificateSecretHandlerImpl}
#### 步骤
1. 实现CertificateSecretHandler接口
2. 利用springboot容器功能将实现类注入到容器中,注意使用@Primary注解
\ No newline at end of file
<?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>common</artifactId>
<groupId>com.ssyf</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>security</artifactId>
<dependencies>
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>1.37.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>com.ssyf</groupId>
<artifactId>web</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.ssyf.common.security.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 证书校验注解,通过校验用户传入的证书判断用户请求是否有效;
* 证书传入方式,通过请求头的certificate传入,参见{@link com.ssyf.common.security.helper.ClientContextHelper}的getCertificate()方法
* 校验方式参见{@link CertificateValidAop}
* @author huwei
* @date 2023/11/4 15:58
*/
@Target({ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface CertificateValid {
/**
* 证书的事件类型
* @return
*/
String type() default "";
/**
* 事件源
* @return
*/
String event() default "";
/**
* 事件源变量:
* 若定义该值,则将从请求参数中获取该值
* 优先级小于event()定义;
* @return
*/
String eventVar() default "";
/**
* 事件内容
* @return
*/
String content() default "";
/**
* 事件内容变量;
* 若定义该值,则将从请求参数中获取该值;
* 优先级小于content()定义
* @return
*/
String contentVar() default "";
}
package com.ssyf.common.security.aop;
import cn.hutool.core.util.StrUtil;
import com.ssyf.common.core.util.AssertUtil;
import com.ssyf.common.core.util.StringUtil;
import com.ssyf.common.security.domain.Certificate;
import com.ssyf.common.security.handler.CertificateSecretHandler;
import com.ssyf.common.security.helper.CertificateHelper;
import com.ssyf.common.security.helper.ClientContextHelper;
import com.ssyf.common.web.apicode.ApiCodeUser;
import com.ssyf.common.web.error.ServiceRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* 证书校验拦截
* @author huwei
* @date 2023/11/4 15:57
*/
@Slf4j
@Aspect
@Component
public class CertificateValidAop {
@Autowired
private CertificateSecretHandler certificateSecretHandler;
@Pointcut("@annotation(CertificateValid)")
public void pointcut(){}
@Around("pointcut()")
public Object valid(ProceedingJoinPoint joinPoint) throws Throwable {
//证书
String certificateStr = ClientContextHelper.getCertificate();
AssertUtil.isTrue(StrUtil.isNotBlank(certificateStr) ,ServiceRuntimeException.instance(ApiCodeUser.PARAMETER_ERROR.getCode() ,"证书为空"));
//解析CertificateValid
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
CertificateValid certificateValid = method.getAnnotation(CertificateValid.class);
Certificate certificate = parseAndGet(joinPoint ,certificateValid);
//获取证书秘钥
String secret = certificateSecretHandler.getSecret(ClientContextHelper.getClientInfo());
//校验证书
if(!CertificateHelper.valid(secret ,certificate,certificateStr)){
throw ServiceRuntimeException.instance(ApiCodeUser.PARAMETER_ERROR.getCode() ,"无效证书");
}
return joinPoint.proceed();
}
/**
* 系欸小证书注解并获取预期证书信息
* @param joinPoint
* @param certificateValid
* @return
*/
private Certificate parseAndGet(ProceedingJoinPoint joinPoint , CertificateValid certificateValid){
Certificate certificate = new Certificate();
certificate.setType(certificateValid.type()).setEvent(certificateValid.event()).setContent(certificateValid.content());
Map<String ,Object> varMap = null;
//动态event
if(StrUtil.isBlank(certificate.getEvent()) && StrUtil.isNotBlank(certificateValid.eventVar())){
varMap = getParameterMap(joinPoint);
String var = new StringBuilder("${").append(certificateValid.eventVar()).append("}").toString();
//获取动态event
Object obj = StringUtil.replacePlaceholder(var ,varMap);
String event = obj != null ? obj.toString() : null;
certificate.setEvent(event);
}
//动态内容
Boolean isEmptyContent = certificate.getContent() == null || StrUtil.isBlank(certificate.getContent().toString());
if(isEmptyContent && StrUtil.isNotBlank(certificateValid.contentVar())){
varMap = varMap == null ? getParameterMap(joinPoint) : varMap;
String var = new StringBuilder("${").append(certificateValid.contentVar()).append("}").toString();
//获取动态event
Object obj = StringUtil.replacePlaceholder(var ,varMap);
String content = obj != null ? obj.toString() : null;
certificate.setContent(content);
}
return certificate;
}
/**
* 获取指定的参数值
* @param joinPoint
* @return
*/
private Map<String ,Object> getParameterMap(ProceedingJoinPoint joinPoint){
CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
String[] parameterNames = codeSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
Map<String ,Object> map = new HashMap<>(args.length);
for (int i = 0; i < parameterNames.length; i++) {
map.put(parameterNames[i] ,args[i]);
}
return map;
}
}
package com.ssyf.common.security.config;
import com.ssyf.common.core.util.MapUtil;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* swagger 路由控制
*
* @author huwei
* @date 2023/11/5 14:31
*/
@Component
public class GlobalRequestConfig implements RequestConfig {
@Override
public List<String> math() {
return Arrays.asList("/auth/**", "/auth/**", "/api/**");
}
@Override
public List<String> notMath() {
return Arrays.asList("/openapi","/test/**", "/share/**", "/doc.html", "/webjars/**", "/swagger-resources/**", "/*/api-docs"
, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/swagger**/**", "/v2/api-docs", "/v3/api-docs");
}
@Override
public Map<String, String> responseHeader() {
return MapUtil.builder()
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
.put("X-XSS-Protection", "1; mode=block")
// 禁用浏览器内容嗅探
.put("X-Content-Type-Options", "nosniff")
//跨域
.put("Access-Control-Allow-Origin", "*")
.put("Access-Control-Allow-Methods", "*")
.put("Access-Control-Allow-Headers", "*")
.build();
}
}
package com.ssyf.common.security.config;
import java.util.List;
import java.util.Map;
/**
* 路由认证配置
* @author huwei
* @date 2023/11/5 14:27
*/
public interface RequestConfig {
/**
* 包含
* @return
*/
default List<String> math(){
return null;
}
/**
* 排除
* @return
*/
default List<String> notMath(){
return null;
}
/**
* 返回请求头
* @return
*/
default Map<String ,String> responseHeader(){
return null;
}
}
package com.ssyf.common.security.config;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.router.SaRouterStaff;
import cn.dev33.satoken.same.SaSameUtil;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.ssyf.common.security.handler.SecurityErrorHandler;
import com.ssyf.common.web.holder.Holder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Optional;
/**
* Sa-Token 整合 jwt (Mixin 混入模式)
*
* @author huwei
* @date 2023/11/4 9:44
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Autowired(required = false)
private Optional<SecurityErrorHandler> securityErrorHandler;
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForSimple();
}
/**
* 开启注解
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor().isAnnotation(true)).addPathPatterns("/**");
}
/**
* 注册 [Sa-Token全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 拦截路由 与 放行路由
.addInclude("/**").addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(this::auth)
//错误处理
.setError(this::handleError)
//认证前置
.setBeforeAuth(this::beforeAuth);
}
private void auth(Object obj){
SaRouterStaff saRouterStaff = SaRouter.newMatch();
//路由控制
Holder.getBeans(RequestConfig.class).forEach(requestAuthConfig -> {
//认证路由
if(CollectionUtil.isNotEmpty(requestAuthConfig.math())){
saRouterStaff.match(requestAuthConfig.math());
}
//放行路由
if(CollectionUtil.isNotEmpty(requestAuthConfig.notMath())){
saRouterStaff.notMatch(requestAuthConfig.notMath());
}
});
//路由校验
saRouterStaff.check(()->{
String sameToken = SaHolder.getRequest().getHeader(SaSameUtil.SAME_TOKEN);
//同源
if(SaSameUtil.isValid(sameToken)){
return;
}
//不同源
StpUtil.checkLogin();
// 获取剩余过期时间(秒)
long tokenTimeout = StpUtil.getTokenTimeout();
// 如果剩余时间小于1小时(3600秒),则续期
if(tokenTimeout < 3600) {
// 续期至默认过期时间(通常为配置的tokenTimeout)
StpUtil.renewTimeout(SaManager.getConfig().getTimeout());
}
});
}
private Object handleError(Throwable e){
if(securityErrorHandler.get() == null){
throw new RuntimeException(e);
}
if(e instanceof NotLoginException){
return securityErrorHandler.get().notLoginHandle((NotLoginException) e);
}else if(e instanceof NotPermissionException){
return securityErrorHandler.get().notPermissionHandle((NotPermissionException) e);
}else if(e instanceof NotRoleException){
return securityErrorHandler.get().notRoleHandle((NotRoleException) e);
}else{
return securityErrorHandler.get().otherHandle(e);
}
}
private void beforeAuth(Object obj){
SaResponse saResponse = SaHolder.getResponse();
//设置请求头
Holder.getBeans(RequestConfig.class).forEach(requestAuthConfig -> {
if(CollectionUtil.isNotEmpty(requestAuthConfig.responseHeader())){
requestAuthConfig.responseHeader().entrySet().forEach(entry->{
saResponse.setHeader(entry.getKey() ,entry.getValue());
});
}
});
}
}
package com.ssyf.common.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Component
@Configuration
public class SwaggerBootstrapUiDemoApplication implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
package com.ssyf.common.security.constant;
import java.util.concurrent.TimeUnit;
/**
* 安全常量
* @author huwei
* @date 2023/11/4 16:15
*/
public class SecurityConstant {
/**
* 证书
*/
public static final String CERTIFICATE_NAME = "certificate";
/**
* 证书过期时间
*/
public static long CERTIFICATE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(15);
}
package com.ssyf.common.security.domain;
import com.ssyf.common.security.constant.SecurityConstant;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* <p>证书类</p>
* <p>某事件的有效证明信息</p>
* @author huwei
* @date 2023/9/24 15:47
*/
@Data
@Accessors(chain = true)
public class Certificate<T> {
/**
* 证书类型
*/
private String type;
/**
* 事件源
* 示例:
* 1. 手机验证码证书,event为验证手机
* 2. 邮箱验证码证书,event为验证邮箱
*
*/
private String event;
/**
* 事件内容/证书内容
* 示例:
* 1. 手机验证码证书,content为当前手机的验证码
* 2. 邮箱验证码证书,content为当前手机的验证码
*/
private T content;
/**
* 签证时间戳
*/
private long startTimeMillis = System.currentTimeMillis();
/**
* 到期时间戳
*/
private long endTimeMillis;
public Certificate(){}
public Certificate( String type, String event,T content) {
this(type ,event ,content , SecurityConstant.CERTIFICATE_TIMEOUT_MILLIS);
}
public Certificate(String type ,String event ,T content, long timeoutMillis) {
this.event = event;
this.type = type;
this.content = content;
this.endTimeMillis = startTimeMillis + timeoutMillis;
}
}
package com.ssyf.common.security.domain;
import lombok.Getter;
import lombok.experimental.Accessors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
/**
* 业务上下文
*
* @author huwei
* @date 2022/7/15 15:48
*/
@Getter
@Accessors(chain = true)
public class ClientContext {
/**
* 请求对象
*/
private HttpServletRequest request;
/**
* 请求响应对象
*/
private HttpServletResponse response;
/**
* 进入业务时间
*/
private LocalDateTime enterTime;
public ClientContext(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
this.enterTime = LocalDateTime.now();
}
}
package com.ssyf.common.security.domain;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 客户信息
* @author huwei
* @date 2023/11/4 9:53
*/
@Data
@Accessors(chain = true)
public class ClientInfo {
/**
* 客户id
*/
private Object id;
private Integer intId;
/**
* 客户名称
*/
private String nickname;
private String type;
/**
* 游客
*/
private boolean visitor = false;
/**
* 动态令牌,用户会话时生成的动态秘钥,用于对敏感数据加解密处理,或辅助特殊业务做数据验证处理
* <p>示例:用户登录之前需做验证码校验,此时可将验证码和登录业务分离,在用户完成验证码后使用动态秘钥生成证书,在登录验证证书是否有效即可</p>
*/
private String dys;
}
package com.ssyf.common.security.enums;
/**
* 证书类型
* @author huwei
* @date 2023/11/10 22:33
*/
public enum CertificateTypeEnum {
/**
* 全局通用
*/
global("*"),
/**
* 验证码
*/
captcha("captcha"),
/**
* 验证码,邮箱
*/
captchaMail("captchaMail"),
/**
* 验证码,短信
*/
captchaSms("captchaSms");
private String name;
CertificateTypeEnum(String name){
this.name = name;
}
public String getName() {
return this.name;
}
}
package com.ssyf.common.security.filter;
import com.ssyf.common.security.domain.ClientContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 客户端上下文持有类
*
* @author huwei
* @date 2023/9/17 10:39
*/
public class ClientContextHolder {
private static final ThreadLocal<ClientContext> CONTAINER = new InheritableThreadLocal<>();
public static ClientContext getContext() {
return CONTAINER.get();
}
static void init(HttpServletRequest request , HttpServletResponse response){
CONTAINER.set(new ClientContext(request ,response));
}
static void clear() {
CONTAINER.remove();
}
}
\ No newline at end of file
package com.ssyf.common.security.filter;
import com.ssyf.common.security.helper.ClientContextHelper;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 客户信息上下文初始化过滤器
* @author huwei
* @date 2022/7/17 15:53
*/
@Component
@Order(-7000)
public class ClientContextInitializationFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//设置客户端上下文
ClientContextHolder.init(request ,response);
//请求继续
filterChain.doFilter(servletRequest ,servletResponse);
//清理客户端上下文
ClientContextHolder.clear();
}
}
package com.ssyf.common.security.handler;
import com.ssyf.common.security.domain.ClientInfo;
/**
* 证书秘钥处理类
* @author huwei
* @date 2024/2/27 17:07
*/
public interface CertificateSecretHandler {
/**
* 获取密钥
* @param clientInfo
* @return
*/
String getSecret(ClientInfo clientInfo);
}
package com.ssyf.common.security.handler;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.json.JSONUtil;
import com.ssyf.common.web.apicode.ApiCodeServer;
import com.ssyf.common.web.apicode.ApiCodeUser;
import com.ssyf.common.web.result.Result;
/**
* 安全错误处理
* @author huwei
* @date 2023/11/5 10:35
*/
public interface SecurityErrorHandler {
/**
* 未登录处理
* @param e
* @return
*/
default Object notLoginHandle(NotLoginException e){
// 设置响应头
SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
return JSONUtil.toJsonStr(Result.fail(ApiCodeUser.NOT_AUTHORIZED));
}
/**
* 没权限处理
* @param e
* @return
*/
default Object notPermissionHandle(NotPermissionException e){
// 设置响应头
SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
return JSONUtil.toJsonStr(Result.fail(ApiCodeUser.NOT_AUTHORIZED));
}
/**
* 没角色权限处理
* @param e
* @return
*/
default Object notRoleHandle(NotRoleException e){
// 设置响应头
SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
return JSONUtil.toJsonStr(Result.fail(ApiCodeUser.NOT_AUTHORIZED));
}
/**
* 其它异常处理
* @param e
* @return
*/
default Object otherHandle(Throwable e){
e.printStackTrace();
// 设置响应头
SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
return JSONUtil.toJsonStr(Result.fail(ApiCodeServer.ERROR));
}
}
package com.ssyf.common.security.handler.impl;
import com.ssyf.common.security.domain.ClientInfo;
import com.ssyf.common.security.handler.CertificateSecretHandler;
import org.springframework.stereotype.Component;
/**
* 默认实现
* @author huwei
* @date 2024/2/27 17:18
*/
@Component
public class CertificateSecretHandlerImpl implements CertificateSecretHandler {
@Override
public String getSecret(ClientInfo clientInfo) {
return clientInfo.getDys();
}
}
package com.ssyf.common.security.handler.impl;
import com.ssyf.common.security.handler.SecurityErrorHandler;
import org.springframework.stereotype.Component;
/**
* 安全异常处理
* @author huwei
* @date 2023/11/5 10:53
*/
@Component
public class SecurityErrorHandlerImpl implements SecurityErrorHandler {
}
package com.ssyf.common.security.helper;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.ssyf.common.security.domain.Certificate;
import com.ssyf.common.security.holder.CertificateSecretHandlerHolder;
/**
* 证书工具
* @author huwei
* @date 2023/9/24 15:51
*/
public class CertificateHelper {
/**
* 获取秘钥
* @return
*/
public static byte[] getSecret(){
return CertificateSecretHandlerHolder.holder().getSecret(ClientContextHelper.getClientInfo()).getBytes();
}
/**
* 生成证书
* @param certificate
* @return
*/
public static String generate(Certificate certificate){
return generate(getSecret() ,certificate);
}
/**
* 生成证书
* @param secret
* @param certificate
* @return
*/
public static String generate(byte[] secret , Certificate certificate){
return SecureUtil.aes(secret).encryptBase64(JSONUtil.toJsonStr(certificate));
}
/**
* 解析证书并获取证书对象
* @param certificate
* @return
* @param <T>
*/
public static <T> Certificate<T> parse(String certificate){
return parse(getSecret() ,certificate);
}
/**
* 解析证书并获取证书对象
* @param secret
* @param certificate
* @param <T>
* @return
*/
public static <T> Certificate<T> parse(byte[] secret , String certificate){
if(secret == null || StrUtil.isBlank(certificate)){
return null;
}
try{
String json = SecureUtil.aes(secret).decryptStr(certificate);
Certificate<T> result = JSONUtil.toBean(json , Certificate.class);
return result;
}catch (Exception e){
//解析失败,忽略错误,返回null
}
return null;
}
/**
* 是否过期
* @param certificate
* @return
*/
public static boolean isTimeout(Certificate certificate){
return certificate == null || System.currentTimeMillis() > certificate.getEndTimeMillis();
}
/**
* 校验证书
* @param certificate
* @return
*/
public static boolean valid(String certificate){
return valid(parse(getSecret(),certificate));
}
/**
* 校验证书
* @param secret
* @param certificate
* @return
*/
public static boolean valid(String secret ,String certificate){
return valid(parse(secret.getBytes() ,certificate));
}
/**
* 校验证书
* @param certificate
* @return
*/
public static boolean valid(Certificate certificate){
return certificate != null && !isTimeout(certificate);
}
/**
* 校验证书
* @param secret
* @param source
* @param certificateStr
* @return
*/
public static boolean valid(String secret ,Certificate source ,String certificateStr){
return valid(source ,parse(secret.getBytes() ,certificateStr));
}
/**
* 校验证书
* @param source
* @param target
* @return
*/
public static boolean valid(Certificate source , Certificate target){
//有效性
if(!valid(target)){
return false;
}
if(StrUtil.isNotBlank(source.getEvent()) && !source.getEvent().equals(target.getEvent())){
return false;
}
if(StrUtil.isNotBlank(source.getType()) && !source.getType().equals(target.getType())){
return false;
}
if(source.getContent() != null && StrUtil.isNotBlank(source.getContent().toString()) && !source.getContent().equals(target.getContent())){
return false;
}
return true;
}
}
package com.ssyf.common.security.helper;
import cn.dev33.satoken.stp.SaLoginConfig;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWTUtil;
import com.ssyf.common.security.domain.ClientInfo;
/**
* 登录帮助类
* @author huwei
* @date 2023/11/5 20:35
*/
public class LoginHelper {
/**
* 登录
* @param clientInfo
*/
public static void login(ClientInfo clientInfo){
//设置秘钥
if(StrUtil.isBlank(clientInfo.getDys())){
clientInfo.setDys(createDynamicById(clientInfo.getId()));
}
//设置属性值
SaLoginModel saLoginModel = SaLoginConfig.create();
BeanUtil.beanToMap(clientInfo).entrySet().forEach(entry->{
if(entry.getValue() == null){
return;
}
saLoginModel.setExtra(entry.getKey() ,entry.getValue());
});
StpUtil.login(clientInfo.getId() ,saLoginModel);
}
/**
* 解析并获取客户信息,从当前登录信息中
* @return
*/
public static ClientInfo parseAndGetFormCurrentLogin(){
return parseAndGet(StpUtil.getTokenValue());
}
/**
* 是否登录
* @return
*/
public static boolean isLogin(){
return StpUtil.isLogin();
}
/**
* 解析数据
* @param token
* @return
*/
public static ClientInfo parseAndGet(String token){
return JWTUtil.parseToken(token).getPayloads().toBean(ClientInfo.class);
}
/**
* 创建动态秘钥
* @param id
* @return
*/
public static String createDynamicById(Object id){
return IdUtil.simpleUUID().substring(16);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package com.ssyf.common.web.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface RequestSynchronization {
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment