管理员上传文件到存储桶功能基本完成;可以前端计算文件SHA1;添加腾讯云生成SecretId、SecretKey文档
162
bookshelfplus-frontend/public/assets/lib/crypto-js/sha1.js
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 本代码来自: https://bbs.csdn.net/topics/391839640 评论区,有修改
|
||||
*/
|
||||
/*
|
||||
* Crypto-JS v2.5.1
|
||||
* http://code.google.com/p/crypto-js/
|
||||
* (c) 2009-2011 by Jeff Mott. All rights reserved.
|
||||
* http://code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
(typeof Crypto=="undefined"||!Crypto.util)&&function(){var e=self.Crypto={},g=e.util={rotl:function(a,b){return a<<b|a>>>32-b},rotr:function(a,b){return a<<32-b|a>>>b},endian:function(a){if(a.constructor==Number)return g.rotl(a,8)&16711935|g.rotl(a,24)&4278255360;for(var b=0;b<a.length;b++)a[b]=g.endian(a[b]);return a},randomBytes:function(a){for(var b=[];a>0;a--)b.push(Math.floor(Math.random()*256));return b},bytesToWords:function(a){for(var b=[],c=0,d=0;c<a.length;c++,d+=8)b[d>>>5]|=a[c]<<24-
|
||||
d%32;return b},wordsToBytes:function(a){for(var b=[],c=0;c<a.length*32;c+=8)b.push(a[c>>>5]>>>24-c%32&255);return b},bytesToHex:function(a){for(var b=[],c=0;c<a.length;c++)b.push((a[c]>>>4).toString(16)),b.push((a[c]&15).toString(16));return b.join("")},hexToBytes:function(a){for(var b=[],c=0;c<a.length;c+=2)b.push(parseInt(a.substr(c,2),16));return b},bytesToBase64:function(a){if(typeof btoa=="function")return btoa(f.bytesToString(a));for(var b=[],c=0;c<a.length;c+=3)for(var d=a[c]<<16|a[c+1]<<8|
|
||||
a[c+2],e=0;e<4;e++)c*8+e*6<=a.length*8?b.push("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d>>>6*(3-e)&63)):b.push("=");return b.join("")},base64ToBytes:function(a){if(typeof atob=="function")return f.stringToBytes(atob(a));for(var a=a.replace(/[^A-Z0-9+\/]/ig,""),b=[],c=0,d=0;c<a.length;d=++c%4)d!=0&&b.push(("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(a.charAt(c-1))&Math.pow(2,-2*d+8)-1)<<d*2|"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(a.charAt(c))>>>
|
||||
6-d*2);return b}},e=e.charenc={};e.UTF8={stringToBytes:function(a){return f.stringToBytes(unescape(encodeURIComponent(a)))},bytesToString:function(a){return decodeURIComponent(escape(f.bytesToString(a)))}};var f=e.Binary={stringToBytes:function(a){for(var b=[],c=0;c<a.length;c++)b.push(a.charCodeAt(c)&255);return b},bytesToString:function(a){for(var b=[],c=0;c<a.length;c++)b.push(String.fromCharCode(a[c]));return b.join("")}}}();
|
||||
|
||||
/*
|
||||
* sha1File v1.0.1
|
||||
* https://github.com/dwsVad/sha1File
|
||||
* (c) 2014 by Protsenko Vadim. All rights reserved.
|
||||
* https://github.com/dwsVad/sha1File/blob/master/LICENSE
|
||||
*/
|
||||
async function sha1File(settings, progressingCallback)
|
||||
{
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
var hash = [1732584193, -271733879, -1732584194, 271733878, -1009589776];
|
||||
var buffer = 1024 * 16 * 64;
|
||||
var sha1 = function (block, hash)
|
||||
{
|
||||
var words = [];
|
||||
var count_parts = 16;
|
||||
var h0 = hash[0],
|
||||
h1 = hash[1],
|
||||
h2 = hash[2],
|
||||
h3 = hash[3],
|
||||
h4 = hash[4];
|
||||
for(var i = 0; i < block.length; i += count_parts)
|
||||
{
|
||||
var th0 = h0,
|
||||
th1 = h1,
|
||||
th2 = h2,
|
||||
th3 = h3,
|
||||
th4 = h4;
|
||||
for(var j = 0; j < 80; j++)
|
||||
{
|
||||
if(j < count_parts)
|
||||
words[j] = block[i + j] | 0;
|
||||
else
|
||||
{
|
||||
var n = words[j - 3] ^ words[j - 8] ^ words[j - 14] ^ words[j - count_parts];
|
||||
words[j] = (n << 1) | (n >>> 31);
|
||||
}
|
||||
var f,k;
|
||||
if(j < 20)
|
||||
{
|
||||
f = (h1 & h2 | ~h1 & h3);
|
||||
k = 1518500249;
|
||||
}
|
||||
else if(j < 40)
|
||||
{
|
||||
f = (h1 ^ h2 ^ h3);
|
||||
k = 1859775393;
|
||||
}
|
||||
else if(j < 60)
|
||||
{
|
||||
f = (h1 & h2 | h1 & h3 | h2 & h3);
|
||||
k = -1894007588;
|
||||
}
|
||||
else
|
||||
{
|
||||
f = (h1 ^ h2 ^ h3);
|
||||
k = -899497514;
|
||||
}
|
||||
|
||||
var t = ((h0 << 5) | (h0 >>> 27)) +h4 + (words[j] >>> 0) + f + k;
|
||||
h4 = h3;
|
||||
h3 = h2;
|
||||
h2 = (h1 << 30) | (h1 >>> 2);
|
||||
h1 = h0;
|
||||
h0 = t;
|
||||
}
|
||||
h0 = (h0 + th0) | 0;
|
||||
h1 = (h1 + th1) | 0;
|
||||
h2 = (h2 + th2) | 0;
|
||||
h3 = (h3 + th3) | 0;
|
||||
h4 = (h4 + th4) | 0;
|
||||
}
|
||||
return [h0, h1, h2, h3, h4];
|
||||
}
|
||||
|
||||
var run = function(file,inStart,inEnd)
|
||||
{
|
||||
var end = Math.min(inEnd, file.size);
|
||||
var start = inStart;
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function()
|
||||
{
|
||||
file.sha1_progress = (end * 100 / file.size);
|
||||
var event = event || window.event;
|
||||
var result = event.result || event.target.result
|
||||
var block = Crypto.util.bytesToWords( new Uint8Array(result));
|
||||
|
||||
if (end === file.size)
|
||||
{
|
||||
var bTotal, bLeft, bTotalH, bTotalL;
|
||||
bTotal = file.size * 8;
|
||||
bLeft = (end - start) * 8;
|
||||
|
||||
bTotalH = Math.floor(bTotal / 0x100000000);
|
||||
bTotalL = bTotal & 0xFFFFFFFF;
|
||||
|
||||
// Padding
|
||||
block[bLeft >>> 5] |= 0x80 << (24 - bLeft % 32);
|
||||
block[((bLeft + 64 >>> 9) << 4) + 14] = bTotalH;
|
||||
block[((bLeft + 64 >>> 9) << 4) + 15] = bTotalL;
|
||||
|
||||
hash = sha1(block, hash);
|
||||
file.sha1_hash = Crypto.util.bytesToHex(Crypto.util.wordsToBytes(hash));
|
||||
// console.log(file.sha1_hash);
|
||||
resolve(file.sha1_hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
hash = sha1(block, hash);
|
||||
start += buffer;
|
||||
end += buffer;
|
||||
run(file,start,end);
|
||||
}
|
||||
progressingCallback(file);
|
||||
// console.log(file.sha1_progress);
|
||||
}
|
||||
var blob = file.slice(start, end);
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}
|
||||
|
||||
var checkApi = function()
|
||||
{
|
||||
if((typeof File == 'undefined'))
|
||||
return false;
|
||||
|
||||
if (!File.prototype.slice) {
|
||||
if(File.prototype.webkitSlice)
|
||||
File.prototype.slice = File.prototype.webkitSlice;
|
||||
else if(File.prototype.mozSlice)
|
||||
File.prototype.slice = File.prototype.mozSlice;
|
||||
}
|
||||
|
||||
if (!window.File || !window.FileReader || !window.FileList || !window.Blob || !File.prototype.slice)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(checkApi())
|
||||
{
|
||||
run(settings,0,buffer);
|
||||
}
|
||||
else
|
||||
// return false;
|
||||
reject("File API is not supported");
|
||||
});
|
||||
return await promise;
|
||||
}
|
@@ -30,6 +30,29 @@
|
||||
background-color: grey;
|
||||
color: #b5b5b5;
|
||||
}
|
||||
|
||||
.process-bar {
|
||||
background-color: #b5b5b5;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.process-bar-inner {
|
||||
background-color: #39a705;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.file-info-paragraph>span {
|
||||
background-color: lightpink;
|
||||
color: darkred;
|
||||
padding: 1px 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
<a href="<%= pageUrl %>../">返回上一级</a>
|
||||
@@ -44,36 +67,69 @@
|
||||
<!-- <input type="file" id="input" multiple onchange="handleFiles(this.files)"> -->
|
||||
<input type="file" id="fileSelector" style="display: none;">
|
||||
<p>
|
||||
建议配合 Python 脚本使用,上传文件更方便
|
||||
仅支持 Chrome 内核浏览器
|
||||
</p>
|
||||
<div id="file-detail-container" style="display: none;">
|
||||
<p style="text-align: left;" class="file-info-paragraph">
|
||||
文件名: <span id="file-name"></span><br>
|
||||
文件扩展名:<span id="file-ext"></span><br>
|
||||
文件名(不含扩展名):<span id="file-name-no-ext"></span><br>
|
||||
文件大小: <span id="file-size"></span><br>
|
||||
文件SHA1: <span id="file-sha1"></span><br>
|
||||
</p>
|
||||
<p>计算文件哈希</p>
|
||||
<div id="processBar1" class="process-bar" style="">
|
||||
<div id="processBar1Inner" class="process-bar-inner"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="beginUpload" disabled="true">开始上传</button>
|
||||
|
||||
<script src="/assets/lib/crypto-js/sha1.js"></script>
|
||||
<script>
|
||||
var file = null;
|
||||
var fileInfo = {
|
||||
fileName: "",
|
||||
fileSize: 0,
|
||||
fileType: "",
|
||||
fileSha1: "",
|
||||
fileExt: "",
|
||||
fileNameWithoutExt: "",
|
||||
};
|
||||
|
||||
const inputElement = document.getElementById("fileSelector");
|
||||
inputElement.addEventListener("change", fileSelectorHandleFiles, false);
|
||||
|
||||
const beginUpload = document.getElementById("beginUpload");
|
||||
beginUpload.addEventListener("click", upload);
|
||||
|
||||
//##############################################
|
||||
// 选择文件
|
||||
//##############################################
|
||||
function fileSelectorHandleFiles() {
|
||||
const fileList = this.files; /* now you can work with the file list */
|
||||
// console.log(fileList);
|
||||
handleFiles(fileList);
|
||||
}
|
||||
function handleFiles(fileList) {
|
||||
async function handleFiles(fileList) {
|
||||
updateUI({ isFileEmpty: true });
|
||||
if (fileList.length === 0) {
|
||||
// 还未选择文件
|
||||
file = null;
|
||||
dropbox.classList.remove("green");
|
||||
document.getElementById("fileSize").innerHTML = "0";
|
||||
return;
|
||||
}
|
||||
if (fileList.length > 1) {
|
||||
alert("一次只能选择1个文件!");
|
||||
this.outerHTML = this.outerHTML; // 清除选择的文件
|
||||
file = null;
|
||||
dropbox.classList.remove("green");
|
||||
document.getElementById("fileSize").innerHTML = "0";
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户选择的文件
|
||||
file = fileList[0];
|
||||
console.log(file);
|
||||
|
||||
// // 判断用户选择的文件是否为文件夹
|
||||
// if (await isFolder(file)) {
|
||||
// alert("不支持文件夹,请选择文件!");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 输出所选文件的总大小
|
||||
let nBytes = 0;
|
||||
@@ -90,10 +146,38 @@
|
||||
// document.getElementById("fileNum").innerHTML = fileList.length;
|
||||
document.getElementById("fileSize").innerHTML = sOutput;
|
||||
|
||||
dropbox.classList.add("green");
|
||||
updateUI({ isFileEmpty: false });
|
||||
|
||||
// 记录文件信息
|
||||
fileInfo.fileName = file.name;
|
||||
fileInfo.fileSize = file.size;
|
||||
fileInfo.fileType = file.type;
|
||||
// 获取文件扩展名:首先按照 . 拆分,然后删掉第一个元素(考虑无扩展名文件),再取出最后一个元素
|
||||
let fileNameSplit = file.name.split(".");
|
||||
fileNameSplit.shift();
|
||||
fileInfo.fileExt = fileNameSplit.pop() || "";
|
||||
// 获取文件名(不包含扩展名)
|
||||
fileInfo.fileNameWithoutExt = file.name.substr(0, (file.name.length + file.name.lastIndexOf('.')) % (file.name.length));
|
||||
|
||||
// 更新UI
|
||||
document.getElementById("file-name").innerHTML = fileInfo.fileName;
|
||||
document.getElementById("file-ext").innerHTML = fileInfo.fileExt;
|
||||
document.getElementById("file-name-no-ext").innerHTML = fileInfo.fileNameWithoutExt;
|
||||
document.getElementById("file-size").innerHTML = sOutput;
|
||||
|
||||
// 计算文件哈希
|
||||
let sha1 = await sha1File(file, (file) => {
|
||||
document.getElementById("processBar1Inner").style.width = `${file.sha1_progress}%`;
|
||||
// console.log("计算文件哈希 进度", file.sha1_progress);
|
||||
});
|
||||
fileInfo.fileSha1 = sha1;
|
||||
document.getElementById("file-sha1").innerHTML = fileInfo.fileSha1;
|
||||
|
||||
// 完成
|
||||
console.log(fileInfo);
|
||||
}
|
||||
|
||||
// 通过 click() 方法使用隐藏的 file input 元素
|
||||
// dropbox 点击时触发 file 的 click 事件
|
||||
const fileSelect = document.getElementById("dropbox");
|
||||
fileSelect.addEventListener("click", function (e) {
|
||||
if (inputElement) {
|
||||
@@ -101,7 +185,10 @@
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
||||
//##############################################
|
||||
// 使用拖放来选择文件
|
||||
//##############################################
|
||||
let dropbox = document.getElementById("dropbox");
|
||||
dropbox.addEventListener("dragenter", dragenter, false);
|
||||
dropbox.addEventListener("dragleave", dragleave, false);
|
||||
@@ -135,9 +222,107 @@
|
||||
var dt = e.dataTransfer;
|
||||
var files = dt.files;
|
||||
|
||||
console.log("dt", dt.items);
|
||||
console.log("dt", dt.items[0]);
|
||||
console.log("dt", dt.items[0].webkitGetAsEntry());
|
||||
|
||||
updateUI({ isFileEmpty: true });
|
||||
if (dt.items.length > 1) {
|
||||
alert("一次只能选择1个文件!");
|
||||
return;
|
||||
}
|
||||
if (dt.items[0].webkitGetAsEntry().isDirectory) {
|
||||
alert("不支持文件夹,请选择文件!");
|
||||
return;
|
||||
}
|
||||
handleFiles(files);
|
||||
}
|
||||
|
||||
|
||||
// //##############################################
|
||||
// // 判断拖入的文件是否是文件夹 (非 Chrome 内核浏览器逻辑)
|
||||
// // 读取文件的第一个字符,如果能够读取成功,那么就是文件,否则就是文件夹
|
||||
// // refer: https://segmentfault.com/a/1190000013298317
|
||||
// //##############################################
|
||||
// async function isFolder(file) {
|
||||
// return await new Promise((resolve, reject) => {
|
||||
// try {
|
||||
// var fileReader = new FileReader();
|
||||
|
||||
// fileReader.addEventListener('load', function (e) {
|
||||
// console.log(e, 'load');
|
||||
// resolve(false);
|
||||
// }, false);
|
||||
|
||||
// fileReader.addEventListener('error', function (e) {
|
||||
// console.log(e, 'error,不可以上传文件夹');
|
||||
// resolve(true);
|
||||
// }, false);
|
||||
|
||||
// fileReader.readAsDataURL(file.slice(0, 3));
|
||||
// } catch (e) {
|
||||
// console.log(e, 'catch error,不可以上传文件夹');
|
||||
// resolve(true);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
//##############################################
|
||||
// 更新界面显示
|
||||
//##############################################
|
||||
function updateUI({ isFileEmpty }) {
|
||||
if (isFileEmpty) {
|
||||
// 未选择文件
|
||||
this.outerHTML = this.outerHTML; // 清除选择的文件
|
||||
file = null;
|
||||
fileInfo = {};
|
||||
document.getElementById("fileSize").innerHTML = "0";
|
||||
document.getElementById("processBar1Inner").style.width = 0;
|
||||
document.getElementById("file-name").innerHTML = "";
|
||||
document.getElementById("file-ext").innerHTML = "";
|
||||
document.getElementById("file-name-no-ext").innerHTML = "";
|
||||
document.getElementById("file-size").innerHTML = "";
|
||||
document.getElementById("file-sha1").innerHTML = "";
|
||||
dropbox.classList.remove("green");
|
||||
beginUpload.setAttribute("disabled", "true");
|
||||
document.getElementById("file-detail-container").style.display = "none";
|
||||
} else {
|
||||
// 已选择文件
|
||||
dropbox.classList.add("green");
|
||||
beginUpload.removeAttribute("disabled");
|
||||
document.getElementById("file-detail-container").style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
//##############################################
|
||||
// 获取预授权URL
|
||||
//##############################################
|
||||
function upload() {
|
||||
if (!file) {
|
||||
alert("您还未选择文件!");
|
||||
return;
|
||||
}
|
||||
// 获取预授权URL
|
||||
postRequest("/file/cos/put", { token: localStorageUtils.getToken(), fileName: "test.a", expireMinute: 30 })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
uploadFile(file, data);
|
||||
} else {
|
||||
alert(`出错啦!${data.errMsg} (错误码: ${data.errCode}) `);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
alert("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
|
||||
//##############################################
|
||||
//##############################################
|
||||
//传入预授权 URL ,将文件上传到这个地址
|
||||
function uploadFile(file, preSignedUrl) {
|
||||
// refer: https://cloud.tencent.com/document/product/436/35651
|
||||
|
@@ -1,18 +1,11 @@
|
||||
package plus.bookshelf;
|
||||
|
||||
import com.qcloud.cos.http.HttpMethodName;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import plus.bookshelf.Common.FileManager.QCloudCosUtils;
|
||||
import plus.bookshelf.Config.QCloudCosConfig;
|
||||
|
||||
/**
|
||||
* Hello world!
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"plus.bookshelf"})
|
||||
@RestController
|
||||
@MapperScan("plus.bookshelf.Dao.Mapper")
|
||||
@@ -24,19 +17,12 @@ public class App {
|
||||
|
||||
// 启动SpringBoot项目
|
||||
SpringApplication.run(App.class, args);
|
||||
|
||||
System.out.println("backend service started successfully.");
|
||||
}
|
||||
|
||||
@RequestMapping("/")
|
||||
public String Home() {
|
||||
return "首页";
|
||||
}
|
||||
|
||||
@Autowired
|
||||
QCloudCosConfig qCloudCosConfig;
|
||||
|
||||
@RequestMapping("/cos")
|
||||
public String cos() {
|
||||
QCloudCosUtils QCloudCosUtils = new QCloudCosUtils(qCloudCosConfig);
|
||||
return QCloudCosUtils.getUrl("user-login-token", HttpMethodName.POST, "mydemo.jpg", 5);
|
||||
return "backend service is running.";
|
||||
}
|
||||
}
|
||||
|
23
bookshelfplus/src/main/java/plus/bookshelf/AppListener.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package plus.bookshelf;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import plus.bookshelf.Common.FileManager.QCloudCosUtils;
|
||||
|
||||
@Component
|
||||
public class AppListener implements CommandLineRunner, DisposableBean {
|
||||
//应用启动成功后的回调
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
System.out.println("prepare to start ...");
|
||||
}
|
||||
|
||||
//应用启动关闭前的回调
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
System.out.println("prepare to close ...");
|
||||
QCloudCosUtils.destoryInstance();
|
||||
System.out.println("close success ...");
|
||||
}
|
||||
}
|
@@ -10,21 +10,34 @@ import com.qcloud.cos.http.HttpProtocol;
|
||||
import com.qcloud.cos.region.Region;
|
||||
import plus.bookshelf.Config.QCloudCosConfig;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class QCloudCosUtils {
|
||||
|
||||
// 配置信息
|
||||
QCloudCosConfig qCloudCosConfig;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param qCloudCosConfig
|
||||
*/
|
||||
public QCloudCosUtils(QCloudCosConfig qCloudCosConfig) {
|
||||
this.qCloudCosConfig = qCloudCosConfig;
|
||||
}
|
||||
|
||||
private static COSClient _cosClient = null;
|
||||
|
||||
// refer: https://cloud.tencent.com/document/product/436/35217#.E5.88.9B.E5.BB.BA-cosclient
|
||||
// 创建 COSClient 实例,这个实例用来后续调用请求
|
||||
COSClient createCOSClient() {
|
||||
if (_cosClient != null) {
|
||||
return _cosClient;
|
||||
}
|
||||
// 设置用户身份信息。
|
||||
// SECRETID 和 SECRETKEY 请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理
|
||||
String secretId = qCloudCosConfig.getAccessKey();
|
||||
@@ -54,13 +67,19 @@ public class QCloudCosUtils {
|
||||
// clientConfig.setHttpProxyIp("httpProxyIp");
|
||||
// clientConfig.setHttpProxyPort(80);
|
||||
|
||||
// 生成 cos 客户端。
|
||||
return new COSClient(cred, clientConfig);
|
||||
// 生成 cos 客户端
|
||||
_cosClient = new COSClient(cred, clientConfig);
|
||||
System.out.println("cosClient construct success.");
|
||||
return _cosClient;
|
||||
}
|
||||
|
||||
public String getUrl(String token, String objectKey) {
|
||||
// 如果不指定失效时间,默认为 30 分钟
|
||||
return getUrl(token, HttpMethodName.GET, objectKey, 30);
|
||||
public Boolean doesObjectExist(String objectKey) {
|
||||
COSClient cosClient = createCOSClient();
|
||||
// 存储桶的命名格式为 BucketName-APPID,此处填写的存储桶名称必须为此格式
|
||||
String bucketName = qCloudCosConfig.getBucketName();
|
||||
// 对象键(Key)是对象在存储桶中的唯一标识。详情请参见 [对象键](https://cloud.tencent.com/document/product/436/13324)
|
||||
String key = qCloudCosConfig.getKeyName() + objectKey;
|
||||
return cosClient.doesObjectExist(bucketName, key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,9 +93,10 @@ public class QCloudCosUtils {
|
||||
* @param expireMinute 过期时间
|
||||
* @return
|
||||
*/
|
||||
public String getUrl(String token, HttpMethodName httpMethodName, String objectKey, Integer expireMinute) {
|
||||
public String generatePresignedUrl(String token, HttpMethodName httpMethodName, String objectKey, Integer expireMinute) {
|
||||
// 调用 COS 接口之前必须保证本进程存在一个 COSClient 实例,如果没有则创建
|
||||
// 详细代码参见本页:简单操作 -> 创建 COSClient
|
||||
// COSClient cosClient = createCOSClient();
|
||||
COSClient cosClient = createCOSClient();
|
||||
|
||||
// 存储桶的命名格式为 BucketName-APPID,此处填写的存储桶名称必须为此格式
|
||||
@@ -92,24 +112,27 @@ public class QCloudCosUtils {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("poweredBy", "bookshelf.plus");
|
||||
params.put("userToken", token);
|
||||
|
||||
String downloadGUID = NanoIdUtils.randomNanoId();
|
||||
params.put("downloadGUID", downloadGUID); // 当次生成下载链接的全局唯一Id
|
||||
params.put("温馨提示", "您的每一次下载都会被详细记录,请不要试图绕过系统获取文件下载直链,这是违法行为,请自重!");
|
||||
|
||||
// 填写本次请求的头部,需与实际请求相同,能够防止用户篡改此签名的 HTTP 请求的头部
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
// headers.put("header1", "value1");
|
||||
|
||||
// 请求的 HTTP 方法,上传请求用 PUT,下载请求用 GET,删除请求用 DELETE
|
||||
HttpMethodName method = HttpMethodName.GET;
|
||||
HttpMethodName method = httpMethodName;
|
||||
|
||||
URL url = cosClient.generatePresignedUrl(bucketName, key, expirationDate, method, headers, params);
|
||||
System.out.println(url.toString());
|
||||
|
||||
// [TODO] 确认本进程不再使用 cosClient 实例之后,关闭之
|
||||
cosClient.shutdown();
|
||||
// System.out.println(url.toString());
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
public static void destoryInstance() {
|
||||
if (_cosClient != null) {
|
||||
// 确认本进程不再使用 cosClient 实例之后,关闭之
|
||||
_cosClient.shutdown();
|
||||
System.out.println("cosClient destory success.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
package plus.bookshelf.Controller.Controller;
|
||||
|
||||
import com.qcloud.cos.http.HttpMethodName;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import plus.bookshelf.Common.Error.BusinessErrorCode;
|
||||
import plus.bookshelf.Common.Error.BusinessException;
|
||||
import plus.bookshelf.Common.FileManager.QCloudCosUtils;
|
||||
import plus.bookshelf.Common.Response.CommonReturnType;
|
||||
import plus.bookshelf.Config.QCloudCosConfig;
|
||||
import plus.bookshelf.Service.Impl.UserServiceImpl;
|
||||
import plus.bookshelf.Service.Model.UserModel;
|
||||
|
||||
@Api(tags = "文件管理")
|
||||
@Controller("file")
|
||||
@RequestMapping("/file")
|
||||
public class FileController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
QCloudCosConfig qCloudCosConfig;
|
||||
|
||||
@Autowired
|
||||
UserServiceImpl userService;
|
||||
|
||||
/**
|
||||
* 创建文件操作预授权URL
|
||||
*
|
||||
* @param httpMethod 请求的 HTTP 方法,上传请求用 PUT,下载请求用 GET,删除请求用 DELETE
|
||||
* @param token 当前登录用户的 token
|
||||
* @param fileName 文件名
|
||||
* @param expireMinute 过期时间(分钟)
|
||||
* @return
|
||||
* @throws BusinessException
|
||||
*/
|
||||
@ApiOperation(value = "创建腾讯云 COS 预授权 URL", notes = "")
|
||||
@RequestMapping(value = "/cos/{httpMethod}", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
|
||||
@ResponseBody
|
||||
public CommonReturnType cos(@PathVariable(value = "httpMethod") String httpMethod,
|
||||
@RequestParam(value = "token") String token,
|
||||
@RequestParam(value = "fileName") String fileName,
|
||||
@RequestParam(value = "expireMinute", required = false) Integer expireMinute) throws BusinessException {
|
||||
if (expireMinute == null) {
|
||||
expireMinute = 30;
|
||||
} else if (expireMinute > 60) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "expireMinute 参数不能大于 60");
|
||||
} else if (expireMinute < 1) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "expireMinute 参数不能小于 1");
|
||||
}
|
||||
|
||||
// 已经在 getUserByToken 方法中判断了 token 为空、不合法;用户不存在情况,此处无需再判断
|
||||
UserModel userModel = userService.getUserByToken(redisTemplate, token);
|
||||
|
||||
// 判断httpMethod 是否合法
|
||||
HttpMethodName httpMethodName;
|
||||
try {
|
||||
httpMethodName = HttpMethodName.valueOf(httpMethod.toUpperCase());
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "httpMethod 参数不合法");
|
||||
}
|
||||
|
||||
QCloudCosUtils qCloudCosUtils = new QCloudCosUtils(qCloudCosConfig);
|
||||
|
||||
// 判断对象是否存在
|
||||
Boolean isExist = qCloudCosUtils.doesObjectExist(fileName);
|
||||
switch (httpMethodName) {
|
||||
case PUT:
|
||||
if (isExist) throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件已存在");
|
||||
break;
|
||||
case GET:
|
||||
case DELETE:
|
||||
if (!isExist) throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件不存在");
|
||||
break;
|
||||
default:
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "httpMethod 参数暂不支持");
|
||||
}
|
||||
|
||||
String url = qCloudCosUtils.generatePresignedUrl(token, httpMethodName, fileName, 30);
|
||||
|
||||
return CommonReturnType.create(url);
|
||||
}
|
||||
}
|
@@ -0,0 +1,236 @@
|
||||
package plus.bookshelf.Dao.DO;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class FileOperationDO {
|
||||
/**
|
||||
*
|
||||
* This field was generated by MyBatis Generator.
|
||||
* This field corresponds to the database column file_operation_log.id
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
*
|
||||
* This field was generated by MyBatis Generator.
|
||||
* This field corresponds to the database column file_operation_log.user_id
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
private Integer userId;
|
||||
|
||||
/**
|
||||
*
|
||||
* This field was generated by MyBatis Generator.
|
||||
* This field corresponds to the database column file_operation_log.time
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
private Date time;
|
||||
|
||||
/**
|
||||
*
|
||||
* This field was generated by MyBatis Generator.
|
||||
* This field corresponds to the database column file_operation_log.expire_minute
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
private String expireMinute;
|
||||
|
||||
/**
|
||||
*
|
||||
* This field was generated by MyBatis Generator.
|
||||
* This field corresponds to the database column file_operation_log.method
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
private String method;
|
||||
|
||||
/**
|
||||
*
|
||||
* This field was generated by MyBatis Generator.
|
||||
* This field corresponds to the database column file_operation_log.file_path
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
private String filePath;
|
||||
|
||||
/**
|
||||
*
|
||||
* This field was generated by MyBatis Generator.
|
||||
* This field corresponds to the database column file_operation_log.url_guid
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
private String urlGuid;
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method returns the value of the database column file_operation_log.id
|
||||
*
|
||||
* @return the value of file_operation_log.id
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method sets the value of the database column file_operation_log.id
|
||||
*
|
||||
* @param id the value for file_operation_log.id
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method returns the value of the database column file_operation_log.user_id
|
||||
*
|
||||
* @return the value of file_operation_log.user_id
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public Integer getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method sets the value of the database column file_operation_log.user_id
|
||||
*
|
||||
* @param userId the value for file_operation_log.user_id
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setUserId(Integer userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method returns the value of the database column file_operation_log.time
|
||||
*
|
||||
* @return the value of file_operation_log.time
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public Date getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method sets the value of the database column file_operation_log.time
|
||||
*
|
||||
* @param time the value for file_operation_log.time
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setTime(Date time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method returns the value of the database column file_operation_log.expire_minute
|
||||
*
|
||||
* @return the value of file_operation_log.expire_minute
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public String getExpireMinute() {
|
||||
return expireMinute;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method sets the value of the database column file_operation_log.expire_minute
|
||||
*
|
||||
* @param expireMinute the value for file_operation_log.expire_minute
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setExpireMinute(String expireMinute) {
|
||||
this.expireMinute = expireMinute == null ? null : expireMinute.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method returns the value of the database column file_operation_log.method
|
||||
*
|
||||
* @return the value of file_operation_log.method
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method sets the value of the database column file_operation_log.method
|
||||
*
|
||||
* @param method the value for file_operation_log.method
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setMethod(String method) {
|
||||
this.method = method == null ? null : method.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method returns the value of the database column file_operation_log.file_path
|
||||
*
|
||||
* @return the value of file_operation_log.file_path
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method sets the value of the database column file_operation_log.file_path
|
||||
*
|
||||
* @param filePath the value for file_operation_log.file_path
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath == null ? null : filePath.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method returns the value of the database column file_operation_log.url_guid
|
||||
*
|
||||
* @return the value of file_operation_log.url_guid
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public String getUrlGuid() {
|
||||
return urlGuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method sets the value of the database column file_operation_log.url_guid
|
||||
*
|
||||
* @param urlGuid the value for file_operation_log.url_guid
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
public void setUrlGuid(String urlGuid) {
|
||||
this.urlGuid = urlGuid == null ? null : urlGuid.trim();
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
package plus.bookshelf.Dao.Mapper;
|
||||
|
||||
import plus.bookshelf.Dao.DO.FileOperationDO;
|
||||
|
||||
public interface FileOperationDOMapper {
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method corresponds to the database table file_operation_log
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
int deleteByPrimaryKey(Integer id);
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method corresponds to the database table file_operation_log
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
int insert(FileOperationDO record);
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method corresponds to the database table file_operation_log
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
int insertSelective(FileOperationDO record);
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method corresponds to the database table file_operation_log
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
FileOperationDO selectByPrimaryKey(Integer id);
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method corresponds to the database table file_operation_log
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
int updateByPrimaryKeySelective(FileOperationDO record);
|
||||
|
||||
/**
|
||||
* This method was generated by MyBatis Generator.
|
||||
* This method corresponds to the database table file_operation_log
|
||||
*
|
||||
* @mbg.generated
|
||||
*/
|
||||
int updateByPrimaryKey(FileOperationDO record);
|
||||
}
|
@@ -57,12 +57,16 @@ thirdparty.qq.redirecturi=
|
||||
|
||||
# 腾讯云对象存储
|
||||
# SecretId
|
||||
qcloud.cos.accessKey=AKIDaz80bw0nJYWEgqjLgkY4JHzPYQ2NSGn4
|
||||
qcloud.cos.accessKey=
|
||||
# SecretKey
|
||||
qcloud.cos.secretKey=1lEQKxJPFo66q54lCNGsDH4brqYzA5j6
|
||||
# 地域名
|
||||
qcloud.cos.regionName=ap-shanghai
|
||||
# 存储桶名称
|
||||
qcloud.cos.bucketName=bookshelfplus-ebooks-1302260381
|
||||
# 文件夹名称(可自定义)
|
||||
qcloud.cos.keyName=images
|
||||
qcloud.cos.secretKey=
|
||||
# 地域名
|
||||
qcloud.cos.regionName=
|
||||
# 存储桶名称
|
||||
qcloud.cos.bucketName=
|
||||
# 文件前缀(自定义)
|
||||
# 可以为空,为空表示直接将文件存在对象存储桶的根路径下
|
||||
# 不以 / 结尾,则为文件名前缀
|
||||
# 以 / 结尾,则为存入子文件夹中
|
||||
# 设置后建议不要修改,否则之前在系统中上传的文件将无法找到
|
||||
qcloud.cos.keyName=
|
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="plus.bookshelf.Dao.Mapper.FileOperationDOMapper">
|
||||
<resultMap id="BaseResultMap" type="plus.bookshelf.Dao.DO.FileOperationDO">
|
||||
<!--
|
||||
WARNING - @mbg.generated
|
||||
This element is automatically generated by MyBatis Generator, do not modify.
|
||||
-->
|
||||
<id column="id" jdbcType="INTEGER" property="id" />
|
||||
<result column="user_id" jdbcType="INTEGER" property="userId" />
|
||||
<result column="time" jdbcType="TIMESTAMP" property="time" />
|
||||
<result column="expire_minute" jdbcType="VARCHAR" property="expireMinute" />
|
||||
<result column="method" jdbcType="VARCHAR" property="method" />
|
||||
<result column="file_path" jdbcType="VARCHAR" property="filePath" />
|
||||
<result column="url_guid" jdbcType="VARCHAR" property="urlGuid" />
|
||||
</resultMap>
|
||||
<sql id="Base_Column_List">
|
||||
<!--
|
||||
WARNING - @mbg.generated
|
||||
This element is automatically generated by MyBatis Generator, do not modify.
|
||||
-->
|
||||
id, user_id, `time`, expire_minute, `method`, file_path, url_guid
|
||||
</sql>
|
||||
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
|
||||
<!--
|
||||
WARNING - @mbg.generated
|
||||
This element is automatically generated by MyBatis Generator, do not modify.
|
||||
-->
|
||||
select
|
||||
<include refid="Base_Column_List" />
|
||||
from file_operation_log
|
||||
where id = #{id,jdbcType=INTEGER}
|
||||
</select>
|
||||
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
|
||||
<!--
|
||||
WARNING - @mbg.generated
|
||||
This element is automatically generated by MyBatis Generator, do not modify.
|
||||
-->
|
||||
delete from file_operation_log
|
||||
where id = #{id,jdbcType=INTEGER}
|
||||
</delete>
|
||||
<insert id="insert" parameterType="plus.bookshelf.Dao.DO.FileOperationDO">
|
||||
<!--
|
||||
WARNING - @mbg.generated
|
||||
This element is automatically generated by MyBatis Generator, do not modify.
|
||||
-->
|
||||
insert into file_operation_log (id, user_id, `time`,
|
||||
expire_minute, `method`, file_path,
|
||||
url_guid)
|
||||
values (#{id,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{time,jdbcType=TIMESTAMP},
|
||||
#{expireMinute,jdbcType=VARCHAR}, #{method,jdbcType=VARCHAR}, #{filePath,jdbcType=VARCHAR},
|
||||
#{urlGuid,jdbcType=VARCHAR})
|
||||
</insert>
|
||||
<insert id="insertSelective" parameterType="plus.bookshelf.Dao.DO.FileOperationDO">
|
||||
<!--
|
||||
WARNING - @mbg.generated
|
||||
This element is automatically generated by MyBatis Generator, do not modify.
|
||||
-->
|
||||
insert into file_operation_log
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
id,
|
||||
</if>
|
||||
<if test="userId != null">
|
||||
user_id,
|
||||
</if>
|
||||
<if test="time != null">
|
||||
`time`,
|
||||
</if>
|
||||
<if test="expireMinute != null">
|
||||
expire_minute,
|
||||
</if>
|
||||
<if test="method != null">
|
||||
`method`,
|
||||
</if>
|
||||
<if test="filePath != null">
|
||||
file_path,
|
||||
</if>
|
||||
<if test="urlGuid != null">
|
||||
url_guid,
|
||||
</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
#{id,jdbcType=INTEGER},
|
||||
</if>
|
||||
<if test="userId != null">
|
||||
#{userId,jdbcType=INTEGER},
|
||||
</if>
|
||||
<if test="time != null">
|
||||
#{time,jdbcType=TIMESTAMP},
|
||||
</if>
|
||||
<if test="expireMinute != null">
|
||||
#{expireMinute,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="method != null">
|
||||
#{method,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="filePath != null">
|
||||
#{filePath,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="urlGuid != null">
|
||||
#{urlGuid,jdbcType=VARCHAR},
|
||||
</if>
|
||||
</trim>
|
||||
</insert>
|
||||
<update id="updateByPrimaryKeySelective" parameterType="plus.bookshelf.Dao.DO.FileOperationDO">
|
||||
<!--
|
||||
WARNING - @mbg.generated
|
||||
This element is automatically generated by MyBatis Generator, do not modify.
|
||||
-->
|
||||
update file_operation_log
|
||||
<set>
|
||||
<if test="userId != null">
|
||||
user_id = #{userId,jdbcType=INTEGER},
|
||||
</if>
|
||||
<if test="time != null">
|
||||
`time` = #{time,jdbcType=TIMESTAMP},
|
||||
</if>
|
||||
<if test="expireMinute != null">
|
||||
expire_minute = #{expireMinute,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="method != null">
|
||||
`method` = #{method,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="filePath != null">
|
||||
file_path = #{filePath,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="urlGuid != null">
|
||||
url_guid = #{urlGuid,jdbcType=VARCHAR},
|
||||
</if>
|
||||
</set>
|
||||
where id = #{id,jdbcType=INTEGER}
|
||||
</update>
|
||||
<update id="updateByPrimaryKey" parameterType="plus.bookshelf.Dao.DO.FileOperationDO">
|
||||
<!--
|
||||
WARNING - @mbg.generated
|
||||
This element is automatically generated by MyBatis Generator, do not modify.
|
||||
-->
|
||||
update file_operation_log
|
||||
set user_id = #{userId,jdbcType=INTEGER},
|
||||
`time` = #{time,jdbcType=TIMESTAMP},
|
||||
expire_minute = #{expireMinute,jdbcType=VARCHAR},
|
||||
`method` = #{method,jdbcType=VARCHAR},
|
||||
file_path = #{filePath,jdbcType=VARCHAR},
|
||||
url_guid = #{urlGuid,jdbcType=VARCHAR}
|
||||
where id = #{id,jdbcType=INTEGER}
|
||||
</update>
|
||||
</mapper>
|
@@ -97,5 +97,8 @@
|
||||
<!--<table tableName="user_book_favorites_relation" domainObjectName="UserFavoritesDO" enableCountByExample="false"-->
|
||||
<!-- enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"-->
|
||||
<!-- selectByExampleQueryId="false"></table>-->
|
||||
<!--<table tableName="file_operation_log" domainObjectName="FileOperationDO" enableCountByExample="false"-->
|
||||
<!-- enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"-->
|
||||
<!-- selectByExampleQueryId="false"></table>-->
|
||||
</context>
|
||||
</generatorConfiguration>
|
BIN
docs/腾讯云生成SecretId、SecretKey.assets/image-20220408141017781.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
docs/腾讯云生成SecretId、SecretKey.assets/image-20220408141246583.png
Normal file
After Width: | Height: | Size: 98 KiB |
BIN
docs/腾讯云生成SecretId、SecretKey.assets/image-20220408141715193.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
docs/腾讯云生成SecretId、SecretKey.assets/image-20220408141742336.png
Normal file
After Width: | Height: | Size: 116 KiB |
BIN
docs/腾讯云生成SecretId、SecretKey.assets/image-20220408141827890.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
docs/腾讯云生成SecretId、SecretKey.assets/image-20220408142012036.png
Normal file
After Width: | Height: | Size: 81 KiB |
BIN
docs/腾讯云生成SecretId、SecretKey.assets/image-20220408142047431.png
Normal file
After Width: | Height: | Size: 123 KiB |
BIN
docs/腾讯云生成SecretId、SecretKey.assets/image-20220408142738210.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
docs/腾讯云生成SecretId、SecretKey.assets/image-20220408143017202.png
Normal file
After Width: | Height: | Size: 82 KiB |
57
docs/腾讯云生成SecretId、SecretKey.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 腾讯云生成SecretId、SecretKey
|
||||
|
||||
有两种方式,一种是创建子用户,然后生成子用户的SecrectKey,另一种是直接生成当前账号的SecrectKey。两种方式均可。如果您对权限管理有需求,建议使用第一种;如果您希望尽可能简单的配置,可以使用第二种。如果您不确定使用哪种,那么请用第一种。
|
||||
|
||||
## 第1种:创建子用户
|
||||
|
||||
登陆腾讯云后台,进入访问管理下的用户列表页:https://console.cloud.tencent.com/cam
|
||||
|
||||
点击新建用户
|
||||
|
||||

|
||||
|
||||
点击快速创建。
|
||||
|
||||

|
||||
|
||||
接下来这里有四个地方需要配置。下图仅标出需要配置的项目,具体应该配置成什么请继续往下看。
|
||||
|
||||

|
||||
|
||||
> ① 用户名:自己随便起一个,满足要求即可。(用户名创建后不可以修改)
|
||||
>
|
||||
> ② 访问方式:修改为编程访问。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> ③ 用户权限:
|
||||
>
|
||||
> <1> 取消 `AdministratorAccess` 权限;
|
||||
>
|
||||
> <2> 搜索 `QcloudCOSDataFullControl` ,并勾选 `QcloudCOSDataFullControl` (对象存储(COS)数据读、写、删除、列出的访问权限)
|
||||
>
|
||||
> 
|
||||
>
|
||||
> ④ 根据自己的情况选择即可
|
||||
|
||||
点击创建用户,用户创建成功,获得密钥。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 第2种:直接生成
|
||||
|
||||
访问:https://console.cloud.tencent.com/cam/capi
|
||||
|
||||
点击继续使用。
|
||||
|
||||

|
||||
|
||||
点击新建密钥。
|
||||
|
||||

|
||||
|
||||
密钥创建完成。
|
||||
|
||||

|