前言
本章内容主要记录品牌服务的开发,重点在于阿里云平台oss对象存储、后端数据参数校验JSR303的使用。
一、品牌服务
品牌服务用于管理品牌的添加、修改、删除等操作,可上传logo到图片服务器中,数据库存储的是图片地址,对于品牌的操作主要涉及添加和修改的操作,后端对于数据的校验分为普通校验、分组校验、自定义校验三种方式来校验数据。
二、图片服务
1.开通oss对象存储服务
首先,图片存储服务不止阿里云有,还有像七牛云之类的都有类似的服务,前期使用七牛云主要七牛云存储过程与本项目有点出入,所以这里还是选择阿里云平台进行存储。
- 首先找到oss对象存储并开通服务
-
创建Bucket
箭头标注的为比较重要的点,名称在项目中会使用到,而公共读是方便开发,后期可以修改私有
-
找到AccessKey和SecretKey
-
添加权限
- 找到外网访问域名
-
配置跨域
这里是配置了post的所有请求
-
找到java的示例
至此阿里云平台的配置就完成了,可以进行图片服务的开发了
2.图片储存服务
- 创建图片服务并在注册中心中注册
- 添加maven依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
这里的依赖是使用了cloud的依赖,这样可以直接使用ossClient实例,官方依赖如下:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
- 编写接口类
@RequestMapping("/oss/policy")
public R policy() {
// 填写Host地址,格式为https://bucketname.endpoint。
String host = "https://" + bucket + "." + endpoint;
// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
// String callbackUrl = "https://192.168.0.0:8888";
// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format + "/";
Map<String, String> respMap = null;
// 创建ossClient实例。
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
// respMap.put("expire", formatISO8601Date(expiration));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
}
return R.ok().put("data", respMap);
}
- nacos添加配置
spring:
cloud:
alicloud:
access-key: My access-key
secret-key: My secret-key
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
bucket: guli-smz
至此图片服务编写完成
三、后端数据校验
- 首先加入校验的相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.1.1</version>
</dependency>
1、普通校验
- 给Bean添加注解:javax.validation.constraints,定义自己的message提示
@NotNull(message ="修改必须指定品牌id")
@Null(message = "新增不能指定id")
@TableId
private Long brandId;
- 开启校验,在controller层添加注解@Valid
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand/*, BindingResult result*/){
- 由于错误存在于所有服务,所以在common编写一个错误状态码和错误信息定义枚举类
/**
* 错误状态码和错误信息定义类
* 1、错误码定义规则为5位数字
* 2、前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3、维护错误码后需要维护错误描述,将他们定义为枚举形式
*
* 错误码列表:
* 10:通用
* 001:参数格式校验
* 11:商品
* 12:订单
* 13:购物车
* 14:物流
*/
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001, "参数格式校验失败");
private final int code;
private final String msg;
BizCodeEnume(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode(){
return code;
}
public String getMsg(){
return msg;
}
}
- 编写集中异常处理类
@Slf4j
@RestControllerAdvice(basePackages = "com.smz.guli.product.controller")
public class GuliExceptionControllerAdvice {
/**
*
* @param e 捕获的精确异常
* @return 返回异常信息集合
*/
@ExceptionHandler(value = Exception.class)
public R handleVaildException(MethodArgumentNotValidException e){
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError -> {
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
}));
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(), BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
}
/**
*
* @param throwable 其它异常捕获
* @return 返回错误
*/
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("错误", throwable);
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(), BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
在规则中regexp为正则校验,message为自定义信息,groups为规则组
@Pattern(regexp = "^[a-zA-Z]$",message = "检索首字母必须为字母",groups = {AddGroup.class, UpdateGroup.class})
常用注解如下:
注解 | 作用 |
---|---|
@Null | 被注释的元素必须为null |
@NotNull | 被注释的元素不能为null,可以为空字符串 |
@AssertTrue | 被注释的元素必须为true |
@AssertFalsel | 被注释的元素必须为false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max,min) | 被注释的元素的大小必须在指定的范围内 |
@Digits(integer,fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
被注释的元素必须是电子邮件地址 | |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@Range | 被注释的元素必须在合适的范围内 |
@NotEmpty | 用在集合类上,不能为null,并且长度必须大于0 |
@NotBlank | 只能作用在String上,不能为null,而且调用trim()后,长度必须大于0 |
2、分组校验
分组校验的场景用于同一接口的不同操作来进行校验,不同操作也适用于其它接口。
- 在common编写接口用于区分不同的分组
/**
* 添加分组
*/
public interface AddGroup {
}
/**
* 修改分组
*/
public interface UpdateGroup {
}
- 在实体类添加注解用于区分不同操作对应的分组
/**
* 品牌id
*/
@NotNull(message ="修改必须指定品牌id", groups = {UpdateGroup.class})
@Null(message = "新增不能指定id", groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名不能为空", groups = {AddGroup.class})
private String name;
- 更换controller层注释为@Validated并添加校验分组
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand/*, BindingResult result*/){
brandService.save(brand);
return R.ok();
}
3、自定义校验
自定义校验用于复杂的校验逻辑,用于处理内置校验规则处理不了的数据校验。
- 创建一个配置文件用于存放自定义消息
文件名必须为 ValidationMessages.properties 会自动从该文件找配置项
com.smz.common.valid.ListValue.message=必须填写该项
- 创建一个自定义注解类
@Documented
@Constraint(
validatedBy = {ListValueConstraintValidator.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
// 自定义消息指向
String message() default "{com.smz.common.valid.ListValue.message}";
//实现规则的选取
Class<?>[] groups() default {};
//接收的消息体
Class<? extends Payload>[] payload() default {};
//自定义接收参数
int[] vals() default {};
}
- 创建一个校验器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* @ListValue 校验器
* ListValue 校验注解
* Integer 校验类型
* initialize 初始化方法 (获取详细信息)
* isValid 判断是否校验成功
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private final Set<Integer>set = new HashSet<>();
/**
*
* @param constraintAnnotation 初始化方法
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals){
set.add(val);
}
}
/**
*
* @param value 需要校验的值
* @param context 定义是否包含
* @return 返回是否包含
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
- 使用自定义校验
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals={0,1},groups = {AddGroup.class})
private Integer showStatus;
总结
本文主要讲了阿里OSS对象储存的对接以及后端JSR303数据校验的三种使用。