diff --git a/pom.xml b/pom.xml index cc2e992a8..7e101d340 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,9 @@ 2.7.0 1.2.5 3.9.1 + [7.2.0, 7.2.99] + 2.5.0 + 4.4 diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java new file mode 100644 index 000000000..8b0751afb --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysOssController.java @@ -0,0 +1,178 @@ +package com.ruoyi.web.controller.system; + +import com.alibaba.fastjson.JSON; +import com.google.gson.Gson; +import com.ruoyi.common.base.AjaxResult; +import com.ruoyi.framework.web.base.BaseController; +import com.ruoyi.framework.web.exception.user.OssException; +import com.ruoyi.framework.web.page.TableDataInfo; +import com.ruoyi.framework.web.util.ShiroUtils; +import com.ruoyi.framework.web.util.ValidatorUtils; +import com.ruoyi.system.domain.SysOss; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysOssService; +import com.ruoyi.web.controller.system.cloud.CloudConstant; +import com.ruoyi.web.controller.system.cloud.CloudConstant.CloudService; +import com.ruoyi.web.controller.system.cloud.CloudStorageConfig; +import com.ruoyi.web.controller.system.cloud.CloudStorageService; +import com.ruoyi.web.controller.system.cloud.OSSFactory; +import com.ruoyi.web.controller.system.cloud.valdator.AliyunGroup; +import com.ruoyi.web.controller.system.cloud.valdator.QcloudGroup; +import com.ruoyi.web.controller.system.cloud.valdator.QiniuGroup; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Date; +import java.util.List; + +/** + * 文件上传 + */ +@Controller +@RequestMapping("system/oss") +public class SysOssController extends BaseController +{ + private String prefix = "system/oss"; + + private final static String KEY = CloudConstant.CLOUD_STORAGE_CONFIG_KEY; + + @Autowired + private ISysOssService sysOssService; + + @Autowired + private ISysConfigService sysConfigService; + + @RequiresPermissions("system:dept:view") + @GetMapping() + public String dept() + { + return prefix + "/oss"; + } + + /** + * 列表 + */ + @RequestMapping("list") + @RequiresPermissions("sys:oss:list") + @ResponseBody + public TableDataInfo list(SysOss sysOss) + { + List list = sysOssService.getList(sysOss); + return getDataTable(list); + } + + /** + * 云存储配置信息 + */ + @RequestMapping("config") + @RequiresPermissions("sys:oss:config") + public String config(Model model) + { + String jsonconfig = sysConfigService.selectConfigByKey(CloudConstant.CLOUD_STORAGE_CONFIG_KEY); + // 获取云存储配置信息 + CloudStorageConfig config = JSON.parseObject(jsonconfig, CloudStorageConfig.class); + model.addAttribute("config", config); + return prefix + "/config"; + } + + /** + * 保存云存储配置信息 + */ + @RequestMapping("saveConfig") + @RequiresPermissions("sys:oss:config") + @ResponseBody + public AjaxResult saveConfig(CloudStorageConfig config) + { + // 校验类型 + ValidatorUtils.validateEntity(config); + if (config.getType() == CloudService.QINIU.getValue()) + { + // 校验七牛数据 + ValidatorUtils.validateEntity(config, QiniuGroup.class); + } + else if (config.getType() == CloudService.ALIYUN.getValue()) + { + // 校验阿里云数据 + ValidatorUtils.validateEntity(config, AliyunGroup.class); + } + else if (config.getType() == CloudService.QCLOUD.getValue()) + { + // 校验腾讯云数据 + ValidatorUtils.validateEntity(config, QcloudGroup.class); + } + return toAjax(sysConfigService.updateValueByKey(KEY, new Gson().toJson(config))); + } + + /** + * 上传文件 + */ + @RequestMapping("/upload") + @RequiresPermissions("sys:oss:add") + @ResponseBody + public AjaxResult upload(@RequestParam("file") MultipartFile file) throws Exception + { + if (file.isEmpty()) + { + throw new OssException("上传文件不能为空"); + } + // 上传文件 + String fileName = file.getOriginalFilename(); + String suffix = fileName.substring(fileName.lastIndexOf(".")); + CloudStorageService storage = OSSFactory.build(); + String url = storage.uploadSuffix(file.getBytes(), suffix); + // 保存文件信息 + SysOss ossEntity = new SysOss(); + ossEntity.setUrl(url); + ossEntity.setFileSuffix(suffix); + ossEntity.setCreateBy(ShiroUtils.getLoginName()); + ossEntity.setFileName(fileName); + ossEntity.setCreateTime(new Date()); + ossEntity.setService(storage.getService()); + return toAjax(sysOssService.save(ossEntity)).put("data", ossEntity.getUrl()); + } + + /** + * 修改 + */ + @GetMapping("edit/{ossId}") + @RequiresPermissions("sys:oss:edit") + public String edit(@PathVariable("ossId") Long ossId, Model model) + { + SysOss sysOss = sysOssService.findById(ossId); + model.addAttribute("sysOss", sysOss); + return prefix + "/edit"; + } + + @GetMapping("editor") + @RequiresPermissions("sys:oss:add") + public String editor() + { + return prefix + "/editor"; + } + + /** + * 修改 + */ + @PostMapping("edit") + @RequiresPermissions("sys:oss:edit") + @ResponseBody + public AjaxResult editSave(SysOss sysOss) + { + return toAjax(sysOssService.update(sysOss)); + } + + /** + * 删除 + */ + @RequestMapping("remove") + @RequiresPermissions("sys:oss:remove") + @ResponseBody + public AjaxResult delete(String ids) + { + return toAjax(sysOssService.deleteByIds(ids)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/AliyunCloudStorageService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/AliyunCloudStorageService.java new file mode 100644 index 000000000..dc4e0bbbc --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/AliyunCloudStorageService.java @@ -0,0 +1,60 @@ +package com.ruoyi.web.controller.system.cloud; + +import com.aliyun.oss.OSSClient; +import com.ruoyi.framework.web.exception.user.OssException; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * 阿里云存储 + */ +public class AliyunCloudStorageService extends CloudStorageService +{ + private OSSClient client; + + public AliyunCloudStorageService(CloudStorageConfig config) + { + this.config = config; + // 初始化 + init(); + } + + private void init() + { + client = new OSSClient(config.getAliyunEndPoint(), config.getAliyunAccessKeyId(), + config.getAliyunAccessKeySecret()); + } + + @Override + public String upload(byte[] data, String path) + { + return upload(new ByteArrayInputStream(data), path); + } + + @Override + public String upload(InputStream inputStream, String path) + { + try + { + client.putObject(config.getAliyunBucketName(), path, inputStream); + } + catch (Exception e) + { + throw new OssException("上传文件失败,请检查配置信息"); + } + return config.getAliyunDomain() + "/" + path; + } + + @Override + public String uploadSuffix(byte[] data, String suffix) + { + return upload(data, getPath(config.getAliyunPrefix(), suffix)); + } + + @Override + public String uploadSuffix(InputStream inputStream, String suffix) + { + return upload(inputStream, getPath(config.getAliyunPrefix(), suffix)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudConstant.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudConstant.java new file mode 100644 index 000000000..0a4b0601e --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudConstant.java @@ -0,0 +1,39 @@ +package com.ruoyi.web.controller.system.cloud; + +public class CloudConstant +{ + /** + * 云存储配置KEY + */ + public final static String CLOUD_STORAGE_CONFIG_KEY = "sys.oss.cloudStorage"; + + /** + * 云服务商 + */ + public enum CloudService + { + /** + * 七牛云 + */ + QINIU(1), + /** + * 阿里云 + */ + ALIYUN(2), + /** + * 腾讯云 + */ + QCLOUD(3); + private int value; + + CloudService(int value) + { + this.value = value; + } + + public int getValue() + { + return value; + } + } +} \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageConfig.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageConfig.java new file mode 100644 index 000000000..86aa7cde0 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageConfig.java @@ -0,0 +1,288 @@ +package com.ruoyi.web.controller.system.cloud; + +import com.ruoyi.web.controller.system.cloud.valdator.AliyunGroup; +import com.ruoyi.web.controller.system.cloud.valdator.QcloudGroup; +import com.ruoyi.web.controller.system.cloud.valdator.QiniuGroup; +import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.io.Serializable; + +/** + * 云存储配置信息 + */ +public class CloudStorageConfig implements Serializable +{ + // + private static final long serialVersionUID = 9035033846176792944L; + + // 类型 1:七牛 2:阿里云 3:腾讯云 + @Range(min = 1, max = 3, message = "类型错误") + private Integer type; + + // 七牛绑定的域名 + @NotBlank(message = "七牛绑定的域名不能为空", groups = QiniuGroup.class) + @URL(message = "七牛绑定的域名格式不正确", groups = QiniuGroup.class) + private String qiniuDomain; + + // 七牛路径前缀 + private String qiniuPrefix; + + // 七牛ACCESS_KEY + @NotBlank(message = "七牛AccessKey不能为空", groups = QiniuGroup.class) + private String qiniuAccessKey; + + // 七牛SECRET_KEY + @NotBlank(message = "七牛SecretKey不能为空", groups = QiniuGroup.class) + private String qiniuSecretKey; + + // 七牛存储空间名 + @NotBlank(message = "七牛空间名不能为空", groups = QiniuGroup.class) + private String qiniuBucketName; + + // 阿里云绑定的域名 + @NotBlank(message = "阿里云绑定的域名不能为空", groups = AliyunGroup.class) + @URL(message = "阿里云绑定的域名格式不正确", groups = AliyunGroup.class) + private String aliyunDomain; + + // 阿里云路径前缀 + @Pattern(regexp="^[^(/|\\)](.*[^(/|\\)])?$",message="阿里云路径前缀不能'/'或者'\'开头或者结尾",groups = AliyunGroup.class) + private String aliyunPrefix; + + // 阿里云EndPoint + @NotBlank(message = "阿里云EndPoint不能为空", groups = AliyunGroup.class) + private String aliyunEndPoint; + + // 阿里云AccessKeyId + @NotBlank(message = "阿里云AccessKeyId不能为空", groups = AliyunGroup.class) + private String aliyunAccessKeyId; + + // 阿里云AccessKeySecret + @NotBlank(message = "阿里云AccessKeySecret不能为空", groups = AliyunGroup.class) + private String aliyunAccessKeySecret; + + // 阿里云BucketName + @NotBlank(message = "阿里云BucketName不能为空", groups = AliyunGroup.class) + private String aliyunBucketName; + + // 腾讯云绑定的域名 + @NotBlank(message = "腾讯云绑定的域名不能为空", groups = QcloudGroup.class) + @URL(message = "腾讯云绑定的域名格式不正确", groups = QcloudGroup.class) + private String qcloudDomain; + + // 腾讯云路径前缀 + private String qcloudPrefix; + + // 腾讯云AppId + @NotNull(message = "腾讯云AppId不能为空", groups = QcloudGroup.class) + private Integer qcloudAppId; + + // 腾讯云SecretId + @NotBlank(message = "腾讯云SecretId不能为空", groups = QcloudGroup.class) + private String qcloudSecretId; + + // 腾讯云SecretKey + @NotBlank(message = "腾讯云SecretKey不能为空", groups = QcloudGroup.class) + private String qcloudSecretKey; + + // 腾讯云BucketName + @NotBlank(message = "腾讯云BucketName不能为空", groups = QcloudGroup.class) + private String qcloudBucketName; + + // 腾讯云COS所属地区 + @NotBlank(message = "所属地区不能为空", groups = QcloudGroup.class) + private String qcloudRegion; + + public Integer getType() + { + return type; + } + + public void setType(Integer type) + { + this.type = type; + } + + public String getQiniuDomain() + { + return qiniuDomain; + } + + public void setQiniuDomain(String qiniuDomain) + { + this.qiniuDomain = qiniuDomain; + } + + public String getQiniuAccessKey() + { + return qiniuAccessKey; + } + + public void setQiniuAccessKey(String qiniuAccessKey) + { + this.qiniuAccessKey = qiniuAccessKey; + } + + public String getQiniuSecretKey() + { + return qiniuSecretKey; + } + + public void setQiniuSecretKey(String qiniuSecretKey) + { + this.qiniuSecretKey = qiniuSecretKey; + } + + public String getQiniuBucketName() + { + return qiniuBucketName; + } + + public void setQiniuBucketName(String qiniuBucketName) + { + this.qiniuBucketName = qiniuBucketName; + } + + public String getQiniuPrefix() + { + return qiniuPrefix; + } + + public void setQiniuPrefix(String qiniuPrefix) + { + this.qiniuPrefix = qiniuPrefix; + } + + public String getAliyunDomain() + { + return aliyunDomain; + } + + public void setAliyunDomain(String aliyunDomain) + { + this.aliyunDomain = aliyunDomain; + } + + public String getAliyunPrefix() + { + return aliyunPrefix; + } + + public void setAliyunPrefix(String aliyunPrefix) + { + this.aliyunPrefix = aliyunPrefix; + } + + public String getAliyunEndPoint() + { + return aliyunEndPoint; + } + + public void setAliyunEndPoint(String aliyunEndPoint) + { + this.aliyunEndPoint = aliyunEndPoint; + } + + public String getAliyunAccessKeyId() + { + return aliyunAccessKeyId; + } + + public void setAliyunAccessKeyId(String aliyunAccessKeyId) + { + this.aliyunAccessKeyId = aliyunAccessKeyId; + } + + public String getAliyunAccessKeySecret() + { + return aliyunAccessKeySecret; + } + + public void setAliyunAccessKeySecret(String aliyunAccessKeySecret) + { + this.aliyunAccessKeySecret = aliyunAccessKeySecret; + } + + public String getAliyunBucketName() + { + return aliyunBucketName; + } + + public void setAliyunBucketName(String aliyunBucketName) + { + this.aliyunBucketName = aliyunBucketName; + } + + public String getQcloudDomain() + { + return qcloudDomain; + } + + public void setQcloudDomain(String qcloudDomain) + { + this.qcloudDomain = qcloudDomain; + } + + public String getQcloudPrefix() + { + return qcloudPrefix; + } + + public void setQcloudPrefix(String qcloudPrefix) + { + this.qcloudPrefix = qcloudPrefix; + } + + public Integer getQcloudAppId() + { + return qcloudAppId; + } + + public void setQcloudAppId(Integer qcloudAppId) + { + this.qcloudAppId = qcloudAppId; + } + + public String getQcloudSecretId() + { + return qcloudSecretId; + } + + public void setQcloudSecretId(String qcloudSecretId) + { + this.qcloudSecretId = qcloudSecretId; + } + + public String getQcloudSecretKey() + { + return qcloudSecretKey; + } + + public void setQcloudSecretKey(String qcloudSecretKey) + { + this.qcloudSecretKey = qcloudSecretKey; + } + + public String getQcloudBucketName() + { + return qcloudBucketName; + } + + public void setQcloudBucketName(String qcloudBucketName) + { + this.qcloudBucketName = qcloudBucketName; + } + + public String getQcloudRegion() + { + return qcloudRegion; + } + + public void setQcloudRegion(String qcloudRegion) + { + this.qcloudRegion = qcloudRegion; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageService.java new file mode 100644 index 000000000..c0af9eda0 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/CloudStorageService.java @@ -0,0 +1,72 @@ +package com.ruoyi.web.controller.system.cloud; + +import com.ruoyi.common.utils.DateUtils; +import org.apache.commons.lang.StringUtils; + +import java.io.InputStream; +import java.util.UUID; + +/** + * 云存储(支持七牛、阿里云、腾讯云、又拍云) + */ +public abstract class CloudStorageService +{ + /** 云存储配置信息 */ + CloudStorageConfig config; + + public int getService() + { + return config.getType(); + } + + /** + * 文件路径 + * @param prefix 前缀 + * @param suffix 后缀 + * @return 返回上传路径 + */ + public String getPath(String prefix, String suffix) + { + // 生成uuid + String uuid = UUID.randomUUID().toString().replaceAll("-", ""); + // 文件路径 + String path = DateUtils.dateTime() + "/" + uuid; + if (StringUtils.isNotBlank(prefix)) + { + path = prefix + "/" + path; + } + return path + suffix; + } + + /** + * 文件上传 + * @param data 文件字节数组 + * @param path 文件路径,包含文件名 + * @return 返回http地址 + */ + public abstract String upload(byte[] data, String path); + + /** + * 文件上传 + * @param data 文件字节数组 + * @param suffix 后缀 + * @return 返回http地址 + */ + public abstract String uploadSuffix(byte[] data, String suffix); + + /** + * 文件上传 + * @param inputStream 字节流 + * @param path 文件路径,包含文件名 + * @return 返回http地址 + */ + public abstract String upload(InputStream inputStream, String path); + + /** + * 文件上传 + * @param inputStream 字节流 + * @param suffix 后缀 + * @return 返回http地址 + */ + public abstract String uploadSuffix(InputStream inputStream, String suffix); +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/OSSFactory.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/OSSFactory.java new file mode 100644 index 000000000..1963d8d53 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/OSSFactory.java @@ -0,0 +1,38 @@ +package com.ruoyi.web.controller.system.cloud; + +import com.alibaba.fastjson.JSON; +import com.ruoyi.framework.web.util.SpringUtils; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.web.controller.system.cloud.CloudConstant.CloudService; + +/** + * 文件上传Factory + */ +public final class OSSFactory +{ + private static ISysConfigService sysConfigService; + static + { + OSSFactory.sysConfigService = (ISysConfigService) SpringUtils.getBean(ISysConfigService.class); + } + + public static CloudStorageService build() + { + String jsonconfig = sysConfigService.selectConfigByKey(CloudConstant.CLOUD_STORAGE_CONFIG_KEY); + // 获取云存储配置信息 + CloudStorageConfig config = JSON.parseObject(jsonconfig, CloudStorageConfig.class); + if (config.getType() == CloudService.QINIU.getValue()) + { + return new QiniuCloudStorageService(config); + } + else if (config.getType() == CloudService.ALIYUN.getValue()) + { + return new AliyunCloudStorageService(config); + } + else if (config.getType() == CloudService.QCLOUD.getValue()) + { + return new QcloudCloudStorageService(config); + } + return null; + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QcloudCloudStorageService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QcloudCloudStorageService.java new file mode 100644 index 000000000..3c1289906 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QcloudCloudStorageService.java @@ -0,0 +1,83 @@ +package com.ruoyi.web.controller.system.cloud; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.request.UploadFileRequest; +import com.qcloud.cos.sign.Credentials; +import com.ruoyi.framework.web.exception.user.OssException; +import net.sf.json.JSONObject; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 腾讯云存储 + */ +public class QcloudCloudStorageService extends CloudStorageService +{ + private COSClient client; + + public QcloudCloudStorageService(CloudStorageConfig config) + { + this.config = config; + // 初始化 + init(); + } + + private void init() + { + Credentials credentials = new Credentials(config.getQcloudAppId(), config.getQcloudSecretId(), + config.getQcloudSecretKey()); + // 初始化客户端配置 + ClientConfig clientConfig = new ClientConfig(); + // 设置bucket所在的区域,华南:gz 华北:tj 华东:sh + clientConfig.setRegion(config.getQcloudRegion()); + client = new COSClient(clientConfig, credentials); + } + + @Override + public String upload(byte[] data, String path) + { + // 腾讯云必需要以"/"开头 + if (!path.startsWith("/")) + { + path = "/" + path; + } + // 上传到腾讯云 + UploadFileRequest request = new UploadFileRequest(config.getQcloudBucketName(), path, data); + String response = client.uploadFile(request); + JSONObject jsonObject = JSONObject.fromObject(response); + if (jsonObject.getInt("code") != 0) + { + throw new OssException("文件上传失败," + jsonObject.getString("message")); + } + return config.getQcloudDomain() + path; + } + + @Override + public String upload(InputStream inputStream, String path) + { + try + { + byte[] data = IOUtils.toByteArray(inputStream); + return this.upload(data, path); + } + catch (IOException e) + { + throw new OssException("上传文件失败"); + } + } + + @Override + public String uploadSuffix(byte[] data, String suffix) + { + return upload(data, getPath(config.getQcloudPrefix(), suffix)); + } + + @Override + public String uploadSuffix(InputStream inputStream, String suffix) + { + return upload(inputStream, getPath(config.getQcloudPrefix(), suffix)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QiniuCloudStorageService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QiniuCloudStorageService.java new file mode 100644 index 000000000..03c54086d --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/QiniuCloudStorageService.java @@ -0,0 +1,80 @@ +package com.ruoyi.web.controller.system.cloud; + +import com.qiniu.common.Zone; +import com.qiniu.http.Response; +import com.qiniu.storage.Configuration; +import com.qiniu.storage.UploadManager; +import com.qiniu.util.Auth; +import com.ruoyi.framework.web.exception.user.OssException; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 七牛云存储 + */ +public class QiniuCloudStorageService extends CloudStorageService +{ + private UploadManager uploadManager; + + private String token; + + public QiniuCloudStorageService(CloudStorageConfig config) + { + this.config = config; + // 初始化 + init(); + } + + private void init() + { + uploadManager = new UploadManager(new Configuration(Zone.autoZone())); + token = Auth.create(config.getQiniuAccessKey(), config.getQiniuSecretKey()) + .uploadToken(config.getQiniuBucketName()); + } + + @Override + public String upload(byte[] data, String path) + { + try + { + Response res = uploadManager.put(data, path, token); + if (!res.isOK()) + { + throw new RuntimeException("上传七牛出错:" + res.toString()); + } + } + catch (Exception e) + { + throw new OssException("上传文件失败,请核对七牛配置信息"); + } + return config.getQiniuDomain() + "/" + path; + } + + @Override + public String upload(InputStream inputStream, String path) + { + try + { + byte[] data = IOUtils.toByteArray(inputStream); + return this.upload(data, path); + } + catch (IOException e) + { + throw new OssException("上传文件失败"); + } + } + + @Override + public String uploadSuffix(byte[] data, String suffix) + { + return upload(data, getPath(config.getQiniuPrefix(), suffix)); + } + + @Override + public String uploadSuffix(InputStream inputStream, String suffix) + { + return upload(inputStream, getPath(config.getQiniuPrefix(), suffix)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/AliyunGroup.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/AliyunGroup.java new file mode 100644 index 000000000..9c31f1b0a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/AliyunGroup.java @@ -0,0 +1,8 @@ +package com.ruoyi.web.controller.system.cloud.valdator; + +/** + * 阿里云 + */ +public interface AliyunGroup +{ +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QcloudGroup.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QcloudGroup.java new file mode 100644 index 000000000..536ca0ca8 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QcloudGroup.java @@ -0,0 +1,8 @@ +package com.ruoyi.web.controller.system.cloud.valdator; + +/** + * 腾讯云 + */ +public interface QcloudGroup +{ +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QiniuGroup.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QiniuGroup.java new file mode 100644 index 000000000..609b6f9d2 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/cloud/valdator/QiniuGroup.java @@ -0,0 +1,8 @@ +package com.ruoyi.web.controller.system.cloud.valdator; + +/** + * 七牛 + */ +public interface QiniuGroup +{ +} diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 41a6b8343..834a40e96 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -54,8 +54,7 @@ spring: jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss - serialization: - write-dates-as-timestamps: false + profiles: active: druid # 文件上传 @@ -129,7 +128,7 @@ gen: # 作者 author: zhujj # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool - packageName: com.ruoyi.vip + packageName: com.ruoyi.exam # 自动去除表前缀,默认是true autoRemovePre: false # 表前缀(类名不会包含表前缀) diff --git a/ruoyi-admin/src/main/resources/static/ajax/libs/ajaxfile/ajaxupload.js b/ruoyi-admin/src/main/resources/static/ajax/libs/ajaxfile/ajaxupload.js new file mode 100644 index 000000000..7e51768b8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/static/ajax/libs/ajaxfile/ajaxupload.js @@ -0,0 +1,673 @@ +/** + * AJAX Upload ( http://valums.com/ajax-upload/ ) + * Copyright (c) Andris Valums + * Licensed under the MIT license ( http://valums.com/mit-license/ ) + * Thanks to Gary Haran, David Mark, Corey Burns and others for contributions + */ +(function () { + /* global window */ + /* jslint browser: true, devel: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true */ + + /** + * Wrapper for FireBug's console.log + */ + function log(){ + if (typeof(console) != 'undefined' && typeof(console.log) == 'function'){ + Array.prototype.unshift.call(arguments, '[Ajax Upload]'); + console.log( Array.prototype.join.call(arguments, ' ')); + } + } + + /** + * Attaches event to a dom element. + * @param {Element} el + * @param type event name + * @param fn callback This refers to the passed element + */ + function addEvent(el, type, fn){ + if (el.addEventListener) { + el.addEventListener(type, fn, false); + } else if (el.attachEvent) { + el.attachEvent('on' + type, function(){ + fn.call(el); + }); + } else { + throw new Error('not supported or DOM not loaded'); + } + } + + /** + * Attaches resize event to a window, limiting + * number of event fired. Fires only when encounteres + * delay of 100 after series of events. + * + * Some browsers fire event multiple times when resizing + * http://www.quirksmode.org/dom/events/resize.html + * + * @param fn callback This refers to the passed element + */ + function addResizeEvent(fn){ + var timeout; + + addEvent(window, 'resize', function(){ + if (timeout){ + clearTimeout(timeout); + } + timeout = setTimeout(fn, 100); + }); + } + + // Needs more testing, will be rewriten for next version + // getOffset function copied from jQuery lib (http://jquery.com/) + if (document.documentElement.getBoundingClientRect){ + // Get Offset using getBoundingClientRect + // http://ejohn.org/blog/getboundingclientrect-is-awesome/ + var getOffset = function(el){ + var box = el.getBoundingClientRect(); + var doc = el.ownerDocument; + var body = doc.body; + var docElem = doc.documentElement; // for ie + var clientTop = docElem.clientTop || body.clientTop || 0; + var clientLeft = docElem.clientLeft || body.clientLeft || 0; + + // In Internet Explorer 7 getBoundingClientRect property is treated as physical, + // while others are logical. Make all logical, like in IE8. + var zoom = 1; + if (body.getBoundingClientRect) { + var bound = body.getBoundingClientRect(); + zoom = (bound.right - bound.left) / body.clientWidth; + } + + if (zoom > 1) { + clientTop = 0; + clientLeft = 0; + } + + var top = box.top / zoom + (window.pageYOffset || docElem && docElem.scrollTop / zoom || body.scrollTop / zoom) - clientTop, left = box.left / zoom + (window.pageXOffset || docElem && docElem.scrollLeft / zoom || body.scrollLeft / zoom) - clientLeft; + + return { + top: top, + left: left + }; + }; + } else { + // Get offset adding all offsets + var getOffset = function(el){ + var top = 0, left = 0; + do { + top += el.offsetTop || 0; + left += el.offsetLeft || 0; + el = el.offsetParent; + } while (el); + + return { + left: left, + top: top + }; + }; + } + + /** + * Returns left, top, right and bottom properties describing the border-box, + * in pixels, with the top-left relative to the body + * @param {Element} el + * @return {Object} Contains left, top, right,bottom + */ + function getBox(el){ + var left, right, top, bottom; + var offset = getOffset(el); + left = offset.left; + top = offset.top; + + right = left + el.offsetWidth; + bottom = top + el.offsetHeight; + + return { + left: left, + right: right, + top: top, + bottom: bottom + }; + } + + /** + * Helper that takes object literal + * and add all properties to element.style + * @param {Element} el + * @param {Object} styles + */ + function addStyles(el, styles){ + for (var name in styles) { + if (styles.hasOwnProperty(name)) { + el.style[name] = styles[name]; + } + } + } + + /** + * Function places an absolutely positioned + * element on top of the specified element + * copying position and dimentions. + * @param {Element} from + * @param {Element} to + */ + function copyLayout(from, to){ + var box = getBox(from); + + addStyles(to, { + position: 'absolute', + left : box.left + 'px', + top : box.top + 'px', + width : from.offsetWidth + 'px', + height : from.offsetHeight + 'px' + }); + } + + /** + * Creates and returns element from html chunk + * Uses innerHTML to create an element + */ + var toElement = (function(){ + var div = document.createElement('div'); + return function(html){ + div.innerHTML = html; + var el = div.firstChild; + return div.removeChild(el); + }; + })(); + + /** + * Function generates unique id + * @return unique id + */ + var getUID = (function(){ + var id = 0; + return function(){ + return 'ValumsAjaxUpload' + id++; + }; + })(); + + /** + * Get file name from path + * @param {String} file path to file + * @return filename + */ + function fileFromPath(file){ + return file.replace(/.*(\/|\\)/, ""); + } + + /** + * Get file extension lowercase + * @param {String} file name + * @return file extenstion + */ + function getExt(file){ + return (-1 !== file.indexOf('.')) ? file.replace(/.*[.]/, '') : ''; + } + + function hasClass(el, name){ + var re = new RegExp('\\b' + name + '\\b'); + return re.test(el.className); + } + function addClass(el, name){ + if ( ! hasClass(el, name)){ + el.className += ' ' + name; + } + } + function removeClass(el, name){ + var re = new RegExp('\\b' + name + '\\b'); + el.className = el.className.replace(re, ''); + } + + function removeNode(el){ + el.parentNode.removeChild(el); + } + + /** + * Easy styling and uploading + * @constructor + * @param button An element you want convert to + * upload button. Tested dimentions up to 500x500px + * @param {Object} options See defaults below. + */ + window.AjaxUpload = function(button, options){ + this._settings = { + // Location of the server-side upload script + action: 'upload.php', + // File upload name + name: 'userfile', + // Additional data to send + data: {}, + // Submit file as soon as it's selected + autoSubmit: true, + // The type of data that you're expecting back from the server. + // html and xml are detected automatically. + // Only useful when you are using json data as a response. + // Set to "json" in that case. + responseType: false, + // Class applied to button when mouse is hovered + hoverClass: 'hover', + // Class applied to button when AU is disabled + disabledClass: 'disabled', + // When user selects a file, useful with autoSubmit disabled + // You can return false to cancel upload + onChange: function(file, extension){ + }, + // Callback to fire before file is uploaded + // You can return false to cancel upload + onSubmit: function(file, extension){ + }, + // Fired when file upload is completed + // WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE! + onComplete: function(file, response){ + } + }; + + // Merge the users options with our defaults + for (var i in options) { + if (options.hasOwnProperty(i)){ + this._settings[i] = options[i]; + } + } + + // button isn't necessary a dom element + if (button.jquery){ + // jQuery object was passed + button = button[0]; + } else if (typeof button == "string") { + if (/^#.*/.test(button)){ + // If jQuery user passes #elementId don't break it + button = button.slice(1); + } + + button = document.getElementById(button); + } + + if ( ! button || button.nodeType !== 1){ + throw new Error("Please make sure that you're passing a valid element"); + } + + if ( button.nodeName.toUpperCase() == 'A'){ + // disable link + addEvent(button, 'click', function(e){ + if (e && e.preventDefault){ + e.preventDefault(); + } else if (window.event){ + window.event.returnValue = false; + } + }); + } + + // DOM element + this._button = button; + // DOM element + this._input = null; + // If disabled clicking on button won't do anything + this._disabled = false; + + // if the button was disabled before refresh if will remain + // disabled in FireFox, let's fix it + this.enable(); + + this._rerouteClicks(); + }; + + // assigning methods to our class + AjaxUpload.prototype = { + setData: function(data){ + this._settings.data = data; + }, + disable: function(){ + addClass(this._button, this._settings.disabledClass); + this._disabled = true; + + var nodeName = this._button.nodeName.toUpperCase(); + if (nodeName == 'INPUT' || nodeName == 'BUTTON'){ + this._button.setAttribute('disabled', 'disabled'); + } + + // hide input + if (this._input){ + // We use visibility instead of display to fix problem with Safari 4 + // The problem is that the value of input doesn't change if it + // has display none when user selects a file + this._input.parentNode.style.visibility = 'hidden'; + } + }, + enable: function(){ + removeClass(this._button, this._settings.disabledClass); + this._button.removeAttribute('disabled'); + this._disabled = false; + + }, + /** + * Creates invisible file input + * that will hover above the button + *
+ */ + _createInput: function(){ + var self = this; + + var input = document.createElement("input"); + input.setAttribute('type', 'file'); + input.setAttribute('name', this._settings.name); + + addStyles(input, { + 'position' : 'absolute', + // in Opera only 'browse' button + // is clickable and it is located at + // the right side of the input + 'right' : 0, + 'margin' : 0, + 'padding' : 0, + 'fontSize' : '480px', + 'cursor' : 'pointer' + }); + + var div = document.createElement("div"); + addStyles(div, { + 'display' : 'block', + 'position' : 'absolute', + 'overflow' : 'hidden', + 'margin' : 0, + 'padding' : 0, + 'opacity' : 0, + // Make sure browse button is in the right side + // in Internet Explorer + 'direction' : 'ltr', + //Max zIndex supported by Opera 9.0-9.2 + 'zIndex': 2147483583 + }); + + // Make sure that element opacity exists. + // Otherwise use IE filter + if ( div.style.opacity !== "0") { + if (typeof(div.filters) == 'undefined'){ + throw new Error('Opacity not supported by the browser'); + } + div.style.filter = "alpha(opacity=0)"; + } + + addEvent(input, 'change', function(){ + + if ( ! input || input.value === ''){ + return; + } + + // Get filename from input, required + // as some browsers have path instead of it + var file = fileFromPath(input.value); + + if (false === self._settings.onChange.call(self, file, getExt(file))){ + self._clearInput(); + return; + } + + // Submit form when value is changed + if (self._settings.autoSubmit) { + self.submit(); + } + }); + + addEvent(input, 'mouseover', function(){ + addClass(self._button, self._settings.hoverClass); + }); + + addEvent(input, 'mouseout', function(){ + removeClass(self._button, self._settings.hoverClass); + + // We use visibility instead of display to fix problem with Safari 4 + // The problem is that the value of input doesn't change if it + // has display none when user selects a file + input.parentNode.style.visibility = 'hidden'; + + }); + + div.appendChild(input); + document.body.appendChild(div); + + this._input = input; + }, + _clearInput : function(){ + if (!this._input){ + return; + } + + // this._input.value = ''; Doesn't work in IE6 + removeNode(this._input.parentNode); + this._input = null; + this._createInput(); + + removeClass(this._button, this._settings.hoverClass); + }, + /** + * Function makes sure that when user clicks upload button, + * the this._input is clicked instead + */ + _rerouteClicks: function(){ + var self = this; + + // IE will later display 'access denied' error + // if you use using self._input.click() + // other browsers just ignore click() + + addEvent(self._button, 'mouseover', function(){ + if (self._disabled){ + return; + } + + if ( ! self._input){ + self._createInput(); + } + + var div = self._input.parentNode; + copyLayout(self._button, div); + div.style.visibility = 'visible'; + + }); + + + // commented because we now hide input on mouseleave + /** + * When the window is resized the elements + * can be misaligned if button position depends + * on window size + */ + //addResizeEvent(function(){ + // if (self._input){ + // copyLayout(self._button, self._input.parentNode); + // } + //}); + + }, + /** + * Creates iframe with unique name + * @return {Element} iframe + */ + _createIframe: function(){ + // We can't use getTime, because it sometimes return + // same value in safari :( + var id = getUID(); + + // We can't use following code as the name attribute + // won't be properly registered in IE6, and new window + // on form submit will open + // var iframe = document.createElement('iframe'); + // iframe.setAttribute('name', id); + + var iframe = toElement('