阿里云对象存储OSS(Object Storage Service)是阿里云提供的海量、安全、低成本、高可靠的云存储服务。其数据设计持久性不低于99.9999999999%(12个9),服务可用性(或业务连续性)不低于99.995%。
OSS具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
您可以使用阿里云提供的API、SDK接口或者OSS迁移工具轻松地将海量数据移入或移出阿里云OSS。数据存储到阿里云OSS以后,您可以选择标准存储(Standard)作为移动应用、大型网站、图片分享或热点音视频的主要存储方式,也可以选择成本更低、存储期限更长的低频访问存储(Infrequent Access)、归档存储(Archive)作为不经常访问数据的存储方式。
官方文档
com.aliyun.oss</groupId>
aliyun-sdk-oss</artifactId>
3.10.2</version>
</dependency>
org.projectlombok</groupId>
lombok</artifactId>
</dependency>
oss:
endpoint: https://oss-cn-hangzhou.aliyuncs.com #Endpoint以杭州为例,其它Region请按实际情况填写
accessKeyId: yourAccessKeyId
secretAccessKey: yourAccessKeySecret
bucketName: yourBucketName
urlPrefix: 你的文件访问地址 #bucketName+endpoint
@Data
@Configuration
@ConfigurationProperties(prefix = "oss")
//@PropertySource(value = {"classpath:application-oss.properties"})
public class OssConfig {
private String endpoint;
private String accessKeyId;
private String secretAccessKey;
private String bucketName;
private String urlPrefix;
@Bean
public OSS oSSClient() {
return new OSSClientBuilder().build(endpoint, accessKeyId, secretAccessKey);
}
}
@RestController
public class OssController {
@Autowired
private OssUtil ossUtil;
/**
* 文件上传到oss
*
* @param files
* @return
*/
@PostMapping("/oss/upload")
public String upload(@RequestParam("file") MultipartFile file) {
return ossUtil.putObject(file);
}
/**
* 删除文件
*
* @param fileName
* @return
*/
@GetMapping("oss/delete")
public String delete(@RequestParam("fileName") String fileName) {
Boolean flag = ossUtil.deleteObject(fileName);
if (flag) {
return "删除成功";
}
return "删除失败";
}
/**
* 查询文件列表
*
* @return
*/
@GetMapping("oss/list")
public List<OSSObjectSummary> list() {
List<OSSObjectSummary> fileList = ossUtil.listObjects();
return fileList;
}
/**
* 下载文件
*
* @param fileName
* @param response
* @throws IOException
*/
@GetMapping("oss/download")
public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException {
// 通知浏览器以附件形式下载
response.setHeader("Content-Disposition",
"attachment;filename=" + new String(fileName.getBytes(), "ISO-8859-1"));
ossUtil.download(response.getOutputStream(), fileName);
}
/**
* 上传图片
*
* @param file
* @return
*/
@PostMapping("/oss/uploadPic")
public String uploadPic(@RequestParam("file") MultipartFile file) {
boolean flag = ossUtil.isImage(file);
if (flag) {
return ossUtil.uploadPic(file);
}
return "上传失败";
}
}
/**
* 阿里云OSS服务器工具类
*
*/
@Component
public class OssUtil {
@Autowired
private OSS ossClient;
@Autowired
private OssConfig ossConfig;
private static final long EXPIRATION = 3600l * 1000 * 24 * 7;// 七天
// 允许上传的格式
private static final String[] IMAGE_TYPE = new String[] { ".bmp", ".jpg", ".jpeg", ".gif", ".png" };
private String PATH = "javakf/";
/**
* 单文件上传
*
* @param file
* @return
*/
public String uploadFile(MultipartFile file) {
String fileUrl = putObject(file);
return fileUrl;
}
/**
* 多文件上传
*
* @param fileList
* @return
*/
public String multiUpload(List<MultipartFile> fileList) {
String url = null;
String fileUrl = null;
for (int i = 0; i < fileList.size(); i++) {
url = putObject(fileList.get(i));
if (i == 0) {
fileUrl = url;
} else {
fileUrl += ";" + url;
}
}
return fileUrl.trim();
}
/**
* 上传文件-支持单文件,多文件 -返回文件访问路径,多文件以分号‘;’分隔
*
* @param muFiles
* @return
*/
public String uploadFiles(MultipartFile... file) {
if (file.length < 1) {
throw new RuntimeException("上传文件为空!");
}
StringBuilder str = new StringBuilder();
for (MultipartFile muFile : file) {
str.append(putObject(muFile));
str.append(";");
}
return str.deleteCharAt(str.length() - 1).toString();
}
/**
* 上传文件
*
* @param file
* @return
*/
public String putObject(MultipartFile file) {
try {
String fileName = getFilePathName(file, true);
// 上传到阿里云
ossClient.putObject(ossConfig.getBucketName(), fileName, new ByteArrayInputStream(file.getBytes()));
return this.ossConfig.getUrlPrefix() + fileName;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("上传文件失败!");
}
}
/**
* 上传图片
*
* @param file
* @return
*/
public String uploadPic(MultipartFile file) {
try {
String fileName = getFilePathName(file, true);
InputStream inputStream = file.getInputStream();
// 如果是图片文件就进行压缩并添加水印
if (ImageUtil.isImage(file.getOriginalFilename())) {
inputStream = ImageUtil.getInputStream(
ImageUtil.setWatermark(ImageUtil.compress(ImageIO.read(inputStream))),
ImageUtil.getFileExtention(file.getOriginalFilename()));
}
// 上传到阿里云
ossClient.putObject(ossConfig.getBucketName(), fileName, inputStream);
return this.ossConfig.getUrlPrefix() + fileName;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("上传文件失败!");
}
}
/**
* 校验图片格式
*
* @param file
* @return
*/
public boolean isImage(MultipartFile file) {
boolean flag = false;
for (String type : IMAGE_TYPE) {
if (StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), type)) {
flag = true;
break;
}
}
return flag;
}
/**
* 删除文件
*
* @param fileName
* @return
*/
public Boolean deleteObject(String fileName) {
try {
ossClient.deleteObject(ossConfig.getBucketName(), fileName);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 查询文件列表(默认100条)
*
* @return
*/
public List<OSSObjectSummary> listObjects() {
ObjectListing listObjects = ossClient.listObjects(ossConfig.getBucketName());
List<OSSObjectSummary> fileList = listObjects.getObjectSummaries();
return fileList;
}
/**
* 查询文件列表(默认值100条,最大值1000条)
*
* @param maxKeys 要查询的条数
* @return
*/
public List<OSSObjectSummary> listObjects(Integer maxKeys) {
if (maxKeys == null) {
maxKeys = 100;
}
ObjectListing listObjects = ossClient
.listObjects(new ListObjectsRequest(ossConfig.getBucketName()).withMaxKeys(maxKeys));
List<OSSObjectSummary> fileList = listObjects.getObjectSummaries();
return fileList;
}
/**
* 查询指定前缀的文件列表
*
* @param prefix 前缀
* @return
*/
public List<OSSObjectSummary> listObjects(String prefix) {
ObjectListing listObjects = ossClient.listObjects(ossConfig.getBucketName(), prefix);
List<OSSObjectSummary> fileList = listObjects.getObjectSummaries();
return fileList;
}
/**
* 下载文件
*
* @param outputStream
* @param fileName
*/
public void download(OutputStream outputStream, String fileName) {
try {
OSSObject ossObject = ossClient.getObject(ossConfig.getBucketName(), fileName);
BufferedInputStream in = new BufferedInputStream(ossObject.getObjectContent());
BufferedOutputStream out = new BufferedOutputStream(outputStream);
byte[] buffer = new byte[1024];
int lenght = 0;
while ((lenght = in.read(buffer)) != -1) {
out.write(buffer, 0, lenght);
}
if (out != null) {
out.flush();
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 生成下载链接
*
* @param fileName
* @return
*/
public String getUrl(String fileName) {
if (!StringUtils.isEmpty(fileName)) {
// 设置URL过期时间为10年 3600l*1000*24*365*10
Date expiration = new Date(new Date().getTime() + EXPIRATION);
// 生成URL
URL url = ossClient.generatePresignedUrl(ossConfig.getBucketName(), fileName, expiration);
if (url != null) {
return url.toString();
}
}
return null;
}
/**
* 上传文件
*
* @param inputStream
* @param fileName
* @return
*/
public String putObject(InputStream inputStream, String fileName) {
try {
// 文件扩展名
String fileSuffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
// 新的文件名
fileName = PATH + new Random().nextInt(10000) + System.currentTimeMillis() + fileSuffix;
// 创建上传Object的Metadata
ObjectMetadata objectMetadata = new ObjectMetadata();
// 上传的文件的长度
objectMetadata.setContentLength(inputStream.available());
// 指定该Object被下载时的网页的缓存行为
objectMetadata.setCacheControl("no-cache");
// 指定该Object下设置Header
objectMetadata.setHeader("Pragma", "no-cache");
// 指定该Object被下载时的内容编码格式
objectMetadata.setContentEncoding("utf-8");
// 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成,
// 如果没有扩展名则填默认值application/octet-stream
objectMetadata.setContentType(getcontentType(fileSuffix));
// 指定该Object被下载时的名称(指示MINME用户代理如何显示附加的文件,打开或下载,及文件名称)
objectMetadata.setContentDisposition("inline;filename=" + fileName);
// 上传文件
ossClient.putObject(ossConfig.getBucketName(), fileName, inputStream, objectMetadata);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return this.ossConfig.getUrlPrefix() + fileName;
}
/**
* 获取文件名
*
* @param muFile 文件
* @param isRetain 是否保留源文件名
* @return 返回文件名,以当前年月日作为前缀路径
*/
private String getFilePathName(MultipartFile file, boolean isRetain) {
String fileName = file.getOriginalFilename();
String name = fileName;
String fileSuffix = "";
if (fileName.indexOf('.') != -1) {
name = fileName.substring(0, fileName.indexOf('.'));
fileSuffix = fileName.substring(fileName.lastIndexOf("."));
}
LocalDate date = LocalDate.now();
StringBuilder filePathName = new StringBuilder(PATH);
filePathName.append(date.getYear());
filePathName.append("/");
filePathName.append(date.getMonthValue());
filePathName.append("/");
filePathName.append(date.getDayOfMonth());
filePathName.append("/");
// 添加随机后缀
Random r = new Random();
int pix = r.ints(1, (100 + 1)).findFirst().getAsInt();
filePathName.append(System.currentTimeMillis());
filePathName.append("" + pix);
// 文件名超过32字符则截取
if (isRetain) {
filePathName.append("_");
if (name.length() >= 32) {
name = name.substring(0, 32);
}
filePathName.append(name);
}
filePathName.append(fileSuffix);
return filePathName.toString();
}
/**
* 处理文件扩展名
*
* @param fileSuffix
* @return
*/
public String getcontentType(String fileSuffix) {
if (fileSuffix.equalsIgnoreCase(".bmp")) {
return "image/bmp";
}
if (fileSuffix.equalsIgnoreCase(".gif")) {
return "image/gif";
}
if (fileSuffix.equalsIgnoreCase(".jpeg") || fileSuffix.equalsIgnoreCase(".jpg")
|| fileSuffix.equalsIgnoreCase(".png")) {
return "image/jpeg";
}
if (fileSuffix.equalsIgnoreCase(".html")) {
return "text/html";
}
if (fileSuffix.equalsIgnoreCase(".txt")) {
return "text/plain";
}
if (fileSuffix.equalsIgnoreCase(".vsd")) {
return "application/vnd.visio";
}
if (fileSuffix.equalsIgnoreCase(".pptx") || fileSuffix.equalsIgnoreCase(".ppt")) {
return "application/vnd.ms-powerpoint";
}
if (fileSuffix.equalsIgnoreCase(".docx") || fileSuffix.equalsIgnoreCase(".doc")) {
return "application/msword";
}
if (fileSuffix.equalsIgnoreCase(".xml")) {
return "text/xml";
}
// 默认返回类型
return "image/jpeg";
}
/**
* 创建存储空间
*
* @param bucketName
* @return
*/
public boolean createBucket(String bucketName) {
try {
boolean flag = ossClient.doesBucketExist(bucketName);
if (!flag) {
ossClient.createBucket(bucketName);
// 设置存储空间的权限为公共读,默认是私有。
ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 删除存储空间
*
* @param bucketName
* @return
*/
public boolean deleteBucket(String bucketName) {
try {
boolean flag = ossClient.doesBucketExist(bucketName);
if (flag) {
ossClient.deleteBucket(bucketName);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 列举所有的存储空间(名称)
*
* @return
*/
public List<String> listBuckets() {
List<Bucket> buckets = ossClient.listBuckets();
List<String> bucketNames = new ArrayList<>();
for (Bucket bucket : buckets) {
bucketNames.add(bucket.getName());
}
return bucketNames;
}
}
需要添加依赖
net.coobird</groupId>
thumbnailator</artifactId>
0.4.11</version>
</dependency>
/**
* 图像工具类
*/
public class ImageUtil {
// 压缩率
private static final transient float IMAGE_RATIO = 0.1f;
// 压缩最大宽度
private static final transient int IMAGE_WIDTH = 800;
// 水印透明度
private static float alpha = 0.3f;
// 水印文字字体
private static Font font = new Font("PingFang SC Regular", Font.PLAIN, 36);
// 水印文字颜色
private static Color color = new Color(111, 111, 111);
// 水印文字内容
private static final String text = "这是一个水印文本";
// 水印之间的间隔
private static final int XMOVE = 80;
// 水印之间的间隔
private static final int YMOVE = 80;
/**
* 压缩图像
*
* @param image
* @return
* @throws IOException
*/
public static BufferedImage compress(BufferedImage image) throws IOException {
Thumbnails.Builder<BufferedImage> imageBuilder = Thumbnails.of(image).outputQuality(IMAGE_RATIO);
if (image.getWidth() > IMAGE_WIDTH) {
return imageBuilder.width(IMAGE_WIDTH).asBufferedImage();
} else {
return imageBuilder.scale(1).asBufferedImage();
}
}
/**
* 图像添加水印
*
* @param
* @return
*/
public static BufferedImage setWatermark(BufferedImage image) throws IOException {
return Thumbnails.of(image).outputQuality(IMAGE_RATIO).scale(1)
.watermark(Positions.BOTTOM_RIGHT, createWatermark(text, image.getWidth(), image.getHeight()), alpha)
.asBufferedImage();
}
/**
* 根据文件扩展名判断文件是否图片格式
*
* @return
*/
public static boolean isImage(String fileName) {
String[] imageExtension = new String[] { "jpeg", "jpg", "gif", "bmp", "png" };
for (String e : imageExtension)
if (getFileExtention(fileName).toLowerCase().equals(e))
return true;
return false;
}
/**
* 获取文件后缀名称
*
* @param fileName
* @return
*/
public static String getFileExtention(String fileName) {
String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
return extension;
}
/**
* 根据图片对象获取对应InputStream
*
* @param image
* @param readImageFormat
* @return
* @throws IOException
*/
public static InputStream getInputStream(BufferedImage image, String readImageFormat) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(image, readImageFormat, os);
InputStream is = new ByteArrayInputStream(os.toByteArray());
os.close();
return is;
}
/**
* 创建水印图片
*
* @param text 水印文字
* @param width 图片宽
* @param height 图片高
* @return
*/
public static BufferedImage createWatermark(String text, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 2.获取图片画笔
Graphics2D g = image.createGraphics();
// ---------- 增加下面的代码使得背景透明 -----------------
image = g.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
g.dispose();
g = image.createGraphics();
// ---------- 背景透明代码结束 -----------------
// 6、处理文字
AttributedString ats = new AttributedString(text);
ats.addAttribute(TextAttribute.FONT, font, 0, text.length());
// 7、设置对线段的锯齿状边缘处理
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 8、设置水印旋转
g.rotate(Math.toRadians(-30));
// 9、设置水印文字颜色
g.setColor(color);
// 10、设置水印文字Font
g.setFont(font);
// 11、设置水印文字透明度
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
/**
* 水印铺满图片 计算水印位置
*/
int x = -width / 2;
int y = -height / 2;
int[] arr = getWidthAndHeight(text, font);
int markWidth = arr[0];// 字体长度
int markHeight = arr[1];// 字体高度
// 循环添加水印
while (x < width * 1.5) {
y = -height / 2;
while (y < height * 1.5) {
g.drawString(text, x, y);
y += markHeight + YMOVE;
}
x += markWidth + XMOVE;
}
// 13、释放资源
g.dispose();
return image;
}
/**
* 计算字体宽度及高度
*
* @param text
* @param font
* @return
*/
private static int[] getWidthAndHeight(String text, Font font) {
Rectangle2D r = font.getStringBounds(text,
new FontRenderContext(AffineTransform.getScaleInstance(1, 1), false, false));
int unitHeight = (int) Math.floor(r.getHeight());//
// 获取整个str用了font样式的宽度这里用四舍五入后+1保证宽度绝对能容纳这个字符串作为图片的宽度
int width = (int) Math.round(r.getWidth()) + 1;
// 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度
int height = unitHeight + 3;
return new int[] { width, height };
}
}
代码托管:springboot_oss