cloud-demo:父工程,管理依赖
• 子工程 order-service:订单微服务,负责订单相关业务
– 接口:根据id查询订单
• 子工程 user-service:用户微服务,负责用户相关业务
– 接口:根据id查询用户
要求:
• 订单微服务和用户微服务都必须有各自的数据库,相互独立
• 订单服务和用户服务都对外暴露Restful的接口
• 订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库
之后会补充Eureka、GateWay、OpenFeign,使微服务项目更完整。
项目笔记及代码:
百度网盘链接:https://pan.baidu.com/s/15SuZRNdU3co_4Ma_JPcp-w
提取码:6666
(这里项目名称路径改为自己的就行)
用IDEA创建order-service和user-service
项目结构如下:
引入主要包括SpringBoot、SpringCloud,还包括需要用到的MySql,Mybatis和MybatisPlus,以及接口文档Swagger-Knife4j等
<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.0modelVersion>
<groupId>com.rjs.gmgroupId>
<artifactId>cloud-demoartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<modules>
<module>order-servicemodule>
<module>user-servicemodule>
modules>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.7.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring-cloud.version>Hoxton.SR8spring-cloud.version>
<mybatis.starter.version>2.1.2mybatis.starter.version>
<mapper.starter.version>2.1.1mapper.starter.version>
<mysql.starter.version>5.1.47mysql.starter.version>
<mybatis-plus.starter.version>3.4.2mybatis-plus.starter.version>
<swagger.version>2.6.1swagger.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>${mybatis.starter.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.starter.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
<version>2.3.7.RELEASEversion>
dependency>
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>knife4j-spring-boot-starterartifactId>
<version>2.0.8version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.2version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis-plus.starter.version}version>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>knife4j-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.2version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.73version>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombok-maven-pluginartifactId>
<version>1.18.12.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>RELEASEversion>
<scope>testscope>
dependency>
dependencies>
首先,创建俩个数据表
cloud-user表中初始数据如下:
cloud-order表中初始数据如下:
cloud-order表中持有cloud-user表中的id字段。
点击右边栏的database(只有专业版IDEA才有,不是专业版自行下载)。选择MySql数据库
目前的子项目目录结构。找到对于表点右键并点击MybatisX-Generator
选择要生成的项目以及生成的目录,我这里为了方便,直接生成到src/main/java下,包名是,entity是实体类文件名,可以自行修改
我这里选择mybatisplus3版本,并选择自动生成注释
生成文件及目录如下:
新建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 "查询出错";
}
}
在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
也是最后一步了,马上就能看到效果了,兄弟们坚持住!
在创建启动类
@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。这里就不做具体简绍了,具体请看项目源码。
先打开Services窗口,方便查看多个SpirngBoot项目。
启动order-service和user-service俩个微服务。
报错的话首先看配置文件中数据库信息是否正确,部分报错是因为我们的服务注册还没有编写,不影响我们的使用,先忽略。
根据控制类及接口参数等进行接口测试:
控制类接口信息:
测试结果如下:
输入网址http://localhost:8082/doc.html#/打开之前我们专门用到的swagger接口文档,可以很清晰并方便的进行接口测试。
目前我们的返回结果类型仅仅是一个字符串,为了更方便的进行前后端交互以及更好的了解返回实际情况,我们需要专门编写一个返回控制类。
我们将类编写到service-commen子模块中,将以后的公共类以及一些工具类放入此模块中。
pom文件中添加依赖
<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-demoartifactId>
<groupId>com.rjs.gmgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>common-serviceartifactId>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-annotationsartifactId>
<version>2.11.0version>
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 类型
* @return ResultVO
*/
public static <T> ResultVO getSuccess(T data) {
return new ResultVO<T>(data);
}
/**
* 请求成功 状态码 1
*
* @param 类型
* @return ResultVO
*/
public static <T> ResultVO getSuccess(T data, Long total) {
return new ResultVO<T>(data,total);
}
/**
* 请求成功 状态码 1
*
* @param 类型
* @return ResultVO
*/
public static <T> ResultVO getSuccess() {
return new ResultVO<T>();
}
/**
* 请求成功 状态码 1
*
* @param message 返回信息
* @param data 返回对象
* @param 类型
* @return ResultVO
*/
public static <T> ResultVO getSuccess(String message, T data) {
return new ResultVO<T>(SUCCESS, message, data);
}
/**
* 请求成功 状态码 1
*
* @param message 返回信息
* @param data 返回对象
* @param 类型
* @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 类型
* @return ResultVO
*/
public static <T> ResultVO getFailed(String message) {
return new ResultVO<T>(FAILED, message);
}
/**
* 请求失败 状态 0
*
* @param message 返回信息
* @param data 返回数据
* @param 类型
* @return ResultVO
*/
public static <T> ResultVO getFailed(String message, T data) {
return new ResultVO<T>(FAILED, message, data);
}
/**
* 用户未登录
*
* @param 类型
* @return ResultVO
*/
public static <T> ResultVO getNoLogin() {
return new ResultVO<T>(UNLOG, "用户未登录,请重新登录");
}
/**
* 用户没有操作权限
*
* @param 类型
* @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;
}
}
首先需要在order-service和user-service引入我们的common-service模块的依赖
<dependency>
<groupId>com.rjs.gmgroupId>
<artifactId>common-serviceartifactId>
<version>1.0-SNAPSHOTversion>
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("请求出错");
}
}
普通测试
Swagger测试
部分完结撒花:
这样一个简单且完整的SpringCloud项目就完成了,主要就是在一个父项目中实现俩个SpringBoot项目。后续主要就是SpringCloud独有的一些内容了,有需要的话可以进一步了解。
<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-demoartifactId>
<groupId>com.rjs.gmgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>eureka-serviceartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
project>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
端口号设置为8081
server:
port: 8081
spring:
application: #eureka的服务名称
name: service-eureka
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:8081/eureka
浏览器输入网址 http://localhost:8081/ 出现以下页面表示成功。
<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-demoartifactId>
<groupId>com.rjs.gmgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>service-gatewayartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
project>
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
@SpringBootApplication
@EnableEurekaClient
public class ServiceGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceGatewayApplication.class, args);
}
}
当服务比较多的时候,程序员需要记住每个端口号及其对应的服务,比较麻烦。
而网关的主要功能就是路由转发:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。
有无网关请求对比:
无网关:只能通过 localhost:服务端口号/路由地址 来访问
有网关:就可以直接通过 localhost:网关端口号:子服务注册名(全小写)/路由地址 来访问。在服务比较多的情况下对与前后端开发都是很方便的。
• 订单微服务和用户微服务都必须有各自的数据库,相互独立
• 订单服务和用户服务都对外暴露Restful的接口
• 订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库
在服务都注册到注册中心eureka的情况下就可以使用openfeign来进行服务间的调用
在服务调用关系中,会有两个不同的角色:
服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
@SpringBootApplication
@EnableEurekaClient
@EnableSwagger2WebMvc
@MapperScan("com.rjs.gm.mapper")
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
@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("请求出错");
}
}
将所有服务都重新启动,首先启动eureka,确保将所有服务都注册到注册中心中
浏览器打开 http://localhost:8081
普通测试
还是同样的请求路径,这次会发现订单信息中包含了用户信息。
Swagger接口文档测试
完结撒花:
这样我们就完全实现了一个SpringCloud项目,用到了最常用的组件包括注册中心(eureka)、网关(gateway)、以及服务调用(openfeign)。多个子服务注册到服务中心,并实现了服务间调用,同时还用到了网关。子服务即SpringBoot项目,通过MybatisX插件自动生成项目文件,还包括MybatisPlus代码,让我们能够不写一行Sql就能实现常用的增删改查。当然,MybatisPlus也支持各种条件查询,让我们很方便的操作数据库。
本文只是用来记录自己几个月的学习实践所得,并无他用。各位看官觉得有用可以点个小赞啦!