Skip to content

Commit

Permalink
Merge pull request #25 from ATQQ/feature/support-upyun
Browse files Browse the repository at this point in the history
feat: 支持又拍云
  • Loading branch information
ATQQ authored Mar 31, 2024
2 parents 7830154 + f2116e0 commit 381c7eb
Show file tree
Hide file tree
Showing 15 changed files with 250 additions and 38 deletions.
15 changes: 13 additions & 2 deletions packages/cli/.env
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,16 @@ QINIU_PREFIX=image
# 图片的scope
QINIU_SCOPE=default

# token有效期,默认一个月,单位秒,你可以自行设置
# QINIU_EXPIRES=2592000
# token有效期,默认一个月,单位秒,你可以自行设置(60*60*24*30)
# QINIU_EXPIRES=2592000


# 又拍云相关配置
UPYUN_OPERATOR=operator
UPYUN_PASSWORD=password
UPYUN_BUCKET=service-name
UPYUN_DOMAIN=http://service-name.test.upcdn.net
UPYUN_PREFIX=image
UPYUN_SCOPE=default
# token有效期,默认一个月,单位秒,你可以自行设置(60*60*24*30)
# UPYUN_EXPIRES=2592000
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"main": "token.js",
"scripts": {
"generate": "node ./token.js",
"generate:upyun": "node ./upyun-token.js",
"generare:copy": "copy=true node ./token.js"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@ if (!process.env.CI) {
})
}
if (!process.env.copy) {
fs.writeFileSync(path.join(__dirname, '../client/.env.local'), `VITE_APP_QINIU_TOKEN=${envToken}`)
fs.writeFileSync(path.join(__dirname, '../client/.env.local'), `VITE_APP_UPLOAD_TOKEN=${envToken}`)
}
70 changes: 70 additions & 0 deletions packages/cli/upyun-token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import process from 'process'
import fs from 'fs'
import path, { dirname } from 'path'
import { fileURLToPath } from 'url'
import crypto from 'crypto'
import dotenv from 'dotenv'
import ncp from 'copy-paste'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
dotenv.config({ path: path.join(__dirname, '.env.local') })
dotenv.config({ path: path.join(__dirname, '.env') })

// function generateUpyunToken(operator, password, method, uriPrefix, expire) {
// const hmac = crypto.createHmac('sha1', Buffer.from(password, 'hex'))
// const data = `${method}&${uriPrefix}&${expire}`
// hmac.update(data)
// const digest = hmac.digest()
// const base64Encoded = digest.toString('base64')
// return `UPYUN ${operator}:${base64Encoded}`
// }
function MD5(value) {
return crypto.createHash('md5').update(value).digest('hex')
}

function tokenSign(operator, secret, method, uriPrefix, expire) {
const value = [method, uriPrefix, expire].filter(v => !!v).join('&')
const auth = hmacsha1(secret, value)
return `UPYUN ${operator}:${auth}`
}

function hmacsha1(secret, value) {
return crypto.createHmac('sha1', secret).update(value, 'utf8').digest().toString('base64')
}

// 示例参数
const operator = process.env.UPYUN_OPERATOR
const password = process.env.UPYUN_PASSWORD
const bucket = process.env.UPYUN_BUCKET
const prefix = process.env.UPYUN_PREFIX
const scope = process.env.UPYUN_SCOPE
const method = 'PUT'
const uriPrefix = `/${bucket}/${prefix}/${scope}`
const expire = new Date().getTime() + 1000 * 60 * 60 * 24 * 90 // 90天的过期时间
const domain = process.env.UPYUN_DOMAIN

const envToken = btoa(JSON.stringify({
token: tokenSign(operator, MD5(password), method, uriPrefix, expire),
date: expire,
domain,
prefix,
scope,
bucket,
type: 'upyun',
uriPrefix,
// TODO: 自定义 upyun 上传补充配置
// config: {

// },
}))
if (!process.env.CI) {
// 复制到剪贴板
ncp.copy(envToken, () => {
console.log('【token】已写入剪贴板')
console.log(envToken)
})
}
if (!process.env.copy) {
fs.writeFileSync(path.join(__dirname, '../client/.env.local'), `VITE_APP_UPLOAD_TOKEN=${envToken}`)
}
28 changes: 24 additions & 4 deletions packages/client/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<h1 align="center"> 七牛云OSS图床 </h1>
<h1 align="center"> 基于 OSS(对象存储库) 图床 </h1>

<p align="center"><img width="160px" src="./public/favicon.ico"/></p>

<p align="center">基于<a target="_blank" href="https://www.qiniu.com/products/kodo">七牛云对象存储服务</a>搭建的图床应用,<strong>前端纯静态,无需后端</strong></p>
<p align="center">基于对象存储服务搭建的图床应用,<strong>前端纯静态,无需后端</strong></p>

<p align="center">支持 <a target="_blank" href="https://www.qiniu.com/products/kodo">七牛云</a> | <a target="_blank" href="https://www.upyun.com/products/file-storage">又拍云</a></p>

<p align="center">
<a href="https://atqq.github.io/image-bed-qiniu/" target="_blank">GitHub Pages Demo</a> |
Expand All @@ -27,6 +29,7 @@ pnpm preview
```

### 🚧 Docker启动
TODO:待构建镜像

## 配置token
### 生成token
Expand All @@ -47,24 +50,41 @@ QINIU_SCOPE=default

# token有效期,默认一个月,单位秒,你可以自行设置
# QINIU_EXPIRES=2592000

# 又拍云相关配置
UPYUN_OPERATOR=operator
UPYUN_PASSWORD=password
UPYUN_BUCKET=service-name
UPYUN_DOMAIN=http://service-name.test.upcdn.net
UPYUN_PREFIX=image
UPYUN_SCOPE=default
# token有效期,默认一个月,单位秒,你可以自行设置(60*60*24*30)
# UPYUN_EXPIRES=2592000
```
最后资源地址为 **`domain/prefix/scope/md5`**

执行生成 token 脚本
① 七牛云:执行生成 token 脚本
```sh
npm run generate
# 或者
node token.js
```

② 又拍云:执行生成 token 脚本
```sh
npm run generate:upyun
# 或者
node upyun-token.js
```

![token-snippet](./token.png)

### 配置项目默认
*执行 `node token.js` 默认会生成这个文件*

[packages/client](./../client/) 下创建`.env.local`
```sh
VITE_APP_QINIU_TOKEN=你的token
VITE_APP_UPLOAD_TOKEN=你的token
```

启动构建项目即可
Expand Down
2 changes: 1 addition & 1 deletion packages/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>七牛云OSS图床 | 粥里有勺糖</title>
<title>纯静态 OSS 图床 | 粥里有勺糖</title>
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script>
<script>LA.init({ id: "KK6tcRYp7qnv7PCY", ck: "KK6tcRYp7qnv7PCY", hashMode: true })</script>
</head>
Expand Down
1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"qiniu-js": "^3.4.2",
"spark-md5": "^3.0.2",
"upng-js": "^2.1.0",
"upyun": "^3.4.6",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { useConfigStore } from './store';
import { useLocalStorage } from '@vueuse/core';
const store = useConfigStore()
// 默认取用户设置的token
const qiniuToken = useLocalStorage('qiniu-token', undefined)
const uploadToken = useLocalStorage('upload-token', undefined)
watch(qiniuToken, (newValue) => {
store.parseQiniuToken(newValue)
watch(uploadToken, (newValue) => {
store.parseToken(newValue)
}, {
immediate: true
})
Expand Down
10 changes: 5 additions & 5 deletions packages/client/src/components/ConfigPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ElMessage, ElMessageBox, ElButton } from 'element-plus';
import { storeToRefs } from 'pinia';
import { computed, onMounted, ref } from 'vue';
const store = useConfigStore()
const { qiniu } = storeToRefs(store)
const { config } = storeToRefs(store)
const dialogVisible = ref(false)
const expiredTime = ref('')
const countDown = ref('')
Expand All @@ -21,9 +21,9 @@ onMounted(() => {
refreshDDL()
})
function refreshDDL() {
expiredTime.value = formatDate(qiniu.value.date, 'yyyy-MM-dd')
expiredTime.value = formatDate(config.value.date, 'yyyy-MM-dd')
const refreshWait = () => {
let wait = ((qiniu.value.date - Date.now()) / 1000) >> 0
let wait = ((config.value.date - Date.now()) / 1000) >> 0
const day = (wait / (24 * 60 * 60)) >> 0
wait -= day * 24 * 60 * 60
const hour = (wait / (60 * 60)) >> 0
Expand All @@ -39,7 +39,7 @@ const handleCheckConfig = () => {
}
const cfgStr = computed(() => {
return `${JSON.stringify(qiniu.value, (key, value) => {
return `${JSON.stringify(config.value, (key, value) => {
if (key === 'token') {
return
}
Expand All @@ -54,7 +54,7 @@ const handleUpdateToken = () => {
if (!v.value?.trim()) {
return
}
store.parseQiniuToken(v.value)
store.parseToken(v.value)
}).catch(() => {
ElMessage.info('取消')
})
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/components/HomeHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ElAlert, ElLink } from 'element-plus';
<header>
<div class="left">
<img src="../assets/logo.gif">
<span>七牛云 OSS 图床</span>
<span>纯静态 OSS 图床</span>
</div>
<span class="right">
<ConfigPanel />
Expand Down
24 changes: 14 additions & 10 deletions packages/client/src/components/ImageUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import { UploadFilled } from '@element-plus/icons-vue'
import { computed, ref, watch } from 'vue';
import { ElMessage, type UploadInstance, type UploadProps, type UploadUserFile } from 'element-plus'
import { uploadFile } from '../utils/qiniu'
import { uploadFile as qiniuUploadFile } from '../utils/qiniu'
import { uploadFile as upyunUploadFile } from '@/utils/upyun';
import { useFocus } from '@vueuse/core';
import { useConfigStore, useImageStore } from '@/store'
import { storeToRefs } from 'pinia';
Expand All @@ -11,7 +12,7 @@ import { compressImage } from '@/utils/file';
const imageStore = useImageStore()
const configStore = useConfigStore()
const { qiniu } = storeToRefs(configStore)
const { qiniu, upyun, config } = storeToRefs(configStore)
const uploadRef = ref<UploadInstance>()
const files = ref<UploadUserFile[]>([])
Expand Down Expand Up @@ -42,14 +43,17 @@ watch(files, async () => {
fileRaw = await compressImage(file.raw) as any
}
uploadFile(fileRaw, qiniu.value, {
process(percent) {
file.percentage = percent
if (percent === 100) {
file.status = 'success'
}
},
}).then(v => {
const p = config.value?.type === 'upyun' ?
upyunUploadFile(fileRaw, upyun.value) :
qiniuUploadFile(fileRaw, qiniu.value, {
process(percent) {
file.percentage = percent
if (percent === 100) {
file.status = 'success'
}
},
})
p.then(v => {
file.status = 'success'
// 列表里移除已经删除的
files.value.splice(files.value.findIndex(f => f === file), 1)
Expand Down
10 changes: 5 additions & 5 deletions packages/client/src/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ export function useUploadConfig() {

export function useIsExpired() {
const store = useConfigStore()
const { qiniu } = storeToRefs(store)
const isExpired = ref(qiniu.value.date <= Date.now())
const { config } = storeToRefs(store)
const isExpired = ref(config.value.date <= Date.now())

useIntervalFn(() => {
isExpired.value = qiniu.value.date <= Date.now()
isExpired.value = config.value.date <= Date.now()
if (isExpired.value) {
// 过期了,尝试自动取默认的token
localStorage.removeItem('qiniu-token')
store.parseQiniuToken()
localStorage.removeItem('upload-token')
store.parseToken()
}
}, 500)
return isExpired
Expand Down
41 changes: 35 additions & 6 deletions packages/client/src/store/modules/configStore.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import { ElMessage } from 'element-plus'
import { defineStore } from 'pinia'

export interface QiNiuConfig {
interface BaseConfig {
token: string
scope: string
prefix: string
domain: string
compressImage?: any
date: number
compressImage?: any
config?: Record<string, any>
}
export interface QiNiuConfig extends BaseConfig {
config: {
useCdnDomain: boolean
}
}

export interface UPYunConfig extends BaseConfig {
bucket: string
uriPrefix: string
}

const configStore = defineStore('configStore', {
state: () => ({
qiniu: {
Expand All @@ -26,16 +34,32 @@ const configStore = defineStore('configStore', {
useCdnDomain: true,
},
} as QiNiuConfig,
upyun: {
bucket: 'serviceName',
prefix: 'image',
scope: 'default',
token: '',
date: 0,
domain: '',
uriPrefix: '',
} as UPYunConfig,
parsedToken: {} as any,
warningTimer: null as any,
}),
actions: {
parseQiniuToken(token?: string) {
parseToken(token?: string) {
try {
// 兜底都取默认的token
const config = JSON.parse(atob(token || import.meta.env.VITE_APP_QINIU_TOKEN))
Object.assign(this.qiniu, config)
const config = JSON.parse(atob(token || import.meta.env.VITE_APP_UPLOAD_TOKEN))
this.parsedToken = config
if (config?.type === 'upyun') {
Object.assign(this.upyun, config)
}
else {
Object.assign(this.qiniu, config)
}
if (token) {
localStorage.setItem('qiniu-token', token)
localStorage.setItem('upload-token', token)
}
}
catch (err: any) {
Expand All @@ -49,6 +73,11 @@ const configStore = defineStore('configStore', {
}
},
},
getters: {
config(state) {
return state.parsedToken
},
},
})

export default configStore
Loading

0 comments on commit 381c7eb

Please sign in to comment.