一、对象存储OSS服务开通及配置
1.1、开通OSS
1.2、进入管理控制台
1.3、控制台使用
1.3.1、创建Bucket
命名:20230309-oss
读写权限:公共读
1.3.2、上传默认头像
创建文件夹 avater,上传默认的用户头像
1.4、使用RAM子用户
1.4.1、添加用户
登录名称:20230309-oss
访问方式:编程访问
1.4.2、获取子用户的AccessKey ID 、AccessKey Secret
AccessKey ID 、AccessKey Secret
1.4.3、设置用户权限
AliyunOSSFullAccess
二、Junit测试
2.1、新建普通Maven项目
<dependencies>
<!--aliyun OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
<!--工具-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
</dependencies>
2.2、测试
@Slf4j
class OSSTests {
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "your endpoint";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId = "your accessKeyId";
String accessKeySecret = "your accessKeySecret";
String bucketName = "your bucketName";
/**
* 创建Bucket
*/
@Test
public void testCreateBucket() {
// 创建 ossClient 实例
OSS ossClient = new OSSClientBuilder().build(endpoint,accessKeyId,accessKeySecret);
// 创建存储空间
ossClient.createBucket(bucketName);
// 关闭 ossClient
ossClient.shutdown();
log.info("创建Bucket成功!");
}
/**
* 检查Bucket是否存在
*/
@Test
public void testBucketExist() {
// 创建 ossClient 实例
OSS ossClient = new OSSClientBuilder().build(endpoint,accessKeyId,accessKeySecret);
boolean exist = ossClient.doesBucketExist(bucketName);
log.info("exist:{}",exist);
// 关闭 ossClient
ossClient.shutdown();
}
/**
* 设置Bucket的访问权限
*/
@Test
public void testAccessControl() {
// 创建 ossClient 实例
OSS ossClient = new OSSClientBuilder().build(endpoint,accessKeyId,accessKeySecret);
// 设置存储空间的访问权限为:公共读
ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
// 关闭 ossClient
ossClient.shutdown();
log.info("设置Bucket的访问权限成功!");
}
}
三、微服务
3.1、新建springboot项目
aliyun-oss-sample
<dependencies>
<!--aliyun OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
<!--工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>
<!-- swagger ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-bean-validators</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
<!--
用于解决刷新 swagger-ui 页面,控制台报错 swagger For input string: "" 的问题
原文链接:https://blog.csdn.net/hkl_Forever/article/details/119639839
-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3.2、代码整体结构
3.3、application.yml
server:
port: 9000
spring:
application:
name: aliyun-oss-sample
aliyun:
oss:
endpoint: your endpoint
accessKeyId: your accessKeyId
accessKeySecret: your accessKeySecret
bucketName: your bucketName
3.4、AliyunOssSampleApplication
package org.star;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class AliyunOssSampleApplication {
public static void main(String[] args) {
SpringApplication.run(AliyunOssSampleApplication.class, args);
}
}
3.5、org.star.utils/OssProperties
package org.star.utils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Data
@Component
//@ConfigurationProperties(prefix = "aliyun.oss")
public class OssProperties implements InitializingBean {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKeyId}")
private String keyId;
@Value("${aliyun.oss.accessKeySecret}")
private String keySecret;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
public static String ENDPOINT;
public static String KEY_ID;
public static String KEY_SECRET;
public static String BUCKET_NAME;
// 当私有成员被赋值后,此方法被自动调用,从而初始化常量
@Override
public void afterPropertiesSet() throws Exception {
log.info("endpoint:{},keyId:{},keySecret:{},bucketName:{}",endpoint,keyId,keySecret,bucketName);
ENDPOINT = endpoint;
KEY_ID = keyId;
KEY_SECRET = keySecret;
BUCKET_NAME = bucketName;
}
}
3.6、org.star.result
3.6.1、ResponseEnum
package org.star.result;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
@Getter
@AllArgsConstructor
@ToString
public enum ResponseEnum {
SUCCESS(0, "成功"),
ERROR(-1, "服务器内部错误"),
//-1xx 服务器错误
BAD_SQL_GRAMMAR_ERROR(-101, "sql语法错误"),
SERVLET_ERROR(-102, "servlet请求异常"), //-2xx 参数校验
UPLOAD_ERROR(-103, "文件上传错误"),
EXPORT_DATA_ERROR(104, "数据导出失败"),
//-2xx 参数校验
BORROW_AMOUNT_NULL_ERROR(-201, "借款额度不能为空"),
MOBILE_NULL_ERROR(-202, "手机号码不能为空"),
MOBILE_ERROR(-203, "手机号码不正确"),
PASSWORD_NULL_ERROR(204, "密码不能为空"),
CODE_NULL_ERROR(205, "验证码不能为空"),
CODE_ERROR(206, "验证码错误"),
MOBILE_EXIST_ERROR(207, "手机号已被注册"),
LOGIN_MOBILE_ERROR(208, "用户不存在"),
LOGIN_PASSWORD_ERROR(209, "密码错误"),
LOGIN_LOKED_ERROR(210, "用户被锁定"),
LOGIN_AUTH_ERROR(-211, "未登录"),
USER_BIND_IDCARD_EXIST_ERROR(-301, "身份证号码已绑定"),
USER_NO_BIND_ERROR(302, "用户未绑定"),
USER_NO_AMOUNT_ERROR(303, "用户信息未审核"),
USER_AMOUNT_LESS_ERROR(304, "您的借款额度不足"),
LEND_INVEST_ERROR(305, "当前状态无法投标"),
LEND_FULL_SCALE_ERROR(306, "已满标,无法投标"),
NOT_SUFFICIENT_FUNDS_ERROR(307, "余额不足,请充值"),
PAY_UNIFIEDORDER_ERROR(401, "统一下单错误"),
ALIYUN_SMS_LIMIT_CONTROL_ERROR(-502, "短信发送过于频繁"),//业务限流
ALIYUN_SMS_ERROR(-503, "短信发送失败"),//其他失败
WEIXIN_CALLBACK_PARAM_ERROR(-601, "回调参数不正确"),
WEIXIN_FETCH_ACCESSTOKEN_ERROR(-602, "获取access_token失败"),
WEIXIN_FETCH_USERINFO_ERROR(-603, "获取用户信息失败"),
;
//响应状态码
private Integer code;
//响应信息
private String message;
}
3.6.2、R
package org.star.result;
import lombok.Data;
@Data
public class R<T> {
private Integer code;
private String message;
private T data;
/**
* 构造函数私有化
*/
private R(){}
/**
* 返回成功结果
* @return
*/
public static R ok(){
R r = new R();
r.setCode(ResponseEnum.SUCCESS.getCode());
r.setMessage(ResponseEnum.SUCCESS.getMessage());
return r;
}
/**
* 返回失败结果
* @return
*/
public static R error(){
R r = new R();
r.setCode(ResponseEnum.ERROR.getCode());
r.setMessage(ResponseEnum.ERROR.getMessage());
return r;
}
/**
* 设置特定的结果
* @param responseEnum
* @return
*/
public static R setResult(ResponseEnum responseEnum){
R r = new R();
r.setCode(responseEnum.getCode());
r.setMessage(responseEnum.getMessage());
return r;
}
public R data(T entity) {
this.setData(entity);
return this;
}
/**
* 设置特定的响应消息
* @param message
* @return
*/
public R message(String message){
this.setMessage(message);
return this;
}
/**
* 设置特定的响应码
* @param code
* @return
*/
public R code(Integer code){
this.setCode(code);
return this;
}
}
3.7、org.star.exception
3.7.1、BusinessException
package org.star.exception;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.star.result.ResponseEnum;
@Data
@NoArgsConstructor
public class BusinessException extends RuntimeException {
//错误码
private Integer code;
//错误消息
private String message;
/**
*
* @param message 错误消息
*/
public BusinessException(String message) {
this.message = message;
}
/**
*
* @param message 错误消息
* @param code 错误码
*/
public BusinessException(String message, Integer code) {
this.message = message;
this.code = code;
}
/**
*
* @param message 错误消息
* @param code 错误码
* @param cause 原始异常对象
*/
public BusinessException(String message, Integer code, Throwable cause) {
super(cause);
this.message = message;
this.code = code;
}
/**
*
* @param resultCodeEnum 接收枚举类型
*/
public BusinessException(ResponseEnum resultCodeEnum) {
this.message = resultCodeEnum.getMessage();
this.code = resultCodeEnum.getCode();
}
/**
*
* @param resultCodeEnum 接收枚举类型
* @param cause 原始异常对象
*/
public BusinessException(ResponseEnum resultCodeEnum, Throwable cause) {
super(cause);
this.message = resultCodeEnum.getMessage();
this.code = resultCodeEnum.getCode();
}
}
3.7.2、UnifiedExceptionHandler
package org.star.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.star.result.R;
import org.star.result.ResponseEnum;
@Slf4j
@Component
@RestControllerAdvice // 在controller层添加通知。如果使用@ControllerAdvice,则方法上需要添加@ResponseBody
public class UnifiedExceptionHandler {
/**
* 未定义异常,当controller中抛出异常时则捕获
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
public R handleException(Exception e) {
log.error(e.getMessage(),e);
return R.error();
}
/**
* 自定义异常
* @param e
* @return
*/
@ExceptionHandler(value = BusinessException.class)
public R handleException(BusinessException e) {
log.error(e.getMessage(),e);
return R.error().message(e.getMessage()).code(e.getCode());
}
@ExceptionHandler({
NoHandlerFoundException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
HttpMediaTypeNotAcceptableException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
MissingServletRequestPartException.class,
AsyncRequestTimeoutException.class
})
/**
* 处理Controller上层异常
* 如何测试:在save测试用例中输入非法的json参数,则得到下面的结果。我们可以在控制台日志中查看具体的错误原因。前端只需要返回相对简单友好的提示即可。
*/
public R handleServletException(Exception e) {
log.error(e.getMessage(),e);
return R.error().message(ResponseEnum.SERVLET_ERROR.getMessage()).code(ResponseEnum.SERVLET_ERROR.getCode());
}
}
3.7.3、Assert
package org.star.exception;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.star.result.ResponseEnum;
/**
* 自定义断言:用断言的方式封装抛出的异常
*/
@Slf4j
public abstract class Assert {
/**
* 断言对象不为空
* obj 为空则抛异常
* @param obj
* @param responseEnum
*/
public static void notNull(Object obj, ResponseEnum responseEnum){
if(obj == null){
log.info("obj is null.....................");
throw new BusinessException(responseEnum);
}
}
/**
* 断言对象为空
* 如果对象obj不为空,则抛出异常
* @param object
* @param responseEnum
*/
public static void isNull(Object object, ResponseEnum responseEnum) {
if (object != null) {
log.info("obj is not null......");
throw new BusinessException(responseEnum);
}
}
/**
* 断言对象不为空
* 如果对象obj为空,则抛出异常
* @param object
* @param responseEnum
*/
public static void isNotNull(Object object, ResponseEnum responseEnum) {
if (object == null) {
log.info("obj is null......");
throw new BusinessException(responseEnum);
}
}
/**
* 断言表达式为真
* 如果不为真,则抛出异常
*
* @param expression 是否成功
*/
public static void isTrue(boolean expression, ResponseEnum responseEnum) {
if (!expression) {
log.info("fail...............");
throw new BusinessException(responseEnum);
}
}
/**
* 断言两个对象不相等
* 如果相等,则抛出异常
* @param m1
* @param m2
* @param responseEnum
*/
public static void notEquals(Object m1, Object m2, ResponseEnum responseEnum) {
if (m1.equals(m2)) {
log.info("equals...............");
throw new BusinessException(responseEnum);
}
}
/**
* 断言两个对象相等
* 如果不相等,则抛出异常
* @param m1
* @param m2
* @param responseEnum
*/
public static void equals(Object m1, Object m2, ResponseEnum responseEnum) {
if (!m1.equals(m2)) {
log.info("not equals...............");
throw new BusinessException(responseEnum);
}
}
/**
* 断言参数不为空
* 如果为空,则抛出异常
* @param s
* @param responseEnum
*/
public static void notEmpty(String s, ResponseEnum responseEnum) {
if (StringUtils.isEmpty(s)) {
log.info("is empty...............");
throw new BusinessException(responseEnum);
}
}
}
3.8、org.star.service
3.8.1、FileService
package org.star.service;
import java.io.InputStream;
public interface FileService {
/**
* 文件上传至阿里云
* @param inputStream
* @param module
* @param fileName
* @return
*/
String upload(InputStream inputStream,String module,String fileName);
}
3.8.2、FileServiceImpl
package org.star.service;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CannedAccessControlList;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;
import org.star.utils.OssProperties;
import java.io.InputStream;
import java.util.UUID;
@Service
public class FileServiceImpl implements FileService {
@Override
public String upload(InputStream inputStream, String module, String fileName) {
// 创建OssClient实例
OSS ossClient = new OSSClientBuilder().build(OssProperties.ENDPOINT,OssProperties.KEY_ID,OssProperties.KEY_SECRET);
// 判断oss实例是否存在:如果不存在则创建,如果存在则获取
if (!ossClient.doesBucketExist(OssProperties.BUCKET_NAME)) {
// 创建Bucket
ossClient.createBucket(OssProperties.BUCKET_NAME);
// 设置oss实例的访问权限:公共读
ossClient.setBucketAcl(OssProperties.BUCKET_NAME, CannedAccessControlList.PublicRead);
}
// 构建日期路径:avater/2023/03/09/文件名
String folder = new DateTime().toString("yyyy/MM/dd");
// 文件名:uuid.扩展名
fileName = UUID.randomUUID().toString() + fileName.substring(fileName.lastIndexOf("."));
// 文件根路径
String key = module + "/" + folder + "/" + fileName;
// 文件上传至阿里云
ossClient.putObject(OssProperties.BUCKET_NAME,key,inputStream);
// 关闭ossClient
ossClient.shutdown();
// 阿里云文件绝对路径
return "https://" + OssProperties.BUCKET_NAME + "." + OssProperties.ENDPOINT + "/" + key;
}
}
3.9、org.star.controller/FileController
package org.star.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.star.exception.BusinessException;
import org.star.result.R;
import org.star.result.ResponseEnum;
import org.star.service.FileService;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
@Api(tags = "阿里云文件管理")
@CrossOrigin
@RestController
@RequestMapping("/api/oss/file")
public class FileController {
@Autowired
private FileService fileService;
@ApiOperation("文件上传")
@PostMapping("/upload")
public R upload(
@ApiParam(value = "文件",required = true)
@RequestParam(value = "file") MultipartFile file,
@ApiParam(value = "模块",required = true)
@RequestParam(value = "module") String module) {
try {
InputStream inputStream = file.getInputStream();
String originalFilename = file.getOriginalFilename();
String uploadUrl = fileService.upload(inputStream,module,originalFilename);
Map<String,Object> resultMap = new HashMap<>();
resultMap.put("url",uploadUrl);
// 返回R对象
return R.ok().message("文件上传成功").data(resultMap);
} catch (Exception e) {
throw new BusinessException(ResponseEnum.UPLOAD_ERROR,e);
}
}
}
3.10、org.star.config/Swagger2Config
package org.star.config;
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@EnableSwagger2
@EnableSwaggerBootstrapUI
@Configuration
public class Swagger2Config extends WebMvcConfigurerAdapter {
@Bean
public Docket aliyunOssDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName("阿里云文件管理")
.select()
.apis(RequestHandlerSelectors.basePackage("org.star.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("权限管理系统")
.description("权限管理系统API")
.termsOfServiceUrl("http://localhost:9000/doc.html")
.contact(new Contact("阿里巴巴权限管理系统项目", "http://localhost:9000/doc.html", "1293840962@qq.com"))
.version("1.0")
.build();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
3.11、Swagger2测试