SpringCloud框架学习(第一部分:初始项目搭建)

news2024/11/26 13:25:01

目录

一、SpringBoot和SpringCloud版本选型

 1.Springcloud版本选择

2.Springcloud版本选择 

3.Springcloud Alibaba版本选择

4.SpringCloud VS SpringBoot VS SpringCloud Alibaba版本三者制约对应关系

二、SpringCloud介绍

1.单体架构

2.微服务架构

3.springcloud

4.SpringCloud相关组件介绍 

三、微服务项目搭建

1.项目构建

2.MyBatis逆向工程

3.微服务提供者支付module

4.待优化的问题

四、项目改进

1.优化时间格式

(1)方式一:

(2)方式二:

2.统一返回结果Result

(1)步骤一:定义一个枚举类,用于状态码的返回

(2)步骤二:定义统一返回类Result

(3)步骤三:修改原来接口的返回值

3.全局异常处理

五、微服务调用者订单module

1.项目构建

2.服务调用RestTemplate

(1) RestTemplate介绍

(2)项目配置 RestTemplate

3.重复代码抽取

4.存在问题


一、SpringBoot和SpringCloud版本选型

技术版本
Javajdk17+
boot3.2.0
cloud2023.0.0
cloud alibaba2023.0.0.0-RC1
Maven3.9+
MySQL8.0+

 1.Springcloud版本选择

通过上面官网发现,Boot官方强烈建议你使用Java17+升级到3.X以上版本。

2.Springcloud版本选择 

与 SpringBoot 3.2.0 相对应的 SpringCloud 版本为 2023.0.x。

3.Springcloud Alibaba版本选择

官网显示版本为 2021.0.4.0,但官网有延后情况,非最新版(不推荐)。

Github说明:GitHub - alibaba/spring-cloud-alibaba: Spring Cloud Alibaba provides a one-stop solution for application development for the distributed solutions of Alibaba middleware.

因此,我们选择 SpringCloudAlibaba 版本为 2023.0.0.0-RC1。

4.SpringCloud VS SpringBoot VS SpringCloud Alibaba版本三者制约对应关系

若同时用 boot 和 cloud,由话事人 cloud 决定 boot 版本。

 同时,cloud 也决定了使用哪种版本的 SpringCloudAlibaba。

二、SpringCloud介绍

1.单体架构

单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署

优点:

  • 架构简单
  • 部署成本低

当随着业务越来越多,代码量将上升至几万行甚至几十万行,开发人员也会需要越来越多!

各业务之前存在耦合性,编写、提交代码时,可能会产生冲突。

同时,代码量过高也会造成编译,打包时间过长。

某些业务存在高并发,就会很容易将服务器资源耗尽,其他业务就只能等待,访问时就会卡顿。

缺点:

  • 团队协作成本高
  • 系统发布效率低
  • 系统可用性差

总结:单体架构适合开发功能相对简单,规模较小的项目

2.微服务架构

微服务架构:服务化,就是把单体架构中的模块拆分为多个独立的项目。它以专注于单一职责的很多小型项目为基础,组合出复杂度大型应用。

微服务通常需要:

  • 粒度小
  • 团队自治
  • 服务自治 

3.springcloud

实现微服务架构,会面临多种问题,需要通过以下各种技术得以解决:

 大体流程如下:

这就会导致程序员除了编写业务逻辑代码,还需要为了保证项目正常运行,做各种其他的事情。

SpringCloud 就集成了各种微服务功能组件,并基于 SpringBoot 实现了这些组件的自动装配,从而提供了良好的开箱即用体验,帮助程序员减负!

4.SpringCloud相关组件介绍 

在2019年之前,使用的大部分技术都是Netflix提供的,但是由于开发SpringCloud的相关技术不挣钱,因此Netflix就暂停开发相关技术了,虽然他提供的那些技术依旧可以使用,但是已经不推荐了。

对于老的技术栈,可能老项目中会用到,这里只介绍一些推荐的新组件。

注册与发现

  • Eureka【Netflix最后的火种,不推荐】
  • Consul【推荐使用】
  • Etcd【可以使用】
  • Nacos【推荐使用,阿里巴巴提供的】

服务调用和负载均衡

  • Ribbon【Netflix提供的,建议直接弃用】
  • OpenFeign
  • LoadBalancer

分布式事务

  • Seata【推荐使用,阿里巴巴的】
  • LCN
  • Hmily

服务熔断和降级

  • Hystrix【已经停更了,不推荐】
  • Circuit Breaker【这只一套规范,使用的是它的实现类】
    • Resilience4J【CircuitBreaker的实现类,可以使用】
  • Sentinel【阿里巴巴的,推荐使用】

服务链路追踪

  • Sleuth+Zipkin【逐渐被替代了,不推荐】
  • Micrometer Tracing【推荐使用】

服务网关

  • Zuul【不推荐使用】
  • Gate Way

分布式配置管理

  • Config+Bus【不推荐了】
  • Consul
  • Nacos

三、微服务项目搭建

1.项目构建

需求:

① 先做一个通用boot微服务

② 逐步引入cloud组件纳入微服务支撑体系

步骤:

① 新建一个 maven 父工程,该项目只留下 pom.xml 文件,

② 检查项目的编码格式,统一为UTF-8

③ 检查注解支撑是否打开

④ 检查 java 编译版本 

⑤ 父工程的 pom 文件导入依赖,然后刷新 

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <hutool.version>5.8.22</hutool.version>
        <druid.version>1.1.20</druid.version>
        <mybatis.springboot.version>3.0.3</mybatis.springboot.version>
        <mysql.version>8.0.11</mysql.version>
        <swagger3.version>2.2.0</swagger3.version>
        <mapper.version>4.2.3</mapper.version>
        <fastjson2.version>2.0.40</fastjson2.version>
        <persistence-api.version>1.0.2</persistence-api.version>
        <spring.boot.test.version>3.1.5</spring.boot.test.version>
        <spring.boot.version>3.2.0</spring.boot.version>
        <spring.cloud.version>2023.0.0</spring.cloud.version>
        <spring.cloud.alibaba.version>2023.0.0.0-RC1</spring.cloud.alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!--springboot 3.2.0-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--springcloud 2023.0.0-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--springcloud alibaba 2023.0.0.0-RC1-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring.cloud.alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--SpringBoot集成mybatis-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.springboot.version}</version>
            </dependency>
            <!--Mysql数据库驱动8 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!--SpringBoot集成druid连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!--通用Mapper4之tk.mybatis-->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper</artifactId>
                <version>${mapper.version}</version>
            </dependency>
            <!--persistence-->
            <dependency>
                <groupId>javax.persistence</groupId>
                <artifactId>persistence-api</artifactId>
                <version>${persistence-api.version}</version>
            </dependency>
            <!-- fastjson2 -->
            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>${fastjson2.version}</version>
            </dependency>
            <!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
            <dependency>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
                <version>${swagger3.version}</version>
            </dependency>
            <!--hutool-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
            <!-- spring-boot-starter-test -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <version>${spring.boot.test.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

注意:dependencyManagement里只是声明依赖,*并不实现引入*因此子项目需要显示的声明需要用的依赖。

2.MyBatis逆向工程

本次使用 Mapper4,可以一键生成,不用写单表操作了

步骤:

① 建库建表,表名 t_pay

create database db2024;
use db2024;
DROP TABLE IF EXISTS `t_pay`;
CREATE TABLE `t_pay` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `pay_no` VARCHAR(50) NOT NULL COMMENT '支付流水号',
  `order_no` VARCHAR(50) NOT NULL COMMENT '订单流水号',
  `user_id` INT(10) DEFAULT '1' COMMENT '用户账号ID',
  `amount` DECIMAL(8,2) NOT NULL DEFAULT '9.9' COMMENT '交易金额',
  `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
  `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)

) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='支付交易表';
INSERT INTO t_pay(pay_no,order_no) VALUES('pay17203699','6544bafb424a');
SELECT * FROM t_pay;

② 在父工程下面创建一个子模块,给子模块导入依赖

说明:这个工程只是为了暂时存储生成的代码,等到业务工程使用的时候,会将对应的类复制过去

<dependencies>
        <!--Mybatis 通用mapper tk单独使用,自己独有+自带版本号-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.13</version>
        </dependency>
        <!-- Mybatis Generator 自己独有+自带版本号-->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.4.2</version>
        </dependency>
        <!--通用Mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
        </dependency>
        <!--mysql8.0-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--persistence-->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>${basedir}/src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>${basedir}/src/main/resources</directory>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.2</version>
                <configuration>
                    <configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
                    <overwrite>true</overwrite>
                    <verbose>true</verbose>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.33</version>
                    </dependency>
                    <dependency>
                        <groupId>tk.mybatis</groupId>
                        <artifactId>mapper</artifactId>
                        <version>4.2.3</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

③ 在子模块的resources下新建文件 config.properties,将内容改成自己的

#t_pay表包名
package.name=com.mihoyo.cloud

# mysql8.0
jdbc.driverClass = com.mysql.cj.jdbc.Driver
jdbc.url= jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
jdbc.user = root
jdbc.password =123456

③ 在子模块的resources下新建文件 generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <properties resource="config.properties"/>

    <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
            <property name="caseSensitive" value="true"/>
        </plugin>

        <jdbcConnection driverClass="${jdbc.driverClass}"
                        connectionURL="${jdbc.url}"
                        userId="${jdbc.user}"
                        password="${jdbc.password}">
        </jdbcConnection>

        <javaModelGenerator targetPackage="${package.name}.entities" targetProject="src/main/java"/>

        <sqlMapGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java"/>

        <javaClientGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java" type="XMLMAPPER"/>

        <table tableName="t_pay" domainObjectName="Pay">
            <generatedKey column="id" sqlStatement="JDBC"/>
        </table>
    </context>
</generatorConfiguration>

④ 双击运行 Maven 中的插件,一键生成

3.微服务提供者支付module

步骤:

① 创建一个业务逻辑模块(cloud-provider-payment8001)

② 给模块导入依赖

    <dependencies>
        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--SpringBoot集成druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </dependency>
        <!--mybatis和springboot整合-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!--Mysql数据库驱动8 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--persistence-->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
        </dependency>
        <!--通用Mapper4-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!-- fastjson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>provided</scope>
        </dependency>
        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

③ 编写yaml配置文件

server:
  port: 8001

# ==========applicationName + druid-mysql8 driver===================
spring:
  application:
    name: cloud-payment-service

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
    username: root
    password: 123456

# ========================mybatis===================
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.mihoyo.cloud.entities
  configuration:
    map-underscore-to-camel-case: true

④ 创建启动类

@SpringBootApplication
@MapperScan("com.mihoyo.cloud.mapper") //import tk.mybatis.spring.annotation.MapperScan;
public class Main8001 {
    public static void main(String[] args) {
        SpringApplication.run(Main8001.class, args);
    }
}

⑤ 将逆向工程生成的文件拷贝到业务工程中

⑥ 建立传递数值实体类PayDTO

说明:将数据返回给前端时,有些字段不想暴露给前端,将可以暴露的放在PayDTO类中,提高安全性

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayDTO implements Serializable
{
    private Integer id;
    //支付流水号
    private String payNo;
    //订单流水号
    private String orderNo;
    //用户账号ID
    private Integer userId;
    //交易金额
    private BigDecimal amount;
}

⑦ 编写 Service 层的增删改查方法

public interface PayService {
    public int add(Pay pay);
    public int delete(Integer id);
    public int udpate(Pay pay);

    public Pay getById(Integer id);
    public List<Pay> getAll();
}
@Service
public class PayServiceImpl implements PayService {
    @Resource
    private PayMapper payMapper;

    @Override
    public int add(Pay pay) {
        return payMapper.insertSelective(pay);
    }

    @Override
    public int delete(Integer id) {
        return payMapper.deleteByPrimaryKey(id);
    }

    @Override
    public int udpate(Pay pay) {
        return payMapper.updateByPrimaryKeySelective(pay);
    }

    @Override
    public Pay getById(Integer id) {
        return payMapper.selectByPrimaryKey(id);
    }

    @Override
    public List<Pay> getAll() {
        return payMapper.selectAll();
    }
}

⑧ 编写 Controller 层的增删改查方法

@RestController
@Slf4j
@RequestMapping("pay")
public class PayController {
    @Resource
    private PayService payService;

    @PostMapping("add")
    public String addPay(@RequestBody Pay pay){
        int i = payService.add(pay);
        return "成功插入记录,返回值:"+i;
    }

    @DeleteMapping("del/{id}")
    public Integer deletePay(@PathVariable("id") Integer id){
        return payService.delete(id);
    }

    @PutMapping("update")
    public String updatePay(@RequestBody PayDTO payDTO){
        Pay pay = new Pay();
        BeanUtils.copyProperties(payDTO,pay);
        int i = payService.udpate(pay);
        return "成功修改记录,返回值:"+i;
    }

    @GetMapping("get/{id}")
    public Pay getById(@PathVariable("id") Integer id){
        return payService.getById(id);
    }

    @GetMapping("get")
    public List<Pay> getAll(){
        return payService.getAll();
    }
}

⑨ 整合 Swagger3

注解标注位置
@TagController类
@Operation方法上
@Schemamodel层的bean和bean的方法上

注意:pom 文件中必须要导入以下依赖,才能使用 Swagger3

Ⅰ. 添加依赖

<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
   <version>${swagger3.version}</version>
</dependency>

Ⅱ. controller:

@RestController
@Slf4j
@RequestMapping("pay")
@Tag(name = "支付微服务模块",description = "支付CRUD")
public class PayController {
    @Resource
    private PayService payService;

    @PostMapping("add")
    @Operation(summary = "新增",description = "新增支付流水方法,json串做参数")
    public String addPay(@RequestBody Pay pay){
        int i = payService.add(pay);
        return "成功插入记录,返回值:"+i;
    }

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

    @PutMapping("update")
    @Operation(summary = "修改",description = "修改支付流水方法")
    public String updatePay(@RequestBody PayDTO payDTO){
        Pay pay = new Pay();
        BeanUtils.copyProperties(payDTO,pay);
        int i = payService.udpate(pay);
        return "成功修改记录,返回值:"+i;
    }

    @GetMapping("get/{id}")
    @Operation(summary = "按照ID查流水",description = "查询支付流水方法")
    public Pay getById(@PathVariable("id") Integer id){
        return payService.getById(id);
    }

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

Ⅲ. entity:

/**
 * 表名:t_pay
 * 表注释:支付交易表
*/
@Table(name = "t_pay")
@Schema(title = "支付交易表实体类")
public class Pay {
    @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;

    /**
     * @return id
     */
    public Integer getId() {
        return id;
    }

    /**
     * @param id
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
     * 获取支付流水号
     *
     * @return payNo - 支付流水号
     */
    public String getPayNo() {
        return payNo;
    }

    /**
     * 设置支付流水号
     *
     * @param payNo 支付流水号
     */
    public void setPayNo(String payNo) {
        this.payNo = payNo;
    }

    /**
     * 获取订单流水号
     *
     * @return orderNo - 订单流水号
     */
    public String getOrderNo() {
        return orderNo;
    }

    /**
     * 设置订单流水号
     *
     * @param orderNo 订单流水号
     */
    public void setOrderNo(String orderNo) {
        this.orderNo = orderNo;
    }

    /**
     * 获取用户账号ID
     *
     * @return userId - 用户账号ID
     */
    public Integer getUserId() {
        return userId;
    }

    /**
     * 设置用户账号ID
     *
     * @param userId 用户账号ID
     */
    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    /**
     * 获取交易金额
     *
     * @return amount - 交易金额
     */
    public BigDecimal getAmount() {
        return amount;
    }

    /**
     * 设置交易金额
     *
     * @param amount 交易金额
     */
    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }

    /**
     * 获取删除标志,默认0不删除,1删除
     *
     * @return deleted - 删除标志,默认0不删除,1删除
     */
    public Byte getDeleted() {
        return deleted;
    }

    /**
     * 设置删除标志,默认0不删除,1删除
     *
     * @param deleted 删除标志,默认0不删除,1删除
     */
    public void setDeleted(Byte deleted) {
        this.deleted = deleted;
    }

    /**
     * 获取创建时间
     *
     * @return createTime - 创建时间
     */
    public Date getCreateTime() {
        return createTime;
    }

    /**
     * 设置创建时间
     *
     * @param createTime 创建时间
     */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    /**
     * 获取更新时间
     *
     * @return updateTime - 更新时间
     */
    public Date getUpdateTime() {
        return updateTime;
    }

    /**
     * 设置更新时间
     *
     * @param updateTime 更新时间
     */
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}

Ⅳ. 编写配置类,配置Swagger

@Configuration
public class Swagger3Config
{
    @Bean
    public GroupedOpenApi PayApi()
    {
        return GroupedOpenApi.builder().group("支付微服务模块").pathsToMatch("/pay/**").build();
    }
    @Bean
    public GroupedOpenApi OtherApi()
    {
        return GroupedOpenApi.builder().group("其它微服务模块").pathsToMatch("/other/**", "/others").build();
    }
    /*@Bean
    public GroupedOpenApi CustomerApi()
    {
        return GroupedOpenApi.builder().group("客户微服务模块").pathsToMatch("/customer/**", "/customers").build();
    }*/

    @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/"));
    }
}

Ⅴ. 启动项目,访问swagger的地址,调试接口

地址:localhost:8001/swagger-ui/index.html

4.待优化的问题

① 时间日志格式的统一和定制情况?

② Java如何设计API接口实现统一格式返回?

目前的返回值类型有多种:数值,string,对象 .....

这会影响前端、小程序、app等交互体验和开发

③ 如果出现异常如何处理?

全局异常接入返回的标准格式需要全局统一异常,并有统一返回值。

四、项目改进

1.优化时间格式

(1)方式一:

在实体类的时间属性上加 @JsonFormat 注解

@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss" ,timezone = "GMT+8")
private Date createTime;

(2)方式二:

SpringBoot 项目在 yml 中进行配置

spring:
  jackson:
    date-format: yyyy-MM-dd HH-mm-ss
    time-zone: GMT+8

选择:在后续过程中,yml 中的配置内容会越来越多,所以我们选择方式一,采用注解方式

    /**
     * 创建时间
     */
    @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;

2.统一返回结果Result

(1)步骤一:定义一个枚举类,用于状态码的返回

HTTP 请求返回的状态码:

枚举类的书写方法:

① 举值

② 构造

③ 遍历

package com.mihoyo.cloud.resp;

import lombok.Getter;

import java.util.Arrays;

@Getter
public enum ReturnCodeEnum {
    //1.举值
    RC999("999", "操作XXX失败"),
    RC200("200", "success"),
    RC201("201", "服务开启降级保护,请稍后再试!"),
    RC202("202", "热点参数限流,请稍后再试!"),
    RC203("203", "系统规则不满足要求,请稍后再试!"),
    RC204("204", "授权规则不通过,请稍后再试!"),
    RC403("403", "无访问权限,请联系管理员授予权限"),
    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.构造
    private final String code;//自定义状态码,对应枚举的第一个参数
    private final String message;//自定义信息,对应枚举的第二个参数

    ReturnCodeEnum(String code, String message) {
        this.code = code;
        this.message = message;
    }

    //3.遍历
    public static ReturnCodeEnum getReturnCodeEnum(String code) {
        //传入一个状态码,如果有,就返回整个枚举信息,如果没有就返回空
        /* 方式一:传统循环遍历 */
        /*
        for (ReturnCodeEnum element : ReturnCodeEnum.values()) {
            if (element.getCode().equalsIgnoreCase(code)) {
                return element;
            }
        }
        return null;
        */
        /* 方式二:Stream流 */

        return Arrays.stream(ReturnCodeEnum.values()).filter(x -> x.getCode().equalsIgnoreCase(code)).findFirst().orElse(null);
    }
}

(2)步骤二:定义统一返回类Result

返回标准格式:

code状态值由后端统一定义各种返回结果的状态码
message描述本次接口调用的结果描述
data数据本次返回的数据
timestamp接口调用时间(对后期改bug十分重要)
@Data
@Accessors(chain = true)
public class ResultData<T> {

    private String code;
    private String message;
    private T data;
    private long timestamp;//调用方法的时间戳
    
    public ResultData() {
        this.timestamp = System.currentTimeMillis();
    }

    public static <T> ResultData<T> success(T data) {
        ResultData<T> resultData = new ResultData<>();
        resultData.setCode(ReturnCodeEnum.RC200.getCode());
        resultData.setMessage(ReturnCodeEnum.RC200.getMessage());
        resultData.setData(data);
        return resultData;
    }

    public static <T> ResultData<T> fail(String code, String message) {
        ResultData<T> resultData = new ResultData<>();
        resultData.setCode(code);
        resultData.setMessage(message);

        return resultData;
    }
}

(3)步骤三:修改原来接口的返回值

@RestController
@Slf4j
@RequestMapping("pay")
@Tag(name = "支付微服务模块",description = "支付CRUD")
public class PayController {
    @Resource
    private PayService payService;

    @PostMapping("add")
    @Operation(summary = "新增",description = "新增支付流水方法,json串做参数")
    public ResultData<String> addPay(@RequestBody Pay pay){
        int i = payService.add(pay);
        return ResultData.success("成功插入记录,返回值:"+i);
    }

    @DeleteMapping("del/{id}")
    @Operation(summary = "删除",description = "删除支付流水方法")
    public ResultData<Integer> deletePay(@PathVariable("id") Integer id){
        int i = payService.delete(id);
        return ResultData.success(i);
    }

    @PutMapping("update")
    @Operation(summary = "修改",description = "修改支付流水方法")
    public ResultData<String> updatePay(@RequestBody PayDTO payDTO){
        Pay pay = new Pay();
        BeanUtils.copyProperties(payDTO,pay);
        int i = payService.udpate(pay);
        return ResultData.success("成功修改记录,返回值:"+i);
    }

    @GetMapping("get/{id}")
    @Operation(summary = "按照ID查流水",description = "查询支付流水方法")
    public ResultData<Pay> getById(@PathVariable("id") Integer id){
        Pay pay = payService.getById(id);
        return ResultData.success(pay);
    }

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

3.全局异常处理

首先在查询方法中,加入一个异常:

    @GetMapping("get/{id}")
    @Operation(summary = "按照ID查流水",description = "查询支付流水方法")
    public ResultData<Pay> getById(@PathVariable("id") Integer id){
        if(id == -4) throw new RuntimeException("id不能为负数");
        Pay pay = payService.getById(id);
        return ResultData.success(pay);
    }

运行结果:

加入全局异常处理机制:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//触发该异常Http响应状态码为500
    public ResultData<String> exception(Exception e) {
        log.error("全局异常信息:{}", e.getMessage(), e);
        return ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
    }
}

 运行结果:

五、微服务调用者订单module

1.项目构建

步骤:

① 创建一个业务逻辑模块(cloud-consumer-order80)

② 给模块导入依赖

<dependencies>
        <!--web + actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--hutool-all-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!--fastjson2-->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

③ 编写 yaml 配置文件

server:
  port: 80

④ 创建启动类

@SpringBootApplication
public class Main80
{
    public static void main(String[] args)
    {
        SpringApplication.run(Main80.class,args);
    }
}

⑤ 建立传递数值实体类PayDTO

说明:一般而言,调用者不应该获悉服务提供者的 entity 资源并知道表结构关系,所以服务提供方给出的接口文档都都应成为DTO

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayDTO implements Serializable
{
    private Integer id;
    //支付流水号
    private String payNo;
    //订单流水号
    private String orderNo;
    //用户账号ID
    private Integer userId;
    //交易金额
    private BigDecimal amount;
}

2.服务调用RestTemplate

Question:订单微服务 80如何才能调用到支付微服务 8001?

(1) RestTemplate介绍

RestTemplate 提供了多种便捷访问远程 Http 服务的方法,是一种简单便捷的访问 restful 服务模板类,是 Spring 提供的用于访问 Rest 服务的客户端模板工具集。

官网地址

https://docs.spring.io/spring-framework/docs/6.0.11/javadoc-api/org/springframework/web/client/RestTemplate.html

使用:

使用 restTemplate 访问 restful 接口非常的简单粗暴无脑。

(url, requestMap, ResponseBean.class) 这三个参数分别代表 

REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。

getForObject 方法 与 getForEntity 方法的区别:

getForObject:返回对象为响应体中数据转化成的对象,基本上可以理解为 Json  

getForEntity:返回对象为 ResponseEntity 对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等 

GET请求方法POST请求方法
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables);<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);

<T> T getForObject(URI url, Class<T> responseType);

<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
<T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType);<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);

(2)项目配置 RestTemplate

步骤:

① 编写 config 配置类

@Configuration
public class RestTemplateConfig
{
    @Bean
    public RestTemplate restTemplate()
    {
        return new RestTemplate();
    }
}

② 编写 controller 层

@RestController
public class OrderController {
    public static final String PaymentSrv_URL = "http://localhost:8001";//先写死,硬编码

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 一般情况下,通过浏览器的地址栏输入url,发送的只能是get请求
     * 我们底层调用的是post方法,模拟消费者发送get请求
     */
    @GetMapping("/consumer/pay/add")
    public ResultData addOrder(@RequestBody PayDTO payDTO) {
        return restTemplate.postForObject(PaymentSrv_URL + "/pay/add", payDTO, ResultData.class);
    }

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

3.重复代码抽取

存在问题:两个模块中有很多重复的代码。例如实体类、返回结果、异常处理类等。

解决方法:将公共代码抽取到一个模块中,其他模块引用公共模块 

步骤:

① 新建模块(cloud-api-commons)

② 导入依赖

    <dependencies>
        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
        </dependency>
    </dependencies>

③ 将前面两个模块中的公共代码抽取出来放到这个新的模块中(如:entities包、utils包、exception包)

④ maven运行命令clean、install,把模块打成 jar 包,放到本地仓库中。

⑤  支付和订单模块在 pom 文件中引入公共模块的 jar 包

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

⑥ 删除原有模块的重复代码文件,启动项目

4.存在问题

微服务所在的IP地址和端口号硬编码到订单微服务中,会存在非常多的问题

(1)如果订单微服务和支付微服务的IP地址或者端口号发生了变化,则支付微服务将变得不可用,需要同步修改订单微服务中调用支付微服务的IP地址和端口号。

(2)如果系统中提供了多个订单微服务和支付微服务,则无法实现微服务的负载均衡功能。

(3)如果系统需要支持更高的并发,需要部署更多的订单微服务和支付微服务,硬编码订单微服务则后续的维护会变得异常复杂。

所以,在微服务开发的过程中,需要引入服务治理功能,实现微服务之间的动态注册与发现,从此刻开始我们正式进入SpringCloud实战。

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

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

相关文章

【动手学运动规划】 4.1 图搜的基础

&#x1f3f0;代码及环境配置&#xff1a;请参考 环境配置和代码运行! 4.1.1 基础概念 4.1.1.1 Configuration Space(配置空间) configuration: 机器人上每一点位置的完整说明degrees of freedom: 机器人能够独立移动或旋转的关节数量&#xff08;下图所示有4个自由度&#x…

如何用彩屏显示精美的动画

1什么样的动画是精美的&#xff1f; 1&#xff09;视觉暂留 视频播放的原理基于人眼的视觉暂留现象。‌视频是由一系列静态图像&#xff08;帧&#xff09;组成的&#xff0c;这些图像以特定的频率&#xff08;帧率&#xff09;连续播放&#xff0c;使得人眼无法区分单帧图像&…

信息安全工程师(81)网络安全测评质量管理与标准

一、网络安全测评质量管理 遵循标准和流程 网络安全测评应严格遵循国家相关标准和流程&#xff0c;确保测评工作的规范性和一致性。这些标准和流程通常包括测评方法、测评步骤、测评指标等&#xff0c;为测评工作提供明确的指导和依据。 选择合格的测评团队 测评团队应具备相关…

【CTFN】基于耦合翻译融合网络的多模态情感分析的层次学习

同样用了翻译模块的论文->MTMSA 代码地址->github地址 abstract 多模态情感分析是一个具有挑战性的研究领域&#xff0c;涉及多个异构模态的融合。主要的挑战是在多模式融合过程中出现一些缺失的模式。然而&#xff0c;现有的技术需要所有的模态作为输入&#xff0c;因…

1.每日SQL----2024/11/7

题目&#xff1a; 计算用户次日留存率,即用户第二天继续登录的概率 表&#xff1a; iddevice_iddate121382024-05-03232142024-05-09332142024-06-15465432024-08-13523152024-08-13623152024-08-14723152024-08-15832142024-05-09932142024-08-151065432024-08-131123152024-…

安利一款开源企业级的报表系统SpringReport

SpringReport是一款企业级的报表系统&#xff0c;支持在线设计报表&#xff0c;并绑定动态数据源&#xff0c;无需写代码即可快速生成想要的报表&#xff0c;可以支持excel报表和word报表两种格式&#xff0c;同时还可以支持excel多人协同编辑&#xff0c;后续考虑实现大屏设计…

使用ookii-dialogs-wpf在WPF选择文件夹时能输入路径

在进行WPF开发时&#xff0c;System.Windows.Forms.FolderBrowserDialog的选择文件夹功能不支持输入路径&#xff1a; 希望能够获得下图所示的选择文件夹功能&#xff1a; 于是&#xff0c;通过NuGet中安装Ookii.Dialogs.Wpf包&#xff0c;并创建一个简单的工具类&#xff1a; …

RHCE---搭建lnmp云存储

一、恢复快照后&#xff0c;检查安全性&#xff08;查看selinux 以及防火墙&#xff09; 二、搭建LNMP环境 [rootserver ~]# yum -y install nginx mariadb-server php*三、上传软件 1、将nextcloud-25.0.1.zip压缩包传递到根目录下 2、解压缩nextcloud-25.0.1.zip &#xf…

Day95 Docker

Docker的使用 1、Docker是什么 docker是一个用来管理镜像的容器 容器(container)&#xff1a;可以装东西 镜像( image )&#xff1a;所谓的镜像&#xff0c;你可以认为就是一个虚拟机 虚拟机&#xff1a;用软件代理硬件来模拟整个计算机的这样一套软件就成为 虚拟机 镜像说白了…

从分析Vue实例生命周期开始,剖析Vue页面跳转背后执行过程

文章目录 1.概要2.Vue实例生命周期3.生命周期函数解释4.存在父子组件情况页面执行过程5. 分析路由跳转页面执行过程6.扩展补充7.小结 1.概要 本文旨在分析Vue页面进行路由切换时&#xff0c;Vue背后的运行过程&#xff0c;旨在让大家更加清晰地明白Vue页面运行过程中钩子方法的…

超分辨重建——复现SwinIR网络推理测试(详细图文教程)

&#x1f4aa; 专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &#x1f4dd;《图像增强》 &a…

【青牛科技】应用方案 | D75xx-150mA三端稳压器

概 述 D75XX系列是一套三端高电流低压稳压器。它们可以提供 150mA 的输出电流和允许输入电压高达30V。它们有几个固定的输出电压范围为3.0 V至5.0 V。CMOS 技术确保低电压降和低静态电流。 虽然这些设备主要设计为固定电压调节器&#xff0c;但它们可以与外部元件一起使用&…

BO-CNN-LSTM回归预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多输入单输出回归预测

BO-CNN-LSTM回归预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多输入单输出回归预测 目录 BO-CNN-LSTM回归预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多输入单输出回归预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 …

WPF 打包

打包为单个exe文件直接运行 - - -版本.NET8 新建WPF项目 右键 - 发布 选择发布文件夹 选择发布文件夹 选择发布文件夹 配置 配置,保存 发布 WPF 打包为exe安装程序 示例 实现思路 引导项目中嵌入其它项目可运行目录的zip引导项目中解压zip文件到指定文件夹是…

三维测量与建模笔记 - 3.3 张正友标定法

上图中&#xff0c;提到了世界坐标系在张正友标定法中的设计&#xff0c;可以理解为将世界坐标系的原点放到了棋盘格左上角点的位置&#xff0c;并且棋盘格平面上所有点的Z为0&#xff0c;将Z规定为0的话&#xff0c;可以简化掉一个维度&#xff08;列向量r3&#xff09;。去掉…

【解决办法】无法使用右键“通过VSCode打开文件夹”

个人博客&#xff1a;苏三有春的博客 前言 作者的编程环境为VScode&#xff0c;工作时常使用VScode打开整个工程文件夹。如果先打开VScode再从VScode中选择文件夹打开效率太慢&#xff0c;作者一般使用的方式是右键文件夹&#xff0c;直接选择"通过code打开文件夹"…

推荐一款ETCD桌面客户端——Etcd Workbench

Etcd Workbench 我相信很多人在开始管理ETCD的时候都去搜了Etcd客户端工具&#xff0c;然后找到了官方的Etcd Manager&#xff0c;但用完之后发现它并不好用&#xff0c;还不支持多连接和代码格式化&#xff0c;并且已经好几年不更新了&#xff0c;于是市面上就有了好多其他客…

Docker配置及简单应用

谈论/理解 Docker 的常用核心部分&#xff0c;以下皆在 Ubuntu 操作系统下进行 1 国内源安装 Docker-ce 1.1 配置 Linux 内核流量转发 因为docker和宿主机的端口映射&#xff0c;本质是内核的流量转发功能&#xff0c;所以要对其进行配置 1.1.1 未配置流量转发 如果没有配置流…

(十二)JavaWeb后端开发——MySQL数据库

目录 1.数据库概述 2.MyQSL 3.数据库设计 DDL 4.MySQL常见数据类型 5.DML 1.数据库概述 数据库&#xff1a;DataBase(DB)&#xff0c;是存储和管理数据的仓库 数据库管理系统&#xff1a;DataBase ManagementSystem(DBMS)&#xff0c;操纵和管理数据库的大型软件 SQL&a…

fastadmin后台列表根据所选中的行统计指定字段|fastadmin点击checkbox或反选统计某个字段的值

当选中对应行时&#xff0c;统计选中行的用户注册数和用户点击数。 此项功能需要有 点击全选触发事件、点击反选触发事件、勾选某一行触发事件、反选某一行触发事件&#xff0c;用到fastadmin自带的表格事件功能&#xff0c;参考&#xff1a;https://doc.fastadmin.net/doc/19…