Explorar o código

Merge branch 'master' of https://gitee.com/y_project/RuoYi-Vue

 Conflicts:
	ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
	ruoyi-admin/src/main/resources/application.yml
	ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java
	ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java
	ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java
	ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java
	ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
	ruoyi-ui/package.json
	ruoyi-ui/src/views/index.vue
	ruoyi-ui/src/views/tool/gen/editTable.vue
疯狂的狮子li %!s(int64=4) %!d(string=hai) anos
pai
achega
ff563c44e2

+ 2 - 2
pom.xml

@@ -6,14 +6,14 @@
 	
     <groupId>com.ruoyi</groupId>
     <artifactId>ruoyi</artifactId>
-    <version>3.2.0</version>
+    <version>3.2.1</version>
 
     <name>ruoyi</name>
     <url>http://www.ruoyi.vip</url>
     <description>若依管理系统</description>
     
     <properties>
-        <ruoyi.version>3.2.0</ruoyi.version>
+        <ruoyi.version>3.2.1</ruoyi.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>1.8</java.version>

+ 1 - 1
ruoyi-admin/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.2.0</version>
+        <version>3.2.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <packaging>jar</packaging>

+ 26 - 17
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java

@@ -5,6 +5,7 @@ import javax.servlet.http.HttpServletResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -41,17 +42,15 @@ public class CommonController
     {
         try
         {
-            if (!FileUtils.isValidFilename(fileName))
+            if (!FileUtils.checkAllowDownload(fileName))
             {
                 throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
             }
             String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
             String filePath = RuoYiConfig.getDownloadPath() + fileName;
 
-            response.setCharacterEncoding("utf-8");
-            response.setContentType("multipart/form-data");
-            response.setHeader("Content-Disposition",
-                    "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, realFileName));
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            FileUtils.setAttachmentResponseHeader(response, realFileName);
             FileUtils.writeBytes(filePath, response.getOutputStream());
             if (delete)
             {
@@ -92,18 +91,28 @@ public class CommonController
      * 本地资源通用下载
      */
     @GetMapping("/common/download/resource")
-    public void resourceDownload(String name, HttpServletRequest request, HttpServletResponse response) throws Exception
+    public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
+            throws Exception
     {
-        // 本地资源路径
-        String localPath = RuoYiConfig.getProfile();
-        // 数据库资源地址
-        String downloadPath = localPath + StringUtils.substringAfter(name, Constants.RESOURCE_PREFIX);
-        // 下载名称
-        String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
-        response.setCharacterEncoding("utf-8");
-        response.setContentType("multipart/form-data");
-        response.setHeader("Content-Disposition",
-                "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, downloadName));
-        FileUtils.writeBytes(downloadPath, response.getOutputStream());
+        try
+        {
+            if (!FileUtils.checkAllowDownload(resource))
+            {
+                throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource));
+            }
+            // 本地资源路径
+            String localPath = RuoYiConfig.getProfile();
+            // 数据库资源地址
+            String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
+            // 下载名称
+            String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            FileUtils.setAttachmentResponseHeader(response, downloadName);
+            FileUtils.writeBytes(downloadPath, response.getOutputStream());
+        }
+        catch (Exception e)
+        {
+            log.error("下载文件失败", e);
+        }
     }
 }

+ 2 - 2
ruoyi-admin/src/main/resources/application.yml

@@ -3,7 +3,7 @@ ruoyi:
   # 名称
   name: RuoYi
   # 版本
-  version: 3.2.0
+  version: 3.2.1
   # 版权年份
   copyrightYear: 2020
   # 实例演示开关
@@ -70,7 +70,7 @@ token:
     secret: abcdefghijklmnopqrstuvwxyz
     # 令牌有效期(默认30分钟)
     expireTime: 30
-
+  
 # MyBatis配置
 # https://baomidou.com/config/
 mybatis-plus:

+ 1 - 1
ruoyi-common/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.2.0</version>
+        <version>3.2.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 3 - 0
ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java

@@ -74,6 +74,9 @@ public class GenConstants
     /** 日期控件 */
     public static final String HTML_DATETIME = "datetime";
 
+    /** 上传控件 */
+    public static final String HTML_UPLOAD_IMAGE = "uploadImage";
+
     /** 富文本控件 */
     public static final String HTML_EDITOR = "editor";
 

+ 47 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java

@@ -0,0 +1,47 @@
+package com.ruoyi.common.utils.file;
+
+import java.io.File;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 文件类型工具类
+ *
+ * @author ruoyi
+ */
+public class FileTypeUtils
+{
+    /**
+     * 获取文件类型
+     * <p>
+     * 例如: ruoyi.txt, 返回: txt
+     * 
+     * @param file 文件名
+     * @return 后缀(不含".")
+     */
+    public static String getFileType(File file)
+    {
+        if (null == file)
+        {
+            return StringUtils.EMPTY;
+        }
+        return getFileType(file.getName());
+    }
+
+    /**
+     * 获取文件类型
+     * <p>
+     * 例如: ruoyi.txt, 返回: txt
+     *
+     * @param fileName 文件名
+     * @return 后缀(不含".")
+     */
+    public static String getFileType(String fileName)
+    {
+        int separatorIndex = fileName.lastIndexOf(".");
+        if (separatorIndex < 0)
+        {
+            return "";
+        }
+        return fileName.substring(separatorIndex + 1).toLowerCase();
+    }
+}

+ 63 - 2
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java

@@ -7,7 +7,11 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.ArrayUtils;
+import com.ruoyi.common.utils.StringUtils;
 
 /**
  * 文件处理工具类
@@ -104,6 +108,30 @@ public class FileUtils extends org.apache.commons.io.FileUtils
         return filename.matches(FILENAME_PATTERN);
     }
 
+    /**
+     * 检查文件是否可下载
+     *
+     * @param resource 需要下载的文件
+     * @return true 正常 false 非法
+     */
+    public static boolean checkAllowDownload(String resource)
+    {
+        // 禁止目录上跳级别
+        if (StringUtils.contains(resource, ".."))
+        {
+            return false;
+        }
+
+        // 检查允许下载的文件规则
+        if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
+        {
+            return true;
+        }
+
+        // 不在允许下载的文件规则
+        return false;
+    }
+
     /**
      * 下载文件名重新编码
      * 
@@ -111,8 +139,7 @@ public class FileUtils extends org.apache.commons.io.FileUtils
      * @param fileName 文件名
      * @return 编码后的文件名
      */
-    public static String setFileDownloadHeader(HttpServletRequest request, String fileName)
-            throws UnsupportedEncodingException
+    public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
     {
         final String agent = request.getHeader("USER-AGENT");
         String filename = fileName;
@@ -139,4 +166,38 @@ public class FileUtils extends org.apache.commons.io.FileUtils
         }
         return filename;
     }
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @param response 响应对象
+     * @param realFileName 真实文件名
+     * @return
+     */
+    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
+    {
+        String percentEncodedFileName = percentEncode(realFileName);
+
+        StringBuilder contentDispositionValue = new StringBuilder();
+        contentDispositionValue.append("attachment; filename=")
+                .append(percentEncodedFileName)
+                .append(";")
+                .append("filename*=")
+                .append("utf-8''")
+                .append(percentEncodedFileName);
+
+        response.setHeader("Content-disposition", contentDispositionValue.toString());
+    }
+
+    /**
+     * 百分号编码工具方法
+     *
+     * @param s 需要百分号编码的字符串
+     * @return 百分号编码后的字符串
+     */
+    public static String percentEncode(String s) throws UnsupportedEncodingException
+    {
+        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
+        return encode.replaceAll("\\+", "%20");
+    }
 }

+ 1 - 1
ruoyi-framework/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.2.0</version>
+        <version>3.2.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
ruoyi-generator/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.2.0</version>
+        <version>3.2.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java

@@ -59,7 +59,7 @@ public class GenTableColumn extends BaseEntity
     /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */
     private String queryType;
 
-    /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、editor富文本控件) */
+    /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、upload上传控件、editor富文本控件) */
     private String htmlType;
 
     /** 字典类型 */

+ 5 - 0
ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java

@@ -111,6 +111,11 @@ public class GenUtils
         {
             column.setHtmlType(GenConstants.HTML_SELECT);
         }
+        // 文件字段设置上传控件
+        else if (StringUtils.endsWithIgnoreCase(columnName, "image"))
+        {
+            column.setHtmlType(GenConstants.HTML_UPLOAD_IMAGE);
+        }
         // 内容字段设置富文本控件
         else if (StringUtils.endsWithIgnoreCase(columnName, "content"))
         {

+ 16 - 0
ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm

@@ -139,6 +139,10 @@
         <el-form-item label="${comment}" prop="${field}">
           <el-input v-model="form.${field}" placeholder="请输入${comment}" />
         </el-form-item>
+#elseif($column.htmlType == "uploadImage")
+        <el-form-item label="${comment}">
+          <uploadImage v-model="form.${field}"/>
+        </el-form-item>
 #elseif($column.htmlType == "editor")
         <el-form-item label="${comment}">
           <editor v-model="form.${field}" :min-height="192"/>
@@ -226,6 +230,12 @@ import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${Busin
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 #foreach($column in $columns)
+#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "uploadImage")
+import UploadImage from '@/components/UploadImage';
+#break
+#end
+#end
+#foreach($column in $columns)
 #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
 import Editor from '@/components/Editor';
 #break
@@ -236,6 +246,12 @@ export default {
   name: "${BusinessName}",
   components: {
 #foreach($column in $columns)
+#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "uploadImage")
+    UploadImage,
+#break
+#end
+#end
+#foreach($column in $columns)
 #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
     Editor,
 #break

+ 19 - 1
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm

@@ -168,6 +168,10 @@
         <el-form-item label="${comment}" prop="${field}">
           <el-input v-model="form.${field}" placeholder="请输入${comment}" />
         </el-form-item>
+#elseif($column.htmlType == "uploadImage")
+        <el-form-item label="${comment}">
+          <uploadImage v-model="form.${field}"/>
+        </el-form-item>
 #elseif($column.htmlType == "editor")
         <el-form-item label="${comment}">
           <editor v-model="form.${field}" :min-height="192"/>
@@ -253,6 +257,12 @@
 <script>
 import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName}, export${BusinessName} } from "@/api/${moduleName}/${businessName}";
 #foreach($column in $columns)
+#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "uploadImage")
+import UploadImage from '@/components/UploadImage';
+#break
+#end
+#end
+#foreach($column in $columns)
 #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
 import Editor from '@/components/Editor';
 #break
@@ -261,12 +271,20 @@ import Editor from '@/components/Editor';
 
 export default {
   name: "${BusinessName}",
+  components: {
+#foreach($column in $columns)
+#if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "uploadImage")
+    UploadImage,
+#break
+#end
+#end
 #foreach($column in $columns)
 #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "editor")
-  components: { Editor },
+    Editor,
 #break
 #end
 #end
+  },
   data() {
     return {
       // 遮罩层

+ 1 - 1
ruoyi-quartz/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.2.0</version>
+        <version>3.2.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
ruoyi-system/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>ruoyi</artifactId>
         <groupId>com.ruoyi</groupId>
-        <version>3.2.0</version>
+        <version>3.2.1</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
ruoyi-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "ruoyi",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "若依管理系统",
   "author": "若依",
   "license": "MIT",

+ 68 - 0
ruoyi-ui/src/components/UploadImage/index.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="component-upload-image">
+    <el-upload
+      :action="uploadImgUrl"
+      list-type="picture-card"
+      :on-success="handleUploadSuccess"
+      :before-upload="handleBeforeUpload"
+      :on-error="handleUploadError"
+      name="file"
+      :show-file-list="false"
+      :headers="headers"
+      style="display: inline-block; vertical-align: top"
+    >
+      <img v-if="value" :src="value" class="avatar" />
+      <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+    </el-upload>
+  </div>
+</template>
+
+<script>
+import { getToken } from "@/utils/auth";
+
+export default {
+  components: {},
+  data() {
+    return {
+      uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
+      headers: {
+        Authorization: "Bearer " + getToken(),
+      },
+    };
+  },
+  props: {
+    value: {
+      type: String,
+      default: "",
+    },
+  },
+  methods: {
+    handleUploadSuccess(res) {
+      this.$emit("input", res.url);
+      this.loading.close();
+    },
+    handleBeforeUpload() {
+      this.loading = this.$loading({
+        lock: true,
+        text: "上传中",
+        background: "rgba(0, 0, 0, 0.7)",
+      });
+    },
+    handleUploadError() {
+      this.$message({
+        type: "error",
+        message: "上传失败",
+      });
+      this.loading.close();
+    },
+  },
+  watch: {},
+};
+</script>
+
+<style scoped lang="scss">
+.avatar {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 15 - 1
ruoyi-ui/src/views/index.vue

@@ -146,6 +146,20 @@
             <span>更新日志</span>
           </div>
           <el-collapse accordion>
+            <el-collapse-item title="v3.2.1 - 2020-11-18">
+              <ol>
+                <li>阻止任意文件下载漏洞</li>
+                <li>代码生成支持上传控件</li>
+                <li>新增图片上传组件</li>
+                <li>调整默认首页</li>
+                <li>升级druid到最新版本v1.2.2</li>
+                <li>mapperLocations配置支持分隔符</li>
+                <li>权限信息调整</li>
+                <li>调整sql默认时间</li>
+                <li>解决代码生成没有bit类型的问题</li>
+                <li>升级pagehelper到最新版1.3.0</li>
+              </ol>
+            </el-collapse-item>
             <el-collapse-item title="v3.2.0 - 2020-10-10">
               <ol>
                 <li>升级springboot版本到2.1.17 提升安全性</li>
@@ -421,7 +435,7 @@ export default {
   data() {
     return {
       // 版本号
-      version: "3.2.0",
+      version: "3.2.1",
     };
   },
   methods: {

+ 1 - 0
ruoyi-ui/src/views/tool/gen/editTable.vue

@@ -90,6 +90,7 @@
                 <el-option label="单选框" value="radio" />
                 <el-option label="复选框" value="checkbox" />
                 <el-option label="日期控件" value="datetime" />
+                <el-option label="上传控件" value="uploadImage" />
                 <el-option label="富文本控件" value="editor" />
               </el-select>
             </template>