2025.4.17 自己实现filebrowser上传文件返回url

This commit is contained in:
zhangsan 2025-04-17 18:55:25 +08:00
parent 4dd7b78917
commit 88c32f02b5
13 changed files with 299 additions and 127 deletions

View File

@ -40,6 +40,11 @@
<groupId>com.aliyun.oss</groupId> <groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId> <artifactId>aliyun-sdk-oss</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.13</version>
</dependency>
<dependency> <dependency>
<groupId>javax.xml.bind</groupId> <groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId> <artifactId>jaxb-api</artifactId>

View File

@ -0,0 +1,14 @@
package com.sky.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "sky.filebrowser")
@Data
public class FileBrowserProperties {
private String domain;
private String username;
private String password;
}

View File

@ -24,79 +24,57 @@ import java.util.UUID;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@Slf4j @Slf4j
@Component
public class AliOssUtil { public class AliOssUtil {
@Autowired
private AliOssProperties aliOssProperties;
// private String endpoint; private String endpoint;
// private String accessKeyId; private String accessKeyId;
// private String accessKeySecret; private String accessKeySecret;
// private String bucketName; private String bucketName;
//
// /**
// * 文件上传
// *
// * @param bytes
// * @param objectName
// * @return
// */
// public String upload(byte[] bytes, String objectName) {
//
// // 创建OSSClient实例
// OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
//
// try {
// // 创建PutObject请求
// ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
// } catch (OSSException oe) {
// System.out.println("Caught an OSSException, which means your request made it to OSS, "
// + "but was rejected with an error response for some reason.");
// System.out.println("Error Message:" + oe.getErrorMessage());
// System.out.println("Error Code:" + oe.getErrorCode());
// System.out.println("Request ID:" + oe.getRequestId());
// System.out.println("Host ID:" + oe.getHostId());
// } catch (ClientException ce) {
// System.out.println("Caught an ClientException, which means the client encountered "
// + "a serious internal problem while trying to communicate with OSS, "
// + "such as not being able to access the network.");
// System.out.println("Error Message:" + ce.getMessage());
// } finally {
// if (ossClient != null) {
// ossClient.shutdown();
// }
// }
//
// //文件访问路径规则 https://BucketName.Endpoint/ObjectName
// StringBuilder stringBuilder = new StringBuilder("https://");
// stringBuilder
// .append(bucketName)
// .append(".")
// .append(endpoint)
// .append("/")
// .append(objectName);
//
// log.info("文件上传到:{}", stringBuilder.toString());
//
// return stringBuilder.toString();
// }
public String upload(MultipartFile file) throws IOException, com.aliyuncs.exceptions.ClientException {
InputStream inputStream = file.getInputStream();
// 避免文件覆盖
String originalFilename = file.getOriginalFilename();
String extname = originalFilename.substring(originalFilename.lastIndexOf("."));//文件扩展名
String fileName = UUID.randomUUID().toString() + extname;
//上传文件到 OSS /**
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider(); //从环境变量中获取 * 文件上传
OSS ossClient = new OSSClientBuilder().build(aliOssProperties.getEndpoint(), credentialsProvider); *
PutObjectRequest putObjectRequest = new PutObjectRequest(aliOssProperties.getBucketName(), fileName, inputStream); * @param bytes
PutObjectResult result = ossClient.putObject(putObjectRequest); * @param objectName
* @return
*/
public String upload(byte[] bytes, String objectName) {
//文件访问路径 // 创建OSSClient实例
String url = aliOssProperties.getEndpoint().split("//")[0] + "//" + aliOssProperties.getBucketName() + "." + aliOssProperties.getEndpoint().split("//")[1] + "/" + fileName; OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 关闭ossClient
ossClient.shutdown(); try {
return url;// 把上传到oss的路径返回 // 创建PutObject请求
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
//文件访问路径规则 https://BucketName.Endpoint/ObjectName
StringBuilder stringBuilder = new StringBuilder("https://");
stringBuilder
.append(bucketName)
.append(".")
.append(endpoint)
.append("/")
.append(objectName);
log.info("文件上传到:{}", stringBuilder.toString());
return stringBuilder.toString();
} }
} }

View File

@ -0,0 +1,121 @@
package com.sky.utils;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
@Slf4j
@AllArgsConstructor
@Data
public class FileBrowserUtil {
private String domain;
private String username;
private String password;
/**
* 第一步登录拿 token
* 调用 /api/login 接口返回原始的 JWT token 字符串
* curl -X POST "https://fshare.bitday.top/api/login" \
* -H "Content-Type: application/json" \
* -d '{
* "username":"admin",
* "password":"asdf14789"
* }'
* 返回值 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
*/
public String login() throws IOException {
String url = domain + "/api/login";
// 创建 HttpClient 实例
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 构造 POST 请求
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
// 构造 JSON body
JSONObject json = new JSONObject();
json.put("username", username);
json.put("password", password);
// 设置请求体
StringEntity requestEntity = new StringEntity(json.toString(), StandardCharsets.UTF_8);
requestEntity.setContentType("application/json");
httpPost.setEntity(requestEntity);
// 发送请求并处理响应
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode >= 200 && statusCode < 300) {
HttpEntity respEntity = response.getEntity();
String body = EntityUtils.toString(respEntity, StandardCharsets.UTF_8);
log.info("token:{}",body);
// 返回原始返回值假设就是 JWT token 字符串
return body;
} else {
throw new IOException("Login failed, HTTP status code: " + statusCode);
}
}
}
}
/**
* 第二步上传文件
* curl -v -X POST \
* "$DOMAIN/api/resources/$REMOTE_PATH?override=true" \ //服务器上相对路径
* -H "X-Auth: $TOKEN" \
* -F "data=@/path/to/local/photo.jpg" //photo.jpg multipart/form-data 的格式上传
*/
public String upload(byte[] fileBytes, String objectName) throws IOException {
// 1.获取唯一文件名
// 2. 构造远程路径固定到 userfiles 目录
String remotePath = "userfiles/" + objectName;
// 3. 获取登录令牌
String token = login();
// 4. URL 编码保留斜杠
String encodedPath = URLEncoder.encode(remotePath, StandardCharsets.UTF_8.toString())
.replace("%2F", "/");
String url = domain + "/api/resources/" + encodedPath + "?override=true";
// 5. 构建并发送 multipart/form-data 请求
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpPost post = new HttpPost(url);
post.setHeader("X-Auth", token);
HttpEntity multipart = MultipartEntityBuilder.create()
.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
// name="files"filename 用新生成的 objectName
.addBinaryBody("files", fileBytes, ContentType.APPLICATION_OCTET_STREAM, objectName)
.build();
post.setEntity(multipart);
try (CloseableHttpResponse resp = client.execute(post)) {
int status = resp.getStatusLine().getStatusCode();
String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
if (status >= 200 && status < 300) {
log.info("上传成功 → {}", remotePath);
return body;
} else {
throw new IOException("上传失败HTTP " + status + ",响应:" + body);
}
}
}
}
}

View File

@ -115,8 +115,8 @@
<artifactId>poi-ooxml</artifactId> <artifactId>poi-ooxml</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-security-crypto</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.pagehelper</groupId> <groupId>com.github.pagehelper</groupId>
@ -132,6 +132,7 @@
<artifactId>spring-boot-starter-cache</artifactId> <artifactId>spring-boot-starter-cache</artifactId>
<version>2.7.3</version> <version>2.7.3</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -0,0 +1,20 @@
package com.sky.config;
import com.sky.properties.FileBrowserProperties;
import com.sky.utils.FileBrowserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class FileBrowserConfiguration {
@Bean
@ConditionalOnMissingBean
public FileBrowserUtil fileBrowserUtil(FileBrowserProperties fileBrowserProperties){
log.info("开始创建filebrowser上传工具类对象{}",fileBrowserProperties);
return new FileBrowserUtil(fileBrowserProperties.getDomain(),
fileBrowserProperties.getUsername(),
fileBrowserProperties.getPassword());
}
}

View File

@ -0,0 +1,26 @@
package com.sky.config;
import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置类用于创建AliOssUtil对象
*/
@Configuration
@Slf4j
public class OssConfiguration {
@Bean
@ConditionalOnMissingBean
public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
return new AliOssUtil(aliOssProperties.getEndpoint(),
aliOssProperties.getAccessKeyId(),
aliOssProperties.getAccessKeySecret(),
aliOssProperties.getBucketName());
}
}

View File

@ -0,0 +1,15 @@
package com.sky.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfiguration {
@Bean
public PasswordEncoder passwordEncoder() {
// 参数 strength 为工作因子默认为 10这里可以根据需要进行调整
return new BCryptPasswordEncoder(10);
}
}

View File

@ -1,44 +0,0 @@
package com.sky.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll() // 允许所有请求
.and()
.csrf().disable(); // 禁用CSRF保护
}
@Bean
public BCryptPasswordEncoder encoder(){
return new BCryptPasswordEncoder();
}
}
//@Configuration
//@EnableWebSecurity
//public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// http
// .authorizeRequests()
// .anyRequest().authenticated() // 需要认证才能访问
// .and()
// .httpBasic() // 使用基本的HTTP认证
// .and()
// .csrf().disable(); // 禁用CSRF保护适用于API服务
// }
//
// @Bean
// public BCryptPasswordEncoder encoder() {
// return new BCryptPasswordEncoder();
// }
//}

View File

@ -1,6 +1,6 @@
package com.sky.controller.admin; package com.sky.controller.admin;
import com.aliyuncs.exceptions.ClientException; import com.sky.constant.MessageConstant;
import com.sky.result.Result; import com.sky.result.Result;
import com.sky.utils.AliOssUtil; import com.sky.utils.AliOssUtil;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
@ -13,20 +13,45 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
import java.util.UUID;
/**
* 通用接口
*/
@RestController @RestController
@RequestMapping("/admin/common") @RequestMapping("/admin/common")
@Api(tags = "通用接口") @Api(tags = "通用接口")
@Slf4j @Slf4j
public class CommonController { public class CommonController {
@Autowired @Autowired
private AliOssUtil aliOssUtil; private AliOssUtil aliOssUtil;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload") @PostMapping("/upload")
@ApiOperation("文件上传") @ApiOperation("文件上传")
public Result<String> upload(MultipartFile file) throws IOException, ClientException { public Result<String> upload(MultipartFile file){
log.info("文件上传:{}",file); log.info("文件上传:{}",file);
String url=aliOssUtil.upload(file);
return Result.success(url); try {
//原始文件名
String originalFilename = file.getOriginalFilename();
//截取原始文件名的后缀 dfdfdf.png
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
//构造新文件名称
String objectName = UUID.randomUUID().toString() + extension;
//文件的访问地址
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
return Result.success(filePath);
} catch (IOException e) {
log.error("文件上传失败:{}", e);
}
return Result.error(MessageConstant.UPLOAD_FAILED);
} }
} }

View File

@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
@ -32,7 +33,7 @@ public class EmployeeServiceImpl implements EmployeeService {
@Autowired @Autowired
private EmployeeMapper employeeMapper; private EmployeeMapper employeeMapper;
@Autowired @Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder; private PasswordEncoder passwordEncoder;
/** /**
* 员工登录 * 员工登录
@ -54,7 +55,7 @@ public class EmployeeServiceImpl implements EmployeeService {
} }
//密码比对 //密码比对
if (!bCryptPasswordEncoder.matches(password,employee.getPassword())) { if (!passwordEncoder.matches(password,employee.getPassword())) {
//密码错误 //密码错误
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR); throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
} }
@ -73,7 +74,7 @@ public class EmployeeServiceImpl implements EmployeeService {
Employee employee = new Employee(); Employee employee = new Employee();
//对象属性拷贝 //对象属性拷贝
BeanUtils.copyProperties(employeeDTO, employee); BeanUtils.copyProperties(employeeDTO, employee);
String encodedPassword=bCryptPasswordEncoder.encode(PasswordConstant.DEFAULT_PASSWORD); String encodedPassword=passwordEncoder.encode(PasswordConstant.DEFAULT_PASSWORD);
employee.setPassword(encodedPassword); employee.setPassword(encodedPassword);
employee.setStatus(StatusConstant.ENABLE); employee.setStatus(StatusConstant.ENABLE);
employeeMapper.save(employee); employeeMapper.save(employee);
@ -113,8 +114,8 @@ public class EmployeeServiceImpl implements EmployeeService {
@Override @Override
public void changePassword(EmployeeChangePasswordDTO employeeChangePasswordDTO) { public void changePassword(EmployeeChangePasswordDTO employeeChangePasswordDTO) {
Employee employee=employeeMapper.queryById(BaseContext.getCurrentId()); Employee employee=employeeMapper.queryById(BaseContext.getCurrentId());
if(bCryptPasswordEncoder.matches(employeeChangePasswordDTO.getOldPassword(),employee.getPassword())){ if(passwordEncoder.matches(employeeChangePasswordDTO.getOldPassword(),employee.getPassword())){
String encodedPassword = bCryptPasswordEncoder.encode(employeeChangePasswordDTO.getNewPassword()); String encodedPassword = passwordEncoder.encode(employeeChangePasswordDTO.getNewPassword());
employee.setPassword(encodedPassword); employee.setPassword(encodedPassword);
employeeMapper.update(employee); employeeMapper.update(employee);
}else }else

View File

@ -17,6 +17,10 @@ sky:
access-key-secret: AJPJSYc5sdwiZoj8RWzsXtjKR3W8f0 access-key-secret: AJPJSYc5sdwiZoj8RWzsXtjKR3W8f0
endpoint: https://oss-cn-hangzhou.aliyuncs.com endpoint: https://oss-cn-hangzhou.aliyuncs.com
bucket-name: zyjavaweb bucket-name: zyjavaweb
filebrowser:
domain: https://fshare.bitday.top
username: admin
password: asdf14789
wechat: wechat:
appid: wxa3b6f70e4ffb92cd appid: wxa3b6f70e4ffb92cd

View File

@ -55,6 +55,12 @@ sky:
bucket-name: {sky.alioss.bucket-name} bucket-name: {sky.alioss.bucket-name}
access-key-id: {sky.alioss.access-key-id} access-key-id: {sky.alioss.access-key-id}
access-key-secret: {sky.alioss.access-key-secret} access-key-secret: {sky.alioss.access-key-secret}
filebrowser:
domain: {sky.filebrowser.domain}
username: {sky.filebrowser.username}
password: {sky.filebrowser.password}
wechat: wechat:
appid: ${sky.wechat.appid} appid: ${sky.wechat.appid}
secret: ${sky.wechat.secret} secret: ${sky.wechat.secret}