Browse Source

添加文件处理模块

gaoxiong 2 years ago
parent
commit
c6ff0932b5

+ 22 - 0
pom.xml

@@ -24,6 +24,7 @@
         <druid.version>1.2.16</druid.version>
         <commons.io.version>2.11.0</commons.io.version>
         <fastjson.version>2.0.25</fastjson.version>
+        <jackson.version>2.9.8</jackson.version>
         <jjwt.version>0.9.1</jjwt.version>
         <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
         <hutool.version>5.4.1</hutool.version>
@@ -33,6 +34,8 @@
         <pagehelper.boot.version>1.4.6</pagehelper.boot.version>
         <mybatis.plus.version>3.5.1</mybatis.plus.version>
         <commons.codec.version>1.10</commons.codec.version>
+        <commons.lang3.version>3.12.0</commons.lang3.version>
+        <okhttp.version>3.10.0</okhttp.version>
     </properties>
     <dependencyManagement>
         <dependencies>
@@ -83,6 +86,12 @@
                 <version>${fastjson.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.fasterxml.jackson.datatype</groupId>
+                <artifactId>jackson-datatype-hibernate5</artifactId>
+                <version>${jackson.version}</version>
+            </dependency>
+
             <!-- JWT -->
             <dependency>
                 <groupId>io.jsonwebtoken</groupId>
@@ -120,6 +129,7 @@
                 <artifactId>hutool-all</artifactId>
                 <version>${hutool.version}</version>
             </dependency>
+
             <dependency>
                 <groupId>com.alibaba</groupId>
                 <artifactId>easyexcel</artifactId>
@@ -127,6 +137,12 @@
             </dependency>
 
             <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp</artifactId>
+                <version>${okhttp.version}</version>
+            </dependency>
+
+            <dependency>
                 <groupId>commons-codec</groupId>
                 <artifactId>commons-codec</artifactId>
                 <version>${commons.codec.version}</version>
@@ -139,6 +155,12 @@
                 <version>${commons.io.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-lang3</artifactId>
+                <version>${commons.lang3.version}</version>
+            </dependency>
+
             <!--核心模块-->
             <dependency>
                 <groupId>com.xunmei</groupId>

+ 1 - 0
soc-modules/pom.xml

@@ -20,6 +20,7 @@
         <module>soc-modules-core</module>
         <module>soc-modules-api</module>
         <module>soc-modules-sync</module>
+        <module>soc-modules-file</module>
     </modules>
 
 </project>

+ 78 - 0
soc-modules/soc-modules-file/pom.xml

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.xunmei</groupId>
+        <artifactId>soc-modules</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>soc-modules-file</artifactId>
+
+    <description>
+        soc-modules-file文件服务
+    </description>
+
+    <dependencies>
+
+        <!-- SpringCloud Alibaba Nacos -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+
+        <!-- SpringCloud Alibaba Nacos Config -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+
+        <!-- SpringCloud Alibaba Sentinel -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
+
+        <!-- SpringBoot Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+
+        <!-- soc Api System -->
+        <dependency>
+            <groupId>com.xunmei</groupId>
+            <artifactId>soc-api-system</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+
+        <!-- soc Common Swagger -->
+        <dependency>
+            <groupId>com.xunmei</groupId>
+            <artifactId>soc-common-swagger</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 16 - 0
soc-modules/soc-modules-file/src/main/java/com/xunmei/file/SocFileApplication.java

@@ -0,0 +1,16 @@
+package com.xunmei.file;
+
+import com.xunmei.common.swagger.annotation.EnableCustomSwagger2;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+
+@EnableCustomSwagger2
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
+public class SocFileApplication {
+    public static void main(String[] args)
+    {
+        SpringApplication.run(SocFileApplication.class, args);
+        System.out.println("(♥◠‿◠)ノ゙  文件服务模块启动成功   ლ(´ڡ`ლ)゙ ");
+    }
+}

+ 51 - 0
soc-modules/soc-modules-file/src/main/java/com/xunmei/file/config/ResourcesConfig.java

@@ -0,0 +1,51 @@
+package com.xunmei.file.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.io.File;
+
+/**
+ * 通用映射配置
+ * 
+ * @author xunmei
+ */
+@Configuration
+public class ResourcesConfig implements WebMvcConfigurer
+{
+    /**
+     * 上传文件存储在本地的根路径
+     */
+    @Value("${file.path}")
+    private String localFilePath;
+
+    /**
+     * 资源映射路径 前缀
+     */
+    @Value("${file.prefix}")
+    public String localFilePrefix;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry)
+    {
+        /** 本地文件上传路径 */
+        registry.addResourceHandler(localFilePrefix + "/**")
+                .addResourceLocations("file:" + localFilePath + File.separator);
+    }
+    
+    /**
+     * 开启跨域
+     */
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        // 设置允许跨域的路由
+        registry.addMapping(localFilePrefix  + "/**")
+                // 设置允许跨域请求的域名
+                .allowedOrigins("*")
+                // 设置允许的方法
+                .allowedMethods("GET");
+    }
+}

+ 48 - 0
soc-modules/soc-modules-file/src/main/java/com/xunmei/file/controller/SysFileController.java

@@ -0,0 +1,48 @@
+package com.xunmei.file.controller;
+
+import com.xunmei.common.core.domain.R;
+import com.xunmei.common.core.utils.file.FileUtils;
+import com.xunmei.file.service.ISysFileService;
+import com.xunmei.system.api.domain.SysFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 文件请求处理
+ * 
+ * @author xunmei
+ */
+@RestController
+public class SysFileController
+{
+    private static final Logger log = LoggerFactory.getLogger(SysFileController.class);
+
+    @Autowired
+    private ISysFileService sysFileService;
+
+    /**
+     * 文件上传请求
+     */
+    @PostMapping("upload")
+    public R<SysFile> upload(MultipartFile file)
+    {
+        try
+        {
+            // 上传并返回访问地址
+            String url = sysFileService.uploadFile(file);
+            SysFile sysFile = new SysFile();
+            sysFile.setName(FileUtils.getName(url));
+            sysFile.setUrl(url);
+            return R.ok(sysFile);
+        }
+        catch (Exception e)
+        {
+            log.error("上传文件失败", e);
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 20 - 0
soc-modules/soc-modules-file/src/main/java/com/xunmei/file/service/ISysFileService.java

@@ -0,0 +1,20 @@
+package com.xunmei.file.service;
+
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 文件上传接口
+ * 
+ * @author xunmei
+ */
+public interface ISysFileService
+{
+    /**
+     * 文件上传接口
+     * 
+     * @param file 上传的文件
+     * @return 访问地址
+     * @throws Exception
+     */
+    public String uploadFile(MultipartFile file) throws Exception;
+}

+ 50 - 0
soc-modules/soc-modules-file/src/main/java/com/xunmei/file/service/LocalSysFileServiceImpl.java

@@ -0,0 +1,50 @@
+package com.xunmei.file.service;
+
+import com.xunmei.file.utils.FileUploadUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 本地文件存储
+ * 
+ * @author xunmei
+ */
+@Primary
+@Service
+public class LocalSysFileServiceImpl implements ISysFileService
+{
+    /**
+     * 资源映射路径 前缀
+     */
+    @Value("${file.prefix}")
+    public String localFilePrefix;
+
+    /**
+     * 域名或本机访问地址
+     */
+    @Value("${file.domain}")
+    public String domain;
+    
+    /**
+     * 上传文件存储在本地的根路径
+     */
+    @Value("${file.path}")
+    private String localFilePath;
+
+    /**
+     * 本地文件上传接口
+     * 
+     * @param file 上传的文件
+     * @return 访问地址
+     * @throws Exception
+     */
+    @Override
+    public String uploadFile(MultipartFile file) throws Exception
+    {
+        String name = FileUploadUtils.upload(localFilePath, file);
+        String url = domain + localFilePrefix + name;
+        return url;
+    }
+}

+ 186 - 0
soc-modules/soc-modules-file/src/main/java/com/xunmei/file/utils/FileUploadUtils.java

@@ -0,0 +1,186 @@
+package com.xunmei.file.utils;
+
+import com.xunmei.common.core.exception.file.FileException;
+import com.xunmei.common.core.exception.file.FileNameLengthLimitExceededException;
+import com.xunmei.common.core.exception.file.FileSizeLimitExceededException;
+import com.xunmei.common.core.exception.file.InvalidExtensionException;
+import com.xunmei.common.core.utils.DateUtils;
+import com.xunmei.common.core.utils.StringUtils;
+import com.xunmei.common.core.utils.file.FileTypeUtils;
+import com.xunmei.common.core.utils.file.MimeTypeUtils;
+import com.xunmei.common.core.utils.uuid.Seq;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+/**
+ * 文件上传工具类
+ * 
+ * @author xunmei
+ */
+public class FileUploadUtils
+{
+    /**
+     * 默认大小 50M
+     */
+    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
+
+    /**
+     * 默认的文件名最大长度 100
+     */
+    public static final int DEFAULT_FILE_NAME_LENGTH = 100;
+
+    /**
+     * 根据文件路径上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws IOException
+     */
+    public static final String upload(String baseDir, MultipartFile file) throws IOException
+    {
+        try
+        {
+            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        catch (FileException fe)
+        {
+            throw new IOException(fe.getDefaultMessage(), fe);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @param allowedExtension 上传文件类型
+     * @return 返回上传成功的文件名
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws FileNameLengthLimitExceededException 文件名太长
+     * @throws IOException 比如读写文件出错时
+     * @throws InvalidExtensionException 文件校验异常
+     */
+    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+            InvalidExtensionException
+    {
+        int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
+        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
+        {
+            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+        }
+
+        assertAllowed(file, allowedExtension);
+
+        String fileName = extractFilename(file);
+
+        String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
+        file.transferTo(Paths.get(absPath));
+        return getPathFileName(fileName);
+    }
+
+    /**
+     * 编码文件名
+     */
+    public static final String extractFilename(MultipartFile file)
+    {
+        return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
+                FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), FileTypeUtils.getExtension(file));
+    }
+
+    private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
+    {
+        File desc = new File(uploadDir + File.separator + fileName);
+
+        if (!desc.exists())
+        {
+            if (!desc.getParentFile().exists())
+            {
+                desc.getParentFile().mkdirs();
+            }
+        }
+        return desc.isAbsolute() ? desc : desc.getAbsoluteFile();
+    }
+
+    private static final String getPathFileName(String fileName) throws IOException
+    {
+        String pathFileName = "/" + fileName;
+        return pathFileName;
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws InvalidExtensionException 文件校验异常
+     */
+    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, InvalidExtensionException
+    {
+        long size = file.getSize();
+        if (size > DEFAULT_MAX_SIZE)
+        {
+            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
+        }
+
+        String fileName = file.getOriginalFilename();
+        String extension = FileTypeUtils.getExtension(file);
+        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
+        {
+            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else
+            {
+                throw new InvalidExtensionException(allowedExtension, extension, fileName);
+            }
+        }
+    }
+
+    /**
+     * 判断MIME类型是否是允许的MIME类型
+     *
+     * @param extension 上传文件类型
+     * @param allowedExtension 允许上传文件类型
+     * @return true/false
+     */
+    public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
+    {
+        for (String str : allowedExtension)
+        {
+            if (str.equalsIgnoreCase(extension))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 12 - 0
soc-modules/soc-modules-file/src/main/resources/banner.txt

@@ -0,0 +1,12 @@
+Spring Boot Version: ${spring-boot.version}
+Spring Application Name: ${spring.application.name}
+
+ _______  _______  _______    _______ _________ _        _______
+(  ____ \(  ___  )(  ____ \  (  ____ \\__   __/( \      (  ____ \
+| (    \/| (   ) || (    \/  | (    \/   ) (   | (      | (    \/
+| (_____ | |   | || |        | (__       | |   | |      | (__
+(_____  )| |   | || |        |  __)      | |   | |      |  __)
+      ) || |   | || |        | (         | |   | |      | (
+/\____) || (___) || (____/\  | )      ___) (___| (____/\| (____/\
+\_______)(_______)(_______/  |/       \_______/(_______/(_______/
+

+ 28 - 0
soc-modules/soc-modules-file/src/main/resources/bootstrap.yml

@@ -0,0 +1,28 @@
+# Tomcat
+server:
+  port: 9300
+
+# Spring
+spring: 
+  application:
+    # 应用名称
+    name: soc-file
+  profiles:
+    # 环境配置
+    active: dev
+  cloud:
+    nacos:
+      discovery:
+        # 服务注册地址
+        server-addr: 10.87.10.227:8848
+      config:
+        # 配置中心地址
+        server-addr: 10.87.10.227:8848
+        # 配置文件格式
+        file-extension: yml
+        # 共享配置
+        shared-configs:
+          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
+logging:
+  file:
+    name: logs/${spring.application.name}/info.log

+ 74 - 0
soc-modules/soc-modules-file/src/main/resources/logback.xml

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="logs/soc-file" />
+   <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+    <!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+
+    <!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 系统模块日志级别控制  -->
+	<logger name="com.ruoyi" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+	
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+</configuration>

+ 0 - 1
soc-modules/soc-modules-system/src/main/java/com/xunmei/system/controller/MenuController.java

@@ -8,5 +8,4 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping("/menu")
 public class MenuController {
 
-
 }

+ 12 - 0
soc-modules/soc-modules-system/src/main/java/com/xunmei/system/domain/SysMenu.java

@@ -30,6 +30,9 @@ public class SysMenu extends BaseEntity {
     @TableField(exist = false)
     private String parentName;
 
+    /** 平台类型 0.移动端 1.web端*/
+    private Integer platformType;
+
     /** 父菜单ID */
     private Long parentId;
 
@@ -236,6 +239,14 @@ public class SysMenu extends BaseEntity {
         this.children = children;
     }
 
+    public Integer getPlatformType() {
+        return platformType;
+    }
+
+    public void setPlatformType(Integer platformType) {
+        this.platformType = platformType;
+    }
+
     @Override
     public String toString() {
         return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
@@ -243,6 +254,7 @@ public class SysMenu extends BaseEntity {
                 .append("menuName", getMenuName())
                 .append("parentId", getParentId())
                 .append("orderNum", getOrderNum())
+                .append("platformType", getPlatformType())
                 .append("path", getPath())
                 .append("component", getComponent())
                 .append("isFrame", getIsFrame())