圖片裁剪與上傳處理方案 —— 基于阿里云 OSS 處理用戶資料

目錄

01: 通用組件:input 構建方案分析

02: 通用組件:input 構建方案

03: 構建用戶資料基礎樣式?

04: 用戶基本資料修改方案

05: 處理不保存時的同步問題?

06: 頭像修改方案流程分析

07: 通用組件:Dialog 構建方案分析

08: 通用組件:Dialog 構建方案

09: 應用 Dialog 展示頭像

10: 頭像裁剪構建方案?

11. 阿里云 OSS 與騰訊云 COS 對象存儲方案分析?

騰訊云 COS

COS SDK(COS 的包)?

阿里云 OSS

OSS 基礎概念

創建存儲桶(Bucket)

使用 STS 臨時訪問憑證訪問 OSS?

上傳圖片到 Bucket 的流程分析

配置 CORS 跨域處理

12. 使用臨時憑證,上傳裁剪圖片到阿里云 OSS

13. 完成頭像更新操作

14. 登錄鑒權解決方案

15: 總結


?

01: 通用組件:input 構建方案分析

?

期望通用組件 input 至少滿足 4 個功能:

? ? ? ? 1. 支持單行文本輸入?

? ? ? ? 2. 支持多行文本輸入

? ? ? ? 3. 通過 v-model 實現雙向數據綁定

? ? ? ? 4. 支持最大文本輸入

根據以上功能點,可判斷出 input 組件要有 3 個 prop:

? ? ? ? 1. v-model

? ? ? ? 2. type:單行 or 多行

? ? ? ? 3. max:支持最大字符數

02: 通用組件:input 構建方案

- src/libs
- - input
- - - index.vue
// src/libs/input/index.vue<template><div class="relative"><inputv-if="type === TYPE_TEXT"class="border-gray-200 dark:border-zinc-600 dark:bg-zinc-800 duration-100 dark:text-zinc-400 border-[1px] outline-0 py-0.5 px-1 text-sm rounded-sm focus:border-blue-400 w-full"type="text"v-model="text":maxlength="max"/><textareav-if="type === TYPE_TEXTAREA"v-model="text":maxlength="max"rows="5"class="border-gray-200 dark:border-zinc-600 dark:bg-zinc-800 duration-100 dark:text-zinc-400 border-[1px] outline-0 py-0.5 px-1 text-sm rounded-sm focus:border-blue-400 w-full"></textarea><spanv-if="max"class="absolute right-1 bottom-0.5 text-zinc-400 text-xs":class="{ 'text-red-700': currentNumber === parseInt(max) }">{{ currentNumber }} / {{ max }}</span></div>
</template><script>
const TYPE_TEXT = 'text'
const TYPE_TEXTAREA = 'textarea'
</script><script setup>
import { useVModel } from '@vueuse/core'
import { computed } from 'vue'const props = defineProps({modelValue: {required: true,type: String},type: {type: String,default: TYPE_TEXT,validator(value) {const arr = [TYPE_TEXT, TYPE_TEXTAREA]const result = arr.includes(value)if (!result) {throw new Error(`type 的值必須在可選范圍內 [${arr.join('、')}]`)}return result}},max: {type: [String, Number]}
})// 事件聲明
defineEmits(['update:modelValue'])// 輸入的字符
const text = useVModel(props)// 輸入的字符數
const currentNumber = computed(() => {return text.value?.length
})
</script><style lang="scss" scoped></style>

03: 構建用戶資料基礎樣式?

tailwindcss 先構建移動端,再構建 PC 端更方便。

- src/views
- - profile
- - - index.vue
// 路由信息{path: '/profile',name: 'profile',component: () => import('@/views/profile/index.vue'),// 標記當前的頁面只有用戶登錄之后才可以進入meta: {user: true}
},
// src/views/profile/index.vue<template><divclass="h-full bg-zinc-200 dark:bg-zinc-800 duration-400 overflow-auto xl:pt-1"><divclass="relative max-w-screen-lg mx-auto bg-white dark:bg-zinc-900 duration-400 xl:rounded-sm xl:border-zinc-200 xl:dark:border-zinc-600 xl:border-[1px] xl:px-4 xl:py-2"><!-- 移動端 navbar --><m-navbar sticky v-if="isMobileTerminal" :clickLeft="onNavbarLeftClick">個人資料</m-navbar><!-- pc 端 --><div v-else class="text-lg font-bold text-center mb-4 dark:text-zinc-300">個人資料</div><div class="h-full w-full px-1 pb-4 text-sm mt-2 xl:w-2/3 xl:pb-0"><!-- 頭像 --><div class="py-1 xl:absolute xl:right-[16%] xl:text-center"><spanclass="w-8 inline-block mb-2 font-bold text-sm dark:text-zinc-300 xl:block xl:mx-auto">我的頭像</span><!-- 頭像部分 --><divclass="relative w-[80px] h-[80px] group xl:cursor-pointer xl:left-[50%] xl:translate-x-[-50%]"@click="onAvatarClick"><imgv-lazy:src="$store.getters.userInfo.avatar"alt=""class="rounded-[50%] w-full h-full xl:inline-block"/><divclass="absolute top-0 rounded-[50%] w-full h-full bg-[rgba(0,0,0,.4)] hidden xl:group-hover:block"><m-svg-iconname="change-header-image"class="w-2 h-2 m-auto mt-2"></m-svg-icon><divclass="text-xs text-white dark:text-zinc-300 scale-90 mt-0.5">點擊更換頭像</div></div></div><!-- 隱藏域 --><inputv-show="false"ref="inputFileTarget"type="file"accept=".png, .jpeg, .jpg, .gif"@change="onSelectImgHandler"/><p class="mt-1 text-zinc-500 dark:text-zinc-400 text-xs xl:w-10">支持 jpg、png、jpeg 格式大小 5M 以內的圖片</p></div><!-- 用戶名 --><div class="py-1 xl:flex xl:items-center xl:my-1"><span class="w-8 block mb-1 font-bold dark:text-zinc-300 xl:mb-0">用戶名</span><m-inputv-model="userInfo.nickname"class="w-full"type="text"max="20"></m-input></div><!-- 職位 --><div class="py-1 xl:flex xl:items-center xl:my-1"><span class="w-8 block mb-1 font-bold dark:text-zinc-300 xl:mb-0">職位</span><m-inputv-model="userInfo.title"class="w-full"type="text"></m-input></div><!-- 公司 --><div class="py-1 xl:flex xl:items-center xl:my-1"><span class="w-8 block mb-1 font-bold dark:text-zinc-300 xl:mb-0">公司</span><m-inputv-model="userInfo.company"class="w-full"type="text"></m-input></div><!-- 個人主頁 --><div class="py-1 xl:flex xl:items-center xl:my-1"><span class="w-8 block mb-1 font-bold dark:text-zinc-300 xl:mb-0">個人主頁</span><m-inputv-model="userInfo.homePage"class="w-full"type="text"></m-input></div><!-- 個人介紹 --><div class="py-1 xl:flex xl:my-1"><span class="w-8 block mb-1 font-bold dark:text-zinc-300 xl:mb-0">個人介紹</span><m-inputv-model="userInfo.introduction"class="w-full"type="textarea"max="50"></m-input></div><!-- 保存修改 --><m-buttonclass="w-full mt-2 mb-4 dark:text-zinc-300 dark:bg-zinc-800 xl:w-[160px] xl:ml-[50%] xl:translate-x-[-50%]":loading="loading"@click="onChangeProfile">保存修改</m-button><!-- 移動端退出登錄 --><m-buttonv-if="isMobileTerminal"class="w-full dark:text-zinc-300 dark:bg-zinc-800 xl:w-[160px] xl:ml-[50%] xl:translate-x-[-50%]"@click="onLogoutClick">退出登錄</m-button></div></div><!-- PC 端 --><m-dialog v-if="!isMobileTerminal" v-model="isDialogVisible"><change-avatar-vue:blob="currentBolb"@close="isDialogVisible = false"></change-avatar-vue></m-dialog><!-- 移動端 --><m-popupv-else:class="{ 'h-screen': isDialogVisible }"v-model="isDialogVisible"><change-avatar-vue:blob="currentBolb"@close="isDialogVisible = false"></change-avatar-vue></m-popup></div>
</template><script>
export default {name: 'profile'
}
</script><script setup>
import { isMobileTerminal } from '@/utils/flexible'
import { putProfile } from '@/api/sys'
import { message, confirm } from '@/libs'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { ref, watch } from 'vue'
import changeAvatarVue from './components/change-avatar.vue'const store = useStore()
const router = useRouter()// 隱藏域
const inputFileTarget = ref(null)
// 頭像 dialog 展示
const isDialogVisible = ref(false)
// 選中的圖片
const currentBolb = ref('')
/*** 更換頭像點擊事件*/
const onAvatarClick = () => {inputFileTarget.value.click()
}/*** 頭像選擇之后的回調*/
const onSelectImgHandler = () => {// 獲取選中的文件const imgFile = inputFileTarget.value.files[0]// 生成 blob 對象const blob = URL.createObjectURL(imgFile)// 獲取選中的圖片currentBolb.value = blob// 展示 DialogisDialogVisible.value = true
}/*** 監聽 dialog 關閉*/
watch(isDialogVisible, (val) => {if (!val) {// 防止 change 不重復觸發inputFileTarget.value.value = null}
})/*** 數據本地的雙向同步,增加一個單層深拷貝*/
const userInfo = ref({ ...store.getters.userInfo })
// const changeStoreUserInfo = (key, value) => {
//   store.commit('user/setUserInfo', {
//     ...store.getters.userInfo,
//     [key]: value
//   })
// }/*** 修改個人信息*/
const loading = ref(false)
const onChangeProfile = async () => {loading.value = trueawait putProfile(userInfo.value)message('success', '用戶信息修改成功')// 更新 vuexstore.commit('user/setUserInfo', userInfo.value)loading.value = false
}/*** 移動端后退處理*/
const onNavbarLeftClick = () => {// 配置跳轉方式store.commit('app/changeRouterType', 'back')router.back()
}/*** 移動端:退出登錄*/
const onLogoutClick = () => {confirm('確定要退出登錄嗎?').then(() => {store.dispatch('user/logout')})
}
</script><style lang="scss" scoped></style>

04: 用戶基本資料修改方案

// 第一種方式:直接修改 vuex state 數據,違背 vue 理念。<m-input v-model="$store.getters.userInfo.nickname" />// 第二種方式:優化第一種方式。<m-input :modelValue="$store.getters.userInfo.nickname"@update:modelValue="changeStoreUserInfo('nickname', $event)"
/>const changeStoreUserInfo = (key, value) => {store.commit('user/setUserInfo', {...store.getters.userInfo,[key]: value})
}// 第三種方式:解決用戶信息未提交,但提前緩存問題。推薦。<m-input v-model="userInfo.nickname" />/*** 數據本地的雙向同步,增加一個單層深拷貝*/
const userInfo = ref({ ...store.getters.userInfo })/*** 修改個人信息*/
const onChangeProfile = async () => {// 發送修改請求 代碼省略// 更新 vuexstore.commit('user/setUserInfo', userInfo.value)
}

05: 處理不保存時的同步問題?

講解一下 v-model 拆解的問題。

給不理解的同學講解一下:為什么不能用 v-model 直接綁定 vuex 中的數據?以及綁定之后會產生什么樣的問題?

思路及代碼在上一小節之中。

06: 頭像修改方案流程分析

接下來我們需要處理頭像修改的業務邏輯。

對于該功能而言,分為 PC 端和 移動端 兩種情況,我們需要分別進行處理:

1. PC 端:

? ? ? ? 1. 點擊更換頭像。

? ? ? ? 2. 選擇對應文件。

? ? ? ? 3. 通過 Dialog 展示圖片剪裁。

? ? ? ? 4. 剪裁后圖片上傳。

? ? ? ? 5. 功能完成。

2. 移動端:

? ? ? ? 1. 點擊更換頭像。

? ? ? ? 2. 選擇對應文件。

? ? ? ? 3. 通過 popup 展示圖片剪裁。

? ? ? ? 4. 剪裁后圖片上傳。

? ? ? ? 5. 功能完成。

由此可以發現,兩者之間需要通過不同的組件進行裁剪展示。

因此我們后續的開發流程為:

????????1. 通用組件:Dialog。

????????2. ?處理圖片剪裁。

????????3. 處理剪裁后上傳。

07: 通用組件:Dialog 構建方案分析

首先我們來處理 Dialog 通用組件。

對于 Dialog 通用組件而言,我們可以參考 confirm 組件的構建過程。

它們兩個構建方案非常相似,唯二不同的地方是:

????????1. Dialog 無需通過方法調用的形式展示。

????????2. Dialog 的內容區可以渲染任意的內容。

排除這兩點之后,其余與 confirm 完全相同。

08: 通用組件:Dialog 構建方案

- src/libs
- - dialog
- - - index.vue
<template><div><!-- 蒙版 --><transition name="fade"><divv-if="isVisable"@click="close"class="w-screen h-screen bg-zinc-900/80 z-40 fixed top-0 left-0"></div></transition><!-- 內容 --><transition name="up"><divv-if="isVisable"class="max-w-[80%] max-h-[80%] overflow-auto fixed top-[10%] left-[50%] translate-x-[-50%] z-50 px-2 py-1.5 rounded-sm border dark:border-zinc-600 cursor-pointer bg-white dark:bg-zinc-800 xl:min-w-[35%]"><!-- 標題 --><divclass="text-lg font-bold text-zinc-900 dark:text-zinc-200 mb-2"v-if="title">{{ title }}</div><!-- 內容 --><div class="text-base text-zinc-900 dark:text-zinc-200 mb-2"><slot /></div><!-- 按鈕 --><div class="flex justify-end" v-if="cancelHandler || confirmHandler"><m-button type="info" class="mr-2" @click="onCancelClick">{{cancelText}}</m-button><m-button type="primary" @click="onConfirmClick">{{confirmText}}</m-button></div></div></transition></div>
</template><script setup>
import { useVModel } from '@vueuse/core'const props = defineProps({// 控制開關modelValue: {type: Boolean,required: true},// 標題title: {type: String},// 取消按鈕文本cancelText: {type: String,default: '取消'},// 確定按鈕文本confirmText: {type: String,default: '確定'},// 取消按鈕點擊事件cancelHandler: {type: Function},// 確定按鈕點擊事件confirmHandler: {type: Function},// 關閉的回調close: {type: Function}
})defineEmits(['update:modelValue'])// 控制顯示處理
const isVisable = useVModel(props)/*** 取消按鈕點擊事件*/
const onCancelClick = () => {if (props.cancelHandler) {props.cancelHandler()}close()
}/*** 確定按鈕點擊事件*/
const onConfirmClick = () => {if (props.confirmHandler) {props.confirmHandler()}close()
}const close = () => {isVisable.value = falseif (props.close) {props.close()}
}
</script><style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {transition: all 0.3s;
}.fade-enter-from,
.fade-leave-to {opacity: 0;
}.up-enter-active,
.up-leave-active {transition: all 0.3s;
}.up-enter-from,
.up-leave-to {opacity: 0;transform: translate3d(-50%, 100px, 0);
}
</style>

09: 應用 Dialog 展示頭像

// src/views/profile/index.vue<template><img :src="currentBolb" />
</template><script setup>// 選中的圖片
const currentBolb = ref('')/*** 頭像選擇之后的回調*/
const onSelectImgHandler = () => {// 獲取選中的文件const imgFile = inputFileTarget.value.files[0]// 生成 blob 對象const blob = URL.createObjectURL(imgFile)// 獲取選中的圖片currentBolb.value = blob// 展示 DialogisDialogVisible.value = true
}/*** 當 input file 兩次選擇文件,是同一個的時候,change 的回調不會被再次觸發。* 想要解決這個問題,只需要在每次選擇的圖片不再被使用之后,清空掉 input file 的 value。* 監聽 dialog 關閉*/
watch(isDialogVisible, (val) => {if (!val) {// 防止 change 不重復觸發inputFileTarget.value.value = null}
})
</script>
- src/views/profile
- - components
- - - change-avatar.vue
// src/views/profile/components/change-avatar.vue<template><div class="overflow-auto relative flex flex-col items-center"><m-svg-iconv-if="isMobileTerminal"name="close"class="w-3 h-3 p-0.5 m-1 ml-auto"fillClass="fill-zinc-900 dark:fill-zinc-200 "@click="close"></m-svg-icon><img class="" ref="imageTarget" :src="blob" /><m-buttonclass="mt-4 w-[80%] xl:w-1/2":loading="loading"@click="onConfirmClick">確定</m-button></div>
</template><script>
const EMITS_CLOSE = 'close'// 移動端配置對象
const mobileOptions = {// 將裁剪框限制在畫布的大小viewMode: 1,// 移動畫布,裁剪框不動dragMode: 'move',// 裁剪框固定縱橫比:1:1aspectRatio: 1,// 裁剪框不可移動cropBoxMovable: false,// 不可調整裁剪框大小cropBoxResizable: false
}// PC 端配置對象
const pcOptions = {// 裁剪框固定縱橫比:1:1aspectRatio: 1
}
</script><script setup>
import { isMobileTerminal } from '@/utils/flexible'
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css'
import { ref, onMounted } from 'vue'
import { getOSSClient } from '@/utils/sts'
import { message } from '@/libs'
import { useStore } from 'vuex'
import { putProfile } from '@/api/sys'defineProps({blob: {type: String,required: true}
})const emits = defineEmits([EMITS_CLOSE])/*** 圖片裁剪處理*/
const imageTarget = ref(null)
let cropper = null
onMounted(() => {/*** 接收兩個參數:* 1. 需要裁剪的圖片 DOM* 2. options 配置對象*/cropper = new Cropper(imageTarget.value,isMobileTerminal.value ? mobileOptions : pcOptions)
})/*** 確定按鈕點擊事件*/
const loading = ref(false)
const onConfirmClick = () => {loading.value = true// 獲取裁剪后的圖片cropper.getCroppedCanvas().toBlob((blob) => {// 裁剪后的 blob 地址// console.log(URL.createObjectURL(blob))putObjectToOSS(blob)})
}/*** 進行 OSS 上傳*/
let ossClient = null
let store = useStore()
const putObjectToOSS = async (file) => {if (!ossClient) {ossClient = await getOSSClient()}try {// 因為當前憑證只具備 images 文件夾下的訪問權限,所以圖片需要上傳到 images/xxx.xx 。否則你將得到一個 《AccessDeniedError: You have no right to access this object because of bucket acl.》 的錯誤const fileTypeArr = file.type.split('/')const fileName = `${store.getters.userInfo.username}/${Date.now()}.${fileTypeArr[fileTypeArr.length - 1]}`// 文件存放路徑,文件const res = await ossClient.put(`images/${fileName}`, file)// 通知服務器onChangeProfile(res.url)} catch (e) {message('error', e)}
}/*** 上傳新頭像到服務器*/
const onChangeProfile = async (avatar) => {// 更新本地數據store.commit('user/setUserInfo', {...store.getters.userInfo,avatar})// 更新服務器數據await putProfile(store.getters.userInfo)// 通知用戶message('success', '用戶頭像修改成功')// 關閉 loadingloading.value = false// 關閉 dialogclose()
}/*** 關閉事件*/
const close = () => {emits(EMITS_CLOSE)
}
</script><style lang="scss" scoped></style>

10: 頭像裁剪構建方案?

????????接下來我們需要在 src/views/profile/components/change-avatar.vue 中處理對應的圖片裁剪功能。?

? ? ? ? 想要處理圖片裁剪,我們需要使用到 cropperjs,它是一個 Javascript 的庫,同時支持 PC 端 和 移動端。

? ? ? ? 目前 cropperjs 的最新發布版本為?1.6.2,V2 級別的版本還是 RC?階段,所以我們還是使用它的 V1 版本。

1. 安裝 cropperjs

npm install cropperjs@1.5.12 --save

2. 在 src/views/profile/components/change-acatar.vue 中進行導入

import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css'

3. 使用 new Cropper 進行初始化,區分 PC端 和 移動端:所有配置項

// 移動端配置對象
const mobileOptions = {// 將裁剪框限制在畫布的大小viewMode: 1,// 移動畫布,裁剪框不動dragMode: 'move',// 裁剪框固定縱橫比:1:1aspectRatio: 1,// 裁剪框不可移動cropBoxMovable: false,// 不可調整裁剪框大小cropBoxResizable: false
}// PC 端配置對象
const pcOptions = {// 裁剪框固定縱橫比:1:1aspectRatio: 1
}

4.?

/*** 圖片裁剪處理*/
const imageTarget = ref(null)
let cropper = null
onMounted(() => {/*** 接收兩個參數:* 1. 需要裁剪的圖片 DOM* 2. options 配置對象*/cropper = new Cropper(imageTarget.value,isMobileTerminal.value ? mobileOptions : pcOptions)
})

5.

/*** 確定按鈕點擊事件*/
const loading = ref(false)
const onConfirmClick = () => {loading.value = true// 獲取裁剪后的圖片cropper.getCroppedCanvas().toBlob((blob) => {// 裁剪后的 blob 地址// console.log(URL.createObjectURL(blob))putObjectToOSS(blob)})
}

11. 阿里云 OSS 與騰訊云 COS 對象存儲方案分析?

當圖片裁剪處理完成之后,接下來我們就可以處理裁剪之后的圖片上傳了。

????????通常情況下,在企業開發中,圖片或文件的管理都會通過 對象存儲 的方案進行。目前國內常見的對象存儲云方案主要有兩個平臺:?

????????1.?阿里云 OSS

????????2.?騰訊云 COS

騰訊云 COS

騰訊云 COS 目前提供了 實名認證贈送 6個月 對象存儲的服務,點擊跳轉

?

????????這個服務對大家而言是非常好的一個練習服務,大家可以直接使用該服務來實現 裁剪圖片上傳到騰訊云。

以下為騰訊云 COS 使用流程:

????????1. 注冊 騰訊云 賬號,并完成 實名認證。?

????????2. 選擇免費產品。

????????3. 選擇騰訊云 COS。

????????4. 點擊立即開通。

????????5. 創建存儲桶。

????????6. 點擊創建。

????????7. 選擇 公有讀、私有寫

????????8. 一路下一步,創建即可。

此時你可以得到對應的存儲桶,所有的文件都會被上傳到該存儲桶之中。

接下來就可以按照以下步驟進行圖片上傳:

COS SDK(COS 的包)?

對應文檔地址?

1. 下載依賴包:

npm i cos-js-sdk-v5 --save

2. 構建 cos 實例:初始化 cos 對象參數?

名稱描述
SecretId開發者擁有的項目身份識別 ID,用以身份認證,可在 API 密鑰管理 頁面獲取
SecretKey開發者擁有的項目身份密鑰,可在 API 密鑰管理 頁面獲取
import COS from 'cos-js-sdk-v5'const cos = new COS({SecretId: '',SecretKey: ''
})

3. 執行上傳操作

cos.putObject({Bucket: '',  // 填入您自己的存儲痛,必須字段Region: '',  // 存儲桶所在地域,例如 ap-beijing,必須字段Key: params.file.name,  // 存儲在桶里的對象鍵(例如 1.jpg a/b/test.txt),必須字段StorageClass: 'STANDARD',Body: ,  // 上傳文件對象onProgress: function (progressData) {console.log(JSON.stringify(progressData)}
}, function (err, data) {// 上傳成功返回的數據,data.location 為圖片的地址console.log(err || data)
})

4. 圖片上傳成功,在存儲桶中即可查看上傳的圖片。?

阿里云 OSS

目前國內企業,使用最多的云服務為 阿里云,所以說咱們文章將會以 阿里云 為例進行開發。

阿里云的 OSS 服務,有新人三個月的免費試用。

我們將使用 OSS 進行圖片的上傳。

阿里云 OSS 使用流程:

? ? ? ? 1. 注冊登錄 阿里云服務。

? ? ? ? 2. 找到 阿里云 OSS 對象存儲服務。

? ? ? ? 3. 點擊 立即開通。

? ? ? ? 4. 選擇 立即開通。

? ? ? ? 5. 勾選服務,點擊立即開通。

? ? ? ? 6. 提示開通成功之后,可以進行兩個操作。

? ? ? ? ? ? ? ? 1. 點擊 管理控制臺 進入 OSS 控制臺。

? ? ? ? ? ? ? ? 2. 點擊 對象存儲新手入門 查看文檔。

OSS 基礎概念

OSS 中包含了很多的基礎概念,可以點擊?這里 直接進行訪問。

需要大家了解的基礎概念有:

? ? ? ? 1. Bucket:存儲空間。

? ? ? ? 2. Object:存儲文件。

創建存儲桶(Bucket)

控制臺 左側點擊 Bucket 列表,然后點擊 創建 Bucket(讀寫權限為 私有

使用 STS 臨時訪問憑證訪問 OSS?

在 Bucket 構建完成之后,接下來我們就需要去處理 訪問憑證。?

注意:構建訪問憑證,會涉及到服務端的配置。在實際開發中,不需要 前端工程師來處理訪問憑證相關的內容。

具體的構建流程可以點擊 這里 進行查看。

名詞解釋:

????????RAM(Resource Access Management)

????????STS(Security Token?Service)

????????ARN是指云服務所定義的資源(Aliyun Resource Name)

上傳圖片到 Bucket 的流程分析

1. 想要上傳文件到 Bucket,我們需要使用 ali-sdk ali-oss。

2. 利用 ali-oss 生成 OSS 對象。

3. 在生成 OSS 對象時,需要傳遞 文件上傳憑證

? ? ? ? 1. accessKeyId。

? ? ? ? 2. accessKeySecret。

? ? ? ? 3. stsToken。

4. 需要通過接口 /user/sts 獲取 文件上傳憑證

整體的文件上傳流程為:

? ? ? ? 1. 通過接口 /user/sts 獲取 文件上傳憑證

? ? ? ? 2. 通過 npm i ali-oss 安裝依賴包。

? ? ? ? 3. 使用憑證中的數據構建 OSS 對象。點擊這里查看文檔。

配置 CORS 跨域處理

當我們嘗試使用 put 方法上傳文件時,會得到一個跨域的錯誤。

想要處理這個問題,則需要 配置跨域規則。點擊這里查看配置方案。

12. 使用臨時憑證,上傳裁剪圖片到阿里云 OSS

本小節我們將講解如何將裁剪后的圖片上傳到阿里云 OSS。

具體步驟如下:

? ? ? ? 1. 安裝 ali-oss 依賴。

? ? ? ? 2. 通過接口獲取臨時訪問憑證,生成 OSS 實例。

? ? ? ? 3. 利用 ossClient.put 方法,完成對應上傳。

接下來我們一步一步去做:

1. 安裝 ali-oss 依賴。

npm i --save ali-oss@6.17.0

2. 創建 src/utils/sts.js 模塊,用來生成 OSS 實例。

import OSS from 'ali-oss'
import { REGION, BUCKET } from '@/constants'
import { getSts } from '@/api/sys'export const getOSSClient = async () => {const res = await getSts()return new OSS({// yourRegion填寫Bucket所在地域。以華東1(杭州)為例,Region填寫為oss-cn-hangzhou。region: REGION,// 從STS服務獲取的臨時訪問密鑰(AccessKey ID和AccessKey Secret)。accessKeyId: res.Credentials.AccessKeyId,accessKeySecret: res.Credentials.AccessKeySecret,// 從STS服務獲取的安全令牌(SecurityToken)。stsToken: res.Credentials.SecurityToken,// 填寫Bucket名稱。bucket: BUCKET,// 刷新 token,在 token 過期后自動調用(但是并不生效,可能會在后續的版本中修復)refreshSTSToken: async () => {// 向您搭建的STS服務獲取臨時訪問憑證。const res = await getSts()return {accessKeyId: res.Credentials.AccessKeyId,accessKeySecret: res.Credentials.AccessKeySecret,stsToken: res.Credentials.SecurityToken}},// 刷新臨時訪問憑證的時間間隔,單位為毫秒。refreshSTSTokenInterval: 5 * 1000})
}

3. 在 constants 中定義 REGION,BUCKET

// STS 上傳數據
export const REGION = 'oss-cn-beijing'
export const BUCKET = 'imooc-front'

4.?

/*** 進行 OSS 上傳*/
let ossClient = null
let store = useStore()
const putObjectToOSS = async (file) => {if (!ossClient) {ossClient = await getOSSClient()}try {// 因為當前憑證只具備 images 文件夾下的訪問權限,所以圖片需要上傳到 images/xxx.xx 。否則你將得到一個 《AccessDeniedError: You have no right to access this object because of bucket acl.》 的錯誤const fileTypeArr = file.type.split('/')const fileName = `${store.getters.userInfo.username}/${Date.now()}.${fileTypeArr[fileTypeArr.length - 1]}`// 文件存放路徑,文件const res = await ossClient.put(`images/${fileName}`, file)// 通知服務器onChangeProfile(res.url)} catch (e) {message('error', e)}
}/*** 上傳新頭像到服務器*/
const onChangeProfile = async (avatar) => {// 更新本地數據store.commit('user/setUserInfo', {...store.getters.userInfo,avatar})// 更新服務器數據await putProfile(store.getters.userInfo)// 通知用戶message('success', '用戶頭像修改成功')// 關閉 loadingloading.value = false// 關閉 dialogclose()
}/*** 關閉事件*/
const close = () => {emits(EMITS_CLOSE)
}

13. 完成頭像更新操作

????????為了代碼連貫性,故代碼寫在上一小節中。

14. 登錄鑒權解決方案

// src/permission.jsimport router from '@/router'
import store from '@/store'
import { message } from '@/libs'/*** 處理需登錄頁面的訪問權限*/
router.beforeEach((to, from) => {// 無需登錄的頁面訪問if (!to.meta.user) {return}// 已登錄,可進入if (store.getters.token) {return true}// 未登錄,警告然后返回首頁message('warn', '登錄失效,請重新登錄!')return '/'
})

15: 總結

在本篇文章中,我們接觸到了兩個新的通用組件:

? ? ? ? 1. input

? ? ? ? 2. dialog

????????除此之外,我們還接觸到了頭像裁剪、OSS、COS 的概念。這些概念可能對有些同學而言會比較新奇。大家也可以自己申請一個對應的 OSS 或者 COS 的賬號(推薦 COS)。走一遍完整的流程,這樣大家會對這個操作更加的熟悉。?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/22859.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/22859.shtml
英文地址,請注明出處:http://en.pswp.cn/web/22859.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

計算機組成原理·考點知識點整理

根據往年考試題&#xff0c;對考點和知識點的一個整理。 校驗編碼 碼距 一種編碼的最小碼距&#xff0c;其實就是指這種編碼的碼距。碼距有兩種定義&#xff1a; 碼距所描述的對象含義 2 2 2 個特定的碼其二進制表示中不同位的個數一種編碼這種編碼中任意 2 2 2 個合法編碼的…

【linux進程控制(三)】進程程序替換--如何自己實現一個bash解釋器?

&#x1f493;博主CSDN主頁:杭電碼農-NEO&#x1f493; ? ?專欄分類:Linux從入門到精通? ? &#x1f69a;代碼倉庫:NEO的學習日記&#x1f69a; ? &#x1f339;關注我&#x1faf5;帶你學更多操作系統知識 ? &#x1f51d;&#x1f51d; 進程程序替換 1. 前言2. exec…

【JMeter接口自動化】第8講 Fiddler抓包Jmeter

1&#xff09;配置好Fiddler 設置Fiddler-Tools-Options-HTTPS 設置Fiddler-Tools-Options-Connections&#xff0c;設置端口為8888 2&#xff09;查看IP 在CMD中輸入ipconfig 查看IP地址 3&#xff09;配置Jmeter Http請求——基本&#xff0c;設置Http請求&#xff0c;使用…

輕量管理內核復雜級別的項目

在嵌入式開發中&#xff0c;管理大型項目&#xff08;例如Linux內核&#xff09;往往是一個復雜的過程。常規的版本控制系統如Git在處理小型項目時非常高效&#xff0c;但面對龐大的代碼庫時可能會顯得笨重且占用大量存儲空間。本文將介紹幾種輕量級的方法來管理內核級別的項目…

Python 快速入門

1. 語言基礎 1.1 數據類型與變量 Python 是一門動態類型語言&#xff0c;這意味著你不需要顯式聲明變量的類型。Python 解釋器會根據你賦予變量的值自動推斷其類型。這使得 Python 代碼簡潔易懂&#xff0c;但同時也需要注意一些潛在的問題。 1.1.1 Python 數據類型概述 Py…

408鏈表的創建和初始化

首先第一個頭文件&#xff0c;定義結構體類型 typedef struct LNode {int data;struct LNode* next; }LNode,*LinkList; //可能作為第一次寫c語言的小伙伴看不懂這一段typedef是如何定義的 //基本的解釋如下所示 //typedef struct LNode LNode; //typedef struct LNode* LinkL…

apex代碼發送郵件時進行抄送

在 Salesforce 中使用 Apex 代碼發送電子郵件時&#xff0c;可以通過 ccAddresses 屬性來添加抄送&#xff08;CC&#xff09;收件人。以下是一個示例代碼&#xff0c;展示了如何使用 Messaging.SingleEmailMessage 類來發送帶有抄送的電子郵件。 示例代碼 public class Emai…

北航數據結構與程序設計第四次作業選填題復習

首先都是線性的&#xff0c;線性包括順序和鏈式&#xff0c;棧和隊都可以用兩種方式實現。棧只能存于棧頂取于棧頂&#xff0c;隊列先進先出&#xff0c;因此存取點是固定的。 函數棧幀創建原理 畫圖即可。 A.顯然不行&#xff0c;5如果第一個出來說明5是最后一個進的&#xf…

Lambda表達式與函數式工具在Python中的應用詳解

目錄 一、引言 二、Lambda表達式 Lambda表達式的定義 Lambda表達式的使用場景 Lambda表達式的示例 三、函數式工具 map()函數 filter()函數 reduce()函數 itertools模塊 functools模塊 四、Lambda表達式與函數式工具的結合使用 五、Lambda表達式與函數式工具的注意…

C語言面試題(拓展)

1、字符串中獲取最長無重復字符子串。 要在字符串中找到最長的無重復字符的子串&#xff0c;可以使用滑動窗口技術。滑動窗口通過兩個指針來表示當前窗口的起始和結束位置&#xff0c;并且維護一個哈希表來記錄字符及其最后出現的位置&#xff0c;以此來確保字符不重復。 以下…

【云嵐家政】-day00-開發環境配置

文章目錄 1 開發工具版本2 IDEA環境配置2.1 編碼配置2.2 自動導包設置2.3 提示忽略大小寫2.4 設置 Java 編譯級別 3 Maven環境3.1 安裝Maven3.2 配置倉庫3.3 IDEA中配置maven 4 配置虛擬機4.1 導入虛擬機4.2 問題 5 配置數據庫環境5.1 啟動mysql容器5.2 使用MySQL客戶端連接數據…

Java Socket 網絡編程實例(阻塞IO、非阻塞IO、多路復用Selector、AIO)

文章目錄 1. 概述2. TCP 阻塞式IO 網絡編程實例2.1 TCP網絡編程服務端2.2 ByteBufferUtil2.3 客戶端代碼2.4 運行截圖 3. TCP 非阻塞式IO 網絡編程實例3.1 服務端3.2 客戶端3.3 運行截圖 4. 多路復用4.1 服務器端4.2 客戶端4.3 運行截圖 5. AIO5.1 AIO 服務端5.2 客戶端5.3 運行…

C++筆試強訓day39

目錄 1.神奇的字母&#xff08;二&#xff09; 2.字符編碼 3.最少的完全平方數 1.神奇的字母&#xff08;二&#xff09; 鏈接https://ac.nowcoder.com/acm/problem/205832 看輸出描述即可知輸出次數最多的那個字母即可。 哈希表直接秒了&#xff1a; #include <iostre…

一維時間序列突變檢測方法(小波等,MATLAB R2021B)

信號的突變點檢測問題是指在生產實踐中&#xff0c;反映各種系統工作狀態的信號&#xff0c;可能因為受到不同類型的噪聲或外界干擾而發生了信號突變&#xff0c;導致嚴重失真的信號出現&#xff0c;因此必須探測突變出現的起點和終點。研究目的在于設計出檢測方案&#xff0c;…

CPU內部結構窺探·「2」

從一條匯編加法指令出發&#xff0c;分析cpu內部發生了什么&#xff1f; 本文將詳細剖析ARMv8架構中加法指令的執行過程&#xff0c;深入理解其在CPU上的運行機制。 ARMv8匯編基礎 在ARMv8匯編語言中&#xff0c;加法指令ADD的基本格式如下&#xff1a; ADD destination, s…

【python】python租房數據分析可視化(源碼+數據+報告)【獨一無二】

&#x1f449;博__主&#x1f448;&#xff1a;米碼收割機 &#x1f449;技__能&#x1f448;&#xff1a;C/Python語言 &#x1f449;公眾號&#x1f448;&#xff1a;測試開發自動化【獲取源碼商業合作】 &#x1f449;榮__譽&#x1f448;&#xff1a;阿里云博客專家博主、5…

在Go語言中如何使用變量

1. 變量 Go 中的變量是標識符。例如&#xff0c;我們可能需要存儲客戶的電子郵件地址&#xff0c;但還需要確保它是有效的。這種情況下&#xff0c;可以創建一個名為 email 的變量來存儲電子郵件的值。電子郵件地址可以分配給 email 變量。 變量引用一個內存地址&#xff0c;賦…

OpenCV學習(4.3) 圖像閾值

1.目的 在本教程中&#xff1a; 你會學到簡單閾值法&#xff0c;自適應閾值法&#xff0c;以及 Otsu 閾值法(俗稱大津法)等。你會學到如下函數&#xff1a;**cv.threshold&#xff0c;cv.adaptiveThreshold** 等。 2.簡單閾值法 此方法是直截了當的。如果像素值大于閾值&am…

word2016版本中同時顯示多個頁面

為了方便查看word內容&#xff0c;我們會將多個頁面同時顯示。 對于2016版&#xff0c;操作方法如下&#xff1a; 視圖 ---》多頁

Jan任意文件讀取/下載和上傳漏洞

自從ChatGPT橫空出世以來&#xff0c;我一直想找一個可以自己訓練的AI大模型&#xff0c;然而在使用Jan的過程中&#xff0c;數據包中傳遞的參數引起了我的興趣&#xff0c;簡單嘗試后發現了任意文件讀取和任意文件上傳漏洞。 簡介 Jan是ChatGPT的開源替代品&#xff0c;它在…