(1) 相關博文地址:
SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(一):搭建基本環境:https://www.cnblogs.com/l-y-h/p/12930895.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(二):引入 element-ui 定義基本頁面顯示:https://www.cnblogs.com/l-y-h/p/12935300.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(三):引入 js-cookie、axios、mock 封裝請求處理以及回傳結果:https://www.cnblogs.com/l-y-h/p/12955001.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(四):引入 vuex 進行狀態管理、引入 vue-i18n 進行國際化管理:https://www.cnblogs.com/l-y-h/p/12963576.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(五):引入 vue-router 進行路由管理、模塊化封裝 axios 請求、使用 iframe 標簽嵌套頁面:https://www.cnblogs.com/l-y-h/p/12973364.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(六):使用 vue-router 進行動態加載選單:https://www.cnblogs.com/l-y-h/p/13052196.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 后端篇(一): 搭建基本環境、整合 Swagger、MyBatisPlus、JSR303 以及國際化操作:https://www.cnblogs.com/l-y-h/p/13083375.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 后端篇(二): 整合 Redis(常用工具類、快取)、整合郵件發送功能:https://www.cnblogs.com/l-y-h/p/13163653.html
(2)代碼地址:
https://github.com/lyh-man/admin-vue-template.git
一、使用阿里云 OSS 服務
1、簡介
OSS 為 Object Storage Service,即物件存盤服務,是阿里云提供的海量、安全、低成本、高可靠的云存盤服務,
【官方使用檔案:】 https://help.aliyun.com/document_detail/31817.html 【快速上手 OSS 參考:】 https://www.cnblogs.com/l-y-h/p/12805028.html
2、使用 -- 開通 OSS 服務、創建 AccessKey
(1)登錄網站、開通 OSS 服務
通 OSS 服務,用于存盤檔案,
【官網地址:】 https://www.aliyun.com/

(2)創建 bucket,用于保存檔案,
Step1:
進入 OSS 控制臺,點擊創建 bucket,用于創建檔案保存空間,

Step2:
填寫 bucket 相關資訊,(視財力選擇功能)
注:
讀寫權限可以根據專案需要,酌情選擇,
私有指的是 讀寫操作 均需要 進行身份的驗證(此專案中使用),
公共讀指的是 寫操作需要進行身份驗證,讀操作不需要(即通過 url 可以直接訪問),

Step3:
配置跨域訪問(放行 post、get 等請求),


(3)創建 AccessKey,用于獲取操作 OSS 的權限,
Step1:
點擊 Accesskey,會彈出一個頁面,點擊 開始使用子用戶 AccessKey,


Step2:
創建用戶(admin-vue-template),并選擇編程訪問,

Step3:
保存 AccessKey 相關資訊,后續會使用,
建議保存在自己知道的地方,頁面關閉后無法再次獲取,只有重新創建了(=_=),
【用戶登錄名稱】 admin-vue-template@1675783906103019.onaliyun.com 【AccessKey ID】 LTAI4GEWZbLZocBzXKYEfmmq 【SECRET】 rZLsruKxWex2qGYVA3UsuBgW5B3uJQ

Step4:
給創建的用戶添加權限(OSS 權限),


3、使用 -- 服務端上傳代碼
(1)創建一張表 back_oss,用于存盤 檔案 url 地址,
USE admin_template; -- 檔案上傳 CREATE TABLE back_oss ( id bigint NOT NULL COMMENT '檔案 ID', file_url varchar(500) COMMENT 'URL 地址', oss_name varchar(200) COMMENT '存盤在 OSS 中的檔案名', file_name varchar(100) COMMENT '檔案名', create_time datetime COMMENT '創建時間', PRIMARY KEY (id), UNIQUE INDEX (oss_name) ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='檔案上傳';

(2)使用 mybatis-plus 代碼生成器為該表生成基本代碼,
此處,我將代碼生成在 modules/oss 中,也可生成在原來的路徑中,

當然,對于 創建時間 這個欄位,可以使用 mybatis-plus 的 @TableField 注解對其進行填充,
之前有過介紹,此處不再重復介紹
可參考:https://www.cnblogs.com/l-y-h/p/13083375.html#_label2_1,
package com.lyh.admin_template.back.modules.oss.entity; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import java.io.Serializable; import java.util.Date; /** * <p> * 檔案上傳 * </p> * * @author lyh * @since 2020-06-19 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value="BackOss物件", description="檔案上傳") public class BackOss implements Serializable { private static final long serialVersionUID=1L; @ApiModelProperty(value = "檔案 ID") @TableId(value = "id", type = IdType.ASSIGN_ID) private Long id; @ApiModelProperty(value = "URL 地址") private String fileUrl; @ApiModelProperty(value = "存盤在 OSS 中的檔案名") private String ossName; @ApiModelProperty(value = "檔案名") private String fileName; @TableField(fill = FieldFill.INSERT) @ApiModelProperty(value = "創建時間") private Date createTime; }

注:
由于 mapper 生成的位置與之前代碼不一致,需要在組態檔中,對其進行掃描,
@MapperScan(basePackages = {"com.lyh.admin_template.back.mapper", "com.lyh.admin_template.back.modules.oss.mapper"})

(3)由于涉及到 阿里云 的相關配置資訊,就要考慮到配置資訊修改問題,
處理一:可以使用 組態檔 存盤,通過修改組態檔來修改 OSS 相關資訊,
處理二:可以使用資料庫存盤配置資訊(Json 形式),通過修改資料庫資料的方式對其進行修改,
資料表設計如下:
USE admin_template; -- 系統配置資訊 CREATE TABLE back_config ( id bigint NOT NULL COMMIT '配置資訊 ID', param_key varchar(50) COMMENT 'key', param_value varchar(2000) COMMENT 'value', status tinyint DEFAULT 1 COMMENT '狀態 0:隱藏 1:顯示', remark varchar(500) COMMENT '備注', PRIMARY KEY (id), UNIQUE INDEX (param_key) ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COMMENT='系統配置資訊表';
此處僅使用 處理一,在組態檔中填寫相關的配置資訊,
# 阿里云配置資訊 aliyun: # common 配置資訊 accessKeyId: LTAI4GEWZbLZocBzXKYEfmmq accessKeySecret: rZLsruKxWex2qGYVA3UsuBgW5B3uJQ # OSS 相關配置資訊 endPoint: http://oss-cn-beijing.aliyuncs.com bucketName: admin-vue-template domain: http://admin-vue-template.oss-cn-beijing.aliyuncs.com

(4)添加 OSS 依賴
<!-- aliyun oss -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.8.0</version>
</dependency>

(5)撰寫一個 OSS 工具類 (OssUtil.java),通過其來操作 檔案上傳,
通過 @Value 來獲取組態檔(application.yml)中的值,
注:
若使用 @Value 獲取到的值為 null,需在類上 標注 @Component 注解,
package com.lyh.admin_template.back.common.utils; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URL; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.UUID; /** * Oss 工具類,用于操作 OSS */ @Data @Component public class OssUtil { @Value("${aliyun.endPoint}") private String endPoint; @Value("${aliyun.bucketName}") private String bucketName; @Value("${aliyun.accessKeyId}") private String accessKeyId; @Value("${aliyun.accessKeySecret}") private String accessKeySecret; @Value("${aliyun.domain}") private String domain; /** * 設定檔案上傳路徑(prefix + 日期 + uuid + suffix) */ public String getPath(String prefix, String suffix) { // 生成 UUID String uuid = UUID.randomUUID().toString().replaceAll("-", ""); // 格式化日期 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd"); // 拼接檔案路徑 String path = dateTimeFormatter.format(LocalDateTime.now()) + "/" + uuid; if (StringUtils.isNotEmpty(prefix)) { path = prefix + "/" + path; } return path + "-" + suffix; } /** * 上傳檔案 */ public String upload(InputStream inputStream, String path) { try { // 創建 OSSClient 實體, OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); // 上傳檔案到 指定 bucket ossClient.putObject(bucketName, path, inputStream); // 關閉 OSSClient ossClient.shutdown(); } catch (Exception e) { throw new RuntimeException("上傳檔案失敗"); } return path; } /** * 上傳檔案 */ public String upload(byte[] data, String path) { return upload(new ByteArrayInputStream(data), path); } /** * 上傳檔案,自定義 前后綴 */ public String uploadSuffix(byte[] data, String prefix ,String suffix) { return upload(data, getPath(prefix, suffix)); } /** * 上傳檔案,自定義 前后綴 */ public String uploadSuffix(InputStream inputStream, String prefix, String suffix) { return upload(inputStream, getPath(prefix, suffix)); } /** * 獲取檔案 url */ public String getUrl(String key) { // 用于保存 url 地址 URL url = null; try { // 創建 OSSClient 實體, OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); // 設定 url 過期時間(10 年) Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 24 * 365 * 10); // 獲取 url 地址 url = ossClient.generatePresignedUrl(bucketName, key, expiration); // 關閉 OSSClient ossClient.shutdown(); } catch (Exception e) { throw new RuntimeException("獲取檔案 url 失敗"); } return url != null ? url.toString() : null; } }

(6)撰寫 測驗代碼簡單測驗一下,
使用 Swagger 簡單測驗一下(此處只上傳單檔案,可以使用 Swagger 進行測驗,多檔案可以使用 Postman 進行測驗),
package com.lyh.admin_template.back.modules.oss.controller; import com.lyh.admin_template.back.common.exception.GlobalException; import com.lyh.admin_template.back.common.utils.OssUtil; import com.lyh.admin_template.back.common.utils.Result; import com.lyh.admin_template.back.modules.oss.entity.BackOss; import com.lyh.admin_template.back.modules.oss.service.BackOssService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.beans.factory.annotation.Autowired; 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.io.IOException; /** * <p> * 檔案上傳 前端控制器 * </p> * * @author lyh * @since 2020-06-19 */ @RestController @RequestMapping("/oss/back-oss") @Api(tags = "檔案上傳") public class BackOssController { @Autowired private OssUtil ossUtil; @Autowired private BackOssService backOssService; @ApiOperation(value = "上傳檔案") @PostMapping("/upload") public Result upload(@ApiParam MultipartFile file) { // 用于保存檔案 url String url = null; // 用于保存檔案資訊 BackOss backOss = new BackOss(); try { // 獲取檔案上傳路徑 url = ossUtil.uploadSuffix(file.getInputStream(), "aliyun", file.getOriginalFilename()); // 保存檔案路徑到資料庫中 backOss.setFileName(file.getOriginalFilename()); backOss.setOssName(url); backOss.setFileUrl(ossUtil.getUrl(url)); backOssService.save(backOss); } catch (IOException e) { throw new GlobalException("檔案上傳失敗"); } return Result.ok().message("檔案上傳成功").data("file", backOss); } @ApiOperation(value = "獲取所有檔案資訊") @GetMapping("/getAll") public Result getAll() { return Result.ok().data("file", backOssService.list()); } }

測驗結果如下:

4、使用 -- 服務端簽名后直傳(vue + element-ui 方式傳送檔案)
(1)簡介:
前面的一種檔案傳輸方式是將 檔案 從前臺傳輸到 后臺,再由后臺向 OSS 服務器傳輸,增加了后臺服務器的壓力(只適用于傳輸小檔案、圖片等),
采用服務端簽名后直傳的方式,是由 前臺呼叫后臺介面,回傳一個簽名資料,前臺根據這個簽名資料直接向 OSS 服務器發送檔案(適合傳輸大檔案),
詳情參考:
https://www.cnblogs.com/l-y-h/p/12805028.html#_label2_3
(2)介面代碼
可以在 工具類 OssUtil.java 中把相關邏輯封裝一下,
邏輯參考下面代碼中 getPolicy() 方法,
package com.lyh.admin_template.back.common.utils; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.common.utils.BinaryUtil; import com.aliyun.oss.model.MatchMode; import com.aliyun.oss.model.PolicyConditions; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URL; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * Oss 工具類,用于操作 OSS */ @Data @Component public class OssUtil { @Value("${aliyun.endPoint}") private String endPoint; @Value("${aliyun.bucketName}") private String bucketName; @Value("${aliyun.accessKeyId}") private String accessKeyId; @Value("${aliyun.accessKeySecret}") private String accessKeySecret; @Value("${aliyun.domain}") private String domain; /** * 設定檔案上傳路徑(prefix + 日期 + uuid + suffix) */ public String getPath(String prefix, String suffix) { // 生成 UUID String uuid = UUID.randomUUID().toString().replaceAll("-", ""); // 格式化日期 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd"); // 拼接檔案路徑 String path = dateTimeFormatter.format(LocalDateTime.now()) + "/" + uuid; if (StringUtils.isNotEmpty(prefix)) { path = prefix + "/" + path; } return path + "-" + suffix; } /** * 上傳檔案 */ public String upload(byte[] data, String path) { return upload(new ByteArrayInputStream(data), path); } /** * 上傳檔案,自定義 前后綴 */ public String uploadSuffix(byte[] data, String prefix ,String suffix) { return upload(data, getPath(prefix, suffix)); } /** * 上傳檔案,自定義 前后綴 */ public String uploadSuffix(InputStream inputStream, String prefix, String suffix) { return upload(inputStream, getPath(prefix, suffix)); } /** * 上傳檔案 */ public String upload(InputStream inputStream, String path) { try { // 創建 OSSClient 實體, OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); // 上傳檔案到 指定 bucket ossClient.putObject(bucketName, path, inputStream); // 關閉 OSSClient ossClient.shutdown(); } catch (Exception e) { throw new RuntimeException("上傳檔案失敗"); } return path; } /** * 獲取檔案 url */ public String getUrl(String key) { // 用于保存 url 地址 URL url = null; try { // 創建 OSSClient 實體, OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); // 設定 url 過期時間(10 年) Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 24 * 365 * 10); // 獲取 url 地址 url = ossClient.generatePresignedUrl(bucketName, key, expiration); // 關閉 OSSClient ossClient.shutdown(); } catch (Exception e) { throw new RuntimeException("獲取檔案 url 失敗"); } return url != null ? url.toString() : null; } /** * 用于獲取簽名資料 */ public Map<String, String> getPolicy() { return getPolicy(getPath("aliyun", "signature")); } /** * 用于獲取簽名資料,用于服務端直傳檔案到服務器 */ public Map<String, String> getPolicy(String path) { // 用于保存 Map<String, String> map = new HashMap<>(); try { // 創建 OSSClient 實體, OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); // 用于設定 post 上傳條件 PolicyConditions policyConditions = new PolicyConditions(); // 設定最大上傳檔案大小(1G) policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000); // 設定檔案前綴 policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, path); // 設定簽名過期時間(6 小時) Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 6); // 生成 policy String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions); // 設定編碼字符集(UTF-8) byte[] binaryData =https://www.cnblogs.com/l-y-h/p/ postPolicy.getBytes(StandardCharsets.UTF_8); // 設定加密格式(Base64) String encodedPolicy = BinaryUtil.toBase64String(binaryData); // 計算簽名 String postSignature = ossClient.calculatePostSignature(postPolicy); // 封裝資料 map.put("ossaccessKeyId", accessKeyId); map.put("policy", encodedPolicy); map.put("signature", postSignature); map.put("key", path); map.put("expire", String.valueOf(expiration.getTime() / 1000)); map.put("host", domain); // 關閉 OSSClient ossClient.shutdown(); } catch (Exception e) { throw new RuntimeException("獲取簽名資料失敗"); } return map; } }
(3)撰寫測驗介面用于測驗,
package com.lyh.admin_template.back.modules.oss.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.lyh.admin_template.back.common.utils.OssUtil; import com.lyh.admin_template.back.common.utils.Result; import com.lyh.admin_template.back.modules.oss.entity.BackOss; import com.lyh.admin_template.back.modules.oss.service.BackOssService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * <p> * 檔案上傳 前端控制器 * </p> * * @author lyh * @since 2020-06-19 */ @RestController @RequestMapping("/oss/back-oss") @Api(tags = "檔案上傳") public class BackOssController { @Autowired private OssUtil ossUtil; @Autowired private BackOssService backOssService; @ApiOperation(value = "獲取簽名資料") @GetMapping("/policy") public Result policy() { return Result.ok().data("policyData", ossUtil.getPolicy()); } @ApiOperation(value = "保存并獲取檔案 url") @PostMapping("/saveUrl") public Result saveUrl(@RequestParam String key, @RequestParam String fileName) { BackOss backOss = new BackOss(); backOss.setOssName(key); backOss.setFileName(fileName); backOss.setFileUrl(ossUtil.getUrl(key)); QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.eq("oss_name", key); backOssService.saveOrUpdate(backOss, queryWrapper); return Result.ok().data("file", backOssService.getOne(queryWrapper)); } }
(4)前臺代碼(vue + element-ui):
此處僅用于測驗介面,并未整合到實際代碼中(后續在整合到前臺代碼中),
此處采用普通 html,并引入 vue、element-ui 相關 cdn 進行演示,
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<!-- 引入樣式 -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/lib/theme-chalk/index.css">
<!-- 引入組件庫 -->
<script src="https://unpkg.com/[email protected]/lib/index.js"></script>
</head>
<body>
<div id="test">
<!--
使用 element-ui 上傳組件 (el-upload),
action 用于指定上傳的地址(必寫),
on-preview 點擊檔案串列中檔案觸發,
on-remove 移除檔案串列中檔案觸發,
before-upload 用于上傳檔案前觸發(可用于檢測檔案大小、格式之類的),
on-success 檔案上傳成功后觸發,
on-error 檔案上傳失敗觸發,
multiple 用于支持選擇多檔案,
limit 表示每次可以選擇的檔案數目,
on-exceed 檔案數目超出限制時觸發,
file-list 表示上傳檔案串列,
data 表示額外傳遞的引數,
accept 用于指定格式(默認 * )
-->
<el-upload :action="policyData.host" :on-preview="handlePreview" :on-remove="handleRemove" :before-upload="beforeUpload"
:on-success="handleSuccess" :on-error="handleError" multiple :limit="3" :on-exceed="handleExceed" :file-list="fileList"
:data="policyData" accept=".jpg,.JPG,.jpeg,.JPEG,.png,.PNG,.gif,.GIF">
<el-button size="small" type="primary">點擊上傳</el-button>
<!-- tip 表示提示文字 -->
<div slot="tip">只能上傳jpg/png/gif檔案,且不超過 5 MB</div>
</el-upload>
</div>
<script type="text/javascript">
var vm = new Vue({
el: "#test",
data: {
fileList: [{
name: 'food.jpeg',
url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'
}],
policyData: {
"signature": "H3mPx51DPO73i0NJKTLgzvjX5dA=",
"expire": "1592989560",
"host": "http://admin-vue-template.oss-cn-beijing.aliyuncs.com",
"ossaccessKeyId": "LTAI4GEWZbLZocBzXKYEfmmq",
"key": "aliyun/20200624/be4678d9d2db4e5fb34f195e0b854615-",
"policy": "eyJleHBpcmF0aW9uIjoiMjAyMC0wNi0yNFQwOTowNjowMC45NDBaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCJhbGl5dW4vMjAyMDA2MjQvYmU0Njc4ZDlkMmRiNGU1ZmIzNGYxOTVlMGI4NTQ2MTUtIl1dfQ=="
}
},
methods: {
handleRemove(file, fileList) {
console.log(file, fileList)
},
handlePreview(file) {
console.log(file)
},
handleExceed(files, fileList) {
this.$message.warning(`當前限制選擇 3 個檔案,本次選擇了 ${files.length} 個檔案,共選擇了 ${files.length + fileList.length} 個檔案`)
},
beforeUpload(file) {
let size = file.size / 1024 / 1024
let type = ".jpg,.JPG,.jpeg,.JPEG,.png,.PNG,.gif,.GIF".split(",");
let fileType = file.name.substring(file.name.lastIndexOf("."))
if (size > 5) {
this.$message.warning(`上傳檔案不能超過 5 M`)
return false
}
if (type.indexOf(fileType) === -1) {
this.$message.warning(`上傳檔案格式不正確`)
return false
}
},
handleSuccess(response, file, fileList) {
console.log(response)
console.log(file)
console.log(fileList)
},
handleError(error, file, fileList) {
console.log(error)
console.log(file)
console.log(fileList)
},
}
});
</script>
</body>
</html>
(5)測驗介面:
由于并未整合 axios 發送請求,所以手動通過 swagger 觸發介面并將資料粘貼到相應地方進行測驗,
首先呼叫后臺介面 policy 獲取到簽名資料,將該資料復制并替換前臺代碼 policyData 中,
然后執行上傳檔案即可,
最后呼叫 saveUrl 介面,將 url 以及檔案資訊保存到 資料庫中,

5、json 資料顯示問題 -- 日期少 8 小時、 id 值與資料庫值不一致
(1)問題:
如下圖所示:
回傳的 json 資料,可以看到 id 值與 日期值 與資料庫有明顯的區別,


(2)解決 id 與資料庫不一致問題,
原因分析:
由于后臺代碼,id 生成策略選擇 type = IdType.ASSIGN_ID,其會通過雪花演算法生成一個長的 Long 型數字,而這個數字傳遞到前臺超過了 js 的數字存盤范圍,使數字精度丟失,
解決思路:
在 Long 型別轉為 Json 之前,將其 變為 String 型別,這樣前臺獲取的即為 String 型別,從而保證精度,
解決方式一:(有局限性,需要對每個物體類進行標注)
在物體類上標注 @JsonSerialize 注解,并指定序列化方式,
@TableId(value = "https://www.cnblogs.com/l-y-h/p/id", type = IdType.ASSIGN_ID) @JsonSerialize(using = ToStringSerializer.class) private Long id;

解決方式二:(通用)
撰寫一個 Jackson2ObjectMapperBuilderCustomizer 物件,并交給 Spring 管理,
@Configuration public class Config { @Bean public Jackson2ObjectMapperBuilderCustomizer builderCustomizer() { return builder -> { // 所有 Long 型別轉換成 String 到前臺 builder.serializerByType(Long.class, ToStringSerializer.instance); }; } }

(3)解決日期少 8 小時問題,
原因分析:
少 8 小時,即時區的問題,
解決思路:
給其時區添加 8 小時,同時可以指定 日期輸出格式,
解決方式一:(有局限性,需要對每個物體類進行標注)
在物體類上標注 @JsonFormat 注解,并指定轉換格式 以及 時區,
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private Date createTime;

解決方式二:(通用)
在 組態檔中,配置時區以及格式,
spring: # 設定 json 中日期顯示格式 jackson: # 設定顯示格式 date-format: yyyy-MM-dd HH:mm:ss # 設定時區 time-zone: GMT+8

(4)再次獲取資料,
上面兩個問題,本專案中均采用解決方式二去解決,


6、洗掉檔案
(1)洗掉檔案
洗掉 oss 檔案的同時也要洗掉資料庫中的資料,
(2)代碼實作:
Step1:
在工具類中撰寫 oss 洗掉邏輯,
package com.lyh.admin_template.back.common.utils; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.common.utils.BinaryUtil; import com.aliyun.oss.model.MatchMode; import com.aliyun.oss.model.PolicyConditions; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URL; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * Oss 工具類,用于操作 OSS */ @Data @Component public class OssUtil { @Value("${aliyun.endPoint}") private String endPoint; @Value("${aliyun.bucketName}") private String bucketName; @Value("${aliyun.accessKeyId}") private String accessKeyId; @Value("${aliyun.accessKeySecret}") private String accessKeySecret; @Value("${aliyun.domain}") private String domain; /** * 設定檔案上傳路徑(prefix + 日期 + uuid + suffix) */ public String getPath(String prefix, String suffix) { // 生成 UUID String uuid = UUID.randomUUID().toString().replaceAll("-", ""); // 格式化日期 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd"); // 拼接檔案路徑 String path = dateTimeFormatter.format(LocalDateTime.now()) + "/" + uuid; if (StringUtils.isNotEmpty(prefix)) { path = prefix + "/" + path; } return path + "-" + suffix; } /** * 上傳檔案 */ public String upload(byte[] data, String path) { return upload(new ByteArrayInputStream(data), path); } /** * 上傳檔案,自定義 前后綴 */ public String uploadSuffix(byte[] data, String prefix ,String suffix) { return upload(data, getPath(prefix, suffix)); } /** * 上傳檔案,自定義 前后綴 */ public String uploadSuffix(InputStream inputStream, String prefix, String suffix) { return upload(inputStream, getPath(prefix, suffix)); } /** * 上傳檔案 */ public String upload(InputStream inputStream, String path) { try { // 創建 OSSClient 實體, OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); // 上傳檔案到 指定 bucket ossClient.putObject(bucketName, path, inputStream); // 關閉 OSSClient ossClient.shutdown(); } catch (Exception e) { throw new RuntimeException("上傳檔案失敗"); } return path; } /** * 獲取檔案 url */ public String getUrl(String key) { // 用于保存 url 地址 URL url = null; try { // 創建 OSSClient 實體, OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); // 設定 url 過期時間(10 年) Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 24 * 365 * 10); // 獲取 url 地址 url = ossClient.generatePresignedUrl(bucketName, key, expiration); // 關閉 OSSClient ossClient.shutdown(); } catch (Exception e) { throw new RuntimeException("獲取檔案 url 失敗"); } return url != null ? url.toString() : null; } /** * 用于獲取簽名資料 */ public Map<String, String> getPolicy() { return getPolicy(getPath("aliyun", "signature")); } /** * 用于獲取簽名資料,用于服務端直傳檔案到服務器 */ public Map<String, String> getPolicy(String path) { // 用于保存 Map<String, String> map = new HashMap<>(); try { // 創建 OSSClient 實體, OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); // 用于設定 post 上傳條件 PolicyConditions policyConditions = new PolicyConditions(); // 設定最大上傳檔案大小(1G) policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000); // 設定檔案前綴 policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, path); // 設定簽名過期時間(6 小時) Date expiration = new Date(new Date().getTime() + 1000L * 60 * 60 * 6); // 生成 policy String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions); // 設定編碼字符集(UTF-8) byte[] binaryData =https://www.cnblogs.com/l-y-h/p/ postPolicy.getBytes(StandardCharsets.UTF_8); // 設定加密格式(Base64) String encodedPolicy = BinaryUtil.toBase64String(binaryData); // 計算簽名 String postSignature = ossClient.calculatePostSignature(postPolicy); // 封裝資料 map.put("ossaccessKeyId", accessKeyId); map.put("policy", encodedPolicy); map.put("signature", postSignature); map.put("key", path); map.put("expire", String.valueOf(expiration.getTime() / 1000)); map.put("host", domain); // 關閉 OSSClient ossClient.shutdown(); } catch (Exception e) { throw new RuntimeException("獲取簽名資料失敗"); } return map; } /** * 洗掉 OOS 中的檔案 */ public void deleteObject(String objectName) { try { // 創建 OSSClient 實體, OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret); // 洗掉指定 bucket 中的檔案 ossClient.deleteObject(bucketName, objectName); // 關閉 OSSClient ossClient.shutdown(); } catch (Exception e) { throw new RuntimeException("洗掉檔案失敗"); } } }

Step2:
呼叫工具類,并洗掉資料庫的資料,
package com.lyh.admin_template.back.modules.oss.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.lyh.admin_template.back.common.utils.OssUtil; import com.lyh.admin_template.back.common.utils.Result; import com.lyh.admin_template.back.modules.oss.service.BackOssService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * <p> * 檔案上傳 前端控制器 * </p> * * @author lyh * @since 2020-06-19 */ @RestController @RequestMapping("/oss/back-oss") @Api(tags = "檔案上傳") public class BackOssController { @Autowired private OssUtil ossUtil; @Autowired private BackOssService backOssService; @ApiOperation(value = "洗掉檔案") @DeleteMapping("/delete/object") public Result deleteObject(@RequestParam String key) { ossUtil.deleteObject(key); QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.eq("oss_name", key); backOssService.remove(queryWrapper); return Result.ok(); } }

Step3:
簡單測驗一下,
先上傳一個檔案,然后根據其 oss_name 將檔案洗掉,

7、下載檔案
(1)最簡單的方式:
只支持部分型別(比如:image/jpeg),有些型別會直接打開(比如:video/mp4)
直接使用 window.open(url) ,此時會觸發瀏覽器下載功能(檔案名默認不可更改),
【舉例:】
【舉例:】
window.open("http://admin-vue-template.oss-cn-beijing.aliyuncs.com/aliyun/20200624/025a5edee3a34df19ed8b0d51d4c8053-signature?Expires=1908339884&OSSAccessKeyId=LTAI4GEWZbLZocBzXKYEfmmq&Signature=budNdWygT241vRNOVW9OCioZ4jQ%3D")
(2)使用 Blob 流處理檔案
Step1:
訪問 url 地址時可能產生跨域問題,此處采用一個粗暴的方法,直接關閉 Chrome 安全策略進行測驗(非必須操作),
關閉 Chrome 安全策略(替換 chrome.exe 位置,命令列執行,會彈出一個瀏覽器視窗)
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --no-sandbox --disable-web-security --disable-gpu --user-data-dir=~/chromeTemp
Step2:
用 CDN 方式引入 axios 發送請求,
此處只簡單在 html 頁面中使用并測驗,專案中可以對其進行適當修改,
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.js"></script>
Step3:
撰寫 Blob 轉為檔案的邏輯,
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<!-- 引入樣式 -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/lib/theme-chalk/index.css">
<!-- 引入組件庫 -->
<script src="https://unpkg.com/[email protected]/lib/index.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.js"></script>
</head>
<body>
<div id="test">
<!--
使用 element-ui 上傳組件 (el-upload),
action 用于指定上傳的地址(必寫),
on-preview 點擊檔案串列中檔案觸發,
on-remove 移除檔案串列中檔案觸發,
before-upload 用于上傳檔案前觸發(可用于檢測檔案大小、格式之類的),
on-success 檔案上傳成功后觸發,
on-error 檔案上傳失敗觸發,
multiple 用于支持選擇多檔案,
limit 表示每次可以選擇的檔案數目,
on-exceed 檔案數目超出限制時觸發,
file-list 表示上傳檔案串列,
data 表示額外傳遞的引數,
accept 用于指定格式(默認 * )
-->
<el-upload :action="policyData.host" :on-preview="handlePreview" :on-remove="handleRemove" :before-upload="beforeUpload"
:on-success="handleSuccess" :on-error="handleError" multiple :limit="3" :on-exceed="handleExceed" :file-list="fileList"
:data="policyData" accept=".jpg,.JPG,.jpeg,.JPEG,.png,.PNG,.gif,.GIF">
<el-button size="small" type="primary">點擊上傳</el-button>
<!-- tip 表示提示文字 -->
<div slot="tip">只能上傳jpg/png/gif檔案,且不超過 5 MB</div>
</el-upload>
<el-button size="small" type="primary" @click="download">下載</el-button>
</div>
<script type="text/javascript">
var vm = new Vue({
el: "#test",
data: {
fileList: [{
name: 'food.jpeg',
url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'
}],
policyData: {
"signature": "Q5n13+6c7PZg2PKf6JgQ6rXvJbE=",
"expire": "1593001415",
"host": "http://admin-vue-template.oss-cn-beijing.aliyuncs.com",
"ossaccessKeyId": "LTAI4GEWZbLZocBzXKYEfmmq",
"key": "aliyun/20200624/025a5edee3a34df19ed8b0d51d4c8053-signature",
"policy": "eyJleHBpcmF0aW9uIjoiMjAyMC0wNi0yNFQxMjoyMzozNS4zNzJaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCJhbGl5dW4vMjAyMDA2MjQvMDI1YTVlZGVlM2EzNGRmMTllZDhiMGQ1MWQ0YzgwNTMtc2lnbmF0dXJlIl1dfQ=="
}
},
methods: {
handleRemove(file, fileList) {
console.log(file, fileList)
},
handlePreview(file) {
console.log(file)
},
handleExceed(files, fileList) {
this.$message.warning(`當前限制選擇 3 個檔案,本次選擇了 ${files.length} 個檔案,共選擇了 ${files.length + fileList.length} 個檔案`)
},
beforeUpload(file) {
let size = file.size / 1024 / 1024
let type = ".jpg,.JPG,.jpeg,.JPEG,.png,.PNG,.gif,.GIF".split(",");
let fileType = file.name.substring(file.name.lastIndexOf("."))
if (size > 5) {
this.$message.warning(`上傳檔案不能超過 5 M`)
return false
}
if (type.indexOf(fileType) === -1) {
this.$message.warning(`上傳檔案格式不正確`)
return false
}
},
handleSuccess(response, file, fileList) {
console.log(response)
console.log(file)
console.log(fileList)
},
handleError(error, file, fileList) {
console.log(error)
console.log(file)
console.log(fileList)
},
download() {
let url =
"https://admin-vue-template.oss-cn-beijing.aliyuncs.com/aliyun/20200624/025a5edee3a34df19ed8b0d51d4c8053-signature?Expires=1592995819&OSSAccessKeyId=TMP.3KjPgiYUJXZdW4yUwvDi2w58gCkdgAn2XFbExLPMgdWe4H6Y7JSzdVYxM5hiAn7PaKuBNG6zFhw9x2hB2GGTo5HPTXBwoY&Signature=4qZqZFgPW5NRYYHbCBRzgpZIpXA%3D"
let url2 =
"https://admin-vue-template.oss-cn-beijing.aliyuncs.com/aliyun/20200624/89481add65a04224b7ec97088e1ec2a7-test3.mp4?Expires=1592995837&OSSAccessKeyId=TMP.3KjPgiYUJXZdW4yUwvDi2w58gCkdgAn2XFbExLPMgdWe4H6Y7JSzdVYxM5hiAn7PaKuBNG6zFhw9x2hB2GGTo5HPTXBwoY&Signature=orq3cbgSz8Zgejv0Gsf0UMDyydw%3D"
axios.get(url, {
responseType: 'blob'
}).then(res => {
this.blobToFile(res.data, res.data.type)
}).catch(error => {
console.log(error)
})
axios.get(url2, {
responseType: 'blob'
}).then(res => {
console.log(res)
this.blobToFile(res, "video/mp4")
}).catch(error => {
console.log(error)
})
},
blobToFile(res, type) {
// res.data是后臺回傳的二進制資料,type:types為下載的資料型別
let blob = new Blob([res], {
type: type
})
let downLoadEle = document.createElement('a')
let href = URL.createObjectURL(blob)
downLoadEle.href = href
// ooo為自定義檔案名
downLoadEle.download = 'ooo'
document.body.appendChild(downLoadEle)
downLoadEle.click()
document.body.removeChild(downLoadEle)
window.URL.revokeObjectURL(href)
}
}
});
</script>
</body>
</html>
Step4:
測驗效果如下,使用 get 請求,根據 url 獲取檔案流,并將其內容置為 超鏈接,通過超鏈接的形式進行下載,(可以自定義檔案名)

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/155844.html
標籤:Java
