SpringCloud(一)前置知识学习

70 阅读10分钟

SpringCloud(一)

1.cloud项目构建过程

1.使用mybatis_generator 2024 来生Dao成数据库的增删改查

2.如何构建一个完整的微服务

  1. 建module: 在Maven父工程,右击,新建module
  2. 改pom文件
  3. 写YML
  4. 主启动
  5. 业务类

约定>环境>配置

2. Swagger 3的使用

常用注解:

日常的话前三个足够了

  • @Tag:标注在controller类上,用于标识controller的作用
  • @Schema:标注在model层的JavaBean,用于描述模型作用以及每个属性的作用
  • @Operation:标注在controller内部的方法上,描述方法的作用
  • @Parameter:标注在参数旁,用于标注参数的作用

  • @Parameters:标注在参数旁,用于参数的多重说明

  • @ApiResponse:标注在方法上,描述相应状态码

controller类:


@RestController
@Slf4j
@Tag(name = "支付微服务模块",description = "支付的CRUD")

public class PayController {


    @Resource
    private PayService payService; //注入service

    //增加
    @PostMapping(value = "/pay/add")
    @Operation(summary = "新增",description = "新增支付流水方法,json串作参数")
    public String addPay(@RequestBody Pay pay){//前端传来的数据封装成json
        System.out.println(pay.toString());
        int i=payService.add(pay);
        return "成功插入记录,返回值" + i;
    }

    //删除
    @DeleteMapping(value = "pay/del/{id}")
    @Operation(summary = "删除",description = "删除支付流水方法根据主键id")
    public Integer deletePay(@PathVariable("id") Integer id){
        return payService.delete(id);
    }

    //修改
    @PutMapping(value = "pay/update")
    @Operation(summary = "修改",description = "修改支付流水方法")
    public String updatePay(@RequestBody PayDto payDto){
        //参数使用PayDto,因为比如管理员要修改,不能让其看到密码等隐私信息
        //因为service层和Dao层只会和Pay实体类进行交互,而不是PayDto,所以如果想要将Dto的数据往后传
        // 最好先将PayDto的数据传给需Pay实体对象,然后由Pay对象向后进行交互

        Pay pay=new Pay();//新建pay空对象
        BeanUtils.copyProperties(payDto,pay);//将PayDto中的数据拷贝到pay空对象中
        int i=payService.update(pay);
        return "成功修改记录,返回值" + i;
    }

    //根据id查询:
    @GetMapping(value = "pay/get/{id}")
    @Operation(summary = "查询",description = "根据主键id查询支付流水方法")
    public Pay getById(@PathVariable("id") Integer id){
        return payService.getById(id);
    }

    //全部查询
    @Operation(summary = "查询",description = "查询全部支付流水方法")
    @GetMapping(value = "pay/getAll")
    public List<Pay> getAll(){
        return payService.getAll();
    }

}

实体类:

@Table(name = "t_pay")
@Schema(title = "支付交易表的实体类")
public class Pay {
    @Id //代表id是主键
    @GeneratedValue(generator = "JDBC")
    @Schema(title = "主键")
    private Integer id;

    /**
     * 支付流水号
     */
    @Column(name = "pay_no")
    @Schema(title = "支付流水号")
    private String payNo;

    /**
     * 订单流水号
     */
    @Column(name = "order_no")
    @Schema(title = "订单流水号")
    private String orderNo;

    /**
     * 用户账号ID
     */
    @Column(name = "user_id")
    @Schema(title= "用户账号ID")
    private Integer userId;

    /**
     * 交易金额
     */
    @Schema(title = "交易金额")
    private BigDecimal amount;

    /**
     * 删除标志,默认0不删除,1删除
     */
    private Byte deleted;

    /**
     * 创建时间
     */
    @Column(name = "create_time")
    @Schema(title = "创建时间")
    private Date createTime;

    /**
     * 更新时间
     */
    @Column(name = "update_time")
    @Schema(title = "更新时间")
    private Date updateTime;

配置类:


@Configuration
public class Swagger3Config
{
    @Bean
    public GroupedOpenApi PayApi()  //按组分类
    {
        return GroupedOpenApi.builder().group("支付微服务模块").pathsToMatch("/pay/**").build();
        //意为:只要是以pay开头的都是支付模块
    }
    @Bean
    public GroupedOpenApi OtherApi()
    {
        return GroupedOpenApi.builder().group("其它微服务模块").pathsToMatch("/other/**", "/others").build();
        //意为:只要是以other开头的都是其它模块
    }
    /*@Bean
    public GroupedOpenApi CustomerApi()
    {
        return GroupedOpenApi.builder().group("客户微服务模块").pathsToMatch("/customer/**", "/customers").build();
       //意为:只要是以customer开头的都是客户模块

    }*/

    @Bean
    public OpenAPI docsOpenApi()  //文档的描述说明
    {
        return new OpenAPI()
                .info(new Info().title("cloud2024")
                        .description("通用设计rest")
                        .version("v1.0"))
                .externalDocs(new ExternalDocumentation()
                        .description("www.atguigu.com")
                        .url("https://yiyan.baidu.com/"));
    }
}

3.完善

  1. 时间格式问题
  2. Java设计API接口实现统一格式返回
  3. 全局异常接入返回的标准格式:有统一的返回值+全局统一异常

时间格式:

时间格式要符合日常的使用习惯

可以在相应的实体类的属性上使用**@JsonFormat主键**:


    /**
     * 创建时间
     */
    @Column(name = "create_time")
    @Schema(title = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date createTime;

    /**
     * 更新时间
     */
    @Column(name = "update_time")
    @Schema(title = "更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date updateTime;

统一返回值格式问题:

定义返回标准格式 的3大标配:

  • code状态值(key):由后端统一定义各种返回结果的状态码
  • message描述(value):本次接口调用的结果描述
  • data数据:本次返回的数据
  • timestamp:时间戳,接口调用的时间

步骤:

1. 新建枚举类ReturnCodeEnum

HTTP请求返回的状态码:

分类区间分类描述
1**100~199信息,服务器接收到请求,需要请求者继续执行操作
2**200~299成功,操作被成功接收并处理
3**300~399重定向,需要进一步的操作以完成请求
4**400~499客户端错误,请求包含语法错误或无法完成请求
5**500~599服务器错误,服务器在处理请求的过程中发送了错误

ReturnCodeEnum枚举类:


/**
 * 枚举类:如何定义一个通用的枚举类:
 *     举值(需要返回的相应信息个数,有几个就写几个)--构造--遍历
 */
@Getter
public enum ReturnCodeEnum {

    //1.举值:枚举(枚举包含code和message两个参数)

    /**操作失败**/
    RC999("999","操作XXX失败"),
    /**操作成功**/
    RC200("200","success"),
    /**服务降级**/
    RC201("201","服务开启降级保护,请稍后再试!"),
    /**热点参数限流**/
    RC202("202","热点参数限流,请稍后再试!"),
    /**系统规则不满足**/
    RC203("203","系统规则不满足要求,请稍后再试!"),
    /**授权规则不通过**/
    RC204("204","授权规则不通过,请稍后再试!"),
    /**access_denied**/
    RC403("403","无访问权限,请联系管理员授予权限"),
    /**access_denied**/
    RC401("401","匿名用户访问无权限资源时的异常"),
    RC404("404","404页面找不到的异常"),
    /**服务异常**/
    RC500("500","系统异常,请稍后重试"),
    RC375("375","数学运算异常,请稍后重试"),

    INVALID_TOKEN("2001","访问令牌不合法"),
    ACCESS_DENIED("2003","没有权限访问该资源"),
    CLIENT_AUTHENTICATION_FAILED("1001","客户端认证失败"),
    USERNAME_OR_PASSWORD_ERROR("1002","用户名或密码错误"),
    BUSINESS_ERROR("1004","业务逻辑异常"),
    UNSUPPORTED_GRANT_TYPE("1003", "不支持的认证模式");


    //2.构造:这里包含两个参数:即code 和 message
    private final String code; //定义 自定义状态码
    private final String message;//定义 自定义描述

    ReturnCodeEnum(String code, String message) { //构造函数
        this.code = code;
        this.message = message;
    }


    //3.遍历

    //(1)传统方法:通过数组遍历

    public static ReturnCodeEnum getreturnCodeEnum(String code){

        for (ReturnCodeEnum element : ReturnCodeEnum.values()) {
    //values()方法:枚举自带的方法,将枚举类转换成枚举类型的数组,因为枚举中没有下标,我们没有办法通过下标来快速找到需要的枚举,这时候,转变为数组之后,我们就可以通过数组的下标,来找到我们需要的枚举。
            if(element.getCode().equals(code)){
                return element; //如果和code匹配上了就给前台返回整个枚举值
            }
        }
        return null; //否则,返回null
    }


    //(2)Stream流式计算:即通过流来遍历!!!
    public static ReturnCodeEnum getreturnCodeEnumV2(String code){

       return Arrays.stream(ReturnCodeEnum.values()).filter(x ->x.getCode().equalsIgnoreCase(code)).findFirst().orElse(null);
       //              将数组转换成stream流,      filter方法遍历流   将遍历的枚举放入x中  如果x的code与code相同  返回对应的枚举   否则返回null
    }

    
    "注意:"(1)、(2)选择一种方法即可

2.定义统一返回对象ResultData
通过ResultData对返回结果进行包装后返回给前端;(即使用ResultData对象将code、message、data、timestamp进行封装然后统一返回)


@Data
@Accessors(chain = true)
public class ResultData<T> {

    //结果状态
    private String code;
    private String message;
    private T data; //data可以是任何类型
    private Long timestamp;


    //用来获取当前时间:比如调用某个接口时或者初始化对象成功时,可以显示目前的时间
    public ResultData(){ //构造方法
        this.timestamp=System.currentTimeMillis();
    }

    //(1)执行成功返回的结果:要code、message、data、timestamp将封装为ResultData对象返回
      //code、message可以从枚举类获取,timestamp可以自动获取,所以就只需要传入data即可
    public static <T> ResultData<T> success(T data){

        ResultData<T> resultData=new ResultData<>();//创建一个ResultData对象用来封装返回的code、message、data、timestamp

        resultData.setCode(ReturnCodeEnum.RC200.getCode());//设置code的值
        resultData.setMessage(ReturnCodeEnum.RC200.getMessage());//设置message的值
        resultData.setData(data);//设置data的值

        return resultData; //返回结果

    }


    //(2)执行失败的方法:因为执行失败,所以不需要返回data,只需要将code、message、timestamp封装到R对象返回即可
        //返回对象:ResultData(code、message可以从枚举类中获取,timestamp自动获取)
    public static <T> ResultData<T> fail(String code,String message){

        ResultData<T> resultData=new ResultData<>();//创建一个ResultData对象用于封装code、message、timestamp并返回

        resultData.setCode(code);
        resultData.setMessage(message);

        return resultData;


    }

}

全局异常处理:


/**
 * 全局异常处理器
 */

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    //以捕捉500异常为例


    @ExceptionHandler(RuntimeException.class) //捕捉运行时异常
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//500异常:服务器
    public ResultData<String> exception(Exception e){
        //返回结果仍为:ResultData,泛型为String类型:因为我们想要看的是异常的信息;

        System.out.println("######come in GlobalExceptionHandler");
        log.error("全局异常信息:{}",e.getMessage(),e);


        return ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
    }
}

RestTemplate

  • RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务的模板类

  • 是Spring提供的用于访问Rest服务客户端模板工具集

1.使用说明:

​ 客户模块调用订单服务,需要提供一个Rest地址

​ 使用RestTemplate访问restful接口有3个参数:

  • url:Rest请求地址
  • requestMap:请求参数
  • ResponseBean.class:返回值类型

2.getForObject()/ResponseEntity()

返回对象为响应体中数据转换成的对象,也就是返回了一个json串信息,并不包含响应头、响应状态码、响应体这种结构。就是使用getForObject方法。

返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。就是使用getForEntity方法

这两个方法中都要传入2个参数:第一个是url,第二个是json信息或者响应体中的信息对应的类型的类的class实例。

3.postForObject()/postEntity()\

其它不变

这两个方法中都要传入3个参数:第一个是url,第二个是输入的参数、第三个是返回值

如果是postEntity(),后面其要加一个getBody();

4.delete方法/put方法

delete只有一个参数,就是url。

put有两个参数,第一个是url,第二个是接收请求体信息的对象。

private final String  PaymentSrv_URL ="http://cloud-payment-service";//服务注册中心上的8001微服务名称

  @Resource
  private RestTemplate restTemplate;

 @GetMapping("/consumer/pay/add")
    public ResultData addOrder(PayDto payDTO){
        return restTemplate.postForObject(PaymentSrv_URL + "/pay/add",payDTO,ResultData.class);
    }

    //删除
    @DeleteMapping(value = "/consumer/pay/del/{id}")
    public ResultData deleteOrder(@PathVariable("id") Integer id){
         restTemplate.delete(PaymentSrv_URL+"/pay/del"+id,id);
         return ResultData.success("成功删除");
    }

    //修改
    @PutMapping(value = "/consumer/pay/update/")
    public ResultData updateOrder(@RequestBody PayDto payDto){
        restTemplate.put(PaymentSrv_URL+"pay/update",ResultData.class);
        return ResultData.success("修改成功");
    }

    //查询

      //根据id查询
    @GetMapping(value = "/consumer/pay/get/{id}")
    public ResultData getPayInfo(@PathVariable("id") Integer id){
        return restTemplate.getForObject(PaymentSrv_URL +"/pay/get/"+id,ResultData.class, id);
    }

  //查询全部
    @GetMapping(value = "/consumer/pay/getAll")
    public ResultData getAllPayInfo(){
        return restTemplate.getForObject(PaymentSrv_URL+"/pay/getAll",ResultData.class);
    }

通用服务:

在多个微服务模块中,通常会有通用的一些部分,比如对外暴露通用的组件、api、接口、工具类等。

所以新建一个微服务作为通用模块

在将这些部分加入到通用模块中后,要clean+install做成通用包将其通用模块加入到maven本地仓库中去。

然后在其它模块的pom文件中的dependcies中引入自己的通用包。

<!-- 引入自己定义的api通用包 -->
<dependency>
    <groupId>com.atguigu.cloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>