概况
- 项目使用第三方
BladeX-Boot
单体版本框架,Spring Boot
、Mybatis
、MyBatis-Plus
- 项目数据库表使用租户字段
tenant_id
隔离租户数据 - 使用阿里云OSS存储文件,并且每个租户使用独立的OSS空间和域名,方便租户使用自己的OSS和域名
- 前端上传到OSS的文件名必须为
nocheck_
前缀开头,OSS设置的生命周期规则会删除一天前上传的nocheck_
前缀文件,避免未保存到数据库的文件占用OSS空间(比如用户上传文件后,没有提交表单)
需求
- 1.由于每个租户OSS域名不同,需要接口出参时,根据本条数据的租户来拼接OSS域名
- 2.前端上传到OSS的文件名称都是
nocheck_
前缀开头,接口保存数据时需要去掉nocheck_
前缀 - 3.数据库保存或修改数据时,需要去掉OSS域名,只保存OSS文件路径
实现
- 创建注解,在实体类使用注解标识OSS字段,方便后续处理,注解如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 阿里云文件注解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AliOssCheck {
/**
* 是否 List<String>,false代表String,true代表List<String>
*/
boolean isJsonList() default false;
}
- 在实体类的OSS字段添加注解,比如用户头像:
@Data
@TableName("gecco_user")
@EqualsAndHashCode(callSuper = true)
public class User extends TenantEntity {
private static final long serialVersionUID = 1L;
/**
* 头像
*/
@AliOssCheck
private String avatar;
}
接口出参拼接OSS域名
- 添加域名的方法,通过判断传入对象是否有OSS注解进行拼接,方法如下:
/**
* 添加域名
*/
public void addOssDomain(Collection<Object> list) {
for (Object obj : list) {
if (obj == null) {
continue;
}
//处理有AliOssCheck的字段
Field[] fields = ReflectUtil.getFields(obj.getClass());
for (Field field : fields) {
AliOssCheck annotation = field.getAnnotation(AliOssCheck.class);
if (annotation == null) {
continue;
}
Object value = ReflectUtil.getFieldValue(obj, field);
if (value == null) {
continue;
}
//获取租户oss
Object tenantId = ReflectUtil.getFieldValue(obj, GeccoConstant.DB_TENANT_KEY);
Oss oss = SpringUtil.getBean(IOssService.class).selectEnableOssCache(tenantId != null ? tenantId.toString() : TokenUtil.DEFAULT_TENANT_ID);
if (oss == null || StrUtil.isBlank(oss.getDomain())) {
continue;
}
if (annotation.isJsonList()) {
List<Object> valueList = (List) value;
ReflectUtil.setFieldValue(obj, field, valueList.stream().map(item -> {
if (item instanceof String) {
//富文本不处理
return item.toString().contains("<") ? item : oss.getDomain() + "/" + item;
} else {
return item;
}
}).collect(Collectors.toList()));
} else {
ReflectUtil.setFieldValue(obj, field, oss.getDomain() + "/" + value);
}
}
}
}
UserWrapper
中调用拼接域名的方法,代码如下:
/**
* 包装类,返回视图层所需的字段
*/
public class UserWrapper extends BaseEntityWrapper<User, UserVO> {
public static UserWrapper build() {
return new UserWrapper();
}
@Override
public UserVO entityVO(User user) {
UserVO userVO = Objects.requireNonNull(BeanUtil.copy(user, UserVO.class));
//添加oss域名
OssWrapper.build().addOssDomain(Collections.singletonList(userVO));
return userVO;
}
}
保存时去掉域名和前缀
- 比如
https://www.xinpapa.com/aaa/bbb/nocheck_file.jpg
保存为aaa/bbb/file.jpg
- 使用切面实现,切入点为框架的保存和更新方法,代码如下:
- 注:调用save、update等方法时,不能使用this调用,否则切面不生效;比如
this.save(user)
改为SpringUtil.getBean(IUserService.class).save(user)
/**
* 阿里文件切面
*/
@Aspect
@Component
@Slf4j
public class OssCheckAspect {
static final String NOCHECK = "nocheck_";
/**
* Pointcut 切入点
*/
@Pointcut("execution(public * com.transnal.core.mp.base.BaseServiceImpl.save*(..)) || " +
"execution(public * com.transnal.core.mp.base.BaseServiceImpl.update*(..))")
public void ossCheck() {
}
/**
* 方法执行前
*/
@Before(value = "ossCheck()")
public void before(JoinPoint joinPoint) {
if (log.isDebugEnabled()) {
log.debug("AOP 开始替换上传{}文件名", NOCHECK);
}
Object[] args = joinPoint.getArgs();
Object firstParam = args[0];
if (firstParam instanceof Collection) {
Collection list = (Collection) firstParam;
for (Object o : list) {
checkFile(o);
}
} else {
checkFile(firstParam);
}
}
private Object checkFile(Object m) {
try {
Field[] fields = ReflectUtil.getFields(m.getClass());
for (Field field : fields) {
boolean fieldExist = field.isAnnotationPresent(AliOssCheck.class);
if (fieldExist) {
Object oldValue = ReflectUtil.getFieldValue(m, field);
if (oldValue == null) {
continue;
}
AliOssCheck annotation = field.getAnnotation(AliOssCheck.class);
Object newValue = oldValue;
if (annotation.isJsonList()) {
newValue = checkFileList((List) oldValue);
} else {
newValue = checkFile((String) oldValue);
}
ReflectUtil.setFieldValue(m, field.getName(), newValue);
}
}
} catch (SecurityException e) {
log.error("SecurityException", e);
} catch (UtilException e) {
log.error("UtilException", e);
}
return m;
}
private String checkFile(String fileName) {
if (StrUtil.isBlank(fileName)) {
return null;
}
//如果不是富文本但是有域名,则去掉域名
if (!fileName.contains("<") && fileName.contains("http")) {
fileName = CommonUtil.getOssPath(fileName);
}
if (!fileName.contains(NOCHECK)) {
return fileName;
}
IOssService iOssService = SpringUtil.getBean(IOssService.class);
Oss oss = iOssService.selectEnableOssCache(SecureUtil.getTenantId());
OSS aliOssClient = new OSSClientBuilder().build(oss.getEndpoint(), oss.getAccessKey(), oss.getSecretKey());
Pattern pattern = Pattern.compile("([\\w|-]+/)*" + NOCHECK + "\\S{1,60}\\.\\w{1,5}");
Matcher matcher = pattern.matcher(fileName);
StringBuffer stringBuffer = new StringBuffer();
while (matcher.find()) {
String nocheckName = matcher.group();
String newName = nocheckName.replace(NOCHECK, "");
matcher.appendReplacement(stringBuffer, newName);
try {
aliOssClient.copyObject(oss.getBucketName(), nocheckName, oss.getBucketName(), newName);
} catch (Exception e) {
log.error("ossCheck异常", e);
}
}
return stringBuffer.toString();
}
private List<String> checkFileList(List<String> fileNames) {
if (fileNames == null) {
return null;
}
return fileNames.stream().map(this::checkFile).collect(Collectors.toList());
}
}