Nacos是Spring Cloud Alibaba提供的一个软件
这个软件主要具有注册中心和配置中心的功能,此处先聊注册中心的功能
微服务中所有项目都必须注册到注册中心才能成为微服务的一部分
注册中心和企业中的人力资源管理部门有相似
一般电商项目的微服务结构:
启动Nacos必须保证当前系统配置了java环境变量
简单来说就是要环境变量中,有JAVA_HOME的配置,指向安装jdk的路径
确定了支持java后,可以通过下面路径下载
https://github.com/alibaba/nacos/releases/download/1.4.3/nacos-server-1.4.3.zip
将下载好的Nacos压缩包解压
将压缩包解压(注意不要有中文路径或空格)
打开解压得到的文件夹后打开bin目录会有如下内容
- cmd结尾的文件是windows版本的
- sh结尾的文件是linux和mac版本的
- startup是启动文件,shutdown是停止文件
Windows下启动Nacos不能直接双击cmd文件
需要进入dos命令运行
在当前资源管理器地址栏输入cmd
F:nacosbin>startup.cmd -m standalone
- -m是设置启动方式参数
- standalone翻译为标准的单独的,意思是单机模式标准运行
运行成功默认占用8848端口,并且在代码中提示
如果不输入standalone运行会失败
验证Nacos的运行状态
打开浏览器输入http://localhost:8848/nacos
首次访问,会出现这个界面
登录系统 - 用户名:nacos
- 密码:nacos
登录之后可以进入后台列表,不能关闭启动nacos的dos窗口
我们要让我们编写的项目注册到Nacos,才能真正是微服务项目
模拟用户选中购物车中商品并且确定了数量的情况下点击提交订单时后端的操作
业务逻辑:
1.减少选中商品sku的库存数
2.删除用户再购物车中勾选的商品
3.生成订单,将订单信息保存到数据库
上面三个步骤分别由3个模块完成:
- 库存模块:减少库存
- 购物车模块:删除购物车信息
- 订单模块:新增订单
首先删除项目的src目录,因为我们使用不到
其次,pom文件有大量配置
如下:
A.创建通用项目commons4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.4 cn.bob small-ex 0.0.1-SNAPSHOT small-ex 微服务框架演示案例 pom 1.8 2020.0.3 2.2.2.RELEASE 2.5.4 2.3.0.RELEASE 1.0.10.RELEASE 2.2.0 3.4.1 1.4.0 8.0.26 1.18.20 2.0.9 2.3.10 5.5.2 1.2.45 1.1.20 0.9.0 1.4.2 org.projectlombok lombok io.seata seata-all ${seata-server.version} org.projectlombok lombok ${lombok.version} mysql mysql-connector-java ${mysql.version} runtime com.alibaba druid ${druid.version} org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis-spring-boot.version} com.baomidou mybatis-plus-boot-starter ${mybaits-plus.version} com.baomidou mybatis-plus-generator ${mybaits-plus.version} com.github.pagehelper pagehelper-spring-boot-starter ${pagehelper-spring-boot.version} org.springframework.boot spring-boot-starter ${spring-boot.version} org.springframework.boot spring-boot-starter-web ${spring-boot.version} org.springframework.boot spring-boot-starter-freemarker ${spring-boot.version} org.springframework.boot spring-boot-starter-validation ${spring-boot.version} org.springframework.boot spring-boot-starter-security ${spring-boot.version} org.springframework.boot spring-boot-starter-oauth2-client ${spring-boot.version} org.springframework.boot spring-boot-configuration-processor ${spring-boot-configuration-processor.version} org.springframework.security spring-security-jwt ${spring-security-jwt.version} com.github.xiaoymin knife4j-spring-boot-starter ${knife4j-spring-boot.version} org.springframework.boot spring-boot-starter-data-redis ${spring-boot.version} org.springframework.boot spring-boot-starter-data-mongodb ${spring-boot.version} org.springframework.boot spring-boot-starter-data-elasticsearch ${spring-boot.version} org.springframework.boot spring-boot-starter-amqp ${spring-boot.version} org.springframework.boot spring-boot-starter-actuator ${spring-boot.version} org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import com.alibaba fastjson ${fastjson.version} io.jsonwebtoken jjwt ${jjwt.version} org.springframework.boot spring-boot-starter-test ${spring-boot.version} test org.springframework.amqp spring-rabbit-test ${spring-rabbit-test.version} test org.springframework.security spring-security-test ${spring-security-test.version} test io.seata seata-spring-boot-starter ${seata-server.version}
- 删除test测试文件夹
- 删除resources目录
- 删除SpringBoot启动类
编写父项目的module配置
csmall-commons
修改子项目pom文件
4.0.0 cn.bob small-ex 0.0.1-SNAPSHOT cn.bob small-commons 0.0.1-SNAPSHOT small-commons Demo project for Spring Boot com.github.xiaoymin knife4j-spring-boot-starter org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-json org.springframework.boot spring-boot-starter-tomcat
创建包
cn.bob.small.commons.pojo.cart.dto包
包中创建类CartAddDTO
如下:
package cn.bob.small.commons.pojo.cart.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@ApiModel("购物车新增DTO")
@Data
public class CartAddDTO implements Serializable {
@ApiModelProperty(value = "商品编号",name = "commodityCode",example = "PC100")
private String commodityCode;
@ApiModelProperty(value = "商品单价",name = "price",example = "100")
private Integer price;
@ApiModelProperty(value = "数量",name = "count",example = "10")
private Integer count;
@ApiModelProperty(value = "用户ID",name = "userId",example = "UU100")
private String userId;
}
创建包
pojo.cart.entity
包中创建类Cart
package cn.bob.small.commons.pojo.cart.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class Cart implements Serializable {
private Integer id;
// 商品编号
private String commodityCode;
// 价格
private Integer price;
// 数量
private Integer count;
// 用户id
private Integer userId;
}
创建订单模块需要的类
pojo.order.dto.OrderAddDTO
package cn.bob.small.commons.pojo.cart.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@ApiModel("新增订单的DTO")
@Data
public class OrderAddDTO implements Serializable {
@ApiModelProperty(value = "用户id",name="userId",example = "UU100")
private String userId;
@ApiModelProperty(value = "商品编号",name="commodityCode",example = "PC100")
private String commodityCode;
@ApiModelProperty(value = "商品数量",name="count",example = "5")
private Integer count;
@ApiModelProperty(value = "总金额",name="money",example = "50")
private Integer money;
}
pojo.order.entity.Order
package cn.bob.small.commons.pojo.cart.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class Order implements Serializable {
private Integer id;
private String userId;
private String commodityCode;
private Integer count;
private Integer money;
}
库存相关的类
pojo.stock.dto.StockReduceCountDTO
package cn.bob.small.commons.pojo.cart.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@ApiModel("商品减少库存DTO")
@Data
public class StockReduceCountDTO implements Serializable {
@ApiModelProperty(value = "商品编号",name="commodityCode",example = "PC100")
private String commodityCode;
@ApiModelProperty(value = "减库存数",name="reduceCount",example = "5")
private Integer reduceCount;
}
pojo.stock.entity.Stock
package cn.bob.small.commons.pojo.cart.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class Stock implements Serializable {
private Integer id;
private String commodityCode;
private Integer reduceCount;
}
创建异常相关类
commons项目除了实体类之外
项目使用到的所有异常相关类,也统一编写在这个类中
创建cn.bob.small.commons.restful包
首先创建常见响应状态码的枚举
package cn.bob.small.commons.restful;
public enum ResponseCode {
OK(200),
BAD_REQUEST(400),
UNAUTHORIZED(401),
FORBIDDEN(403),
NOT_FOUND(404),
NOT_ACCEPTABLE(406),
CONFLICT(409),
INTERNAL_SERVER_ERROR(500);
private Integer value;
ResponseCode(Integer value) {
this.value = value;
}
public Integer getValue() {
return value;
}
}
创建自定义异常类
创建包cn.bob.small.commons.exception
包中创建类SimpleMallServiceException
package cn.bob.small.commons.exception;
import cn.bob.small.commons.restful.ResponseCode;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class SimpleMallServiceException extends RuntimeException{
private ResponseCode responseCode;
public SimpleMallServiceException(
ResponseCode responseCode,String message){
super(message);
setResponseCode(responseCode);
}
}
在restful包中创建用于控制器返回的JsonResult类
package cn.bob.small.commons.restful; import cn.bob.small.commons.exception.SimpleMallServiceException; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; @Data public class JsonResultimplements Serializable { @ApiModelProperty(value = "业务状态码", position = 1, example = "200, 400, 401, 403, 404, 409, 500") private Integer state; @ApiModelProperty(value = "业务消息", position = 2, example = "登录失败!密码错误!") private String message; @ApiModelProperty(value = "业务数据", position = 3) private T data; public static JsonResult ok() { return ok("OK"); } public static JsonResult ok(String message){ JsonResult jsonResult=new JsonResult(); jsonResult.setState(ResponseCode.OK.getValue()); jsonResult.setMessage(message); jsonResult.setData(null); return jsonResult; } public static JsonResult ok(String message,T data) { JsonResult jsonResult = new JsonResult<>(); jsonResult.setState(ResponseCode.OK.getValue()); jsonResult.setData(data); return jsonResult; } public static JsonResult failed(SimpleMallServiceException e) { return failed(e.getResponseCode(), e); } public static JsonResult failed(ResponseCode responseCode, Throwable e) { return failed(responseCode, e.getMessage()); } public static JsonResult failed(ResponseCode responseCode, String message) { JsonResult jsonResult = new JsonResult<>(); jsonResult.setState(responseCode.getValue()); jsonResult.setMessage(message); return jsonResult; } }
exception包下最后创建handler包再统一异常处理类
package cn.bob.small.commons.exception.handler;
import cn.bob.small.commons.restful.JsonResult;
import cn.bob.small.commons.restful.ResponseCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalControllerExceptionHandler {
@ExceptionHandler({SimpleMallServiceException.class})
public JsonResult handleCoolSharkServiceException(SimpleMallServiceException e) {
log.debug("出现业务异常,业务错误码={},描述文本={}", e.getResponseCode().getValue(), e.getMessage());
e.printStackTrace();
JsonResult result = JsonResult.failed(e);
log.debug("即将返回:{}", result);
return result;
}
@ExceptionHandler(BindException.class)
public JsonResult handleBindException(BindException e) {
log.debug("验证请求数据时出现异常:{}", e.getClass().getName());
e.printStackTrace();
String message = e.getBindingResult().getFieldError().getDefaultMessage();
JsonResult result = JsonResult.failed(ResponseCode.BAD_REQUEST, message);
log.debug("即将返回:{}", result);
return result;
}
@ExceptionHandler({Throwable.class})
public JsonResult handleSystemError(Throwable e) {
log.debug("出现系统异常,异常类型={},描述文本={}", e.getClass().getName(), e.getMessage());
e.printStackTrace();
JsonResult result = JsonResult.failed(ResponseCode.INTERNAL_SERVER_ERROR, e);
log.debug("即将返回:{}", result);
return result;
}
}
commons项目编写内容到此结束
business可以理解为业务的消费者
我们创建这个模块是为了触发订单生成业务的
创建small-business
父子相认,最终子项目pom文件为:
4.0.0 cn.bob small-ex 0.0.1-SNAPSHOT cn.bob small-business 0.0.1-SNAPSHOT small-business Demo project for Spring Boot org.springframework.boot spring-boot-starter-web com.github.xiaoymin knife4j-spring-boot-starter cn.bob small-commons 0.0.1-SNAPSHOT
创建application.yml文件
server:
port: 20000
#公共配置
mybatis:
configuration:
# 禁用缓存
cache-enabled: false
# 配置映射驼峰命名法,数据库中user_name的字段,会映射在java的userName属性上
map-underscore-to-camel-case: true
# 将运行的sql语句输出到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
knife4j:
# 开启增强配置
enable: true
# 生产环境屏蔽,开启将禁止访问在线API文档
production: false
# Basic认证功能,即是否需要通过用户名、密码验证后才可以访问在线API文档
basic:
# 是否开启Basic认证
enable: false
# 用户名,如果开启Basic认证却未配置用户名与密码,默认是:admin/123321
username: root
# 密码
password: root
spring:
profiles:
active: dev
创建application-dev.yml
文件以备后续配置使用,现在为空即可
删除项目中src下的test测试文件夹
创建config包,添加一些必要配置
创建CommonsConfiguration
package cn.bob.small.business.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
// 当前项目默认情况下不会扫描commons项目中的资源和内容,编写这个类,配置扫描commons
@Configuration // 所有配置Spring的配置类须加此注解
@ComponentScan(basePackages = "cn.bob.small.commons.exception")
public class CommonsConfiguration {
}
创建Knife4jConfiguration
package cn.bob.small.business.config;
import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
private String basePackage = "cn.bob.small.business.controller";
private String groupName = "base-business";
private String host = "http://java.bob.cn";
private String title = "Simple-Mall项目案例在线API文档--基础business-web实例";
private String description = "构建基础business-web项目,实现购买";
private String termsOfServiceUrl = "http://www.apache.org/licenses/LICENSE-2.0";
private String contactName = "项目研发部";
private String contactUrl = "http://java.bob.cn";
private String contactEmail = "java@bob.cn";
private String version = "1.0-SNAPSHOT";
@Autowired
private OpenApiExtensionResolver openApiExtensionResolver;
@Bean
public Docket docket() {
String groupName = "1.0-SNAPSHOT";
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.host(host)
.apiInfo(apiInfo())
.groupName(groupName)
.select()
.apis(RequestHandlerSelectors.basePackage(basePackage))
.paths(PathSelectors.any())
.build()
.extensions(openApiExtensionResolver.buildExtensions(groupName));
return docket;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(title)
.description(description)
.termsOfServiceUrl(termsOfServiceUrl)
.contact(new Contact(contactName, contactUrl, contactEmail))
.version(version)
.build();
}
}
编写业务
因为business是业务的触发者,不需要连接数据库,所以从业务逻辑层开始写即可
创建service包
包中创建接口IBusinessService
package cn.bob.small.business.service;
public interface IBusinessService {
// business业务触发购买下订单的方法声明
void buy();
}
业务逻辑层实现
创建包service.impl
创建类BusinessServiceImpl
package cn.bob.small.business.service.impl;
import cn.bob.small.business.service.IBusinessService;
import cn.bob.small.commons.pojo.cart.dto.OrderAddDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class BusinessServiceImpl implements IBusinessService {
@Override
public void buy() {
// 暂时模拟一个下单业务
// 创建OrderAddDTO类,赋值并输出信息
OrderAddDTO orderAddDTO=new OrderAddDTO();
orderAddDTO.setCommodityCode("PC100");
orderAddDTO.setUserId("UU100");
orderAddDTO.setMoney(500);
orderAddDTO.setCount(5);
// 因为没有持久层,只能输出一下,表示运行正常
log.info("新增订单信息为:{}",orderAddDTO);
}
}
控制层代码
创建controller包
包中创建BusinessController类
package cn.bob.small.business.controller;
import cn.bob.small.business.service.IBusinessService;
import cn.bob.small.commons.restful.JsonResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/base/business")
//knife4j介绍当前控制器作用
@Api(tags = "购买业务开始模块")
public class BusinessController {
@Autowired
private IBusinessService businessService;
@PostMapping("/buy") // localhost:20000/base/business/buy
@ApiOperation("发起购买")
public JsonResult buy(){
// 调用业务逻辑层方法即可
businessService.buy();
return JsonResult.ok("购买完成");
}
}
可以启动当前项目
访问:http://localhost:20000/doc.html
点击测试,观察输出结果和控制台输出内容是否正常
项目要想称为微服务项目必须注册到nacos
做具体配置之前,要明确,启动business之前,一定保证nacos在运行状态,否则启动business会报错的
首先在business的pom文件中添加依赖
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery
然后再application-dev.yml文件中添加当前项目对nacos注册的配置
spring:
application:
# 当前Springboot项目的名称,用作注册中心服务的名称
name: nacos-business
cloud:
nacos:
discovery:
# 定义nacos运行的路径
server-addr: localhost:8848
在保证nacos已经启动的前提下,启动business项目
可以在nacos的服务列表中看到nacos-business的名称
http://localhost:8848/nacos
此时,business模块就已经在nacos服务中注册上了,当再有其他的服务也注册进来后,就可以经过相同的配置来互相调用了,因条件有限,本次测试只在一个主机上模拟分布式的微服务运行机制,实际微服务项目的每个模块都是分开存储在不同物理地址的主机上的,在nacos注册后就可以实现远程的相互调用了
Nacos内部注册的服务分为两大类
1.临时实例(默认)
2.持久化实例(永久实例)
我们可以通过设置属性来确定它是临时还是永久
cloud:
nacos:
discovery:
# ephemeral设置当前项目启动时注册到nacos的类型 true(默认):临时实例 false:永久实例
ephemeral: true
临时实例和永久实例的区别
a.临时实例
默认情况下,启动服务后,每隔5秒会向nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息
Nacos收到这个"心跳包"如果发现这个服务的信息不在注册列表中,就进行注册,如果这个服务的信息在注册列表中就表明这个服务还是健康的
如果Nacos15秒内没接收到某个服务的心跳包,Nacos会将这个服务标记为不健康的状态
如果30秒内没有接收到这个服务的心跳包,Nacos会将这个服务从注册列表中剔除
这些时间都是可以通过配置修改的
b.持久化实例(永久实例)
持久化实例启动时向nacos注册,nacos会对这个实例进行持久化处理
心跳包的规则和临时实例一致,只是不会将该服务从列表中剔除
各类型使用时机:
一般情况下,我们创建的服务都是临时实例
只有项目的主干业务才会设置为永久实例
我们每次开机都要先启动nacos才能启动其他服务
我们使用dos窗口敲代码启动nacos还是比较麻烦的
我们可以使用idea提供的功能,简化nacos启动的过程
配置完成后,就可以在idea中启动nacos



