LIVE_YE 1 жил өмнө
parent
commit
7bf9d794c8
22 өөрчлөгдсөн 2070 нэмэгдсэн , 21 устгасан
  1. 16 0
      ruoyi-admin/src/main/resources/application-dev.yml
  2. 16 0
      ruoyi-admin/src/main/resources/application-prod.yml
  3. 8 14
      ruoyi-admin/src/main/resources/application.yml
  4. 40 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/RuoYiConfig.java
  5. 144 0
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/Constants.java
  6. 12 0
      ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java
  7. 33 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/ServerConfig.java
  8. 144 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/constent/Constants.java
  9. 169 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/common/CommonController.java
  10. 2 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/info/XiaoyuanInfoController.java
  11. 12 6
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/RegisterSchoolServiceImpl.java
  12. 1 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
  13. 77 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/FileTypeUtils.java
  14. 61 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/FileUploadException.java
  15. 232 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/FileUploadUtils.java
  16. 243 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/FileUtils.java
  17. 99 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/ImageUtils.java
  18. 80 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/InvalidExtensionException.java
  19. 59 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/MimeTypeUtils.java
  20. 49 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/uuid/IdUtils.java
  21. 87 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/uuid/Seq.java
  22. 486 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/uuid/UUID.java

+ 16 - 0
ruoyi-admin/src/main/resources/application-dev.yml

@@ -1,3 +1,19 @@
+
+# 项目相关配置
+ruoyi:
+  # 名称
+  name: zhinengxiaoguanjia
+  # 版本
+  version: ${revision}
+  # 版权年份
+  copyrightYear: 2023
+  # 实例演示开关
+  demoEnabled: false
+  # 获取ip地址开关
+  addressEnabled: true
+  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
+  profile: D:/ruoyi/uploadPath
+
 --- # 监控中心配置
 spring.boot.admin.client:
   # 增加客户端开关

+ 16 - 0
ruoyi-admin/src/main/resources/application-prod.yml

@@ -1,3 +1,19 @@
+
+# 项目相关配置
+ruoyi:
+  # 名称
+  name: zhinengxiaoguanjia
+  # 版本
+  version: ${revision}
+  # 版权年份
+  copyrightYear: 2023
+  # 实例演示开关
+  demoEnabled: false
+  # 获取ip地址开关
+  addressEnabled: true
+  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
+  profile: /home/ruoyi/uploadPath
+
 --- # 临时文件存储位置 避免临时文件被系统清理报错
 spring.servlet.multipart.location: /ruoyi/server/temp
 

+ 8 - 14
ruoyi-admin/src/main/resources/application.yml

@@ -1,15 +1,3 @@
-# 项目相关配置
-ruoyi:
-  # 名称
-  name: zhinengxiaoguanjia
-  # 版本
-  version: ${revision}
-  # 版权年份
-  copyrightYear: 2023
-  # 实例演示开关
-  demoEnabled: false
-  # 获取ip地址开关
-  addressEnabled: true
 
 captcha:
   enable: true
@@ -64,7 +52,7 @@ user:
 # Spring配置
 spring:
   application:
-    name: ${ruoyi.name}
+    name: zhinengxiaoguanjia
   # 资源信息
   messages:
     # 国际化资源文件路径
@@ -78,6 +66,11 @@ spring:
       max-file-size: 100MB
       # 设置总上传的文件大小
       max-request-size: 50MB
+  # 服务模块
+  devtools:
+    restart:
+      # 热部署开关
+      enabled: true
   mvc:
     format:
       date-time: yyyy-MM-dd HH:mm:ss
@@ -118,6 +111,7 @@ security:
     - /**/*.html
     - /**/*.css
     - /**/*.js
+    - /profile/**
     # 公共路径
     - /favicon.ico
     - /error
@@ -237,7 +231,7 @@ xss:
   # 过滤开关
   enabled: true
   # 排除链接(多个用逗号分隔)
-  excludes: /system/notice
+  excludes: /system/notice,/system/app/new,/system/xiaoYuanInfo
   # 匹配链接
   urlPatterns: /system/*,/monitor/*,/tool/*
 

+ 40 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/RuoYiConfig.java

@@ -36,6 +36,10 @@ public class RuoYiConfig {
      */
     private boolean demoEnabled;
 
+    /** 上传路径 */
+    @Getter
+    private static String profile;
+
     /**
      * 获取地址开关
      */
@@ -46,4 +50,40 @@ public class RuoYiConfig {
         RuoYiConfig.addressEnabled = addressEnabled;
     }
 
+    public void setProfile(String profile) {
+        RuoYiConfig.profile = profile;
+    }
+
+    /**
+     * 获取导入上传路径
+     */
+    public static String getImportPath()
+    {
+        return getProfile() + "/import";
+    }
+
+    /**
+     * 获取头像上传路径
+     */
+    public static String getAvatarPath()
+    {
+        return getProfile() + "/avatar";
+    }
+
+    /**
+     * 获取下载路径
+     */
+    public static String getDownloadPath()
+    {
+        return getProfile() + "/download/";
+    }
+
+    /**
+     * 获取上传路径
+     */
+    public static String getUploadPath()
+    {
+        return getProfile() + "/upload";
+    }
+
 }

+ 144 - 0
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/Constants.java

@@ -0,0 +1,144 @@
+package org.dromara.common.web.config;
+
+
+/**
+ * 通用常量信息
+ *
+ * @author ruoyi
+ */
+public class Constants
+{
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    public static final String GBK = "GBK";
+
+    /**
+     * www主域
+     */
+    public static final String WWW = "www.";
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    public static final String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    public static final String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    public static final String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    public static final String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    public static final String LOGOUT = "Logout";
+
+    /**
+     * 注册
+     */
+    public static final String REGISTER = "Register";
+
+    /**
+     * 登录失败
+     */
+    public static final String LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    public static final Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    public static final String TOKEN = "token";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    /**
+     * 用户ID
+     */
+    public static final String JWT_USERID = "userid";
+
+    /**
+     * 自定义常量
+     */
+    public static final String ONE = "1";
+    public static final String TWO = "2";
+    public static final String THR = "3";
+    public static final String FOR = "4";
+
+    /**
+     * 用户头像
+     */
+    public static final String JWT_AVATAR = "avatar";
+
+    /**
+     * 创建时间
+     */
+    public static final String JWT_CREATED = "created";
+
+    /**
+     * 用户权限
+     */
+    public static final String JWT_AUTHORITIES = "authorities";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+
+    /**
+     * RMI 远程方法调用
+     */
+    public static final String LOOKUP_RMI = "rmi:";
+
+    /**
+     * LDAP 远程方法调用
+     */
+    public static final String LOOKUP_LDAP = "ldap:";
+
+    /**
+     * LDAPS 远程方法调用
+     */
+    public static final String LOOKUP_LDAPS = "ldaps:";
+
+    /**
+     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
+     */
+    public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" };
+
+    /**
+     * 定时任务违规的字符
+     */
+    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
+            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" };
+}

+ 12 - 0
ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/ResourcesConfig.java

@@ -1,8 +1,10 @@
 package org.dromara.common.web.config;
 
+import org.dromara.common.core.config.RuoYiConfig;
 import org.dromara.common.web.interceptor.PlusWebInvokeTimeInterceptor;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.context.annotation.Bean;
+import org.springframework.http.CacheControl;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import org.springframework.web.filter.CorsFilter;
@@ -10,6 +12,8 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * 通用配置
  *
@@ -26,6 +30,14 @@ public class ResourcesConfig implements WebMvcConfigurer {
 
     @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        /** 本地文件上传路径 */
+        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
+            .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
+
+        /** swagger配置 */
+        registry.addResourceHandler("/swagger-ui/**")
+            .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
+            .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());;
     }
 
     /**

+ 33 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/ServerConfig.java

@@ -0,0 +1,33 @@
+package org.dromara.system.config;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.dromara.common.core.utils.ServletUtils;
+import org.springframework.stereotype.Component;
+
+
+/**
+ * 服务相关配置
+ *
+ * @author ruoyi
+ */
+@Component
+public class ServerConfig
+{
+    /**
+     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
+     *
+     * @return 服务地址
+     */
+    public String getUrl()
+    {
+        HttpServletRequest request = ServletUtils.getRequest();
+        return getDomain(request);
+    }
+
+    public static String getDomain(HttpServletRequest request)
+    {
+        StringBuffer url = request.getRequestURL();
+        String contextPath = request.getServletContext().getContextPath();
+        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
+    }
+}

+ 144 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/constent/Constants.java

@@ -0,0 +1,144 @@
+package org.dromara.system.constent;
+
+
+/**
+ * 通用常量信息
+ *
+ * @author ruoyi
+ */
+public class Constants
+{
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    public static final String GBK = "GBK";
+
+    /**
+     * www主域
+     */
+    public static final String WWW = "www.";
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    public static final String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    public static final String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    public static final String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    public static final String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    public static final String LOGOUT = "Logout";
+
+    /**
+     * 注册
+     */
+    public static final String REGISTER = "Register";
+
+    /**
+     * 登录失败
+     */
+    public static final String LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    public static final Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    public static final String TOKEN = "token";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    /**
+     * 用户ID
+     */
+    public static final String JWT_USERID = "userid";
+
+    /**
+     * 自定义常量
+     */
+    public static final String ONE = "1";
+    public static final String TWO = "2";
+    public static final String THR = "3";
+    public static final String FOR = "4";
+
+    /**
+     * 用户头像
+     */
+    public static final String JWT_AVATAR = "avatar";
+
+    /**
+     * 创建时间
+     */
+    public static final String JWT_CREATED = "created";
+
+    /**
+     * 用户权限
+     */
+    public static final String JWT_AUTHORITIES = "authorities";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+
+    /**
+     * RMI 远程方法调用
+     */
+    public static final String LOOKUP_RMI = "rmi:";
+
+    /**
+     * LDAP 远程方法调用
+     */
+    public static final String LOOKUP_LDAP = "ldap:";
+
+    /**
+     * LDAPS 远程方法调用
+     */
+    public static final String LOOKUP_LDAPS = "ldaps:";
+
+    /**
+     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
+     */
+    public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" };
+
+    /**
+     * 定时任务违规的字符
+     */
+    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
+            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" };
+}

+ 169 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/common/CommonController.java

@@ -0,0 +1,169 @@
+package org.dromara.system.controller.common;
+
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.dromara.common.core.config.RuoYiConfig;
+import org.dromara.common.core.domain.AjaxResult;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.config.ServerConfig;
+import org.dromara.system.constent.Constants;
+import org.dromara.system.utils.file.FileUploadUtils;
+import org.dromara.system.utils.file.FileUtils;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 通用请求处理
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/common")
+public class CommonController
+{
+    private static final Logger log = LoggerFactory.getLogger(CommonController.class);
+
+    @Autowired
+    private ServerConfig serverConfig;
+
+    @Autowired
+    private RuoYiConfig ruoYiConfig;
+
+    private static final String FILE_DELIMETER = ",";
+
+    /**
+     * 通用下载请求
+     *
+     * @param fileName 文件名称
+     * @param delete 是否删除
+     */
+    @GetMapping("/download")
+    public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request)
+    {
+        try
+        {
+            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.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            FileUtils.setAttachmentResponseHeader(response, realFileName);
+            FileUtils.writeBytes(filePath, response.getOutputStream());
+            if (delete)
+            {
+                FileUtils.deleteFile(filePath);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("下载文件失败", e);
+        }
+    }
+
+    /**
+     * 通用上传请求(单个)
+     */
+    @PostMapping("/upload")
+    public AjaxResult uploadFile(MultipartFile file) throws Exception
+    {
+        try
+        {
+            // 上传文件路径
+            String filePath = ruoYiConfig.getUploadPath();
+            // 上传并返回新文件名称
+            String fileName = FileUploadUtils.upload(filePath, file);
+            String url = serverConfig.getUrl() + fileName;
+            AjaxResult ajax = AjaxResult.success();
+            ajax.put("url", url);
+            ajax.put("fileName", fileName);
+            ajax.put("newFileName", FileUtils.getName(fileName));
+            ajax.put("originalFilename", file.getOriginalFilename());
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 通用上传请求(多个)
+     */
+    @PostMapping("/uploads")
+    public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception
+    {
+        try
+        {
+            // 上传文件路径
+            String filePath = RuoYiConfig.getUploadPath();
+            List<String> urls = new ArrayList<String>();
+            List<String> fileNames = new ArrayList<String>();
+            List<String> newFileNames = new ArrayList<String>();
+            List<String> originalFilenames = new ArrayList<String>();
+            for (MultipartFile file : files)
+            {
+                // 上传并返回新文件名称
+                String fileName = FileUploadUtils.upload(filePath, file);
+                String url = serverConfig.getUrl() + fileName;
+                urls.add(url);
+                fileNames.add(fileName);
+                newFileNames.add(FileUtils.getName(fileName));
+                originalFilenames.add(file.getOriginalFilename());
+            }
+            AjaxResult ajax = AjaxResult.success();
+            ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER));
+            ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER));
+            ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER));
+            ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER));
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 本地资源通用下载
+     */
+    @GetMapping("/download/resource")
+    public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
+            throws Exception
+    {
+        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 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/info/XiaoyuanInfoController.java

@@ -2,6 +2,7 @@ package org.dromara.system.controller.info;
 
 import java.util.List;
 
+import cn.dev33.satoken.annotation.SaIgnore;
 import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
@@ -64,6 +65,7 @@ public class XiaoyuanInfoController extends BaseController {
      *
      * @param infoId 主键
      */
+    @SaIgnore
     @SaCheckPermission("xiaoYuanInfo:xiaoYuanInfo:query")
     @GetMapping("/{infoId}")
     public R<XiaoyuanInfoVo> getInfo(@NotNull(message = "主键不能为空")

+ 12 - 6
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/RegisterSchoolServiceImpl.java

@@ -1,7 +1,9 @@
 package org.dromara.system.service.impl;
 
+import lombok.SneakyThrows;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.ClassUtils;
 import org.dromara.common.core.utils.DateUtils;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
@@ -113,6 +115,7 @@ public class RegisterSchoolServiceImpl implements IRegisterSchoolService {
     /**
      * 新增注册-学校
      */
+    @SneakyThrows
     @Override
     @Transactional(rollbackFor = Exception.class)
     public R<RegisterSchoolVo> insertByBo(RegisterSchoolBo bo) {
@@ -135,18 +138,19 @@ public class RegisterSchoolServiceImpl implements IRegisterSchoolService {
         RegisterSchool add = MapstructUtils.convert(bo, RegisterSchool.class);
         validEntityBeforeSave(add);
         boolean flag = baseMapper.insert(add) > 0;
+        bo.setId(add.getId());
 
         if(bo.getRegisterSchoolFileList()!=null && bo.getRegisterSchoolFileList().size()>0){
             List<RegisterSchoolFileVo> registerSchoolFile = bo.getRegisterSchoolFileList();
             for (RegisterSchoolFileVo schoolFile : registerSchoolFile) {
                 schoolFile.setSchoolId(bo.getId());
-                RegisterSchoolFileBo file = MapstructUtils.convert(schoolFile, RegisterSchoolFileBo.class);
-                registerSchoolFileService.insertByBo(file);
+
+                RegisterSchoolFileBo fileBo = new RegisterSchoolFileBo();
+                ClassUtils.copyProperties(schoolFile, fileBo);
+                registerSchoolFileService.insertByBo(fileBo);
             }
         }
 
-
-
         if (flag) {
             bo.setId(add.getId());
             return R.ok();
@@ -157,6 +161,7 @@ public class RegisterSchoolServiceImpl implements IRegisterSchoolService {
     /**
      * 修改注册-学校
      */
+    @SneakyThrows
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean updateByBo(RegisterSchoolBo bo) {
@@ -168,8 +173,9 @@ public class RegisterSchoolServiceImpl implements IRegisterSchoolService {
             List<RegisterSchoolFileVo> registerSchoolFile = bo.getRegisterSchoolFileList();
             for (RegisterSchoolFileVo schoolFile : registerSchoolFile) {
                 schoolFile.setSchoolId(bo.getId());
-                RegisterSchoolFileBo file = MapstructUtils.convert(schoolFile, RegisterSchoolFileBo.class);
-                registerSchoolFileService.insertByBo(file);
+                RegisterSchoolFileBo fileBo = new RegisterSchoolFileBo();
+                ClassUtils.copyProperties(schoolFile, fileBo);
+                registerSchoolFileService.insertByBo(fileBo);
             }
         }
         RegisterSchool update = MapstructUtils.convert(bo, RegisterSchool.class);

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java

@@ -124,7 +124,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
     public SysOssVo upload(MultipartFile file) {
         String originalfileName = file.getOriginalFilename();
         String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
-        OssClient storage = OssFactory.instance("image");
+        OssClient storage = OssFactory.instance();
         UploadResult uploadResult;
         try {
             uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());

+ 77 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/FileTypeUtils.java

@@ -0,0 +1,77 @@
+package org.dromara.system.utils.file;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+
+/**
+ * 文件类型工具类
+ *
+ * @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();
+    }
+
+    /**
+     * 获取文件类型
+     *
+     * @param photoByte 文件字节码
+     * @return 后缀(不含".")
+     */
+    public static String getFileExtendName(byte[] photoByte)
+    {
+        String strFileExtendName = "JPG";
+        if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
+                && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
+        {
+            strFileExtendName = "GIF";
+        }
+        else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
+        {
+            strFileExtendName = "JPG";
+        }
+        else if ((photoByte[0] == 66) && (photoByte[1] == 77))
+        {
+            strFileExtendName = "BMP";
+        }
+        else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
+        {
+            strFileExtendName = "PNG";
+        }
+        return strFileExtendName;
+    }
+}

+ 61 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/FileUploadException.java

@@ -0,0 +1,61 @@
+package org.dromara.system.utils.file;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * 文件上传异常类
+ *
+ * @author ruoyi
+ */
+public class FileUploadException extends Exception
+{
+
+    private static final long serialVersionUID = 1L;
+
+    private final Throwable cause;
+
+    public FileUploadException()
+    {
+        this(null, null);
+    }
+
+    public FileUploadException(final String msg)
+    {
+        this(msg, null);
+    }
+
+    public FileUploadException(String msg, Throwable cause)
+    {
+        super(msg);
+        this.cause = cause;
+    }
+
+    @Override
+    public void printStackTrace(PrintStream stream)
+    {
+        super.printStackTrace(stream);
+        if (cause != null)
+        {
+            stream.println("Caused by:");
+            cause.printStackTrace(stream);
+        }
+    }
+
+    @Override
+    public void printStackTrace(PrintWriter writer)
+    {
+        super.printStackTrace(writer);
+        if (cause != null)
+        {
+            writer.println("Caused by:");
+            cause.printStackTrace(writer);
+        }
+    }
+
+    @Override
+    public Throwable getCause()
+    {
+        return cause;
+    }
+}

+ 232 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/FileUploadUtils.java

@@ -0,0 +1,232 @@
+package org.dromara.system.utils.file;
+
+import org.apache.commons.io.FilenameUtils;
+import org.dromara.common.core.config.RuoYiConfig;
+import org.dromara.common.core.exception.file.FileNameLengthLimitExceededException;
+import org.dromara.common.core.exception.file.FileSizeLimitExceededException;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.constent.Constants;
+import org.dromara.system.utils.uuid.Seq;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+/**
+ * 文件上传工具类
+ *
+ * @author ruoyi
+ */
+public class FileUploadUtils
+{
+    /**
+     * 默认大小 50M
+     */
+    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
+
+    /**
+     * 默认的文件名最大长度 100
+     */
+    public static final int DEFAULT_FILE_NAME_LENGTH = 100;
+
+    /**
+     * 默认上传的地址
+     */
+    private static String defaultBaseDir = RuoYiConfig.getProfile();
+
+    public static void setDefaultBaseDir(String defaultBaseDir)
+    {
+        FileUploadUtils.defaultBaseDir = defaultBaseDir;
+    }
+
+    public static String getDefaultBaseDir()
+    {
+        return defaultBaseDir;
+    }
+
+    /**
+     * 以默认配置进行文件上传
+     *
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws Exception
+     */
+    public static final String upload(MultipartFile file) throws IOException
+    {
+        try
+        {
+            return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 根据文件路径上传
+     *
+     * @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 (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(baseDir, fileName);
+    }
+
+    /**
+     * 编码文件名
+     */
+    public static final String extractFilename(MultipartFile file)
+    {
+        return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
+                FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
+    }
+
+    public 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;
+    }
+
+    public static final String getPathFileName(String uploadDir, String fileName) throws IOException
+    {
+        int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
+        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
+        return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @return
+     * @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 = 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
+     */
+    public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
+    {
+        for (String str : allowedExtension)
+        {
+            if (str.equalsIgnoreCase(extension))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取文件名的后缀
+     *
+     * @param file 表单文件
+     * @return 后缀名
+     */
+    public static final String getExtension(MultipartFile file)
+    {
+        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
+        if (StringUtils.isEmpty(extension))
+        {
+            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
+        }
+        return extension;
+    }
+}

+ 243 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/FileUtils.java

@@ -0,0 +1,243 @@
+package org.dromara.system.utils.file;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.dromara.common.core.config.RuoYiConfig;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.utils.uuid.IdUtils;
+import java.io.*;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 文件处理工具类
+ *
+ * @author ruoyi
+ */
+public class FileUtils {
+    public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
+
+    /**
+     * 输出指定文件的byte数组
+     *
+     * @param filePath 文件路径
+     * @param os       输出流
+     * @return
+     */
+    public static void writeBytes(String filePath, OutputStream os) throws IOException {
+        FileInputStream fis = null;
+        try {
+            File file = new File(filePath);
+            if (!file.exists()) {
+                throw new FileNotFoundException(filePath);
+            }
+            fis = new FileInputStream(file);
+            byte[] b = new byte[1024];
+            int length;
+            while ((length = fis.read(b)) > 0) {
+                os.write(b, 0, length);
+            }
+        } catch (IOException e) {
+            throw e;
+        } finally {
+            IOUtils.close(os);
+            IOUtils.close(fis);
+        }
+    }
+
+    /**
+     * 写数据到文件中
+     *
+     * @param data 数据
+     * @return 目标文件
+     * @throws IOException IO异常
+     */
+    /*public static String writeImportBytes(byte[] data) throws IOException {
+        return writeBytes(data, RuoYiConfig.getImportPath());
+    }*/
+
+    /**
+     * 写数据到文件中
+     *
+     * @param data      数据
+     * @param uploadDir 目标文件
+     * @return 目标文件
+     * @throws IOException IO异常
+     */
+    public static String writeBytes(byte[] data, String uploadDir) throws IOException {
+        FileOutputStream fos = null;
+        String pathName = "";
+        try {
+            String extension = getFileExtendName(data);
+            pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
+            File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName);
+            fos = new FileOutputStream(file);
+            fos.write(data);
+        } finally {
+            IOUtils.close(fos);
+        }
+        return FileUploadUtils.getPathFileName(uploadDir, pathName);
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param filePath 文件
+     * @return
+     */
+    public static boolean deleteFile(String filePath) {
+        boolean flag = false;
+        File file = new File(filePath);
+        // 路径为文件且不为空则进行删除
+        if (file.isFile() && file.exists()) {
+            flag = file.delete();
+        }
+        return flag;
+    }
+
+    /**
+     * 文件名称验证
+     *
+     * @param filename 文件名称
+     * @return true 正常 false 非法
+     */
+    public static boolean isValidFilename(String filename) {
+        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;
+    }
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @param request  请求对象
+     * @param fileName 文件名
+     * @return 编码后的文件名
+     */
+    public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException {
+        final String agent = request.getHeader("USER-AGENT");
+        String filename = fileName;
+        if (agent.contains("MSIE")) {
+            // IE浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+            filename = filename.replace("+", " ");
+        } else if (agent.contains("Firefox")) {
+            // 火狐浏览器
+            filename = new String(fileName.getBytes(), "ISO8859-1");
+        } else if (agent.contains("Chrome")) {
+            // google浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+        } else {
+            // 其它浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+        }
+        return filename;
+    }
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @param response     响应对象
+     * @param realFileName 真实文件名
+     */
+    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.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
+        response.setHeader("Content-disposition", contentDispositionValue.toString());
+        response.setHeader("download-filename", percentEncodedFileName);
+    }
+
+    /**
+     * 百分号编码工具方法
+     *
+     * @param s 需要百分号编码的字符串
+     * @return 百分号编码后的字符串
+     */
+    public static String percentEncode(String s) throws UnsupportedEncodingException {
+        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
+        return encode.replaceAll("\\+", "%20");
+    }
+
+    /**
+     * 获取图像后缀
+     *
+     * @param photoByte 图像数据
+     * @return 后缀名
+     */
+    public static String getFileExtendName(byte[] photoByte) {
+        String strFileExtendName = "jpg";
+        if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
+            && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) {
+            strFileExtendName = "gif";
+        } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) {
+            strFileExtendName = "jpg";
+        } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) {
+            strFileExtendName = "bmp";
+        } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) {
+            strFileExtendName = "png";
+        }
+        return strFileExtendName;
+    }
+
+    /**
+     * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png
+     *
+     * @param fileName 路径名称
+     * @return 没有文件路径的名称
+     */
+    public static String getName(String fileName) {
+        if (fileName == null) {
+            return null;
+        }
+        int lastUnixPos = fileName.lastIndexOf('/');
+        int lastWindowsPos = fileName.lastIndexOf('\\');
+        int index = Math.max(lastUnixPos, lastWindowsPos);
+        return fileName.substring(index + 1);
+    }
+
+    /**
+     * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi
+     *
+     * @param fileName 路径名称
+     * @return 没有文件路径和后缀的名称
+     */
+    public static String getNameNotSuffix(String fileName) {
+        if (fileName == null) {
+            return null;
+        }
+        String baseName = FilenameUtils.getBaseName(fileName);
+        return baseName;
+    }
+}

+ 99 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/ImageUtils.java

@@ -0,0 +1,99 @@
+package org.dromara.system.utils.file;
+
+import org.apache.poi.util.IOUtils;
+import org.dromara.common.core.config.RuoYiConfig;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.constent.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+
+/**
+ * 图片处理工具类
+ *
+ * @author ruoyi
+ */
+public class ImageUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
+
+    public static byte[] getImage(String imagePath)
+    {
+        InputStream is = getFile(imagePath);
+        try
+        {
+            return IOUtils.toByteArray(is);
+        }
+        catch (Exception e)
+        {
+            log.error("图片加载异常 {}", e);
+            return null;
+        }
+        finally
+        {
+            IOUtils.closeQuietly(is);
+        }
+    }
+
+    public static InputStream getFile(String imagePath)
+    {
+        try
+        {
+            byte[] result = readFile(imagePath);
+            result = Arrays.copyOf(result, result.length);
+            return new ByteArrayInputStream(result);
+        }
+        catch (Exception e)
+        {
+            log.error("获取图片异常 {}", e);
+        }
+        return null;
+    }
+
+    /**
+     * 读取文件为字节数据
+     *
+     * @param url 地址
+     * @return 字节数据
+     */
+    public static byte[] readFile(String url)
+    {
+        InputStream in = null;
+        try
+        {
+            if (url.startsWith("http"))
+            {
+                // 网络地址
+                URL urlObj = new URL(url);
+                URLConnection urlConnection = urlObj.openConnection();
+                urlConnection.setConnectTimeout(30 * 1000);
+                urlConnection.setReadTimeout(60 * 1000);
+                urlConnection.setDoInput(true);
+                in = urlConnection.getInputStream();
+            }
+            else
+            {
+                // 本机地址
+                String localPath = RuoYiConfig.getProfile();
+                String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX);
+                in = new FileInputStream(downloadPath);
+            }
+            return IOUtils.toByteArray(in);
+        }
+        catch (Exception e)
+        {
+            log.error("获取文件路径异常 {}", e);
+            return null;
+        }
+        finally
+        {
+            IOUtils.closeQuietly(in);
+        }
+    }
+}

+ 80 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/InvalidExtensionException.java

@@ -0,0 +1,80 @@
+package org.dromara.system.utils.file;
+
+import java.util.Arrays;
+
+/**
+ * 文件上传 误异常类
+ *
+ * @author ruoyi
+ */
+public class InvalidExtensionException extends FileUploadException
+{
+    private static final long serialVersionUID = 1L;
+
+    private String[] allowedExtension;
+    private String extension;
+    private String filename;
+
+    public InvalidExtensionException(String[] allowedExtension, String extension, String filename)
+    {
+        super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式");
+        this.allowedExtension = allowedExtension;
+        this.extension = extension;
+        this.filename = filename;
+    }
+
+    public String[] getAllowedExtension()
+    {
+        return allowedExtension;
+    }
+
+    public String getExtension()
+    {
+        return extension;
+    }
+
+    public String getFilename()
+    {
+        return filename;
+    }
+
+    public static class InvalidImageExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidFlashExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidMediaExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidVideoExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+}

+ 59 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/file/MimeTypeUtils.java

@@ -0,0 +1,59 @@
+package org.dromara.system.utils.file;
+
+/**
+ * 媒体类型工具类
+ *
+ * @author ruoyi
+ */
+public class MimeTypeUtils
+{
+    public static final String IMAGE_PNG = "image/png";
+
+    public static final String IMAGE_JPG = "image/jpg";
+
+    public static final String IMAGE_JPEG = "image/jpeg";
+
+    public static final String IMAGE_BMP = "image/bmp";
+
+    public static final String IMAGE_GIF = "image/gif";
+
+    public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
+
+    public static final String[] FLASH_EXTENSION = { "swf", "flv" };
+
+    public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+            "asf", "rm", "rmvb" };
+
+    public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" };
+
+    public static final String[] DEFAULT_ALLOWED_EXTENSION = {
+            // 图片
+            "bmp", "gif", "jpg", "jpeg", "png",
+            // word excel powerpoint
+            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+            // 压缩文件
+            "rar", "zip", "gz", "bz2",
+            // 视频格式
+            "mp4", "avi", "rmvb",
+            // pdf
+            "pdf" };
+
+    public static String getExtension(String prefix)
+    {
+        switch (prefix)
+        {
+            case IMAGE_PNG:
+                return "png";
+            case IMAGE_JPG:
+                return "jpg";
+            case IMAGE_JPEG:
+                return "jpeg";
+            case IMAGE_BMP:
+                return "bmp";
+            case IMAGE_GIF:
+                return "gif";
+            default:
+                return "";
+        }
+    }
+}

+ 49 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/uuid/IdUtils.java

@@ -0,0 +1,49 @@
+package org.dromara.system.utils.uuid;
+
+/**
+ * ID生成器工具类
+ *
+ * @author ruoyi
+ */
+public class IdUtils
+{
+    /**
+     * 获取随机UUID
+     *
+     * @return 随机UUID
+     */
+    public static String randomUUID()
+    {
+        return UUID.randomUUID().toString();
+    }
+
+    /**
+     * 简化的UUID,去掉了横线
+     *
+     * @return 简化的UUID,去掉了横线
+     */
+    public static String simpleUUID()
+    {
+        return UUID.randomUUID().toString(true);
+    }
+
+    /**
+     * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID
+     *
+     * @return 随机UUID
+     */
+    public static String fastUUID()
+    {
+        return UUID.fastUUID().toString();
+    }
+
+    /**
+     * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID
+     *
+     * @return 简化的UUID,去掉了横线
+     */
+    public static String fastSimpleUUID()
+    {
+        return UUID.fastUUID().toString(true);
+    }
+}

+ 87 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/uuid/Seq.java

@@ -0,0 +1,87 @@
+package org.dromara.system.utils.uuid;
+
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author ruoyi 序列生成类
+ */
+public class Seq
+{
+    // 通用序列类型
+    public static final String commSeqType = "COMMON";
+
+    // 上传序列类型
+    public static final String uploadSeqType = "UPLOAD";
+
+    // 通用接口序列数
+    private static AtomicInteger commSeq = new AtomicInteger(1);
+
+    // 上传接口序列数
+    private static AtomicInteger uploadSeq = new AtomicInteger(1);
+
+    // 机器标识
+    private static final String machineCode = "A";
+
+    /**
+     * 获取通用序列号
+     *
+     * @return 序列值
+     */
+    public static String getId()
+    {
+        return getId(commSeqType);
+    }
+
+    /**
+     * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串
+     *
+     * @return 序列值
+     */
+    public static String getId(String type)
+    {
+        AtomicInteger atomicInt = commSeq;
+        if (uploadSeqType.equals(type))
+        {
+            atomicInt = uploadSeq;
+        }
+        return getId(atomicInt, 3);
+    }
+
+    /**
+     * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串
+     *
+     * @param atomicInt 序列数
+     * @param length 数值长度
+     * @return 序列值
+     */
+    public static String getId(AtomicInteger atomicInt, int length)
+    {
+        String result = DateUtils.dateTimeNow();
+        result += machineCode;
+        result += getSeq(atomicInt, length);
+        return result;
+    }
+
+    /**
+     * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数
+     *
+     * @return 序列值
+     */
+    private synchronized static String getSeq(AtomicInteger atomicInt, int length)
+    {
+        // 先取值再+1
+        int value = atomicInt.getAndIncrement();
+
+        // 如果更新后值>=10 的 (length)幂次方则重置为1
+        int maxSeq = (int) Math.pow(10, length);
+        if (atomicInt.get() >= maxSeq)
+        {
+            atomicInt.set(1);
+        }
+        // 转字符串,用0左补齐
+        return StringUtils.padl(value, length);
+    }
+}

+ 486 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/utils/uuid/UUID.java

@@ -0,0 +1,486 @@
+package org.dromara.system.utils.uuid;
+
+
+import cn.hutool.core.exceptions.UtilException;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 提供通用唯一识别码(universally unique identifier)(UUID)实现
+ *
+ * @author ruoyi
+ */
+public final class UUID implements java.io.Serializable, Comparable<UUID>
+{
+    private static final long serialVersionUID = -1185015143654744140L;
+
+    /**
+     * SecureRandom 的单例
+     *
+     */
+    private static class Holder
+    {
+        static final SecureRandom numberGenerator = getSecureRandom();
+    }
+
+    /** 此UUID的最高64有效位 */
+    private final long mostSigBits;
+
+    /** 此UUID的最低64有效位 */
+    private final long leastSigBits;
+
+    /**
+     * 私有构造
+     *
+     * @param data 数据
+     */
+    private UUID(byte[] data)
+    {
+        long msb = 0;
+        long lsb = 0;
+        assert data.length == 16 : "data must be 16 bytes in length";
+        for (int i = 0; i < 8; i++)
+        {
+            msb = (msb << 8) | (data[i] & 0xff);
+        }
+        for (int i = 8; i < 16; i++)
+        {
+            lsb = (lsb << 8) | (data[i] & 0xff);
+        }
+        this.mostSigBits = msb;
+        this.leastSigBits = lsb;
+    }
+
+    /**
+     * 使用指定的数据构造新的 UUID。
+     *
+     * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位
+     * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位
+     */
+    public UUID(long mostSigBits, long leastSigBits)
+    {
+        this.mostSigBits = mostSigBits;
+        this.leastSigBits = leastSigBits;
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。
+     *
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID fastUUID()
+    {
+        return randomUUID(false);
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
+     *
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID randomUUID()
+    {
+        return randomUUID(true);
+    }
+
+    /**
+     * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。
+     *
+     * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能
+     * @return 随机生成的 {@code UUID}
+     */
+    public static UUID randomUUID(boolean isSecure)
+    {
+        final Random ng = isSecure ? Holder.numberGenerator : getRandom();
+
+        byte[] randomBytes = new byte[16];
+        ng.nextBytes(randomBytes);
+        randomBytes[6] &= 0x0f; /* clear version */
+        randomBytes[6] |= 0x40; /* set to version 4 */
+        randomBytes[8] &= 0x3f; /* clear variant */
+        randomBytes[8] |= 0x80; /* set to IETF variant */
+        return new UUID(randomBytes);
+    }
+
+    /**
+     * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。
+     *
+     * @param name 用于构造 UUID 的字节数组。
+     *
+     * @return 根据指定数组生成的 {@code UUID}
+     */
+    public static UUID nameUUIDFromBytes(byte[] name)
+    {
+        MessageDigest md;
+        try
+        {
+            md = MessageDigest.getInstance("MD5");
+        }
+        catch (NoSuchAlgorithmException nsae)
+        {
+            throw new InternalError("MD5 not supported");
+        }
+        byte[] md5Bytes = md.digest(name);
+        md5Bytes[6] &= 0x0f; /* clear version */
+        md5Bytes[6] |= 0x30; /* set to version 3 */
+        md5Bytes[8] &= 0x3f; /* clear variant */
+        md5Bytes[8] |= 0x80; /* set to IETF variant */
+        return new UUID(md5Bytes);
+    }
+
+    /**
+     * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。
+     *
+     * @param name 指定 {@code UUID} 字符串
+     * @return 具有指定值的 {@code UUID}
+     * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常
+     *
+     */
+    public static UUID fromString(String name)
+    {
+        String[] components = name.split("-");
+        if (components.length != 5)
+        {
+            throw new IllegalArgumentException("Invalid UUID string: " + name);
+        }
+        for (int i = 0; i < 5; i++)
+        {
+            components[i] = "0x" + components[i];
+        }
+
+        long mostSigBits = Long.decode(components[0]).longValue();
+        mostSigBits <<= 16;
+        mostSigBits |= Long.decode(components[1]).longValue();
+        mostSigBits <<= 16;
+        mostSigBits |= Long.decode(components[2]).longValue();
+
+        long leastSigBits = Long.decode(components[3]).longValue();
+        leastSigBits <<= 48;
+        leastSigBits |= Long.decode(components[4]).longValue();
+
+        return new UUID(mostSigBits, leastSigBits);
+    }
+
+    /**
+     * 返回此 UUID 的 128 位值中的最低有效 64 位。
+     *
+     * @return 此 UUID 的 128 位值中的最低有效 64 位。
+     */
+    public long getLeastSignificantBits()
+    {
+        return leastSigBits;
+    }
+
+    /**
+     * 返回此 UUID 的 128 位值中的最高有效 64 位。
+     *
+     * @return 此 UUID 的 128 位值中最高有效 64 位。
+     */
+    public long getMostSignificantBits()
+    {
+        return mostSigBits;
+    }
+
+    /**
+     * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。
+     * <p>
+     * 版本号具有以下含意:
+     * <ul>
+     * <li>1 基于时间的 UUID
+     * <li>2 DCE 安全 UUID
+     * <li>3 基于名称的 UUID
+     * <li>4 随机生成的 UUID
+     * </ul>
+     *
+     * @return 此 {@code UUID} 的版本号
+     */
+    public int version()
+    {
+        // Version is bits masked by 0x000000000000F000 in MS long
+        return (int) ((mostSigBits >> 12) & 0x0f);
+    }
+
+    /**
+     * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。
+     * <p>
+     * 变体号具有以下含意:
+     * <ul>
+     * <li>0 为 NCS 向后兼容保留
+     * <li>2 <a href="http://www.ietf.org/rfc/rfc4122.txt">IETF&nbsp;RFC&nbsp;4122</a>(Leach-Salz), 用于此类
+     * <li>6 保留,微软向后兼容
+     * <li>7 保留供以后定义使用
+     * </ul>
+     *
+     * @return 此 {@code UUID} 相关联的变体号
+     */
+    public int variant()
+    {
+        // This field is composed of a varying number of bits.
+        // 0 - - Reserved for NCS backward compatibility
+        // 1 0 - The IETF aka Leach-Salz variant (used by this class)
+        // 1 1 0 Reserved, Microsoft backward compatibility
+        // 1 1 1 Reserved for future definition.
+        return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63));
+    }
+
+    /**
+     * 与此 UUID 相关联的时间戳值。
+     *
+     * <p>
+     * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。<br>
+     * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。
+     *
+     * <p>
+     * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
+     * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
+     *
+     * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。
+     */
+    public long timestamp() throws UnsupportedOperationException
+    {
+        checkTimeBase();
+        return (mostSigBits & 0x0FFFL) << 48//
+                | ((mostSigBits >> 16) & 0x0FFFFL) << 32//
+                | mostSigBits >>> 32;
+    }
+
+    /**
+     * 与此 UUID 相关联的时钟序列值。
+     *
+     * <p>
+     * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。
+     * <p>
+     * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出
+     * UnsupportedOperationException。
+     *
+     * @return 此 {@code UUID} 的时钟序列
+     *
+     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
+     */
+    public int clockSequence() throws UnsupportedOperationException
+    {
+        checkTimeBase();
+        return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48);
+    }
+
+    /**
+     * 与此 UUID 相关的节点值。
+     *
+     * <p>
+     * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。
+     * <p>
+     * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。<br>
+     * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。
+     *
+     * @return 此 {@code UUID} 的节点值
+     *
+     * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1
+     */
+    public long node() throws UnsupportedOperationException
+    {
+        checkTimeBase();
+        return leastSigBits & 0x0000FFFFFFFFFFFFL;
+    }
+
+    /**
+     * 返回此{@code UUID} 的字符串表现形式。
+     *
+     * <p>
+     * UUID 的字符串表示形式由此 BNF 描述:
+     *
+     * <pre>
+     * {@code
+     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
+     * time_low               = 4*<hexOctet>
+     * time_mid               = 2*<hexOctet>
+     * time_high_and_version  = 2*<hexOctet>
+     * variant_and_sequence   = 2*<hexOctet>
+     * node                   = 6*<hexOctet>
+     * hexOctet               = <hexDigit><hexDigit>
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * </pre>
+     *
+     * </blockquote>
+     *
+     * @return 此{@code UUID} 的字符串表现形式
+     * @see #toString(boolean)
+     */
+    @Override
+    public String toString()
+    {
+        return toString(false);
+    }
+
+    /**
+     * 返回此{@code UUID} 的字符串表现形式。
+     *
+     * <p>
+     * UUID 的字符串表示形式由此 BNF 描述:
+     *
+     * <pre>
+     * {@code
+     * UUID                   = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
+     * time_low               = 4*<hexOctet>
+     * time_mid               = 2*<hexOctet>
+     * time_high_and_version  = 2*<hexOctet>
+     * variant_and_sequence   = 2*<hexOctet>
+     * node                   = 6*<hexOctet>
+     * hexOctet               = <hexDigit><hexDigit>
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * </pre>
+     *
+     * </blockquote>
+     *
+     * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串
+     * @return 此{@code UUID} 的字符串表现形式
+     */
+    public String toString(boolean isSimple)
+    {
+        final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36);
+        // time_low
+        builder.append(digits(mostSigBits >> 32, 8));
+        if (!isSimple)
+        {
+            builder.append('-');
+        }
+        // time_mid
+        builder.append(digits(mostSigBits >> 16, 4));
+        if (!isSimple)
+        {
+            builder.append('-');
+        }
+        // time_high_and_version
+        builder.append(digits(mostSigBits, 4));
+        if (!isSimple)
+        {
+            builder.append('-');
+        }
+        // variant_and_sequence
+        builder.append(digits(leastSigBits >> 48, 4));
+        if (!isSimple)
+        {
+            builder.append('-');
+        }
+        // node
+        builder.append(digits(leastSigBits, 12));
+
+        return builder.toString();
+    }
+
+    /**
+     * 返回此 UUID 的哈希码。
+     *
+     * @return UUID 的哈希码值。
+     */
+    @Override
+    public int hashCode()
+    {
+        long hilo = mostSigBits ^ leastSigBits;
+        return ((int) (hilo >> 32)) ^ (int) hilo;
+    }
+
+    /**
+     * 将此对象与指定对象比较。
+     * <p>
+     * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。
+     *
+     * @param obj 要与之比较的对象
+     *
+     * @return 如果对象相同,则返回 {@code true};否则返回 {@code false}
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if ((null == obj) || (obj.getClass() != UUID.class))
+        {
+            return false;
+        }
+        UUID id = (UUID) obj;
+        return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits);
+    }
+
+    // Comparison Operations
+
+    /**
+     * 将此 UUID 与指定的 UUID 比较。
+     *
+     * <p>
+     * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。
+     *
+     * @param val 与此 UUID 比较的 UUID
+     *
+     * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。
+     *
+     */
+    @Override
+    public int compareTo(UUID val)
+    {
+        // The ordering is intentionally set up so that the UUIDs
+        // can simply be numerically compared as two numbers
+        return (this.mostSigBits < val.mostSigBits ? -1 : //
+                (this.mostSigBits > val.mostSigBits ? 1 : //
+                        (this.leastSigBits < val.leastSigBits ? -1 : //
+                                (this.leastSigBits > val.leastSigBits ? 1 : //
+                                        0))));
+    }
+
+    // -------------------------------------------------------------------------------------------------------------------
+    // Private method start
+    /**
+     * 返回指定数字对应的hex值
+     *
+     * @param val 值
+     * @param digits 位
+     * @return 值
+     */
+    private static String digits(long val, int digits)
+    {
+        long hi = 1L << (digits * 4);
+        return Long.toHexString(hi | (val & (hi - 1))).substring(1);
+    }
+
+    /**
+     * 检查是否为time-based版本UUID
+     */
+    private void checkTimeBase()
+    {
+        if (version() != 1)
+        {
+            throw new UnsupportedOperationException("Not a time-based UUID");
+        }
+    }
+
+    /**
+     * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG)
+     *
+     * @return {@link SecureRandom}
+     */
+    public static SecureRandom getSecureRandom()
+    {
+        try
+        {
+            return SecureRandom.getInstance("SHA1PRNG");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new UtilException(e);
+        }
+    }
+
+    /**
+     * 获取随机数生成器对象<br>
+     * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。
+     *
+     * @return {@link ThreadLocalRandom}
+     */
+    public static ThreadLocalRandom getRandom()
+    {
+        return ThreadLocalRandom.current();
+    }
+}