[Spring Cloud] gateway全局异常捕捉统一返回值

news2024/11/17 7:11:27

文章目录

    • 处理转发失败的情况
    • 全局参数
    • 同一返回格式
      • 操作消息对象AjaxResult
      • 返回值状态描述对象AjaxStatus
      • 返回值枚举接口层StatusCode
    • 全局异常处理器
    • 自定义通用异常
      • 定一个自定义异常
      • 覆盖默认的异常处理
      • 自定义异常处理工具

在上一篇章时我们有了一个简单的gateway网关
[Spring Cloud] gateway简单搭建与请求转发-CSDN博客
现在我们需要根据这个网关进行一部分的改进

现在我们需要进一步的处理一些问题,来使得网关更加完善。
本篇文章的完整代码文件已放置在gitee。
杉极简/gateway网关阶段学习

处理转发失败的情况

正常情况下,我们请求了一个接口,并得到了一个结果,如下:
image.png
但是,我们依然要考虑,如果访问到一个不存在的接口,会得到什么样的结果?
如果不做任何修改的时候,我们会得到以下的结果:
image.png
但这不是我们想要的。
我们想要如下返回结果。
image.png
因此我们需要配置一个全局异常处理器来处理。
为达到这个目的,我们开始构建一些基础的功能,描述如下。

全局参数

我们需要去创建一个全部配置,如下所示:
这里统一存放着我们需要读取的配置,主要为全局使用的参数。具体配置方式如下所示:
在网关项目中创建一个config文件夹,用于存放网关的相关配置。(当然,能想做网关的人,应该都熟练的会使用Spring Boot了,在Spring Boot项目中,这都是一些较为基础的内容)
此时,我们先配置一个全局异常捕捉-打印堆栈异常的参数,用于本文的一些功能当中。
image.png

global:
  # 全局异常捕捉-打印堆栈异常
  printStackTrace: true
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


/**
 * @author fir
 * @date 2023/7/28 17:53
 */
@Data
@Component
@ConfigurationProperties(prefix = "global")
public class GlobalConfig {

    /**
     * 全局异常捕捉-打印堆栈异常
     */
    private boolean printStackTrace;
}

image.png

同一返回格式

创建一个result文件夹,用于存放统一返回值的相关配置
该部分比较基础,暂时只说明配置方式与代码。
image.png

操作消息对象AjaxResult

import java.io.Serializable;
import java.util.HashMap;


/**
 * 操作消息-JSON
 *
 * @author fir
 */
public class AjaxResult extends HashMap<String, Object> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";

    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";

    /**
     * 数据对象
     */
    public static final String DATA_TAG = "data";


    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
    }


    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  状态描述
     */
    public AjaxResult(int code, String msg) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }


    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  状态描述
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (data != null) {
            super.put(DATA_TAG, data);
        }
    }


    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success() {
        AjaxStatus success = AjaxStatus.SUCCESS;
        return AjaxResult.success(success.getCode(), success.getMsg());
    }


    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data) {
        AjaxStatus success = AjaxStatus.SUCCESS;
        return AjaxResult.success(success.getMsg(), data);
    }


    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(AjaxStatus success) {

        return AjaxResult.success(success.getMsg(), new HashMap<>(0));
    }


    /**
     * 返回成功消息
     *
     * @param code 状态吗
     * @param msg  状态描述
     * @return 消息体
     */
    public static AjaxResult success(Integer code, String msg) {
        return new AjaxResult(code, msg);
    }


    /**
     * 返回成功消息
     *
     * @param msg  状态描述
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data) {
        AjaxStatus success = AjaxStatus.SUCCESS;
        return new AjaxResult(success.getCode(), msg, data);
    }


    /**
     * 返回特定状态描述
     *
     * @param statusCode 特定的枚举结果
     * @param data       数据对象
     * @return 请求结果
     */
    public static AjaxResult success(StatusCode statusCode, Object data) {
        return new AjaxResult(statusCode.getCode(), statusCode.getMsg(), data);
    }


    /**
     * 返回错误消息
     *
     * @return 警告消息
     */
    public static AjaxResult error() {
        return AjaxResult.error(AjaxStatus.LOSE_OPERATION.getMsg());
    }


    /**
     * 返回错误消息
     *
     * @param msg 状态描述
     * @return 警告消息
     */
    public static AjaxResult error(String msg) {
        return AjaxResult.error(msg, new HashMap<>(0));
    }


    /**
     * 返回错误消息
     *
     * @param msg  状态描述
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data) {
        AjaxStatus loseEfficacy = AjaxStatus.LOSE_EFFICACY;
        return new AjaxResult(loseEfficacy.getCode(), msg, data);
    }


    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg  状态描述
     * @return 警告消息
     */
    public static AjaxResult error(int code, String msg) {
        return new AjaxResult(code, msg, new HashMap<>(0));
    }


    /**
     * 返回特定状态描述
     *
     * @param statusCode 特定的枚举结果
     * @param data       数据对象
     * @return 请求结果
     */
    public static AjaxResult error(StatusCode statusCode, Object data) {
        return new AjaxResult(statusCode.getCode(), statusCode.getMsg(), data);
    }


    /**
     * 返回特定状态描述
     *
     * @param statusCode 特定的枚举结果
     * @return 请求结果
     */
    public static AjaxResult error(StatusCode statusCode) {
        return new AjaxResult(statusCode.getCode(), statusCode.getMsg(), new HashMap<>(0));
    }
}

返回值状态描述对象AjaxStatus

import lombok.Getter;


/**
 * 返回值状态与描述
 * 
 * @author fir
 */

@Getter
public enum AjaxStatus implements StatusCode {
    /**
     * 请求成功
     */
    SUCCESS(200, "请求成功"),
    /**
     * 登录成功
     */
    SUCCESS_LOGIN(200, "登录成功"),
    /**
     * 登出成功
     */
    SUCCESS_LOGOUT(200, "登出成功"),
    /**
     * 启动成功
     */
    SUCCESS_FLOW_START(200, "启动成功"),
    /**
     * 暂无数据
     */
    NO_DATA(200, "暂无数据"),
    /**
     * 错误请求
     */
    BAD_REQUEST(400, "错误请求"),
    /**
     * 登录过期
     */
    EXPIRATION_TOKEN(401, "登录过期"),
    /**
     * 服务不存在
     */
    FAILED_SERVICE_DOES(404, "服务不存在"),
    /**
     * 账号或密码为空
     */
    NULL_LOGIN_DATA(480, "账号或密码为空"),
    /**
     * 建立通信-通信建立失败
     */
    FAILED_COMMUNICATION(481, "通信建立失败"),
    /**
     * 接口不存在
     */
    NULL_API(404, "接口不存在"),
    /**
     * 建立通信-非法请求
     */
    ILLEGAL_REQUEST(482, "非法请求"),
    /**
     * 请求失败
     */
    LOSE_EFFICACY(490, "请求失败"),
    /**
     * 操作失败
     */
    LOSE_OPERATION(491, "操作失败"),
    /**
     * 请求失败
     */
    FAILED(500, "请求失败"),
    /**
     * 服务不可用(gateway网关总定义-所有未定义处理的异常都返回该异常)
     */
    SERVICE_UNAVAILABLE(500, "服务不可用"),
    /**
     * 请求整体加密-无效会话
     */
    SESSION_INVALID(601, "无效会话"),
    /**
     * 请求整体加密-会话过期
     */
    SESSION_EXPIRE(602, "会话过期"),
    /**
     * 防重放校验失败
     */
    ANTI_REPLAY_VERIFY_FAILED(701, "防重放校验失败"),
    /**
     * 防重放校验失败
     */
    INTEGRITY_VERIFY_FAILED(801, "完整性校验失败"),
    /**
     * 预留
     */
    PASS(1000, "请求失败");

    private final int code;
    private final String msg;

    AjaxStatus(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

返回值枚举接口层StatusCode

package com.fir.gateway.config.result;

/**
 * 返回值枚举接口层
 * @author 18714
 */
public interface StatusCode {


    /**
     * 获取code信息
     *
     * @return code码
     */
     int getCode();


    /**
     * 获取msg信息
     *
     * @return msg描述
     */
     String getMsg();
}

全局异常处理器

做两步

  1. 覆盖默认的异常处理。
  2. 自定义异常处理。

自定义通用异常

通常的处理过程为抛出异常->全局异常捕捉->返回前端
通常在代码中,对于某个特定的条件,我们抛出一个自定义异常,并携带特定的状态码与状态描述

if (session == null) {
    throw new CustomException(AjaxStatus.SESSION_INVALID);
}

而此时,我们在开发环境中通常需要显示堆栈异常,但是生成环境中,大多数是不需要的,此时我们就是用了全局参数配置在自定义异常工具这个类中,我们配置了定义异常默认不打印堆栈异常

定一个自定义异常

import com.fir.gateway.config.result.AjaxStatus;
import lombok.Getter;

/**
 * 自定义通用异常
 * 抛出异常->全局异常捕捉->返回前端
 *
 * @author fir
 */
@Getter
public class CustomException extends RuntimeException {

    /**
     * code状态码
     */
    private final int code;

    /**
     * 错误状态码
     */
    private final AjaxStatus ajaxStatus;


    public CustomException(AjaxStatus ajaxStatus) {
        super(ajaxStatus.getMsg());
        this.code = ajaxStatus.getCode();
        this.ajaxStatus = ajaxStatus;
    }

}

覆盖默认的异常处理

此时我们覆盖默认的异常处理,并使用自定义的异常处理工具JsonExceptionHandler

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.Collections;
import java.util.List;


/**
 * 覆盖默认的异常处理
 * @author fir
 */
@Configuration
@EnableConfigurationProperties({ServerProperties.class, WebProperties.class})
public class ErrorHandlerConfiguration {

    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final WebProperties webProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfiguration(ServerProperties serverProperties,
                                     WebProperties webProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                     ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.webProperties = webProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.webProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }

}

自定义异常处理工具

解决三个问题

  1. 处理自定义异常
  2. 处理不存在的接口
  3. 对于其他的异常,统一返回一个指定的错误描述AjaxStatus.SERVICE_UNAVAILABLE
package com.fir.gateway.config.exception;


import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.result.AjaxResult;
import com.fir.gateway.config.result.AjaxStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;


/**
 * 自定义异常处理工具
 * 异常时用JSON代替HTML异常信息
 *
 * @author fir
 */
@Slf4j
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {


    /**
     * 网关参数配置
     */
    @Resource
    private GlobalConfig globalConfig;


    public JsonExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, webProperties.getResources(), errorProperties, applicationContext);
        log.info(String.valueOf(errorProperties));
        log.info(String.valueOf(errorAttributes));
    }

    /**
     * 重构方法,设置返回属性格式
     */
    @Override
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        Throwable errorThrowable = getError(request);
        // 自定义异常默认不打印堆栈异常
        // 决定是否打印堆栈异常
        boolean printStackTrace = globalConfig.isPrintStackTrace();
        if (printStackTrace) {
            errorThrowable.printStackTrace();
        }
        // 打印全局异常
        log.error(errorThrowable.getMessage());

        Class<?> errorClass = errorThrowable.getClass();
        String simpleName = errorClass.getSimpleName();
        AjaxStatus ajaxStatus;
        switch (simpleName) {
            case "CustomException":
                // 处理自定义异常
                CustomException customException = (CustomException) errorThrowable;
                ajaxStatus = customException.getAjaxStatus();
                break;
            case "NotFoundException":
            case "ResponseStatusException":
                // 处理404
                ajaxStatus = AjaxStatus.NULL_API;
                break;
            default:
                // 统一返回一个服务错误描述
                ajaxStatus = AjaxStatus.SERVICE_UNAVAILABLE;
                break;
        }

        AjaxResult result = AjaxResult.error(ajaxStatus);
        return ServerResponse.status(200).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(result));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1571193.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3中实现文本显示省略号和tooltips提示框

前言 在 B 端业务中&#xff0c;我们经常会遇到文本内容超出容器区域需显示省略号的需求。当鼠标移入文本时&#xff0c;会出现 Tooltip 显示完整内容。最近&#xff0c;我也遇到了这样的场景。为了提高业务通用性&#xff0c;我已将其封装为组件、Hook 和指令等形式供使用。 …

从0到1实现RPC | 04 负载均衡和静态注册中心

一、Router的定义 Router路由用于预筛选&#xff0c;Dubbo有这样的设计&#xff0c;SpringCloud没有。 二、LoadBanlancer定义 负载均衡器&#xff1a;默认取第一个 当前支持随机和轮询两种负载均衡器。 随机&#xff1a;从所有provider中随机选择一个。 轮询&#xff1a;每…

如何选择优质的静动态住宅代理IP提供商

在从事跨境电商业务时选择优质的静动态住宅代理IP提供商是一个涉及多个方面考量的过程。在进行选择时&#xff0c;需要综合考虑多种因素&#xff0c;以确保选择的提供商能够满足您的需求并提供稳定、可靠的服务。以下是一些建议&#xff0c;以帮助您进行选择&#xff1a; 第一…

js中使let关键字报错,改用var关键字解决

js中使let关键字报错,改用var关键字解决 项目场景&#xff1a;问题描述原因分析&#xff1a;解决方案&#xff1a;总结 项目场景&#xff1a; 使用 let 关键字报错&#xff0c;报错信息为&#xff1a; Uncaught ReferenceError: maxNum is not defined at getMaxNum (4-3.htm…

独角数卡对接支付卡跳转问题解决方法

问题描述 最近在用独角数卡搭建了一个测试版的商店程序&#xff0c;结果却在对接易支付的过程中出现了卡跳转的问题&#xff0c;支付能正常完成&#xff0c;订单发卡也正常&#xff0c;就是会卡在这个弹窗页面无法正常跳转至订单查看页面。 本来这种BUG无关痛痒&#xff0c;但…

蓝桥杯(5):python动态规划DF[2:背包问题]

1 0-1背包介绍【每件物品只能拿1件或者不拿】 1.1 简介 贪心是不可以的&#xff01;&#xff01;&#xff01; 1.2 状态 及状态转移 转移解释&#xff1a;要么不选 则上一个直接转移过来【dp[i-1][j]】&#xff0c;要么是选这个之后体积为j 则上一个对应的就是【dp[i-1][j-wi]…

阿里云邮件服务器多少钱?邮件服务器租用费用

阿里云邮件服务器租用费用&#xff0c;2核2G3M带宽99元一年、2核4G4M服务器199元一年&#xff0c;不只是云服务器ECS&#xff0c;还可以选择轻量应用服务器。 0、在阿里云CLUB中心领取 aliyun.club 当前最新的优惠券和服务器报价单 1、阿里云服务器ECS经济型e实例&#xff0c;2…

leetcode代码记录(买卖股票的最佳时机

目录 1. 题目&#xff1a;2. 我的代码&#xff1a;小结&#xff1a; 1. 题目&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股…

导入项目运行后,报错java: Cannot find JDK ‘XX‘ for module ‘XX‘

解决方案&#xff1a; 1、删除.idea和.iml文件 2、右击此module&#xff0c;点击 Open Module Settings 在 Module SDK 中选择所安装的java版本后&#xff0c;点击右下角 Apply 3、再运行试试吧&#xff0c;成功&#xff01;

Linux存储的基本管理

实验环境&#xff1a; 系统里添加两块硬盘 ##1.设备识别## 设备接入系统后都是以文件的形式存在 设备文件名称&#xff1a; SATA/SAS/USB /dev/sda,/dev/sdb ##s SATA, dDISK a第几块 IDE /dev/hd0,/dev/hd1 ##h hard VIRTIO-BLOCK /de…

金陵科技学院软件工程学院软件工程专业

感兴趣的小伙伴可以私信我哦~~ 是笔者写的各种高质量作业和实验哦~~ 感兴趣的小伙伴可以私信我哦~~ 是笔者写的各种高质量作业和实验哦~~ 感兴趣的小伙伴可以私信我哦~~ 是笔者写的各种高质量作业和实验哦~~ 感兴趣的小伙伴可以私信我哦~~ 是笔者写的各种高质量作业和实验哦…

大数据基础设施搭建 - Spark

文章目录 一、解压压缩包二、修改配置文件conf/spark-env.sh三、测试提交Spark任务四、Spark on Hive配置4.1 创建hive-site.xml&#xff08;spark/conf目录&#xff09;4.2 查看hive的hive-site.xml配置与3.1配置的是否一致4.3 测试SparkSQL4.3.1 启动SparkSQL客户端&#xff…

Qtxlsx第三方库的安装和使用

本文仅作为一个记录&#xff0c;安装QtXlsx方便操作excel&#xff0c;主要参考了这篇博文&#xff1a;https://blog.csdn.net/u014779536/article/details/111769792 1&#xff0c;下载安装Perl脚本Strawberry Perl for Windows&#xff0c;默认安装strawberry-perl-5.30.0.1-…

AcWing1402.星空之夜

【题目链接】1402. 星空之夜 - AcWing题库 夜空深处&#xff0c;闪亮的星星以星群的形式出现在人们眼中&#xff0c;形态万千。 一个星群是指一组非空的在水平&#xff0c;垂直或对角线方向相邻的星星的集合。 一个星群不能是一个更大星群的一部分。 星群可能是相似的。 如…

力扣面试150 加油站 贪心 找规律

Problem: 134. 加油站 复杂度 ⏰ 时间复杂度: O ( n ) O(n) O(n) &#x1f30e; 空间复杂度: O ( 1 ) O(1) O(1) &#x1f496; 贪心 &#x1f468;‍&#x1f3eb; 参考题解 class Solution {public int canCompleteCircuit(int[] gas, int[] cost) {int n gas.length…

【美团笔试题汇总】2023-08-26-美团春秋招笔试题-三语言题解(CPP/Python/Java)

&#x1f36d; 大家好这里是KK爱Coding &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新小米近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f…

喜讯 ChatGPT 3.5 免登录|免注册就可以使用了

https://chat.openai.com/ 直接访问openai 官网直接使用&#xff0c;当然还是要魔法的&#xff0c;不用再去用别人二次开发的&#xff0c;还有次数限制&#xff0c;还有开会员&#x1f605;才能用的。&#x1f600;试用啦一下&#xff0c;基本秒回答&#xff0c;能力也是在线的…

【系统架构师】-系统可靠性分析与设计

1、可靠性与可用性区别 1、系统可靠性&#xff1a;系统在规定时间内及规定的环境下&#xff0c;完成规定功能的能力&#xff0c;即系统无故障运行的概率 2、系统可用性&#xff1a;在某个给定时间点上系统能够按照需求执行的概率。 可靠性分为软件、硬件可靠性 2、可靠性指标…

【美团笔试题汇总】2023-09-02-美团春秋招笔试题-三语言题解(CPP/Python/Java)

&#x1f36d; 大家好这里是KK爱Coding &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新美团近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f…

软著说明文档生成/辅助填写工具

软著说明文档生成/辅助填写工具&#xff0c;自行申请软著的话&#xff0c;软著60页源码还比较容易搞定&#xff0c;但是说明文档有格式和字数要求&#xff0c;就很烦。这个网站可以进行格式和内容的辅助填写&#xff0c;不用再把精力浪费到没用的调整格式上&#xff0c;网站地址…