feat(oss): 增加对象存储模块并支持多种存储策略
- 新增对象存储服务模块 `han-note-oss`,集成 Rustfs、阿里云 OSS 及腾讯云 Cos 存储 - 提供统一的 `FileStrategy` 接口及 `FileStrategyFactory` 工厂类,根据存储类型动态选择存储策略 - 实现阿里云 OSS、腾讯云 Cos 和 Rustfs 具体存储逻辑 - 增加文件上传接口 `FileController`,支持接收文件并返回访问路径 - 完成用户密码更新接口,使用`spring.security`对密码进行加密
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -43,3 +43,4 @@ build/
|
|||||||
/han-note-auth/logs/
|
/han-note-auth/logs/
|
||||||
/logs/
|
/logs/
|
||||||
/.idea/
|
/.idea/
|
||||||
|
/han-note-oss/han-note-oss-biz/src/main/resources/application-dev.yml
|
||||||
|
|||||||
6
.idea/encodings.xml
generated
6
.idea/encodings.xml
generated
@@ -5,6 +5,12 @@
|
|||||||
<file url="file://$PROJECT_DIR$/han-note-auth/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-auth/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-gateway/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-gateway/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-gateway/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-gateway/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-oss/han-note-oss-api/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-oss/han-note-oss-api/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-oss/han-note-oss-biz/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-oss/han-note-oss-biz/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/hanserwei-framework/hanserwei-common/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/hanserwei-framework/hanserwei-common/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/hanserwei-framework/hanserwei-common/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/hanserwei-framework/hanserwei-common/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java" charset="UTF-8" />
|
||||||
|
|||||||
@@ -97,6 +97,11 @@
|
|||||||
<groupId>com.hanserwei</groupId>
|
<groupId>com.hanserwei</groupId>
|
||||||
<artifactId>hanserwei-spring-boot-starter-biz-context</artifactId>
|
<artifactId>hanserwei-spring-boot-starter-biz-context</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 密码加密 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.hanserwei.hannote.auth.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class PasswordEncoderConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
// BCrypt 是一种安全且适合密码存储的哈希算法,它在进行哈希时会自动加入“盐”,增加密码的安全性。
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||||
|
System.out.println(encoder.encode("qwe123"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,17 @@ package com.hanserwei.hannote.auth.controller;
|
|||||||
|
|
||||||
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
|
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.auth.model.vo.user.UpdatePasswordReqVO;
|
||||||
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
|
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
|
||||||
import com.hanserwei.hannote.auth.service.UserService;
|
import com.hanserwei.hannote.auth.service.UserService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/user")
|
@RequestMapping("/user")
|
||||||
@@ -30,4 +34,10 @@ public class UserController {
|
|||||||
public Response<?> logout() {
|
public Response<?> logout() {
|
||||||
return userService.logout();
|
return userService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/password/update")
|
||||||
|
@ApiOperationLog(description = "修改密码")
|
||||||
|
public Response<?> updatePassword(@Validated @RequestBody UpdatePasswordReqVO updatePasswordReqVO) {
|
||||||
|
return userService.updatePassword(updatePasswordReqVO);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
|
|||||||
// ----------- 业务异常状态码 -----------
|
// ----------- 业务异常状态码 -----------
|
||||||
VERIFICATION_CODE_SEND_FREQUENTLY("AUTH-20000", "请求太频繁,请3分钟后再试"),
|
VERIFICATION_CODE_SEND_FREQUENTLY("AUTH-20000", "请求太频繁,请3分钟后再试"),
|
||||||
VERIFICATION_CODE_ERROR("AUTH-20001", "验证码错误"),
|
VERIFICATION_CODE_ERROR("AUTH-20001", "验证码错误"),
|
||||||
|
LOGIN_TYPE_ERROR("AUTH-20002", "登录类型错误"),
|
||||||
|
USER_NOT_FOUND("AUTH-20003", "该用户不存在"),
|
||||||
|
MAIL_OR_PASSWORD_ERROR("AUTH-20004", "邮箱号或密码错误"),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.hanserwei.hannote.auth.model.vo.user;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class UpdatePasswordReqVO {
|
||||||
|
|
||||||
|
@NotBlank(message = "新密码不能为空")
|
||||||
|
private String newPassword;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.hanserwei.hannote.auth.service;
|
|||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
import com.hanserwei.hannote.auth.domain.dataobject.UserDO;
|
import com.hanserwei.hannote.auth.domain.dataobject.UserDO;
|
||||||
|
import com.hanserwei.hannote.auth.model.vo.user.UpdatePasswordReqVO;
|
||||||
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
|
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
|
||||||
|
|
||||||
public interface UserService extends IService<UserDO> {
|
public interface UserService extends IService<UserDO> {
|
||||||
@@ -20,4 +21,11 @@ public interface UserService extends IService<UserDO> {
|
|||||||
* @return 响应结果
|
* @return 响应结果
|
||||||
*/
|
*/
|
||||||
Response<?> logout();
|
Response<?> logout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码
|
||||||
|
* @param updatePasswordReqVO 请求参数
|
||||||
|
* @return 响应结果
|
||||||
|
*/
|
||||||
|
Response<?> updatePassword(UpdatePasswordReqVO updatePasswordReqVO);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.hanserwei.hannote.auth.domain.mapper.UserDOMapper;
|
|||||||
import com.hanserwei.hannote.auth.domain.mapper.UserRoleDOMapper;
|
import com.hanserwei.hannote.auth.domain.mapper.UserRoleDOMapper;
|
||||||
import com.hanserwei.hannote.auth.enums.LoginTypeEnum;
|
import com.hanserwei.hannote.auth.enums.LoginTypeEnum;
|
||||||
import com.hanserwei.hannote.auth.enums.ResponseCodeEnum;
|
import com.hanserwei.hannote.auth.enums.ResponseCodeEnum;
|
||||||
|
import com.hanserwei.hannote.auth.model.vo.user.UpdatePasswordReqVO;
|
||||||
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
|
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
|
||||||
import com.hanserwei.hannote.auth.service.UserService;
|
import com.hanserwei.hannote.auth.service.UserService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -30,6 +31,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ public class UserServiceImpl extends ServiceImpl<UserDOMapper, UserDO> implement
|
|||||||
private final RoleDOMapper roleDOMapper;
|
private final RoleDOMapper roleDOMapper;
|
||||||
@Resource(name = "authTaskExecutor")
|
@Resource(name = "authTaskExecutor")
|
||||||
private ThreadPoolTaskExecutor authTaskExecutor;
|
private ThreadPoolTaskExecutor authTaskExecutor;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<String> loginAndRegister(UserLoginReqVO reqVO) {
|
public Response<String> loginAndRegister(UserLoginReqVO reqVO) {
|
||||||
@@ -87,6 +90,19 @@ public class UserServiceImpl extends ServiceImpl<UserDOMapper, UserDO> implement
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PASSWORD:
|
case PASSWORD:
|
||||||
|
String password = reqVO.getPassword();
|
||||||
|
// 根据邮箱号查询
|
||||||
|
UserDO userDO1 = this.getOne(new QueryWrapper<UserDO>().eq("email", email));
|
||||||
|
if (Objects.isNull(userDO1)){
|
||||||
|
throw new ApiException(ResponseCodeEnum.USER_NOT_FOUND);
|
||||||
|
}
|
||||||
|
// 拿到密文密码
|
||||||
|
String encodePassword = userDO1.getPassword();
|
||||||
|
boolean isPasswordCorrect = passwordEncoder.matches(password, encodePassword);
|
||||||
|
if (!isPasswordCorrect) {
|
||||||
|
throw new ApiException(ResponseCodeEnum.MAIL_OR_PASSWORD_ERROR);
|
||||||
|
}
|
||||||
|
userId = userDO1.getId();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -158,4 +174,23 @@ public class UserServiceImpl extends ServiceImpl<UserDOMapper, UserDO> implement
|
|||||||
StpUtil.logout(userId);
|
StpUtil.logout(userId);
|
||||||
return Response.success();
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<?> updatePassword(UpdatePasswordReqVO updatePasswordReqVO) {
|
||||||
|
// 新密码
|
||||||
|
String newPassword = updatePasswordReqVO.getNewPassword();
|
||||||
|
// 加密后的密码
|
||||||
|
String encodePassword = passwordEncoder.encode(newPassword);
|
||||||
|
// 获取用户ID
|
||||||
|
Long userId = LoginUserContextHolder.getUserId();
|
||||||
|
|
||||||
|
UserDO userDO = UserDO.builder()
|
||||||
|
.id(userId)
|
||||||
|
.password(encodePassword)
|
||||||
|
.updateTime(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
// 更新用户密码
|
||||||
|
this.updateById(userDO);
|
||||||
|
return Response.success();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
han-note-oss/han-note-oss-api/pom.xml
Normal file
24
han-note-oss/han-note-oss-api/pom.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<!-- 指定父项目 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note-oss</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 打包方式 -->
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<artifactId>han-note-oss-api</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>RPC层, 供其他服务调用</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
88
han-note-oss/han-note-oss-biz/pom.xml
Normal file
88
han-note-oss/han-note-oss-biz/pom.xml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<!-- 指定父项目 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note-oss</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 打包方式 -->
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<artifactId>han-note-oss-biz</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>对象存储业务层</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 服务发现 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 配置中心 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!--Rustfs对象存储-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>s3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 阿里云 OSS -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun.oss</groupId>
|
||||||
|
<artifactId>aliyun-sdk-oss</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>activation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- no more than 2.3.3-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 腾讯云 OSS -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.qcloud</groupId>
|
||||||
|
<artifactId>cos_api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.hanserwei.hannote.oss;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class HannoteOssBizApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(HannoteOssBizApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.hanserwei.hannote.oss.config;
|
||||||
|
|
||||||
|
import com.aliyun.oss.OSS;
|
||||||
|
import com.aliyun.oss.OSSClientBuilder;
|
||||||
|
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
|
||||||
|
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class AliyunOSSConfig {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AliyunOSSProperties aliyunOSSProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 阿里云 OSS 客户端
|
||||||
|
*
|
||||||
|
* @return 阿里云 OSS 客户端
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public OSS aliyunOSSClient() {
|
||||||
|
// 设置访问凭证
|
||||||
|
DefaultCredentialProvider credentialsProvider = CredentialsProviderFactory.newDefaultCredentialProvider(
|
||||||
|
aliyunOSSProperties.getAccessKey(), aliyunOSSProperties.getSecretKey());
|
||||||
|
|
||||||
|
// 创建 OSSClient 实例
|
||||||
|
return new OSSClientBuilder().build(aliyunOSSProperties.getEndpoint(), credentialsProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.hanserwei.hannote.oss.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "storage.aliyun-oss")
|
||||||
|
@Component
|
||||||
|
@Data
|
||||||
|
public class AliyunOSSProperties {
|
||||||
|
private String endpoint;
|
||||||
|
private String accessKey;
|
||||||
|
private String secretKey;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.hanserwei.hannote.oss.config;
|
||||||
|
|
||||||
|
import com.qcloud.cos.COSClient;
|
||||||
|
import com.qcloud.cos.ClientConfig;
|
||||||
|
import com.qcloud.cos.auth.BasicCOSCredentials;
|
||||||
|
import com.qcloud.cos.auth.COSCredentials;
|
||||||
|
import com.qcloud.cos.endpoint.EndpointBuilder;
|
||||||
|
import com.qcloud.cos.region.Region;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CosConfig {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CosProperties cosProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public COSClient cosClient() {
|
||||||
|
// 1. 初始化用户身份信息(SecretId, SecretKey)
|
||||||
|
COSCredentials cred = new BasicCOSCredentials(
|
||||||
|
cosProperties.getSecretId(),
|
||||||
|
cosProperties.getSecretKey()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. 设置 bucket 的地域
|
||||||
|
Region region = new Region(cosProperties.getRegion());
|
||||||
|
ClientConfig clientConfig = new ClientConfig(region);
|
||||||
|
if (cosProperties.getEndpoint() != null && !cosProperties.getEndpoint().isEmpty()) {
|
||||||
|
clientConfig.setEndpointBuilder(new EndpointBuilder() {
|
||||||
|
@Override
|
||||||
|
public String buildGeneralApiEndpoint(String bucketName) {
|
||||||
|
// 所有 API 请求都会使用自定义域名
|
||||||
|
return cosProperties.getEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String buildGetServiceApiEndpoint() {
|
||||||
|
return cosProperties.getEndpoint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 构建 COSClient
|
||||||
|
return new COSClient(cred, clientConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.hanserwei.hannote.oss.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties(prefix = "storage.cos")
|
||||||
|
public class CosProperties {
|
||||||
|
private String endpoint;
|
||||||
|
private String secretId;
|
||||||
|
private String secretKey;
|
||||||
|
private String appId;
|
||||||
|
private String region;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.hanserwei.hannote.oss.config;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||||
|
import software.amazon.awssdk.regions.Region;
|
||||||
|
import software.amazon.awssdk.services.s3.S3Client;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RustfsConfig {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RustfsProperties rustfsProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public S3Client minioClient() {
|
||||||
|
// 构建 Rustfs 客户端
|
||||||
|
return S3Client.builder()
|
||||||
|
.endpointOverride(URI.create(rustfsProperties.getEndpoint()))
|
||||||
|
.region(Region.US_EAST_1)
|
||||||
|
.credentialsProvider(() -> AwsBasicCredentials.create(rustfsProperties.getAccessKey(), rustfsProperties.getSecretKey()))
|
||||||
|
.forcePathStyle(true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.hanserwei.hannote.oss.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "storage.rustfs")
|
||||||
|
@Component
|
||||||
|
@Data
|
||||||
|
public class RustfsProperties {
|
||||||
|
private String endpoint;
|
||||||
|
private String accessKey;
|
||||||
|
private String secretKey;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.hanserwei.hannote.oss.controller;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.oss.service.FileService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/file")
|
||||||
|
@Slf4j
|
||||||
|
public class FileController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FileService fileService;
|
||||||
|
|
||||||
|
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
|
public Response<?> uploadFile(@RequestPart(value = "file") MultipartFile file) {
|
||||||
|
return fileService.uploadFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.hanserwei.hannote.oss.enums;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.exception.BaseExceptionInterface;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ResponseCodeEnum implements BaseExceptionInterface {
|
||||||
|
|
||||||
|
// ----------- 通用异常状态码 -----------
|
||||||
|
SYSTEM_ERROR("OSS-10000", "出错啦,后台小维正在努力修复中..."),
|
||||||
|
PARAM_NOT_VALID("OSS-10001", "参数错误!!!"),
|
||||||
|
|
||||||
|
// ----------- 业务异常状态码 -----------
|
||||||
|
;
|
||||||
|
|
||||||
|
// 异常码
|
||||||
|
private final String errorCode;
|
||||||
|
// 错误信息
|
||||||
|
private final String errorMsg;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.hanserwei.hannote.oss.exception;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.exception.ApiException;
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.oss.enums.ResponseCodeEnum;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@SuppressWarnings("LoggingSimilarMessage")
|
||||||
|
@ControllerAdvice
|
||||||
|
@Slf4j
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获自定义业务异常
|
||||||
|
*
|
||||||
|
* @return Response.fail(e)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({ApiException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleApiException(HttpServletRequest request, ApiException e) {
|
||||||
|
log.warn("{} request fail, errorCode: {}, errorMessage: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMsg());
|
||||||
|
return Response.fail(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获参数校验异常
|
||||||
|
*
|
||||||
|
* @return Response.fail(errorCode, errorMessage)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({MethodArgumentNotValidException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
|
||||||
|
// 参数错误异常码
|
||||||
|
String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
|
||||||
|
|
||||||
|
// 获取 BindingResult
|
||||||
|
BindingResult bindingResult = e.getBindingResult();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 获取校验不通过的字段,并组合错误信息,格式为: email 邮箱格式不正确, 当前值: '123124qq.com';
|
||||||
|
Optional.of(bindingResult.getFieldErrors()).ifPresent(errors -> {
|
||||||
|
errors.forEach(error ->
|
||||||
|
sb.append(error.getField())
|
||||||
|
.append(" ")
|
||||||
|
.append(error.getDefaultMessage())
|
||||||
|
.append(", 当前值: '")
|
||||||
|
.append(error.getRejectedValue())
|
||||||
|
.append("'; ")
|
||||||
|
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 错误信息
|
||||||
|
String errorMessage = sb.toString();
|
||||||
|
|
||||||
|
log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);
|
||||||
|
|
||||||
|
return Response.fail(errorCode, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获 guava 参数校验异常
|
||||||
|
*
|
||||||
|
* @return Response.fail(ResponseCodeEnum.PARAM_NOT_VALID)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({IllegalArgumentException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
|
||||||
|
// 参数错误异常码
|
||||||
|
String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
|
||||||
|
|
||||||
|
// 错误信息
|
||||||
|
String errorMessage = e.getMessage();
|
||||||
|
|
||||||
|
log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);
|
||||||
|
|
||||||
|
return Response.fail(errorCode, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 其他类型异常
|
||||||
|
*
|
||||||
|
* @param request 请求
|
||||||
|
* @param e 异常
|
||||||
|
* @return Response.fail(ResponseCodeEnum.SYSTEM_ERROR)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({Exception.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleOtherException(HttpServletRequest request, Exception e) {
|
||||||
|
log.error("{} request error, ", request.getRequestURI(), e);
|
||||||
|
return Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.hanserwei.hannote.oss.factory;
|
||||||
|
|
||||||
|
import com.hanserwei.hannote.oss.strategy.FileStrategy;
|
||||||
|
import com.hanserwei.hannote.oss.strategy.impl.AliyunOSSFileStrategy;
|
||||||
|
import com.hanserwei.hannote.oss.strategy.impl.CosFileStrategy;
|
||||||
|
import com.hanserwei.hannote.oss.strategy.impl.RustfsFileStrategy;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@RefreshScope
|
||||||
|
public class FileStrategyFactory {
|
||||||
|
|
||||||
|
@Value("${storage.type}")
|
||||||
|
private String strategyType;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@RefreshScope
|
||||||
|
public FileStrategy getFileStrategy() {
|
||||||
|
if (strategyType == null) {
|
||||||
|
throw new IllegalArgumentException("存储类型不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (strategyType.toLowerCase()) {
|
||||||
|
case "rustfs" -> new RustfsFileStrategy();
|
||||||
|
case "aliyun" -> new AliyunOSSFileStrategy();
|
||||||
|
case "cos" -> new CosFileStrategy();
|
||||||
|
default -> throw new IllegalArgumentException("不可用的存储类型: " + strategyType);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.hanserwei.hannote.oss.service;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
public interface FileService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
*
|
||||||
|
* @param file 文件
|
||||||
|
* @return 文件上传结果
|
||||||
|
*/
|
||||||
|
Response<?> uploadFile(MultipartFile file);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.hanserwei.hannote.oss.service.impl;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.oss.service.FileService;
|
||||||
|
import com.hanserwei.hannote.oss.strategy.FileStrategy;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class FileServiceImpl implements FileService {
|
||||||
|
@Resource
|
||||||
|
private FileStrategy fileStrategy;
|
||||||
|
|
||||||
|
private static final String BUCKET_NAME = "han-note";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<?> uploadFile(MultipartFile file) {
|
||||||
|
// 上传文件
|
||||||
|
String url = fileStrategy.uploadFile(file, BUCKET_NAME);
|
||||||
|
return Response.success(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.hanserwei.hannote.oss.strategy;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
public interface FileStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
*
|
||||||
|
* @param file 文件
|
||||||
|
* @param bucketName 存储桶名称
|
||||||
|
* @return 文件访问路径
|
||||||
|
*/
|
||||||
|
String uploadFile(MultipartFile file, String bucketName);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.hanserwei.hannote.oss.strategy.impl;
|
||||||
|
|
||||||
|
import com.aliyun.oss.OSS;
|
||||||
|
import com.hanserwei.hannote.oss.config.AliyunOSSProperties;
|
||||||
|
import com.hanserwei.hannote.oss.strategy.FileStrategy;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class AliyunOSSFileStrategy implements FileStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AliyunOSSProperties aliyunOSSProperties;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private OSS ossClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public String uploadFile(MultipartFile file, String bucketName) {
|
||||||
|
log.info("## 上传文件至阿里云 OSS ...");
|
||||||
|
|
||||||
|
// 判断文件是否为空
|
||||||
|
if (file == null || file.getSize() == 0) {
|
||||||
|
log.error("==> 上传文件异常:文件大小为空 ...");
|
||||||
|
throw new RuntimeException("文件大小不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件的原始名称
|
||||||
|
String originalFileName = file.getOriginalFilename();
|
||||||
|
|
||||||
|
// 生成存储对象的名称(将 UUID 字符串中的 - 替换成空字符串)
|
||||||
|
String key = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
// 获取文件的后缀,如 .jpg
|
||||||
|
String suffix = null;
|
||||||
|
if (originalFileName != null) {
|
||||||
|
suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接上文件后缀,即为要存储的文件名
|
||||||
|
String objectName = String.format("%s%s", key, suffix);
|
||||||
|
|
||||||
|
log.info("==> 开始上传文件至阿里云 OSS, ObjectName: {}", objectName);
|
||||||
|
|
||||||
|
// 上传文件至阿里云 OSS
|
||||||
|
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(file.getInputStream().readAllBytes()));
|
||||||
|
|
||||||
|
// 返回文件的访问链接
|
||||||
|
String url = String.format("https://%s.%s/%s", bucketName, aliyunOSSProperties.getEndpoint(), objectName);
|
||||||
|
log.info("==> 上传文件至阿里云 OSS 成功,访问路径: {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.hanserwei.hannote.oss.strategy.impl;
|
||||||
|
|
||||||
|
import com.hanserwei.hannote.oss.config.CosProperties;
|
||||||
|
import com.hanserwei.hannote.oss.strategy.FileStrategy;
|
||||||
|
import com.qcloud.cos.COSClient;
|
||||||
|
import com.qcloud.cos.model.ObjectMetadata;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@SuppressWarnings("DuplicatedCode")
|
||||||
|
@Slf4j
|
||||||
|
public class CosFileStrategy implements FileStrategy {
|
||||||
|
@Resource
|
||||||
|
private CosProperties cosProperties;
|
||||||
|
@Resource
|
||||||
|
private COSClient cosClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public String uploadFile(MultipartFile file, String bucketName) {
|
||||||
|
log.info("## 上传文件至腾讯云Cos ...");
|
||||||
|
|
||||||
|
// 判断文件是否为空
|
||||||
|
if (file == null || file.getSize() == 0) {
|
||||||
|
log.error("==> 上传文件异常:文件大小为空 ...");
|
||||||
|
throw new RuntimeException("文件大小不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件的原始名称
|
||||||
|
String originalFileName = file.getOriginalFilename();
|
||||||
|
|
||||||
|
// 生成存储对象的名称(将 UUID 字符串中的 - 替换成空字符串)
|
||||||
|
String key = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
// 获取文件的后缀,如 .jpg
|
||||||
|
String suffix = null;
|
||||||
|
if (originalFileName != null) {
|
||||||
|
suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接上文件后缀,即为要存储的文件名
|
||||||
|
String objectName = String.format("%s%s", key, suffix);
|
||||||
|
|
||||||
|
log.info("==> 开始上传文件至腾讯云Cos, ObjectName: {}", objectName);
|
||||||
|
|
||||||
|
// 设置元数据
|
||||||
|
ObjectMetadata metadata = new ObjectMetadata();
|
||||||
|
metadata.setContentLength(file.getSize());
|
||||||
|
metadata.setContentType(file.getContentType());
|
||||||
|
|
||||||
|
// 执行上传
|
||||||
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
|
cosClient.putObject(bucketName, objectName, inputStream, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回文件的访问链接
|
||||||
|
String url = String.format("https://%s/%s", cosProperties.getEndpoint(), objectName);
|
||||||
|
log.info("==> 上传文件至腾讯云 Cos 成功,访问路径: {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.hanserwei.hannote.oss.strategy.impl;
|
||||||
|
|
||||||
|
import com.hanserwei.hannote.oss.config.RustfsProperties;
|
||||||
|
import com.hanserwei.hannote.oss.strategy.FileStrategy;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import software.amazon.awssdk.core.sync.RequestBody;
|
||||||
|
import software.amazon.awssdk.services.s3.S3Client;
|
||||||
|
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class RustfsFileStrategy implements FileStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RustfsProperties rustfsProperties;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private S3Client rustfsClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public String uploadFile(MultipartFile file, String bucketName) {
|
||||||
|
log.info("## 上传文件至 Rustfs ...");
|
||||||
|
|
||||||
|
// 判断文件是否为空
|
||||||
|
if (file == null || file.isEmpty()) {
|
||||||
|
log.error("==> 上传文件异常:文件为空 ...");
|
||||||
|
throw new RuntimeException("文件不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件的原始名称
|
||||||
|
String originalFileName = file.getOriginalFilename();
|
||||||
|
String contentType = file.getContentType();
|
||||||
|
|
||||||
|
// 生成存储对象的名称
|
||||||
|
String key = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
String suffix = "";
|
||||||
|
if (originalFileName != null && originalFileName.contains(".")) {
|
||||||
|
suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接最终文件名
|
||||||
|
String objectName = key + suffix;
|
||||||
|
|
||||||
|
log.info("==> 开始上传文件至 Rustfs, ObjectName: {}", objectName);
|
||||||
|
|
||||||
|
// 执行上传
|
||||||
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
|
rustfsClient.putObject(
|
||||||
|
PutObjectRequest.builder()
|
||||||
|
.bucket(bucketName) // 方法参数传入 bucketName
|
||||||
|
.key(objectName)
|
||||||
|
.contentType(contentType)
|
||||||
|
.build(),
|
||||||
|
RequestBody.fromInputStream(inputStream, file.getSize())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回文件的访问链接(注意:Rustfs 是否支持直链要看配置)
|
||||||
|
String url = String.format("%s/%s/%s",
|
||||||
|
rustfsProperties.getEndpoint().replaceAll("/$", ""), // 去掉结尾的斜杠
|
||||||
|
bucketName,
|
||||||
|
objectName);
|
||||||
|
|
||||||
|
log.info("==> 上传文件至 Rustfs 成功,访问路径: {}", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
server:
|
||||||
|
port: 8081 # 项目启动的端口
|
||||||
|
|
||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: dev # 默认激活 dev 本地开发环境
|
||||||
|
|
||||||
|
storage:
|
||||||
|
type: rustfs # 对象存储类型
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: han-note-oss # 应用名称
|
||||||
|
profiles:
|
||||||
|
active: dev # 默认激活 dev 本地开发环境
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
enabled: true # 启用服务发现
|
||||||
|
group: DEFAULT_GROUP # 所属组
|
||||||
|
namespace: han-note # 命名空间
|
||||||
|
server-addr: 127.0.0.1:8848 # 指定 Nacos 配置中心的服务器地址
|
||||||
|
config:
|
||||||
|
server-addr: http://127.0.0.1:8848 # 指定 Nacos 配置中心的服务器地址
|
||||||
|
prefix: ${spring.application.name} # 配置 Data Id 前缀,这里使用应用名称作为前缀
|
||||||
|
group: DEFAULT_GROUP # 所属组
|
||||||
|
namespace: han-note # 命名空间
|
||||||
|
file-extension: yaml # 配置文件格式
|
||||||
|
refresh-enabled: true # 是否开启动态刷新
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<configuration>
|
||||||
|
<!-- 引用 Spring Boot 的 logback 基础配置 -->
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||||
|
|
||||||
|
<!-- 应用名称 -->
|
||||||
|
<property scope="context" name="appName" value="oss"/>
|
||||||
|
<!-- 自定义日志输出路径,以及日志名称前缀 -->
|
||||||
|
<property name="LOG_FILE" value="./logs/${appName}.%d{yyyy-MM-dd}"/>
|
||||||
|
<!-- 每行日志输出的格式 -->
|
||||||
|
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
|
||||||
|
|
||||||
|
<!-- 文件输出 -->
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件的命名格式 -->
|
||||||
|
<fileNamePattern>${LOG_FILE}-%i.log</fileNamePattern>
|
||||||
|
<!-- 保留 30 天的日志文件 -->
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
<!-- 单个日志文件最大大小 -->
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
<!-- 日志文件的总大小,0 表示不限制 -->
|
||||||
|
<totalSizeCap>0</totalSizeCap>
|
||||||
|
<!-- 重启服务时,是否清除历史日志,不推荐清理 -->
|
||||||
|
<cleanHistoryOnStart>false</cleanHistoryOnStart>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 异步写入日志,提升性能 -->
|
||||||
|
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<!-- 是否丢弃日志, 0 表示不丢弃。默认情况下,如果队列满 80%, 会丢弃 TRACE、DEBUG、INFO 级别的日志 -->
|
||||||
|
<discardingThreshold>0</discardingThreshold>
|
||||||
|
<!-- 队列大小。默认值为 256 -->
|
||||||
|
<queueSize>256</queueSize>
|
||||||
|
<appender-ref ref="FILE"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 本地 dev 开发环境 -->
|
||||||
|
<springProfile name="dev">
|
||||||
|
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/> <!-- 输出控制台日志 -->
|
||||||
|
<appender-ref ref="ASYNC_FILE"/> <!-- 打印日志到文件中。PS: 本地环境下,如果不想打印日志到文件,可注释掉此行 -->
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<!-- 其它环境 -->
|
||||||
|
<springProfile name="prod">
|
||||||
|
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="ASYNC_FILE"/> <!-- 生产环境下,仅打印日志到文件中 -->
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
25
han-note-oss/pom.xml
Normal file
25
han-note-oss/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<!-- 指定父项目 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 多模块项目需要配置打包方式为 pom -->
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<!-- 子模块管理 -->
|
||||||
|
<modules>
|
||||||
|
<module>han-note-oss-api</module>
|
||||||
|
<module>han-note-oss-biz</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<artifactId>han-note-oss</artifactId>
|
||||||
|
<!-- 项目名称 -->
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<!-- 项目描述 -->
|
||||||
|
<description>对象存储服务</description>
|
||||||
|
</project>
|
||||||
41
pom.xml
41
pom.xml
@@ -14,6 +14,7 @@
|
|||||||
<module>han-note-auth</module>
|
<module>han-note-auth</module>
|
||||||
<module>hanserwei-framework</module>
|
<module>hanserwei-framework</module>
|
||||||
<module>han-note-gateway</module>
|
<module>han-note-gateway</module>
|
||||||
|
<module>han-note-oss</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -42,6 +43,12 @@
|
|||||||
<hutool.version>5.8.40</hutool.version>
|
<hutool.version>5.8.40</hutool.version>
|
||||||
<commons-lang3.version>3.19.0</commons-lang3.version>
|
<commons-lang3.version>3.19.0</commons-lang3.version>
|
||||||
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||||
|
<aws-sdk.version>2.34.8</aws-sdk.version>
|
||||||
|
<aliyun-sdk-oss.version>3.17.4</aliyun-sdk-oss.version>
|
||||||
|
<jaxb-api.version>2.3.1</jaxb-api.version>
|
||||||
|
<activation.version>1.1.1</activation.version>
|
||||||
|
<jaxb-runtime.version>2.3.3</jaxb-runtime.version>
|
||||||
|
<cos-api.version>5.6.227</cos-api.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -161,6 +168,40 @@
|
|||||||
<artifactId>transmittable-thread-local</artifactId>
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
<version>${transmittable-thread-local.version}</version>
|
<version>${transmittable-thread-local.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>s3</artifactId>
|
||||||
|
<version>${aws-sdk.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 阿里云 OSS -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun.oss</groupId>
|
||||||
|
<artifactId>aliyun-sdk-oss</artifactId>
|
||||||
|
<version>${aliyun-sdk-oss.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
<version>${jaxb-api.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>activation</artifactId>
|
||||||
|
<version>${activation.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- no more than 2.3.3-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
<version>${jaxb-runtime.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 腾讯云 OSS -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.qcloud</groupId>
|
||||||
|
<artifactId>cos_api</artifactId>
|
||||||
|
<version>${cos-api.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user