diff --git a/README.md b/README.md index 1a32d42..ea3b024 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Markdown 文档自动即时渲染为微信图文,让你不再为微信文章 | 2 | GitHub 图床 | 配置 `Repo`、`Token` 参数 | [如何获取 GitHub token?](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) | | 3 | 阿里云 OSS | 配置 `AccessKey ID`、`AccessKey Secret`、`Bucket`、`Region` 等参数 | [如何使用阿里云 OSS?](https://help.aliyun.com/document_detail/31883.html) | | 4 | 腾讯云 COS | 配置 `SecretId`、`SecretKey`、`Bucket`、`Region` 等参数 | [如何使用腾讯云 COS?](https://cloud.tencent.com/document/product/436/38484) | +| 5 | 七牛云 Kodo | 配置 `AccessKey`、`SecretKey`、`Bucket`、`Domain`、`Region` 等参数 | [如何使用七牛云 Kodo?](https://cloud.tencent.com/document/product/436/38484) | ![select-and-change-color-theme](./public/assets/images/select-and-change-color-theme.gif) diff --git a/package.json b/package.json index bc2a991..52f4db7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vue-md", "author": "doocs", - "version": "1.4.1", + "version": "1.4.2", "private": true, "homepage": "https://doocs.gitee.io/md", "scripts": { @@ -17,12 +17,14 @@ "codemirror": "^5.50.2", "core-js": "^3.4.4", "cos-js-sdk-v5": "^0.5.27", + "crypto-js": "^4.0.0", "element-ui": "^2.13.0", "jquery": "^3.4.1", "juice": "^6.0.0", "marked": "^0.8.0", "prettier": "^2.0.5", "prettify": "^0.1.7", + "qiniu-js": "^3.1.2", "uuid": "^8.3.0", "vue": "^2.6.10", "vue-router": "^3.1.3", diff --git a/src/api/file.js b/src/api/file.js index ba74498..a9196f2 100644 --- a/src/api/file.js +++ b/src/api/file.js @@ -1,8 +1,11 @@ import fetch from "./fetch"; +import CryptoJS from "crypto-js"; import OSS from "ali-oss"; import COS from "cos-js-sdk-v5"; import Buffer from "buffer-from"; import { v4 as uuidv4 } from "uuid"; +import * as qiniu from "qiniu-js"; +import { utf16to8, base64encode, safe64 } from "../assets/scripts/tokenTools"; const defaultConfig = { username: "filess", @@ -28,6 +31,8 @@ function fileUpload(content, file) { return aliOSSFileUpload(content, file.name); case "txCOS": return txCOSFileUpload(file); + case "qiniu": + return qiniuUpload(file); case "github": default: return ghFileUpload(content, file.name); @@ -81,6 +86,14 @@ function getGitHubConfig() { ); } +function getQiniuToken(accessKey, secretKey, putPolicy) { + const policy = JSON.stringify(putPolicy); + const encoded = base64encode(utf16to8(policy)); + const hash = CryptoJS.HmacSHA1(encoded, secretKey); + const encodedSigned = hash.toString(CryptoJS.enc.Base64); + return accessKey + ":" + safe64(encodedSigned) + ":" + encoded; +} + async function ghFileUpload(content, filename) { const isDefault = localStorage.getItem("imgHost") !== "github"; const config = isDefault ? getDefaultConfig() : getGitHubConfig(); @@ -166,6 +179,44 @@ async function txCOSFileUpload(file) { }); } +async function qiniuUpload(file) { + const qiniuConfig = JSON.parse(localStorage.getItem("qiniuConfig")); + const putPolicy = { + scope: qiniuConfig.bucket, + deadline: Math.trunc(new Date().getTime() / 1000) + 3600, + }; + const token = getQiniuToken( + qiniuConfig.accessKey, + qiniuConfig.secretKey, + putPolicy + ); + const dir = qiniuConfig.path ? qiniuConfig.path + "/" : ""; + const dateFilename = + dir + + new Date().getTime() + + "-" + + uuidv4() + + "." + + file.name.split(".")[1]; + const config = { + region: qiniuConfig.region, + }; + const observable = qiniu.upload(file, dateFilename, token, {}, config); + return new Promise((resolve, reject) => { + observable.subscribe({ + next: (result) => { + console.log(result); + }, + error: (err) => { + reject(err.message); + }, + complete: (result) => { + resolve(qiniuConfig.domain + "/" + result.key); + }, + }); + }); +} + export default { fileUpload, }; diff --git a/src/assets/scripts/tokenTools.js b/src/assets/scripts/tokenTools.js new file mode 100644 index 0000000..7a30f33 --- /dev/null +++ b/src/assets/scripts/tokenTools.js @@ -0,0 +1,269 @@ +export function utf16to8(str) { + var out, i, len, c; + out = ""; + len = str.length; + for (i = 0; i < len; i++) { + c = str.charCodeAt(i); + if (c >= 0x0001 && c <= 0x007f) { + out += str.charAt(i); + } else if (c > 0x07ff) { + out += String.fromCharCode(0xe0 | ((c >> 12) & 0x0f)); + out += String.fromCharCode(0x80 | ((c >> 6) & 0x3f)); + out += String.fromCharCode(0x80 | ((c >> 0) & 0x3f)); + } else { + out += String.fromCharCode(0xc0 | ((c >> 6) & 0x1f)); + out += String.fromCharCode(0x80 | ((c >> 0) & 0x3f)); + } + } + return out; +} + +export function utf8to16(str) { + var out, i, len, c; + var char2, char3; + out = ""; + len = str.length; + i = 0; + while (i < len) { + c = str.charCodeAt(i++); + switch (c >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + // 0xxxxxxx + out += str.charAt(i - 1); + break; + case 12: + case 13: + // 110x xxxx 10xx xxxx + char2 = str.charCodeAt(i++); + out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f)); + break; + case 14: + // 1110 xxxx 10xx xxxx 10xx xxxx + char2 = str.charCodeAt(i++); + char3 = str.charCodeAt(i++); + out += String.fromCharCode( + ((c & 0x0f) << 12) | + ((char2 & 0x3f) << 6) | + ((char3 & 0x3f) << 0) + ); + break; + } + } + return out; +} + +var base64EncodeChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +var base64DecodeChars = new Array( + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 62, + -1, + -1, + -1, + 63, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + -1, + -1, + -1, + -1, + -1, + -1, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + -1, + -1, + -1, + -1, + -1 +); +export function base64encode(str) { + var out, i, len; + var c1, c2, c3; + len = str.length; + i = 0; + out = ""; + while (i < len) { + c1 = str.charCodeAt(i++) & 0xff; + if (i == len) { + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt((c1 & 0x3) << 4); + out += "=="; + break; + } + c2 = str.charCodeAt(i++); + if (i == len) { + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt( + ((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4) + ); + out += base64EncodeChars.charAt((c2 & 0xf) << 2); + out += "="; + break; + } + c3 = str.charCodeAt(i++); + out += base64EncodeChars.charAt(c1 >> 2); + out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4)); + out += base64EncodeChars.charAt(((c2 & 0xf) << 2) | ((c3 & 0xc0) >> 6)); + out += base64EncodeChars.charAt(c3 & 0x3f); + } + return out; +} + +export function base64decode(str) { + var c1, c2, c3, c4; + var i, len, out; + len = str.length; + i = 0; + out = ""; + while (i < len) { + /* c1 */ + do { + c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; + } while (i < len && c1 == -1); + if (c1 == -1) break; + /* c2 */ + do { + c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; + } while (i < len && c2 == -1); + if (c2 == -1) break; + out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); + /* c3 */ + do { + c3 = str.charCodeAt(i++) & 0xff; + if (c3 == 61) return out; + c3 = base64DecodeChars[c3]; + } while (i < len && c3 == -1); + if (c3 == -1) break; + out += String.fromCharCode(((c2 & 0xf) << 4) | ((c3 & 0x3c) >> 2)); + /* c4 */ + do { + c4 = str.charCodeAt(i++) & 0xff; + if (c4 == 61) return out; + c4 = base64DecodeChars[c4]; + } while (i < len && c4 == -1); + if (c4 == -1) break; + out += String.fromCharCode(((c3 & 0x03) << 6) | c4); + } + return out; +} + +export function safe64(base64) { + base64 = base64.replace(/\+/g, "-"); + base64 = base64.replace(/\//g, "_"); + return base64; +} diff --git a/src/components/CodemirrorEditor/uploadImgDialog.vue b/src/components/CodemirrorEditor/uploadImgDialog.vue index d57ee6c..375f42c 100644 --- a/src/components/CodemirrorEditor/uploadImgDialog.vue +++ b/src/components/CodemirrorEditor/uploadImgDialog.vue @@ -201,6 +201,66 @@ + + + + + + + + + + + + + + + + + + + + 如何使用七牛云 Kodo? + + + 保存配置 + + + @@ -238,6 +298,13 @@ export default { path: "", cdnHost: "", }, + formQiniu: { + accessKey: "", + secretKey: "", + bucket: "", + domain: "", + region: "", + }, options: [ { value: "default", @@ -255,6 +322,10 @@ export default { value: "txCOS", label: "腾讯云", }, + { + value: "qiniu", + label: "七牛云", + }, ], imgHost: "default", uploadingImg: false, @@ -353,6 +424,30 @@ export default { }); }, + saveQiniuConfiguration() { + if ( + !( + this.formQiniu.accessKey && + this.formQiniu.secretKey && + this.formQiniu.bucket && + this.formQiniu.domain && + this.formQiniu.region + ) + ) { + this.$message({ + showClose: true, + message: `七牛云 Kodo 参数配置不全`, + type: "error", + }); + return; + } + localStorage.setItem("qiniuConfig", JSON.stringify(this.formQiniu)); + this.$message({ + message: "保存成功", + type: "success", + }); + }, + // 图片上传前的处理 beforeUpload(file) { if (!this.validateConfig()) {