1、Vant
- Vant官网:
- https://youzan.github.io/vant/v2/#/zh-CN/quickstart
- https://gitee.com/vant-contrib/vant/
00.什么是Vant?
Vant是一个轻量,可靠的移动端组件库,于 2017 年开源。
目前 Vant 官方提供了 [Vue 2 版本](https://vant-contrib.gitee.io/vant/v2)、
[Vue 3 版本](https://vant-contrib.gitee.io/vant)和
[微信小程序版本](http://vant-contrib.gitee.io/vant-weapp),
并由社区团队维护 [React 版本](https://github.com/3lang3/react-vant)和
[支付宝小程序版本](https://github.com/ant-move/Vant-Aliapp)。
课程中使用Vant 2.x版本可以浏览网站
Vant官网:https://youzan.github.io/vant/v2/#/zh-CN/
(1)Vant的优势
- ElementUI是【开发电脑浏览器页面】的组件库
- 而Vant是【开发移动端显示页面】的组件库
- 酷鲨商城前台设计的是手机来访问,所以使用移动端更合适也就是使用Vant
(2)Vant特性
- 🚀 性能极佳,组件平均体积小于 1KB(min+gzip)
- 🚀 65+ 个高质量组件,覆盖移动端主流场景
- 💪 使用 TypeScript 编写,提供完整的类型定义
- 💪 单元测试覆盖率超过 90%,提供稳定性保障
- 📖 提供完善的中英文文档和组件示例
- …
01.第一个Vant程序安装(demo-vant)
(1)安装vant > 创建第一个工程 > 添加vant引用 > 启动 > 视图
(1)
《创建第1个Vue CLI工程:vue-home》:
0.因为之前电脑安装了Vue Cli脚手架,所以我此时在新的路径下直接创建vue项目,
————>但发现提示不成功,原因是找不到Vue Cli脚手架的安装信息,
————>接下来我需要做的是:
————>还可参考第四阶段创建Vue项目的步骤来创建此Vue项目:
我的原创:【https://blog.csdn.net/weixin_59404863/article/details/125026036】中的
:【1、Vue CLI:Vue脚手架】
1.首先查看是否安装了 nmp:
————>在【D:\soft\01Java\00soft\01project\haolan\vue-home>】下:
————>输入指令:【npm config get registry】,
————>若出现了【https://registry.npm.taobao.org】这个网址即为【配置npm源】成功
2.接下来安装Vue CLI脚手架:
————>执行命令:【npm install -g @Vue/cli】
注:-g表示全局安装,则当前操作系统中所有用户均可使用,且自动配置Vue环境变量
————>出现以下提示就是安装成功:
added 897 packages in 1m
npm notice
npm notice New minor version of npm available! 8.5.0 -> 8.10.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.10.0
npm notice Run npm install -g npm@8.10.0 to update!
npm notice
————>查看vue版本号:【vue -V】 即可显示当前的Vue CLI版本号 即安装成功!
3.接下来就可以创建第1个Vue CLI工程:项目名为:【vue-home】
————>首先确定一个文件夹创建Vue项目:
【D:\soft\01Java\00soft\01project\haolan】下创建文件夹【vue-home】,
————>进入【vue-home】文件夹内,上面路径栏输入【cmd】敲回车进入【dos窗口】
————>输入指令:【vue create demo-vant】
4.我们创建一个Vue项目,Vue项目不会自动支持Vant
我们需要将Vant添加到Vue项目中
进入到我们刚刚创建的项目的文件夹
再来安装手机端的:轻量、可靠的移动端组件库【vant】
————>在【D:\soft\01Java\00soft\01project\haolan\vue-home】下:
执行命令【cd demo-vant】
————>在【D:\soft\01Java\00soft\01project\haolan\vue-home\demo-vant】下:
执行【运行安装\添加Vant支持的npm】命令:【npm i vant@latest-v2 -S】
————>若上面安装成功后可能有警告,无视掉
最终看到的提示可能是:【added 5 packages in 2s】
————>到此为止,当前Vue项目,安装【Vant】成功!就支持Vant功能了
————>我们【利用idea打开这个项目】即可!
———————————————————————————————————————————————————————————————————————————————
(2)
《添加Vant引用》:
如果想在Vue项目中开始使用Vant的功能,还需要在Vue项目代码中添加Vant的引用
————>在:Vue项目的【src/main.js】中,添加如下引用,启用Vant功能:
// 添加Vant引用
import Vant from 'vant'
import 'vant/lib/index.css'
Vue.use(Vant)
———————————————————————————————————————————————————————————————————————————————
(3)
《启动该工程》:
添加了上面的引用,当前Vue项目就可以使用Vant组件了
为了实时运行项目,我们启动Vue项目
————>在idea界面最下面一栏点击【Terminal】,
————>使用【cd】命令进入此工程
例:在【D:\soft\01Java\00soft\01project\haolan\vue-home\demo-vant】下:
输入指令:【npm run serve】来启动服务
———————————————————————————————————————————————————————————————————————————————
(4)
《浏览器中进入移动端页面调试模式》:
打开浏览器
————>输入localhost:8080
————>进入移动端页面调试模式
————>以Google浏览器为例按F12进入调试模式后点击【移动端图标】调试即可
(2)按钮组件
(1)
在HomeView.vue中修改代码为:
<template>
<div class="home">
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
</div>
</template>
打开页面,就可以看到按钮
(3)表单页面
使用Vant网站中直接提供的组件:【表单组件】实现登录页面
在AboutView.vue页面中编写代码如下: 【注意@submit和@failed这两个事件的绑定关系】
<template>
<div class="about">
<van-form @submit="onSubmit" @failed="myFail">
<van-field
v-model="username"
name="用户名"
label="用户名"
placeholder="用户名"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<van-field
v-model="password"
type="password"
name="密码"
label="密码"
placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]"
/>
<div style="margin: 16px;">
<van-button round block type="info" native-type="submit">提交</van-button>
</div>
</van-form>
</div>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
};
},
methods: {
onSubmit(values) {
// 提交成功的方法
console.log('submit', values);
},
myFail(errorInfo){
// 提交失败的方法
console.log(errorInfo);
}
},
};
</script>
(4)area省市区选择
(1)
先在views文件夹下创建AreaView.vue文件:编写代码如下:
<template>
<div>
<van-area title="省市区选择" :area-list="areaList" />
</div>
</template>
<script>
const areaList = {
province_list: {
110000: '北京市',
120000: '天津市',
},
city_list: {
110100: '北京市',
120100: '天津市',
},
county_list: {
110101: '东城区',
110102: '西城区',
// ....
},
};
export default {
data(){
return{areaList}
}
}
</script>
—————————————————————————————————————————————————————————————————————————————————
(2)
《新建的页面要设置路由才能访问》:
————>在【src/router/index.js】中添加如下新增:
<!-- 以下为新增: -->
{
path: '/area',
name: 'area',
component: () => import('../views/AreaView.vue')
}
],
————>通过在浏览器输入路径:【http://localhost:8080/area】
————>访问省市区选择页面
————>只有少量数据
—————————————————————————————————————————————————————————————————————————————————
—————————————————————————————————————————————————————————————————————————————————
(3)
如果需要真实的全国省市区数据,我们需要用npm命令来引入:
————>在此工程的路径下:使用npm命令如下:【npm i @vant/area-data】
————>下面我们修改AreaView.vue的代码(见下面的具体代码↓)
————>引用安装好的全国数据,并绑定确认按钮,输出选择的省市区的信息
<template>
<div>
<van-area title="省市区选择" :area-list="areaList" @confirm="showArea"/>
</div>
</template>
<script>
// const areaList = {
// province_list: {
// 110000: '北京市',
// 120000: '天津市',
// },
// city_list: {
// 110100: '北京市',
// 120100: '天津市',
// },
// county_list: {
// 110101: '东城区',
// 110102: '西城区',
// // ....
// },
// };
/*引入区域集合*/
import {areaList} from '@vant/area-data'
export default {
data(){
return{
areaList
}
},
methods:{
showArea(area){
console.log(area);
}
}
}
</script>
如图:
(5)商品列表
(1)
我们的电商网站一定会需要商品列表
————>移动端Vant直接支持了商品列表的格式
————>我们也不需要大范围的修改
————>创建:【ListView.vue】
————>代码如下:
<template>
<div>
<van-row>
<van-col span="8">综合</van-col>
<van-col span="8">销量</van-col>
<van-col span="8">价格</van-col>
</van-row>
<van-card
num="2"
price="168.00"
desc="超10万好评热销产品"
title="酷鲨键盘鼠标套装"
thumb="https://img01.yzcdn.cn/vant/ipad.jpeg"
>
<template #tags>
<van-tag type="primary">自营</van-tag>
<van-tag plain type="primary">酷鲨物流</van-tag>
</template>
<template #footer>
<van-button size="mini">按钮</van-button>
<van-button size="mini">按钮</van-button>
</template>
</van-card>
<van-card
num="2"
price="168.00"
desc="超10万好评热销产品"
title="酷鲨键盘鼠标套装"
thumb="https://img01.yzcdn.cn/vant/ipad.jpeg"
>
<template #tags>
<van-tag type="primary">自营</van-tag>
<van-tag plain type="primary">酷鲨物流</van-tag>
</template>
<template #footer>
<van-button size="mini">按钮</van-button>
<van-button size="mini">按钮</van-button>
</template>
</van-card>
</div>
</template>
<script>
</script>
————————————————————————————————————————————————————————————————————————————————————————
(2)
路由代码:
{
path: '/list',
name: 'list',
component: () => import('../views/ListView.vue')
}
————————————————
2、微服务:“高并发,高可用,高性能”
00.关于注解:
(0)
@SpringBootApplication
:添加在Spring Boot项目中的启动类上的注解,
:每个Spring Boot只能有1个类添加此注解,
此注解的元注解包括@SpringBootConfiguration和@ComponentScan,
所以,启动类本身也是配置类,且默认就执行组件扫描
——————————————————————————————————————————————————————————————————————————
(1.1)
@ApiModel("购物车新增DTO")
:用Knife4j对类的解释
————>用于类,表示对类进行说明,用于参数用实体类接收
(1.2)
@ApiModelProperty(value = "商品编号",name="commodityCode",example = "PC")
:使用在被 @ApiModel 注解的模型类的属性上
————>对类中商品的解释
————>用于方法或者字段(属性),表示对model属性的说明或者数据操作更改
例:
@ApiModel("购物车新增DTO")
@Data //lambok框架的注解,作用是自动生成get、set等方法
public class CartAddDTO implements Serializable {
@ApiModelProperty(value = "商品编号",name="commodityCode",example = "PC")
private String commodityCode;
}
-----------------------------------------------------------------------------
(1.3)
@Api(tags="购买业务开始模块")
:用在类上
:Knife4j框架来介绍当前控制器类的作用
(1.4)
@ApiOperation("发起购买")
:用在控制器类中的方法上
:用来对方法的描述
例:
@ApiOperation("发起购买")
public JsonResult buy(){
//调用业务逻辑层方法即可
businessService.buy();;
return JsonResult.ok("购买成功");
}
——————————————————————————————————————————————————————————————————————————————
(2)
@EqualsAndHashCode(callSuper = false)
:重写equals 和 hashcode方法
:在类是继承父类的情况下:
————>EqualsAndHashCode实则就是在比较两个对象的属性;
————>当@EqualsAndHashCode(callSuper = false)时不会比较其继承的父类的属性可能会导致错误判断;
————>当@EqualsAndHashCode(callSuper = true)时会比较其继承的父类的属性;
@EnableSwagger2WebMvc
:
——————————————————————————————————————————————————————————————————————————————
(3.1)
@RequestBody
:添加在处理请求的方法的参数之前,
仅当客户端使用JSON格式组织请求参数时使用此注解,
如果客户端的请求参数是FormData格式的(使用 & 拼接的),则不可以添加此注解
(3.2)
@RestController
:等效于@Controller + @ResponseBody
(3.3)
@ControllerAdvice
:添加在类之前,此类中的代码在处理任何请求时都会生效
(3.4)
@RestControllerAdvice
:等效于@ControllerAdvice + @ResponseBody
:用在全局处理异常类上
:表示此类是一个对控制器进阶的管理,管理的是"异常处理程序(ExceptionHandler)"
(3.5)
@ExceptionHandler({CoolSharkServiceException.class})
:用在全局处理异常类中的方法上
:当产生对应该参数中的异常的时候走此注解下的方法
————>添加在处理异常的方法之前,可通过注解参数配置需要处理的异常类型
——————————————————————————————————————————————————————————————————————————————
(4)
@Configuration
:用在Spring的配置类上 ; 所有配置Spring的配置类必须添加这个注解
:添加在类的声明之前,表示此类是配置类,会自动执行配置类中的@Bean方法,并解读配置类上的其它注解
——————————————————————————————————————————————————————————————————————————————
【四个组件注解】 和 【一个组件扫描注解】:
(5.1)
@Component
:组件注解,添加在类的声明之前,
————>添加在不是控制器类,也不是业务逻辑类,也不是数据访问类的类上
:表示此类是组件类,是通用注解
(5.2)
@Controller
:组件注解,添加在类的声明之前,表示此类是组件类,应该添加在控制器类上
(5.3)
@Service
:用在业务逻辑层
:组件注解,添加在类的声明之前,表示此类是组件类,应该添加在业务逻辑类上
(5.4)
@Repository
:组件注解,添加在类的声明之前,表示此类是组件类,应该添加在数据访问类上
:若mapper接口上加上此注解,spring就会把该接口封装在容器中,就会认为:
————>在其他类上如果有用@Autowired注解来标明该接口,就可以直接使用此接口
@Autowired ————>
:自动装配的注解,当某个属性需要被Spring装配值时,在属性之前添加此注解,
另外,此注解可以添加在Setter方法、构造方法之前,通常不需要这样处理
(5.5)
@ComponentScan(basePackages = "cn.tedu.csmall.commons.exception")
:用在Spring的配置类上
:组件扫描,当加载到此注解时,Spring会扫描此注解配置的根包下是否存在组件类,
如果存在,会自动创建组件类的对象,
如果某些类不是组件,会被跳过(无视)
——————————————————————————————————————————————————————————————————————————————
(6.1)
@Data
:添加在POJO类上的属性,
使得Lombok会在编译期自动生成全属性的:
Settter & Getter、hashCode()、equals()、toString()方法
(6.2)
@Slf4j
:添加在任意类上,在类中可以使用名为log变量输出日志
——————————————————————————————————————————————————————————————————————————————
(7.1)
@RequestMapping("/base/business")
:通常添加在类上,
:用于配置请求路径的前缀部分,
也使用produces属性配置此控制器类中所有处理请求的方法响应时的文档类型,
例如在类上配置为:
@RequestMapping(value="user", produces="application/json; charset=utf-8")
(7.2)
@GetMapping
:是将请求类型限制为GET的@RequestMapping,
:通常添加在处理请求的方法上,用于配置此方法映射的请求路径
(7.3)
@PostMapping
:参考@GetMapping
——————————————————————————————————————————————————————————————————————————————
(8.1)
@Mapper
:添加在使用Mybatis时访问数据的(每一个)接口上,
使得Mybatis明确这是一个用于访问数据的接口,从而Mybatis会生成此接口的代理对象,
实现最终的数据访问功能,但此方式不推荐,另见@MapperScan注解
(8.2)
@MapperScan
:添加在配置类上,用于扫描使用Mybatis时的访问数据的接口所在的根包,
使用此注解后,各接口不再需要使用@Mapper注解,在配置根包时,应保证此包下只有访问数据的接口,
切不可存在其它接口,因为Mybatis并不会解析接口的内部代码,
只要是接口都会创建代理对象,如果你自行创建类实现了此接口还被Spring创建了对象,会出现错误
————————————————————————————————————————————————————————————————————————
(9)
@Param
:添加在使用Mybatis时访问数据的接口中的抽象方法的参数上,用于指定参数名称,
在配置SQL语句时使用占位符时,占位符内的名称就是此注解的参数值,
当抽象方法的参数超过1个时,必须使用此注解配置参数名称
例:
//删除购物车中商品的方法
@Delete("delete from cart_tbl where user_id=#{userId} and commodity_code=#{commodityCode}")
void deleteCartByUserIdAndCommodityCode(@Param("userId") String userId,
@Param("commodityCode") String commodityCode);
————————————————————————————————————————————————————————————————————————
(10.1)
@ApiImplicitParam(name="name",value="用户名",dataType="String", paramType = "query")
:用在方法上,表示单独的请求参数
————>还可以用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
:参数:
- name:参数名。
- value:参数的具体意义,作用。
- required:参数是否必填。
- dataType:参数的数据类型。
- ParamType:查询参数类型,这里有几种形式:
(10.2)
@ApiImplicitParams
:用于方法,包含多个 @ApiImplicitParam:
例:
@ApiOperation("查询测试")
@GetMapping("select")
//@ApiImplicitParam(name="name",value="用户名",dataType="String", paramType = "query")
@ApiImplicitParams({
@ApiImplicitParam(name="name",value="用户名",dataType="string", paramType = "query",example="xingguo"),
@ApiImplicitParam(name="id",value="用户id",dataType="long", paramType = "query"
)})
public void select(){
}
例2:
@ApiImplicitParams({
@ApiImplicitParam(value = "用户id",name="userId", example = "UU100",required = true),
@ApiImplicitParam(value = "商品编号",name="commodityCode", example = "PC100",required = true),
})
public JsonResult deleteUserCart(String userId,String commodityCode){
//调用业务逻辑层删除购物车商品的方法
cartService.deleteUserCart(userId,commodityCode);
return JsonResult.ok("删除购物车商品成功");
}
————————————————————————————————————————————————————————————————————————
(11.1)
@DubboService
:表示当前业务逻辑层实现类中的所有方法均会注册到nacos,成为dubbo可以发现的业务逻辑层方法
例:
@DubboService //表示当前业务逻辑层实现类中的所有方法均会注册到nacos,成为dubbo可以发现的业务逻辑层方法
@Service
@Slf4j
public class StockServiceImpl implements IStockService {
(11.2)
@EnableDubbo
:如果当前项目是Dubbo服务的生产者,必须添加这个注解
例:
@SpringBootApplication
@EnableDubbo //如果当前项目是Dubbo服务的生产者,必须添加这个注解
public class CsmallStockWebapiApplication {
public static void main(String[] args) {
SpringApplication.run(CsmallStockWebapiApplication.class, args);
}
}
(11.3)
@DubboReference
:业务逻辑层远程调用其他模块时使用该注解获取业务逻辑层实现类对象
————>当前order模块消费stock的业务逻辑层方法,以减少库存
因为stock模块的减库存方法在nacos中注册,所以可以使用Dubbo调用
要想调用就必须使用@DubboReference,才能获得业务逻辑层实现类对象
例:
@DubboService //因为business模块要调用这个业务逻辑层中的方法,所以这个类也要注册到Nacos
@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {
@Autowired
private OrderMapper orderMapper;
//当前order模块消费stock的业务逻辑层方法,以减少库存
//因为stock模块的减库存方法在nacos中注册,所以可以使用Dubbo调用
//要想调用就必须使用@DubboReference,才能获得业务逻辑层实现类对象
@DubboReference
private IStockService dubboStockService;
————————————————————————————————————————————————————————————————————————————
(12)
@GlobalTransactional
:一旦标记这个注解,seata机会将这个方法当作一个分部事务的起点,
之后所有远程Dubbo调用的数据库操作要么都成功,要么都失败
例:
/*
一旦标记这个注解,seata机会将这个方法当作一个分部事务的起点,
之后所有远程Dubbo调用的数据库操作要么都成功,要么都失败
*/
@GlobalTransactional
@Override
public void buy() {
————————————————————————————————————————————————————————————————————————————
(13.1)
《Sentinel:哨兵:控制流量》:
@SentinelResource("减少库存方法(控制器)")
:此注解标记的方法会被Sentinel监控,()里的内容时这个监控的名称,我们可以在"仪表台"中看到
例:
@Autowired
private IStockService stockService;
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存业务")
//此注解标记的方法会被Sentinel监控,()里的内容时这个监控的名称,我们可以在"仪表台"中看到
@SentinelResource("减少库存方法(控制器)")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
(13.2)
@SentinelResource(value="减少库存方法(控制器)",blockHandler = "blockError")
:value的值是这个监控的名称,我们可以在"仪表台"中看到;
blockHandler的值指定了被限流时运行的方法名称(该方法名称我们可以在此方法的下边声明)
例:
//value的值是这个监控的名称,我们可以在"仪表台"中看到;blockHandler的值指定了被限流时运行的方法名称(该方法名称我们可以在此方法的下边声明)
@SentinelResource(value="减少库存方法(控制器)",blockHandler = "blockError")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
————————————————————————————————————————————————————————————————————————————————————————
(14)
《lombok注解》:
(1)
@Accessors(chain = true)
:生产和链式赋值的set方法
(2)
@AllArgsConstructor
:自动生成全参构造器
(3)
@NoArgsConstructor
:自动生成无参构造器
例:
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Item implements Serializable {
}
————————————————————————————————————————————————————————————————————————————————————————
(15)
《Spring家族的:SpringData框架》:
(1)
@Document(indexName = "items")
:springData要求我们在"实体类"中使用特定注解标记
@Document注解标记当前类和ES关联
- indexName:指定索引名称,我们这里叫items,当操作这个索引时,如果索引不存在,会自动创建
例:
/*
springData要求我们在"实体类"中使用特定注解标记
@Document注解标记当前类和ES关联
indexName:指定索引名称,我们这里叫items,当操作这个索引时,如果索引不存在,会自动创建
*/
@Document(indexName = "items")
public class Item implements Serializable {
}
------------------------------------------------------------------------------------
(2)
@Id
:SpringData标记这个字段为当前类主键
例:
@Id //SpringData标记这个字段为当前类主键
private Long id;
------------------------------------------------------------------------------------
(3.1)
@Field(type = FieldType.Text,xxx)
:SpringData使用@Field标记文档中属性的类型和各种特征
//SpringData使用@Field标记文档中属性的类型和各种特征
@Field(type = FieldType.Text,
analyzer = "ik_max_word",
searchAnalyzer = "ik_max_word")
private String title;//商品名称
(3.2)
@Field(type = FieldType.Keyword,index = false)
:图片地址不会称为搜索条件,所以设置index=false
效果是imgPath字段不会生成索引库,节省空间
例:
/*
图片地址不会称为搜索条件,所以设置index=false
效果是imgPath字段不会生成索引库,节省空间
*/
@Field(type = FieldType.Keyword,index = false)
private String imgPath;//图片地址
------------------------------------------------------------------------------------
00.介绍
《微服务开发框架》:
目前微服务的开发框架,最常用的有以下四个:
- Spring Cloud:http://projects.spring.io/spring-cloud(现在非常流行的微服务架构)
- Dubbo:http://dubbo.io
- Dropwizard:http://www.dropwizard.io (关注单个微服务的开发)
- Consul、etcd&etc.(微服务的模块)
————————————————————————————————————————————————————————————————————————————————————————
《Sprint cloud 和 Sprint boot区别》:
- Spring Boot:旨在简化创建产品级的Spring应用和服务,简化了配置文件,使用嵌入式web服务器,
含有诸多开箱即用微服务功能,可以和spring cloud联合部署。
- Spring Cloud:微服务工具包,
为开发者提供了:
在分布式系统的配置管理、服务发现、断路器、智能路由、微代理、控制总线等开发工具包。
————————————————————————————————————————————————————————————————————————————————————————
(1)服务器端项目演进
《服务器初期状态》
————>最早的服务器就是安装部署了一些静态页面
————>功能非常单一,只能做信息的呈现和输出
《服务器动态页面》
————>后来因为业务和技术的发展,页面连接了数据库,页面中大部分数据来自于数据库中
————>用户的操作可以修改数据库的内容,这样在不修改页面代码的前提下,页面中的数据可能发生变化
————>这样的页面可以实现用户注册,登录,查询(基本增删改查)功能
《用户贡献网页内容》
————>进入21世纪,随着互联网进入千家万户,个人多社交需求的提升
————>出现了很多主要由用户贡献信息的网站
————>现在也有很多这样的网站
————>微博,抖音,淘宝,大众点评或类似的网站
《微服务时代》
————>随着用户量的增加,和各种新需求的出现,一个服务器应用程序不能支持网站的运行时
————>就需要多台服务器共同支持网站的运行
————>我们需要强大的服务器性能,支持各种网站的活动
————>达到"三高"的目标
————>"高并发,高可用,高性能"
(2)Java服务器项目分类
《现在市面上常见的java开发的项目可以分为两大类》
:---------1.企业级应用
:---------2.互联网应用
《1.企业级应用》
————>一般指一个企业或机构内部使用的网站或服务器应用程序
————>包括的领域很多,包括并不限于:商业,企事业单位,医疗,军事,政府,金融等
————>这种项目的特征是访问人数不多,不是全国乃至全世界都需要使用的网站
————>因为人数比较少又没有替代品,所以不强烈要求三高
————>但是企业级项目一般会有比较复杂的权限设置和业务流程
2.互联网应用
————>指对全国乃至全世界开放的网站或服务器引用程序
————>我们手机中安装的app大部分都是互联网应用
————>微信,支付宝,京东,淘宝,饿了么,美团,抖音,qq音乐,爱奇艺,高德地图等
————>他们因为商业竞争等各种原因,对服务器性能有要求,也就是前面提到的"三高"
————>及时并发量非常大,程序也要正常运行,迅速响应
————>互联网应用一般业务和权限相对简单,但是要求"三高"
————>因为上述java项目的分类偏重点不同
————>在当今java开发的业界中,有下面的基本规律
————>如果开发的是企业级应用,使用单体架构的情况比较多
————>如果开发的是互联网应用,使用微服务架构的情况比较多
(3)微服务概述
《什么是微服务》
————>微服务的概念是由Martin Fowler(马丁·福勒)在2014年提出的
微服务是由以单一应用程序构成的小服务,
自己拥有自己的行程与轻量化处理,服务依业务功能设计,以全自动的方式部署,
与其他服务使用 HTTP API 通信。
同时服务会使用最小的规模的集中管理能力,服务可以用不同的编程语言与数据库等组件实现。
简单来说,
微服务就是将一个大型项目的各个业务代码,拆分成多个互不干扰的小项目,
而这些小项目专心完成自己的功能,
而且可以调用别的小项目的方法,
从而完成整体功能
京东\淘宝这样的大型互联网应用程序,基本【每个操作都是一个单独的微服务在支持】:
- 登录服务器
- 搜索服务器
- 商品信息服务器
- 购物车服务器
- 订单服务器
- 支付服务器
- 物流服务器
- .....
(4)为什么使用微服务.
《左侧小餐馆就像单体项目》
————>一旦服务器忙,所有业务都无法快速响应
————>即使添加了服务器,也不能很好的解决这个问题
————>不能很好的实现"高并发,高可用,高性能"
————>但是因为服务器数量少,所以成本低,适合并发访问少的项目
—————————————————————————————————————————————————————————————————————————————————————
《右侧的大餐厅就是微服务项目》
————>每个业务有专门一批人负责,业务之间互不影响
————>能够针对的在性能不足的业务上添加服务器来改善性能,同时万一一个服务器异常,不会影响整体功能
————>但是服务器数量多,成本高,适合要要求"高并发,高可用,高性能"的项目
————>高并发:集群,启动多个完全相同的服务器,只是端口号不同
(5)怎么搭建微服务项目
在微服务概念提出之前(2014年),每个厂商都有自己的解决方案
但是Martin Fowler(马丁·福勒)提出了微服务的标准之后,为了技术统一和兼容性,很多企业开始支持这个标准
现在业界中开发微服务项目,大多数都是在这个标准下的
如果我们自己编写支持这个标准的代码是不现实的,
必须通过现成的框架或组件完成满足这个微服务标准的项目结构和格式
当今程序员要想快速完成微服务标准的程序,首选————> SpringCloud
(6)微服务学习路径
2.1.SpringCloud(Nacos的功能之:“注册中心”))
- 无法直接自己写微服务,需要依靠框架集(SpringCloud)来快速搭建微服务架构程序
(1)什么是Spring Cloud
SpringCloud是由Spring提供的一套能够快速搭建微服务架构程序的框架集
框架集表示SpringCloud不是一个框架,而是很多框架的统称
SpringCloud是为了搭建微服务架构的程序而出现的
有人将SpringCloud称之为"Spring全家桶",广义上指代所有Spring的产品
(2)SpringCloud的内容
《从内容提供者角度》
————>Spring自己编写的框架和软件
————>Netflix(奈非):早期提供了很多(全套)微服务架构组件
————>alibaba(阿里巴巴):新版本SpringCloud推荐使用(正在迅速占领市场)
课程中大量使用alibaba的微服务组件
《从功能上分类》
————>微服务的注册中心
————>微服务间的调用
————>微服务的分布式事务
————>微服务的限流
————>微服务的网关
————>......
01.Nacos(功能:“注册中心 和 配置中心”)
(1)什么Nacos(是SpringCloud的重要组件)
Nacos是:Spring Cloud Alibaba提供的一个软件
这个软件主要具有【注册中心 和 配置中心】的功能
我们先学习它——————>【注册中心的功能】
微服务中所有项目都必须【注册到注册中心】才能成为微服务的一部分
注册中心和企业中的人力资源管理部门有相似
(2)Nacos的启动
《启动的前提》:
————>我们要启动Nacos必须保证当前系统配置了java环境变量
————>简单来说就是要环境变量中,有JAVA_HOME的配置,指向安装jdk的路径
《下载 Nacos》:
确定了支持java后,可以通过下面路径下载[Nacos]
————> https://github.com/alibaba/nacos/releases/download/1.4.3/nacos-server-1.4.3.zip
————> 国外网站,下载困难可以多试几次
(3)安装并在dos窗口启动Nacos
- 将下载好的Nacos压缩包解压
- 将压缩包解压(注意不要有中文路径或空格)
- 打开解压得到的文件夹后打开bin目录会有如下内容
- cmd结尾的文件是windows版本的
sh结尾的文件是linux和mac版本的
startup是启动文件,shutdown是停止文件 - Windows下启动Nacos不能直接双击cmd文件;需要进入dos命令运行:
- 在当前资源管理器地址栏输入cmd执行命令【startup.cmd -m standalone;】
- D:\soft\01Java\00soft\ruanjian\nacos-server-1.4.3\nacos\bin>
startup.cmd -m standalone;
- -m是设置启动方式参数
- standalone翻译为标准的孤独的
- 意思是单机模式标准运行
- 如果不输入standalone运行会失败
- 运行成功默认占用8848端口,并且在代码中有立体的图案提示
- 验证Nacos的运行状态:
- 打开浏览器输入
http://localhost:8848/nacos
: - 如果是首次访问,会出现这个界面:
- 打开浏览器输入
- 登录系统:
用户名:nacos
密码:nacos - 登录之后可以进入后台列表:
注意:不能关闭启动nacos的dos窗口
- 我们要让我们编写的项目注册到Nacos,才能真正是微服务项目
(4)使用idea:【启动nacos服务】
(1)在idea界面上边窗口栏点击:如图:
(2)按图点击
(3)将右侧的路径输入【本机电脑安装的:nacos路径】
(4)并在此路径后加上【nacos组件bin目录中的启动文件】,添加【-m standalone】,并点击【Run】:
(5)如图,在idea界面最上边窗口栏出现了【启动的服务nacos】,点击运行,控制台出现【立体的NACOS图案】即为启动成功
(6)此时再刷新下【nacos网站:http://localhost:8848/nacos】;
:若能进入此网站即为设置idea中启动nacos服务成功
(5)Nacos的心跳机制
《Nacos内部注册的服务》:
————>分为两大类:
1.临时实例(默认)
2.持久化实例(永久实例)
我们可以通过设置属性来确定它是临时还是永久
在《application-dev.yml》配置文件里添加【ephemeral: true/false】来设置:
cloud:
nacos:
discovery:
# ephemeral设置当前项目启动时注册到nacos的类型 true(默认):临时实例 false:永久实例
ephemeral: true
————————————————————————————————————————————————————————————————————————————————
《临时实例 和 永久实例的区别》:
/1.《临时实例》:
————>默认情况下,启动服务后,每隔5秒会向nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息
————>Nacos收到这个"心跳包"如果发现这个服务的信息不在注册列表中,就进行注册,
如果这个服务的信息在注册列表中就表明这个服务还是健康的
————>如果Nacos15秒内没接收到某个服务的心跳包,Nacos会将这个服务标记为不健康的状态
————>如果30秒内没有接收到这个服务的心跳包,Nacos会将这个服务从注册列表中剔除
————>这些时间都是可以通过配置修改的
/2.《持久化实例(永久实例)》:
————>持久化实例启动时向nacos注册,nacos会对这个实例进行持久化处理
————>心跳包的规则和临时实例一致,只是不会将该服务从列表中剔除
————————————————————————————————————————————————————————————————————————————————
《各类型使用时机》
————>一般情况下,我们创建的服务都是临时实例
————>只有项目的主干业务才会设置为永久实例
------------
02.创建父级项目csmall
- 我们要让我们编写的项目注册到Nacos,才能真正是微服务项目
- 我们的项目要想称为微服务项目必须注册到nacos
(0)csmall项目的业务目标:
这个项目作为学习微服务组件使用
————>我们需要完成一个添加订单的业务:
模拟用户选中购物车中商品并且确定了数量的情况下点击提交订单时后端的操作
————>1.减少选中商品sku的库存数
————>2.删除用户再购物车中勾选的商品
————>3.生成订单,将订单信息保存到数据库
上面三个步骤分别由3个模块完成
————>库存模块:减少库存
————>购物车模块:删除购物车信息
————>订单模块:新增订单
下面就开始搭建这个项目:
(1)创建csmall项目
创建【项目名称:csmall】
————>创建Module时所选属性:
————>Name:csmall
————>Group:cn.tedu
————>Artifact:csmall
————>Package name:cn.tedu
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
————>首先删除项目的src目录,因为我们使用不到
————>其次,pom文件有大量配置
直接从提供给大家的完整版中复制
————>最终的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-demo</name>
<description>微服务框架演示案例</description>
<packaging>pom</packaging>
<modules>
<!--
<module>csmall-business</module>
<module>csmall-commons</module>
<module>csmall-cart</module>
<module>csmall-order</module>
<module>csmall-stock</module>
-->
</modules>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
<spring-boot.version>2.5.4</spring-boot.version>
<spring-boot-configuration-processor.version>2.3.0.RELEASE</spring-boot-configuration-processor.version>
<spring-security-jwt.version>1.0.10.RELEASE</spring-security-jwt.version>
<mybatis-spring-boot.version>2.2.0</mybatis-spring-boot.version>
<mybaits-plus.version>3.4.1</mybaits-plus.version>
<pagehelper-spring-boot.version>1.4.0</pagehelper-spring-boot.version>
<mysql.version>8.0.26</mysql.version>
<lombok.version>1.18.20</lombok.version>
<knife4j-spring-boot.version>2.0.9</knife4j-spring-boot.version>
<spring-rabbit-test.version>2.3.10</spring-rabbit-test.version>
<spring-security-test.version>5.5.2</spring-security-test.version>
<fastjson.version>1.2.45</fastjson.version>
<druid.version>1.1.20</druid.version>
<jjwt.version>0.9.0</jjwt.version>
<seata-server.version>1.4.2</seata-server.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<!-- 依赖管理 -->
<dependencyManagement>
<dependencies>
<!--seata-all-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata-server.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Alibaba Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- MyBatis Spring Boot:数据访问层MyBatis编程 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
<!-- MyBatis Plus Spring Boot:MyBatis增强 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybaits-plus.version}</version>
</dependency>
<!-- MyBatis Plus Generator:代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybaits-plus.version}</version>
</dependency>
<!-- PageHelper Spring Boot:MyBatis分页 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-spring-boot.version}</version>
</dependency>
<!-- Spring Boot:基础框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Web:WEB应用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Freemarker:MyBaits Plus Generator的辅助项 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Validation:验证请求参数 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Security:认证授权 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Oauth2:认证授权 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot配置处理器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot-configuration-processor.version}</version>
</dependency>
<!-- Spring Security JWT -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>${spring-security-jwt.version}</version>
</dependency>
<!-- Knife4j Spring Boot:在线API -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j-spring-boot.version}</version>
</dependency>
<!-- Spring Boot Data Redis:缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Data MongoDB:缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Data Elasticsearch:文档搜索 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot AMQP:消息队列 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Boot Actuator:健康监测 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring Cloud家族 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba -->
<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>
<!-- Alibaba FastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- Spring Boot Test:测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<!-- Spring Rabbit Test:消息队列测试 -->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<version>${spring-rabbit-test.version}</version>
<scope>test</scope>
</dependency>
<!-- Spring Security Test:Security测试 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>${spring-security-test.version}</version>
<scope>test</scope>
</dependency>
<!--seata整合springboot-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata-server.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
03.创建子模块csmall-commons(通用的项目)
(1)
《创建子模块csmall-commons》
在【csmall下】创建【csmall-commons】:
————>创建Module时所选属性:
————>Name:csmall-commons
————>Group:cn.tedu
————>Artifact:csmall-commons
————>Package name:cn.tedu.csmall.commons 注意加"."点
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
创建好之后
————>删除test测试文件夹
————>删除resources目录
————>删除SpringBoot启动类
————>这些都用不到
————>编写父项目csmall的pom.xml配置:
放开<modules>节点中的子模块【<module>csmall-commons</module>】节点
————>修改子模块csmall-commons的pom.xml文件,完整pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-commons</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot Web:WEB应用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
———————————————————————————————————————————————————————————————————————————————
(2)
《创建包pojo.cart.dto:下创建类CartAddDTO》:
————>在根包cn.tedu.csmall.commons下:
创建pojo.cart.dto包:
包中创建【新增购物车商品使用的类:CartAddDTO】
————>代码如下:
@ApiModel("购物车新增DTO")
@Data
public class CartAddDTO implements Serializable {
@ApiModelProperty(value = "商品编号",name = "commodityCode",example = "PC100")
private String commodityCode;
@ApiModelProperty(value = "商品单价",name = "price",example = "188")
private Integer price;
@ApiModelProperty(value = "商品个数",name = "count",example = "5")
private Integer count;
@ApiModelProperty(value = "用户ID",name = "userId",example = "UU100")
private String userId;
}
03(1).创建实体相关类
(1.1)创建pojo.cart.dto.CartAddDTO类
《创建包pojo.cart.dto:下创建类CartAddDTO————>新增购物车商品使用》:
————>在根包cn.tedu.csmall.commons下:
创建pojo.cart.dto包:
包中创建【新增购物车商品使用的类:CartAddDTO】
————>代码如下:
package cn.tedu.csmall.commons.pojo.cart.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 往数据库里【增】的一个类
*/
@ApiModel("购物车新增DTO")
@Data //lambok框架的注解,作用是自动生成get、set等方法
public class CartAddDTO implements Serializable {
@ApiModelProperty(value = "商品编号",name="commodityCode",example = "PC")
private String commodityCode;
@ApiModelProperty(value = "商品单价",name="price",example = "188")
private Integer price;
@ApiModelProperty(value = "商品个数",name="count",example = "5")
private Integer count;
@ApiModelProperty(value = "用户Id",name="userId",example = "uu")
private String userId;
}
(1.2)创建pojo.cart.entity.Cart类
package cn.tedu.csmall.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 String userId;
}
(2.1)创建pojo.order.dto.OrderAddDTO类
package cn.tedu.csmall.commons.pojo.order.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 订单添加的 DTO
*/
@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;
}
(2.2)创建pojo.order.entity.Order类
package cn.tedu.csmall.commons.pojo.order.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;
}
(3.1)创建pojo.stock.dto.StockReduceCountDTO类
package cn.tedu.csmall.commons.pojo.stock.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 库存减少计数的 DTO
*/
@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;
}
(3.2)创建pojo.stock.entity.Stock类
package cn.tedu.csmall.commons.pojo.stock.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 库存相关的类:Stock
*/
@Data
public class Stock implements Serializable {
private Integer id;
//商库存编号
private String commodityCode;
//库存数量
private Integer count;
}
03(2.1).创建异常相关类
(1)创建restful.ResponseCode错误状态代码枚举类型
package cn.tedu.csmall.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;
}
}
03(3.1).创建异常类的自定义异常类
(1)创建exception.CoolSharkServiceException 自定义异常类
package cn.tedu.csmall.commons.exception;
import cn.tedu.csmall.commons.restful.ResponseCode;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 业务异常:酷鲨服务异常类
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class CoolSharkServiceException extends RuntimeException {
private ResponseCode responseCode;
public CoolSharkServiceException(ResponseCode responseCode, String message) {
super(message);
setResponseCode(responseCode);
}
}
03(2.2).再次创建异常相关类
(1)创建restful.JsonResult通用响应对象
package cn.tedu.csmall.commons.restful;
import cn.tedu.csmall.commons.exception.CoolSharkServiceException;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 通用响应对象
*/
@Data
public class JsonResult<T> implements 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;
/**
* 创建响应结果对象,表示"成功",不封装其它任何数据
* @return 响应结果对象
*/
public static JsonResult<Void> 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;
}
/**
* 创建响应结果对象,表示"成功",且封装客户端期望响应的数据
* @param data 客户端期望响应的数据
* @return 响应结果对象
*/
public static <T> JsonResult<T> ok(String message,T data) {
JsonResult<T> jsonResult = new JsonResult<>();
jsonResult.setState(ResponseCode.OK.getValue());
jsonResult.setData(data);
return jsonResult;
}
/**
* 创建响应结果对象,表示"失败",且封装"失败"的描述
*
* @param e CoolSharkServiceException异常对象
* @return 响应结果对象
*/
public static JsonResult<Void> failed(CoolSharkServiceException e) {
return failed(e.getResponseCode(), e);
}
/**
* 创建响应结果对象,表示"失败",且封装"失败"的描述
*
* @param responseCode "失败"的状态码
* @param e "失败"时抛出的异常对象
* @return 响应结果对象
*/
public static JsonResult<Void> failed(ResponseCode responseCode, Throwable e) {
return failed(responseCode, e.getMessage());
}
/**
* 创建响应结果对象,表示"失败",且封装"失败"的描述
*
* @param responseCode "失败"的状态码
* @param message "失败"的描述文本
* @return 响应结果对象
*/
public static JsonResult<Void> failed(ResponseCode responseCode, String message) {
JsonResult<Void> jsonResult = new JsonResult<>();
jsonResult.setState(responseCode.getValue());
jsonResult.setMessage(message);
return jsonResult;
}
}
03(3.2).创建异常类的全局异常处理类
(1)创建exception.handler.GlobalControllerExceptionHandler
package cn.tedu.csmall.commons.exception.handler;
import cn.tedu.csmall.commons.exception.CoolSharkServiceException;
import cn.tedu.csmall.commons.restful.JsonResult;
import cn.tedu.csmall.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 //表示此类是一个对控制器进阶的管理,管理的是"异常处理程序(ExceptionHandler)"
@Slf4j
public class GlobalControllerExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler({CoolSharkServiceException.class}) //当产生对应该参数中的异常的时候走此注解下的方法
public JsonResult<Void> handleCoolSharkServiceException(CoolSharkServiceException e) {
log.debug("出现业务异常,业务错误码={},描述文本={}", e.getResponseCode().getValue(), e.getMessage());
e.printStackTrace();
JsonResult<Void> result = JsonResult.failed(e);
log.debug("即将返回:{}", result);
return result;
}
/**
* 处理绑定异常(通过Validation框架验证请求参数时的异常)
*/
@ExceptionHandler(BindException.class)
public JsonResult<Void> handleBindException(BindException e) {
log.debug("验证请求数据时出现异常:{}", e.getClass().getName());
e.printStackTrace();
String message = e.getBindingResult().getFieldError().getDefaultMessage();
JsonResult<Void> result = JsonResult.failed(ResponseCode.BAD_REQUEST, message);
log.debug("即将返回:{}", result);
return result;
}
/**
* 处理系统(其它)异常
*/
@ExceptionHandler({Throwable.class})
public JsonResult<Void> handleSystemError(Throwable e) {
log.debug("出现系统异常,异常类型={},描述文本={}", e.getClass().getName(), e.getMessage());
e.printStackTrace();
JsonResult<Void> result = JsonResult.failed(ResponseCode.INTERNAL_SERVER_ERROR, e);
log.debug("即将返回:{}", result);
return result;
}
}
------------
04.创建子模块csmall-business:[触发]订单生成业务
business可以理解为业务的
我们创建这个模块是为了:————>【触发】订单生成业务的
(0)
《创建子模块csmall-business》
在【csmall下】创建【csmall-business】:
————>创建Module时所选属性:
————>Name:csmall-business
————>Group:cn.tedu
————>Artifact:csmall-business
————>Package name:cn.tedu.csmall.business 注意加"."点
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
——————————————————————————————————————————————————————————————————————————————
(1)
《修改子模块csmall-business 的pom.xml文件》
————>也要父子相认:在<parent>节点中修改
————>删除全部依赖,再添加<dependencies>节点中的以下三个依赖
————>编写父项目csmall的pom.xml配置:
放开<modules>节点中的子模块【<module>csmall-business</module>】节点
————>修改子模块csmall-business的pom.xml文件,完整pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-business</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-business</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
——————————————————————————————————————————————————————————————————————————————
(2)
《添加".yml"配置文件》:
在csmall-business:
在resources目录:
————>删除项目中的测试文件夹:test
————>删除原有的application.properties配置文件
————>创建新的application.yml配置文件,↓代码如下:
————>再创建新的application-dev.yml配置文件 目前为空,以备后续配置使用
《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
04(1).创建config配置类
(1)创建config.CommonsConfiguration配置类
package cn.tedu.csmall.business.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 当前项目默认情况下不会扫描commons项目中的资源和内容,编写这个类,来配置扫描commons
*/
@Configuration //所有配置Spring的配置类必须添加这个注解
@ComponentScan(basePackages = "cn.tedu.csmall.commons.exception")
public class CommonsConfiguration {
}
(2)创建config.Knife4jConfiguration配置类
package cn.tedu.csmall.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;
/**
* Knife4j(Swagger2)的配置
*/
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
/**
* 【重要】指定Controller包路径
*/
private String basePackage = "cn.tedu.csmall.business.controller";
/**
* 分组名称
*/
private String groupName = "base-business";
/**
* 主机名
*/
private String host = "http://java.tedu.cn";
/**
* 标题
*/
private String title = "酷鲨商城项目案例在线API文档--基础businessr-web实例";
/**
* 简介
*/
private String description = "构建基础business-web项目,实现购买";
/**
* 服务条款URL
*/
private String termsOfServiceUrl = "http://www.apache.org/licenses/LICENSE-2.0";
/**
* 联系人
*/
private String contactName = "项目研发部";
/**
* 联系网址
*/
private String contactUrl = "http://java.tedu.cn";
/**
* 联系邮箱
*/
private String contactEmail = "java@tedu.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();
}
}
04(2).创建service业务逻辑层
- 因为business是业务的触发者 ;不需要连接数据库 ;所以从业务逻辑层开始写即可
(1)创建service.IBusinessService 订单生成业务接口
package cn.tedu.csmall.business.service;
/**
* 订单生成业务类
*/
public interface IBusinessService {
//business业务触发购买下订单的方法声明
void buy();
}
(2)创建service.impl.BusinessServiceImpl业务服务实施类
package cn.tedu.csmall.business.service.impl;
import cn.tedu.csmall.business.service.IBusinessService;
import cn.tedu.csmall.commons.pojo.order.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.setMoney(500);
orderAddDTO.setCount(5);
//因为没有持久层,只能输出一下,表示运行正常
log.info("新增订单信息为:{}",orderAddDTO);
}
}
04(3).创建controller控制层
(1)创建controller.BusinessController业务控制类
package cn.tedu.csmall.business.controller;
import cn.tedu.csmall.business.service.IBusinessService;
import cn.tedu.csmall.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")
@ApiOperation("发起购买")
public JsonResult buy(){
//调用业务逻辑层方法即可
businessService.buy();;
return JsonResult.ok("购买成功");
}
}
04(4).启动csmall-business项目进行测试
- 点击测试,观察输出结果和控制台输出内容是否正常
(1)如下图,访问 http://localhost:20000/doc.html 按箭头点击,可以看到返回200状态码的购买成功信息
(2)此时在控制台也会出现【发起购买的信息】:
05.注册到Nacos
我们的项目要想称为微服务项目必须注册到nacos
做具体配置之前,要明确,启动business之前,一定保证nacos在运行状态,否则启动business会报错的
首先在business的pom文件中添加依赖:
(3)
《在csmall-business的pom.xml中添加依赖》:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
————————————————————————————————————————————————————————————————————————————
(4)
《在为空的 application-dev.yml配置文件 中添加配置》:
spring:
application:
# 此名字会在注册成功后,出现在nacos组件的注册中心的服务列表里
name: nacos-business
cloud:
nacos:
discovery:
# 定义nacos运行的浏览器路径
server-addr: localhost:8848
————————————————————————————————————————————————————————————————————————————
(5.1)
《启动nacos组件,将自己写的模块注册到》:
————>在安装 nacos 的bin目录下执行【startup.cmd -m standalone;】来启动该组件
——>在【D:\soft\01Java\00soft\ruanjian\nacos-server-1.4.3\nacos\bin】下执行启动命令
——>出现【立体的NACOS图案】即为启动成功
(5.2)
《访问nacos组件的网站》
————>浏览器登录:【http://localhost:8848/nacos】,即可跳转到该网站:
————>可能会输入【用户名nacos 和 密码nacos】来登录即可
————>点击【左侧:服务管理 > 服务列表】
发现此时【右侧为空,并无注册成功的模块】
如下图:
(6)
我们要保证nacos已经启动的前提下,再启动business项目
————>因此重新启动【csmall-business】中的启动类,
启动之后,business一切功能正常,而且可以在nacos的服务列表中看到nacos-business的名称
————>浏览器中刷新【http://localhost:8848/nacos】网站,
————>再次点击【左侧:服务管理 > 服务列表】,
发现此时【右侧已经有了:服务名nacos-business 的模块】
即:我们已经成功的将自己写的【busines触发订单生成业务】注册到【nacos组件中】
————>注:若将项目的启动类关闭,则在nacos网站的服务列表里显示为没有数据,
一旦注册成功后,以后只需启动项目则可在该服务列表里显示注册的模块
如下图:
------------
05.创建子模块csmall-cart:(新增/删除 购物车中商品)
(0)
《创建子模块csmall-cart》
在【csmall下】创建【csmall-cart】:
————>创建Module时所选属性:
————>Name:csmall-cart
————>Group:cn.tedu
————>Artifact:csmall-cart
————>Package name:cn.tedu.csmall.cart 注意加"."点
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
——————————————————————————————————————————————————————————————————————————————
(1)配置依赖
(1)
《修改子模块csmall-cart的pom.xml文件》
————>也要父子相认:在<parent>节点中修改
————>删除全部依赖,再添加一些依赖
————>编写父项目csmall的pom.xml配置:
放开<modules>节点中的子模块【<module>csmall-cart</module>】节点
————>修改子模块csmall-cart的pom.xml文件,完整pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父子相认-->
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-cart</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-cart</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--web实例-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--alibaba 数据源德鲁伊-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--all-common依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!--配置nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
——————————————————————————————————————————————————————————————————————————————
(2)配置数据库连接信息
(2)
《添加".yml"配置文件》:
在csmall-cart:
在resources目录:
————>删除项目中的测试文件夹:test
————>删除原有的application.properties配置文件
————>创建新的application.yml配置文件,注意修改端口号,↓代码如下:
————>再创建新的application-dev.yml配置文件吗,
因为csmall-cart时真要连接数据库的,所以配置连接数据库信息,↓代码如下:
《application.yml》:
server:
port: 20001
#公共配置
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》:
spring:
datasource:
url: jdbc:mysql://localhost:3306/csmall_db?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: root;
application:
name: nacos-cart
cloud:
nacos:
discovery:
server-addr: localhost:8848
——————————————————————————————————————————————————————————————————————————————
(3)
因为 application-dev.yml 配置文件中的指定了【数据库的名字:csmall_db】,
所以我们在:
Mysql数据库中首先创建库【csmall_db】,并且在idea中连接数据库的窗口中执行以下命令:
————>运行完毕之后,会出现csmall_db数据库
————>其中有4个表
————>表中有少量数据
————>后面会使用
USE csmall_db;
DROP TABLE IF EXISTS cart_tbl;
CREATE TABLE cart_tbl
(
`id` int NOT NULL AUTO_INCREMENT COMMENT '购物车id',
`commodity_code` varchar(255) DEFAULT NULL COMMENT '商品编码',
`price` int DEFAULT '0' COMMENT '商品单价',
`count` int DEFAULT '0' COMMENT '购买数量',
`user_id` varchar(255) DEFAULT NULL COMMENT '用户id',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE = InnoDB
AUTO_INCREMENT = 3
DEFAULT CHARSET = utf8mb3;
insert into cart_tbl(id, commodity_code, price, count, user_id)
values (1, 'PU201', 500, 10, 'UU100');
DROP TABLE IF EXISTS order_tbl;
CREATE TABLE order_tbl
(
id int NOT NULL AUTO_INCREMENT COMMENT '订单id',
user_id varchar(255) DEFAULT NULL COMMENT '用户id',
commodity_code varchar(255) DEFAULT NULL COMMENT '商品编码,也可以是商品id',
count int DEFAULT '0' COMMENT '购买这个商品的数量',
money int DEFAULT '0' COMMENT '订单金额',
PRIMARY KEY (id)
) ENGINE = InnoDB
AUTO_INCREMENT = 27
DEFAULT CHARSET = utf8mb3;
insert into order_tbl(id, user_id, commodity_code, count, money)
values (22, 'UU100', 'PU201', 10, 200),
(23, 'UU100', 'PU201', 10, 200),
(24, 'UU100', 'PU201', 10, 200),
(25, 'UU100', 'PU201', 10, 200);
DROP TABLE IF EXISTS stock_tbl;
CREATE TABLE stock_tbl
(
id int NOT NULL AUTO_INCREMENT COMMENT '商品id',
commodity_code varchar(255) DEFAULT NULL COMMENT '商品编码',
count int DEFAULT '0' COMMENT '商品库存',
PRIMARY KEY (`id`),
UNIQUE KEY commodity_code (commodity_code)
) ENGINE = InnoDB
AUTO_INCREMENT = 3
DEFAULT CHARSET = utf8mb3;
insert into stock_tbl(id, commodity_code, count)
values (1, 'PU201', 990);
DROP TABLE IF EXISTS undo_log;
CREATE TABLE undo_log
(
id bigint NOT NULL AUTO_INCREMENT,
branch_id bigint NOT NULL,
xid varchar(100) NOT NULL,
context varchar(128) NOT NULL,
rollback_info longblob NOT NULL,
log_status int NOT NULL,
log_created datetime NOT NULL,
log_modified datetime NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY ux_undo_log (xid, branch_id)
) ENGINE = InnoDB
AUTO_INCREMENT = 68
DEFAULT CHARSET = utf8mb3;
05(1).创建config配置类
(1)创建cart.config.CommonConfiguration配置类
package cn.tedu.csmall.cart.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration // 所有配置Spring的配置类必须添加这个注解
@ComponentScan(basePackages = {"cn.tedu.csmall.commons.exception"})
public class CommonConfiguration {
}
(2)创建cart.config.Knife4jConfiguration配置类
package cn.tedu.csmall.cart.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;
/**
* Knife4j(Swagger2)的配置
*/
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
/**
* 【重要】指定Controller包路径
*/
private String basePackage = "cn.tedu.csmall.cart.controller";
/**
* 分组名称
*/
private String groupName = "base-cart";
/**
* 主机名
*/
private String host = "http://java.tedu.cn";
/**
* 标题
*/
private String title = "酷鲨商城项目案例在线API文档--基础cart-web实例";
/**
* 简介
*/
private String description = "构建基础cart-web项目,实现购物车维护";
/**
* 服务条款URL
*/
private String termsOfServiceUrl = "http://www.apache.org/licenses/LICENSE-2.0";
/**
* 联系人
*/
private String contactName = "项目研发部";
/**
* 联系网址
*/
private String contactUrl = "http://java.tedu.cn";
/**
* 联系邮箱
*/
private String contactEmail = "java@tedu.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();
}
}
(3)创建cart.config.MyBatisConfiguration配置类
- 当前cart模块除了上述配置之外还要添加一个Mybatis扫描的配置
- config包中再创建一个MybatisConfiguration类代码如下
package cn.tedu.csmall.cart.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
/**
* 只做包扫描
* 如果不需要mybatis配置可以注释掉
*/
@Configuration
// Mybatis扫描必须指定到mapper包:
@MapperScan("cn.tedu.csmall.cart.mapper")
public class MyBatisConfiguration {
}
(4)测试启动csmall-cart:
- 在nacos启动的前提下,我们的cart模块就可以启动并注册到nacos了
- 因为之前启动了Nacos服务,因此接下来再启动当前项目csmall-cart即可将cart模块成功注册到nacos了
- 如下图:
05(1.2).编写cart项目的业务内容
在上一步的测试中,可以看到:
————>cart模块能够正常启动,并且成功注册到【nacos注册中心】里了;
————>但是还没有任何业务
————>结合我们最终生成订单的业务
————>当前cart模块需要开发如下两个功能
————>1.新增购物车中商品
————>2.删除购物车中商品
05(2).创建mapper持久层
(1)创建mapper.CartMapper 接口
package cn.tedu.csmall.cart.mapper;
import cn.tedu.csmall.commons.pojo.cart.entity.Cart;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository //加上此注解spring就会把该接口封装在容器中,就会认为:在其他类上如果有用@Autowired注解来标明该接口,就可以直接使用此接口
public interface CartMapper {
// 新增购物车中商品的方法
@Insert("insert into cart_tbl(commodity_code,user_id,price,count) " +
"values(#{commodityCode},#{userId},#{price},#{count})")
void insertCart(Cart cart);
// 删除购物车中商品的方法
@Delete("delete from cart_tbl where user_id=#{userId} and commodity_code=#{commodityCode}")
void deleteCartByUserIdAndCommodityCode(@Param("userId") String userid,
@Param("commodityCode") String commodityCode);
}
05(3).创建service业务逻辑层
(1)创建service.ICartService 购物车服务接口
接口中应该包含新增和删除购物车的业务逻辑层方法声明:
package cn.tedu.csmall.business.service;
/**
* 订单生成业务类
*/
public interface IBusinessService {
//business业务触发购买下订单的方法声明
void buy();
}
(2)创建service.impl.CartServiceImpl购物车服务实施类
package cn.tedu.csmall.cart.service.impl;
import cn.tedu.csmall.cart.mapper.CartMapper;
import cn.tedu.csmall.cart.service.ICartService;
import cn.tedu.csmall.commons.pojo.cart.dto.CartAddDTO;
import cn.tedu.csmall.commons.pojo.cart.entity.Cart;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class CartServiceImpl implements ICartService {
@Autowired
private CartMapper cartMapper;
/**
* 新增购物车
* @param cartAddDTO
*/
@Override
public void cartAdd(CartAddDTO cartAddDTO) {
// 实例化一个Cart对象
Cart cart=new Cart();
// 利用工具类,将cartAddDTO中的属性值赋值到cart对象的同名属性中
BeanUtils.copyProperties(cartAddDTO,cart);
// 调用cartMapper对象实现新增功能
cartMapper.insertCart(cart);
log.info("新增购物车商品成功!{}",cart);
}
/**
* 删除用户购物车
* @param userId
* @param commodityCode
*/
@Override
public void deleteUserCart(String userId, String commodityCode) {
// 直接调用mapper删除购物车商品的方法即可
cartMapper.deleteCartByUserIdAndCommodityCode(userId,commodityCode);
log.info("购物车商品删除成功");
}
}
05(3).创建controller控制层
(1)创建controller.CartController业务控制类
package cn.tedu.csmall.cart.controller;
import cn.tedu.csmall.cart.service.ICartService;
import cn.tedu.csmall.commons.pojo.cart.dto.CartAddDTO;
import cn.tedu.csmall.commons.restful.JsonResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
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/cart")
@Api(tags = "购物车模块")
public class CartController {
@Autowired
private ICartService cartService;
@PostMapping("/add")
@ApiOperation("新增购物车商品")
public JsonResult cartAdd(CartAddDTO cartAddDTO){
cartService.cartAdd(cartAddDTO);
return JsonResult.ok("新增购物车商品完成");
}
@PostMapping("/delete")
@ApiOperation("删除购物车中商品")
@ApiImplicitParams({
@ApiImplicitParam(value = "用户id",name="userId",
example = "UU100",required = true),
@ApiImplicitParam(value = "商品编号",name="commodityCode",
example = "PC100",required = true)
})
public JsonResult deleteUserCart(String userId,String commodityCode){
// 调用业务逻辑层删除购物车商品的方法
cartService.deleteUserCart(userId,commodityCode);
return JsonResult.ok("删除购物车商品成功");
}
}
05(4).启动csmall-cart项目进行测试
(0)访问nacos注册中心网站: 【http://localhost:8848/nacos/】
点击【左侧:服务管理 > 服务列表】,
发现此时【右侧已经有了:服务名nacos-cart的模块】
即:我们已经成功的将自己写的【cart新增购物车业务】注册到【nacos组件中】了
(1)如下图,访问API文档【 http://localhost:20001/doc.html 】按箭头点击,
可以看到返回200状态码的新增购物车商品成功信息:
(2)此时在控制台也会出现【新增购物车商品的信息】:
(3)查看数据库中的【cart_tbl】表格,
可以看到自己在【API文档网站】添加的【购物车PC、188的信息】已经在数据库中成功添加!!!
------------
06.创建子模块csmall-order:(新增订单)
(0)
《创建子模块csmall-order》
在【csmall下】创建【csmall-order】:
————>创建Module时所选属性:
————>Name:csmall-order
————>Group:cn.tedu
————>Artifact:csmall-order
————>Package name:cn.tedu.csmall.order 注意加"."点
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
——————————————————————————————————————————————————————————————————————————————
(1)配置依赖
(1)
《修改子模块csmall-order的pom.xml文件》
————>也要父子相认:在<parent>节点中修改
————>删除全部依赖,再添加一些依赖
————>编写父项目csmall的pom.xml配置:
放开<modules>节点中的子模块【<module>csmall-order</module>】节点
————>修改子模块csmall-order的pom.xml文件,完整pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父子相认-->
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-order</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--web实例-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--alibaba 数据源德鲁伊-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--all-common依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!--配置nacos注册中心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
——————————————————————————————————————————————————————————————————————————————
(2)配置数据库连接信息
(2)
《添加".yml"配置文件》:
在csmall-order:
在resources目录:
————>删除项目中的测试文件夹:test
————>删除原有的application.properties配置文件
————>创建新的application.yml配置文件,注意修改端口号,↓代码如下:
————>再创建新的application-dev.yml配置文件吗,
因为csmall-order时真要连接数据库的,所以配置连接数据库信息,↓代码如下:
《application.yml》:
————>复制csmall-cart的并将端口号改为20002:
————>【port: 20002】,其余不动
《application-dev.yml》:
————>复制csmall-cart的并将name改为:【name: nacos-order】
06(1).创建config配置类
(1)创建order.config.CommonConfiguration配置类
将 csmall-cart中的复制过来: 什么也不用改
(2)创建order.config.Knife4jConfiguration配置类
将 csmall-cart中的复制过来:
修改:
————>private String basePackage = "cn.tedu.csmall.order.controller";
————>private String groupName = "base-order";
————>private String title = "酷鲨商城项目案例在线API文档--基础order-web实例";
————>private String description = "构建基础order-web项目,实现新增订单";
(3)创建cart.config.MyBatisConfiguration配置类
将 csmall-cart中的复制过来:
修改类上的注解:
————>@MapperScan("cn.tedu.csmall.order.mapper")
06(1.2).编写order项目的编写新增订单功能业务:
06(2).创建mapper持久层
(1)创建mapper.OrderMapper 接口
package cn.tedu.csmall.order.mapper;
import cn.tedu.csmall.commons.pojo.order.entity.Order;
import org.apache.ibatis.annotations.Insert;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderMapper {
// 新增订单的mapper方法
@Insert("insert into order_tbl(user_id,commodity_code,count,money) " +
"values(#{userId},#{commodityCode},#{count},#{money})")
void insertOrder(Order order);
}
06(3).创建service开发业务逻辑层
(1)创建service.IOrderService订单服务接口
接口中应该包含新增订单到数据库的业务逻辑层方法声明:
package cn.tedu.csmall.order.service;
import cn.tedu.csmall.commons.pojo.order.dto.OrderAddDTO;
public interface IOrderService {
// 新增订单到数据库的业务逻辑层方法
void orderAdd(OrderAddDTO orderAddDTO);
}
(2)创建service.impl.OrderServiceImpl订单服务实施类
package cn.tedu.csmall.cart.service.impl;
import cn.tedu.csmall.cart.mapper.CartMapper;
import cn.tedu.csmall.cart.service.ICartService;
import cn.tedu.csmall.commons.pojo.cart.dto.CartAddDTO;
import cn.tedu.csmall.commons.pojo.cart.entity.Cart;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Override
public void orderAdd(OrderAddDTO orderAddDTO) {
// 1.减少订单商品的库存(要调用stock模块的方法)
// 2.删除订单中购物车商品的信息(要调用cart模块的方法)
// 3.新增订单
// 实例化订单对象
Order order=new Order();
// 赋值同名属性
BeanUtils.copyProperties(orderAddDTO,order);
// 调用持久层方法
orderMapper.insertOrder(order);
log.info("新增订单完成:{}",order);
}
}
06(3).创建controller控制层
(1)创建controller.OrderController业务控制类
package cn.tedu.csmall.order.controller;
import cn.tedu.csmall.commons.pojo.order.dto.OrderAddDTO;
import cn.tedu.csmall.commons.restful.JsonResult;
import cn.tedu.csmall.order.service.IOrderService;
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
@Api(tags = "订单模块")
@RequestMapping("/base/order")
public class OrderController {
@Autowired
private IOrderService orderService;
@ApiOperation("新增订单")
@PostMapping("/add")
public JsonResult orderAdd(OrderAddDTO orderAddDTO){
//调用业务逻辑层方法
orderService.orderAdd(orderAddDTO);
return JsonResult.ok("新增订单完成");
}
}
06(4).启动csmall-order项目进行测试
(0)访问nacos注册中心网站: 【http://localhost:8848/nacos/】
点击【左侧:服务管理 > 服务列表】,
发现此时【右侧已经有了:服务名nacos-order 的模块】
即:我们已经成功的将自己写的【order新增订单业务】注册到【nacos组件中】了
(1)如下图,访问API文档【 http://localhost:20002/doc.html 】按箭头点击,
可以看到返回200状态码的新增订单成功成功信息:
(2)此时在控制台也会出现【新增订单成功的信息】:(3)查看数据库中的【order_tbl】表格,
可以看到自己在【API文档网站】添加的【商品编码PC100、5的信息】已经在数据库中成功添加!!!
------------
07.创建子模块csmall-stock:(减少库存功的功能业务)
(0)
《创建子模块csmall-stock》
在【csmall下】创建【csmall-stock】:
————>创建Module时所选属性:
————>Name:csmall-stock
————>Group:cn.tedu
————>Artifact:csmall-stock
————>Package name:cn.tedu.csmall.stock 注意加"."点
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
——————————————————————————————————————————————————————————————————————————————
(1)配置依赖
(1)
《修改子模块csmall-stock的pom.xml文件》
————>也要父子相认:在<parent>节点中修改
————>删除全部依赖,再添加一些依赖
————>编写父项目csmall的pom.xml配置:
放开<modules>节点中的子模块【<module>csmall-stock</module>】节点
————>修改子模块csmall-stock的pom.xml文件,完整pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父子相认-->
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--web实例-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--alibaba 数据源德鲁伊-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--all-common依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!--配置nacos注册中心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
——————————————————————————————————————————————————————————————————————————————
(2)配置"yml"配置文件
(2)
《添加".yml"配置文件》:
在csmall-stock:
在resources目录:
————>删除项目中的测试文件夹:test
————>删除原有的application.properties配置文件
————>创建新的application.yml配置文件,注意修改端口号,↓代码如下:
————>再创建新的application-dev.yml配置文件吗,
因为csmall-order时真要连接数据库的,所以配置连接数据库信息,↓代码如下:
《application.yml》:
————>复制csmall-order的并将端口号改为20003:
————>【port: 20003】,其余不动
《application-dev.yml》:
————>复制csmall-order的并将name改为:【name: nacos-stock】
07(1).创建config配置类
(1)创建stock.config.CommonConfiguration配置类
将 csmall-order中的复制过来: 什么也不用改
(2)创建stock.config.Knife4jConfiguration配置类
将 csmall-order中的复制过来:
修改:
————>private String basePackage = "cn.tedu.csmall.stock.controller";
————>private String groupName = "base-stock";
————>private String title = "酷鲨商城项目案例在线API文档--基础stock-web实例";
————>private String description = "构建基础stock-web项目,实现库存管理";
(3)创建stock.config.MyBatisConfiguration配置类
将 csmall-cart中的复制过来:
修改类上的注解:
————>@MapperScan("cn.tedu.csmall.stock.mapper")
07(1.2).编写stock减少库存功能业务:
07(2).创建mapper持久层
(1)创建mapper.StockMapper 接口
package cn.tedu.csmall.stock.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
@Repository
public interface StockMapper {
//减少指定商品库存的方法
@Update("update stock_tbl set count=count-#{reduceCount} where commodity_code=#{commodityCode} and count>=#{reduceCount}")
void updateStockByCommodityCode(@Param("commodityCode") String commodityCode,
@Param("reduceCount") Integer reduceCount);
}
07(3).创建service开发业务逻辑层
(1)创建service.IStockService库存服务接口
接口中应该包含减少库存数的业务逻辑层方法:
package cn.tedu.csmall.stock.service;
import cn.tedu.csmall.commons.pojo.stock.dto.StockReduceCountDTO;
public interface IStockService {
//减少库存数的业务逻辑层方法:
void reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO);
}
(2)创建service.impl.StockServiceImpl库存服务实施类
package cn.tedu.csmall.stock.service.impl;
import cn.tedu.csmall.commons.pojo.stock.dto.StockReduceCountDTO;
import cn.tedu.csmall.stock.mapper.StockMapper;
import cn.tedu.csmall.stock.service.IStockService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class StockServiceImpl implements IStockService {
@Autowired
private StockMapper stockMapper;
@Override
public void reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO) {
stockMapper.updateStockByCommodityCode(
stockReduceCountDTO.getCommodityCode(),//第一个参数是商品编号
stockReduceCountDTO.getReduceCount()//第二个参数是减少的库存数
);
log.info("库存减少完成!");
}
}
07(3).创建controller控制层
(1)创建controller.StockController业务控制类
package cn.tedu.csmall.stock.controller;
import cn.tedu.csmall.commons.pojo.stock.dto.StockReduceCountDTO;
import cn.tedu.csmall.commons.restful.JsonResult;
import cn.tedu.csmall.stock.service.IStockService;
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/stock")
@Api(tags="库存管理")
public class StockController {
@Autowired
private IStockService stockService;
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存业务")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
}
07(4).启动csmall-order项目进行测试
(0)访问nacos注册中心网站: 【http://localhost:8848/nacos/】
点击【左侧:服务管理 > 服务列表】,
发现此时【右侧已经有了:服务名nacos-stock 的模块】
即:我们已经成功的将自己写的【stock减少库存业务】注册到【nacos组件中】了
(1)如下图,访问API文档【 http://localhost:20002/doc.html 】按箭头点击,
可以看到返回200状态码的新增订单成功成功信息:
(2)此时在控制台也会出现【库存减少完成的信息】:
(3)查看数据库中的【stock_tbl】表格,发现数据库的信息是如图:
(4)可以在酷鲨在线API【http://localhost:20003/doc.html】中:
————>修改要减少库存的商品编号的数量count为990:
(5)在数据库【stock_tbl】表格中:可以看到自己在【API文档网站】减少的商品编号为PU201的库存数量count已经变为0!!!
------------
2.1.1RPC框架:Dubbo
(0)什么是RPC:(通信协议和序列化协议)
RPC是:Remote Procedure Call的缩写
————>翻译为:远程过程调用
目标是:
————>为了实现两台(多台)计算机\服务器,
互相调用方法\通信的解决方案
RPC的概念主要定义了两部分内容
————>1.序列化协议
————>2.通信协议
下面这张图助于理解 RPC:
上面图是老婆和老公在家的时,老婆让老公洗碗的调用流程
但这个流程是本地的,
我们再换成远程调用的图片:
RPC的概念主要定义了两部分内容
————>1.序列化协议
————>2.通信协议
——————————————————————————————————————————————————————————————————————————
《通信协议》:
————>————> 通信协议指的就是远程调用的通信方式
————>在上面图片调用中,老婆使用手机信息的方法通知老公去洗碗
————>实际上这个通知的方式可以有多重
————>例如:写信,飞鸽传书,闪送等等
《序列化协议》:
————>————> 序列化协议指通信内容的格式,双方都要理解这个格式
————>上面的图片中,老婆给老公发信息,一定是双方都能理解的信息
————>发送信息是序列化过程,接收信息需要反序列化
(1)什么是Dubbo:是RPC的框架
理解了RPC再学习Dubbo就会简单一些了
Dubbo是一套 RPC框架 。
既然是框架,我们可以在框架结构高度,定义Dubbo中使用的通信协议,使用的序列化框架技术,
而数据格式由Dubbo定义,我们负责配置之后直接通过客户端调用服务端代码。
简单来说
————>Dubbo就是RPC概念的实现
————>Dubbo是Spring Cloud Alibaba提供的一个框架
————>能够
————>实现微服务项目的各个调用
(2)Dubbo的发展历程
2012年底dubbo停止更新后到2017年dubbo继续更新之前
2015SpringCloud开始兴起,当时没有阿里的框架
国内公司要从SpringCloud和Dubbo中抉择使用哪个微服务方案
在2012年dubbo停止更新后国内的当当网在dubbo的基础上开发了dubboX框架,并进行维护
2019年后,SpringCloud和Dubbo才能共同使用
(2)Dubbo的协议支持
RPC框架分通信协议和序列化协议
Dubbo框架支持:《多种通信协议》 和 《多种序列化协议》,
————>可以通过配置文件进行修改
《支持多种通信协议》:
————>dubbo协议(默认)
————>rmi协议
————>hessian协议
————>http协议
————>webservice
.....
《支持多种多种序列化协议》:
————>hessian2(默认)
————>java序列化
————>compactedjava
————>nativejava
————>fastjson
————>dubbo
————>fst
————>kryo
《Dubbo默认情况下,协议的特征如下》
————>采用N非阻塞式IO(IO)单一长连接
————>优秀的并发性能,但是大型文件的处理差
————>Dubbo开发简单,有助于提升开发效率
(3)Dubbo服务的注册与发现
在Dubbo的调用过程中,必须包含注册中心的支持
注册中心推荐使用Nacos,但是如果使用其他软件也能实现例如(Redis,zookeeper等)
服务发现,即消费端自动发现服务地址列表的能力,是微服务框架需要具备的关键能力,
借助于自动化的服务发现,微服务之间可以在无需感知对端部署位置与 IP 地址的情况下实现通信。
上面的示例中,老婆就是服务器的发现者,它能够获取老公的所有功能列表
老婆一旦调用公的服务就完成了Dubbo的调用
consumer服务的消费者:
————>指服务的调用者(使用者)也就是老婆的位置
provider服务的提供者:
————>指服务的拥有者(生成者)也就是老公的位置
在Dubbo中,远程调用依据是服务的提供者在Nacos中注册的服务名称
一个服务名称,可能有多个运行的实例,任何一个空闲的实例都可以提供服务
————————————————————————————————————————————————————————————————————————————————————————
常见面试题:《Dubbo的注册发现流程》
1.首先服务的提供者启动服务到注册中心注册,包括各种ip端口信息,
Dubbo会同时注册该项目提供的远程调用的方法
2.服务的消费者(使用者)注册到注册中心,订阅发现
3.当有新的远程调用方法注册到注册中心时,
注册中心会通知服务的消费者有哪些新的方法,如何调用的信息
4.RPC调用,在上面条件满足的情况下,服务的调用者无需知道ip和端口号,
只需要服务名称就可以调用到服务提供者的方法
------------
08(0).Dubbo实现微服务调用
(1)确定调用关系
在上面的模型中:
————>以order调用stock减少库存的业务举例
————>order模块是消费者stock模块是生产者
————>在代码调用时,首先要对项目进行必要的配置
————>我们调用时一般会在消费者项目的代码业务逻辑层中 ,编写调用生产者业务逻辑层方法的代码
————>这样做的好处:
————>我们的生成者正常编写mapper>service>controller这个开发流程不会因为Dubbo的介入而变化
08(1).修改csmall-stock模块:
- 完成order(消费者)调用stock(生产者)减少库存的业务:
08(1.1)创建子子模块csmall-stock-service
(1)配置依赖
(0)
《创建子子模块csmall-stock-service》
在【csmall-stock下】创建【csmall-stock-service】:
————>创建Module时所选属性:
————>Name:csmall-stock-service
————>Group:cn.tedu
————>Artifact:csmall-stock-service
————>Package name:cn.tedu.csmall.stock.service 注意加"."点
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
——————————————————————————————————————————————————————————————————————————————
(1)
《修改子模块csmall-stock-service的pom.xml文件》
————>删除resources文件夹
————>删除自带的SpringBoot启动类
————>也要父子相认:在<parent>节点中修改
————>删除全部依赖,再添加一个依赖
————>在csmall-stock父项目pom文件添加必要配置:
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock</name>
<description>Demo project for Spring Boot</description>
<!-- ↓↓↓↓↓↓↓↓以下四行代码为新增↓↓↓↓↓↓↓↓↓↓↓↓↓-->
<packaging>pom</packaging>
<modules>
<module>csmall-stock-service</module>
</modules>
————>修改子模块csmall-stock-service的pom.xml文件,完整pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock-service</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
(2)根包下复制子模块的 IStockService 业务逻辑层接口
package cn.tedu.csmall.stock.service;
import cn.tedu.csmall.commons.pojo.stock.dto.StockReduceCountDTO;
public interface IStockService {
//减少库存数的业务逻辑层方法
void reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO);
}
08(1.2)创建子子模块csmall-stock-webapi
(1)配置依赖
(0)
《创建子子模块csmall-stock-webapi》
在【csmall-stock下】创建【csmall-stock-webapi】:
————>创建Module时所选属性:
————>Name:csmall-stock-webapi
————>Group:cn.tedu
————>Artifact:csmall-stock-webapi
————>Package name:cn.tedu.csmall.stock.webapi 注意加"."点
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
——————————————————————————————————————————————————————————————————————————————
(1)
《修改子模块csmall-stock的pom.xml文件》:
————>在csmall-stock父项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock</name>
<description>Demo project for Spring Boot</description>
<packaging>pom</packaging>
<modules>
<module>csmall-stock-service</module>
<module>csmall-stock-webapi</module>
</modules>
</project>
——————————————————————————————————————————————————————————————————————————————
(2)
《修改子子模块csmall-stock-webapi的pom.xml文件》:
————>删除删除test测试文件夹
————>也要父子相认:在<parent>节点中修改
————>删除全部依赖,再添加一个依赖
————>修改子模块csmall-stock-webapi的pom.xml文件,完整pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock-webapi</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-stock-webapi</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--web实例-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--alibaba 数据源德鲁伊-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--all-common依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!-- nacos注册中心依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Dubbo依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- 添加camall-stock-service的依赖,以实现业务逻辑层接口 -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
(2)配置"yml"配置文件
(2)
《添加".yml"配置文件》:
在csmall-stock-webapi:
在resources目录:
————>删除原有的测试文件夹:test
————>删除原有的application.properties配置文件
————>创建新的application.yml配置文件,注意修改端口号,↓代码如下:
————>再创建新的application-dev.yml配置文件,
《application.yml》:
server:
port: 20003
#公共配置
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》:
spring:
datasource:
url: jdbc:mysql://localhost:3306/csmall_db?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: root
application:
name: nacos-stock
cloud:
nacos:
discovery:
server-addr: localhost:8848
dubbo:
protocol:
# 设置Dubbo服务调用的端口 设置-1能够实现动态自动设置合适端口,生成规则是从20880开始递增
port: -1
# 设置端口名称,一般固定就叫dubbo
name: dubbo
registry:
# 配置当前Dubbo注册中心的类型和地址
address: nacos://localhost:8848
consumer:
# 设置为false表示当前项目启动时,不检查要调用的远程服务是否可用,避免报错
check: false
(3)修改子模块csmall-stock的中子模块的包的结构
(1)
因为我们在子模块csmall-stock的:
子子模块csmall-stock-service中:
已经复制了子模块的 IStockService 接口
,所以,我们将子模块csmall-stock的:service包下的 IStockService 接口删除
再将 子模块csmall-stock的:【四个包:config/controller/mapper/service】复制到:
子子模块csmall-stock-webapi的根包下,
复制过来可以看到service包已经变成了impl包,
不过没有关系,可以将包impl改名为【service.impl】,若是不改名也不影响!
最后,将子模块csmall-stock的【src文件夹】删除即可,因为我们已经复制到子子模块csmall-stock-webapi中了!
此时子模块csmall-stock的结构应该是:
csmall-stock
csmall-stock-service
csmall-stock-webapi
——————————————————————————————————————————————————————————————————————————————————
(2)
再修改配置类《Knife4jConfiguration》:
————> private String basePackage = "cn.tedu.csmall.stock.webapi.controller";
——————————————————————————————————————————————————————————————————————————————————
(3)
再修改配置类《MyBatisConfiguration》:
————> @MapperScan("cn.tedu.csmall.stock.webapi.mapper")
——————————————————————————————————————————————————————————————————————————————————
(4)
在业务逻辑层[service.impl]包下的《StockServiceImpl》类中添加【@DubboService】注解:
————> @DubboService //表示当前业务逻辑层实现类中的所有方法均会注册到nacos,成为dubbo可以发现的业务逻辑层方法
——————————————————————————————————————————————————————————————————————————————————
(5)
在子子模块《csmall-stock-webapi》SpringBoot启动类中添加【@EnableDubbo】注解:
————> @EnableDubbo //如果当前项目是Dubbo服务的生产者,必须添加这个注解
08(1.3).启动csmall-stock项目进行测试
(1)
访问nacos注册中心网站: 【http://localhost:8848/nacos/】
点击【左侧:服务管理 > 服务列表】,
发现此时【右侧已经有了:Dubbo的服务名nacos-stock 的模块】
即:我们已经成功的将自己写的【Dubbo生产者】注册到【nacos组件中】了
08(2).修改csmall-cart模块:完全参照【08】即可!
------------
2.1.2负载均衡:LoadBalance
(1)什么是负载均衡
在实际开发中,一个服务基本都是集群模式的,也就是多个功能相同的项目在运行,这样才能承受更高的并发
这时一个请求到这个服务,就需要确定访问哪一个服务器
Dubbo框架内部支持负载均衡算法,能够尽可能的让请求在相对空闲的服务器上运行
我们要实现设置好负载均衡的策略算法,并设置好每个服务器的运行权重
才能更好的实现负载均衡的效果
Loadbalance:就是负载均衡的意思
(2)Dubbo内置负载均衡策略算法
Dubbo内置4种负载均衡算法
——————>(2.1) random loadbalance:随机分配策略(默认)
——————>(2.2) round Robin Loadbalance:权重平均分配
——————>(2.3) leastactive Loadbalance:活跃度自动感知分配
——————>(2.4) consistanthash Loadbalance:一致性hash算法分配
实际运行过程中,每个服务器性能不同
在负载均衡时,都会有性能权重,这些策略算法都考虑权重问题
(2.1)随机分配策略(默认)
《随机分配策略(默认)》:
————>随机生成随机数
————>在哪个范围内让哪个服务器运行
————>优点:
算法简单,效率高,长时间运行下,任务分配比例准确
————>缺点:
偶然性高,如果连续的几个随机请求发送到性能弱的服务器,会导致异常甚至宕机
(2.2)权重平均分配
如果几个服务器权重一致,那么就是依次运行
3个服务器:
————> 1>1 2>2 3>3 4>1
但是服务器的性能权重一致的可能性很小
————>所以我们需要权重评价分配
————>Dubbo2.6.4之前平均分配权重算法是有问题的
————>如果3个服务器的权重比5:3:1
————> 1>1 2>1 3>1 4>1 5>1 6>2 7>2 8>2 9>3
————> 10>1
————>Dubbo2.7之后更新了这个算法使用"平滑加权算法"优化权重平均分配策略
(2.3)活跃度自动感知
记录每个服务器处理一次请求的时间
安装时间比例来分配任务数,运行一次需要时间多的分配的请求数较少
(2.4)一致性Hash算法
根据请求的参数进行hash运算
以后每次相同参数的请求都会访问固定服务器
因为根据参数选择服务器,不能平均分配到每台服务器上
使用的也不多
09(1).修改csmall-order模块:
09(1.1)创建子子模块csmall-order-service:完全参照【08】即可!
09(2.2)创建子子模块csmall-order-webapi
(1)配置依赖
(0)
《创建子子模块csmall-order-webapi》
在【csmall-order下】创建【csmall-order-webapi】:
————>创建Module时所选属性:
————>Name:csmall-order-webapi
————>Group:cn.tedu
————>Artifact:csmall-order-webapi
————>Package name:cn.tedu.csmall.order.webapi 注意加"."点
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
——————————————————————————————————————————————————————————————————————————————
(1)
《修改子模块csmall-order的pom.xml文件》:
————>在csmall-order中的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父子相认-->
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-order</name>
<description>Demo project for Spring Boot</description>
<packaging>pom</packaging>
<modules>
<module>csmall-order-service</module>
<module>csmall-order-webapi</module>
</modules>
</project>
——————————————————————————————————————————————————————————————————————————————
(2)
《修改子子模块csmall-order-webapi的pom.xml文件》:
————>删除test测试文件夹
————>也要父子相认:在<parent>节点中修改
————>修改子模块csmall-order-webapi的pom.xml文件,完整pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父子想让-->
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-order</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall-order-webapi</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>csmall-order-webapi</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--web实例-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--alibaba 数据源德鲁伊-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--all-common依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--在线api文档-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!-- nacos注册中心依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Dubbo依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- service接口项目依赖 -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- order模块同时作为服务的消费者要将需要调用的服务所在的业务逻辑层接口依赖添加上 -->
<!-- 我们需要stock模块的减库存方法和cart模块删除购物车商品的方法 -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-cart-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-stock-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
(2)配置"yml"配置文件
(2)
《添加".yml"配置文件》:
在csmall-order-webapi:
在resources目录:
————>删除原有的测试文件夹:test
————>删除原有的application.properties配置文件
————>创建新的application.yml配置文件,注意修改端口号,↓代码如下:
————>再创建新的application-dev.yml配置文件,
《application.yml》:
server:
port: 20002
#公共配置
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》:
spring:
datasource:
url: jdbc:mysql://localhost:3306/csmall_db?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: root
application:
name: nacos-order
cloud:
nacos:
discovery:
server-addr: localhost:8848
dubbo:
protocol:
port: -1 # 设置Dubbo服务调用的端口 设置-1能够实现动态自动设置合适端口,生成规则是从20880开始递增
name: dubbo # 设置端口名称,一般固定就叫dubbo
registry:
address: nacos://localhost:8848 # 配置当前Dubbo注册中心的类型和地址
consumer:
check: false # 设置为false表示当前项目启动时,不检查要调用的远程服务是否可用,避免报错
(3)修改子模块csmall-stock的中子模块的包的结构
(1)
因为我们在子模块csmall-order的:
子子模块csmall-order-service中:
已经复制了子模块的 IStockService 接口
,所以,我们将子模块csmall-order的:service包下的 IStockService 接口删除
再将 子模块csmall-order的:【四个包:config/controller/mapper/service】复制到:
子子模块csmall-order-webapi的根包下,
复制过来可以看到service包已经变成了impl包,
不过没有关系,可以将包impl改名为【service.impl】,若是不改名也不影响!
最后,将子模块csmall-order的【src文件夹】删除即可,因为我们已经复制到子子模块csmall-order-webapi中了!
此时子模块csmall-order的结构应该是:
csmall-order
csmall-order-service
csmall-order-webapi
——————————————————————————————————————————————————————————————————————————————————
(2)
再修改配置类《Knife4jConfiguration》:
————> private String basePackage = "cn.tedu.csmall.order.webapi.controller";
——————————————————————————————————————————————————————————————————————————————————
(3)
再修改配置类《MyBatisConfiguration》:
————> @MapperScan("cn.tedu.csmall.order.webapi.mapper")
——————————————————————————————————————————————————————————————————————————————————
(4)
在业务逻辑层[service.impl]包下的《StockServiceImpl》类中添加【@DubboService】注解:
————> @DubboService //因为business模块要调用这个业务逻辑层中的方法,所以这个类也要注册到Nacos
————> 在该 StockServiceImpl 类中修改orderAdd方法:
package cn.tedu.csmall.order.webapi.service.impl;
import cn.tedu.csmall.cart.service.ICartService;
import cn.tedu.csmall.commons.pojo.order.dto.OrderAddDTO;
import cn.tedu.csmall.commons.pojo.order.entity.Order;
import cn.tedu.csmall.commons.pojo.stock.dto.StockReduceCountDTO;
import cn.tedu.csmall.order.service.IOrderService;
import cn.tedu.csmall.order.webapi.mapper.OrderMapper;
import cn.tedu.csmall.stock.service.IStockService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 订单服务实施:OrderServiceImpl
*/
@DubboService //因为business模块要调用这个业务逻辑层中的方法,所以这个类也要注册到Nacos
@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {
@Autowired
private OrderMapper orderMapper;
//当前order模块消费stock的业务逻辑层方法,以减少库存
//因为stock模块的减库存方法在nacos中注册,所以可以使用Dubbo调用
//要想调用就必须使用@DubboReference,才能获得业务逻辑层实现类对象
@DubboReference
private IStockService dubboStockService;
// order还需要cart模块的删除购物车商品的方法
@DubboReference
private ICartService dubboCartService;
@Override
public void orderAdd(OrderAddDTO orderAddDTO) {
// 1.减少订单商品的库存(要调用stock模块的方法)
StockReduceCountDTO stockReduceCountDTO=new StockReduceCountDTO();
// 为减少库存的商品编号赋值
stockReduceCountDTO.setCommodityCode(orderAddDTO.getCommodityCode());
// 为减少库存的数量赋值
stockReduceCountDTO.setReduceCount(orderAddDTO.getCount());
dubboStockService.reduceCommodityCount(stockReduceCountDTO);
// 2.删除订单中购物车商品的信息(要调用cart模块的方法)
dubboCartService.deleteUserCart(orderAddDTO.getUserId(),
orderAddDTO.getCommodityCode());
// 3.新增订单
// 实例化订单对象
Order order=new Order();
// 赋值同名属性
BeanUtils.copyProperties(orderAddDTO,order);
// 调用持久层方法
orderMapper.insertOrder(order);
log.info("新增订单完成:{}",order);
}
}
——————————————————————————————————————————————————————————————————————————————————
(5)
在子子模块《csmall-stock-webapi》SpringBoot启动类中添加【@EnableDubbo】注解:
————> @EnableDubbo //如果当前项目是Dubbo服务的生产者,必须添加这个注解
09(1.3).启动csmall-order项目进行测试
(1)
访问nacos注册中心网站: 【http://localhost:8848/nacos/】
点击【左侧:服务管理 > 服务列表】,
发现此时【右侧已经有了:Dubbo的服务名nacos-order 的模块】
即:我们已经成功的将自己写的【Dubbo生产者】注册到【nacos组件中】了
(2)
我们可以测试这个Dubbo的功能
————> 先启动nacos
————> 然后再启动cart和stock
————> 最后启动order
访问
————> http://localhost:20002/doc.html运行测试
(3)
注意运行前,数据库的数据状态和运行后的比较一下:
运行前:
运行后多了一条数据:【在http://localhost:20002/doc.html 中的新增订单】
09(2).修改csmall-business模块
business模块是我们设计的整体业务的起点
它是单纯的消费者
我们不需要像生产者一样去创建两个子项目
直接在原有项目上修改即可
——————————————————————————————————————————————————————————————————————————————————————————
(1)添加依赖:
(1)
直接到csmall-business的 pom文件中添加dubbo和相关接口的依赖:
<!-- dubbo依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- business模块消费order模块的生成订单的方法,所以添加order接口的依赖 -->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>csmall-order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
——————————————————————————————————————————————————————————————————————————————————————————
(2)修改"yml"配置文件
(2)
在:《application-dev.yml》中添加【dubbo】的配置:
spring:
application:
# 当前Springboot项目的名称,用作注册中心服务的名称
name: nacos-business
cloud:
nacos:
discovery:
# 定义nacos运行的路径
server-addr: localhost:8848
# ephemeral设置当前项目启动时注册到nacos的类型 true(默认):临时实例 false:永久实例
ephemeral: true
dubbo:
protocol:
port: -1 # 设置Dubbo服务调用的端口 设置-1能够实现动态自动设置合适端口,生成规则是从20880开始递增
name: dubbo # 设置端口名称,一般固定就叫dubbo
registry:
address: nacos://localhost:8848 # 配置当前Dubbo注册中心的类型和地址
consumer:
check: false # 设置为false表示当前项目启动时,不检查要调用的远程服务是否可用,避免报错
——————————————————————————————————————————————————————————————————————————————————————————
(3)
我们要在当前busindess模块的业务逻辑层实现类(BusinessServiceImpl)中:
————>实现Dubbo调用order模块的生成订单方法
package cn.tedu.csmall.business.service.impl;
import cn.tedu.csmall.business.service.IBusinessService;
import cn.tedu.csmall.commons.pojo.order.dto.OrderAddDTO;
import cn.tedu.csmall.order.service.IOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
/**
* 业务服务实施
*/
@Service
@Slf4j
public class BusinessServiceImpl implements IBusinessService {
//Dubbo在获取order模块的业务逻辑层实现类
@DubboReference
private IOrderService dubboOrderService;
@Override
public void buy() {
//暂时模拟一个下单业务
//创建OrderAddDTO类,并赋值
OrderAddDTO orderAddDTO = new OrderAddDTO();
orderAddDTO.setCommodityCode("PC100");
orderAddDTO.setMoney(500);
orderAddDTO.setCount(9);
//因为没有持久层,只能输出一下,表示运行正常
log.info("新增订单信息为:{}",orderAddDTO);
//dubbo调用生成订单方法
dubboOrderService.orderAdd(orderAddDTO);
}
}
09(2.1).启动csmall-business项目进行测试
(1)
访问nacos注册中心网站: 【http://localhost:8848/nacos/】
点击【左侧:服务管理 > 服务列表】,
发现此时【右侧已经有了:Dubbo的服务名nacos-xxx 这四个模块】
即:我们已经成功的将自己写的【四个模块】注册到【nacos组件中】了
(2)
我们可以测试这个Dubbo的功能
————> 先启动nacos
————> 然后再启动cart和stock
————> 再启动order
————> 然后再启动order
访问
————> http://localhost:20002/doc.html运行测试
————>点击发送后,可以看到下图:发起购买成功
(3)
此时在控制台可以看到在【csmall-business模块中的业务逻辑类BusinessServiceImpl中buy方法里设置的下单数量count:9】和【控制台的信息所对应】:
(4)
注意运行前数据库的数据状态 和 运行后的比较一下:【可以发现】
运行测试前:
运行测试后只有【库存 模块】的对应【PC100的数据发生了改变】:
————>原因:【在业务逻辑类BusinessServiceImpl中buy方法里设置的下单数量count:9】
————>所以:此处运行后的图相比运行前的图数量减少了9
10.Dubbo生产者消费者配置小结
Dubbo生产者消费者相同的配置
pom文件添加dubbo依赖,yml文件配置dubbo信息
《生产者》:
————>要有service接口项目
————>提供服务的业务逻辑层实现类要添加@DubboService注解
————>SpringBoot启动类要添加@EnableDubbo注解
《消费者》:
————>pom文件添加消费模块的service依赖
————>业务逻辑层远程调用前天模块时使用@DubboReference注解获取业务逻辑层实现类对象
2.2.SpringCloud(Seata:分布式事务)
- 启动【Seata】服务:bin目录下的dos窗口输入指令:【seata-server.bat -h 127.0.0.1 -m file;】
- 下载Seata:
- https://github.com/seata/seata/releases
- https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip
(1)什么是Seata(:分布式事务解决方案)
【Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务】
也是Spring Cloud Alibaba提供的组件
Seata官方文档
————>https://seata.io/zh-cn/
————>更多信息可以通过官方文档获取
(2)为什么需要Seata
我们首先简单复习一下事务的概念:
事务的4个特性:
ACID特性,即:
————>原子性
————>一致性
————>隔离性
————>永久性
我们再业务中,必须保证数据库操作的原子性,也就是当前业务的所有数据库操作要么都成功,要么都失败
————>之前我们使用Spring声明式事务来解决本地的事务问题
————>但是现在是微服务环境,一个业务可能涉及多个模块的数据库操作
————>这种情况就需要专门的微服务状态下解决事务问题的"分布式事务"解决方案
————>我们学习的Seata就是这样的产品
————>【Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,】为用户打造一站式的分布式解决方案。
(3.1)Seata的构成:(TC\TM\RM)
————>TC(事务协调器):是Server端,要单独部署,维护全局和分支事务的状态,驱动全局事务提交或回滚;
————>TM(事务管理器):是Client端,由业务系统集成,定义全局事务;
————>RM(资源管理器):是Client端,由业务系统集成,管理分支事务处理的资源。
(3.2)Seata管理的分布式事务的典型生命周期:
(1)TM要求TC开始一项新的全局事务。TC生成代表全局事务的XID。
(2)XID通过微服务的调用链传播。
(3)RM将本地事务注册为XID到TC的相应全局事务的分支。
(4)TM要求TC提交或回退相应的XID全局事务。
(5)TC驱动XID的相应全局事务下的所有分支事务以完成分支提交或回滚。
(4)Seata的[AT模式]运行原理/过程
观察下面事务模型:
上面结构是比较典型的远程调用结构
————>如果account操作数据库失败需要让order模块和storage模块撤销(回滚)操作
————>声明式事务不能完成这个操作
————>需要Seata来解决
————>解决模型如下:
Seata构成部分包含:
————>TC(事务协调器):是Server端,要单独部署,维护全局和分支事务的状态,驱动全局事务提交或回滚;
————>TM(事务管理器):是Client端,由业务系统集成,定义全局事务;
————>RM(资源管理器):是Client端,由业务系统集成,管理分支事务处理的资源。
我们项目使用AT(自动)模式完成分布式事务的解决:
————>AT模式运行过程:
————>1.事务的发起方(TM)会向事务协调器(TC)申请一个全局事务id,并保存
————>2.Seata会管理事务中所有相关的参与方的数据源,
将数据操作之前和之后的镜像都保存在undo_log表中,这个表是seata框架规定的,
方便提交(commit)或回滚(roll back)
————>3.事务的发起方(TM)会连同全局id一起通过远程调用运行资源管理器(RM)中的方法
————>4.资源管理器(RM)接收到全局id,并运行指定的方法,将运行的状态同步到事务协调器(TC)
————>5.如果运行整体没有发生异常,发起方(TM)会通过事务协调器通知所有分支,
将本次事务所有对数据库的影响真正生效,反之如果任何一个RM运行发生异常,
那么都会通知事务协调器,再由事务协调器通知所有分支,回滚数据中的数据
——>注:回滚时可以使用undo_log表中的数据来实现回滚
(5)其他模式简介
AT模式运行有一个非常明显的条件
就是事务分支都必须是操作关系型数据库(mysql\MariaDB\Oracle)
但是如果一个事务中有操作例如Redis这样的非关系型数据库时就不能使用AT模式了
除了AT模式之外还有TCC、SAGA 和 XA 事务模式
《Seata的4种事务模式》:
Seata针对不同的业务场景提供了四种不同的事务模式,具体如下:
————>AT模式: AT 模式的一阶段、二阶段提交和回滚(借助undo_log表来实现)均由 Seata 框架自动生成,
用户只需编写“业务SQL”,便能轻松接入分布式事务,
AT 模式是一种对业务无任何侵入的分布式事务解决方案。
————>TTC模式:相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,
但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT模式高很多。
适用于核心系统等对性能有很高要求的场景。
————>SAGA模式:Sage 是长事务解决方案,事务驱动,使用那种存在流程审核的业务场景,
如: 金融行业,需要层层审核。
————>XA模式: XA模式是分布式强一致性的解决方案,但性能低而使用较少。
————————————————————————————————————————————————————————————————
《TCC模式》:
————>这个模式简单来说就是自己编写代码进行事务的提交和回滚
————>我们需要在各个分支业务逻辑层代码中编写一组三个方法(prepare\commit\rollback)
————>prepare:准备 commit:提交 rollback:回滚
————>prepare方法是无论事务成功与否都会运行的代码
————>commit当整体事务运行成功时运行的方法
————>rollback当整体事务运行失败是运行的方法
————>优点:虽然代码是自己写的,但是事务整体提交或回滚的机制仍然可用
————>缺点:每个业务都要编写3个方法来对应,代码冗余,而且业务入侵量大
————————————————————————————————————————————————————————————————
《SAGA模式》:
————>SAGA模式的思想是编写一个类,当指定的事务问题时,运行SAGA编写的回滚类
————>这样编写代码不影响已经编写好的业务逻辑代码
————>一般用于修改已经编写完成的老代码
————>缺点是每个事务分支都要编写一个类来回滚业务,
————>类数量多,开发量大
————————————————————————————————————————————————————————————————
《XA模式》:
————>支持XA协议的数据库分布式事务,使用比较少
(5.1)Seata的4种事务模式之:AT模式
AT模式,分为两个阶段
————> 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
————> 二阶段:提交异步化 ( 或者事务执行失败,回滚通过一阶段的回滚日志进行反向补偿)
《一阶段》:
————>在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,
————>找到“业务 SQL”要更新的业务数据,在业务数据被更新前,
————>将其保存成“before image”,然后执行“业务 SQL”更新业务数据,
————>在业务数据更新之后,再将其保存成“after image”,最后生成行锁。
————>以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
如图:
《二阶段:执行成功,进行分布式事务提交》:
————>业务 SQL”在一阶段已经提交至数据库,
所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
如图:
《二阶段:执行失败,进行业务回滚》:
————>首先对比“数据库当前业务数据”和 “after image”,
避免发生业务脏写(类似于CAS操作),完成校验后用“before image”还原业务数据,并且删除中间数据。
如图:
(5.2)Seata的4种事务模式之:TTC模式
tcc模式主要可以分为三个阶段:
————>Try:做业务检查和资源预留
————>Confirm:确认提交
————>Cancel:业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放
————————————————————————————————————————————————————————————————————————————————————
《TCC模式下常见的三种异常》:
————>1.空回滚
:空回滚就是对于一个分布式事务,
在没有调用 TCC 资源 Try 方法的情况下(如机器宕机、网络异常),
调用了二阶段的 Cancel 方法,Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。
:解决方案:需要一张额外的事务控制表,其中有分布式事务 ID 和分支事务 ID,
第一阶段 Try 方法里会插入一条记录,表示一阶段执行了。Cancel 接口里读取该记录,
如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。
————>2.幂等
:幂等就是对于同一个分布式事务的同一个分支事务,重复去调用该分支事务的第二阶段接口,
因此,要求 TCC 的二阶段 Confirm 和 Cancel 接口保证幂等,不会重复使用或者释放资源。
如果幂等控制没有做好,很有可能导致资损等严重问题。
:解决方案:记录每个分支事务的执行状态。在执行前状态,如果已执行,那就不再执行;
否则,正常执行。前面在讲空回滚的时候,已经有一张事务控制表了,
事务控制表的每条记录关联一个分支事务,
那我们完全可以在这张事务控制表上加一个状态字段,用来记录每个分支事务的执行状态。
————>3.悬挂
:悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。
因为允许空回滚的原因,Cancel 接口认为 Try 接口没执行,空回滚直接返回成功,
对于 Seata 框架来说,认为分布式事务的二阶段接口已经执行成功,整个分布式事务就结束了。
:解决方案:二阶段执行时插入一条事务控制记录,状态为已回滚,这样当一阶段执行时,
先读取该记录,如果记录存在,就认为二阶段已经执行;否则二阶段没执行。
(5.3)Seata的4种事务模式之:SAGA模式
Saga模式是SEATA提供的长事务解决方案,在Saga模式中:
————>业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,
(发生失败成功的先关联的本地事务都会被回滚,可以理解为原子性)
一阶段正向服务和二阶段补偿服务都由业务开发实现。
目前SEATA提供的Saga模式是基于状态机引擎来实现的,机制是:
————>通过状态图来定义服务调用的流程并生成 json 状态语言定义文件
————>状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点
————>状态图 json 由状态机引擎驱动执行,
当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚
————>可以实现服务编排需求,
支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能
Seata Saga 实现了一个状态机,可以编排服务的调用流程及正向服务的补偿服务,
生成一个 json 文件定义的状态图,状态机引擎驱动到这个图的运行,
当发生异常的时候状态机触发回滚,逐个执行补偿服务。
同时Seata 也存在空回滚、幂等、悬挂问题。
(5.4)Seata的4种事务模式之:XA模式
XA 模式是 Seata 将会开源的另一种无侵入的分布式事务解决方案:
————>无侵入
————>将快照数据和行锁等通过 XA 指令委托给了数据库来完成
XA模式是分布式强一致性的解决方案,但性能低而使用较少。
01.使用Seata
(1)配置依赖和修改application-dev.yml 文件
《cart\stock\order都是具备数据库操作的模块》配置过程如下:
在以上三个模块中的pom.xml文件添加以下下依赖:
<!-- seata和SpringBoot整合依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!-- Seata完成分布式事务需要的两个相关依赖(Seata需要下面两个依赖中的资源) -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
——————————————————————————————————————————————————————————————————————————————————
《修改application-dev.yml 文件》:
修改《application-dev.yml》的【seata相关配置的代码】并:
粘贴在【order/cart/business/stock】中:
代码如下:
seata:
tx-service-group: csmall_group #定义分组名称
service:
vgroup-mapping:
csmall_group: default # 使用seata默认事务配置
grouplist:
default: localhost:8091 # 8091是seata默认的地址
注意同一个事务必须在同一个【tx-service-group】中
————>同时指定相同的seata地址和端口
——————————————————————————————————————————————————————————————————————————————————
business模块配置更简单
————>因为它是服务的发起者,不需要数据库操作,所以配置更简单
————>但是它是TM的角色,不配置肯定是不行的
————>在《csmall-business》中添加:
<!--seata整合-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
——————————————————————————————————————————————————————————————————————————————————
(2)修改业务逻辑层BusinessServiceImpl
《BusinessServiceImpl》类最终代码:
package cn.tedu.csmall.business.service.impl;
import cn.tedu.csmall.business.service.IBusinessService;
import cn.tedu.csmall.commons.exception.CoolSharkServiceException;
import cn.tedu.csmall.commons.pojo.order.dto.OrderAddDTO;
import cn.tedu.csmall.commons.restful.ResponseCode;
import cn.tedu.csmall.order.service.IOrderService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
/**
* 业务服务实施
*/
@Service
@Slf4j
public class BusinessServiceImpl implements IBusinessService {
//Dubbo在获取order模块的业务逻辑层实现类
@DubboReference
private IOrderService dubboOrderService;
/*
一旦标记这个注解,seata机会将这个方法当作一个分部事务的起点,
之后所有远程Dubbo调用的数据库操作要么都成功,要么都失败
*/
@GlobalTransactional //|-------新增-------|
@Override
public void buy() {
//暂时模拟一个下单业务
//创建OrderAddDTO类,并赋值
OrderAddDTO orderAddDTO = new OrderAddDTO();
orderAddDTO.setCommodityCode("PC100");
orderAddDTO.setMoney(500);
orderAddDTO.setCount(9);
//因为没有持久层,只能输出一下,表示运行正常
log.info("新增订单信息为:{}",orderAddDTO);
//dubbo调用生成订单方法
dubboOrderService.orderAdd(orderAddDTO);
//|-------以下代码为新增-------|
/*
为了验证我们seata是有效果的,在当前业务逻辑层方法中随机发生异常,
我们可以通过观察正常运行时数据是否提交和发生异常是数据是否回滚来判断seata是否工作
*/
if(Math.random()<0.5){
throw new CoolSharkServiceException(ResponseCode.INTERNAL_SERVER_ERROR,
"发生随机异常");
}
}
}
(3)【测试分布式事务】可以成功:
1.首先启动【Nacos】服务;
2.再启动【Seata】服务,输入指令seata-server.bat -h 127.0.0.1 -m file;
或配置到idea直接就可以启动无需输入指令
3.按顺序启动这四个项目模块:
————>cart
————>stock
————>order
————>business
利用knife4j访问business模块,否则无法触发事务效果,business模块是seata事务的启动
————>在windows系统中运行seata可能出现不稳定的情况,重启seata即可解决
:
访问酷鲨API在线文档:【http://localhost:20000/doc.html#/】
>根据是否发生随机异常,来判断seata是否有效:
————>若返回200成功,则数据库的库存表格中的库存数量发生变化
(变化的值:就是csmall-business模块中的业务逻辑层BusinessServiceImpl的buy方法内自己写的数量:“9”)
————>若返回500发生随机异常, 库存数量不变
2.3.SpringCloud(Sentinel:流量控制)
- Sentinel 限流针对控制层
(1)什么是Sentinel(流量控制,限流)
Sentinel也是Spring Cloud Alibaba的组件
Sentinel英文翻译"哨兵\门卫"
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。
【Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。】
官网地址
————> https://sentinelguard.io/zh-cn/
下载地址
————> https://github.com/alibaba/Sentinel/releases
(2)为什么需要Sentinel
丰富的应用场景
————>双11,秒杀,12306抢火车票
完备的实时状态监控
————>可以支持显示当前项目各个服务的运行和压力状态,分析出每台服务器处理的秒级别的数据
广泛的开源生态
————>很多技术可以和Sentinel进行整合,SpringCloud,Dubbo,而且依赖少配置简单
完善的SPI扩展
————>Sentinel支持程序设置各种自定义的规则
01.Sentinel 限流效果
(1)基本配置
我们找一个相对简单的模块测试和观察限流效果
以csmall-stock-webapi模块为例
添加pom依赖如下:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
————————————————————————————————————————————————————————————————————
application-dev.yml修改配置如下:
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 # 配置Sentinel仪表台的位置
port: 8721 # 真正执行限流的端口也要设置一下,注意这个端口其他微服务项目不能相同
如上↑:【sentinel.transport.port】每个微服务项目不能相同
——————————————————————————————————————————————————————————————————————————————
《下面进行限流操作》:
————> Sentinel限流针对控制层方法也就是说
————> 我们要到Controller类中的方法上去做设置:
在【csmall-stock】的【StockController】控制类中修改:
@RestController
@RequestMapping("/base/stock")
@Api(tags = "库存管理")
public class StockController {
@Autowired
private IStockService stockService;
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存业务")
//此注解标记的方法会被Sentinel监控,()里的内容时这个监控的名称,我们可以在"仪表台"中看到
//↓↓↓↓↓↓以下代码有改动↓↓↓↓↓↓
@SentinelResource("减少库存方法(控制器)")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
stockService.reduceCommodityCount(stockReduceCountDTO);
/*
以下【try-catch代码块】用来测试:
————> sentinel 网站中的流控规则的[并发线程数]限流问题
*/
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
return JsonResult.ok("商品库存减少完成!");
}
}
——————————————————————————————————————————————————————————————————————————————
(4)启动csmall-stock模块进行测试:
(4)
启动Nacos;
启动Seata 组件
启动Sentinel:
————>【D:\soft\01Java\00soft\ruanjian\WeiFuWu\sentinel】中的【start-sentinel.bat】,
只测试csmall-stock 模块,因此只启动此模块即可,其他都可以不启动
访问【http://localhost:8080】,会出现【Sentinel】的登录页面,
————>输入【sentinel sentinel】登录,
————>可以进入【Sentinel控制台】
因为此时未在【酷鲨商城项目案例在线API文档-:http://localhost:20003/doc.html】中点击发送,
因此此时控制台空白
如图:
(5)
我们在:【http://localhost:8080/#/dashboard/flow/nacos-stock】中点击【发送】
如图 返回200成功:
(6.1)
那我们在【http://localhost:8080】:【流控规则】中【输入 单机阈值为 1】
————>意思就是: 当我指定【QPS】的【单机阈值为"1"】时,
在Api在线文档中一秒内点击不能超过1次,否则报500
;反之,若1秒时再点击,则报200
————>经过此测试,可以测试出【限流成功】
如下图:
测试:在Api在线文档中一秒内点击不超过1次,发现报200
测试:在Api在线文档中一秒内点击超过1次,发现报500
(6.2)
当我再新增流控规则,将并发线程数修改【并发线程数】的【单机阈值为"1"】时,
可以发现无论多快速点击也会报200,也不会报500,因为我们手动点击的速度永远跟不上他的速度。
因此我们在(7)中通过设置线程睡眠时间来完成【并发线程数】:见下面的【(7)】
(6.3)
当我们删除sentinel 网站中的左侧流控规则,在【http://localhost:20003/doc.html】快速点击也没有被限流。无论1秒内点击多少次,也不会报500,只会报200,因为该流控规则已经被删了
(7)
我们继续在【http://localhost:8080】:【流控规则】中修改:【并发线程数】
————>意思就是:当我们设置了【并发线程数】后,
在不同的两个浏览器各自访问【http://localhost:20003/doc.html】,
点击【发送】,
会发现这两个浏览器中都会先加载【在类中设置的5秒时间】后再返回200
具体实现:
下面我们首先将【csmall-stock】的【StockController】类中设置上【线程的sleep方法的睡眠时间】:
————>在【reduceCommodityCount】方法中添加【睡眠时间】:
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
stockService.reduceCommodityCount(stockReduceCountDTO);
//以下try catch为新增代码:
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//源代码.......
}
}
———————————————————————————————————————————————————————————————————————————————
(8)
重新启动【csmall-stock】的启动类进行测试:
发现【火狐浏览器 和 IE浏览器】各自访问该网站时,都会加载【5秒】后再返回【200】
测试:火狐访问:点击发送后会等待【5秒】再返回【200】
测试:IE访问:点击发送后会等待【5秒】再返回【200】
02.Sentinel 限流提示(流控与降级)
(1.1)流控逻辑:blockHander
上面我们完成了使用Sentinel实现QPS或并发线程数进行流控的操作
但是被流控的请求只有一个错误,没有错误提示,不方便给用户反馈
我们应该在开发时,对于被流控的请求给与相关提示
——————————————————————————————————————————————————————————————————————
StockController中编写代码如下:
《定义被Sentinel 限流时运行的方法》:
————>具体实现:
————>下面我们首先将【csmall-stock】的【StockController】类中的:
【reduceCommodityCount】方法中【添加注解的参数 和 添加限流方法blockError】:
//--------↓↓↓以下是修改代码↓↓↓--------
/*
此注解标记的方法会被Sentinel监控,value的值是这个监控的名称,
我们可以在"仪表台"中看到;
blockHandler的值指定了被限流时运行的方法名称(该方法名称我们可以在此方法的下边声明)
*/
@SentinelResource(value="减少库存方法(控制器)",blockHandler = "blockError")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
//Sentinel 限流方法应该满足如下需求:
//1.必须是public修改
//2.返回值类型必须和上面注解中的控制方法(JsonResult)一致
//3.方法名称要和控制器方法限流注解中的名称(blockError)一致
//4.参数列表必须和控制器一致,可以在所有参数后声明BlockException来获得限流异常
public JsonResult blockError(StockReduceCountDTO stockReduceCountDTO,
BlockException e){
return JsonResult.failed(ResponseCode.BAD_REQUEST,"服务器忙,请稍后再试");
}
(1.2)测试【限流方法】
(1)
启动Nacos;
启动Seata 组件;
启动Sentinel:
————>【D:\soft\01Java\00soft\ruanjian\WeiFuWu\sentinel】中的【start-sentinel.bat】,
只测试csmall-stock 模块,因此只启动此模块即可,其他都可以不启动
访问【http://localhost:8080】,会出现【Sentinel】的登录页面,
————>输入【sentinel sentinel】登录,
————>可以进入【Sentinel控制台】
————>发现此时页面时空的,因为我们还没有在:
酷鲨API文档【http://localhost:20003/doc.html】中点击【发送】
————>当我们点击完发送后,返回200 成功;
在【Sentinel】网站中刷新后可以看到已经有了【nack-stock】模块的仪表台
————> 点击左侧簇点链路
> 点击右侧[资源名: 减少库存方法(控制器)]
> 点击【+流控】
> 点击【QPS 单机阈值为"1"】,
> 新增该流控规则后,在API文档【http://localhost:20003/doc.html】中进行【发送】
> 当点击发送后,若一秒内点击次数超过"1" 则会报【400】:
{
"state": 400,
"message": "服务器忙,请稍后再试",
"data": null
}
可以看到上面中返回的400的描述就是我们在【stock模块中控制器类中自己定义的错误信息】
如图:
(2.1)降级逻辑:fallback
上面方法定义了被Sentinel限流时运行的方法
下面我们要学习降级的功能
降级功能和我们之前接触的 统一异常处理类 有相似的地方
但是降级:是Sentinel的功能
————————————————————————————————————————————————————————————————————
《定义被Sentinel 降级时运行的方法》:
————>具体实现:
————>下面我们首先将【csmall-stock】的【StockController】类中的:
【reduceCommodityCount】方法中【添加注解的参数 和 添加降级方法fallbackError】:
//--------↓↓↓以下是修改代码↓↓↓--------
/*
此注解标记的方法会被Sentinel监控,value的值是这个监控的名称,我们可以在"仪表台"中看到;
blockHandler的值指定了被限流时运行的方法名称(该方法名称我们可以在此方法的下边声明)
*/
@SentinelResource(value="减少库存方法(控制器)",blockHandler = "blockError",
fallback = "fallbackError")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
//生成一个50%几率随机触发降级流程
if(Math.random()<0.5){
throw new CoolSharkServiceException(ResponseCode.INTERNAL_SERVER_ERROR,"异常");
}
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
public JsonResult blockError(StockReduceCountDTO stockReduceCountDTO,
BlockException e){
return JsonResult.failed(ResponseCode.BAD_REQUEST,"服务器忙,请稍后再试");
}
//--------↓↓↓以下是添加代码↓↓↓--------
/*
这个方法是Sentinel注解中fallback属性指定的降级方法
:当前控制器方法运行发送异常时,Sentinel会运行下面的降级方法
降级方法中,可以不直接结束请求,而去运行一些代替代码或者补救措施
让用户获得最低限度的响应。
*/
public JsonResult fallbackError(StockReduceCountDTO stockReduceCountDTO){
return JsonResult.failed(ResponseCode.NOT_ACCEPTABLE,"因为运行异常,服务降级");
}
(2.2)测试【降级方法】
(1)
启动Nacos;
启动Seata 组件;
启动Sentinel:
————>【D:\soft\01Java\00soft\ruanjian\WeiFuWu\sentinel】中的【start-sentinel.bat】,
只测试csmall-stock 模块,因此只启动此模块即可,其他都可以不启动
访问酷鲨API文档【http://localhost:20003/doc.html】中点击【发送】
当我们点击完发送后,随机返回200和406错误;
随即返回:————>因为我们在上面的控制器类中添加了生成一个50%几率随机触发降级的流程
出现406: ————>因为我们在上面【stock模块中控制器类中自己添加的降级方法:
fallbackError中有此描述信息】
{
"state": 406,
"message": "因为运行异常,服务降级",
"data": null
}
(1 | 2)blockHandler和fallback的区别
两者都是
————> 不能正常调用资源返回值的顶替处理逻辑.
————> blockHander只能处理BlockException 流控限制之后的逻辑.
————> fallback处理的是资源调用异常的降级逻辑.
(3)作业:
《作业》:
为business模块控制器的buy方法添加Sentinel流控和降级的功能;
流控时输出"服务器忙",降级时输出"服务器降级"
1.pom文件
2.yml(port属性不能和stock模块的相同8722)
3.修改控制器代码(注解,流控和降级方法)
——————————————————————————————————————————————————————————————————————————————
——————————————————————————————————————————————————————————————————————————————
具体实现:
在《csmall-business 模块中的pom.xml中添加依赖》:
<!--谁测试流量谁限流,则谁就加此依赖:-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
——————————————————————————————————————————————————————————————————————————————
在csmall-business 模块:"application-dev.yml"配置文件:添加【sentinel配置】:
cloud:
nacos:
discovery:
# 定义nacos运行的浏览器路径
server-addr: localhost:8848
# ephemeral::设置当前项目启动时注册到nacos的类型,true(默认,不写也可以):临时实例 false:永久实例
ephemeral: true
sentinel:
transport:
dashboard: localhost:8080 # 配置Sentinel仪表台的位置
port: 8721 # 真正执行限流的端口也要设置一下,注意这个端口其他微服务项目不能相同
——————————————————————————————————————————————————————————————————————————————
在BusinessController中:添加:
/*
blockHandlerClass = "cn.tedu.dd.hh"
此【blockHandler 参数的值的方法】可以写在"blockHandlerClass"指定的包中位置
*/
@SentinelResource(value="buy",
blockHandler = "blockError",
fallback = "fallbackError")
public JsonResult buy(){
//调用业务逻辑层方法即可
businessService.buy();;
return JsonResult.ok("购买成功");
}
//限流的方法
public JsonResult blockError(BlockException e){
return JsonResult.failed(ResponseCode.BAD_REQUEST,"服务器忙");
}
//降级的方法
public JsonResult fallbackError(){
return JsonResult.failed(ResponseCode.INTERNAL_SERVER_ERROR,"服务器降级");
}
——————————————————————————————————————————————————————————————————————————————
依次启动模块:
启动Nacos;
启动Seata 组件;
启动Sentinel:
————>【D:\soft\01Java\00soft\ruanjian\WeiFuWu\sentinel】中的【start-sentinel.bat】,
stock
cart
order
business
——————————————————————————————————————————————————————————————————————————————
访问【http://localhost:20000/doc.html】,点击发送,
可以看到200 和 500随机出现:
{
"state": 200,
"message": "购买成功",
"data": null
}
{
"state": 500,
"message": "服务器降级",
"data": null
}
进行流控测试:
访问【http://localhost:8080/】,
给【business模块中的buy方法】添加流控
此时我们可以在【http://localhost:20000/doc.html】中看到:
————> 200购买成功
————> 400服务器忙
————> 500服务器降级
是随机出现的,
即:已经实现了【为business模块控制器的buy方法添加Sentinel流控和降级的功能;
流控时输出"服务器忙",降级时输出"服务器降级"】 的测试
【注意:限流和降级同时使用的话,要只写限流方法的参数】
如下图:
2.4.SpringCloud(Gateway 网关:统一入口)
(1)奈非框架简介
早期(2020年前)奈非提供的微服务组件和框架受到了很多开发者的欢迎
【这些框架】和【Spring Cloud Alibaba】的【对应关系】我们要知道:
————>Nacos 对应 Eureka 都是注册中心
————>Dubbo 对应 ribbon+feign都是实现微服务间调用
————>Sentinel 对应 Hystrix都是项目限流熔断降级组件
————>Gateway 对应 zuul都是项目的网关
————>Gateway不是阿里巴巴的而是Spring提供的 :《Spring Gateway》
(2)什么是网关?功能?
《"网关"》:就是网络中的统一入口
————>网是网络,
————>关是关口\关卡
————>关口\关卡的意思就是"统一入口"
《程序中的网关》:就是微服务项目提供的外界所有请求统一访问的微服务项目
————>因为提供了统一入口之后,方便对所有请求进行统一的检查和管理
《网关的主要功能》有:
————>将所有请求统一由经过网关
————>网关可以对这些请求进行检查
————>网关方便记录所有请求的日志
————>网关可以统一将所有请求路由到正确的模块\服务上
————>路由的近义词就是"分配"
(3)Spring Gateway简介
我们使用Spring Gateway作为当前项目的网关框架
Spring Gateway 是Spring自己编写的,也是SpringCloud中的组件
《SpringGateway官网》
————> https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
《网关项目git地址》
————> https://gitee.com/jtzhanghl/gateway-demo.git
(4)简单网关项目(gateway-demo)演示
网关是一个我们创建的项目,不是一个需要安装的软件
————>网关也是当前微服务项目的一员,也要注册到Nacos,所以保证Nacos的运行
————>运行之前,我们看一下网关演示项目已经存在的基本结构
————>beijing和shanghai是编写好的两个项目
————>gateway没有编写yml文件配置
————>要想实现网关的路由效果需要修改yml文件如下:
server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes: # gateway开始配置路由信息
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
# 如果java访问这个数字元素的方式:spring.cloud.gateway.routes[0].predicates[0]
# routes属性实际上是一个数组,yml文件中出现 "- ...."配置时表示当前配置时一个数组元素*/
- id: gateway-beijing # 这个配置指定这个路由的名称,这个名称和其他任何位置没有关联
# 只需要注意不能再和其他路由名称重复
# uri设置路由的目标
# lb是LoadBalance(负载均衡)的缩写,beijing是注册到nacos的服务名称
uri: lb://beijing
# 我们需要设置一个条件,当访问路径满足特定条件是,使用当前路由规则
predicates:
# predicates翻译为断言,所谓断言就是判断一个条件是否满足
# Path 是路径断言,意思是满足路径为XXX时使用这个路由
- Path=/bj/**
# http://localhost:9000/bj/show 会路由到 9001/bj/show
————————————————————————————————————————————————————————————————————————
《内置断言》:*/
————>断言就是判断一个条件,如果条件满足就执行某个操作
————>【predicates】就是断言的意思
————>我们前面章节使用的Path就是内置断言中的一种,指访问的路径是否满足条件
————>除了路径断言之外,还有很多内置断言常见的内置断言列表
——> after
——> before
——> between
——> cookie
——> header
——> host
——> method
——> path
——> query
——> remoteaddr
————————————————————————————————————————————————————————————————————————
《时间相关》:
————>after,before,between
————>在指定时间之后,之前或之间
————>判断是否满足时间条件,如果满足才允许访问
————>我们先使用下面代码获得当前包含时区的系统时间表:【ZonedDateTime.now()】
————————————————————————————————————————————————————————————————————————
《使用After设置必须在指定时间之后访问》
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- After=2022-06-24T15:30:30.999+08:00[Asia/Shanghai]
————————————————————————————————————————————————————————————————————————
《使用Before设置必须在指定时间之后访问》
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- Before=2022-06-24T15:34:00.999+08:00[Asia/Shanghai]
————————————————————————————————————————————————————————————————————————
《使用Between设置必须在指定时间之间访问》
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- Between=2022-06-24T15:34:00.999+08:00[Asia/Shanghai],2022-06-24T15:36:20.999+08:00[Asia/Shanghai]
————————————————————————————————————————————————————————————————————————
《要求指定参数》:
————> Query断言,要求必须包含指定的参数才能访问资源
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- Query=name
(5)内置过滤器
(0)
Gateway还提供的内置过滤器
不要和我们学习的filter混淆
内置过滤器允许我们在路由请求到目标资源的同时,对这个请求进行一些加工或处理
下面我们使用AddRequestParameter过滤器,想请求中添加参数:
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- Query=name
filters:
- AddRequestParameter=age,80
—————————————————————————————————————————————————————————————————————————————
(1)
《shanghai项目的控制器接收这个参数》
@GetMapping("/show")
public String show(String name,Integer age){
return "这里是上海!"+name+","+age;
}
—————————————————————————————————————————————————————————————————————————————
(2)
《重启网关和shanghai项目》
例如输入如下路径:【http://localhost:9000/sh/show?name=tom】
因为过滤器的存在,控制可以获取网关过滤器添加的参数值
————>其他内置过滤器和自定义过滤器的使用,可以查阅相关文档自己了解
如图:
(6)动态路由
如果项目微服务数量多
————>那么gateway项目yml文件配置也会越来越冗余,维护的工作量也会越来越大
————>所谓我们希望能够根据固定特征自动的路由到每个微服务模块
————>这个功能就是SpringGateway的动态路由功能
————>只需要在配置文件中配置开启动态路由功能即可
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 开启Spring Gateway的动态路由功能
# 规则是根据注册到Nacos的项目名称作为路径的前缀,就可以访问到指定项目了
enabled: true
开启之后访问项目的格式以beijing为例
:【 localhost:9000/beijing/bj/show 】
(6)Gateway和SpringMvc依赖冲突问题 和 解决:
《网关依赖》:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
《SpringMvc依赖》:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
这两个依赖在同一个项目中时,
————>默认情况下启动会报错
————>因为SpringMvc依赖中自带一个Tomcat服务器
————>而Gateway依赖中自带一个Netty服务器
————>因为在启动服务时这两个服务器都想启动,会因为争夺端口号和主动权而发生冲突
————>我们需要在网关模块(gateway)的【yml文件】中添加以下配置解决:
——>注意写在【spring】的下面
spring:
# 添加此配置解决springmvc和gateway自带依赖的服务器冲突问题:
main:
web-application-type: reactive
01.创建子模块gateway网关
(1)配置依赖
(0)
《创建子子模块gateway》
在【csmall】下创建【csmall-order-webapi】:
————>创建Module时所选属性:
————>Name:gateway
————>Group:cn.tedu
————>Artifact:csmall-order-webapi
————>Package name:cn.tedu.gateway 注意【不】加"."点
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
——————————————————————————————————————————————————————————————————————————————
(1)
《修改子模块 gateway 的pom.xml文件》:
————>在gateway中的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<!--SpringMVC依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 网负载均衡支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Spring Gateway 网关依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--聚合网关 knife4j-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
(2)配置"yml"配置文件
(2)
《添加".yml"配置文件》:
在csmall-order-webapi:
在resources目录:
————>删除原有的测试文件夹:test
————>删除原有的application.properties配置文件
————>创建新的application.yml配置文件,↓代码如下:
《application.yml》:
spring:
application:
name: gateway-server
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 开启网关动态路由
enabled: true
main:
web-application-type: reactive
server:
port: 10000
(3)knife4j(API在线文档)网关配置
我们希望配置网关之后,在使用knife4j测试时
就不来回切换端口号了
我们需要配置Knife4j才能实现
如下创建三个固定写法的类:
————>根包cn.tedu.gateway下创建:config包 下创建:SwaggerProvider
————>根包cn.tedu.gateway下创建:controller包 下创建:SwaggerController类
————>根包cn.tedu.gateway下创建:filter包 下创建:SwaggerHeaderFilter类
————————————————————————————————————————————————————————————————————————————
(1)
《————>根包cn.tedu.gateway下创建:config包 下创建:SwaggerProvider》
package cn.tedu.gateway.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
public class SwaggerProvider implements SwaggerResourcesProvider {
/**
* 接口地址
*/
public static final String API_URI = "/v2/api-docs";
/**
* 路由加载器
*/
@Autowired
private RouteLocator routeLocator;
/**
* 网关应用名称
*/
@Value("${spring.application.name}")
private String applicationName;
@Override
public List<SwaggerResource> get() {
//接口资源列表
List<SwaggerResource> resources = new ArrayList<>();
//服务名称列表
List<String> routeHosts = new ArrayList<>();
// 获取所有可用的应用名称
routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
.filter(route -> !applicationName.equals(route.getUri().getHost()))
.subscribe(route -> routeHosts.add(route.getUri().getHost()));
// 去重,多负载服务只添加一次
Set<String> existsServer = new HashSet<>();
routeHosts.forEach(host -> {
// 拼接url
String url = "/" + host + API_URI;
//不存在则添加
if (!existsServer.contains(url)) {
existsServer.add(url);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setUrl(url);
swaggerResource.setName(host);
resources.add(swaggerResource);
}
});
return resources;
}
}
————————————————————————————————————————————————————————————————————————————
(2)
————>根包cn.tedu.gateway下创建:controller包 下创建:SwaggerController类
package cn.tedu.gateway.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
import java.util.Optional;
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerController {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerController(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
————————————————————————————————————————————————————————————————————————————
(3)
————>根包cn.tedu.gateway下创建:filter包 下创建:SwaggerHeaderFilter类
package cn.tedu.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
private static final String URI = "/v2/api-docs";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path,URI )) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}
(4)测试gateway网关的功能
1.启动Nacos\Seata\Sentinel
2.按顺序启动:cart\stock\order\business 四个模块
3.在保证一般访问正常的情况下——>再启动gateway 模块
4.访问nacos网站:【http://localhost:8848/nacos/】来观察各模块是否已经注册到nacos中
5.可以通过下面路径访问之前的各个模块的业务
————>http://localhost:10000/nacos-stock/doc.html
————>http://localhost:10000/nacos-cart/doc.html
————>http://localhost:10000/nacos-order/doc.html
————>http://localhost:10000/nacos-business/doc.html
5.测试以上端口号为1000的各网址,
可以发现只用【gateway网关模块中"yml"中配置的一个端口号】 和【再通过修改上面测试网址中的各模块的不同】
来访问各自的【酷鲨在线API文档】,
————>就可以进入【各模块】的【酷鲨在线API文档】点击发送获得不同的返回信息
5.注意:如果不使用网关一切正常,但是启动网关访问失败的话,就是gateway项目配置问题
3、Elasticsearch:全文搜索引擎
(1)什么是Elasticsearch
————> elastic:富有弹性的
————> search:搜索
在计算机开发界简称:ES
————> 这个软件不是SpringCloud的组件,甚至其他语言都可以使用它
————> 是一个java开发的软件,所以启动需要java环境变量
————> 功能是:从大量数据中根据指定的关键字搜索出匹配的结果
————> 这样的软件有一个名称:【全文搜索引擎】
————> 使用它的方式是:访问它提供的控制器方法,它开发了多种控制器方法
访问不同方法实现对数据的增删改查
————> 【ES也是将数据保存在硬盘上的】
————> 【常见面试题ES的实现结构】
————> java有一套名为Lucene的API
————> 是搜索引擎的核心支持,
Elasticsearch在Lucene的基础上开发出了一个功能全面的开箱即用的全文搜索引擎
————> 市面上ES的竞品有:
————> Solr/MongoDB
(2)为什么使用Elasticsearch
因为我们之前学习的所有【关系型数据库:】都有一个严重的[性能缺陷]:
————> mysql\mariaDB\oracle\DB2等
————> 【就是前模糊的模糊查询不能使用索引】
:select * from spu where spu_name like '%鼠标%'
————> 测试证明:【一张千万级别的数据库表进行模糊查询需要20秒以上】
————> 当今需求"三高"的需求下,不能接受这样的性能
我们【使用ES来优化后同样的查询我们能将效率提高100倍】
————> 将大型的查询也能控制在毫秒级别
(3)Elasticsearch查询原理
如果不使用ES,而让数据库查询,没有[索引]加持的模糊查询就是全表搜索-----性能差
但是[Elasticsearch]可以 利用添加数据库,完成对数据的分词-倒排索引,形成[索引库]
————> 在查询时直接查询索引库,获得符合查询条件的数据信息
例:如下图:
(4)关于数据库的索引
所谓索引其实就是数据库中数据的目录
目的是能够提高查询的效率
数据库索引分类
- 聚集索引
:聚集索引就是数据库保存数据的物理顺序,一般都是id,所以按物理顺序查询也就是按id查询效率非常高
- 非聚集索引
:在聚集索引的基础上,如果再定义其他索引,就是非聚集索引了
如果数据表中有一个姓名列,我们为姓名列创建索引
例如有"张三"这个姓名,添加索引后,查询的话效率会明显提升
但是如果不创建索引,去查询张三,就只能逐行检索姓名列是否为张三,查询效率低
————————————————————————————————————————————————————————————————————————————————
常见面试题:索引的使用规则和注意事项
(1)索引会占用数据库空间
(2)对数据进行增删改操作,可能会引起索引的更新,效率会低
(3)操作数据库时先添加数据,再创建索引
(4)不要对数据样本少的列添加索引
(5)每次查询从数据库中查询结果越多,索引的效果越低
(6)使用where字句查询时,将具有索引的列放在第一个条件
经过我们对索引的简单了解,我们需要知道索引的基本概念和使用
所有关系型数据库都有一个缺陷,就是模糊查询时(查询条件前模糊),是不能利用索引进行查询的
一定会引起全表搜索,查询效率非常低
(5)Elasticsearch的启动
官方下载链接:【https://www.elastic.co/cn/downloads/past-releases#elasticsearch】
我们练习使用7.6.2版本:压缩包280M左右,复制到没有中文,没有空格的目录下解压
《启动方式》:
双击【bin目录下的elasticsearch.bat】运行
运行之后可能看到下面界面:
上面这个界面不能关闭,一旦关闭ES就停止了
我们启动ES双击这个bat文件即可,当然也可以设置Idea的shell script
验证ES是否在运行
浏览器输入地址:【localhost:9200】看到如下内容即可
:如图:
mac系统启动
tar -xvf elasticsearch-7.6.2-darwin-x86_64.tar.gz
cd elasticsearch-7.6.2/bin
./elasticsearch
——————————————————————————————————————————————————————————————————————————————————————————
linux系统启动:
tar -xvf elasticsearch-7.6.2-linux-x86_64.tar.gz
cd elasticsearch-7.6.2/bin
./elasticsearch
01.ES基本使用:创建子模块search
(1)配置依赖
(0)
《创建子模块search》
在【csmall下】创建【search】:
————>创建Module时所选属性:
————>Name:search
————>Group:cn.tedu
————>Artifact:search
————>Package name:cn.tedu.search
————>Java:8
————>点击Next
————>什么也不勾选,点击Finish即可
——————————————————————————————————————————————————————————————————————————————
(1)
《修改子模块search 的pom.xml文件》
————>也要父子相认:在<parent>节点中修改
————>只留下【spring-boot-starter 和 spring-boot-starter-test】依赖,其余删除
————>编写父项目csmall的pom.xml配置:
添加<modules>节点中的子模块【<module>search</module>】节点
————>修改子模块search 的pom.xml文件,完整pom.xml如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>search</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>search</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
(2.1)创建【es.http】文件(可以发送各种请求的文件)
如上图:文件类型叫【HTTP Request】文件
————>行业中有人将它称之为【http client(http客户端)】
——————————————————————————————————————————————————————————————————————————————————————————
(1)
《创建完毕之后,我们向ES发送一个最简单的请求》:
GET http://localhost:9200
获得的结果和之前浏览器响应结果一致,表示当前http client文件正常运行
如图:
(2.2)es.http文件的:Es分词效果
《Es分词效果》:
### 注释和分隔符,每次编写请求前,都要先编写3个"#"号
GET http://localhost:9200
### Es分词测试
POST http://localhost:9200/_analyze
Content-Type: application/json
{
"text": "我是牛牛",
"analyzer": "standard"
}
例:
《当指定"text"中的内容为英文时,下面控制台会单独以 单词 分隔》
《当指定"text"中的内容为中文时,下面控制台会单独以 每个字 分隔》
我们代码中编写的"analyzer": "standard"是默认分词器
如果不写出这行,也时默认这个分词器得
这个分词器只能对英文等西文字符(有空格的),进行分词
但是中文分词不能按空格分
下面我们要安装中文分词插件,实现中文分词效果
(3)安装中文分词插件【ik】:实现中文分词效果
我们使用开源的分词词库IK实现中文分词:
————> 安装插件之后要重启ES才能生效
————> 关闭ES窗口之后再启动ES即可
————> ES启动之后,将中文分词器插件设置完成,再运行分词
《将 "analyzer"后的参数换为: "ik_smarta"》
{
"text": "我是牛牛",
"analyzer": "ik_smarta"
}
我们安装了【中文分词插件】后,
再次运行分词测试,应该看到正常的中文分词效果
发现相比上一步(2.2)中已经实现了【中文分词效果】。
(3.1)ik分词插件使用:ik_smart 和 ik_max_word 的区别
我们安装的ik实际上不只一个分词器
————>实际上[除了 ik_smart之外 还有 ik_max_word]:
### ES分词测试 analyze(分析)
POST http://localhost:9200/_analyze
Content-Type: application/json
{
"text": "北京顺利举办了冬季奥林匹克运动会",
"analyzer": "ik_smart"
}
### ES分词测试 analyze(分析)
POST http://localhost:9200/_analyze
Content-Type: application/json
{
"text": "北京顺利举办了冬季奥林匹克运动会",
"analyzer": "ik_max_word"
}
————————————————————————————————————————————————————————————————————————————————
上面的运行会有不同的分词效果
《ik_smart 和 ik_max_word 的区别》:
《ik_smart》
优点:特征是粗略快速的将文字进行分词,占用空间小,查询速度快
缺点:分词的颗粒度大,可能跳过一些分词,导致查询结果不全面
《ik_max_word》
优点:特征是详细的文字片段进行分词,查询时查全率高,不容易遗漏数据
缺点:因为分词太过详细,导致有一些无用分词,占用空间较大,查询速度慢
(4)使用ES操作数据
我们先了解一下ES保存数据的结构:
《ES保存数据的结构》:
- ES启动后,可以创建多个index(索引),index相当于数据库中表的概念
- 一个index可以创建保存多个document(文档),一个document相当于表中的一行数据
- 一个document中可以有多个属性和对应的值,相当于一行数据中字段和字段的值
————————————————————————————————————————————————————————————————————————————————
项目node文件夹下共享了ES文档,命令都在里面,可以测试:
《ES文档》命令如下:
ES文档
### 创建 index
PUT http://localhost:9200/questions
--------------------------------------------------------------------------------
### 删除一个Index
DELETE http://localhost:9200/questions
--------------------------------------------------------------------------------
### 设置index中的文档属性采用ik分词
POST http://localhost:9200/questions/_mapping
Content-Type: application/json
{
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
},
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}
--------------------------------------------------------------------------------
### questions 中添加文档
POST http://localhost:9200/questions/_create/1
Content-Type: application/json
{
"id":1,
"title":"Java基本数据类型有哪些",
"content":"面时候为啥要问基本类型这么简单问题呀,我们要如何回答呢?"
}
--------------------------------------------------------------------------------
### questions 中添加文档
POST http://localhost:9200/questions/_create/2
Content-Type: application/json
{
"id":2,
"title":"int类型的范围",
"content":"为啥要了解int类型的范围呢?"
}
--------------------------------------------------------------------------------
### questions 中添加文档
POST http://localhost:9200/questions/_create/3
Content-Type: application/json
{
"id":3,
"title":"常用集合类有哪些",
"content":"为啥企业经常问集合呀?该如何回复呢"
}
--------------------------------------------------------------------------------
### questions 中添加文档
POST http://localhost:9200/questions/_create/4
Content-Type: application/json
{
"id":4,
"title":"线程的run方法和start方法有啥区别",
"content":"run方法可以执行线程的计算过程, start也可以执行线程的计算过程,用途一样么?"
}
--------------------------------------------------------------------------------
### 更新questions索引中的文档
POST http://localhost:9200/questions/_doc/4/_update
Content-Type: application/json
{
"doc": {
"title": "Java线程的run方法和start方法有啥区别"
}
}
--------------------------------------------------------------------------------
### 删除questions中的一个文档
DELETE http://localhost:9200/questions/_doc/2
--------------------------------------------------------------------------------
### 查询数据
GET http://localhost:9200/questions/_doc/4
--------------------------------------------------------------------------------
### 收索 ES
POST http://localhost:9200/questions/_search
Content-Type: application/json
{
"query": { "match": {"title": "类型" } }
}
--------------------------------------------------------------------------------
### 多字段搜索
POST http://localhost:9200/questions/_search
Content-Type: application/json
{
"query": {
"bool": {
"should": [
{ "match": { "title": "java类型" }},
{ "match": { "content": "java类型"}}
]
}
}
}
02.Spring Data(是SpringBoot来操作 Elasticsearch的)
原生状态下,我们使用JDBC连接数据库,因为代码过于繁琐,所以改为使用Mybatis框架
————>在ES的原生状态下,我们java代码需要使用socket访问ES,
但是也是过于繁琐,我们可以使用SpringData框架简化
————>Spring Data是Spring提供的一套连接各种第三方数据源的框架集
————>我们需要使用的是其中连接ES的Spring Data Elasticseatrch
————>官方网站:【https://spring.io/projects/spring-data】如上图↑
————>官网中列出了它可以操作的数据源列表
————>每个列表中都包含一些使用的介绍
————>要想实现Spring Boot操作ES,需要添加依赖后,再按照要求编写代码即可
(1)添加Spring Data依赖 和 配置pom文件
(1)
我们要想实现Spring Boot操作,需要ES添加依赖后,再按照要求编写代码即可
《在子模块search 的pom.xml中添加依赖》:完整的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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>csmall</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.tedu</groupId>
<artifactId>search</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>search</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--Spring Data Elasticsearch 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
</project>
————————————————————————————————————————————————————————————————————————————————————————————————
(2)
《application.properties添加配置》:
# 设置连接ES的ip地址和端口号
spring.elasticsearch.rest.uris=http://localhost:9200
# 为了观察运行状态信息,将日志输出门槛设置为debug
logging.level.cn.tedu.search=debug
logging.level.org.elasticsearch.client.RestClient=debug
(2)创建一个操作ES的数据类
和数据库一样:
————> 我们操作ES时也需要一个类似实体类的数据类,作为操作ES的数据载体
————> search项目创建entity包
————> 在包中创建Item(商品)类
package cn.tedu.search.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.Serializable;
/**
* Item 实体类
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
/*
SpringData要求我们在"实体类"中使用特定注解标记
@Document注解标记当前类和ES关联
indexName:指定索引名称,我们这里叫items,当操作这个索引时,如果索引不存在,会自动创建
*/
@Document(indexName = "items")
public class Item implements Serializable {
//SpringData标记这个字段为当前类主键
@Id
private Long id;
//SpringData使用@Field标记文档中属性的类型和各种特征
@Field(type = FieldType.Text,
analyzer = "ik_max_word",
searchAnalyzer = "ik_max_word")
private String title;//商品名称
@Field(type = FieldType.Keyword)
private String category;//分类
@Field(type = FieldType.Keyword)
private String brand;//品牌
@Field(type = FieldType.Double)
private Double price;//价格
/*
图片地址不会称为搜索条件,所以设置index=false
效果是imgPath字段不会生成索引库,节省空间
*/
@Field(type = FieldType.Keyword,index = false)
private String imgPath;//图片地址
}
(3)创建操作ES的持久层(repository)
我们使用 【SpringData连接操作ES】
————> 需要使用SpringData框架对持久层的命名规则
————> 创建repository包,在包中创建接口ItemRepository
代码如下:
// Spring 家族下持久层名称都叫repository
@Repository
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
// 当前接口继承ElasticsearchRepository父接口后
// 会自动在类中生成基本的增删改查方法,直接可以使用
// 它自动识别或自动生成的规则,是我们定义的两个泛型ElasticsearchRepository<[实体类名],[主键类型]>
}
(4)创建测试类repository.SearchApplicationTests :完成单增/获取/批量增/全部查功能
package cn.tedu.search.repository;
import cn.tedu.search.entity.Item;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@SpringBootTest
public class SearchApplicationTests {
// 注入SpringData操作Es的持久层对象
@Autowired
private ItemRepository itemRepository;
/**
* 测试ES的单个增加
*/
@Test
void addOne() {
// 实例化Item对象,赋值并新增到ES
Item item=new Item()
.setId(1L)
.setTitle("罗技激光无线游戏鼠标")
.setCategory("鼠标")
.setBrand("罗技")
.setPrice(128.0)
.setImgPath("/1.jpg");
// 利用自动生成的方法将item新增到ES,索引不存在会自动创建
itemRepository.save(item);
System.out.println("ok");
/*
控制台输出:
ok
*/
}
/**
* 测试ES按id基本查询
*/
@Test
void getOne(){
//SpringData框架自带的按id查询的方法:
//Optional是一个类似包装类的概念,查询的结果封装到了这个类型中
Optional<Item> optional = itemRepository.findById(1L);
//需要使用查询内容时使用optional.get()即可
System.out.println(optional.get());
/*
控制台输出:
Item(id=1, title=罗技激光无线游戏鼠标, category=鼠标, brand=罗技, price=128.0, imgPath=/1.jpg)
*/
}
/**
* 测试ES批量增数据
*/
@Test
void addList(){
//实例化一个List集合
List<Item> list = new ArrayList<>();
//将要新增的Item对象保存到这个list中
list.add(new Item(2L,"罗技激光有线办公鼠标","鼠标","罗技",89.0,"/2.jpg"));
list.add(new Item(3L,"雷蛇机械无线游戏键盘","键盘","雷蛇",299.0,"/3.jpg"));
list.add(new Item(4L,"微软有线静音办公鼠标","鼠标","微软",208.0,"/4.jpg"));
list.add(new Item(5L,"罗技有线机械背光键盘","键盘","罗技",266.0,"/5.jpg"));
//下面使用SpringData提供的方法执行批量新增
itemRepository.saveAll(list);
System.out.println("ok");
/*
控制台输出:
ok
*/
}
/**
* 测试ES全部查询
*/
@Test
void getAll(){
//利用SpringData的方法从ES中查询所有数据
Iterable<Item> items = itemRepository.findAll();
//for(数据类型 元素 : 对象名){}
// for(Item item : items){
// System.out.println(item);
// }
//lambda表达式和上面的for循环作用相同:
items.forEach(item -> System.out.println(item));
/*
控制台输出:
Item(id=1, title=罗技激光无线游戏鼠标, category=鼠标, brand=罗技, price=128.0, imgPath=/1.jpg)
Item(id=2, title=罗技激光有线办公鼠标, category=鼠标, brand=罗技, price=89.0, imgPath=/2.jpg)
Item(id=3, title=雷蛇机械无线游戏键盘, category=键盘, brand=雷蛇, price=299.0, imgPath=/3.jpg)
Item(id=4, title=微软有线静音办公鼠标, category=鼠标, brand=微软, price=208.0, imgPath=/4.jpg)
Item(id=5, title=罗技有线机械背光键盘, category=键盘, brand=罗技, price=266.0, imgPath=/5.jpg)
*/
}
}
03.SpringData自定义查询
SpringData框架提供基本增删改查方法
但是如果有具体的针对性的查询逻辑,一定还是需要我们自己编写代码
例如实现类似数据库中的模糊查询
(1)单条件查询
- (先在接口ItemRepository里写抽象方法,再从测试类SearchApplicationTests写对应的方法测试)
(1)
我们的查询需求是查询title属性中包含"游戏"这个分词的商品信息
————>参考模糊查询代码:【select * from item where title like '%游戏%'】
————>我们使用ES查询,本质上运行的就是我们在es.http文档中编写的查询语句
————>但是SpringData框架下,编写查询语句更加简单
————>我们《在ItemRepository接口中》添加如下代码:
package cn.tedu.search.repository;
import cn.tedu.search.entity.Item;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
//Spring 家族下的持久层名称都叫:repository
//当前接口继承 ElasticsearchRepository父接口后,会自动在类中生成基本的增删改查方法,
//直接可以使用它自动识别或自动生成的规则,是我们定义的两个泛型 ElasticsearchRepository<[实体类名],[主键类型]>
/**
* SpringSata自定义查询之:单条件查询
* 遵循SpingData框架规定的格式的前提下,编写方法会自动生成查询逻辑
* query:表示当前执行的是一个查询功能,类似sql中的select
* Item/Items:表示查询结果的实体类,带s的返回集合
* By:标识开始设置条件,类似sql的where
* Title:要查询的字段名称
* Matches:是要执行的查询操作,这里是分词查询,类似sql的like
*/
Iterable<Item> queryItemsByTitleMatches(String title);
}
————————————————————————————————————————————————————————————————————————————————————————————————
(2)
《在测试类SearchApplicationTests中进行测试》:
/**
* 单条件自定义查询
*/
@Test
void queryOne(){
//查询ES中title字段包含"游戏"分词的数据
Iterable<Item> items = itemRepository.queryItemsByTitleMatches("罗技游戏");
//for(数据类型 元素 : 对象名){}
// for(Item item : items){
// System.out.println(item);
// }
//lambda表达式和上面的for循环作用相同:
items.forEach(item -> System.out.println(item));
/*
控制台输出:
Item(id=1, title=罗技激光无线游戏鼠标, category=鼠标, brand=罗技, price=128.0, imgPath=/1.jpg)
Item(id=3, title=雷蛇机械无线游戏键盘, category=键盘, brand=雷蛇, price=299.0, imgPath=/3.jpg)
*/
}
上面代码运行时底层运行的查询语句为:
### 单条件搜索
POST http://localhost:9200/items/_search
Content-Type: application/json
{
"query": {"match": { "title": "游戏" }}
}
(2)多条件查询:And 和 Or
- (先在接口ItemRepository里写抽象方法,再从测试类SearchApplicationTests写对应的方法测试)
————> 【 当查询条件关系为And时, 查询语句关键字为must 】
————> 【 当查询条件关系为Or 时, 查询语句关键字为should 】
(0)
在相对复杂的查询逻辑下
————> 经常使用多个条件来定位查询需要的数据
————> 这样就需要逻辑运算符"and"/"or"
————————————————————————————————————————————————————————————————————————————————————————————————
(1)
《And查询》:
————> 《在ItemRepository接口中》添加多条件【And】的查询方法:
/**
* SpringSata自定义查询之:多条件查询
* 多条件查询:两个或多个条件之间直接编写And 或 Or表示查询逻辑
*/
Iterable<Item> queryItemsByTitleMatchesAndBrandMatches(String title,String brand);
------------------------------------------------------------------------------------------------
上面的查询添加了品牌作为条件
————> 逻辑关系是 or(或)
————> 《在SearchApplicationTests测试类中》添加测试多条件【And】的查询方法:
/**
* 多条件查询:And
*/
@Test
void queryTwo(){
Iterable<Item> items = itemRepository.queryItemsByTitleMatchesAndBrandMatches("游戏","雷蛇");
// for(Item item : items){
// System.out.println(item);
// }
//lambda表达式和上面的for循环作用相同:
items.forEach(item -> System.out.println("当查询条件关系为And时:"+item));
/*
控制台输出:
当查询条件关系为And时:Item(id=3, title=雷蛇机械无线游戏键盘, category=键盘, brand=雷蛇, price=299.0, imgPath=/3.jpg)
*/
}
上面代码运行时底层运行的查询语句为:
### 多字段搜索
POST http://localhost:9200/items/_search
Content-Type: application/json
{
"query": {
"bool": {
"must": [
{ "match": { "title": "游戏"}},
{ "match": { "brand": "雷蛇"}}
]
}
}
}
————————————————————————————————————————————————————————————————————————————————————————————————
————————————————————————————————————————————————————————————————————————————————————————————————
(2)
《Or 查询》:
————> 《在ItemRepository接口中》添加多条件【Or】的查询方法:
/**
* SpringSata自定义查询之:多条件查询
* 多条件查询:两个或多个条件之间直接编写And 或 Or表示查询逻辑
*/
Iterable<Item> queryItemsByTitleMatchesOrBrandMatches(String title,String brand);
------------------------------------------------------------------------------------------------
上面的查询添加了品牌作为条件
————> 逻辑关系是 And(与)
————> 《在SearchApplicationTests测试类中》添加测试多条件【Or】的查询方法:
/**
* 多条件查询:Or
*/
@Test
void queryTwo1(){
Iterable<Item> items = itemRepository.queryItemsByTitleMatchesOrBrandMatches("游戏","雷蛇");
// for(Item item : items){
// System.out.println(item);
// }
//lambda表达式和上面的for循环作用相同:
items.forEach(item -> System.out.println("当查询条件关系为Or时:"+item));
/*
控制台输出:
当查询条件关系为Or时:Item(id=3, title=雷蛇机械无线游戏键盘, category=键盘, brand=雷蛇, price=299.0, imgPath=/3.jpg)
当查询条件关系为Or时:Item(id=1, title=罗技激光无线游戏鼠标, category=鼠标, brand=罗技, price=128.0, imgPath=/1.jpg)
*/
}
(3)排序查询
- (先在接口ItemRepository里写抽象方法,再从测试类SearchApplicationTests写对应的方法测试)
上面我们完成了单条件查询和多条件查询
但是条件的变化只是查询的需求之一
我们还需要像排序等需求的查询
如果实施排序需求:
————>就在Repository接口中添加方法如下
————————————————————————————————————————————————————————————————————————————————
(1)
《在ItemRepository接口中添加方法如下》:
/**
* 排序查询
* 默认情况下,ES查询结果按score排序,如果想按其他的规则排序可以加OrderBy
* 和数据库一样,默认升序排序,Desc结尾会降序
*/
Iterable<Item> queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc(
String title,String brand);
————————————————————————————————————————————————————————————————————————————————
(2)
《测试类 SearchApplicationTests 》代码如下:
// 排序查询
@Test
void queryOrder(){
Iterable<Item> items=itemRepository
.queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc("游戏","罗技");
items.forEach(item -> System.out.println(item));
}
上述代码的底层代码逻辑如下:
### 多字段搜索
POST http://localhost:9200/items/_search
Content-Type: application/json
{
"query": {
"bool": {
"should": [
{ "match": { "title": "游戏"}},
{ "match": { "brand": "罗技"}}
]
}
},"sort":[{"price":"desc"}]
}
(4)分页查询
- (先在接口ItemRepository里写抽象方法,再从测试类SearchApplicationTests写对应的方法测试)
SpringData框架支持分页查询
只需要修改参数和返回值就能实现自动分页的效果
————>在ItemRepository接口中添加方法如下
————————————————————————————————————————————————————————————————————————————————
(1)
《在ItemRepository接口中添加方法如下》:
/**
* 分页查询
* 当查询数据较多时,我们可以利用SpringData的分页功能,按用户要求的代码查询需要的数据
* 返回值修改为Page类型;这个类型对象除了包含Iterable能够包含的集合信息之外,还包含分页信息
*/
Page<Item> queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc(
String title, String brand, Pageable pageable);
// select * from xxx limit [(页码-1)*6],6
————————————————————————————————————————————————————————————————————————————————
(2)
《测试类 SearchApplicationTests 》代码如下:
/**
* 分页查询
*/
@Test
void queryPage(){
int pageNum = 1;//页码
int pageSize = 2;//每页条数
Page<Item> page = itemRepository
.queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc(
"游戏","罗技", PageRequest.of(pageNum,pageSize));
page.forEach(item -> System.out.println(item));
//page对象中还包含了一些基本的分页信息:
System.out.println("总页数:"+page.getTotalPages());
System.out.println("当前页:"+page.getNumber());
System.out.println("每页条数:"+page.getSize());
System.out.println("当前页是不是首页:"+page.isFirst());
System.out.println("当前页是不是末页"+page.isLast());
/*
控制台输出:
Item(id=1, title=罗技激光无线游戏鼠标, category=鼠标, brand=罗技, price=128.0, imgPath=/1.jpg)
Item(id=2, title=罗技激光有线办公鼠标, category=鼠标, brand=罗技, price=89.0, imgPath=/2.jpg)
总页数:2
当前页:1
每页条数:2
当前页是不是首页:false
当前页是不是末页true
*/
}