Feature/server java check style & p3c 框架相关改造 (#273)
* checkstyle P3C和相关框架优化 * checkstyle P3C和相关框架优化 --------- Co-authored-by: zdl <zdl@qq.com>
This commit is contained in:
parent
0cd3e312e7
commit
1125661c1f
91
pom.xml
91
pom.xml
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<modules>
|
<modules>
|
||||||
|
<module>survey-checkstyle</module>
|
||||||
<module>survey-server</module>
|
<module>survey-server</module>
|
||||||
<module>survey-common</module>
|
<module>survey-common</module>
|
||||||
<module>survey-dal</module>
|
<module>survey-dal</module>
|
||||||
@ -72,6 +73,16 @@
|
|||||||
<artifactId>java-jwt</artifactId>
|
<artifactId>java-jwt</artifactId>
|
||||||
<version>${jwt.version}</version>
|
<version>${jwt.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>${fastjson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>${lang3.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
@ -123,6 +134,86 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<!-- PMD插件 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-pmd-plugin</artifactId>
|
||||||
|
<version>3.16.0</version>
|
||||||
|
<configuration>
|
||||||
|
<verbose>true</verbose>
|
||||||
|
<!-- 规范的配置 -->
|
||||||
|
<rulesets>
|
||||||
|
<ruleset>rulesets/java/ali-concurrent.xml</ruleset>
|
||||||
|
<ruleset>rulesets/java/ali-constant.xml</ruleset>
|
||||||
|
<ruleset>rulesets/java/ali-exception.xml</ruleset>
|
||||||
|
<ruleset>rulesets/java/ali-flowcontrol.xml</ruleset>
|
||||||
|
<ruleset>rulesets/java/ali-naming.xml</ruleset>
|
||||||
|
<ruleset>rulesets/java/ali-oop.xml</ruleset>
|
||||||
|
<ruleset>rulesets/java/ali-orm.xml</ruleset>
|
||||||
|
<ruleset>rulesets/java/ali-other.xml</ruleset>
|
||||||
|
<ruleset>rulesets/java/ali-set.xml</ruleset>
|
||||||
|
</rulesets>
|
||||||
|
<printFailingErrors>true</printFailingErrors>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>pmd-check-verify</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.p3c</groupId>
|
||||||
|
<artifactId>p3c-pmd</artifactId>
|
||||||
|
<version>2.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||||
|
<version>3.1.2</version>
|
||||||
|
<configuration>
|
||||||
|
<configLocation>checkstyle-rule.xml</configLocation>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
<logViolationsToConsole>true</logViolationsToConsole>
|
||||||
|
<consoleOutput>true</consoleOutput>
|
||||||
|
<failsOnError>true</failsOnError>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>validate</id>
|
||||||
|
<phase>validate</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.xiaojusurvey.engine</groupId>
|
||||||
|
<artifactId>survey-checkstyle</artifactId>
|
||||||
|
<version>base</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.puppycrawl.tools</groupId>
|
||||||
|
<artifactId>checkstyle</artifactId>
|
||||||
|
<version>8.41.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.19.1</version>
|
||||||
|
<configuration>
|
||||||
|
<skip>false</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
</build>
|
</build>
|
||||||
|
25
survey-checkstyle/pom.xml
Normal file
25
survey-checkstyle/pom.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>survey-engine</artifactId>
|
||||||
|
<groupId>com.xiaojusurvey.engine</groupId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>survey-checkstyle</artifactId>
|
||||||
|
<version>base</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<skip>true</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
176
survey-checkstyle/src/main/resources/checkstyle-rule.xml
Normal file
176
survey-checkstyle/src/main/resources/checkstyle-rule.xml
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||||
|
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
||||||
|
|
||||||
|
<module name="Checker">
|
||||||
|
|
||||||
|
<!-- 检查文件是否以一个空行结束 -->
|
||||||
|
<module name="NewlineAtEndOfFile"/>
|
||||||
|
|
||||||
|
<!-- 文件长度不超过1500行 -->
|
||||||
|
<module name="FileLength">
|
||||||
|
<property name="max" value="1500"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- 每个java文件一个语法树 -->
|
||||||
|
<module name="TreeWalker">
|
||||||
|
<!-- import检查-->
|
||||||
|
<!-- 检查是否从非法的包中导入了类 -->
|
||||||
|
<module name="IllegalImport"/>
|
||||||
|
<!-- 检查是否导入了多余的包 -->
|
||||||
|
<module name="RedundantImport"/>
|
||||||
|
<!-- 没用的import检查,比如:1.没有被用到2.重复的3.import java.lang的4.import 与该类在同一个package的 -->
|
||||||
|
<module name="UnusedImports"/>
|
||||||
|
|
||||||
|
<!-- 注释检查 -->
|
||||||
|
<!-- 检查构造函数的javadoc -->
|
||||||
|
<module name="JavadocType">
|
||||||
|
<property name="allowUnknownTags" value="true"/>
|
||||||
|
<message key="javadoc.missing" value="类注释:缺少Javadoc注释。"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- 命名检查 -->
|
||||||
|
<!-- 局部的final变量,包括catch中的参数的检查 -->
|
||||||
|
<module name="LocalFinalVariableName"/>
|
||||||
|
<!-- 局部的非final型的变量,包括catch中的参数的检查 -->
|
||||||
|
<module name="LocalVariableName"/>
|
||||||
|
<!-- 包名的检查(只允许小写字母),默认^[a-z]+(\.[a-zA-Z_][a-zA-Z_0-9_]*)*$ -->
|
||||||
|
<module name="PackageName">
|
||||||
|
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
|
||||||
|
<message key="name.invalidPattern" value="包名 ''{0}'' 要符合 ''{1}''格式."/>
|
||||||
|
</module>
|
||||||
|
<!-- 仅仅是static型的变量(不包括static final型)的检查 -->
|
||||||
|
<module name="StaticVariableName"/>
|
||||||
|
<!-- Class或Interface名检查,默认^[A-Z][a-zA-Z0-9]*$-->
|
||||||
|
<module name="TypeName">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
<message key="name.invalidPattern" value="名称 ''{0}'' 要符合 ''{1}''格式."/>
|
||||||
|
</module>
|
||||||
|
<!-- 非static型变量的检查 -->
|
||||||
|
<module name="MemberName"/>
|
||||||
|
<!-- 方法名的检查 -->
|
||||||
|
<module name="MethodName"/>
|
||||||
|
<!-- 方法的参数名 -->
|
||||||
|
<module name="ParameterName "/>
|
||||||
|
<!-- 常量名的检查(只允许大写),默认^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$ -->
|
||||||
|
<module name="ConstantName"/>
|
||||||
|
|
||||||
|
<!-- 定义检查 -->
|
||||||
|
<!-- 检查数组类型定义的样式 -->
|
||||||
|
<module name="ArrayTypeStyle"/>
|
||||||
|
<!-- 检查long型定义是否有大写的“L” -->
|
||||||
|
<module name="UpperEll"/>
|
||||||
|
<!-- 方法不超过50行 -->
|
||||||
|
<module name="MethodLength">
|
||||||
|
<property name="tokens" value="METHOD_DEF"/>
|
||||||
|
<property name="max" value="50"/>
|
||||||
|
</module>
|
||||||
|
<!-- 方法的参数个数不超过5个。 并且不对构造方法进行检查-->
|
||||||
|
<module name="ParameterNumber">
|
||||||
|
<property name="max" value="5"/>
|
||||||
|
<property name="ignoreOverriddenMethods" value="true"/>
|
||||||
|
<property name="tokens" value="METHOD_DEF"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- 空格检查-->
|
||||||
|
<!-- 方法名后跟左圆括号"(" -->
|
||||||
|
<module name="MethodParamPad"/>
|
||||||
|
<!-- 在类型转换时,不允许左圆括号右边有空格,也不允许与右圆括号左边有空格 -->
|
||||||
|
<module name="TypecastParenPad"/>
|
||||||
|
<!-- 检查在某个特定关键字之后应保留空格 -->
|
||||||
|
<module name="NoWhitespaceAfter"/>
|
||||||
|
<!-- 检查在某个特定关键字之前应保留空格 -->
|
||||||
|
<module name="NoWhitespaceBefore"/>
|
||||||
|
<!-- 操作符换行策略检查 -->
|
||||||
|
<module name="OperatorWrap"/>
|
||||||
|
<!-- 圆括号空白 -->
|
||||||
|
<module name="ParenPad"/>
|
||||||
|
<!-- 检查分隔符是否在空白之后 -->
|
||||||
|
<module name="WhitespaceAfter"/>
|
||||||
|
<!-- 检查分隔符周围是否有空白 -->
|
||||||
|
<module name="WhitespaceAround"/>
|
||||||
|
|
||||||
|
<!-- 修饰符检查 -->
|
||||||
|
<!-- 检查修饰符的顺序是否遵照java语言规范,默认public、protected、private、abstract、static、final、transient、volatile、synchronized、native、strictfp -->
|
||||||
|
<module name="ModifierOrder"/>
|
||||||
|
<!-- 检查接口和annotation中是否有多余修饰符,如接口方法不必使用public -->
|
||||||
|
<module name="RedundantModifier"/>
|
||||||
|
|
||||||
|
<!-- 代码块检查 -->
|
||||||
|
<!-- 检查是否有嵌套代码块 -->
|
||||||
|
<module name="AvoidNestedBlocks"/>
|
||||||
|
<!-- 检查是否有空代码块 -->
|
||||||
|
<module name="EmptyBlock"/>
|
||||||
|
<!-- 检查左大括号位置 -->
|
||||||
|
<module name="LeftCurly"/>
|
||||||
|
<!-- 检查代码块是否缺失{} -->
|
||||||
|
<module name="NeedBraces"/>
|
||||||
|
<!-- 检查右大括号位置 -->
|
||||||
|
<module name="RightCurly"/>
|
||||||
|
|
||||||
|
<!-- 代码检查 -->
|
||||||
|
<!-- 检查空的代码段 -->
|
||||||
|
<module name="EmptyStatement"/>
|
||||||
|
<!-- 检查在重写了equals方法后是否重写了hashCode方法 -->
|
||||||
|
<module name="EqualsHashCode"/>
|
||||||
|
<!-- 检查局部变量或参数是否隐藏了类中的变量 -->
|
||||||
|
<module name="HiddenField">
|
||||||
|
<property name="tokens" value="VARIABLE_DEF"/>
|
||||||
|
</module>
|
||||||
|
<!-- 检查子表达式中是否有赋值操作 -->
|
||||||
|
<module name="InnerAssignment"/>
|
||||||
|
<!-- 检查switch语句是否有default -->
|
||||||
|
<module name="MissingSwitchDefault"/>
|
||||||
|
<!-- 检查是否有过度复杂的布尔表达式 -->
|
||||||
|
<module name="SimplifyBooleanExpression"/>
|
||||||
|
<!-- 检查是否有过于复杂的布尔返回代码段 -->
|
||||||
|
<module name="SimplifyBooleanReturn"/>
|
||||||
|
|
||||||
|
<!-- 类设计检查 -->
|
||||||
|
<!-- 检查类是否为扩展设计l -->
|
||||||
|
<!-- 检查只有private构造函数的类是否声明为final -->
|
||||||
|
<module name="FinalClass"/>
|
||||||
|
<!-- 检查接口是否仅定义类型 -->
|
||||||
|
<module name="InterfaceIsType"/>
|
||||||
|
<!-- 检查类成员的可见度 检查类成员的可见性。只有static final 成员是public的
|
||||||
|
除非在本检查的protectedAllowed和packagedAllowed属性中进行了设置-->
|
||||||
|
<module name="VisibilityModifier">
|
||||||
|
<property name="packageAllowed" value="true"/>
|
||||||
|
<property name="protectedAllowed" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- 语法 -->
|
||||||
|
<!-- String的比较不能用!= 和 == -->
|
||||||
|
<module name="StringLiteralEquality"/>
|
||||||
|
<!-- 限制for循环最多嵌套2层 -->
|
||||||
|
<module name="NestedForDepth">
|
||||||
|
<property name="max" value="2"/>
|
||||||
|
</module>
|
||||||
|
<!-- if最多嵌套3层 -->
|
||||||
|
<module name="NestedIfDepth">
|
||||||
|
<property name="max" value="3"/>
|
||||||
|
</module>
|
||||||
|
<!-- 检查未被注释的main方法,排除以Appllication结尾命名的类 -->
|
||||||
|
<module name="UncommentedMain">
|
||||||
|
<property name="excludedClasses" value=".*[Application,Test]$"/>
|
||||||
|
</module>
|
||||||
|
<!-- 禁止使用System.out.println -->
|
||||||
|
<module name="Regexp">
|
||||||
|
<property name="format" value="System\.out\.println"/>
|
||||||
|
<property name="illegalPattern" value="true"/>
|
||||||
|
</module>
|
||||||
|
<!-- return个数 3个-->
|
||||||
|
<module name="ReturnCount">
|
||||||
|
<property name="max" value="3"/>
|
||||||
|
</module>
|
||||||
|
<!--try catch 异常处理数量 3-->
|
||||||
|
<module name="NestedTryDepth ">
|
||||||
|
<property name="max" value="3"/>
|
||||||
|
</module>
|
||||||
|
<!-- clone方法必须调用了super.clone() -->
|
||||||
|
<module name="SuperClone"/>
|
||||||
|
<!-- finalize 必须调用了super.finalize() -->
|
||||||
|
<module name="SuperFinalize"/>
|
||||||
|
</module>
|
||||||
|
</module>
|
@ -31,8 +31,6 @@
|
|||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- jwt-->
|
<!-- jwt-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.auth0</groupId>
|
<groupId>com.auth0</groupId>
|
||||||
|
@ -3,7 +3,6 @@ package com.xiaojusurvey.engine.common.entity;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class Status implements Serializable {
|
public class Status implements Serializable {
|
||||||
|
@ -5,11 +5,9 @@ import lombok.Data;
|
|||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: LYF
|
* @Author: LYF
|
||||||
@ -20,7 +18,7 @@ import java.util.ArrayList;
|
|||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ToString(callSuper = true)
|
@ToString(callSuper = true)
|
||||||
@Data
|
@Data
|
||||||
public class User extends BaseEntity{
|
public class User extends BaseEntity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户名
|
* 用户名
|
||||||
|
@ -14,15 +14,15 @@ public abstract class BaseException extends RuntimeException {
|
|||||||
|
|
||||||
public BaseException(String errorMsg, Integer code) {
|
public BaseException(String errorMsg, Integer code) {
|
||||||
super(errorMsg);
|
super(errorMsg);
|
||||||
this.code=code;
|
this.code = code;
|
||||||
this.errorMsg=errorMsg;
|
this.errorMsg = errorMsg;
|
||||||
this.message=errorMsg;
|
this.message = errorMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseException(String errorMsg, Integer code,Throwable t) {
|
public BaseException(String errorMsg, Integer code, Throwable t) {
|
||||||
super(errorMsg,t);
|
super(errorMsg, t);
|
||||||
this.code=code;
|
this.code = code;
|
||||||
this.errorMsg=errorMsg;
|
this.errorMsg = errorMsg;
|
||||||
this.message=errorMsg;
|
this.message = errorMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.xiaojusurvey.engine.common.exception;
|
package com.xiaojusurvey.engine.common.exception;
|
||||||
|
|
||||||
public class DaoException extends BaseException{
|
public class DaoException extends BaseException {
|
||||||
|
|
||||||
public DaoException(String errorMsg, Integer code) {
|
public DaoException(String errorMsg, Integer code) {
|
||||||
super(errorMsg, code);
|
super(errorMsg, code);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.xiaojusurvey.engine.core.auth;
|
package com.xiaojusurvey.engine.core.auth;
|
||||||
|
|
||||||
import com.xiaojusurvey.engine.common.entity.user.CaptchaVo;
|
import com.xiaojusurvey.engine.core.auth.vo.CaptchaVo;
|
||||||
import com.xiaojusurvey.engine.core.auth.domain.UserParam;
|
import com.xiaojusurvey.engine.core.auth.param.UserParam;
|
||||||
import com.xiaojusurvey.engine.core.auth.domain.UserVo;
|
import com.xiaojusurvey.engine.core.auth.vo.UserVo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: LYF
|
* @Author: LYF
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.xiaojusurvey.engine.core.auth.captcha;
|
package com.xiaojusurvey.engine.core.auth.captcha;
|
||||||
|
|
||||||
import com.xiaojusurvey.engine.common.entity.user.Captcha;
|
import com.xiaojusurvey.engine.common.entity.user.Captcha;
|
||||||
import com.xiaojusurvey.engine.common.entity.user.CaptchaVo;
|
import com.xiaojusurvey.engine.core.auth.vo.CaptchaVo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: LYF
|
* @Author: LYF
|
||||||
@ -11,6 +11,7 @@ import com.xiaojusurvey.engine.common.entity.user.CaptchaVo;
|
|||||||
public interface CaptchaGenerator {
|
public interface CaptchaGenerator {
|
||||||
/**
|
/**
|
||||||
* 根据文本生成SVG
|
* 根据文本生成SVG
|
||||||
|
*
|
||||||
* @param text
|
* @param text
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@ -19,16 +20,17 @@ public interface CaptchaGenerator {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成验证码持久对象
|
* 生成验证码持久对象
|
||||||
|
*
|
||||||
* @param length
|
* @param length
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
Captcha generateRandomText(int length);
|
Captcha generateRandomText(int length);
|
||||||
|
|
||||||
default CaptchaVo generateRandomSvg(Captcha captcha) {
|
default CaptchaVo generateRandomSvg(Captcha captcha) {
|
||||||
if (captcha == null || captcha.getId() == null){
|
if (captcha == null || captcha.getId() == null) {
|
||||||
throw new IllegalArgumentException("captcha or id is null");
|
throw new IllegalArgumentException("captcha or id is null");
|
||||||
}else {
|
} else {
|
||||||
return new CaptchaVo(captcha.getId(), generateRandomSvg(captcha.getText()));
|
return new CaptchaVo(captcha.getId(), generateRandomSvg(captcha.getText()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,18 +11,21 @@ import java.util.Random;
|
|||||||
* @Description: 简单验证码生成工具类
|
* @Description: 简单验证码生成工具类
|
||||||
*/
|
*/
|
||||||
@Service("simpleCaptchaGenerator")
|
@Service("simpleCaptchaGenerator")
|
||||||
public class SimpleCaptchaGenerator implements CaptchaGenerator {
|
public class SimpleCaptchaGenerator implements CaptchaGenerator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateRandomSvg(String text) {
|
public String generateRandomSvg(String text) {
|
||||||
// 生成包含验证码的 SVG 数据
|
// 生成包含验证码的 SVG 数据
|
||||||
String svgData = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"150\" height=\"50\" viewBox=\"0,0,150,50\">" +
|
String svgData = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"150\" height=\"50\" viewBox=\"0,0,150,50\">"
|
||||||
"<rect width=\"100%\" height=\"100%\" fill=\"#f0f0f0\"/>" +
|
+ "<rect width=\"100%\" height=\"100%\" fill=\"#f0f0f0\"/>"
|
||||||
"<text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" fill=\"#000\">" + text + "</text>" +
|
+ "<text x=\"50%\" y=\"50%\" dominant-baseline=\"middle\" text-anchor=\"middle\" fill=\"#000\">"
|
||||||
"</svg>";
|
+ text
|
||||||
|
+ "</text>"
|
||||||
|
+ "</svg>";
|
||||||
|
|
||||||
return svgData;
|
return svgData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Captcha generateRandomText(int length) {
|
public Captcha generateRandomText(int length) {
|
||||||
String chars = "123456789";
|
String chars = "123456789";
|
||||||
|
@ -3,13 +3,13 @@ package com.xiaojusurvey.engine.core.auth.impl;
|
|||||||
import com.xiaojusurvey.engine.common.constants.RespErrorCode;
|
import com.xiaojusurvey.engine.common.constants.RespErrorCode;
|
||||||
import com.xiaojusurvey.engine.common.entity.token.Token;
|
import com.xiaojusurvey.engine.common.entity.token.Token;
|
||||||
import com.xiaojusurvey.engine.common.entity.user.Captcha;
|
import com.xiaojusurvey.engine.common.entity.user.Captcha;
|
||||||
import com.xiaojusurvey.engine.common.entity.user.CaptchaVo;
|
import com.xiaojusurvey.engine.core.auth.vo.CaptchaVo;
|
||||||
import com.xiaojusurvey.engine.common.entity.user.User;
|
import com.xiaojusurvey.engine.common.entity.user.User;
|
||||||
import com.xiaojusurvey.engine.common.exception.ServiceException;
|
import com.xiaojusurvey.engine.common.exception.ServiceException;
|
||||||
import com.xiaojusurvey.engine.core.auth.AuthService;
|
import com.xiaojusurvey.engine.core.auth.AuthService;
|
||||||
import com.xiaojusurvey.engine.core.auth.captcha.CaptchaGenerator;
|
import com.xiaojusurvey.engine.core.auth.captcha.CaptchaGenerator;
|
||||||
import com.xiaojusurvey.engine.core.auth.domain.UserParam;
|
import com.xiaojusurvey.engine.core.auth.param.UserParam;
|
||||||
import com.xiaojusurvey.engine.core.auth.domain.UserVo;
|
import com.xiaojusurvey.engine.core.auth.vo.UserVo;
|
||||||
import com.xiaojusurvey.engine.core.auth.util.AuthUtil;
|
import com.xiaojusurvey.engine.core.auth.util.AuthUtil;
|
||||||
import com.xiaojusurvey.engine.core.auth.util.JwtTokenUtil;
|
import com.xiaojusurvey.engine.core.auth.util.JwtTokenUtil;
|
||||||
import com.xiaojusurvey.engine.core.user.UserService;
|
import com.xiaojusurvey.engine.core.user.UserService;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.xiaojusurvey.engine.core.auth.domain;
|
package com.xiaojusurvey.engine.core.auth.param;
|
||||||
|
|
||||||
import com.xiaojusurvey.engine.common.entity.user.User;
|
import com.xiaojusurvey.engine.common.entity.user.User;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
@ -1,6 +1,5 @@
|
|||||||
package com.xiaojusurvey.engine.core.auth.util;
|
package com.xiaojusurvey.engine.core.auth.util;
|
||||||
|
|
||||||
import com.xiaojusurvey.engine.common.entity.user.User;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@ -14,9 +13,10 @@ public class AuthUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成BCryptPasswordEncoder密码
|
* 生成BCryptPasswordEncoder密码
|
||||||
|
*
|
||||||
* @return 加密字符串
|
* @return 加密字符串
|
||||||
*/
|
*/
|
||||||
public static String encryptPassword(String password,String username) {
|
public static String encryptPassword(String password, String username) {
|
||||||
String inputWithSalt = password + username;
|
String inputWithSalt = password + username;
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
@ -32,5 +32,4 @@ public class AuthUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ package com.xiaojusurvey.engine.core.auth.util;
|
|||||||
import com.auth0.jwt.JWT;
|
import com.auth0.jwt.JWT;
|
||||||
import com.auth0.jwt.JWTVerifier;
|
import com.auth0.jwt.JWTVerifier;
|
||||||
import com.auth0.jwt.algorithms.Algorithm;
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
import com.auth0.jwt.interfaces.Claim;
|
|
||||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
import com.sun.javafx.binding.StringConstant;
|
|
||||||
import com.xiaojusurvey.engine.common.constants.RespErrorCode;
|
import com.xiaojusurvey.engine.common.constants.RespErrorCode;
|
||||||
import com.xiaojusurvey.engine.common.entity.token.Token;
|
import com.xiaojusurvey.engine.common.entity.token.Token;
|
||||||
import com.xiaojusurvey.engine.common.entity.user.User;
|
import com.xiaojusurvey.engine.common.entity.user.User;
|
||||||
@ -16,7 +14,6 @@ import org.springframework.util.StringUtils;
|
|||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -36,21 +33,23 @@ public class JwtTokenUtil {
|
|||||||
/**
|
/**
|
||||||
* 认证头
|
* 认证头
|
||||||
*/
|
*/
|
||||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 空格
|
* 空格
|
||||||
*/
|
*/
|
||||||
public static final String SPACE = " ";
|
private static final String SPACE = " ";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 令牌前缀正则表达式
|
* 令牌前缀正则表达式
|
||||||
*/
|
*/
|
||||||
public final static String BEARER_PATTERN = "^Bearer$";
|
private static final String BEARER_PATTERN = "^Bearer$";
|
||||||
|
|
||||||
|
private static final int LENGTH = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成token
|
* 生成token
|
||||||
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@ -64,11 +63,12 @@ public class JwtTokenUtil {
|
|||||||
.withExpiresAt(expiryDate)
|
.withExpiresAt(expiryDate)
|
||||||
.withJWTId(UUID.randomUUID().toString())
|
.withJWTId(UUID.randomUUID().toString())
|
||||||
.sign(Algorithm.HMAC256(secret));
|
.sign(Algorithm.HMAC256(secret));
|
||||||
return new Token(user.getUsername(), token,new Date(now.getTime() + expirationTime * HOUR_SECOND));
|
return new Token(user.getUsername(), token, new Date(now.getTime() + expirationTime * HOUR_SECOND));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解密访问令牌
|
* 解密访问令牌
|
||||||
|
*
|
||||||
* @param tokenString 令牌
|
* @param tokenString 令牌
|
||||||
* @return 密钥内容
|
* @return 密钥内容
|
||||||
*/
|
*/
|
||||||
@ -77,20 +77,19 @@ public class JwtTokenUtil {
|
|||||||
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();
|
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();
|
||||||
DecodedJWT jwt = verifier.verify(tokenString);
|
DecodedJWT jwt = verifier.verify(tokenString);
|
||||||
return jwt;
|
return jwt;
|
||||||
}catch (Exception e){
|
} catch (Exception e) {
|
||||||
throw new ServiceException(RespErrorCode.USER_NOT_EXISTS.getMessage(), RespErrorCode.USER_NOT_EXISTS.getCode());
|
throw new ServiceException(RespErrorCode.USER_NOT_EXISTS.getMessage(), RespErrorCode.USER_NOT_EXISTS.getCode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public DecodedJWT getTokenStrByRequest(HttpServletRequest request) {
|
public DecodedJWT getTokenStrByRequest(HttpServletRequest request) {
|
||||||
String header = request.getHeader(AUTHORIZATION_HEADER);
|
String header = request.getHeader(AUTHORIZATION_HEADER);
|
||||||
if (!StringUtils.hasText(header)) {
|
if (!StringUtils.hasText(header)) {
|
||||||
throw new ServiceException(RespErrorCode.USER_CREDENTIALS_ERROR.getMessage(), RespErrorCode.USER_CREDENTIALS_ERROR.getCode());
|
throw new ServiceException(RespErrorCode.USER_CREDENTIALS_ERROR.getMessage(), RespErrorCode.USER_CREDENTIALS_ERROR.getCode());
|
||||||
}
|
}
|
||||||
String[] splits = header.split(SPACE);
|
String[] splits = header.split(SPACE);
|
||||||
if (splits.length != 2) {
|
if (splits.length != LENGTH) {
|
||||||
throw new ServiceException(RespErrorCode.USER_CREDENTIALS_ERROR.getMessage(), RespErrorCode.USER_CREDENTIALS_ERROR.getCode());
|
throw new ServiceException(RespErrorCode.USER_CREDENTIALS_ERROR.getMessage(), RespErrorCode.USER_CREDENTIALS_ERROR.getCode());
|
||||||
}
|
}
|
||||||
if (!Pattern.matches(BEARER_PATTERN, splits[0])) {
|
if (!Pattern.matches(BEARER_PATTERN, splits[0])) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.xiaojusurvey.engine.common.entity.user;
|
package com.xiaojusurvey.engine.core.auth.vo;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -12,7 +12,7 @@ import lombok.NoArgsConstructor;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Data
|
@Data
|
||||||
public class CaptchaVo{
|
public class CaptchaVo {
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package com.xiaojusurvey.engine.core.auth.domain;
|
package com.xiaojusurvey.engine.core.auth.vo;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ import lombok.Data;
|
|||||||
* @Description: 用户vo
|
* @Description: 用户vo
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class UserVo{
|
public class UserVo {
|
||||||
|
|
||||||
private String token;
|
private String token;
|
||||||
private String username;
|
private String username;
|
@ -27,6 +27,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询用户
|
* 查询用户
|
||||||
|
*
|
||||||
* @param username
|
* @param username
|
||||||
* @param password
|
* @param password
|
||||||
* @return
|
* @return
|
||||||
@ -38,7 +39,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
query.addCriteria(Criteria.where("username").is(username).and("password").is(encryptPassword));
|
query.addCriteria(Criteria.where("username").is(username).and("password").is(encryptPassword));
|
||||||
//查询用户并返回
|
//查询用户并返回
|
||||||
User user = mongoRepository.findOne(query, User.class);
|
User user = mongoRepository.findOne(query, User.class);
|
||||||
if (ObjectUtils.isEmpty(user)){
|
if (ObjectUtils.isEmpty(user)) {
|
||||||
throw new ServiceException(RespErrorCode.USER_PASSWORD_ERROR.getMessage(), RespErrorCode.USER_PASSWORD_ERROR.getCode());
|
throw new ServiceException(RespErrorCode.USER_PASSWORD_ERROR.getMessage(), RespErrorCode.USER_PASSWORD_ERROR.getCode());
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
|
@ -3,7 +3,7 @@ package com.xiaojusurvey.engine.repository.enums;
|
|||||||
|
|
||||||
public enum ErrorEnum {
|
public enum ErrorEnum {
|
||||||
|
|
||||||
PARAM_NULL_ERROR("%s is null",1);
|
PARAM_NULL_ERROR("%s is null", 1);
|
||||||
|
|
||||||
private String errorMsg;
|
private String errorMsg;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ public class MongoRepositoryImpl implements MongoRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends BaseEntity> T findOne(Query query, Class<T> entityClass) {
|
public <T extends BaseEntity> T findOne(Query query, Class<T> entityClass) {
|
||||||
return mongoTemplate.findOne(query,entityClass);
|
return mongoTemplate.findOne(query, entityClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -9,6 +9,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class TestMessageProcessor implements MessageProcessor {
|
public class TestMessageProcessor implements MessageProcessor {
|
||||||
|
|
||||||
|
private static final int ORDER = 99;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void before(Invocation invocation) {
|
public void before(Invocation invocation) {
|
||||||
@ -19,4 +20,9 @@ public class TestMessageProcessor implements MessageProcessor {
|
|||||||
public Result after(Result result) {
|
public Result after(Result result) {
|
||||||
return new Result();
|
return new Result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return ORDER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,4 +5,6 @@ public interface ExtensionProcessor {
|
|||||||
void before(Invocation invocation);
|
void before(Invocation invocation);
|
||||||
|
|
||||||
Result after(Result result);
|
Result after(Result result);
|
||||||
|
|
||||||
|
int getOrder();
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
package com.xiaojusurvey.engine.extensions.processor.aop;
|
package com.xiaojusurvey.engine.extensions.processor.aop;
|
||||||
|
|
||||||
|
|
||||||
import com.xiaojusurvey.engine.extensions.processor.Invocation;
|
import com.xiaojusurvey.engine.extensions.processor.Invocation;
|
||||||
import com.xiaojusurvey.engine.extensions.processor.Result;
|
import com.xiaojusurvey.engine.extensions.processor.Result;
|
||||||
import com.xiaojusurvey.engine.extensions.processor.annotation.Message;
|
import com.xiaojusurvey.engine.extensions.router.BaseRouter;
|
||||||
import com.xiaojusurvey.engine.extensions.processor.annotation.Security;
|
|
||||||
import com.xiaojusurvey.engine.extensions.router.MessageProcessorRouter;
|
|
||||||
import com.xiaojusurvey.engine.extensions.router.SecurityProcessorRouter;
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Objects;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
public class ExtensionAspect {
|
public class ExtensionAspect implements InitializingBean {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MessageProcessorRouter messageProcessorRouter;
|
private List<BaseRouter> routerList;
|
||||||
|
|
||||||
|
private Map<Class, BaseRouter> routerMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SecurityProcessorRouter securityProcessorRouter;
|
|
||||||
|
|
||||||
@Pointcut("@annotation(com.xiaojusurvey.engine.extensions.processor.annotation.Message) || @annotation(com.xiaojusurvey.engine.extensions.processor.annotation.Security)")
|
@Pointcut("@annotation(com.xiaojusurvey.engine.extensions.processor.annotation.Message) || @annotation(com.xiaojusurvey.engine.extensions.processor.annotation.Security)")
|
||||||
public void pointcut() {
|
public void pointcut() {
|
||||||
@ -37,24 +37,25 @@ public class ExtensionAspect {
|
|||||||
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||||
MethodSignature signature = (MethodSignature) pjp.getSignature();
|
MethodSignature signature = (MethodSignature) pjp.getSignature();
|
||||||
Method method = signature.getMethod();
|
Method method = signature.getMethod();
|
||||||
Message message = method.getAnnotation(Message.class);
|
for (Map.Entry<Class, BaseRouter> routerEntry : routerMap.entrySet()) {
|
||||||
Security security = method.getAnnotation(Security.class);
|
if (null != method.getAnnotation(routerEntry.getKey())) {
|
||||||
if (!Objects.isNull(message)) {
|
Invocation invocation = new Invocation(method.getName(), method.getParameterTypes(), pjp.getArgs());
|
||||||
Invocation invocation = new Invocation(method.getName(), method.getParameterTypes(), pjp.getArgs());
|
routerEntry.getValue().before(invocation);
|
||||||
messageProcessorRouter.before(invocation);
|
}
|
||||||
}
|
|
||||||
if (!Objects.isNull(security)) {
|
|
||||||
Invocation invocation = new Invocation(method.getName(), method.getParameterTypes(), pjp.getArgs());
|
|
||||||
securityProcessorRouter.before(invocation);
|
|
||||||
}
|
}
|
||||||
Result result = new Result(pjp.proceed(pjp.getArgs()));
|
Result result = new Result(pjp.proceed(pjp.getArgs()));
|
||||||
if (!Objects.isNull(message)) {
|
for (Map.Entry<Class, BaseRouter> routerEntry : routerMap.entrySet()) {
|
||||||
result = messageProcessorRouter.after(result);
|
if (null != method.getAnnotation(routerEntry.getKey())) {
|
||||||
}
|
result = routerEntry.getValue().after(result);
|
||||||
if (!Objects.isNull(security)) {
|
}
|
||||||
result = securityProcessorRouter.after(result);
|
|
||||||
}
|
}
|
||||||
return result.getValue();
|
return result.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
if (!CollectionUtils.isEmpty(routerList)) {
|
||||||
|
routerList.forEach(e -> routerMap.put(e.annotationClass(), e));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,48 @@
|
|||||||
package com.xiaojusurvey.engine.extensions.router;
|
package com.xiaojusurvey.engine.extensions.router;
|
||||||
|
|
||||||
|
import com.xiaojusurvey.engine.extensions.processor.ExtensionProcessor;
|
||||||
import com.xiaojusurvey.engine.extensions.processor.Invocation;
|
import com.xiaojusurvey.engine.extensions.processor.Invocation;
|
||||||
import com.xiaojusurvey.engine.extensions.processor.Result;
|
import com.xiaojusurvey.engine.extensions.processor.Result;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
public abstract class BaseRouter implements ApplicationContextAware {
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class BaseRouter<T extends ExtensionProcessor> implements ApplicationContextAware, InitializingBean {
|
||||||
|
|
||||||
protected ApplicationContext applicationContext;
|
protected ApplicationContext applicationContext;
|
||||||
|
|
||||||
public abstract void before(Invocation invocation);
|
protected List<T> extensionProcessors = new ArrayList<>();
|
||||||
|
|
||||||
public abstract Result after(Result result);
|
public void before(Invocation invocation) {
|
||||||
|
for (T extensionProcessor : extensionProcessors) {
|
||||||
|
extensionProcessor.before(invocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result after(Result result) {
|
||||||
|
for (T extensionProcessor : extensionProcessors) {
|
||||||
|
result = extensionProcessor.after(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Class<?> annotationClass();
|
||||||
|
|
||||||
|
protected void initExtensionProcessors(Class<T> clazz) {
|
||||||
|
Map<String, T> messageProcessorMap = applicationContext.getBeansOfType(clazz);
|
||||||
|
if (!CollectionUtils.isEmpty(messageProcessorMap)) {
|
||||||
|
messageProcessorMap.entrySet().stream().forEach(e -> extensionProcessors.add(e.getValue()));
|
||||||
|
extensionProcessors.sort(Comparator.comparing(ExtensionProcessor::getOrder));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
|
@ -1,42 +1,21 @@
|
|||||||
package com.xiaojusurvey.engine.extensions.router;
|
package com.xiaojusurvey.engine.extensions.router;
|
||||||
|
|
||||||
import com.xiaojusurvey.engine.extensions.message.MessageProcessor;
|
import com.xiaojusurvey.engine.extensions.message.MessageProcessor;
|
||||||
import com.xiaojusurvey.engine.extensions.processor.Invocation;
|
import com.xiaojusurvey.engine.extensions.processor.annotation.Message;
|
||||||
import com.xiaojusurvey.engine.extensions.processor.Result;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MessageProcessorRouter extends BaseRouter implements InitializingBean {
|
@Order(0)
|
||||||
|
public class MessageProcessorRouter extends BaseRouter<MessageProcessor> {
|
||||||
private List<MessageProcessor> messageProcessorList = new ArrayList<>();
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
Map<String, MessageProcessor> messageProcessorMap = applicationContext.getBeansOfType(MessageProcessor.class);
|
initExtensionProcessors(MessageProcessor.class);
|
||||||
if (!CollectionUtils.isEmpty(messageProcessorMap)) {
|
|
||||||
messageProcessorMap.entrySet().stream().forEach(e -> messageProcessorList.add(e.getValue()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void before(Invocation invocation) {
|
public Class<?> annotationClass() {
|
||||||
for (MessageProcessor messageProcessor : messageProcessorList) {
|
return Message.class;
|
||||||
messageProcessor.before(invocation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result after(Result result) {
|
|
||||||
for (MessageProcessor messageProcessor : messageProcessorList) {
|
|
||||||
result = messageProcessor.after(result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,21 @@
|
|||||||
package com.xiaojusurvey.engine.extensions.router;
|
package com.xiaojusurvey.engine.extensions.router;
|
||||||
|
|
||||||
import com.xiaojusurvey.engine.extensions.processor.Invocation;
|
import com.xiaojusurvey.engine.extensions.processor.annotation.Security;
|
||||||
import com.xiaojusurvey.engine.extensions.processor.Result;
|
|
||||||
import com.xiaojusurvey.engine.extensions.security.SecurityProcessor;
|
import com.xiaojusurvey.engine.extensions.security.SecurityProcessor;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class SecurityProcessorRouter extends BaseRouter implements InitializingBean {
|
@Order(1)
|
||||||
|
public class SecurityProcessorRouter extends BaseRouter<SecurityProcessor> {
|
||||||
private List<SecurityProcessor> securityProcessorList = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
Map<String, SecurityProcessor> messageProcessorMap = applicationContext.getBeansOfType(SecurityProcessor.class);
|
initExtensionProcessors(SecurityProcessor.class);
|
||||||
if (!CollectionUtils.isEmpty(messageProcessorMap)) {
|
|
||||||
messageProcessorMap.entrySet().stream().forEach(e -> securityProcessorList.add(e.getValue()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void before(Invocation invocation) {
|
public Class<?> annotationClass() {
|
||||||
for (SecurityProcessor securityProcessor : securityProcessorList) {
|
return Security.class;
|
||||||
securityProcessor.before(invocation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result after(Result result) {
|
|
||||||
for (SecurityProcessor securityProcessor : securityProcessorList) {
|
|
||||||
result= securityProcessor.after(result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class TestSecurityProcessor implements SecurityProcessor {
|
public class TestSecurityProcessor implements SecurityProcessor {
|
||||||
|
|
||||||
|
private static final int ORDER = 100;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void before(Invocation invocation) {
|
public void before(Invocation invocation) {
|
||||||
@ -18,4 +19,9 @@ public class TestSecurityProcessor implements SecurityProcessor {
|
|||||||
public Result after(Result result) {
|
public Result after(Result result) {
|
||||||
return new Result();
|
return new Result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return ORDER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,21 +35,16 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>fastjson</artifactId>
|
<artifactId>fastjson</artifactId>
|
||||||
<version>${fastjson.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>${lang3.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -11,20 +11,21 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局异常处理器
|
* 全局异常处理器
|
||||||
|
*
|
||||||
* @Author: LYF
|
* @Author: LYF
|
||||||
* @CreateTime: 2024-06-05
|
* @CreateTime: 2024-06-05
|
||||||
* @Description: 全局异常处理器
|
* @Description: 全局异常处理器
|
||||||
*/
|
*/
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
public class ValidatedExceptionHandler {
|
public class ValidatedExceptionHandler {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ValidatedExceptionHandler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ValidatedExceptionHandler.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义验证异常
|
* 自定义验证异常
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(BindException.class)
|
@ExceptionHandler(BindException.class)
|
||||||
public RpcResult handleBindException(BindException e) {
|
public RpcResult handleBindException(BindException e) {
|
||||||
log.error(e.getMessage(), e);
|
LOG.error(e.getMessage(), e);
|
||||||
String message = e.getAllErrors().get(0).getDefaultMessage();
|
String message = e.getAllErrors().get(0).getDefaultMessage();
|
||||||
return RpcResultUtil.createFailedResult(RespErrorCode.PARAMETER_ERROR.getCode(), message);
|
return RpcResultUtil.createFailedResult(RespErrorCode.PARAMETER_ERROR.getCode(), message);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.xiaojusurvey.engine.controller;
|
package com.xiaojusurvey.engine.controller;
|
||||||
|
|
||||||
import com.xiaojusurvey.engine.common.entity.user.CaptchaVo;
|
import com.xiaojusurvey.engine.core.auth.vo.CaptchaVo;
|
||||||
import com.xiaojusurvey.engine.core.auth.domain.UserParam;
|
import com.xiaojusurvey.engine.core.auth.param.UserParam;
|
||||||
import com.xiaojusurvey.engine.core.auth.domain.UserVo;
|
import com.xiaojusurvey.engine.core.auth.vo.UserVo;
|
||||||
import com.xiaojusurvey.engine.common.rpc.RpcResult;
|
import com.xiaojusurvey.engine.common.rpc.RpcResult;
|
||||||
import com.xiaojusurvey.engine.common.util.RpcResultUtil;
|
import com.xiaojusurvey.engine.common.util.RpcResultUtil;
|
||||||
import com.xiaojusurvey.engine.core.auth.AuthService;
|
import com.xiaojusurvey.engine.core.auth.AuthService;
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
package com.xiaojusurvey.engine.interceptor;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.slf4j.MDC;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.StopWatch;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
|
||||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
|
||||||
import org.springframework.web.util.WebUtils;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class LogFilter extends OncePerRequestFilter {
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
|
||||||
|
|
||||||
public static final String TRACE_ID = "traceId";
|
|
||||||
public static final int SLOW_TIME_MILLIS = 5000;
|
|
||||||
public static final int LOG_MAX_LENGTH = 1024;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
|
||||||
String url = null;
|
|
||||||
//计时器
|
|
||||||
StopWatch watch = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取url
|
|
||||||
url = getCompleteUrl(request);
|
|
||||||
// 设置traceId
|
|
||||||
String traceId = request.getHeader(TRACE_ID);
|
|
||||||
if (StringUtils.isBlank(traceId)) {
|
|
||||||
traceId = UUID.randomUUID().toString();
|
|
||||||
}
|
|
||||||
MDC.put(TRACE_ID, traceId);
|
|
||||||
|
|
||||||
request = new ContentCachingRequestWrapper(request);
|
|
||||||
response = new ContentCachingResponseWrapper(response);
|
|
||||||
|
|
||||||
watch = new StopWatch();
|
|
||||||
watch.start();
|
|
||||||
|
|
||||||
logger.info("Start request, ip:{} host:{} url:{} body:{}", getRemoteIP(request),
|
|
||||||
request.getRemoteHost(), url, getRequestBody(request));
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (watch != null) {
|
|
||||||
watch.stop();
|
|
||||||
if (watch.getTotalTimeMillis() > SLOW_TIME_MILLIS && !url.endsWith(".js")) {
|
|
||||||
logger.error("End slow request, ip:{} host:{} url:{} cost:{} ms resp:{}", getRemoteIP(request),
|
|
||||||
request.getRemoteHost(), url, watch.getTotalTimeMillis(), getResponseBody(response));
|
|
||||||
} else {
|
|
||||||
logger.info("End request, ip:{} host:{} url:{} cost:{} ms resp:{}", getRemoteIP(request),
|
|
||||||
request.getRemoteHost(), url, watch.getTotalTimeMillis(), getResponseBody(response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MDC.clear();
|
|
||||||
updateResponse(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String getRequestBody(HttpServletRequest request) {
|
|
||||||
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
|
|
||||||
if (wrapper == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return getBody(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**获取body 避免过长
|
|
||||||
*
|
|
||||||
* @param contentAsByteArray 请求体或响应体
|
|
||||||
* @param characterEncoding 字符集
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private String getBody(byte[] contentAsByteArray, String characterEncoding) {
|
|
||||||
if (contentAsByteArray == null) return null;
|
|
||||||
String body = "";
|
|
||||||
try {
|
|
||||||
body = new String(contentAsByteArray, characterEncoding);
|
|
||||||
// 最多打印1024字符
|
|
||||||
if (body.length() > LOG_MAX_LENGTH) {
|
|
||||||
body = body.substring(0, LOG_MAX_LENGTH) + "...";
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getResponseBody(HttpServletResponse response) {
|
|
||||||
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
|
|
||||||
if (responseWrapper == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return getBody(responseWrapper.getContentAsByteArray(), "UTF-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateResponse(HttpServletResponse response) throws IOException {
|
|
||||||
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
|
|
||||||
Objects.requireNonNull(responseWrapper).copyBodyToResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCompleteUrl(HttpServletRequest request) {
|
|
||||||
String url = request.getRequestURL().toString();
|
|
||||||
String queryString = "";
|
|
||||||
if (StringUtils.isNotEmpty(request.getQueryString())) {
|
|
||||||
try {
|
|
||||||
queryString = URLDecoder.decode(request.getQueryString(), "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
logger.error("URLDecoder.decode error:" + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
url = url + "?" + queryString;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String getRemoteIP(HttpServletRequest request) {
|
|
||||||
if (request.getHeader("x-forwarded-for") == null) {
|
|
||||||
return request.getRemoteAddr();
|
|
||||||
}
|
|
||||||
return request.getHeader("x-forwarded-for");
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,9 +8,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class SurveyWebMvcConfigurer implements WebMvcConfigurer {
|
public class SurveyWebMvcConfigurer implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public LoginInterceptor loginInterceptor(){
|
public LoginInterceptor loginInterceptor() {
|
||||||
return new LoginInterceptor();
|
return new LoginInterceptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
server.port=8080
|
server.port=8080
|
||||||
|
|
||||||
spring.data.mongodb.uri=
|
spring.data.mongodb.uri=
|
||||||
spring.data.mongodb.database=
|
#spring.data.mongodb.database=
|
||||||
|
|
||||||
|
spring.data.mongodb.host=localhost
|
||||||
|
spring.data.mongodb.port=27017
|
||||||
|
spring.data.mongodb.database=testdb
|
||||||
|
|
||||||
|
XIAOJU_SURVEY_JWT_EXPIRES_IN=8
|
||||||
|
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
server.port=8080
|
server.port=8080
|
||||||
|
|
||||||
spring.data.mongodb.uri=
|
spring.data.mongodb.uri=
|
||||||
spring.data.mongodb.database=
|
spring.data.mongodb.database=
|
||||||
|
|
||||||
|
XIAOJU_SURVEY_JWT_EXPIRES_IN=8
|
||||||
|
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret
|
||||||
|
@ -1 +1,7 @@
|
|||||||
server.port=8080
|
server.port=8080
|
||||||
|
|
||||||
|
spring.data.mongodb.uri=
|
||||||
|
spring.data.mongodb.database=
|
||||||
|
|
||||||
|
XIAOJU_SURVEY_JWT_EXPIRES_IN=8
|
||||||
|
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret
|
||||||
|
@ -1,6 +1 @@
|
|||||||
spring.profiles.active=@spring.profiles.active@
|
spring.profiles.active=@spring.profiles.active@
|
||||||
|
|
||||||
|
|
||||||
#??
|
|
||||||
XIAOJU_SURVEY_JWT_EXPIRES_IN=8
|
|
||||||
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret
|
|
@ -1,10 +1,10 @@
|
|||||||
package com.xiaojusurvey.engine.controller;
|
package com.xiaojusurvey.engine.controller;
|
||||||
|
|
||||||
import com.xiaojusurvey.engine.SurveyApplication;
|
import com.xiaojusurvey.engine.SurveyApplication;
|
||||||
import com.xiaojusurvey.engine.common.entity.user.CaptchaVo;
|
import com.xiaojusurvey.engine.core.auth.vo.CaptchaVo;
|
||||||
import com.xiaojusurvey.engine.common.rpc.RpcResult;
|
import com.xiaojusurvey.engine.common.rpc.RpcResult;
|
||||||
import com.xiaojusurvey.engine.core.auth.domain.UserParam;
|
import com.xiaojusurvey.engine.core.auth.param.UserParam;
|
||||||
import com.xiaojusurvey.engine.core.auth.domain.UserVo;
|
import com.xiaojusurvey.engine.core.auth.vo.UserVo;
|
||||||
import com.xiaojusurvey.engine.core.auth.util.JwtTokenUtil;
|
import com.xiaojusurvey.engine.core.auth.util.JwtTokenUtil;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -23,8 +23,6 @@ public class AuthTest {
|
|||||||
@Resource
|
@Resource
|
||||||
private JwtTokenUtil jwtTokenUtil;
|
private JwtTokenUtil jwtTokenUtil;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
AuthController authController;
|
AuthController authController;
|
||||||
|
|
||||||
@ -59,5 +57,4 @@ public class AuthTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user