微服务开发流程及详细案例
文章目录
一、 微服务示例简介
cloud-demo:父工程,管理依赖
• 子工程 order-service:订单微服务,负责订单相关业务
– 接口:根据id查询订单
• 子工程 user-service:用户微服务,负责用户相关业务
– 接口:根据id查询用户
要求:
• 订单微服务和用户微服务都必须有各自的数据库,相互独立
• 订单服务和用户服务都对外暴露Restful的接口
• 订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库
之后会补充Eureka、GateWay、OpenFeign,使微服务项目更完整。
项目笔记及代码:
百度网盘链接:https://pan.baidu.com/s/15SuZRNdU3co_4Ma_JPcp-w
提取码:6666
二、 IDEA创建父Maven工程及子项目
2.1 使用IDEA创建Maven项目
(这里项目名称路径改为自己的就行)
2.2创建子项目模块
用IDEA创建order-service和user-service
项目结构如下:
2.3父项目引入依赖
引入主要包括SpringBoot、SpringCloud,还包括需要用到的MySql,Mybatis和MybatisPlus,以及接口文档Swagger-Knife4j等
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rjs.gm</groupId>
<artifactId>cloud-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>order-service</module>
<module>user-service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<mybatis.starter.version>2.1.2</mybatis.starter.version>
<mapper.starter.version>2.1.1</mapper.starter.version>
<mysql.starter.version>5.1.47</mysql.starter.version>
<mybatis-plus.starter.version>3.4.2</mybatis-plus.starter.version>
<swagger.version>2.6.1</swagger.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- springcloud依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 通用Mapper启动器-->
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.starter.version}</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.starter.version}</version>
</dependency>
<!-- spring-boot-devtools热启动依赖包 start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<version>2.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.8</version>
</dependency>
<!--swagger 自动生成接口文档end-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.starter.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<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>
</plugins>
</build>
</project>
三、编写子项目
3.1 写入子项目(order-service和user-service)依赖
<dependencies>
<!--service-common module依赖 之后会用到-->
<!-- <dependency>-->
<!-- <groupId>com.cloud</groupId>-->
<!-- <artifactId>service-common</artifactId>-->
<!-- <version>1.0-SNAPSHOT</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis 和 mybatisplus只能二选一,我这里选择使用mybatisplus-->
<!-- <dependency>-->
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<!--erueka客户端依赖,服务注册,之后会用到-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--fegin依赖,服务调用,之后会用到-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.12.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
3.2创建数据库表
首先,创建俩个数据表
cloud-user表中初始数据如下:
cloud-order表中初始数据如下:
cloud-order表中持有cloud-user表中的id字段。
3.2 添加插件MybatisX
3.3 IEDA连接数据库
点击右边栏的database(只有专业版IDEA才有,不是专业版自行下载)。选择MySql数据库
3.4通过MybatisX插件自动生成项目文件
目前的子项目目录结构。找到对于表点右键并点击MybatisX-Generator
选择要生成的项目以及生成的目录,我这里为了方便,直接生成到src/main/java下,包名是,entity是实体类文件名,可以自行修改
我这里选择mybatisplus3版本,并选择自动生成注释
生成文件及目录如下:
3.5编写控制类及接口
新建controller目录,并编写订单控制类,这里只编写一个简单查询接口,返回类型先不做限制,之后会介绍具体方法。
@Api(tags = "订单信息接口", description = "提供订单信息的相关 API")
@RestController
//@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping(value = "order")
public class OrderController {
/** * 订单 */
@Resource
private CloudOrderService cloudOrderService;
@ApiOperation(value = "查询单个订单信息")
@GetMapping("selectOrderById")
public String selectPartById(@RequestParam(value = "id") Integer id){
CloudOrder cloudOrder = cloudOrderService.getById(id);
if (cloudOrder != null) {
return cloudOrder.getName();
}
return "查询出错";
}
}
3.6编写子项目配置文件
在resource目录下创建application.yml配置文件
主要包括端口号、数据库信息,还有一些注册中心eureka的相关信息(先不做了解),以及一些mybatis和mybatisplus的配置信息
server:
# 服务端口号
port: 8082
spring:
application:
# 服务名称 - 服务之间使用名称进行通讯
name: Order-Service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/gm?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: root
eureka:
client:
service-url:
# 填写注册中心服务器地址
defaultZone: http://127.0.0.1:8081/eureka
# 是否需要将自己注册到注册中心
register-with-eureka: true
# 是否需要搜索服务信息
fetch-registry: true
instance:
# 使用ip地址注册到注册中心
prefer-ip-address: true
# 注册中心列表中显示的状态参数
instance-id: ${
spring.cloud.client.ip-address}:${
server.port}
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.cloud.OrderService.entity
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.7 编写项目启动类
也是最后一步了,马上就能看到效果了,兄弟们坚持住!
在创建启动类
@SpringBootApplication
@EnableEurekaClient
@EnableSwagger2WebMvc
@MapperScan("com.rjs.gm.mapper")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
user-service与order-sevice创建过程完全一样,端口号设置为8083。这里就不做具体简绍了,具体请看项目源码。
四、项目启动以及接口测试
4.1 项目启动
先打开Services窗口,方便查看多个SpirngBoot项目。
启动order-service和user-service俩个微服务。
报错的话首先看配置文件中数据库信息是否正确,部分报错是因为我们的服务注册还没有编写,不影响我们的使用,先忽略。
4.2 普通接口测试
根据控制类及接口参数等进行接口测试:
控制类接口信息:
测试结果如下:
4.3 Swagger接口文档测试
输入网址http://localhost:8082/doc.html#/打开之前我们专门用到的swagger接口文档,可以很清晰并方便的进行接口测试。
五、接口返回结果类编写
目前我们的返回结果类型仅仅是一个字符串,为了更方便的进行前后端交互以及更好的了解返回实际情况,我们需要专门编写一个返回控制类。
我们将类编写到service-commen子模块中,将以后的公共类以及一些工具类放入此模块中。
5.1新建service-common子项目模块
pom文件中添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>com.rjs.gm</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-service</artifactId>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
</project>
新建ResultVO类
该返回结果对象类属性包括返回状态码(code)、返回消息(message)、返回数据(data)、返回总记录数(total)【用于分页查询使用】。方法主要包括请求成功、请求失败、没权限等几种函数。
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResultVO<T> {
public static final Integer SUCCESS = 0;
public static final Integer FAILED = 1;
public static final Integer ERROR = 2;
public static final Integer UNLOG = 3;
public static final Integer NOAUTH = 4;
//("状态码 0成功 1失败 2服务器出错 3未登录 4没有权限")
private Integer code=SUCCESS;
private String message;
//("返回数据")
private T data;
private Long total= 1L;
private ResultVO() {
this.code = SUCCESS;
this.message = "请求成功...";
}
private ResultVO(T data) {
this.code = SUCCESS;
this.message = "请求成功...";
this.data = data;
}
private ResultVO(T data, Long total) {
this.code = SUCCESS;
this.message = "请求成功...";
this.data = data;
this.total = total;
}
private ResultVO(Integer code, String message) {
this.code = code;
this.message = message;
}
private ResultVO(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
private ResultVO(Integer code, String message, T data, Long total) {
this.code = code;
this.message = message;
this.data = data;
this.total = total;
}
private ResultVO(Throwable exp){
this.code=ERROR;
this.message=exp.getMessage();
}
/** * 请求成功 状态码 1 * * @param <T> 类型 * @return ResultVO */
public static <T> ResultVO getSuccess(T data) {
return new ResultVO<T>(data);
}
/** * 请求成功 状态码 1 * * @param <T> 类型 * @return ResultVO */
public static <T> ResultVO getSuccess(T data, Long total) {
return new ResultVO<T>(data,total);
}
/** * 请求成功 状态码 1 * * @param <T> 类型 * @return ResultVO */
public static <T> ResultVO getSuccess() {
return new ResultVO<T>();
}
/** * 请求成功 状态码 1 * * @param message 返回信息 * @param data 返回对象 * @param <T> 类型 * @return ResultVO */
public static <T> ResultVO getSuccess(String message, T data) {
return new ResultVO<T>(SUCCESS, message, data);
}
/** * 请求成功 状态码 1 * * @param message 返回信息 * @param data 返回对象 * @param <T> 类型 * @return ResultVO */
public static <T> ResultVO getSuccess(String message, T data, Long total) {
return new ResultVO<T>(SUCCESS, message, data, total);
}
/** * 请求失败 状态码 0 * * @param message 返回信息 * @param <T> 类型 * @return ResultVO */
public static <T> ResultVO getFailed(String message) {
return new ResultVO<T>(FAILED, message);
}
/** * 请求失败 状态 0 * * @param message 返回信息 * @param data 返回数据 * @param <T> 类型 * @return ResultVO */
public static <T> ResultVO getFailed(String message, T data) {
return new ResultVO<T>(FAILED, message, data);
}
/** * 用户未登录 * * @param <T> 类型 * @return ResultVO */
public static <T> ResultVO getNoLogin() {
return new ResultVO<T>(UNLOG, "用户未登录,请重新登录");
}
/** * 用户没有操作权限 * * @param <T> 类型 * @return ResultVO */
public static <T> ResultVO getNoAuthorization() {
return new ResultVO<T>(NOAUTH, "用户没有操作权限,请重新登录");
}
public static <T> ResultVO getException(Throwable exp) {
return new ResultVO<T>(exp);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Long getTotal() {
return total;
}
public void setTotal(Long code) {
this.total = total;
}
}
5.2 修改order-service接口返回类型
首先需要在order-service和user-service引入我们的common-service模块的依赖
<dependency>
<groupId>com.rjs.gm</groupId>
<artifactId>common-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
接下来就可以修改我们的控制类接口的返回类型了。
我们重新编写一个查询接口,这次返回一个ResultVO泛型类。
@Api(tags = "订单信息接口", description = "提供订单信息的相关 API")
@RestController
//@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping(value = "order")
public class OrderController {
/** * 订单 */
@Resource
private CloudOrderService cloudOrderService;
// @ApiOperation(value = "查询单个订单信息")
// @GetMapping("selectOrderById")
// public String selectOrderById(@RequestParam(value = "id") Integer id){
// CloudOrder cloudOrder = cloudOrderService.getById(id);
//
// if (cloudOrder != null) {
// return cloudOrder.getName();
// }
//
// return "查询出错";
// }
@ApiOperation(value = "根据id查询单个订单信息")
@GetMapping("selectOrderById")
public ResultVO<CloudOrder> selectOrderById(@RequestParam(value = "id") Integer id){
CloudOrder cloudOrder = cloudOrderService.getById(id);
if (cloudOrder != null) {
return ResultVO.getSuccess(cloudOrder);
}
return ResultVO.getFailed("请求出错");
}
}
5.3 重新启动并测试接口
普通测试
Swagger测试
部分完结撒花🐱🏍🐱🏍🐱🏍:
这样一个简单且完整的SpringCloud项目就完成了,主要就是在一个父项目中实现俩个SpringBoot项目。后续主要就是SpringCloud独有的一些内容了,有需要的话可以进一步了解。
六、SpringCloud注册中心—Eureka
6.1 新建service-eureka模块
6.2 添加项目依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>com.rjs.gm</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
6.3 在需要注册的微服务的pom文件中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
6.4 编写配置文件
端口号设置为8081
server:
port: 8081
spring:
application: #eureka的服务名称
name: service-eureka
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:8081/eureka
6.5 编写启动类
6.6 启动并测试
浏览器输入网址 http://localhost:8081/ 出现以下页面表示成功。
七、SpringCloud网关—GateWay
7.1 新建service-gateway模块
7.2 添加项目依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>com.rjs.gm</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
7.3 编写配置文件
server:
port: 9090
spring:
application:
name: service-gateway
cloud:
gateway:
discovery:
locator:
# 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
# http://网关地址/服务名称(小写)/**
enabled: true
# 通过小写的注册中心的服务名称进行访问
lower-case-service-id: true
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8081/eureka
instance:
prefer-ip-address: true
7.4 添加启动类
@SpringBootApplication
@EnableEurekaClient
public class ServiceGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceGatewayApplication.class, args);
}
}
7.5 启动项目并测试
当服务比较多的时候,程序员需要记住每个端口号及其对应的服务,比较麻烦。
而网关的主要功能就是路由转发:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。
有无网关请求对比:
无网关:只能通过 localhost:服务端口号/路由地址 来访问
有网关:就可以直接通过 localhost:网关端口号:子服务注册名(全小写)/路由地址 来访问。在服务比较多的情况下对与前后端开发都是很方便的。
八、SpringCloud服务调用—OpenFeign
8.1 要求:
• 订单微服务和用户微服务都必须有各自的数据库,相互独立
• 订单服务和用户服务都对外暴露Restful的接口
• 订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库
在服务都注册到注册中心eureka的情况下就可以使用openfeign来进行服务间的调用
8.2 提供者与消费者
在服务调用关系中,会有两个不同的角色:
服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
8.3 详细过程
8.3.1 服务消费者(order-service)引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
8.3.2 添加user-service依赖
8.3.3 修改CloudOrder类
8.3.4 修改服务启动类
@SpringBootApplication
@EnableEurekaClient
@EnableSwagger2WebMvc
@MapperScan("com.rjs.gm.mapper")
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
8.3.5 创建服务调用接口
@FeignClient("UserService") //被调用服务的注册服务名称
public interface UserServiceByClient {
// user-service服务的根据id查询用户的接口
@GetMapping("/user/selectUserById")
ResultVO<CloudUser> selectUserById(@RequestParam(value = "id") Integer id);
}
其中,该调用接口对应于user-service服务控制类中的接口
8.3.6 修改控制类
@Api(tags = "订单信息接口", description = "提供订单信息的相关 API")
@RestController
//@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping(value = "order")
public class OrderController {
/** * 订单 */
@Resource
private CloudOrderService cloudOrderService;
@Autowired
private UserServiceByClient userServiceByClient;
@ApiOperation(value = "根据id查询单个订单信息")
@GetMapping("selectOrderById")
public ResultVO<CloudOrder> selectOrderById(@RequestParam(value = "id") Integer id){
// 1.查询订单
CloudOrder cloudOrder = cloudOrderService.getById(id);
// 2.利用OpenFeign发起http请求。查询用户
CloudUser user = this.userServiceByClient.selectUserById(cloudOrder.getUserId().intValue()).getData();
cloudOrder.setUser(user);
if (cloudOrder != null) {
return ResultVO.getSuccess(cloudOrder);
}
return ResultVO.getFailed("请求出错");
}
}
8.4 启动并测试
将所有服务都重新启动,首先启动eureka,确保将所有服务都注册到注册中心中
浏览器打开 http://localhost:8081
普通测试
还是同样的请求路径,这次会发现订单信息中包含了用户信息。
Swagger接口文档测试
完结撒花🐱🏍🐱🏍🐱🏍🐱🏍🐱🏍:
这样我们就完全实现了一个SpringCloud项目,用到了最常用的组件包括注册中心(eureka)、网关(gateway)、以及服务调用(openfeign)。多个子服务注册到服务中心,并实现了服务间调用,同时还用到了网关。子服务即SpringBoot项目,通过MybatisX插件自动生成项目文件,还包括MybatisPlus代码,让我们能够不写一行Sql就能实现常用的增删改查。当然,MybatisPlus也支持各种条件查询,让我们很方便的操作数据库。
本文只是用来记录自己几个月的学习实践所得,并无他用。各位看官觉得有用可以点个小赞啦!
今天的文章Java微服务开发流程及详细案例分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/23762.html