Kaynağa Gözat

first commit

gufj 7 ay önce
işleme
ae50674667
25 değiştirilmiş dosya ile 1847 ekleme ve 0 silme
  1. 33 0
      .gitignore
  2. 122 0
      pom.xml
  3. 14 0
      src/main/java/com/cloud/bound/link/plate/sso/SsoApplication.java
  4. 37 0
      src/main/java/com/cloud/bound/link/plate/sso/controller/LoginController.java
  5. 37 0
      src/main/java/com/cloud/bound/link/plate/sso/domain/LoginForm.java
  6. 33 0
      src/main/java/com/cloud/bound/link/plate/sso/domain/LoginResultVO.java
  7. 96 0
      src/main/java/com/cloud/bound/link/plate/sso/domain/RequestEmployee.java
  8. 52 0
      src/main/java/com/cloud/bound/link/plate/sso/domain/RequestUser.java
  9. 134 0
      src/main/java/com/cloud/bound/link/plate/sso/domain/ResponseDTO.java
  10. 96 0
      src/main/java/com/cloud/bound/link/plate/sso/enumeration/BaseEnum.java
  11. 29 0
      src/main/java/com/cloud/bound/link/plate/sso/enumeration/DataTypeEnum.java
  12. 34 0
      src/main/java/com/cloud/bound/link/plate/sso/enumeration/UserTypeEnum.java
  13. 524 0
      src/main/java/com/cloud/bound/link/plate/sso/service/LoginService.java
  14. 23 0
      src/main/java/com/cloud/bound/link/plate/sso/strategy/LoginStrategy.java
  15. 17 0
      src/main/resources/dev/application.yaml
  16. 118 0
      src/main/resources/dev/log4j2-spring.xml
  17. 18 0
      src/main/resources/dev/spy.properties
  18. 15 0
      src/main/resources/pre/application.yaml
  19. 118 0
      src/main/resources/pre/log4j2-spring.xml
  20. 15 0
      src/main/resources/prod/application.yaml
  21. 118 0
      src/main/resources/prod/log4j2-spring.xml
  22. 15 0
      src/main/resources/test/application.yaml
  23. 118 0
      src/main/resources/test/log4j2-spring.xml
  24. 18 0
      src/main/resources/test/spy.properties
  25. 13 0
      src/test/java/com/cloud/bound/link/plate/sso/SsoApplicationTests.java

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 122 - 0
pom.xml

@@ -0,0 +1,122 @@
+<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.example</groupId>
+    <artifactId>BoundLink-Plate-SSO</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>demo</name>
+    <description>Demo project for Spring Boot</description>
+    <properties>
+        <java.version>1.8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <spring-boot.version>2.7.6</spring-boot.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.googlecode.concurrentlinkedhashmap</groupId>
+            <artifactId>concurrentlinkedhashmap-lru</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- sa-token start -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot-starter</artifactId>
+            <version>1.37.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-redis-jackson</artifactId>
+            <version>1.37.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.22</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.79</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>20.0</version>
+        </dependency>
+        <!-- sa-token end -->
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <configuration>
+                    <mainClass>com.cloud.bound.link.plate.sso.SsoApplication</mainClass>
+                    <skip>true</skip>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>repackage</id>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 14 - 0
src/main/java/com/cloud/bound/link/plate/sso/SsoApplication.java

@@ -0,0 +1,14 @@
+package com.cloud.bound.link.plate.sso;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SsoApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(SsoApplication.class, args);
+        System.out.println("Hello World!");
+    }
+
+}

+ 37 - 0
src/main/java/com/cloud/bound/link/plate/sso/controller/LoginController.java

@@ -0,0 +1,37 @@
+package com.cloud.bound.link.plate.sso.controller;
+
+import com.cloud.bound.link.plate.sso.domain.LoginForm;
+import com.cloud.bound.link.plate.sso.domain.LoginResultVO;
+import com.cloud.bound.link.plate.sso.domain.ResponseDTO;
+import com.cloud.bound.link.plate.sso.service.LoginService;
+import com.cloud.bound.link.plate.sso.strategy.LoginStrategy;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+
+/**
+ * 员工登录
+ *
+ * @Author 云畅联:admin
+ * @Date 2021-12-15 21:05:46
+ */
+@RestController
+public class LoginController {
+
+    @Resource
+    private LoginService loginService;
+    @Resource
+    private LoginStrategyFactory loginStrategyFactory;
+
+    @PostMapping("/login")
+    public ResponseDTO<LoginResultVO> login(@Valid @RequestBody LoginForm loginForm, HttpServletRequest request) {
+        LoginStrategy loginStrategy = loginStrategyFactory.getLoginStrategy(loginForm.getLoginType());
+        return loginStrategy.login(loginForm, request);
+    }
+
+
+}

+ 37 - 0
src/main/java/com/cloud/bound/link/plate/sso/domain/LoginForm.java

@@ -0,0 +1,37 @@
+package com.cloud.bound.link.plate.sso.domain;
+
+import com.cloud.sa.base.common.swagger.SchemaEnum;
+import com.cloud.sa.base.constant.LoginDeviceEnum;
+import com.cloud.sa.base.module.support.captcha.domain.CaptchaForm;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 员工登录
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-19 11:49:45
+ */
+@Data
+public class LoginForm extends CaptchaForm {
+
+    @Schema(description = "登录账号")
+    @Length(max = 30, message = "登录账号最多30字符")
+    private String loginName;
+
+    @Schema(description = "密码")
+    private String password;
+
+    @SchemaEnum(desc = "登录终端", value = LoginDeviceEnum.class)
+    private Integer loginDevice;
+
+    @Schema(description = "邮箱验证码")
+    private String emailCode;
+
+    @Schema(description = "登录方式")
+    private String loginType;
+
+    @Schema(description = "authCode")
+    private String authCode;
+}

+ 33 - 0
src/main/java/com/cloud/bound/link/plate/sso/domain/LoginResultVO.java

@@ -0,0 +1,33 @@
+package com.cloud.bound.link.plate.sso.domain;
+
+import com.cloud.sa.admin.module.system.menu.domain.vo.MenuVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 登录结果信息
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-19 11:49:45
+ */
+@Data
+public class LoginResultVO extends RequestEmployee {
+
+    private String token;
+
+    private List<MenuVO> menuList;
+
+    private Boolean needUpdatePwdFlag;
+
+    private String lastLoginIp;
+
+    private String lastLoginIpRegion;
+
+    private String lastLoginUserAgent;
+
+    private LocalDateTime lastLoginTime;
+
+}

+ 96 - 0
src/main/java/com/cloud/bound/link/plate/sso/domain/RequestEmployee.java

@@ -0,0 +1,96 @@
+package com.cloud.bound.link.plate.sso.domain;
+
+import com.cloud.sa.base.common.domain.RequestUser;
+import com.cloud.sa.base.common.enumeration.GenderEnum;
+import com.cloud.sa.base.common.enumeration.UserTypeEnum;
+import com.cloud.sa.base.common.swagger.SchemaEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 请求员工登录信息
+ *
+ * @Author 云畅联: admin
+ * @Date 2021/8/4 21:15
+ */
+@Data
+public class RequestEmployee implements RequestUser {
+
+    @Schema(description = "员工id")
+    private Long employeeId;
+
+    @SchemaEnum(UserTypeEnum.class)
+    private UserTypeEnum userType;
+
+    @Schema(description = "登录账号")
+    private String loginName;
+
+    @Schema(description = "员工名称")
+    private String actualName;
+
+    @Schema(description = "头像")
+    private String avatar;
+
+    @SchemaEnum(GenderEnum.class)
+    private Integer gender;
+
+    @Schema(description = "手机号码")
+    private String phone;
+
+    @Schema(description = "部门id")
+    private Long departmentId;
+
+    @Schema(description = "部门名称")
+    private String departmentName;
+
+    @Schema(description = "是否禁用")
+    private Boolean disabledFlag;
+
+    @Schema(description = "是否为超管")
+    private Boolean administratorFlag;
+
+    @Schema(description = "备注")
+    private String remark;
+
+    @Schema(description = "请求ip")
+    private String ip;
+
+    @Schema(description = "请求user-agent")
+    private String userAgent;
+
+    @Override
+    public Long getUserId() {
+        return employeeId;
+    }
+
+    @Override
+    public String getUserCode() {
+        return loginName;
+    }
+
+    @Override
+    public String getUserName() {
+        return actualName;
+    }
+
+    //流程专用start
+    public String getUsername() {
+        return loginName;
+    }
+
+    public String getIpaddr() {
+        return ip;
+    }
+
+    public String getOs() {
+        return userAgent;
+    }
+
+    public String getUserNickName() {
+        return actualName;
+    }
+
+    //流程专用end
+
+
+}

+ 52 - 0
src/main/java/com/cloud/bound/link/plate/sso/domain/RequestUser.java

@@ -0,0 +1,52 @@
+package com.cloud.bound.link.plate.sso.domain;
+
+import com.cloud.sa.base.common.enumeration.UserTypeEnum;
+
+/**
+ * 请求用户
+ *
+ * @Author 畅联云: admin
+ * @Date 2021-12-21 19:55:07
+ */
+public interface RequestUser {
+
+    /**
+     * 请求用户id
+     *
+     * @return
+     */
+    Long getUserId();
+
+    /**
+     * 请求用户userCode
+     * @return
+     */
+    String getUserCode();
+
+    /**
+     * 请求用户名称
+     *
+     * @return
+     */
+    String getUserName();
+
+    /**
+     * 获取用户类型
+     */
+    UserTypeEnum getUserType();
+
+    /**
+     * 获取请求的IP
+     *
+     * @return
+     */
+    String getIp();
+
+    /**
+     * 获取请求 user-agent
+     *
+     * @return
+     */
+    String getUserAgent();
+
+}

+ 134 - 0
src/main/java/com/cloud/bound/link/plate/sso/domain/ResponseDTO.java

@@ -0,0 +1,134 @@
+package com.cloud.bound.link.plate.sso.domain;
+
+
+import com.cloud.sa.base.common.code.ErrorCode;
+import com.cloud.sa.base.common.code.UserErrorCode;
+import com.cloud.sa.base.common.enumeration.DataTypeEnum;
+import com.cloud.sa.base.common.swagger.SchemaEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Map;
+
+/**
+ * 请求返回对象
+ *
+ * @Author 畅联云: admin
+ * @Date 2021-10-31 21:06:11
+ */
+@Data
+public class ResponseDTO<T> {
+
+    public static final int OK_CODE = 0;
+
+    public static final String OK_MSG = "操作成功";
+
+    private Integer code;
+
+    private String level;
+
+    private String msg;
+
+    private Boolean ok;
+
+    private T data;
+
+    private Integer dataType;
+
+    /**
+     * 返回汇总数据
+     */
+    Map<String, Object> dataSet;
+
+    public ResponseDTO(Integer code, String level, boolean ok, String msg, T data) {
+        this.code = code;
+        this.level = level;
+        this.ok = ok;
+        this.msg = msg;
+        this.data = data;
+        this.dataType = DataTypeEnum.NORMAL.getValue();
+    }
+
+    public ResponseDTO(Integer code, String level, boolean ok, String msg, T data, Map<String, Object> dataSet) {
+        this.code = code;
+        this.level = level;
+        this.ok = ok;
+        this.msg = msg;
+        this.data = data;
+        this.dataType = DataTypeEnum.NORMAL.getValue();
+        this.dataSet = dataSet;
+    }
+
+    public ResponseDTO(Integer code, String level, boolean ok, String msg) {
+        this.code = code;
+        this.level = level;
+        this.ok = ok;
+        this.msg = msg;
+        this.dataType = DataTypeEnum.NORMAL.getValue();
+    }
+
+    public ResponseDTO(ErrorCode errorCode, boolean ok, String msg, T data) {
+        this.code = errorCode.getCode();
+        this.level = errorCode.getLevel();
+        this.ok = ok;
+        if (StringUtils.isNotBlank(msg)) {
+            this.msg = msg;
+        } else {
+            this.msg = errorCode.getMsg();
+        }
+        this.data = data;
+        this.dataType = DataTypeEnum.NORMAL.getValue();
+    }
+
+    public static <T> ResponseDTO<T> ok() {
+        return new ResponseDTO<>(OK_CODE, null, true, OK_MSG, null);
+    }
+
+    public static <T> ResponseDTO<T> ok(T data) {
+        return new ResponseDTO<>(OK_CODE, null, true, OK_MSG, data);
+    }
+
+    public static <T> ResponseDTO<T> ok(T data, Map<String, Object> dataSet) {
+        return new ResponseDTO<>(OK_CODE, null, true, OK_MSG, data, dataSet);
+    }
+
+    public static <T> ResponseDTO<T> okMsg(String msg) {
+        return new ResponseDTO<>(OK_CODE, null, true, msg, null);
+    }
+
+    // -------------------------------------------- 最常用的 用户参数 错误码 --------------------------------------------
+
+    public static <T> ResponseDTO<T> userErrorParam() {
+        return new ResponseDTO<>(UserErrorCode.PARAM_ERROR, false, null, null);
+    }
+
+
+    public static <T> ResponseDTO<T> userErrorParam(String msg) {
+        return new ResponseDTO<>(UserErrorCode.PARAM_ERROR, false, msg, null);
+    }
+
+    // -------------------------------------------- 错误码 --------------------------------------------
+
+    public static <T> ResponseDTO<T> error(ErrorCode errorCode) {
+        return new ResponseDTO<>(errorCode, false, null, null);
+    }
+
+    public static <T> ResponseDTO<T> error(ErrorCode errorCode, boolean ok) {
+        return new ResponseDTO<>(errorCode, ok, null, null);
+    }
+
+    public static <T> ResponseDTO<T> error(ResponseDTO<?> responseDTO) {
+        return new ResponseDTO<>(responseDTO.getCode(), responseDTO.getLevel(), responseDTO.getOk(), responseDTO.getMsg(), null);
+    }
+
+    public static <T> ResponseDTO<T> error(ErrorCode errorCode, String msg) {
+        return new ResponseDTO<>(errorCode, false, msg, null);
+    }
+
+    public static <T> ResponseDTO<T> errorData(ErrorCode errorCode, T data) {
+        return new ResponseDTO<>(errorCode, false, null, data);
+    }
+
+
+}

+ 96 - 0
src/main/java/com/cloud/bound/link/plate/sso/enumeration/BaseEnum.java

@@ -0,0 +1,96 @@
+package com.cloud.bound.link.plate.sso.enumeration;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONAware;
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.base.CaseFormat;
+import lombok.Data;
+
+import java.util.LinkedHashMap;
+import java.util.Objects;
+
+/**
+ * 枚举类接口
+ *
+ * @Author 云畅联: admin
+ * @Date 2018-07-17 21:22:12
+ */
+public interface BaseEnum {
+
+    /**
+     * 获取枚举类的值
+     *
+     * @return
+     */
+    Object getValue();
+
+    /**
+     * 获取枚举类的说明
+     *
+     * @return String
+     */
+    String getDesc();
+
+    /**
+     * 比较参数是否与枚举类的value相同
+     *
+     * @param value
+     * @return boolean
+     */
+    default boolean equalsValue(Object value) {
+        return Objects.equals(getValue(), value);
+    }
+
+    /**
+     * 比较枚举类是否相同
+     *
+     * @param baseEnum
+     * @return boolean
+     */
+    default boolean equals(BaseEnum baseEnum) {
+        return Objects.equals(getValue(), baseEnum.getValue()) && Objects.equals(getDesc(), baseEnum.getDesc());
+    }
+
+    /**
+     * 返回枚举类的说明
+     *
+     * @param clazz 枚举类类对象
+     * @return
+     */
+    static String getInfo(Class<? extends BaseEnum> clazz) {
+        BaseEnum[] enums = clazz.getEnumConstants();
+        LinkedHashMap<String, JSONObject> json = new LinkedHashMap<>(enums.length);
+        for (BaseEnum e : enums) {
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("value", new DeletedQuotationAware(e.getValue()));
+            jsonObject.put("desc", new DeletedQuotationAware(e.getDesc()));
+            json.put(e.toString(), jsonObject);
+        }
+
+        String enumJson = JSON.toJSONString(json, true);
+        enumJson = enumJson.replaceAll("\"", "");
+        enumJson = enumJson.replaceAll("\t", "&nbsp;&nbsp;");
+        enumJson = enumJson.replaceAll("\n", "<br>");
+        String prefix = "  <br>  export const " + CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, clazz.getSimpleName() + " = <br> ");
+        return prefix + enumJson + " <br>";
+    }
+
+    @Data
+    class DeletedQuotationAware implements JSONAware {
+
+        private String value;
+
+        public DeletedQuotationAware(Object value) {
+            if (value instanceof String) {
+                this.value = "'" + value + "'";
+            } else {
+                this.value = value.toString();
+            }
+        }
+
+        @Override
+        public String toJSONString() {
+            return value;
+        }
+    }
+}

+ 29 - 0
src/main/java/com/cloud/bound/link/plate/sso/enumeration/DataTypeEnum.java

@@ -0,0 +1,29 @@
+package com.cloud.bound.link.plate.sso.enumeration;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Author 云畅联:admin
+ * @Date 2023/10/25 09:47:13
+ */
+
+@Getter
+@AllArgsConstructor
+public enum DataTypeEnum implements BaseEnum {
+
+    /**
+     *普通数据
+     */
+    NORMAL(1, "普通数据"),
+
+    /**
+     * 加密数据
+     */
+    ENCRYPT(10, "加密数据"),
+    ;
+    private final Integer value;
+
+    private final String desc;
+
+}

+ 34 - 0
src/main/java/com/cloud/bound/link/plate/sso/enumeration/UserTypeEnum.java

@@ -0,0 +1,34 @@
+package com.cloud.bound.link.plate.sso.enumeration;
+
+/**
+ * 用户类型
+ *
+ * @Author 云畅联:admin
+ * @Date 2022/10/19 21:46:24
+ */
+public enum UserTypeEnum implements BaseEnum {
+
+    /**
+     * 管理端 员工用户
+     */
+    ADMIN_EMPLOYEE(1, "员工");
+
+    private Integer type;
+
+    private String desc;
+
+    UserTypeEnum(Integer type, String desc) {
+        this.type = type;
+        this.desc = desc;
+    }
+
+    @Override
+    public Integer getValue() {
+        return type;
+    }
+
+    @Override
+    public String getDesc() {
+        return desc;
+    }
+}

+ 524 - 0
src/main/java/com/cloud/bound/link/plate/sso/service/LoginService.java

@@ -0,0 +1,524 @@
+package com.cloud.bound.link.plate.sso.service;
+
+import cn.dev33.satoken.stp.StpInterface;
+import cn.dev33.satoken.stp.StpUtil;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+/**
+ * 登录
+ *
+ * @Author 云畅联: admin
+ * @Date 2021-12-01 22:56:34
+ */
+@Slf4j
+@Service
+public class LoginService implements StpInterface {
+
+    /**
+     * 万能密码的 sa token loginId 前缀
+     */
+    private static final String SUPER_PASSWORD_LOGIN_ID_PREFIX = "S";
+
+    /**
+     * 最大在线缓存人数
+     */
+    private static final long CACHE_MAX_ONLINE_PERSON_COUNT = 1000L;
+
+    /**
+     * 登录信息二级缓存
+     */
+    private final ConcurrentMap<Long, RequestEmployee> loginEmployeeCache = new ConcurrentLinkedHashMap.Builder<Long, RequestEmployee>().maximumWeightedCapacity(CACHE_MAX_ONLINE_PERSON_COUNT).build();
+
+
+    /**
+     * 权限 缓存
+     */
+    private final ConcurrentMap<Long, UserPermission> permissionCache = new ConcurrentLinkedHashMap.Builder<Long, UserPermission>().maximumWeightedCapacity(CACHE_MAX_ONLINE_PERSON_COUNT).build();
+
+    @Resource
+    private EmployeeService employeeService;
+
+    @Resource
+    private DepartmentService departmentService;
+
+    @Resource
+    private CaptchaService captchaService;
+
+    @Resource
+    private ConfigService configService;
+
+    @Resource
+    private LoginLogService loginLogService;
+
+    @Resource
+    private RoleEmployeeService roleEmployeeService;
+
+    @Resource
+    private RoleMenuService roleMenuService;
+
+    @Resource
+    private SecurityLoginService securityLoginService;
+
+    @Resource
+    private SecurityPasswordService protectPasswordService;
+
+    @Resource
+    private IFileStorageService fileStorageService;
+
+    @Resource
+    private ApiEncryptService apiEncryptService;
+
+    @Resource
+    private Level3ProtectConfigService level3ProtectConfigService;
+
+    @Resource
+    private RedisService redisService;
+
+    /**
+     * 员工登陆
+     *
+     * @return 返回用户登录信息
+     */
+    public ResponseDTO<LoginResultVO> login(LoginForm loginForm, String ip, String userAgent) {
+
+        LoginDeviceEnum loginDeviceEnum = BlinkEnumUtil.getEnumByValue(loginForm.getLoginDevice(), LoginDeviceEnum.class);
+        if (loginDeviceEnum == null) {
+            return ResponseDTO.userErrorParam("登录设备暂不支持!");
+        }
+
+        // 校验 图形验证码
+        ResponseDTO<String> checkCaptcha = captchaService.checkCaptcha(loginForm);
+        if (!checkCaptcha.getOk()) {
+            return ResponseDTO.error(UserErrorCode.PARAM_ERROR, checkCaptcha.getMsg());
+        }
+
+        // 验证登录名
+        EmployeeEntity employeeEntity = employeeService.getByLoginName(loginForm.getLoginName());
+        if (null == employeeEntity) {
+            return ResponseDTO.userErrorParam("登录名不存在!");
+        }
+
+        // 验证账号状态
+        if (employeeEntity.getDisabledFlag()) {
+            saveLoginLog(employeeEntity, ip, userAgent, "账号已禁用", LoginLogResultEnum.LOGIN_FAIL);
+            return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员!");
+        }
+
+        // 解密前端加密的密码
+        String requestPassword = apiEncryptService.decrypt(loginForm.getPassword());
+
+        // 验证密码 是否为万能密码
+        String superPassword = configService.getConfigValue(ConfigKeyEnum.SUPER_PASSWORD);
+        boolean superPasswordFlag = superPassword.equals(requestPassword);
+
+        // 校验双因子登录
+        ResponseDTO<String> validateEmailCode = validateEmailCode(loginForm, employeeEntity, superPasswordFlag);
+        if (!validateEmailCode.getOk()) {
+            return ResponseDTO.error(validateEmailCode);
+        }
+
+        // 万能密码特殊操作
+        if (superPasswordFlag) {
+
+            // 对于万能密码:受限制sa token 要求loginId唯一,万能密码只能插入一段uuid
+            String saTokenLoginId = SUPER_PASSWORD_LOGIN_ID_PREFIX + StringConst.COLON + UUID.randomUUID().toString().replace("-", "") + StringConst.COLON + employeeEntity.getEmployeeId();
+            // 万能密码登录只能登录30分钟
+            StpUtil.login(saTokenLoginId, 1800);
+
+        } else {
+
+            // 按照等保登录要求,进行登录失败次数校验
+            ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO = securityLoginService.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
+            if (!loginFailEntityResponseDTO.getOk()) {
+                return ResponseDTO.error(loginFailEntityResponseDTO);
+            }
+
+            // 密码错误
+            if (!employeeEntity.getLoginPwd().equals(SecurityPasswordService.getEncryptPwd(requestPassword))) {
+                // 记录登录失败
+                saveLoginLog(employeeEntity, ip, userAgent, "密码错误", LoginLogResultEnum.LOGIN_FAIL);
+                // 记录等级保护次数
+                String msg = securityLoginService.recordLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, employeeEntity.getLoginName(), loginFailEntityResponseDTO.getData());
+                return msg == null ? ResponseDTO.userErrorParam("登录名或密码错误!") : ResponseDTO.error(UserErrorCode.LOGIN_FAIL_WILL_LOCK, msg);
+            }
+
+            String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId();
+            // 登录
+            StpUtil.login(saTokenLoginId, String.valueOf(loginDeviceEnum.getDesc()));
+
+            // 移除邮箱验证码
+            deleteEmailCode(employeeEntity.getEmployeeId());
+        }
+
+        // 获取员工信息
+        RequestEmployee requestEmployee = loadLoginInfo(employeeEntity);
+
+        // 放入缓存
+        loginEmployeeCache.put(employeeEntity.getEmployeeId(), requestEmployee);
+
+        // 移除登录失败
+        securityLoginService.removeLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
+
+        // 获取登录结果信息
+        String token = StpUtil.getTokenValue();
+        LoginResultVO loginResultVO = getLoginResult(requestEmployee, token);
+        //保存登录记录
+        saveLoginLog(employeeEntity, ip, userAgent, superPasswordFlag ? "万能密码登录" : loginDeviceEnum.getDesc(), LoginLogResultEnum.LOGIN_SUCCESS);
+
+        // 设置 token
+        loginResultVO.setToken(token);
+        // 清除权限缓存
+        permissionCache.remove(employeeEntity.getEmployeeId());
+
+        return ResponseDTO.ok(loginResultVO);
+    }
+
+
+    /**
+     * 获取登录结果信息
+     */
+    public LoginResultVO getLoginResult(RequestEmployee requestEmployee, String token) {
+
+        // 基础信息
+        LoginResultVO loginResultVO = BlinkBeanUtil.copy(requestEmployee, LoginResultVO.class);
+
+        // 前端菜单和功能点清单
+        List<RoleVO> roleList = roleEmployeeService.getRoleIdList(requestEmployee.getEmployeeId());
+        List<MenuVO> menuAndPointsList = roleMenuService.getMenuList(roleList.stream().map(RoleVO::getRoleId).collect(Collectors.toList()), requestEmployee.getAdministratorFlag());
+        loginResultVO.setMenuList(menuAndPointsList);
+
+        // 更新下后端权限缓存
+        UserPermission userPermission = getUserPermission(requestEmployee.getUserId());
+        permissionCache.put(requestEmployee.getUserId(), userPermission);
+
+        // 上次登录信息
+        LoginLogVO loginLogVO = loginLogService.queryLastByUserId(requestEmployee.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, LoginLogResultEnum.LOGIN_SUCCESS);
+        if (loginLogVO != null) {
+            loginResultVO.setLastLoginIp(loginLogVO.getLoginIp());
+            loginResultVO.setLastLoginIpRegion(loginLogVO.getLoginIpRegion());
+            loginResultVO.setLastLoginTime(loginLogVO.getCreateTime());
+            loginResultVO.setLastLoginUserAgent(loginLogVO.getUserAgent());
+        }
+        // 是否需要强制修改密码
+        boolean needChangePasswordFlag = protectPasswordService.checkNeedChangePassword(requestEmployee.getUserType().getValue(), requestEmployee.getUserId());
+        loginResultVO.setNeedUpdatePwdFlag(needChangePasswordFlag);
+
+        // 万能密码登录,则不需要设置强制修改密码
+        String loginIdByToken = (String) StpUtil.getLoginIdByToken(token);
+        if (loginIdByToken != null && loginIdByToken.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) {
+            loginResultVO.setNeedUpdatePwdFlag(false);
+        }
+
+        return loginResultVO;
+    }
+
+
+    /**
+     * 获取登录的用户信息
+     */
+    private RequestEmployee loadLoginInfo(EmployeeEntity employeeEntity) {
+
+        // 基础信息
+        RequestEmployee requestEmployee = BlinkBeanUtil.copy(employeeEntity, RequestEmployee.class);
+        requestEmployee.setUserType(UserTypeEnum.ADMIN_EMPLOYEE);
+
+        // 部门信息
+        DepartmentVO department = departmentService.getDepartmentById(employeeEntity.getDepartmentId());
+        requestEmployee.setDepartmentName(null == department ? StringConst.EMPTY : department.getName());
+
+        // 头像信息
+        String avatar = employeeEntity.getAvatar();
+        if (StringUtils.isNotBlank(avatar)) {
+            ResponseDTO<String> getFileUrl = fileStorageService.getFileUrl(avatar);
+            if (BooleanUtils.isTrue(getFileUrl.getOk())) {
+                requestEmployee.setAvatar(getFileUrl.getData());
+            }
+        }
+
+        return requestEmployee;
+    }
+
+
+    /**
+     * 根据登陆token 获取员请求工信息
+     */
+    public RequestEmployee getLoginEmployee(String loginId, HttpServletRequest request) {
+        if (loginId == null) {
+            return null;
+        }
+
+        Long requestEmployeeId = getEmployeeIdByLoginId(loginId);
+        if (requestEmployeeId == null) {
+            return null;
+        }
+
+        RequestEmployee requestEmployee = loginEmployeeCache.get(requestEmployeeId);
+        if (requestEmployee == null) {
+            // 员工基本信息
+            EmployeeEntity employeeEntity = employeeService.getById(requestEmployeeId);
+            if (employeeEntity == null) {
+                return null;
+            }
+
+            requestEmployee = this.loadLoginInfo(employeeEntity);
+            loginEmployeeCache.put(requestEmployeeId, requestEmployee);
+        }
+
+        // 更新请求ip和user agent
+        requestEmployee.setUserAgent(ServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT));
+        requestEmployee.setIp(ServletUtil.getClientIP(request));
+
+        return requestEmployee;
+    }
+
+    /**
+     * 根据 loginId 获取 员工id
+     */
+    Long getEmployeeIdByLoginId(String loginId) {
+
+        if (loginId == null) {
+            return null;
+        }
+
+        try {
+            // 如果是 万能密码 登录的用户
+            String employeeIdStr = null;
+            if (loginId.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) {
+                employeeIdStr = loginId.split(StringConst.COLON)[2];
+            } else {
+                employeeIdStr = loginId.substring(2);
+            }
+
+            return Long.parseLong(employeeIdStr);
+        } catch (Exception e) {
+            log.error("loginId parse error , loginId : {}", loginId, e);
+            return null;
+        }
+    }
+
+
+    /**
+     * 退出登录
+     */
+    public ResponseDTO<String> logout(String token, RequestUser requestUser) {
+
+        // sa token 登出
+        StpUtil.logoutByTokenValue(token);
+
+        // 清空登录信息缓存
+        loginEmployeeCache.remove(requestUser.getUserId());
+
+        //保存登出日志
+        LoginLogEntity loginEntity = LoginLogEntity.builder()
+                .userId(requestUser.getUserId())
+                .userType(requestUser.getUserType().getValue())
+                .userName(requestUser.getUserName())
+                .userAgent(requestUser.getUserAgent())
+                .loginIp(requestUser.getIp())
+                .loginIpRegion(BlinkIpUtil.getRegion(requestUser.getIp()))
+                .loginResult(LoginLogResultEnum.LOGIN_OUT.getValue())
+                .createTime(LocalDateTime.now())
+                .build();
+        loginLogService.log(loginEntity);
+
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 清除员工登录缓存
+     *
+     * @param employeeId
+     */
+    public void clearLoginEmployeeCache(Long employeeId) {
+        // 清空登录信息缓存
+        loginEmployeeCache.remove(employeeId);
+    }
+
+    /**
+     * 保存登录日志
+     */
+    private void saveLoginLog(EmployeeEntity employeeEntity, String ip, String userAgent, String remark, LoginLogResultEnum result) {
+        LoginLogEntity loginEntity = LoginLogEntity.builder()
+                .userId(employeeEntity.getEmployeeId())
+                .userType(UserTypeEnum.ADMIN_EMPLOYEE.getValue())
+                .userName(employeeEntity.getActualName())
+                .userAgent(userAgent)
+                .loginIp(ip)
+                .loginIpRegion(BlinkIpUtil.getRegion(ip))
+                .remark(remark)
+                .loginResult(result.getValue())
+                .createTime(LocalDateTime.now())
+                .build();
+        loginLogService.log(loginEntity);
+    }
+
+
+    @Override
+    public List<String> getPermissionList(Object loginId, String loginType) {
+        Long employeeId = this.getEmployeeIdByLoginId((String) loginId);
+        if (employeeId == null) {
+            return Collections.emptyList();
+        }
+
+        UserPermission userPermission = permissionCache.get(employeeId);
+        if (userPermission == null) {
+            userPermission = getUserPermission(employeeId);
+            permissionCache.put(employeeId, userPermission);
+        }
+
+        return userPermission.getPermissionList();
+    }
+
+    @Override
+    public List<String> getRoleList(Object loginId, String loginType) {
+        Long employeeId = this.getEmployeeIdByLoginId((String) loginId);
+        if (employeeId == null) {
+            return Collections.emptyList();
+        }
+
+        UserPermission userPermission = permissionCache.get(employeeId);
+        if (userPermission == null) {
+            userPermission = getUserPermission(employeeId);
+            permissionCache.put(employeeId, userPermission);
+        }
+        return userPermission.getRoleList();
+    }
+
+    /**
+     * 获取用户的权限(包含 角色列表、权限列表)
+     */
+    private UserPermission getUserPermission(Long employeeId) {
+
+        UserPermission userPermission = new UserPermission();
+        userPermission.setPermissionList(new ArrayList<>());
+        userPermission.setRoleList(new ArrayList<>());
+
+        // 角色列表
+        List<RoleVO> roleList = roleEmployeeService.getRoleIdList(employeeId);
+        userPermission.getRoleList().addAll(roleList.stream().map(RoleVO::getRoleCode).collect(Collectors.toSet()));
+
+        // 前端菜单和功能点清单
+        EmployeeEntity employeeEntity = employeeService.getById(employeeId);
+
+        List<MenuVO> menuAndPointsList = roleMenuService.getMenuList(roleList.stream().map(RoleVO::getRoleId).collect(Collectors.toList()), employeeEntity.getAdministratorFlag());
+
+        // 权限列表
+        HashSet<String> permissionSet = new HashSet<>();
+        for (MenuVO menu : menuAndPointsList) {
+            if (menu.getPermsType() == null) {
+                continue;
+            }
+
+            String perms = menu.getApiPerms();
+            if (StringUtils.isEmpty(perms)) {
+                continue;
+            }
+            //接口权限
+            String[] split = perms.split(",");
+            permissionSet.addAll(Arrays.asList(split));
+        }
+        userPermission.getPermissionList().addAll(permissionSet);
+
+        return userPermission;
+    }
+
+
+    /**
+     * 发送 邮箱 验证码
+     */
+    public ResponseDTO<String> sendEmailCode(String loginName) {
+
+        // 开启双因子登录
+        if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) {
+            return ResponseDTO.userErrorParam("无需使用邮箱验证码");
+        }
+
+        // 验证登录名
+        EmployeeEntity employeeEntity = employeeService.getByLoginName(loginName);
+        if (null == employeeEntity) {
+            return ResponseDTO.userErrorParam("登录名不存在!");
+        }
+
+        // 验证账号状态
+        if (employeeEntity.getDisabledFlag()) {
+            return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员!");
+        }
+
+        String mail = employeeEntity.getEmail();
+        if (BlinkStringUtil.isBlank(mail)) {
+            return ResponseDTO.userErrorParam("您暂未配置邮箱地址,请联系管理员配置邮箱");
+        }
+
+        // 校验验证码发送时间,60秒内不能重复发生
+        String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
+        String emailCode = redisService.get(redisVerificationCodeKey);
+        long sendCodeTimeMills = -1;
+        if (!com.cloud.sa.base.common.util.BlinkStringUtil.isEmpty(emailCode)) {
+            sendCodeTimeMills = NumberUtil.parseLong(emailCode.split(StringConst.UNDERLINE)[1]);
+        }
+
+        if (System.currentTimeMillis() - sendCodeTimeMills < 60 * 1000) {
+            return ResponseDTO.userErrorParam("邮箱验证码已发送,一分钟内请勿重复发送");
+        }
+
+        //生成验证码
+        long currentTimeMillis = System.currentTimeMillis();
+        String verificationCode = RandomUtil.randomNumbers(4);
+        redisService.set(redisVerificationCodeKey, verificationCode + StringConst.UNDERLINE + currentTimeMillis, 300);
+
+        // 发送邮件验证码
+        HashMap<String, Object> mailParams = new HashMap<>();
+        mailParams.put("code", verificationCode);
+        return mailService.sendMail(MailTemplateCodeEnum.LOGIN_VERIFICATION_CODE, mailParams, Collections.singletonList(employeeEntity.getEmail()));
+    }
+
+
+    /**
+     * 校验邮箱验证码
+     */
+    private ResponseDTO<String> validateEmailCode(LoginForm loginForm, EmployeeEntity employeeEntity, boolean superPasswordFlag) {
+        // 万能密码则不校验
+        if (superPasswordFlag) {
+            return ResponseDTO.ok();
+        }
+
+        // 未开启双因子登录
+        if (!level3ProtectConfigService.isTwoFactorLoginEnabled()) {
+            return ResponseDTO.ok();
+        }
+
+        if (com.cloud.sa.base.common.util.BlinkStringUtil.isEmpty(loginForm.getEmailCode())) {
+            return ResponseDTO.userErrorParam("请输入邮箱验证码");
+        }
+
+        // 校验验证码
+        String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
+        String emailCode = redisService.get(redisVerificationCodeKey);
+        if (com.cloud.sa.base.common.util.BlinkStringUtil.isEmpty(emailCode)) {
+            return ResponseDTO.userErrorParam("邮箱验证码已失效,请重新发送");
+        }
+
+        if (!emailCode.split(StringConst.UNDERLINE)[0].equals(loginForm.getEmailCode().trim())) {
+            return ResponseDTO.userErrorParam("邮箱验证码错误,请重新填写");
+        }
+
+        return ResponseDTO.ok();
+    }
+
+    /**
+     * 移除邮箱验证码
+     */
+    private void deleteEmailCode(Long employeeId) {
+        String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeId);
+        redisService.delete(redisVerificationCodeKey);
+    }
+}

+ 23 - 0
src/main/java/com/cloud/bound/link/plate/sso/strategy/LoginStrategy.java

@@ -0,0 +1,23 @@
+package com.cloud.bound.link.plate.sso.strategy;
+
+
+import com.cloud.bound.link.plate.sso.domain.LoginForm;
+import com.cloud.bound.link.plate.sso.domain.LoginResultVO;
+import com.cloud.bound.link.plate.sso.domain.ResponseDTO;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 定义统一的登录策略接口
+ */
+public interface LoginStrategy {
+
+    /**
+     * 登录
+     *
+     * @param loginForm
+     * @param request
+     * @return
+     */
+    ResponseDTO<LoginResultVO> login(LoginForm loginForm, HttpServletRequest request);
+}

+ 17 - 0
src/main/resources/dev/application.yaml

@@ -0,0 +1,17 @@
+# 项目配置: 名称、日志目录
+project:
+  name: bound_link
+  log-directory: /bound_link/logs/${spring.profiles.active}
+
+# 项目端口和url根路径
+server:
+  port: 6008
+  servlet:
+    context-path: /
+
+# 环境
+spring:
+  profiles:
+    active: '@profiles.active@'
+  main:
+    allow-circular-references: true

+ 118 - 0
src/main/resources/dev/log4j2-spring.xml

@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    status : 这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,会看到log4j2内部各种详细输出
+    monitorInterval : Log4j能够自动检测修改配置文件和重新配置本身, 设置间隔秒数。
+-->
+<Configuration status="INFO" monitorInterval="30">
+    <Properties>
+        <Property name="log-directory" value="${sys:project.log-directory}"/>
+    </Properties>
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+        </Console>
+        <RollingFile name="debug" fileName="${log-directory}/debug/debug.log"
+                     filePattern="${log-directory}/debug/debug-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/debug" maxDepth="1">
+                    <IfFileName glob="debug-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="info" fileName="${log-directory}/info/info.log"
+                     filePattern="${log-directory}/info/info-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/info" maxDepth="1">
+                    <IfFileName glob="info-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="warn" fileName="${log-directory}/warn/warn.log"
+                     filePattern="${log-directory}/warn/warn-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/warn" maxDepth="1">
+                    <IfFileName glob="warn-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="error" fileName="${log-directory}/error/error.log"
+                     filePattern="${log-directory}/error/error-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="fatal" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="10 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/error" maxDepth="1">
+                    <IfFileName glob="error-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="druidSlowSqlLog" fileName="${log-directory}/slow-sql/slow-sql.log"
+                     filePattern="${log-directory}/slow-sql/slow-sql-%d{yyyy-MM-dd}-%i.log">
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="60">
+                <Delete basePath="${log-directory}/slow-sql" maxDepth="1">
+                    <IfFileName glob="slow-sql-*.log"/>
+                    <IfLastModified age="30d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+    </Appenders>
+
+    <Loggers>
+
+        <logger name="com.alibaba.druid.filter.stat.StatFilter" level="ERROR" additivity="false">
+            <AppenderRef ref="druidSlowSqlLog"/>
+        </logger>
+
+        <Root level="debug">
+            <AppenderRef ref="Console"/>
+            <AppenderRef ref="debug"/>
+            <AppenderRef ref="info"/>
+            <AppenderRef ref="warn"/>
+            <AppenderRef ref="error"/>
+        </Root>
+    </Loggers>
+
+</Configuration>

+ 18 - 0
src/main/resources/dev/spy.properties

@@ -0,0 +1,18 @@
+#相关的包
+modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
+# 日志格式
+logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
+#日志输出到控制台
+appender=com.p6spy.engine.spy.appender.StdoutLogger
+# 设置 p6spy driver 代理
+deregisterdrivers=true
+# 取消JDBC URL前缀
+useprefix=true
+# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
+excludecategories=info,debug,result,commit,resultset
+# 日期格式
+dateformat=yyyy-MM-dd HH:mm:ss
+# 开启慢sql
+outagedetection=true
+# 慢SQL记录标准(单位秒)
+outagedetectioninterval=2

+ 15 - 0
src/main/resources/pre/application.yaml

@@ -0,0 +1,15 @@
+# 项目配置: 名称、日志目录
+project:
+  name: bound_link
+  log-directory: /bound_link/logs/${spring.profiles.active}
+
+# 项目端口和url根路径
+server:
+  port: 6008
+  servlet:
+    context-path: /
+
+# 环境
+spring:
+  profiles:
+    active: '@profiles.active@'

+ 118 - 0
src/main/resources/pre/log4j2-spring.xml

@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    status : 这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,会看到log4j2内部各种详细输出
+    monitorInterval : Log4j能够自动检测修改配置文件和重新配置本身, 设置间隔秒数。
+-->
+<Configuration status="INFO" monitorInterval="30">
+    <Properties>
+        <Property name="log-directory" value="${sys:project.log-directory}"/>
+    </Properties>
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+        </Console>
+        <RollingFile name="debug" fileName="${log-directory}/debug/debug.log"
+                     filePattern="${log-directory}/debug/debug-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/debug" maxDepth="1">
+                    <IfFileName glob="debug-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="info" fileName="${log-directory}/info/info.log"
+                     filePattern="${log-directory}/info/info-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/info" maxDepth="1">
+                    <IfFileName glob="info-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="warn" fileName="${log-directory}/warn/warn.log"
+                     filePattern="${log-directory}/warn/warn-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/warn" maxDepth="1">
+                    <IfFileName glob="warn-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="error" fileName="${log-directory}/error/error.log"
+                     filePattern="${log-directory}/error/error-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="fatal" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="10 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/error" maxDepth="1">
+                    <IfFileName glob="error-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="druidSlowSqlLog" fileName="${log-directory}/slow-sql/slow-sql.log"
+                     filePattern="${log-directory}/slow-sql/slow-sql-%d{yyyy-MM-dd}-%i.log">
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="60">
+                <Delete basePath="${log-directory}/slow-sql" maxDepth="1">
+                    <IfFileName glob="slow-sql-*.log"/>
+                    <IfLastModified age="30d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+    </Appenders>
+
+    <Loggers>
+
+        <logger name="com.alibaba.druid.filter.stat.StatFilter" level="ERROR" additivity="false">
+            <AppenderRef ref="druidSlowSqlLog"/>
+        </logger>
+
+        <Root level="info">
+            <AppenderRef ref="Console"/>
+            <AppenderRef ref="debug"/>
+            <AppenderRef ref="info"/>
+            <AppenderRef ref="warn"/>
+            <AppenderRef ref="error"/>
+        </Root>
+    </Loggers>
+
+</Configuration>

+ 15 - 0
src/main/resources/prod/application.yaml

@@ -0,0 +1,15 @@
+# 项目配置: 名称、日志目录
+project:
+  name: bound_link
+  log-directory: /bound_link/logs/${spring.profiles.active}
+
+# 项目端口和url根路径
+server:
+  port: 6008
+  servlet:
+    context-path: /
+
+# 环境
+spring:
+  profiles:
+    active: '@profiles.active@'

+ 118 - 0
src/main/resources/prod/log4j2-spring.xml

@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    status : 这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,会看到log4j2内部各种详细输出
+    monitorInterval : Log4j能够自动检测修改配置文件和重新配置本身, 设置间隔秒数。
+-->
+<Configuration status="INFO" monitorInterval="30">
+    <Properties>
+        <Property name="log-directory" value="${sys:project.log-directory}"/>
+    </Properties>
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+        </Console>
+        <RollingFile name="debug" fileName="${log-directory}/debug/debug.log"
+                     filePattern="${log-directory}/debug/debug-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/debug" maxDepth="1">
+                    <IfFileName glob="debug-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="info" fileName="${log-directory}/info/info.log"
+                     filePattern="${log-directory}/info/info-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/info" maxDepth="1">
+                    <IfFileName glob="info-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="warn" fileName="${log-directory}/warn/warn.log"
+                     filePattern="${log-directory}/warn/warn-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/warn" maxDepth="1">
+                    <IfFileName glob="warn-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="error" fileName="${log-directory}/error/error.log"
+                     filePattern="${log-directory}/error/error-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="fatal" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="10 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/error" maxDepth="1">
+                    <IfFileName glob="error-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="druidSlowSqlLog" fileName="${log-directory}/slow-sql/slow-sql.log"
+                     filePattern="${log-directory}/slow-sql/slow-sql-%d{yyyy-MM-dd}-%i.log">
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="60">
+                <Delete basePath="${log-directory}/slow-sql" maxDepth="1">
+                    <IfFileName glob="slow-sql-*.log"/>
+                    <IfLastModified age="30d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+    </Appenders>
+
+    <Loggers>
+
+        <logger name="com.alibaba.druid.filter.stat.StatFilter" level="ERROR" additivity="false">
+            <AppenderRef ref="druidSlowSqlLog"/>
+        </logger>
+
+        <Root level="warn">
+            <AppenderRef ref="Console"/>
+            <AppenderRef ref="debug"/>
+            <AppenderRef ref="info"/>
+            <AppenderRef ref="warn"/>
+            <AppenderRef ref="error"/>
+        </Root>
+    </Loggers>
+
+</Configuration>

+ 15 - 0
src/main/resources/test/application.yaml

@@ -0,0 +1,15 @@
+# 项目配置: 名称、日志目录
+project:
+  name: bound_link
+  log-directory: /bound_link/logs/${spring.profiles.active}
+
+# 项目端口和url根路径
+server:
+  port: 6008
+  servlet:
+    context-path: /
+
+# 环境
+spring:
+  profiles:
+    active: '@profiles.active@'

+ 118 - 0
src/main/resources/test/log4j2-spring.xml

@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    status : 这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,会看到log4j2内部各种详细输出
+    monitorInterval : Log4j能够自动检测修改配置文件和重新配置本身, 设置间隔秒数。
+-->
+<Configuration status="INFO" monitorInterval="30">
+    <Properties>
+        <Property name="log-directory" value="${sys:project.log-directory}"/>
+    </Properties>
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+        </Console>
+        <RollingFile name="debug" fileName="${log-directory}/debug/debug.log"
+                     filePattern="${log-directory}/debug/debug-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/debug" maxDepth="1">
+                    <IfFileName glob="debug-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="info" fileName="${log-directory}/info/info.log"
+                     filePattern="${log-directory}/info/info-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/info" maxDepth="1">
+                    <IfFileName glob="info-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="warn" fileName="${log-directory}/warn/warn.log"
+                     filePattern="${log-directory}/warn/warn-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/warn" maxDepth="1">
+                    <IfFileName glob="warn-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="error" fileName="${log-directory}/error/error.log"
+                     filePattern="${log-directory}/error/error-%d{yyyy-MM-dd}-%i.log">
+            <Filters>
+                <ThresholdFilter level="fatal" onMatch="DENY" onMismatch="NEUTRAL"/>
+                <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
+            </Filters>
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="10 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="30">
+                <Delete basePath="${log-directory}/error" maxDepth="1">
+                    <IfFileName glob="error-*.log"/>
+                    <IfLastModified age="15d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+        <RollingFile name="druidSlowSqlLog" fileName="${log-directory}/slow-sql/slow-sql.log"
+                     filePattern="${log-directory}/slow-sql/slow-sql-%d{yyyy-MM-dd}-%i.log">
+            <PatternLayout pattern="[%d][%-5p][%t] %m (%F:%L)%n"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+                <SizeBasedTriggeringPolicy size="50 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="60">
+                <Delete basePath="${log-directory}/slow-sql" maxDepth="1">
+                    <IfFileName glob="slow-sql-*.log"/>
+                    <IfLastModified age="30d"/>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+    </Appenders>
+
+    <Loggers>
+
+        <logger name="com.alibaba.druid.filter.stat.StatFilter" level="ERROR" additivity="false">
+            <AppenderRef ref="druidSlowSqlLog"/>
+        </logger>
+
+        <Root level="info">
+            <AppenderRef ref="Console"/>
+            <AppenderRef ref="debug"/>
+            <AppenderRef ref="info"/>
+            <AppenderRef ref="warn"/>
+            <AppenderRef ref="error"/>
+        </Root>
+    </Loggers>
+
+</Configuration>

+ 18 - 0
src/main/resources/test/spy.properties

@@ -0,0 +1,18 @@
+#相关的包
+modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
+# 日志格式
+logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
+#日志输出到控制台
+appender=com.p6spy.engine.spy.appender.StdoutLogger
+# 设置 p6spy driver 代理
+deregisterdrivers=true
+# 取消JDBC URL前缀
+useprefix=true
+# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
+excludecategories=info,debug,result,commit,resultset
+# 日期格式
+dateformat=yyyy-MM-dd HH:mm:ss
+# 开启慢sql
+outagedetection=true
+# 慢SQL记录标准(单位秒)
+outagedetectioninterval=2

+ 13 - 0
src/test/java/com/cloud/bound/link/plate/sso/SsoApplicationTests.java

@@ -0,0 +1,13 @@
+package com.cloud.bound.link.plate.sso;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SsoApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}