Commit b0d635e1f0058bb45094568c5d3cb835a9bf7cea

Authored by 芋道源码
Committed by Gitee
2 parents ed097e5e 1f5c3e74

!210 文件表type字段修改

Merge pull request !210 from 随心/master
sql/mysql/ruoyi-vue-pro.sql
... ... @@ -689,10 +689,10 @@ DROP TABLE IF EXISTS `infra_file`;
689 689 CREATE TABLE `infra_file` (
690 690 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '文件编号',
691 691 `config_id` bigint NULL DEFAULT NULL COMMENT '配置编号',
692   - `name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件名',
  692 + `name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件名',
693 693 `path` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件路径',
694 694 `url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件 URL',
695   - `type` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型',
  695 + `type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件MIME类型',
696 696 `size` int NOT NULL COMMENT '文件大小',
697 697 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
698 698 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
... ...
sql/oracle/ruoyi-vue-pro.sql
... ... @@ -878,7 +878,7 @@ CREATE TABLE "INFRA_FILE" (
878 878 "CONFIG_ID" NUMBER(20,0),
879 879 "PATH" NVARCHAR2(512),
880 880 "URL" NCLOB,
881   - "TYPE" NVARCHAR2(63),
  881 + "TYPE" NVARCHAR2(64),
882 882 "SIZE" NUMBER(11,0) NOT NULL,
883 883 "CREATOR" NVARCHAR2(64),
884 884 "CREATE_TIME" DATE NOT NULL,
... ... @@ -908,7 +908,7 @@ COMMENT ON COLUMN "INFRA_FILE"."ID" IS '文件编号';
908 908 COMMENT ON COLUMN "INFRA_FILE"."CONFIG_ID" IS '配置编号';
909 909 COMMENT ON COLUMN "INFRA_FILE"."PATH" IS '文件路径';
910 910 COMMENT ON COLUMN "INFRA_FILE"."URL" IS '文件 URL';
911   -COMMENT ON COLUMN "INFRA_FILE"."TYPE" IS '文件类型';
  911 +COMMENT ON COLUMN "INFRA_FILE"."TYPE" IS '文件MIME类型';
912 912 COMMENT ON COLUMN "INFRA_FILE"."SIZE" IS '文件大小';
913 913 COMMENT ON COLUMN "INFRA_FILE"."CREATOR" IS '创建者';
914 914 COMMENT ON COLUMN "INFRA_FILE"."CREATE_TIME" IS '创建时间';
... ...
sql/postgresql/ruoyi-vue-pro.sql
... ... @@ -1717,21 +1717,21 @@ CREATE TABLE "infra_file" (
1717 1717 "config_id" int8,
1718 1718 "path" varchar(512) COLLATE "pg_catalog"."default" NOT NULL,
1719 1719 "url" varchar(1024) COLLATE "pg_catalog"."default" NOT NULL,
1720   - "type" varchar(63) COLLATE "pg_catalog"."default",
  1720 + "type" varchar(64) COLLATE "pg_catalog"."default",
1721 1721 "size" int4 NOT NULL,
1722 1722 "creator" varchar(64) COLLATE "pg_catalog"."default",
1723 1723 "create_time" timestamp(6) NOT NULL,
1724 1724 "updater" varchar(64) COLLATE "pg_catalog"."default",
1725 1725 "update_time" timestamp(6) NOT NULL,
1726 1726 "deleted" int2 NOT NULL DEFAULT 0,
1727   - "name" varchar(255) COLLATE "pg_catalog"."default"
  1727 + "name" varchar(512) COLLATE "pg_catalog"."default"
1728 1728 )
1729 1729 ;
1730 1730 COMMENT ON COLUMN "infra_file"."id" IS '文件编号';
1731 1731 COMMENT ON COLUMN "infra_file"."config_id" IS '配置编号';
1732 1732 COMMENT ON COLUMN "infra_file"."path" IS '文件路径';
1733 1733 COMMENT ON COLUMN "infra_file"."url" IS '文件 URL';
1734   -COMMENT ON COLUMN "infra_file"."type" IS '文件类型';
  1734 +COMMENT ON COLUMN "infra_file"."type" IS '文件MIME类型';
1735 1735 COMMENT ON COLUMN "infra_file"."size" IS '文件大小';
1736 1736 COMMENT ON COLUMN "infra_file"."creator" IS '创建者';
1737 1737 COMMENT ON COLUMN "infra_file"."create_time" IS '创建时间';
... ...
sql/sqlserver/ruoyi-vue-pro.sql
... ... @@ -2634,14 +2634,14 @@ CREATE TABLE [dbo].[infra_file] (
2634 2634 [config_id] bigint NULL,
2635 2635 [path] nvarchar(512) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
2636 2636 [url] nvarchar(1024) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
2637   - [type] nvarchar(63) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
  2637 + [type] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
2638 2638 [size] int NOT NULL,
2639 2639 [creator] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
2640 2640 [create_time] datetime2(7) NOT NULL,
2641 2641 [updater] nvarchar(64) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
2642 2642 [update_time] datetime2(7) NOT NULL,
2643 2643 [deleted] bit DEFAULT 0 NOT NULL,
2644   - [name] nvarchar(256) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
  2644 + [name] nvarchar(512) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
2645 2645 )
2646 2646 GO
2647 2647  
... ... @@ -2677,7 +2677,7 @@ EXEC sp_addextendedproperty
2677 2677 GO
2678 2678  
2679 2679 EXEC sp_addextendedproperty
2680   -'MS_Description', N'文件类型',
  2680 +'MS_Description', N'文件 MIME 类型',
2681 2681 'SCHEMA', N'dbo',
2682 2682 'TABLE', N'infra_file',
2683 2683 'COLUMN', N'type'
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java
1 1 package cn.iocoder.yudao.framework.common.util.io;
2 2  
  3 +import cn.hutool.core.io.FileTypeUtil;
3 4 import cn.hutool.core.io.FileUtil;
  5 +import cn.hutool.core.io.file.FileNameUtil;
4 6 import cn.hutool.core.util.IdUtil;
  7 +import cn.hutool.core.util.StrUtil;
  8 +import cn.hutool.crypto.digest.DigestUtil;
5 9 import lombok.SneakyThrows;
6 10  
  11 +import java.io.ByteArrayInputStream;
7 12 import java.io.File;
8 13  
9 14 /**
... ... @@ -58,8 +63,20 @@ public class FileUtils {
58 63 return file;
59 64 }
60 65  
61   -
62   - public static void main(String[] args) {
  66 + /**
  67 + * @param content 文件内容
  68 + * @param originalName 原始文件名
  69 + * @return path,唯一不可重复
  70 + */
  71 + public static String generatePath(byte[] content, String originalName) {
  72 + String sha256Hex = DigestUtil.sha256Hex(content);
  73 + // 如果存在name,则优先使用name的后缀
  74 + if (StrUtil.isNotBlank(originalName)) {
  75 + String extName = FileNameUtil.extName(originalName);
  76 + return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;
  77 + } else {
  78 + return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
  79 + }
63 80 }
64 81  
65 82 }
... ...
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/utils/FileTypeUtils.java
... ... @@ -4,8 +4,6 @@ import com.alibaba.ttl.TransmittableThreadLocal;
4 4 import lombok.SneakyThrows;
5 5 import org.apache.tika.Tika;
6 6  
7   -import java.io.ByteArrayInputStream;
8   -
9 7 /**
10 8 * 文件类型 Utils
11 9 *
... ... @@ -16,14 +14,35 @@ public class FileTypeUtils {
16 14 private static final ThreadLocal<Tika> TIKA = TransmittableThreadLocal.withInitial(Tika::new);
17 15  
18 16 /**
19   - * 获得文件的 mineType
  17 + * 获得文件的 mineType,对于doc,jar等文件会有误差
20 18 *
21 19 * @param data 文件内容
22   - * @return mineType
  20 + * @return mineType 无法识别时会返回“application/octet-stream”
23 21 */
24 22 @SneakyThrows
25 23 public static String getMineType(byte[] data) {
26   - return TIKA.get().detect(new ByteArrayInputStream(data));
  24 + return TIKA.get().detect(data);
  25 + }
  26 +
  27 + /**
  28 + * 已知文件名,获取文件类型,在某些情况下比通过字节数组准确,例如使用jar文件时,通过名字更为准确
  29 + *
  30 + * @param name 文件名
  31 + * @return mineType 无法识别时会返回“application/octet-stream”
  32 + */
  33 + public static String getMineType(String name) {
  34 + return TIKA.get().detect(name);
  35 + }
  36 +
  37 + /**
  38 + * 在拥有文件和数据的情况下,最好使用此方法,最为准确
  39 + *
  40 + * @param data 文件内容
  41 + * @param name 文件名
  42 + * @return mineType 无法识别时会返回“application/octet-stream”
  43 + */
  44 + public static String getMineType(byte[] data, String name) {
  45 + return TIKA.get().detect(data, name);
27 46 }
28 47  
29 48 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePageReqVO.java
... ... @@ -21,7 +21,7 @@ public class FilePageReqVO extends PageParam {
21 21 @ApiModelProperty(value = "文件路径", example = "yudao", notes = "模糊匹配")
22 22 private String path;
23 23  
24   - @ApiModelProperty(value = "文件类型", example = "jpg", notes = "模糊匹配")
  24 + @ApiModelProperty(value = "文件类型", example = "application/octet-stream", notes = "模糊匹配")
25 25 private String type;
26 26  
27 27 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java
... ... @@ -13,6 +13,9 @@ public class FileRespVO {
13 13 @ApiModelProperty(value = "文件编号", required = true, example = "1024")
14 14 private Long id;
15 15  
  16 + @ApiModelProperty(value = "配置编号", required = true, example = "11")
  17 + private Long configId;
  18 +
16 19 @ApiModelProperty(value = "文件路径", required = true, example = "yudao.jpg")
17 20 private String path;
18 21  
... ... @@ -22,7 +25,7 @@ public class FileRespVO {
22 25 @ApiModelProperty(value = "文件 URL", required = true, example = "https://www.iocoder.cn/yudao.jpg")
23 26 private String url;
24 27  
25   - @ApiModelProperty(value = "文件类型", example = "jpg")
  28 + @ApiModelProperty(value = "文件MIME类型", example = "application/octet-stream")
26 29 private String type;
27 30  
28 31 @ApiModelProperty(value = "文件大小", example = "2048", required = true)
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java
... ... @@ -46,9 +46,7 @@ public class FileDO extends BaseDO {
46 46 */
47 47 private String url;
48 48 /**
49   - * 文件类型
50   - *
51   - * 通过 {@link cn.hutool.core.io.FileTypeUtil#getType(InputStream)} 获取
  49 + * 文件的MIME类型,例如"application/octet-stream"
52 50 */
53 51 private String type;
54 52 /**
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
1 1 package cn.iocoder.yudao.module.infra.service.file;
2 2  
  3 +import cn.hutool.core.io.FileUtil;
3 4 import cn.hutool.core.lang.Assert;
4 5 import cn.hutool.core.util.StrUtil;
5   -import cn.hutool.crypto.digest.DigestUtil;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.io.FileUtils;
7 8 import cn.iocoder.yudao.framework.file.core.client.FileClient;
8 9 import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils;
9 10 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
... ... @@ -40,10 +41,9 @@ public class FileServiceImpl implements FileService {
40 41 @SneakyThrows
41 42 public String createFile(String name, String path, byte[] content) {
42 43 // 计算默认的 path 名
43   - String type = FileTypeUtils.getMineType(content);
  44 + String type = FileTypeUtils.getMineType(content, name);
44 45 if (StrUtil.isEmpty(path)) {
45   - path = DigestUtil.md5Hex(content)
46   - + '.' + StrUtil.subAfter(type, '/', true); // 文件的后缀
  46 + path = FileUtils.generatePath(content, name);
47 47 }
48 48 // 如果 name 为空,则使用 path 填充
49 49 if (StrUtil.isEmpty(name)) {
... ...
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java
... ... @@ -40,7 +40,7 @@ public class FileServiceTest extends BaseDbUnitTest {
40 40 // mock 数据
41 41 FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到
42 42 o.setPath("yunai");
43   - o.setType("jpg");
  43 + o.setType("image/jpg");
44 44 o.setCreateTime(buildTime(2021, 1, 15));
45 45 });
46 46 fileMapper.insert(dbFile);
... ... @@ -48,7 +48,7 @@ public class FileServiceTest extends BaseDbUnitTest {
48 48 fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setPath("tudou")));
49 49 // 测试 type 不匹配
50 50 fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> {
51   - o.setType("png");
  51 + o.setType("image/png");
52 52 }));
53 53 // 测试 createTime 不匹配
54 54 fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> {
... ... @@ -90,7 +90,7 @@ public class FileServiceTest extends BaseDbUnitTest {
90 90 assertEquals(10L, file.getConfigId());
91 91 assertEquals(path, file.getPath());
92 92 assertEquals(url, file.getUrl());
93   - assertEquals("jpg", file.getType());
  93 + assertEquals("image/jpg", file.getType());
94 94 assertEquals(content.length, file.getSize());
95 95 }
96 96  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialUserBindDO.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.social;
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 5 import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableId;
6 7 import com.baomidou.mybatisplus.annotation.TableName;
7 8 import lombok.*;
8 9  
... ... @@ -22,6 +23,11 @@ import lombok.*;
22 23 public class SocialUserBindDO extends BaseDO {
23 24  
24 25 /**
  26 + * 编号
  27 + */
  28 + @TableId
  29 + private Long id;
  30 + /**
25 31 * 关联的用户编号
26 32 *
27 33 * 关联 UserDO 的编号
... ...
yudao-ui-admin/src/views/infra/file/index.vue
... ... @@ -26,27 +26,32 @@
26 26  
27 27 <!-- 列表 -->
28 28 <el-table v-loading="loading" :data="list">
29   - <el-table-column label="文件名" align="center" prop="name" />
30   - <el-table-column label="文件路径" align="center" prop="path" />
31   - <el-table-column label="文件 URL" align="center" prop="url" />
32   - <el-table-column label="文件大小" align="center" prop="size" width="120" :formatter="sizeFormat" />
33   - <el-table-column label="文件类型" align="center" prop="type" width="80" />
34   -<!-- <el-table-column label="文件内容" align="center" prop="content">-->
35   -<!-- <template slot-scope="scope">-->
36   -<!-- <img v-if="scope.row.type === 'jpg' || scope.row.type === 'png' || scope.row.type === 'gif'"-->
37   -<!-- width="200px" :src="getFileUrl + scope.row.id">-->
38   -<!-- <i v-else>非图片,无法预览</i>-->
39   -<!-- </template>-->
40   -<!-- </el-table-column>-->
41   - <el-table-column label="上传时间" align="center" prop="createTime" width="180">
  29 + <el-table-column label="文件名" :show-overflow-tooltip="true" align="center" min-width="200px" prop="name"/>
  30 + <el-table-column label="文件路径" :show-overflow-tooltip="true" align="center" min-width="250px" prop="path"/>
  31 + <el-table-column label="文件 URL" :show-overflow-tooltip="true" align="center" min-width="300px" prop="url"/>
  32 + <el-table-column label="文件大小" align="center" prop="size" min-width="120px" :formatter="sizeFormat"/>
  33 + <el-table-column label="文件类型" :show-overflow-tooltip="true" align="center" prop="type" width="180px"/>
  34 + <el-table-column label="文件内容" align="center" prop="content" min-width="150px">
  35 + <template slot-scope="scope">
  36 + <image-preview v-if="scope.row.type&&scope.row.type.indexOf('image/') === 0" :src="scope.row.url"
  37 + :width="'100px'"></image-preview>
  38 + <i v-else>无法预览,点击
  39 + <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" target="_blank"
  40 + :href="getFileUrl + scope.row.configId + '/get/' + scope.row.path">下载
  41 + </el-link>
  42 + </i>
  43 + </template>
  44 + </el-table-column>
  45 + <el-table-column label="上传时间" align="center" prop="createTime" min-width="170px">
42 46 <template slot-scope="scope">
43 47 <span>{{ parseTime(scope.row.createTime) }}</span>
44 48 </template>
45 49 </el-table-column>
46   - <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100">
  50 + <el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="100px">
47 51 <template slot-scope="scope">
48 52 <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
49   - v-hasPermi="['infra:file:delete']">删除</el-button>
  53 + v-hasPermi="['infra:file:delete']">删除
  54 + </el-button>
50 55 </template>
51 56 </el-table-column>
52 57 </el-table>
... ... @@ -77,14 +82,18 @@
77 82 </template>
78 83  
79 84 <script>
80   -import { deleteFile, getFilePage } from "@/api/infra/file";
  85 +import {deleteFile, getFilePage} from "@/api/infra/file";
81 86 import {getAccessToken} from "@/utils/auth";
  87 +import ImagePreview from "@/components/ImagePreview";
82 88  
83 89 export default {
84 90 name: "File",
  91 + components: {
  92 + ImagePreview
  93 + },
85 94 data() {
86 95 return {
87   - getFileUrl: process.env.VUE_APP_BASE_API + '/admin-api/infra/file/get/',
  96 + getFileUrl: process.env.VUE_APP_BASE_API + '/admin-api/infra/file/',
88 97 // 遮罩层
89 98 loading: true,
90 99 // 显示搜索条件
... ...