疯狂的狮子li
2021-09-26 6f6a3566ac50bcb9bcae4ae68744c9f49b75933c
Merge remote-tracking branch 'origin/dev' into satoken

# Conflicts:
# pom.xml
# ruoyi-framework/src/main/java/com/ruoyi/framework/config/AsyncConfig.java
# ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
# ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java
# ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java
已修改9个文件
已添加1个文件
已删除2个文件
602 ■■■■■ 文件已修改
README.md 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java 260 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SwaggerConfig.java 162 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SecurityProperties.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SwaggerProperties.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/tool/gen/editTable.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -14,6 +14,8 @@
| åŠŸèƒ½ä»‹ç» | ä½¿ç”¨æŠ€æœ¯ | æ–‡æ¡£åœ°å€ | ç‰¹æ€§æ³¨æ„äº‹é¡¹ |
|---|---|---|---|
| å½“前框架 | RuoYi-Vue-Plus | [RuoYi-Vue-Plus文档](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages) | é‡å†™RuoYi-Vue全方位升级(不兼容原框架) |
| satoken分支 | RuoYi-Vue-Plus-satoken | [satoken分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/) | ä½¿ç”¨satoken重构权限鉴权(仅供学习不推荐上生产) |
| å•体分支 | RuoYi-Vue-Plus-fast | [fast分支地址](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/) | å•体应用结构 |
| åŽŸæ¡†æž¶ | RuoYi-Vue | [RuoYi-Vue官网](http://ruoyi.vip/) | å®šæœŸåŒæ­¥éœ€è¦çš„功能 |
| å‰ç«¯å¼€å‘框架 | Vue、Element UI | [Element UI官网](https://element.eleme.cn/#/zh-CN) | |
| åŽç«¯å¼€å‘框架 | SpringBoot | [SpringBoot官网](https://spring.io/projects/spring-boot/#learn) | |
@@ -69,6 +71,7 @@
* åŒæ­¥å‡çº§ RuoYi-Vue
* GitHub åœ°å€ [RuoYi-Vue-Plus-github](https://github.com/JavaLionLi/RuoYi-Vue-Plus)
* å•模块 fast åˆ†æ”¯ [RuoYi-Vue-Plus-fast](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/fast/)
* satoken åˆ†æ”¯ [RuoYi-Vue-Plus-satoken](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/)
* ç”¨æˆ·æ‰©å±•项目 [扩展项目列表](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages?sort_id=4478302&doc_id=1469725)
## åŠ ç¾¤ä¸ŽæçŒ®
pom.xml
@@ -14,7 +14,7 @@
    <properties>
        <ruoyi-vue-plus.version>3.1.0</ruoyi-vue-plus.version>
        <spring-boot.version>2.5.4</spring-boot.version>
        <spring-boot.version>2.5.5</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
@@ -26,14 +26,14 @@
        <easyexcel.version>2.2.11</easyexcel.version>
        <velocity.version>1.7</velocity.version>
        <satoken.version>1.26.0</satoken.version>
        <mybatis-plus.version>3.4.3.3</mybatis-plus.version>
        <mybatis-plus.version>3.4.3.4</mybatis-plus.version>
        <p6spy.version>3.9.1</p6spy.version>
        <hutool.version>5.7.13</hutool.version>
        <feign.version>3.0.3</feign.version>
        <feign-okhttp.version>11.6</feign-okhttp.version>
        <okhttp.version>4.9.1</okhttp.version>
        <spring-boot-admin.version>2.5.1</spring-boot-admin.version>
        <redisson.version>3.16.2</redisson.version>
        <redisson.version>3.16.3</redisson.version>
        <lock4j.version>2.2.1</lock4j.version>
        <dynamic-ds.version>3.4.1</dynamic-ds.version>
@@ -43,7 +43,7 @@
        <!-- OSS é…ç½® -->
        <qiniu.version>7.8.0</qiniu.version>
        <aliyun.oss.version>3.13.1</aliyun.oss.version>
        <qcloud.cos.version>5.6.51</qcloud.cos.version>
        <qcloud.cos.version>5.6.55</qcloud.cos.version>
        <minio.version>8.3.0</minio.version>
        <!-- docker é…ç½® -->
ruoyi-admin/src/main/resources/application.yml
@@ -122,6 +122,28 @@
  # æ˜¯å¦è¾“出操作日志
  is-log: true
# security配置
security:
  # ç™»å‡ºè·¯å¾„
  logout-url: /logout
  # åŒ¿åè·¯å¾„
  anonymous:
    - /login
    - /register
    - /captchaImage
    # swagger æ–‡æ¡£é…ç½®
    - /doc.html
    - /swagger-resources/**
    - /webjars/**
    - /*/api-docs
    # druid ç›‘控配置
    - /druid/**
    # actuator ç›‘控配置
    - /actuator
    - /actuator/**
  # ç”¨æˆ·æ”¾è¡Œ
  permit-all:
# é‡å¤æäº¤
repeat-submit:
  # å…¨å±€é—´é𔿗¶é—´(毫秒)
@@ -237,6 +259,11 @@
    name: Lion Li
    email: crazylionli@163.com
    url: https://gitee.com/JavaLionLi/RuoYi-Vue-Plus
  groups:
    - name: æ¼”示案例
      basePackage: com.ruoyi.demo
    - name: ç³»ç»Ÿæ¨¡å—
      basePackage: com.ruoyi.admin
# é˜²æ­¢XSS攻击
xss:
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
ÎļþÒÑɾ³ý
ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java
@@ -27,6 +27,9 @@
    public static String getRealAddressByIP(String ip) {
        String address = UNKNOWN;
        if (StringUtils.isBlank(ip)){
            return address;
        }
        // å†…网不查询
        ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
        if (NetUtil.isInnerIP(ip)) {
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java
ÎļþÒÑɾ³ý
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SwaggerConfig.java
@@ -1,11 +1,12 @@
package com.ruoyi.framework.config;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.ruoyi.common.properties.TokenProperties;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.config.properties.SwaggerProperties;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
@@ -15,6 +16,7 @@
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@@ -27,82 +29,92 @@
@EnableKnife4j
public class SwaggerConfig {
    @Autowired
    private SwaggerProperties swaggerProperties;
    @Autowired
    private SwaggerProperties swaggerProperties;
    /**
     * åˆ›å»ºAPI
     */
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.OAS_30)
            .enable(swaggerProperties.getEnabled())
            // ç”¨æ¥åˆ›å»ºè¯¥API的基本信息,展示在文档的页面中(自定义展示的信息)
            .apiInfo(apiInfo())
            // è®¾ç½®å“ªäº›æŽ¥å£æš´éœ²ç»™Swagger展示
            .select()
            // æ‰«ææ‰€æœ‰æœ‰æ³¨è§£çš„api,用这种方式更灵活
            .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
            // æ‰«ææŒ‡å®šåŒ…中的swagger注解
            // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
            // æ‰«ææ‰€æœ‰ .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.any())
            .build()
            /* è®¾ç½®å®‰å…¨æ¨¡å¼ï¼Œswagger可以设置访问token */
            .securitySchemes(securitySchemes())
            .securityContexts(securityContexts())
            .pathMapping(swaggerProperties.getPathMapping());
    }
    @Autowired
    private TokenProperties tokenProperties;
    /**
     * å®‰å…¨æ¨¡å¼ï¼Œè¿™é‡ŒæŒ‡å®štoken通过Authorization头请求头传递
     */
    private List<SecurityScheme> securitySchemes() {
        List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
        apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
        return apiKeyList;
    }
    /**
     * åˆ›å»ºAPI
     */
    @PostConstruct
    public void createRestApi() {
        for (SwaggerProperties.Groups group : swaggerProperties.getGroups()) {
            String basePackage = group.getBasePackage();
            Docket docket = new Docket(DocumentationType.OAS_30)
                    .enable(swaggerProperties.getEnabled())
                    // ç”¨æ¥åˆ›å»ºè¯¥API的基本信息,展示在文档的页面中(自定义展示的信息)
                    .apiInfo(apiInfo())
                    // è®¾ç½®å“ªäº›æŽ¥å£æš´éœ²ç»™Swagger展示
                    .select()
                    // æ‰«ææ‰€æœ‰æœ‰æ³¨è§£çš„api,用这种方式更灵活
                    //.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                    // æ‰«ææŒ‡å®šåŒ…中的swagger注解
                    .apis(RequestHandlerSelectors.basePackage(basePackage))
                    // æ‰«ææ‰€æœ‰ .apis(RequestHandlerSelectors.any())
                    .paths(PathSelectors.any())
                    .build()
                    .groupName(group.getName())
                    // è®¾ç½®å®‰å…¨æ¨¡å¼ï¼Œswagger可以设置访问token
                    .securitySchemes(securitySchemes())
                    .securityContexts(securityContexts())
                    .pathMapping(swaggerProperties.getPathMapping());
            String beanName = StringUtils.substringAfterLast(basePackage, ".") + "Docket";
            SpringUtils.registerBean(beanName, docket);
        }
    }
    /**
     * å®‰å…¨ä¸Šä¸‹æ–‡
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
            SecurityContext.builder()
                .securityReferences(defaultAuth())
                .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
                .build());
        return securityContexts;
    }
    /**
     * å®‰å…¨æ¨¡å¼ï¼Œè¿™é‡ŒæŒ‡å®štoken通过Authorization头请求头传递
     */
    private List<SecurityScheme> securitySchemes() {
        List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
        String header = tokenProperties.getHeader();
        apiKeyList.add(new ApiKey(header, header, In.HEADER.toValue()));
        return apiKeyList;
    }
    /**
     * é»˜è®¤çš„安全上引用
     */
    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }
    /**
     * å®‰å…¨ä¸Šä¸‹æ–‡
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
                        .build());
        return securityContexts;
    }
    /**
     * æ·»åŠ æ‘˜è¦ä¿¡æ¯
     */
    private ApiInfo apiInfo() {
        // ç”¨ApiInfoBuilder进行定制
        SwaggerProperties.Contact contact = swaggerProperties.getContact();
        return new ApiInfoBuilder()
            // è®¾ç½®æ ‡é¢˜
            .title(swaggerProperties.getTitle())
            // æè¿°
            .description(swaggerProperties.getDescription())
            // ä½œè€…信息
            .contact(new Contact(contact.getName(), contact.getUrl(), contact.getEmail()))
            // ç‰ˆæœ¬
            .version(swaggerProperties.getVersion())
            .build();
    }
    /**
     * é»˜è®¤çš„安全上引用
     */
    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference(tokenProperties.getHeader(), authorizationScopes));
        return securityReferences;
    }
    /**
     * æ·»åŠ æ‘˜è¦ä¿¡æ¯
     */
    private ApiInfo apiInfo() {
        // ç”¨ApiInfoBuilder进行定制
        SwaggerProperties.Contact contact = swaggerProperties.getContact();
        return new ApiInfoBuilder()
                // è®¾ç½®æ ‡é¢˜
                .title(swaggerProperties.getTitle())
                // æè¿°
                .description(swaggerProperties.getDescription())
                // ä½œè€…信息
                .contact(new Contact(contact.getName(), contact.getUrl(), contact.getEmail()))
                // ç‰ˆæœ¬
                .version(swaggerProperties.getVersion())
                .build();
    }
}
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SecurityProperties.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.framework.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * Security é…ç½®å±žæ€§
 *
 * @author Lion Li
 */
@Data
@Component
@ConfigurationProperties(prefix = "security")
public class SecurityProperties {
    /**
     * é€€å‡ºç™»å½•url
     */
    private String logoutUrl;
    /**
     * åŒ¿åæ”¾è¡Œè·¯å¾„
     */
    private String[] anonymous;
    /**
     * ç”¨æˆ·ä»»æ„è®¿é—®æ”¾è¡Œè·¯å¾„
     */
    private String[] permitAll;
}
ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/SwaggerProperties.java
@@ -5,6 +5,8 @@
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * swagger é…ç½®å±žæ€§
 *
@@ -41,23 +43,46 @@
     */
    private Contact contact;
    /**
     * ç»„配置
     */
    private List<Groups> groups;
    @Data
    @NoArgsConstructor
    public static class Contact{
    public static class Contact {
        /**
         * è”系人
         **/
         */
        private String name;
        /**
         * è”系人url
         **/
         */
        private String url;
        /**
         * è”系人email
         **/
         */
        private String email;
    }
    @Data
    @NoArgsConstructor
    public static class Groups {
        /**
         * ç»„名
         */
        private String name;
        /**
         * åŸºç¡€åŒ…路径
         */
        private String basePackage;
    }
}
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
@@ -557,7 +557,7 @@
    /** ${subTable.functionName}删除按钮操作 */
    handleDelete${subClassName}() {
      if (this.checked${subClassName}.length == 0) {
        this.msgError("请先选择要删除的${subTable.functionName}数据");
        this.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据");
      } else {
        const ${subclassName}List = this.${subclassName}List;
        const checked${subClassName} = this.checked${subClassName};
ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java
@@ -77,9 +77,14 @@
            asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
            throw new UserPasswordNotMatchException();
        }
        else
        {
            asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
            throw new ServiceException(e.getMessage());
        }
        asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
        recordLoginInfo(user);
        recordLoginInfo(user.getUserId());
        // ç”Ÿæˆtoken
        StpUtil.login(user.getUserId(), "PC");
        return StpUtil.getTokenValue();
@@ -109,12 +114,15 @@
    /**
     * è®°å½•登录信息
     *
     * @param userId ç”¨æˆ·ID
     */
    public void recordLoginInfo(SysUser user)
    public void recordLoginInfo(Long userId)
    {
        user.setLoginIp(ServletUtils.getClientIP());
        user.setLoginDate(DateUtils.getNowDate());
        user.setUpdateBy(user.getUserName());
        userService.updateUserProfile(user);
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(ServletUtils.getClientIP());
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }
}
ruoyi-ui/src/views/tool/gen/editTable.vue
@@ -4,8 +4,8 @@
      <el-tab-pane label="基本信息" name="basic">
        <basic-info-form ref="basicInfo" :info="info" />
      </el-tab-pane>
      <el-tab-pane label="字段信息" name="cloum">
        <el-table ref="dragTable" :data="cloumns" row-key="columnId" :max-height="tableHeight">
      <el-tab-pane label="字段信息" name="columnInfo">
        <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
          <el-table-column label="序号" type="index" min-width="5%" class-name="allowDrag" />
          <el-table-column
            label="字段列名"
@@ -141,13 +141,13 @@
  data() {
    return {
      // é€‰ä¸­é€‰é¡¹å¡çš„ name
      activeName: "cloum",
      activeName: "columnInfo",
      // è¡¨æ ¼çš„高度
      tableHeight: document.documentElement.scrollHeight - 245 + "px",
      // è¡¨ä¿¡æ¯
      tables: [],
      // è¡¨åˆ—信息
      cloumns: [],
      columns: [],
      // å­—典信息
      dictOptions: [],
      // èœå•信息
@@ -161,7 +161,7 @@
    if (tableId) {
      // èŽ·å–è¡¨è¯¦ç»†ä¿¡æ¯
      getGenTable(tableId).then(res => {
        this.cloumns = res.data.rows;
        this.columns = res.data.rows;
        this.info = res.data.info;
        this.tables = res.data.tables;
      });
@@ -184,7 +184,7 @@
        const validateResult = res.every(item => !!item);
        if (validateResult) {
          const genTable = Object.assign({}, basicForm.model, genForm.model);
          genTable.columns = this.cloumns;
          genTable.columns = this.columns;
          genTable.params = {
            treeCode: genTable.treeCode,
            treeName: genTable.treeName,
@@ -220,10 +220,10 @@
    const sortable = Sortable.create(el, {
      handle: ".allowDrag",
      onEnd: evt => {
        const targetRow = this.cloumns.splice(evt.oldIndex, 1)[0];
        this.cloumns.splice(evt.newIndex, 0, targetRow);
        for (let index in this.cloumns) {
          this.cloumns[index].sort = parseInt(index) + 1;
        const targetRow = this.columns.splice(evt.oldIndex, 1)[0];
        this.columns.splice(evt.newIndex, 0, targetRow);
        for (let index in this.columns) {
          this.columns[index].sort = parseInt(index) + 1;
        }
      }
    });