【小工具】WebClient远程调用,返回值将Long类型转换为String,自定义注解

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. 自定义注解

  1. 自定义注解
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 {

}
  1. 切面拦截注解
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());
    }
}
  1. 自定义异常

自定义异常时需要继承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 &lt;= #{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 &lt;= #{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的使用

  1. 分布式项目中若依使用feign一般是在 api 模块中
    下面是目录结构
    在这里插入图片描述
  2. 编写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

  1. 编写降级处理类
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需要导出一下!!!
这个比较容易忘在这里插入图片描述

  1. 其他模块使用feign
    其他模块使用feign时要在启动类上加上开启feign的注解

    如:system模块想要使用feign在这里插入图片描述


file模块想要使用feign
在这里插入图片描述

哪个模块想要使用feign,哪个模块的启动类(配置类)上需要加上@EnableRyFeignClients注解,如果不加,则识别不到feign

  1. 使用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";
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
WebClientSpring框架中的一个用于进行HTTP请求的非阻塞式客户端。它可以用来发送异步的POST请求。 为了使用WebClient发送异步的POST请求,你需要进行以下几个步骤: 1. 创建WebClient对象:使用`WebClient.create()`方法创建一个WebClient对象。 2. 设置请求URL和参数:使用`.uri(url)`方法设置请求的URL,使用`.body(BodyInserters.fromObject(params))`方法设置请求的参数。在这个例子中,`params`是一个包含请求参数的Map对象。 3. 设置请求头信息:使用`.contentType(MediaType.APPLICATION_FORM_URLENCODED)`方法设置请求的内容类型。在这个例子中,使用的是表单形式的请求。 4. 发送请求并获取响应数据:使用`.retrieve().bodyToFlux(Map.class)`方法发送请求并获取响应数据。这个方法返回一个`Flux<Map>`对象,你可以对这个对象进行订阅来处理异步返回的响应。 下面是一个示例代码,展示了如何使用WebClient发送异步的POST请求并处理响应: ```java String url = configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN); Map<String, String> params = new HashMap<String, String>(); params.put("suite_id", suiteId); params.put("suite_secret", suiteSecret); params.put("suite_ticket", suiteTicket); WebClient client = WebClient.create(); Flux<Map> mapFlux = client.post() .uri(url) .contentType(MediaType.APPLICATION_FORM_URLENCODED) .body(BodyInserters.fromObject(JSON.toJSONString(params))) .retrieve() .bodyToFlux(Map.class); mapFlux.subscribe(map -> System.out.println(map.toString()), Throwable::printStackTrace); ``` 在这个示例中,`mapFlux.subscribe()`方法用来订阅异步返回的响应,当响应到达时,会执行对应的回调函数。你可以在回调函数中处理响应数据或执行后续操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Spring WebClient,异步POST请求代码段](https://blog.csdn.net/sbin456/article/details/109615201)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [SilverLight学习笔记--WebClient异步请求](https://blog.csdn.net/starcrm/article/details/84969094)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打乒乓球只会抽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值