ruoyi-OSS阿里云
1.先去阿里云开通OSS https://blog.csdn.net/m0_55155505/article/details/123688022
2.导入依赖,ruoyi默认配置
<!--阿里云sms短信服务--> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.4.6</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-dysmsapi</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.15.0</version> </dependency>
3.配置文件
aliyun: oss: file: private: keyid: keysecret: bucketname: endpoint: domain: ramkeyid: ramkeyid ramkeysecret: ramarn:ramarn open: keyid: keysecret: bucketname: endpoint: domain:
4.文件结构
5.cloud文件夹
AliyunCloudStorageService
package com.ruoyi.oss.cloud; import cn.hutool.json.JSONUtil; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.OSSObject; import com.ruoyi.oss.config.OssOpenProperies; import com.ruoyi.oss.config.OssPrivateProperies; import com.ruoyi.oss.vo.request.SysOssDownloadFileRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import javax.servlet.http.HttpServletResponse; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; /** * 阿里云存储 * * @author */ public class AliyunCloudStorageService extends CloudStorageService { private static final Logger logger = LoggerFactory.getLogger(AliyunCloudStorageService.class); private OSS client; /** * 文件是否公开参数 */ private Boolean open; public AliyunCloudStorageService(){ //初始化 init(true); } public AliyunCloudStorageService(Boolean isOpen){ //初始化 init(isOpen); } private void init(Boolean isOpen){ if (isOpen == null ? true : isOpen) { client = new OSSClientBuilder().build(OssOpenProperies.END_POINT, OssOpenProperies.ACCESS_KEY_ID, OssOpenProperies.ACCESS_KEY_SECRET); open = true; } else { client = new OSSClientBuilder().build(OssPrivateProperies.END_POINT, OssPrivateProperies.ACCESS_KEY_ID, OssPrivateProperies.ACCESS_KEY_SECRET); open = false; } } @Override public String upload(InputStream inputStream, String path) { try { if (open) { client.putObject(OssOpenProperies.BUCKET_NAME, path, inputStream); } else { client.putObject(OssPrivateProperies.BUCKET_NAME, path, inputStream); } //就是这个 => https://Domain/path if (open) { return OssOpenProperies.DOMAIN + "/" + path; } return OssPrivateProperies.DOMAIN + "/" + path; } catch (Exception e){ logger.error("上传文件失败,请检查配置信息", e); return null; } finally { if (client != null) { client.shutdown(); } } } @Override public void download(SysOssDownloadFileRequest req, HttpServletResponse response) { BufferedInputStream bis = null; OutputStream out = null; try { // ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。 OSSObject ossObject; if (open) { ossObject = client.getObject(OssOpenProperies.BUCKET_NAME, req.getPath()); } else { ossObject = client.getObject(OssPrivateProperies.BUCKET_NAME, req.getPath()); } out = response.getOutputStream(); response.setContentType("application/octet-stream"); response.setHeader(HttpHeaders.CONTENT_TYPE, "application/octet-stream"); String encode = URLEncoder.encode(req.getOldName(), "utf-8"); // 设置文件名 response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encode); // 读取文件内容。 bis = new BufferedInputStream(ossObject.getObjectContent()); byte[] buffer = new byte[1024]; int i = bis.read(buffer); while (i != -1) { out.write(buffer, 0, i); i = bis.read(buffer); } } catch (Exception e){ logger.error("下载文件失败,请检查配置信息" + JSONUtil.toJsonStr(req), e); throw new RuntimeException("下载文件失败,请检查配置信息"); } finally { if (client != null) { client.shutdown(); } try { if (bis != null) { bis.close(); } if (out != null) { out.close(); } } catch (IOException e) { logger.error("通道关闭异常", e); } } } }
CloudStorageService
package com.ruoyi.oss.cloud; import cn.hutool.core.date.DatePattern; import com.ruoyi.oss.vo.request.SysOssDownloadFileRequest; import org.apache.commons.lang3.StringUtils; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.util.Date; import java.util.UUID; /** * OSS */ public abstract class CloudStorageService { /** * 文件路径 * @param prefix 前缀 * @param suffix 后缀 * @return 返回上传路径 */ public String getPath(String prefix, String suffix) { //生成uuid String uuid = UUID.randomUUID().toString().replaceAll("-", ""); //文件路径 String path = DatePattern.PURE_DATE_FORMAT.format(new Date()) + "/" + uuid; if(StringUtils.isNotBlank(prefix)){ path = prefix + "/" + path; } return path + suffix; } /** * 文件上传 * @param inputStream 字节流 * @param path 文件路径,包含文件名 * @return 返回http地址 */ public abstract String upload(InputStream inputStream, String path); /** * 文件下载 * @param req 文件路径,包含文件名 * @return 返回http地址 */ public abstract void download(SysOssDownloadFileRequest req, HttpServletResponse response); }
OSSFactory
package com.ruoyi.oss.cloud; /** * 文件上传Factory */ public final class OSSFactory { public static CloudStorageService build(){ //后面连接其它云可以在这里写逻辑切换 return new AliyunCloudStorageService(); } public static CloudStorageService build(Boolean isOpen){ //后面连接其它云可以在这里写逻辑切换 return new AliyunCloudStorageService(isOpen); } }
OssSignHeader
package com.ruoyi.oss.cloud; import org.apache.commons.codec.binary.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; public class OssSignHeader { private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; private final static String ALGORITHM = "HmacSHA1"; public static String hmacSha1(String data, String key) { try { Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), ALGORITHM); mac.init(keySpec); byte[] rawHmac; rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); return new String(Base64.encodeBase64(rawHmac)); } catch (Exception e) { throw new RuntimeException(e); } } public static String buildSignData(String date, String VERB, String canonicalizedResource){ return VERB + "\n"+ "application/octet-stream"+ "\n" + date + "\n" + canonicalizedResource; } public static String getGMTDate(){ Calendar cd = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); return sdf.format(cd.getTime()); } }
6.config文件夹
OssOpenProperies
package com.ruoyi.oss.config; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class OssOpenProperies implements InitializingBean { //oss @Value("${aliyun.oss.file.open.keyid}") private String keyId; @Value("${aliyun.oss.file.open.keysecret}") private String keySecret; @Value("${aliyun.oss.file.open.bucketname}") private String bucketName; @Value("${aliyun.oss.file.open.endpoint}") private String endpoint; @Value("${aliyun.oss.file.open.domain}") private String domain; public static String ACCESS_KEY_ID; public static String ACCESS_KEY_SECRET; public static String BUCKET_NAME; public static String END_POINT; public static String DOMAIN; @Override public void afterPropertiesSet() throws Exception { ACCESS_KEY_ID = keyId; ACCESS_KEY_SECRET = keySecret; BUCKET_NAME = bucketName; END_POINT = endpoint; DOMAIN = domain; } }
OssPrivateProperies
package com.ruoyi.oss.config; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class OssPrivateProperies implements InitializingBean { //oss @Value("${aliyun.oss.file.private.keyid}") private String keyId; @Value("${aliyun.oss.file.private.keysecret}") private String keySecret; @Value("${aliyun.oss.file.private.bucketname}") private String bucketName; @Value("${aliyun.oss.file.private.endpoint}") private String endpoint; @Value("${aliyun.oss.file.private.domain}") private String domain; @Value("${aliyun.oss.file.private.ramkeyid}") private String ramKeyId; @Value("${aliyun.oss.file.private.ramkeysecret}") private String ramKeySecret; @Value("${aliyun.oss.file.private.ramarn}") private String ramArn; public static String ACCESS_KEY_ID; public static String ACCESS_KEY_SECRET; public static String BUCKET_NAME; public static String END_POINT; public static String DOMAIN; public static String RAM_KEY_ID; public static String RAM_KEY_SECRET; public static String RAM_ARN; @Override public void afterPropertiesSet() throws Exception { ACCESS_KEY_ID = keyId; ACCESS_KEY_SECRET = keySecret; BUCKET_NAME = bucketName; END_POINT = endpoint; DOMAIN = domain; RAM_KEY_ID = ramKeyId; RAM_KEY_SECRET = ramKeySecret; RAM_ARN = ramArn; } }
7.entity文件夹
package com.ruoyi.oss.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; /** * 文件上传表(SysFile) 实体类 **/ @Data @TableName("sys_file") public class SysFileModel { @TableId(type = IdType.AUTO) private Long id; /** 文件业务来源编码 */ private String bizKey; /** 原文件名 */ private String oldName; /** 新文件名(文件储存在服务器的名称,唯一) */ private String newName; /** 文件大小(单位:字节) */ private Long size; /** 文件储存在oss的路径 */ private String path; /** 文件类型 */ private String type; /** 访问oss的url */ private String url; /** 是否公开 10:是 公开的文件 -10:否 私有的文件 */ private Integer open; }
8.enums文件夹,根据业务需求
package com.ruoyi.oss.enums; import lombok.Getter; import lombok.Setter; /** * 文件业务来源枚举 */ public enum BizKeyTypeEnum { /** * 正常 */ ACCOUNT("account", "账号文件上传业务编码"), /** * 正常 */ APPLETS("applets", "小程序配置"); @Getter @Setter private String code; @Getter @Setter private String msg; BizKeyTypeEnum(String code, String msg) { this.code = code; this.msg = msg; } /** * 判断文件业务是否合法 * @param code * @return */ public static boolean getCodeExist(String code) { for (BizKeyTypeEnum value : values()) { if (value.getCode().equals(code)) { return true; } } return false; } }
package com.ruoyi.oss.enums; import lombok.Getter; import lombok.Setter; /** * 文件公开类型枚举 */ public enum FileOpenTypeEnum { /** * 公开 */ OPEN("open", 10), /** * 私有 */ PRIVATE("private", -10); @Getter @Setter private String code; @Getter @Setter private Integer num; FileOpenTypeEnum(String code, Integer num) { this.code = code; this.num = num; } public static boolean isOpen(Integer num) { return num.intValue() == OPEN.getNum().intValue(); } }
9.Impl
package com.ruoyi.oss.service.impl;/** * 文件上传表(SysFile)Service实现类 **/ @Service @Transactional(rollbackFor = Exception.class) public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFileModel> implements SysFileService { private static final Logger logger = LoggerFactory.getLogger(SysFileServiceImpl.class); @Override public Result ossUpload(MultipartFile file, String bizKey, Integer open) { try { //获取文件名 String originalFilename = file.getOriginalFilename(); if (StrUtil.isBlank(originalFilename)) { return Result.failed("文件名为空!"); } String uuidFileName = FileNameUtil.getUUIDFileName(); String fileType = FileNameUtil.getFileType(originalFilename); String newFileName = uuidFileName + fileType; // 上传文件流 InputStream inputStream = file.getInputStream(); Calendar c = Calendar.getInstance(); int year = c.get(Calendar.YEAR); int month = c.get(Calendar.MONTH) + 1; int date = c.get(Calendar.DATE); //根据时间拼接path String path = year+"/"+month+"/"+date+"/"+newFileName; //上传 String domain = OSSFactory.build(FileOpenTypeEnum.isOpen(open)).upload(inputStream, path); if (StrUtil.isBlank(domain)) { return Result.failed("文件上传失败!"); } //文件上传成功后保存到数据库表 long size = file.getSize(); SysFileModel sysFileModel = new SysFileModel(); sysFileModel.setBizKey(bizKey); sysFileModel.setNewName(newFileName); sysFileModel.setOldName(originalFilename); sysFileModel.setUrl(domain); sysFileModel.setPath(path); sysFileModel.setSize(size); sysFileModel.setType(fileType); sysFileModel.setOpen(open); save(sysFileModel); //返回文件上传响应体 SysFileResponse response = new SysFileResponse(); response.setNewName(sysFileModel.getNewName()); response.setOldName(sysFileModel.getOldName()); response.setPath(sysFileModel.getPath()); response.setUrl(domain); response.setOpen(open); response.setSize(size); return Result.success(response); } catch (Exception e) { logger.error("文件上传oss异常!", e); return Result.failed("文件上传失败!"); } } /** * 获取授权stsUrl * @param req * @return */ @Override public Result getSTSUrl(SysStsUrlRequest req){ //有效时间1小时 Long overtime = 3600L; //当前时间 Long time = System.currentTimeMillis() / 1000; //生成指定参数的签名URL //生成OSSClient OSS ossClient = new OSSClientBuilder().build(OssPrivateProperies.END_POINT, OssPrivateProperies.ACCESS_KEY_ID, OssPrivateProperies.ACCESS_KEY_SECRET); // 创建请求。 GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(OssPrivateProperies.BUCKET_NAME, req.getPath()); generatePresignedUrlRequest.setMethod(HttpMethod.GET); //设置响应头强制下载 ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides(); responseHeaders.setContentDisposition("attachment;"); generatePresignedUrlRequest.setResponseHeaders(responseHeaders); // 设置URL过期时间为1小时。 Date expiration = new Date(System.currentTimeMillis() + overtime * time ); generatePresignedUrlRequest.setExpiration(expiration); // 生成签名URL。 URL url = ossClient.generatePresignedUrl(generatePresignedUrlRequest); // 获取STS // String ramSecurityToken = getRamSecurityToken(); // String stsUrl = url.toString() + "&Signature=" + ramSecurityToken; return Result.success(url); } @Override public void ossDownload(SysOssDownloadFileRequest req, HttpServletResponse response) { OSSFactory.build(FileOpenTypeEnum.isOpen(req.getOpen())).download(req, response); }
//获取临时访问url @Override public Result getAutoUrl(List<String> reqList) { try { // Endpoint以杭州为例,其它Region请按实际情况填写。 String endpoint = OssPrivateProperies.END_POINT; String accessKeyId = OssPrivateProperies.ACCESS_KEY_ID; String accessKeySecret = OssPrivateProperies.ACCESS_KEY_SECRET; String bucketName = OssPrivateProperies.BUCKET_NAME; String domain = OssPrivateProperies.DOMAIN + "/"; List<URL> urlList = new ArrayList<>(); // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); // 设置URL过期时间为1小时。 Date expiration = new Date(new Date().getTime() + 3600 * 1000); for(String str : reqList) { if (StrUtil.isNotEmpty(str)) { if (str.contains(domain)) { String objectName = str.replace(domain, ""); // 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。 URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration); urlList.add(url); continue; } } return Result.failed("url错误"); } // 关闭OSSClient。 ossClient.shutdown(); return Result.success(urlList); } catch (Exception e) { logger.error("获取临时地址失败!", e); return Result.failed(); } } /** * 获取RAM权限角色的STS * @return */ private String getRamSecurityToken(){ // STS接入地址,例如sts.cn-hangzhou.aliyuncs.com。 String endpoint = OssPrivateProperies.END_POINT; String accessKeyId = OssPrivateProperies.RAM_KEY_ID; String accessKeySecret = OssPrivateProperies.RAM_KEY_SECRET; // 获取的角色ARN。 String roleArn = OssPrivateProperies.RAM_ARN; // 自定义角色会话名称,用来区分不同的令牌,例如可填写为SessionTest。 String roleSessionName = Convert.toStr(SecurityUtils.getLoginUser().getUserId()); // 临时访问凭证最后获得的权限是RAM角色权限和该Policy设置权限的交集,即仅允许将文件上传至目标存储空间examplebucket下的exampledir目录。 //如果确定该policy不变,可以抽离出来 String policy = new StringBuffer() .append("{") .append(" \"Version\": \"1\", ") .append(" \"Statement\": [") .append(" {") .append(" \"Action\": [") .append(" \"oss:GetObject\"") .append(" ], ") //TODO 特别注意这里的ARN:bucket格式 是否有“:”号? .append(" \"Resource\": [").append("\"").append(OssPrivateProperies.RAM_ARN).append(":").append(OssPrivateProperies.BUCKET_NAME).append("/*\" ").append("], ") .append(" \"Effect\": \"Allow\"") .append(" }") .append(" ]") .append("}") .toString(); try { // regionId表示RAM的地域ID。以华东1(杭州)地域为例,regionID填写为cn-hangzhou。也可以保留默认值,默认值为空字符串("")。 String regionId = ""; DefaultProfile.addEndpoint("", regionId, "Sts", endpoint); // 构造default profile。 IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret); // 构造client。 DefaultAcsClient client = new DefaultAcsClient(profile); final AssumeRoleRequest request = new AssumeRoleRequest(); request.setMethod(MethodType.POST); request.setRoleArn(roleArn); request.setRoleSessionName(roleSessionName); // 如果policy为空,则用户将获得该角色下所有权限。 request.setPolicy(policy); // 设置临时访问凭证的有效时间为3600秒。 request.setDurationSeconds(3600L); final AssumeRoleResponse response = client.getAcsResponse(request); return response.getCredentials().getSecurityToken(); } catch (ClientException e) { logger.error("获取临时访问权限异常!Error code:" + e.getErrCode() + ",Error message: " + e.getErrMsg() + ",RequestId: " + e.getRequestId(), e); return ""; } } }
10.utils
package com.ruoyi.oss.utils; import java.util.UUID; public class FileNameUtil { //根据UUID生成文件名 public static String getUUIDFileName() { UUID uuid = UUID.randomUUID(); return uuid.toString().replace("-", ""); } //根据给定的文件名和后缀截取文件名 public static String getFileType(String fileName){ //9527s.jpg int index = fileName.lastIndexOf("."); return fileName.substring(index); } }
11.vo
@Data @ToString @AllArgsConstructor @NoArgsConstructor public class SysOssDownloadFileRequest { //文件下载请求数据实体 @ApiModelProperty(value = "文件在服务器的名称") @NotBlank(message = "文件在服务器的名称不能为空!") private String newName; @ApiModelProperty(value = "原文件名称") @NotBlank(message = "原文件名称不能为空!") private String oldName; @ApiModelProperty(value = "文件在服务器的路径") @NotBlank(message = "文件在服务器的路径不能为空!") private String path; @ApiModelProperty(value = "文件是否公开") @NotNull(message = "文件是否公开不能为空!") private Integer open; }
/** * 用于获取stsUrl的请求体 */ @Data @ToString @AllArgsConstructor @NoArgsConstructor public class SysStsUrlRequest { @ApiModelProperty(value = "url") private String url; @ApiModelProperty(value = "newName") private String newName; @ApiModelProperty(value = "path") private String path; }
/** * 文件上传返回数据实体 * */ @Data @ToString @AllArgsConstructor @NoArgsConstructor public class SysFileResponse { @ApiModelProperty(value = "原文件名称", required = true) private String oldName; @ApiModelProperty(value = "文件在服务器的名称", required = true) private String newName; @ApiModelProperty(value = "文件在服务器的路径", required = true) private String path; @ApiModelProperty(value = "文件在访问路径", required = true) private String url; @ApiModelProperty(value = "文件是否公开 10:是 公开的文件 -10:否 私有的文件", required = true) private Integer open; @ApiModelProperty(value = "文件大小", required = true) private Long size; }
12.Controller
@ApiOperation(value = "单个文件上传接口", notes = "单个文件上传接口") @ApiImplicitParams({ @ApiImplicitParam(name = "bizKey", value = "业务编码",required = true, paramType = "path", dataType = "String"), @ApiImplicitParam(name = "open", value = "是否公开 10:公开 -10:私有",required = true, paramType = "path", dataType = "Integer") }) @PostMapping("/ossUpload/{bizKey}/{open}") public Result ossUpload(@RequestPart("file") MultipartFile file, @PathVariable("bizKey") String bizKey, @PathVariable("open") Integer open, HttpServletRequest request) { if (Md5Utils.signatureCheck2(request)) { if(file.isEmpty()) { return Result.failed("文件不存在!"); } if (StrUtil.isBlank(bizKey)) { return Result.failed("文件业务编码不能为空!"); } //返回文件上传路径 return sysFileService.ossUpload(file, bizKey, open); } return Result.failed("非法请求"); }
@ApiOperation(value = "oss单个文件下载接口", notes = "oss单个文件下载接口") @PostMapping("/ossDownload") public void ossDownload(@RequestBody SysOssDownloadFileRequest req, HttpServletResponse response) { sysFileService.ossDownload(req, response); }
/** * 获取授权stsUrl * **/ @ApiOperation(value = "获取授权stsUrl", notes = "获取授权stsUrl") @PostMapping("/getSTSUrl") public Result getSTSUrl(@RequestBody SysStsUrlRequest req) { //返回文件stsUrl return sysFileService.getSTSUrl(req); } /** * 获取签名临时访问url */ @ApiOperation(value = "获取签名临时访问url", notes = "获取签名临时访问url") @PostMapping("/getAutoUrl") public Result getAutoUrl(@RequestBody List<String> reqList){ //返回文件签名Url return sysFileService.getAutoUrl(reqList); }