阿里云的 OSS 服务进行云端的文件存储
用户认证需要上传图片、首页轮播需要上传图片,OSS分布式文件服务系统可以提供服务。
- 一般项目使用OSS对象存储服务,主要是对图片、文件、音频等对象集中式管理权限控制,管理数据生命周期等等,提供上传,下载,预览,删除等功能。
- 通过OSS部署前端项目
一、依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
</dependency>
二、在配置文件中配置 OSS 服务对应的 accessKey、secretKey 和 endpoint
alibaba.cloud.access-key=your-ak
alibaba.cloud.secret-key=your-sk
alibaba.cloud.oss.endpoint=***
阿里云 accessKey、secretKey 为例,获取方式如下
①、在阿里云控制台界面,单击右上角头像,选择 accesskeys,或者直接登录用户信息管理界面:
②、获取 accessKey、secretKey:
如果使用了阿里云 STS服务 进行短期访问权限管理,则除了 accessKey、secretKey、endpoint 以外,还需配置 securityToken
③、创建Bucket
进入Bucket,上传文件,可以查看上传文件的详情(URL用来访问该文件)
三、注入OSSClient并进行文件上传下载等操作,大量文件对象操作的场景。
@Service
public class YourService{
@Autowired
private OSSClient ossClient;
public void saveFile(){
//下载文件到本地
ossClient.getObject(new GetObejctRequest(bucketName,objectName),new File("pathOfYourLocalFile"));
}
}
如果是仅仅读取文件对象内容,OSS Starter也支持以Resource方式读取文件
①、在应用的 /src/main/resources/application.properties 中添加基本配置信息和 OSS 配置
spring.application.name=oss-example
server.port=18084
alibaba.cloud.access-key=your-ak
alibaba.cloud.secret-key=your-sk
alibaba.cloud.oss.endpoint=***
②、通过IDE直接启动或者编译打包后启动应用
- IDE直接启动:找到主类 OSSApplication,执行 main 方法启动应用
- 打包编译后启动,执行 mvn clean package 将工程编译打包;执行 java -jar oss-example.jar启动应用
应用启动后会自动在 OSS 上创建一个名为 aliyun-spring-boot-test 的 Bucke
@Value("oss://aliyun-spring-boot/oss-test")
private Resource file;
//文件内容读取
StreamUtils.copyToString(file.getInputStream(),Charset.forName(CharEncoding.UTF_8));
上传或下载文件
#使用 curl 调用上传接口 upload。该接口会上传 classpath 下的的 oss-test.json 文件。文件内容是一段 json:
curl http://localhost:18084/upload
#使用 curl 调用下载接口 download。该接口会下载刚才用 upload 接口上传的 oss-test.json 文件,并打印文件内容到结果中:
curl http://localhost:18084/download
在OSS上验证结果
登陆OSS控制台,可以看到左侧 Bucket 列表新增一个名字为aliyun-spring-boot-test的 Bucket。
单击aliyun-spring-boot-test Bucket,选择 文件管理 页签,发现上传的 oss-test 文件。上传的 objectName 为oss-test.json。目录和文件以’/'符号分割。
===============================================
实战操作
一、导入依赖
<!--阿里云OSS依赖-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<!--日期工具栏依赖-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
配置文件application.properties
server.port=8205
spring.application.name=service-oss
spring.jackson.data-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
spring.redis.host=192.168.44.165
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(附属表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
aliyun.oss.accessKeyId=
aliyun.oss.secret=
@SpringBootApplication(exclude=DataSourceAutoConfiguration.class)//无需加载数据库
@EnableDiscoveryClient
@ComponentScan(basePackages={"com.michael"})
public class ServiceOssApplication{
public static void main(String[] args){
SpringApplication.run(ServiceOssApplication.class,args);
}
}
二、网关模块gateway中进行对上传oss模块进行配置
spring.cloud.gateway.routes[4].id=service-oss
spring.cloud.gateway.routes[4].uri=lb://service-oss
spring.cloud.gateway.routes[4].predicates=Path=/*/oss/**
三、文件流的方式上传
Controller
@RestController
@RequestMapping("/api/oss/file")
public class FileApiController{
@Autowired
private FileService fileService;
//将本地文件上传到阿里云
@PostMapping("fileUpload")
public Result fileUpLoad(MultipartFile file){//SpringMVC中的,可以得到要上传的内容
//返回上传后的路径
String url = fileService.upload(file);
return Result.ok(url);
}
}
Service
public interface FileService{
String upload(MultipartFile file);
}
@Service
public class FileServiceImpl implements FileService{
@Override
public String upload(MultipartFile file){
//以下变量可以设置到配置文件中,然后通过工具类读取
String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
String accessKeyId = "";
String accessKeySecret = "";
String bucket = "";
//创建OSSClient实例
OSS occClient = new OSSClientBuilder().build(endpoint,accessKeyId,accessKeySecret);
//上传文件流
try{
InputStream inputStream = file.getInputStream();//从Controller中获取用户上传的文件流
String fileName = file.getOriginalFilename();//获取文件名称(包括路径)
/**
由于同名原因会导致文件上传的覆盖问题,后上传的覆盖前上传的
*/
//采用uuid生成唯一值,添加到文件名称里(并且将里面的-用空串表示)
String uuid = UUID.randomUUID().toString().replaceAll("-","");
fileName = uuid+fileName;
//按照日期创建文件夹,上传到创建文件夹的里面(joda-time日期工具依赖)
String timeUrl = new DateTime().toString("yyyy/MM/dd");
fileName = timeUrl + "/" + fileName;
ossClient.putObject(bucket,fileName,inputStream);
//关闭OSSClient
ossClient.shutdown();
//上传之后的路径,可以从阿里云工作台的文件详情获取
String url = "https://"+bucketName+"."+endpoint+"/"+fileName;
return url;
}catch(IOException e){
e.printStackTrace();
}
}
}
基于AmazonS3实现 Spring Boot Starter
Amazon Simple Storage Service(Amazon S3,Amazon简便存储服务)是 AWS 最早推出的云服务之一,经过多年的发展,S3 协议在对象存储行业事实上已经成为标准。
- 提供了统一的接口 REST/SOAP 来统一访问任何数据
- 对 S3 来说,存在里面的数据就是对象名(键),和数据(值)
- 不限量,单个文件最高可达 5TB,可动态扩容
- 高速。每个 bucket 下每秒可达 3500 PUT/COPY/POST/DELETE 或 5500 GET/HEAD 请求
- 具备版本,权限控制能力
- 具备数据生命周期管理能力
阿里云OSS兼容S3
七牛云对象存储兼容S3
腾讯云COS兼容S3
Minio兼容S3
今天使用的是阿里云OSS对接阿里云OSS的SDK,后天我们使用的是腾讯COS对接是腾讯云COS,我们何不直接对接AmazonS3实现呢,这样后续不需要调整代码,只需要去各个云服务商配置就好了。
一、创建一个SpringBoot项目
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-s3 -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>${aws.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
二、配置文件OssProperties
oss.endpoint=xxx
oss.accessKey=xxx
oss.secretKey=xxx
@ConfigurationProperties(prefix = “oss”):将配置文件中oss开头的属性绑定到此对象中
@Data
@ConfigurationProperties(prefix = "oss")
public class OssProperties{
/**
* 对象存储服务的URL
*/
private String endpoint;
/**
* 区域
*/
private String region;
/**
* true path-style nginx 反向代理和S3默认支持 pathStyle模式 {http://endpoint/bucketname}
* false supports virtual-hosted-style 阿里云等需要配置为 virtual-hosted-style 模式{http://bucketname.endpoint}
* 只是url的显示不一样
*/
private Boolean pathStyleAccess = true;
/**
* Access key
*/
private String accessKey;
/**
* Secret key
*/
private String secretKey;
/**
* 最大线程数,默认:100
*/
private Integer maxConnections = 100;
}
三、创建接口OssTemplate
public interface OssTemplate{
void createBucket(String bucketName);//创建bucket
List<Bucket> getAllBuckets();//获取所有的bucket
void removeBucket(String bucketName);//通过bucket名称删除bucket
//上传文件
void putObject(String bucketName, String objectName, InputStream stream, String contextType) throws Exception;
void putObject(String bucketName, String objectName, InputStream stream) throws Exception;
//获取文件
S3Object getObject(String bucketName, String objectName);
//获取对象的url
String getObjectURL(String bucketName, String objectName, Integer expires);
//通过bucketName和objectName删除对象
void removeObject(String bucketName, String objectName) throws Exception;
//根据文件前置查询文件
List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive);
}
@RequiredArgsConstructor
public class OssTemplateImpl implements OssTemplate{
private final AmazonS3 amazonS3;
//创建Bucket
@Override
@SneakyThrows
public void createBucket(String bucketName){
if ( !amazonS3.doesBucketExistV2(bucketName) ) {
amazonS3.createBucket((bucketName));
}
}
//获取所有的buckets
@Override
@SneakyThrows
public List<Bucket> getAllBuckets() {
return amazonS3.listBuckets();
}
//通过Bucket名称删除Bucket
@Override
@SneakyThrows
public void removeBucket(String bucketName) {
amazonS3.deleteBucket(bucketName);
}
//上传对象
@Override
@SneakyThrows
public void putObject(String bucketName, String objectName, InputStream stream, String contextType) {
putObject(bucketName, objectName, stream, stream.available(), contextType);
}
@Override
@SneakyThrows
public void putObject(String bucketName, String objectName, InputStream stream) {
putObject(bucketName, objectName, stream, stream.available(), "application/octet-stream");
}
//通过bucketName和objectName获取对象
@Override
@SneakyThrows
public S3Object getObject(String bucketName, String objectName) {
return amazonS3.getObject(bucketName, objectName);
}
//获取对象的url
@Override
@SneakyThrows
public String getObjectURL(String bucketName, String objectName, Integer expires) {
Date date = new Date();
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH, expires);
URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
return url.toString();
}
//通过bucketName和objectName删除对象
@Override
@SneakyThrows
public void removeObject(String bucketName, String objectName) {
amazonS3.deleteObject(bucketName, objectName);
}
//根据bucketName和prefix获取对象集合
@Override
@SneakyThrows
public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);
return objectListing.getObjectSummaries();
}
@SneakyThrows
private PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size,
String contextType) {
byte[] bytes = IOUtils.toByteArray(stream);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(size);
objectMetadata.setContentType(contextType);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 上传
return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
}
}
}
四、创建OssAutoConfiguration
OssAutoConfiguration:自动装配配置类,自动装配的bean有AmazonS3和OssTemplate
@Configuration
@RequiredArgsConstructor //lomnok的注解,替代@Autowired
@EnableConfigurationProperties(OssProperties.class) //自动装配我们的配置类
public class OssAutoConfiguration{
@Bean
@ConditionalOnMissingBean//bean被注册之后,注册相同类型的bean,就不会成功,它会保证你的bean只有一个,即你的实例只有一个。多个会报错
public AmazonS3 ossClient(OssProperties ossProperties){
//客户端配置,主要是全局的配置信息
ClientConfiguration cientConfiguration = new ClientConfiguration();
clientConfiguration.setMaxConnections(ossProperties.getMaxConnections());
//url以及region配置
AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder
.EndpointConfiguration(ossProperties.getEndpoint(), ossProperties.getRegion());
//凭证配置
AWSCredentials awsCredentials = new BasicAWSCredentials(ossProperties.getAccessKey(),
ossProperties.getSecretKey());
AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
// build amazonS3Client客户端
return AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration)
.withClientConfiguration(clientConfiguration).withCredentials(awsCredentialsProvider)
.disableChunkedEncoding().withPathStyleAccessEnabled(ossProperties.getPathStyleAccess()).build();
}
@Bean
@ConditionalOnBean(AmazonS3.class)//当给定的在bean存在时,则实例化当前Bean
public OssTemplate ossTemplate(AmazonS3 amazonS3){
return new OssTemplateImpl(amazonS3);
}
}
五、ClientConfiguration对象
客户端配置,主要是全局的配置信息
六、创建spring.factories
在resources目录下新增META-INF包,下面新建spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.qing.oss.OssAutoConfiguration
七、执行install打包到本地仓库
把springboot工程的启动类,配置文件干掉,干掉Test包。
最重要的是干掉pom文件的spring-boot-maven-plugin,要不然install报错。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
这样我们的一个oss-spring-boot-starter就完成了
执行install打包成jar到我们的本地仓库
到我们的本地仓库就能看到我们的oss-spring-boot-starter
八、测试
创建一个spring-boot工程当作我们的测试工程
①、pom文件新增我们的oss-spring-boot-starter依赖
<properties>
<oss.version>0.0.1-SNAPSHOT</oss.version>
</properties>
<dependency>
<groupId>com.qing</groupId>
<artifactId>oss-spring-boot-starter</artifactId>
<version>${oss.version}</version>
</dependency>
刷新maven后可以看到我们依赖加进来了。
解决打包没有注释的问题
可以发现我们的依赖没有注释没有Javadoc注释,在oss-string-boot-starter的pom文件下加入下面插件,重新install一下就好了。
<build>
<plugins>
<!-- 在打好的jar包中保留javadoc注释,实际会另外生成一个xxxxx-sources.jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
在我们的测试项目里面刷新一下maven可以看到已经带注释了
配置文件添加oss-spring-boot-starter所需要的配置
阿里云,腾讯cos,七牛云,minio等等的配置。
以Minio为例
oss.endpoint=xxx
oss.accessKey=xxx
oss.secretKey=xxx
编写测试方法
@SpringBootTest
class TestOssSpringBpptStarterApplicationTests {
@Autowired
private OssTemplate ossTemplate;
@Test
void contextLoads() {
ossTemplate.createBucket("oss02");
}
}
到我的Minio中查看发现测试成功。
=================================================
Controller
@Slf4j
@RestController
@RequestMapping("/oss")
public class OSSController{
//上传服务
@PostMapping("/upload")
public Response<Map<String,String>> upload(@RequestParam MultipartFile file, @RequestParam(required = false) String ossType)
throws IOException {
OSSTypeEnum ossTypeEnum = OSSTypeEnum.ALIYUN;//阿里云
if(StringUtils.isNotEmpty(ossType)){
ossTypeEnum = Enum.valueOf(OSSTypeEnum.class,ossType);
}
String originalFilename = file.getOriginalFilename();
InputStream inputStream = file.getInputStream();
final OSSTypeEnum finalOssTypeEnum = ossTypeEnum;
Optional<OSSService> optional = OSSContext.getBeans(OSSService.class).stream()//通过spring上下文获取
.filter(item -> item.support(finalOssTypeEnum))
.findFirst();
//com.google.guava:guava:20.0
Map<String,String> result = Maps.newConcurrentMap();
if(optional.isPresent()){
String url = optional.get().uploadStream(inputStream,priginalFilename);
log.info("文件上传的URL = {}", url);
result.put("name",originalFilename);
result.put("url",url);
}else{
return Response.toError(ResponseStatus.Code.FAIL_CODE, "未找到可用的云服务器");
}
return Reponse.toResponse(result);
}
//下载服务(参数:文件路径、文件名称、云服务器类型、响应流)
@ReqeustMapping("/download")
public void download(@RequestParam(required = false) String ossKey,
@RequestParam(required = false) String fileName,
@RequestParam(required = false) String ossType,
HttpServletResponse response)throws Exception{
//得到要下载的文件
String suffix = ossKey.substring(ossKey.lastIndexOf("."));
//hutool-all:4.1.14
String transFilename = DateUtil.formatDate(new Date()) + "-" + fileName + suffix;
//设置响应头
response.setContentType("application/octet-stream");
response.setCharacterEncoding("UTF-8");
response.setHeader("content-disposition", "attachment;filename=" + URLUtil.encode(transFilename, "UTF-8"));
final OSSTypeEnum finalOssTypeEnum = Enum.valueOf(OSSTypeEnum.class, ossType);
Optional<OSSService> optional = OSSContext.getBeans(OSSService.class).stream()
.filter(item -> item.support(finalOssTypeEnum)).findFirst();
if(optional.isPresent()){
//文件输入流
InputStream in = optional.get().download(ossKey);
//创建输出流
OutputStream out = response.getOutputStram();
FileUtil.write(in,out);
}
}
}
Service接口
public interface OSSService {
/**
* 支持ossType 类型
*
* @param ossType OSSType枚举
* @return true 支持
*/
boolean support(final OSSTypeEnum ossType);
/**
* 上传文件
*
* @param is 文件流
* @param fileName 文件名称
* @return 上传的文件路径
*/
String uploadStream(final InputStream is, final String fileName);
/**
* 上传文件
*
* @param file 源文件
* @param fileName 文件名称
* @return 上传的文件路径
*/
String uploadFile(final File file, final String fileName);
/**
* 字节数组上传
*
* @param data 字节数组
* @param fileName 文件名
* @return 上传的文件路径
*/
String uploadToByte(final byte[] data, final String fileName);
/**
* 根据文件key下载获取文件流
*
* @param ossKey 文件key
* @return
*/
InputStream download(final String ossKey);
}
@Service("ALIYUN")
@Slf4j
public class AliyunServiceImpl implements OSSService{
@Autowired(required = false)
private OSS oss;//aliyun-sdk-oss:3.1.0
@Value("${alibaba.cloud.bucket}")
private String bucket;//桶
@Override
public boolean support(final OSSTypeEnum ossType){
return OSSTypeEnum.ALIYUN.equals(ossType);
}
@Override
public String uploadStream(final InputStream is,final String fileName){
String url = null;
String tempFileName = fileName;
try{
String suffix = tempFileName.substring(tempFileName.lastIndexOf(SConsts.POINT) + IConsts.ONE);
//上传的文件key
tempFileName = suffix + SConsts.SLASH
+ DateUtils.format(DateUtils.dateTime(), DateUtils.NORM_DATE_PATTERN)
+ SConsts.SLASH + tempFileName;
//创建上传Object的Metadata aliyun-sdk-oss:3.1.0
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(is.available());
objectMetadata.setCacheControl("no-cache");
objectMetadata.setHeader("Pragma", "no-cache");
objectMetadata.setContentType(FileUtil.getContentType(SConsts.POINT + suffix));
objectMetadata.setContentDisposition("inline;filename=" + tempFileName);
oss.putObject(bucket, tempFileName, is, objectMetadata);
// 获取oss 文件地址
url = oss.generatePresignedUrl(bucket, tempFileName,
new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 10)).toString();
log.info("上传后的地址 url = {}", url);
}catch(Exception e){
log.error("上传文件失败");
}
return url;
}
@Override
public String uploadFile(final File file, final String fileName) {
try {
return this.uploadStream(new FileInputStream(file), fileName);
} catch (FileNotFoundException e) {
log.error("源文件不存在,文件名称 fileName = {}", fileName);
}
return null;
}
@Override
public String uploadToByte(final byte[] data, final String fileName) {
return this.uploadStream(new ByteArrayInputStream(data), fileName);
}
@Override
public InputStream download(final String ossKey) {
OSSObject ossObject = oss.getObject(bucket, ossKey);
return ossObject.getObjectContent();
}
}
@Service("QINIU")
@Slf4j
@EnableConfigurationProperties(QiniuProperties.class)
public class QiniuServiceImpl implements OSSService {
@Autowired(required = false)
private UploadManager uploadManager;
@Autowired(required = false)
private Auth auth;
@Autowired(required = false)
private QiniuProperties qiniuProperties;
@Override
public boolean support(final OSSTypeEnum ossType) {
return OSSTypeEnum.QINIU.equals(ossType);
}
@Override
public String uploadStream(InputStream is, String fileName) {
try {
String key = qiniuProperties.getBucket() + '/' + DateUtils.format(LocalDateTime.now(), DateUtils.NORM_DATE_PATTERN) + "/" + fileName;
Response response = uploadManager.put(is, key, this.getToken(), null, null);
if (response.isOK()) {
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
return putRet.key;
}
} catch (QiniuException e) {
throw new RuntimeException("上传文件失败");
}
return null;
}
@Override
public String uploadFile(File file, String fileName) {
if (!file.exists()) {
throw new RuntimeException("上传的文件不存在");
}
try {
return this.uploadStream(new FileInputStream(file), fileName);
} catch (FileNotFoundException e) {
log.error("文件不存在");
}
return null;
}
@Override
public String uploadToByte(byte[] data, String fileName) {
try {
Response response = uploadManager.put(data, fileName, this.getToken());
if (response.isOK()) {
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
return putRet.key;
}
} catch (QiniuException e) {
e.printStackTrace();
}
return null;
}
@Override
public InputStream download(String ossKey) {
try {
String encodedFileName = URLEncoder.encode(ossKey, "utf-8").replace("+", "%20");
String publicUrl = String.format("%s/%s", qiniuProperties.getEndpoint(), encodedFileName);
String url = auth.privateDownloadUrl(publicUrl, 3600); //1小时,可以自定义链接过期时间
return FileUtil.getRemoteFile(url);
} catch (Exception e) {
log.error("获取文件流失败");
}
return null;
}
/**
* 获取上传token
*
* @return 上传token
*/
private String getToken() {
return auth.uploadToken(qiniuProperties.getBucket());
}
}
七牛
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.2.0, 7.2.99]</version>
</dependency>
工具类
//七牛文件上传
public class QiniuUpload{
private static final String URL = "http://pp7x7b2mm.bkt.clouddn.com/";
/**
* 上传文件
*
* @param file 上传的文件
* @param folder 文件路径
* @return 文件全路径
*/
public static String uploadToFile(File file, String folder) {
return URL + UploadConfiguration.uploadFile(file, folder);
}
/**
* 数据流文件上传
*
* @param is 输入流
* @param fileName 上传文件名
* @return 文件全路径
*/
public static String uploadToStream(InputStream is, String fileName) {
return URL + UploadConfiguration.uploadStream(is, fileName);
}
/**
* 字节数组上传
*
* @param data 字节数组
* @param fileName 文件名
* @return 文件全路径
*/
public static String uploadToByte(byte[] data, String fileName) {
return URL + UploadConfiguration.uploadToByte(data, fileName);
}
}
//七牛存储配置
class UploadConfiguration {
/**
* 获取上传的token值
*/
private static String getToken() {
Auth auth = Auth.create(SysConfigMap.get(SysConfigKeys.QINIU_ACCESS_KEY), SysConfigMap.get(SysConfigKeys.QINIU_SECRET_KEY));
return auth.uploadToken(SysConfigMap.get(SysConfigKeys.QINIU_BUCKET));
}
/**
* 获取设置的UploadManager配置中心
*/
private static UploadManager getUploadManager() {
Configuration configuration = new Configuration(Region.region0());
return new UploadManager(configuration);
}
static String uploadStream(InputStream is, String fileName) {
UploadManager uploadManager = UploadConfiguration.getUploadManager();
try {
String key = SysConfigMap.get(SysConfigKeys.QINIU_BUCKET) + '/' + DateUtils.format(LocalDateTime.now(), DateUtils.NORM_DATE_PATTERN) + "/" + fileName;
Response response = uploadManager.put(is, key, UploadConfiguration.getToken(), null, null);
if (response.isOK()) {
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
return putRet.key;
}
} catch (QiniuException e) {
throw new RuntimeException("上传文件失败");
}
return null;
}
static String uploadFile(File file, String folder) {
UploadManager uploadManager = UploadConfiguration.getUploadManager();
if (!file.exists()) {
throw new RuntimeException("上传的文件不存在");
}
StringBuilder builder = new StringBuilder();
if (!StringUtils.isNullOrEmpty(folder)) {
builder.append(folder);
}
builder.append(file.getName());
try {
Response response = uploadManager.put(file, builder.toString(), UploadConfiguration.getToken());
if (response.isOK()) {
return JSON.parseObject(response.bodyString(), DefaultPutRet.class).key;
}
} catch (QiniuException e) {
throw new RuntimeException("上传文件失败");
}
return null;
}
/**
* 字节数组上传
*
* @param data 字节数组
* @param fileName 文件名
*/
static String uploadToByte(byte[] data, String fileName) {
UploadManager uploadManager = UploadConfiguration.getUploadManager();
try {
Response response = uploadManager.put(data, fileName, UploadConfiguration.getToken());
if (response.isOK()) {
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
return putRet.key;
}
} catch (QiniuException e) {
e.printStackTrace();
}
return null;
}
}
阿里云
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
</dependency>
腾讯云
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>3.1.162</version>
</dependency>
=================================================================
OSS项目实战
①、Controller
- @HasPermissions(“sys:oss:config”) 注解配合切面
- ValidatorUtils工具类
@RestController
@RequestMapping("system/oss")
public class OssController extends BaseController {
private final static String KEY = CloudConstant.CLOUD_STORAGE_CONFIG_KEY;
@Autowired
private IOssService ossService;
@Autowired
private IConfigService configService;
//云存储配置信息
@ReqeustMapping("config")
@HasPermissions("sys:oss:config")
public CloudStorageConfig config(){
String jsonConfig = configService.selectConfigByKey(CloudConstant.CLOUD_STORAGE_CONFIG_KEY);
//获取云存储配置信息(fastjson-1.2.74字符串转换成对象)
CloudStorageConfig config = JSON.parseObject(jsonConfig,CloudStorageConfig.class);
return config;
}
//保存云存储配置信息
@RequestMapping("saveConfig")
@HasPermissions("sys:oss:config")
public R SaveConfig(){
//校验类型
ValidatorUtils.validateEntity(config);
if (config.getType() == CloudService.QINIU.getValue()) {
// 校验七牛数据
ValidatorUtils.validateEntity(config, QiniuGroup.class);
} else if (config.getType() == CloudService.ALIYUN.getValue()) {
// 校验阿里云数据
ValidatorUtils.validateEntity(config, AliyunGroup.class);
} else if (config.getType() == CloudService.QCLOUD.getValue()) {
// 校验腾讯云数据
ValidatorUtils.validateEntity(config, QcloudGroup.class);
}
return toAjax(configService.updateValueByKey(KEY, new Gson().toJson(config)));
}
}
注解@HasPermissions(“sys:oss:config”) 和权限
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HasPermissions {
String value();
}
@Aspect
@Component
@Slf4j
public class PreAuthorizeAspect {
@Autowired
private IMenuService sysMenuService;
@Around("@annotation(com.ruoyi.system.auth.annotation.HasPermissions)")
public Object around(ProceedingJoinPoint point)throws Throwable{
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();//获取切点(注解修饰的)方法
//根据方法,获取注解类型
HasPermissions annotation = method.getAnnotation(HasPermissions.class);
if (annotation == null) {
return point.proceed();
}
String authority = new StringBuilder(annotation.value()).toString();//sys:oss:config
if(has(authority)){
return point.proceed();
}else{
throw new ForbiddenException();
}
}
private boolean has(String authority){
//用超管账号方便测试
HttpServletRequest request = ServletUtils.getRequest();
String tmpUserKey = request.getHeader(Constants.CURRENT_ID);//current_id
if (Optional.ofNullable(tmpUserKey).isPresent()) {
Long userId = Long.valueOf(tmpUserKey);
log.debug("userid:{}", userId);
if (userId == 1L) {
return true;
}
return sysMenuService.selectPermsByUserId(userId).stream().anyMatch(authority::equals);
}
return false;
}
}
工具类
/**
* 客户端工具类
*
* @author ruoyi
*/
public class ServletUtils {
/**
* 获取String参数
*/
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
/**
* 获取String参数
*/
public static String getParameter(String name, String defaultValue) {
return Convert.toStr(getRequest().getParameter(name), defaultValue);
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name) {
return Convert.toInt(getRequest().getParameter(name));
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name, Integer defaultValue) {
return Convert.toInt(getRequest().getParameter(name), defaultValue);
}
/**
* 获取request
*/
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
/**
* 获取response
*/
public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse();
}
/**
* 获取session
*/
public static HttpSession getSession() {
return getRequest().getSession();
}
public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
* @return null
*/
public static String renderString(HttpServletResponse response, String string) {
try {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 是否是Ajax异步请求
*
* @param request
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
String accept = request.getHeader("accept");
if (accept != null && accept.indexOf("application/json") != -1) {
return true;
}
String xRequestedWith = request.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) {
return true;
}
String uri = request.getRequestURI();
if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) {
return true;
}
String ajax = request.getParameter("__ajax");
if (StringUtils.inStringIgnoreCase(ajax, "json", "xml")) {
return true;
}
return false;
}
}
/**
hibernate-validator校验工具类
*/
public class ValidatorUtils{
private static Validator validator;
static{
//validation-api-2.0.1.Final
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
/**
* 校验对象
*
* @param object 待校验对象
* @param groups 待校验的组
* @throws BaseException 校验不通过,则报BaseException异常
*/
public static void validateEntity(Object object, Class<?>... groups)throws BaseException{
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
ConstraintViolation<Object> constraint = (ConstraintViolation<Object>) constraintViolations.iterator()
.next();
throw new BaseException(constraint.getMessage());
}
}
}
查询权限的业务
- 主键缓存@RedisCache(key = “user_perms”, fieldKey = “#userId”)
/**
* 菜单 业务层处理
*
* @author ruoyi
*/
@Service
public class MenuServiceImpl implements IMenuService {
public static final String PREMISSION_STRING = "perms[\"{0}\"]";
@Autowired
private MenuMapper menuMapper;
@Autowired
private RoleMenuMapper roleMenuMapper;
/**
* 根据用户查询菜单
*
* @param user 用户信息
* @return 菜单列表
*/
@Override
public List<Menu> selectMenusByUser(User user) {
List<Menu> menus = new LinkedList<>();
// 管理员显示所有菜单信息
if (User.isAdmin(user.getUserId())) {
menus = menuMapper.selectMenuNormalAll();
} else {
menus = menuMapper.selectMenusByUserId(user.getUserId());
}
return menus;
}
/**
* 查询菜单集合
*
* @return 所有菜单信息
*/
@Override
public List<Menu> selectMenuList(Menu menu) {
return menuMapper.selectMenuList(menu);
}
/**
* 查询菜单集合
*
* @return 所有菜单信息
*/
@Override
@DataScope(parkAlias = "m")
public List<Menu> selectMenuAll() {
return menuMapper.selectMenuAll();
}
/**
* 根据用户ID查询权限
*
* @param userId 用户ID
* @return 权限列表
*/
@Override
@RedisCache(key = "user_perms", fieldKey = "#userId")
public Set<String> selectPermsByUserId(Long userId) {
List<String> perms = menuMapper.selectPermsByUserId(userId);
Set<String> permsSet = new HashSet<>();
for (String perm : perms) {
if (StringUtils.isNotEmpty(perm)) {
permsSet.addAll(Arrays.asList(perm.trim().split(",")));
}
}
return permsSet;
}
@Override
public List<Menu> selectMenuIdsByRoleId(Long roleId) {
return menuMapper.selectMenuIdsByRoleId(roleId);
}
/**
* 根据角色ID查询菜单
*
* @param role 角色对象
* @return 菜单列表
*/
@Override
public List<Ztree> roleMenuTreeData(Role role) {
Long roleId = role.getRoleId();
List<Ztree> ztrees = new ArrayList<Ztree>();
List<Menu> menuList = menuMapper.selectMenuAll();
if (StringUtils.isNotNull(roleId)) {
List<String> roleMenuList = menuMapper.selectMenuTree(roleId);
ztrees = initZtree(menuList, roleMenuList, true);
} else {
ztrees = initZtree(menuList, null, true);
}
return ztrees;
}
/**
* 查询所有菜单
*
* @return 菜单列表
*/
@Override
public List<Ztree> menuTreeData() {
List<Menu> menuList = menuMapper.selectMenuAll();
List<Ztree> ztrees = initZtree(menuList);
return ztrees;
}
/**
* 查询系统所有权限
*
* @return 权限列表
*/
@Override
public LinkedHashMap<String, String> selectPermsAll() {
LinkedHashMap<String, String> section = new LinkedHashMap<>();
List<Menu> permissions = menuMapper.selectMenuAll();
if (StringUtils.isNotEmpty(permissions)) {
for (Menu menu : permissions) {
section.put(menu.getMenuName(), MessageFormat.format(PREMISSION_STRING, menu.getPerms()));
}
}
return section;
}
/**
* 对象转菜单树
*
* @param menuList 菜单列表
* @return 树结构列表
*/
public List<Ztree> initZtree(List<Menu> menuList) {
return initZtree(menuList, null, false);
}
/**
* 对象转菜单树
*
* @param menuList 菜单列表
* @param roleMenuList 角色已存在菜单列表
* @param permsFlag 是否需要显示权限标识
* @return 树结构列表
*/
public List<Ztree> initZtree(List<Menu> menuList, List<String> roleMenuList, boolean permsFlag) {
List<Ztree> ztrees = new ArrayList<Ztree>();
boolean isCheck = StringUtils.isNotNull(roleMenuList);
for (Menu menu : menuList) {
Ztree ztree = new Ztree();
ztree.setId(menu.getMenuId());
ztree.setpId(menu.getParentId());
ztree.setName(transMenuName(menu, roleMenuList, permsFlag));
ztree.setTitle(menu.getMenuName());
if (isCheck) {
ztree.setChecked(roleMenuList.contains(menu.getMenuId() + menu.getPerms()));
}
ztrees.add(ztree);
}
return ztrees;
}
public String transMenuName(Menu menu, List<String> roleMenuList, boolean permsFlag) {
StringBuffer sb = new StringBuffer();
sb.append(menu.getMenuName());
if (permsFlag) {
sb.append("<font color=\"#888\"> " + menu.getPerms() + "</font>");
}
return sb.toString();
}
/**
* 删除菜单管理信息
*
* @param menuId 菜单ID
* @return 结果
*/
@Override
public int deleteMenuById(Long menuId) {
return menuMapper.deleteMenuById(menuId);
}
/**
* 根据菜单ID查询信息
*
* @param menuId 菜单ID
* @return 菜单信息
*/
@Override
public Menu selectMenuById(Long menuId) {
return menuMapper.selectMenuById(menuId);
}
/**
* 查询子菜单数量
*
* @param parentId 父级菜单ID
* @return 结果
*/
@Override
public int selectCountMenuByParentId(Long parentId) {
return menuMapper.selectCountMenuByParentId(parentId);
}
/**
* 查询菜单使用数量
*
* @param menuId 菜单ID
* @return 结果
*/
@Override
public int selectCountRoleMenuByMenuId(Long menuId) {
return roleMenuMapper.selectCountRoleMenuByMenuId(menuId);
}
/**
* 新增保存菜单信息
*
* @param menu 菜单信息
* @return 结果
*/
@Override
public int insertMenu(Menu menu) {
return menuMapper.insertMenu(menu);
}
/**
* 修改保存菜单信息
*
* @param menu 菜单信息
* @return 结果
*/
@Override
public int updateMenu(Menu menu) {
return menuMapper.updateMenu(menu);
}
/**
* 校验菜单名称是否唯一
*
* @param menu 菜单信息
* @return 结果
*/
@Override
public String checkMenuNameUnique(Menu menu) {
Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId();
Menu info = menuMapper.checkMenuNameUnique(menu.getMenuName(), menu.getParentId());
if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue()) {
return UserConstants.MENU_NAME_NOT_UNIQUE;
}
return UserConstants.MENU_NAME_UNIQUE;
}
/**
* 根据父节点的ID获取所有子节点
*
* @param list 分类表
* @param parentId 传入的父节点ID
* @return String
*/
public List<Menu> getChildPerms(List<Menu> list, int parentId) {
List<Menu> returnList = new ArrayList<Menu>();
for (Iterator<Menu> iterator = list.iterator(); iterator.hasNext(); ) {
Menu t = (Menu) iterator.next();
// 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
if (t.getParentId() == parentId) {
recursionFn(list, t);
returnList.add(t);
}
}
return returnList;
}
/**
* 递归列表
*
* @param list
* @param t
*/
private void recursionFn(List<Menu> list, Menu t) {
// 得到子节点列表
List<Menu> childList = getChildList(list, t);
t.setChildren(childList);
for (Menu tChild : childList) {
if (hasChild(list, tChild)) {
// 判断是否有子节点
Iterator<Menu> it = childList.iterator();
while (it.hasNext()) {
Menu n = (Menu) it.next();
recursionFn(list, n);
}
}
}
}
/**
* 得到子节点列表
*/
private List<Menu> getChildList(List<Menu> list, Menu t) {
List<Menu> tlist = new ArrayList<Menu>();
Iterator<Menu> it = list.iterator();
while (it.hasNext()) {
Menu n = (Menu) it.next();
if (n.getParentId().longValue() == t.getMenuId().longValue()) {
tlist.add(n);
}
}
return tlist;
}
/**
* 判断是否有子节点
*/
private boolean hasChild(List<Menu> list, Menu t) {
return getChildList(list, t).size() > 0 ? true : false;
}
}
②、实体类
/**
* 云存储配置信息
*
* @author jack
*/
@Data
public class CloudStorageConfig implements Serializable {
private static final long serialVersionUID = 9035033846176792944L;
// 类型 1:七牛 2:阿里云 3:腾讯云
@Range(min = 1, max = 3, message = "类型错误")//hibernate-validator-6.1.6.Final
private Integer type;
// 七牛绑定的域名(validation-api-2.0.1.Final)
@NotBlank(message = "七牛绑定的域名不能为空", groups = QiniuGroup.class)
@URL(message = "七牛绑定的域名格式不正确", groups = QiniuGroup.class)
private String qiniuDomain;
// 七牛路径前缀
private String qiniuPrefix;
// 七牛ACCESS_KEY (validation-api-2.0.1.Final)
@NotBlank(message = "七牛AccessKey不能为空", groups = QiniuGroup.class)
private String qiniuAccessKey;
// 七牛SECRET_KEY
@NotBlank(message = "七牛SecretKey不能为空", groups = QiniuGroup.class)
private String qiniuSecretKey;
// 七牛存储空间名
@NotBlank(message = "七牛空间名不能为空", groups = QiniuGroup.class)
private String qiniuBucketName;
// 阿里云绑定的域名(hibernate-validator-6.1.6.Final)
@NotBlank(message = "阿里云绑定的域名不能为空", groups = AliyunGroup.class)
@URL(message = "阿里云绑定的域名格式不正确", groups = AliyunGroup.class)
private String aliyunDomain;
// 阿里云路径前缀
@Pattern(regexp = "^[^(/|\\)](.*[^(/|\\)])?$", message = "阿里云路径前缀不能'/'或者'\'开头或者结尾", groups = AliyunGroup.class)
private String aliyunPrefix;
// 阿里云EndPoint
@NotBlank(message = "阿里云EndPoint不能为空", groups = AliyunGroup.class)
private String aliyunEndPoint;
// 阿里云AccessKeyId
@NotBlank(message = "阿里云AccessKeyId不能为空", groups = AliyunGroup.class)
private String aliyunAccessKeyId;
// 阿里云AccessKeySecret
@NotBlank(message = "阿里云AccessKeySecret不能为空", groups = AliyunGroup.class)
private String aliyunAccessKeySecret;
// 阿里云BucketName
@NotBlank(message = "阿里云BucketName不能为空", groups = AliyunGroup.class)
private String aliyunBucketName;
// 腾讯云绑定的域名
@NotBlank(message = "腾讯云绑定的域名不能为空", groups = QcloudGroup.class)
@URL(message = "腾讯云绑定的域名格式不正确", groups = QcloudGroup.class)
private String qcloudDomain;
// 腾讯云路径前缀
private String qcloudPrefix;
// 腾讯云SecretId
@NotBlank(message = "腾讯云SecretId不能为空", groups = QcloudGroup.class)
private String qcloudSecretId;
// 腾讯云SecretKey
@NotBlank(message = "腾讯云SecretKey不能为空", groups = QcloudGroup.class)
private String qcloudSecretKey;
// 腾讯云BucketName
@NotBlank(message = "腾讯云BucketName不能为空", groups = QcloudGroup.class)
private String qcloudBucketName;
// 腾讯云COS所属地区
@NotBlank(message = "所属地区不能为空", groups = QcloudGroup.class)
private String qcloudRegion;
}