文章目录
1. 使用WebClient使用远程调用
<!-- SpringBoot webflux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.Map;
===========================================================================
/**
* 远程调用,发送post请求
*
* @param pathUrl 请求的访问的路径 如:/art/approval
* @param requestBody 请求体(也可以是自定义的某个实体类)
* @return 请求返回的结果,用R接收
*/
private R remotePostCall(String pathUrl, Map<String, String> requestBody) {
WebClient client = WebClient.create();
// 发起POST请求,并设置请求头和请求体
Mono<R> responseMono = client.post()
.uri(uri + pathUrl)
// 设置请求头
.header(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
.header(HttpHeaders.AUTHORIZATION, "you token")
// 设置请求体
.body(BodyInserters.fromValue(requestBody))
.retrieve()
// 将返回结果转换为R类型
.bodyToMono(R.class);
R block = responseMono.block();
return block;
}
这个请求是异步的。在代码中,使用了 Mono 来表示返回结果, Mono 是一个Reactive类型,它表示可能会在未来某个时间点返回的结果。通过使用 bodyToMono() 方法将返回结果转换为 Mono 类型,然后使用 block() 方法来
阻塞
等待结果的返回。这种异步的方式可以提高应用程序的性能和并发处理能力。
可以使用 subscribe()
方法来实现不阻塞等待结果的返回。通过调用 subscribe()
方法,可以注册一个回调函数来处理异步返回的结果。这样,可以在结果返回时执行相应的操作,不需要阻塞等待。
private void remotePostCall(String pathUrl, Map<String, String> requestBody) {
WebClient client = WebClient.create();
// 发起POST请求,并设置请求头和请求体
client.post()
.uri(uri + pathUrl)
// 设置请求头
.header(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8")
.header(HttpHeaders.AUTHORIZATION, "you token")
// 设置请求体
.body(BodyInserters.fromValue(requestBody))
.retrieve()
// 处理返回结果
.bodyToMono(R.class)
.subscribe(result -> {
// 在这里处理异步返回的结果
// 可以执行相应的操作
System.out.println("异步结果:" + result);
});
}
2. 返回值将Long类型转换为String
在分布式项目中,我们主键喜欢使用雪花Id,可能会遇到一些精度丢失或数据截断的问题,特别是在处理大整数时。将 Long 类型转换为 String 类型可以避免这些问题,并确保数据的准确性和完整性。
通过自定义Jackson的对象映射器行为,可以确保在分布式项目中处理长整型数据时的一致性和可靠性。
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfiguration {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
// 返回结果时,统一将 Long 转换成 String
builder.serializerByType(Long.class, ToStringSerializer.instance);
};
}
}
3. 自定义注解
- 自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author shang tf
* @createTime 2023/8/15 10:35
* @Version 1.0.0
* 需要成为数商后才能访问
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface HasPayAuth {
}
- 切面拦截注解
import cn.creatoo.common.core.constant.Constants;
import cn.creatoo.common.security.utils.BdSecurityUtils;
import cn.creatoo.system.exception.NotPayAuthException;
import cn.creatoo.system.domain.BdTerminalUserAuth;
import cn.creatoo.system.mapper.BdTerminalUserAuthMapper;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @Author shang tf
* @createTime 2023/8/15 10:41
* @Version 1.0.0
*/
@Aspect
@Component
public class HasPayAuthAspect {
@Pointcut("@annotation(xx.xxxxx.system.annotation.HasPayAuth)")
private void pointcut() {
}
@Autowired
private BdTerminalUserAuthMapper bdTerminalUserAuthMapper;
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 查询当前登录账号信息
Long userId = BdSecurityUtils.getUserId();
QueryWrapper<BdTerminalUserAuth> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", userId);
BdTerminalUserAuth bdTerminalUserAuth = bdTerminalUserAuthMapper.selectOne(wrapper);
if (ObjectUtil.isEmpty(bdTerminalUserAuth)){
throw new NotPayAuthException("未提交认证,请先认证");
}
// 账号状态
if (!bdTerminalUserAuth.getStatus().equals(Constants.AUTH_AUDIT_CERTIFIED)){
throw new NotPayAuthException("账号未认证,请先认证");
}
// 判断账号是否被禁用
if (bdTerminalUserAuth.getIsAble().equals(Constants.AUTH_IS_ABLE_OFF)){
throw new NotPayAuthException("账号被禁用,请联系管理员");
}
// 放行
return joinPoint.proceed(joinPoint.getArgs());
}
}
- 自定义异常
自定义异常时需要继承RuntimeException
/**
* @Author shang tf
* @createTime 2023/8/15 10:58
* @Version 1.0.0
* 资质认证过期或未认证异常
*/
public class NotPayAuthException extends RuntimeException {
public NotPayAuthException() {
}
public NotPayAuthException(String message){
super(message);
}
}
4. Json可加密解密的工具
MD5加密
请注意,MD5算法是单向散列函数,不可逆。因此,我们无法从MD5加密后的值还原出原始字符串。MD5算法通常用于密码存储和验证等场景,但由于其安全性较低,现在更常用的是更强大的加密算法,如SHA-256等。
ADCryptUtils
通过此工具类可以将字符串进行加密解密,可以解析出原本的字符串
可用于两个系统传输数据,为防止有意者拦截到,可进行加密
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
public class ADCryptUtils {
static { try { Security.addProvider(new BouncyCastleProvider()); } catch (Exception e) { e.printStackTrace(); } }
// vi 076ce634d7da11eb
private static final byte[] DES_DEFAULT_KEY=new byte[] { 10, 20, 50, 99, 101, 54, 51, 52, 100, 55, 100, 97, 49, 49, 112, 98 };
private static final byte[] AES_DEFAULT_KEY=new byte[] { 10, 20, 50, 99, 101, 54, 51, 52, 100, 55, 100, 97, 49, 49, 112, 98 };
/**
* content: 加密内容
*/
public static String encryptAes(String content) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(AES_DEFAULT_KEY, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey,new IvParameterSpec(AES_DEFAULT_KEY));
byte[] encrypted = cipher.doFinal(content.getBytes());
return Base64.encode(encrypted);
}
/**
* content: 解密内容
*/
public static String decryptAes(String base64Content) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(AES_DEFAULT_KEY, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey,new IvParameterSpec(AES_DEFAULT_KEY));
byte[] content = Base64.decode(base64Content);
byte[] encrypted = cipher.doFinal(content);
return new String(encrypted);
}
/**
* content: 加密内容
*/
public static String encryptDes(String content) throws Exception {
Cipher cipher = Cipher.getInstance("DES");
SecretKey secretKey = new SecretKeySpec(DES_DEFAULT_KEY, "DES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encrypted = cipher.doFinal(content.getBytes());
return Base64.encode(encrypted);
}
/**
* content: 解密内容
*/
public static String decryptDes(String base64Content) throws Exception {
Cipher cipher = Cipher.getInstance("DES");
SecretKey secretKey = new SecretKeySpec(DES_DEFAULT_KEY, "DES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] content = Base64.decode(base64Content);
byte[] encrypted = cipher.doFinal(content);
return new String(encrypted);
}
/**
* content: 加密内容
* slatKey: 加密的盐,16位字符串
*/
public static String encryptAes(String content, String slatKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(slatKey.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey,new IvParameterSpec(AES_DEFAULT_KEY));
byte[] encrypted = cipher.doFinal(content.getBytes());
return Base64.encode(encrypted);
}
/**
* content: 解密内容
* slatKey: 加密时使用的盐,16位字符串
*/
public static String decryptAes(String base64Content, String slatKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(slatKey.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey,new IvParameterSpec(AES_DEFAULT_KEY));
byte[] content = Base64.decode(base64Content);
byte[] encrypted = cipher.doFinal(content);
return new String(encrypted);
}
/**
* content: 加密内容
* slatKey: 加密的盐,16位字符串
* vectorKey: 加密的向量,16位字符串
*/
public static String encryptAes(String content, String slatKey, String vectorKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(slatKey.getBytes(), "AES");
IvParameterSpec iv = new IvParameterSpec(vectorKey.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
byte[] encrypted = cipher.doFinal(content.getBytes());
return Base64.encode(encrypted);
}
/**
* content: 解密内容(base64编码格式)
* slatKey: 加密时使用的盐,16位字符串
* vectorKey: 加密时使用的向量,16位字符串
*/
public static String decryptAes(String base64Content, String slatKey, String vectorKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(slatKey.getBytes(), "AES");
IvParameterSpec iv = new IvParameterSpec(vectorKey.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
byte[] content = Base64.decode(base64Content);
byte[] encrypted = cipher.doFinal(content);
return new String(encrypted);
}
/**
* content: 加密内容
* slatKey: 加密的盐,8位字符串
*/
public static String encryptDes(String content, String slatKey) throws Exception {
Cipher cipher = Cipher.getInstance("DES");
SecretKey secretKey = new SecretKeySpec(slatKey.getBytes(), "DES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encrypted = cipher.doFinal(content.getBytes());
return Base64.encode(encrypted);
}
/**
* content: 解密内容
* slatKey: 加密时使用的盐,8位字符串
*/
public static String decryptDes(String base64Content, String slatKey) throws Exception {
Cipher cipher = Cipher.getInstance("DES");
SecretKey secretKey = new SecretKeySpec(slatKey.getBytes(), "DES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] content = Base64.decode(base64Content);
byte[] encrypted = cipher.doFinal(content);
return new String(encrypted);
}
public static void main(String[] args) {
try {
String encryptDes = ADCryptUtils.encryptAes("测试AES加密");
System.out.println(encryptDes);
String decryptDes = ADCryptUtils.decryptAes(encryptDes);
System.out.println(decryptDes);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 自制分页查询
很多时候我们的分页查询不是我们理想的单表的分页查询,使用mybatis_plus不能很好很便捷的满足我们的需求,我们可以手动模拟分页查询。
分页参数实体类
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 标的审核列表传参
*
* @author shang tf
* @version 1.0
* @data 2023/11/16 13:42
*/
@Data
@ApiModel("标的审核列表参数")
public class UniteAuditListParam {
/**
* 当前页
*/
@ApiModelProperty("当前页")
private Integer page;
/**
* 每页大小
*/
@ApiModelProperty("每页大小")
private Integer pageSize;
/**
* 条件查询标的名称
*/
@ApiModelProperty("标的名称")
private String uniteName;
/**
* 条件查询审核状态(0审核中 1已审核 2未通过)
*/
@ApiModelProperty("审核状态(0审核中 1已审核 2未通过)")
private Integer auditStatus;
/**
* 条件查询开始时间
*/
@ApiModelProperty("开始时间")
private String startTime;
/**
* 条件查询结束时间
*/
@ApiModelProperty("结束时间")
private String endTime;
}
分页服务层代码
@Override
public R uniteSourseAuditList(UniteAuditListParam uniteAuditListParam) {
if (ObjectUtil.isEmpty(uniteAuditListParam.getPage()) || ObjectUtil.isEmpty(uniteAuditListParam.getPageSize())) {
return R.fail("分页参数不能为空!");
}
// 如果条件查询没有传结束时间,则默认结束时间为现在时间
if (ObjectUtil.isNotEmpty(uniteAuditListParam.getStartTime()) && ObjectUtil.isEmpty(uniteAuditListParam.getEndTime())) {
uniteAuditListParam.setEndTime(LocalDateTime.now().toString());
}
// 如果条件查询没有传开始时间,则默认传入此时间为开始时间
if (ObjectUtil.isNotEmpty(uniteAuditListParam.getEndTime()) && ObjectUtil.isEmpty(uniteAuditListParam.getStartTime())) {
uniteAuditListParam.setStartTime("2023-07-24 00:00:00");
}
// 如果开始时间和结束时间都为空,将他们都设置成null,为了防止传入的值为空字符串"",主要是跟mapper层做配合
if (ObjectUtil.isEmpty(uniteAuditListParam.getEndTime()) && ObjectUtil.isEmpty(uniteAuditListParam.getStartTime())){
uniteAuditListParam.setStartTime(null);
uniteAuditListParam.setEndTime(null);
}
// 分页参数
int page = uniteAuditListParam.getPage();
int pageSize = uniteAuditListParam.getPageSize();
// 偏移量,用在MySQL中分页查询的起始值
int offset = (page - 1) * pageSize;
uniteAuditListParam.setPage(offset);
// 名称模糊查询
if (ObjectUtil.isNotEmpty(uniteAuditListParam.getUniteName())) {
uniteAuditListParam.setUniteName("%" + uniteAuditListParam.getUniteName() + "%");
}
// 查询总条数和数据列表
Integer count = bdGoodsSpuMapper.countUniteSourseAuditList(uniteAuditListParam);
// 如果总条数大于零,就去数据库查询列表,如果总条数为零不查询数据库,则为空数组
List<UniteAuditList> uniteAuditLists = count > 0 ? bdGoodsSpuMapper.uniteSourseAuditList(uniteAuditListParam) : new ArrayList<>();
// 构建返回结果
PageVo pageVo = new PageVo();
pageVo.setCurrent(page);
pageVo.setSize(pageSize);
pageVo.setTotal(count);
pageVo.setRecords(uniteAuditLists);
// 总页数 整除时不加1,没有整除才要加1
pageVo.setPages(count % pageSize == 0 ? count / pageSize : count / pageSize + 1);
return R.ok(pageVo);
}
mapper层SQL语句
<!-- 后台标的审核列表总条数 -->
<select id="countUniteSourseAuditList" resultType="java.lang.Integer">
SELECT
count(*)
FROM
bd_goods_spu spu
LEFT JOIN bd_terminal_user_auth auth ON auth.user_id = spu.create_user
<where>
spu.is_del = 0
<if test="auditParam.uniteName!=null and auditParam.uniteName!=''">
AND spu.unite_name LIKE #{auditParam.uniteName}
</if>
<if test="auditParam.auditStatus!=null and auditParam.auditStatus!=''">
AND spu.is_examine = #{auditParam.auditStatus}
</if>
<if test="auditParam.startTime!=null or auditParam.endTime !=null ">
AND spu.create_time >= #{auditParam.startTime} AND spu.create_time <= #{auditParam.endTime}
</if>
</where>
</select>
<!-- 后台标的审核列表 -->
<select id="uniteSourseAuditList" resultType="cn.creatoo.system.domain.uniteVo.UniteAuditList">
SELECT
spu.goods_spu_id goodsSpuId,
spu.unite_name uniteName,
spu.unite_description uniteDescription,
spu.data_num dataNum,
spu.cover_link coverLink,
CASE
spu.is_examine
WHEN 0 THEN
'审核中'
WHEN 1 THEN
'审核通过'
WHEN 2 THEN
'审核不通过' ELSE '不识别的审核状态'
END auditStatus,
auth.`name` createUser,
spu.create_time createTime
FROM
bd_goods_spu spu
LEFT JOIN bd_terminal_user_auth auth ON auth.user_id = spu.create_user
<where>
spu.is_del = 0
<if test="auditParam.uniteName!=null and auditParam.uniteName!=''">
AND spu.unite_name LIKE #{auditParam.uniteName}
</if>
<if test="auditParam.auditStatus!=null and auditParam.auditStatus!=''">
AND spu.is_examine = #{auditParam.auditStatus}
</if>
<if test="auditParam.startTime!=null or auditParam.endTime !=null ">
AND spu.create_time >= #{auditParam.startTime} AND spu.create_time <= #{auditParam.endTime}
</if>
</where>
ORDER BY spu.create_time DESC
LIMIT #{auditParam.page},#{auditParam.pageSize}
</select>
6. 查询目录下的资源总数
记录一个工作中的一个需求
目录表
数据表
现在想查询所有的一级目录。并且把一级目录下的资源的总条数也查出来,包括一级目录里的资源总数及子集目录包含的资源总数
WITH RECURSIVE subdirectories AS (
SELECT
dire_id,
dire_name,
parent_id,
data_classify_id,
is_del,
cover,
description,
`status`
FROM
bd_data_dire
WHERE
parent_id = 0
UNION ALL
SELECT
d.dire_id,
d.dire_name,
d.parent_id,
d.data_classify_id,
d.is_del,
d.cover,
d.description,
d.`status`
FROM
bd_data_dire d
INNER JOIN
subdirectories sd ON d.parent_id = sd.dire_id
)
SELECT
sd.dire_id direId,
sd.dire_name direName,
(
SELECT COUNT(*)
FROM bd_data_data r
WHERE r.dire_id = sd.dire_id
) + (
SELECT COUNT(*)
FROM bd_data_data r
WHERE r.dire_id IN (
SELECT dire_id
FROM subdirectories
WHERE parent_id = sd.dire_id
)
) AS resourceNum,
sd.parent_id parentId,
sd.data_classify_id dataClassifyId,
sd.cover,
sd.description
FROM
subdirectories sd
WHERE
sd.parent_id = 0 AND sd.is_del = 0 AND sd.`status` = 0
实际中代码
@Override
public R selectDataList(SelectDataListParam selectDataListParam) {
// 条件查询,属于某个分类下的目录
String[] classifyIds = selectDataListParam.getClassifyIds();
if (ObjectUtil.isEmpty(classifyIds)){
selectDataListParam.setClassifyIds(null);
}
List<DataListVo> bdDataDires = bdDataDireMapper.selectDataList(selectDataListParam);
return R.ok(bdDataDires);
}
Mapper层
<!-- 查询数据列表 -->
<select id="selectDataList" resultType="cn.creatoo.system.domain.vo.cabinet.DataListVo">
WITH RECURSIVE subdirectories AS (
SELECT
dire_id,
dire_name,
parent_id,
data_classify_id,
is_del,
cover,
description,
`status`,
order_num
FROM
bd_data_dire
WHERE
parent_id = 0
UNION ALL
SELECT
d.dire_id,
d.dire_name,
d.parent_id,
d.data_classify_id,
d.is_del,
d.cover,
d.description,
d.`status`,
d.order_num
FROM
bd_data_dire d
INNER JOIN
subdirectories sd ON d.parent_id = sd.dire_id
)
SELECT
sd.dire_id direId,
sd.dire_name direName,
(
SELECT COUNT(*)
FROM bd_data_data r
WHERE r.dire_id = sd.dire_id
) + (
SELECT COUNT(*)
FROM bd_data_data r
WHERE r.dire_id IN (
SELECT dire_id
FROM subdirectories
WHERE parent_id = sd.dire_id
)
) AS resourceNum,
sd.parent_id parentId,
sd.data_classify_id dataClassifyId,
sd.cover,
sd.description
FROM
subdirectories sd
WHERE
sd.parent_id = 0 AND sd.is_del = 0 AND sd.`status` = 0
<if test="selectDataListParam.classifyIds != null">
AND sd.data_classify_id IN
<foreach collection="selectDataListParam.classifyIds" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
ORDER BY
sd.order_num ASC
</select>
7. 递归查询菜单/目录
package cn.creatoo.system.utils;
import cn.creatoo.system.domain.vo.data.SelectDataClassifyVo;
import cn.creatoo.system.domain.vo.dire.SelectDataDireListVo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @author shang tf
* @version 1.0
* @data 2023/12/19 17:21
*/
@Data
public class TreeUtil {
/**
* @param allList
* @return
*/
public static List<SelectDataClassifyVo> fromAllListToTreeList(List<SelectDataClassifyVo> allList) {
List<SelectDataClassifyVo> treeList = new ArrayList<>();
//遍历所有的menu对象,然后发现menu对象有孩子,就继续便利孩子,递归操作
for (SelectDataClassifyVo dataClassify : allList) {
//如果父亲的id为0,就说明是一级目录
if (dataClassify.getParentId() == 0) {
//找自己的孩子,给tree对象设置孩子children
SelectDataClassifyVo treeChildren = setTreeChildren(dataClassify, allList);
treeList.add(treeChildren);
}
}
return treeList;
}
/**
* 找自己的孩子,给tree对象设置孩子children
* @param dataClassify
* @param allList
* @return
*/
private static SelectDataClassifyVo setTreeChildren(SelectDataClassifyVo dataClassify, List<SelectDataClassifyVo> allList) {
//此集合封装所有的孩子
List<SelectDataClassifyVo> children = new ArrayList<>();
for (SelectDataClassifyVo bdDataClassify : allList) {
//tree的id是他所有孩子的父亲id
if (Objects.equals(bdDataClassify.getParentId(), dataClassify.getDataClassifyId())) {
//递归设置自己的孩子
children.add(setTreeChildren(bdDataClassify, allList));
}
}
dataClassify.setChildren(children);
return dataClassify;
}
/**
* 将目录列表转换为树
* @param direListVoList
* @return
*/
public static List<SelectDataDireListVo> fromAllDireListToTreeList(List<SelectDataDireListVo> direListVoList) {
List<SelectDataDireListVo> treeList = new ArrayList<>();
//遍历所有的menu对象,然后发现menu对象有孩子,就继续便利孩子,递归操作
for (SelectDataDireListVo dataDireListVo : direListVoList) {
//如果父亲的id为0,就说明是一级目录
if (dataDireListVo.getParentId() == 0) {
//找自己的孩子,给tree对象设置孩子children,为子节点resourceNum累加值
setDireTreeResourceNum(dataDireListVo, direListVoList);
treeList.add(dataDireListVo);
}
}
return treeList;
}
private static int setDireTreeResourceNum(SelectDataDireListVo dataDireListVo, List<SelectDataDireListVo> direListVoList) {
// 获取当前节点的 resourceNum 值
int resourceNum = dataDireListVo.getResourceNum();
List<SelectDataDireListVo> children = new ArrayList<>();
for (SelectDataDireListVo direListVo : direListVoList) {
if (Objects.equals(direListVo.getParentId(), dataDireListVo.getDireId())) {
int childResourceNum = setDireTreeResourceNum(direListVo, direListVoList);
children.add(direListVo);
// 累加子节点的 resourceNum 值
resourceNum += childResourceNum;
}
}
dataDireListVo.setChildren(children);
// 设置当前节点的 resourceNum 值为子节点的累加值
dataDireListVo.setResourceNum(resourceNum);
// 返回当前节点的 resourceNum 值
return resourceNum;
}
}
8. 在spring boot中有些依赖已经被spring管理版本号也被默认。如果想修改版本号,可以在properties中指定版本。如下:
9. 分布式若依中openfeign的使用
- 分布式项目中若依使用feign一般是在 api 模块中
下面是目录结构
- 编写feign接口
package cn.creatoo.system.api;
import cn.creatoo.common.core.constant.ServiceNameConstants;
import cn.creatoo.common.core.domain.vo.UploadM3u8Vo;
import cn.creatoo.common.core.domain.vo.WaterVo;
import cn.creatoo.system.api.factory.RemoteSystemDataServiceFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @author shang tf
* @version 1.0
* @data 2024/1/19 15:23
*/
@FeignClient(contextId = "remoteSystemDataService",value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteSystemDataServiceFallbackFactory.class)
public interface RemoteSystemDataService {
/**
* 给资源添加m3u8链接
* @param uploadM3u8Vo
*/
@PostMapping("data/uploadM3U8Callback")
void saveM3u8Link(@RequestBody UploadM3u8Vo uploadM3u8Vo);
/**
* 给资源添加水印链接
* @param waterVo
*/
@PostMapping("data/saveWaterLink")
void saveWaterLink(WaterVo waterVo);
}
注意
@FeignClient
注解中的contextId
如果有多个此模块的feign要指定一下contexId
- 编写降级处理类
package cn.creatoo.system.api.factory;
import cn.creatoo.common.core.domain.vo.UploadM3u8Vo;
import cn.creatoo.common.core.domain.vo.WaterVo;
import cn.creatoo.system.api.RemoteSystemDataService;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* @author shang tf
* @version 1.0
* @data 2024/1/19 15:24
*/
@Component
public class RemoteSystemDataServiceFallbackFactory implements FallbackFactory<RemoteSystemDataService> {
@Override
public RemoteSystemDataService create(Throwable cause) {
return new RemoteSystemDataService() {
@Override
public void saveM3u8Link(UploadM3u8Vo uploadM3u8Vo) {
return;
}
@Override
public void saveWaterLink(WaterVo waterVo) {
return;
}
};
}
}
@Component
标识此类为一个bean,但是本模块不是项目模块,其他模块要想识别到此bean需要导出一下!!!
这个比较容易忘
-
其他模块使用feign
其他模块使用feign时要在启动类上加上开启feign的注解如:system模块想要使用feign
file模块想要使用feign
哪个模块想要使用feign,哪个模块的启动类(配置类)上需要加上
@EnableRyFeignClients
注解,如果不加,则识别不到feign
- 使用feign
使用feign则像一个方法一样直接注入就行,feign中定义的接口请求路径需要在相应模块中有相应的接口接收
将一个文件从一个minio中下载下来再上传到另一个minio中
public String transferFile(String fileUrl) throws MalformedURLException {
fileUrl = "http://oss.test.ctbigdatatest.wenhuayun.cn/bigdatacabinet/2024/01/18/e821a174-9c47-408a-bb5e-5189f3db60bb.mp4";
// 1.从第一个minio服务器中下载到本地临时路径
String tempFilePath = "D:\\temp\\file.mp4"; // 临时文件路径
//minioClient.downloadObject(DownloadObjectArgs.builder()
// .bucket(minioConfig.getBucketName())
// .object("2024/01/11/579a1fc7-148e-485b-8ab9-f96a5774d345.jpg")
// .filename(tempFilePath)
// .build());
PresignedUrlDownloadRequest request = new PresignedUrlDownloadRequest(new URL(fileUrl));
File file = new File(tempFilePath);
amazonS3Client.download(request,file);
centerAmazonS3Client.putObject(centerMinioConfig.getBucketName(),"2024/01/26/my-file.mp4",file);
// 3. 返回上传的第二个minio服务器地址
return centerMinioConfig.getViewUrl() +"/"+ centerMinioConfig.getBucketName() + "/2024/01/26/my-file.mp4";
}