背景
前期快速迭代接口,接口返回的数据基本都是VO层包装返回给客户端,那如果我要对这成千上万个接口的VO层做字段数据的修改应该怎么操作? 可以实现全局替换吗? 可以按需优雅替换吗? 本文围绕一个真实使用案例进行阐述,案例为我们使用的文件存储OSS,前期因为没有使用CDN, 数据库、客户端数据返回都直接是OSS的源站域名,现在需要将源站资源地址改成可以全局替换/按需替换CDN地址的方式。
需求梳理
实现
- CDN解析器
public interface CDNResolver {
/**
* 替换CDN地址(默认类型OSS)
*
* @param sourceDomain 源站域名
* @param distDomain 目标域名
*/
void replaceUrl(String sourceDomain, String distDomain);
/**
* 替换CDN地址
*
* @param cdnSourceType
* @param sourceDomain
* @param distDomain
*/
default void replaceUrl(CDNSourceType cdnSourceType, String sourceDomain, String distDomain) {
}
}
- CDN源站类型定义
@AllArgsConstructor
@Getter
public enum CDNSourceType {
/**
* OSS
*/
OSS(1),
/**
* H5页面
*/
H5(2),
/**
* web项目
*/
WEB(3),
;
private final int sourceType;
}
- CDN替换工具类
public class CDNUtil {
/**
* 替换OSS CDN域名
*
* @param url
* @param sourceDomain
* @param distDomain
* @return
*/
public static String replaceOSSDomain(String url, String sourceDomain, String distDomain) {
return replaceDomain(url, false, CDNSourceType.OSS.getSourceType(), sourceDomain, distDomain);
}
/**
* 替换OSS CDN域名
*
* @param url
* @param replaceAll
* @param sourceDomain
* @param distDomain
* @return
*/
public static String replaceOSSDomain(String url, boolean replaceAll, String sourceDomain, String distDomain) {
return replaceDomain(url, replaceAll, CDNSourceType.OSS.getSourceType(), sourceDomain, distDomain);
}
/**
* 替换CDN域名
*
* @param url
* @param replaceAll
* @param cdnSourceType
* @param sourceDomain
* @param distDomain
* @return
*/
public static String replaceDomain(String url, boolean replaceAll, int cdnSourceType, String sourceDomain, String distDomain) {
if (StringUtils.isAnyBlank(url, sourceDomain, distDomain)) {
return url;
}
if (!url.contains(sourceDomain)) {
return url;
}
if (cdnSourceType == CDNSourceType.OSS.getSourceType()) {
return replaceAll ? url.replaceAll(sourceDomain, distDomain) :
url.replace(sourceDomain, distDomain);
}
return url;
}
}
- VO层实现CDN解析器
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LiveRoomMicVo implements Serializable, CDNResolver {
/**
* 麦上用户ID 为null则代表麦上没人
*/
private Long userId;
/**
* 麦位位置 (1-8)
*/
private int position;
/**
* 麦上用户昵称
*/
private String nickname;
/**
* 麦上用户头像
*/
private String avatar;
/**
* 是否老板麦位 true:是 false:否
*/
private boolean bossPosition;
/**
* 麦位状态 {@link com.miyo.user.entity.room.enums.LiveRoomMicState}
*/
private int state;
/**
* 实时上麦魅力值
*/
private BigDecimal charmScore;
/**
* 用户佩戴的头像框
*/
private String avatarFrameUrl;
/**
* 用户佩戴声波
*
* @return
*/
private String soundWaveUrl;
/**
* 是否主麦位
*/
private boolean primaryPosition;
/**
* 图标
* @return
*/
private String icon;
@Override
public void replaceUrl(String sourceDomain, String distDomain) {
this.userAvatar = CDNUtil.replaceOSSDomain(this.userAvatar, sourceDomain, distDomain);
this.roomSignIcon = CDNUtil.replaceOSSDomain(this.roomSignIcon, sourceDomain, distDomain);
this.beautifulNumber = CDNUtil.replaceOSSDomain(this.beautifulNumber, sourceDomain, distDomain);
this.hallLevel = CDNUtil.replaceOSSDomain(this.hallLevel, sourceDomain, distDomain);
}
}
- CDN Nacos动态配置
@Data
@Component
@ConfigurationProperties("cdn")
@RefreshScope
public class CDNNacosConfig implements Serializable {
/**
* OSS源站域名
*/
private String OSS_DOMAIN = "xxx.oss-ap-southeast-1.aliyuncs.com";
/**
* CDN加速域名
*/
private String OSS_CDN_DOMAIN = "cdn-xxx.xxx.com";
}
- 响应过程中需要拦截的对象
- 全局响应拦截处理
@Slf4j(topic = "TOPIC_COMMON")
@ControllerAdvice
public class GlobalResponseBodyAdviceAdapter implements ResponseBodyAdvice {
/**
* CDN配置
*/
@Autowired
private CDNNacosConfig cdnNacosConfig;
@Override
public boolean supports(MethodParameter returnType, Class> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
long startTime = System.nanoTime();
resolverObject(body);
long endTime = System.nanoTime();
double duration = (endTime - startTime) / 1_000_000.0; // 转为毫秒
log.info("替换CDN地址耗时:{}ms", duration);
return body;
}
/**
* 解析对象
*
* @param body
*/
private void resolverObject(Object body) {
if (body instanceof CDNResolver) {
//调用替换CDN接口方法
((CDNResolver) body).replaceUrl(cdnNacosConfig.getOSS_DOMAIN(), cdnNacosConfig.getOSS_CDN_DOMAIN());
} else if (body instanceof Resp) {
Resp resp = (Resp) body;
//递归解析
resolverObject(resp.getData());
} else if (body instanceof List) {
List list = (List) body;
for (Object object : list) {
//递归解析
resolverObject(object);
}
} else if (body instanceof PageResult) {
PageResult pageResult = (PageResult) body;
if (!CollectionUtils.isEmpty(pageResult.getRecord())) {
//递归解析
resolverObject(pageResult.getRecord());
}
}
}
}
总结