1
0
mirror of https://gitee.com/bookshelfplus/bookshelfplus synced 2025-09-12 11:41:39 +08:00
Code Issues Projects Releases Wiki Activity GitHub Gitee

管理员上传文件到存储桶功能基本完成;可以前端计算文件SHA1;添加腾讯云生成SecretId、SecretKey文档

This commit is contained in:
2022-04-08 17:53:32 +08:00
parent d40189c1ca
commit 357a1bd42e
21 changed files with 1014 additions and 49 deletions

View File

@@ -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.";
}
}

View 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 ...");
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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=

View File

@@ -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>

View File

@@ -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>