1.续开发购物车功能
1.1续新增sku到购物车
1.1.1开发业务逻辑层
上次课完成了持久层的代码
下面开发业务逻辑层
创建OmsCartServiceImpl类实现IOmsCartService接口
实现其中方法,先实现新增购物车的方法即可
在编写业务逻辑层具体代码前,先在该类中编写一个从SpringSecurity上下文中获取用户信息的方法
@Service
@Slf4j
public class OmsCartServiceImpl implements IOmsCartService {
@Autowired
private OmsCartMapper omsCartMapper;
@Override
public void addCart(CartAddDTO cartDTO) {
// 获得当前登录用户id
Long userId=getUserId();
// 先检查要添加到购物车中的sku是否已经在当前用户的购物车中了
OmsCart omsCart=omsCartMapper.selectExistsCart(userId,cartDTO.getSkuId());
// 判断omsCart是否为null
if(omsCart==null){
// 如果oms为null,表示当前用户这个sku没有新增到购物车中,执行新增操作
// 执行新增,新增方法的参数是omsCart,所以要实例化一个omsCart对象
OmsCart newOmsCart=new OmsCart();
// 将CartAddDTO对象的同名属性赋值到newOmsCart中
BeanUtils.copyProperties(cartDTO,newOmsCart);
// CartAddDTO对象是没有userId的,需要单独赋值
newOmsCart.setUserId(userId);
// 执行新增
omsCartMapper.saveCart(newOmsCart);
}else{
// omsCart不是null,表示当前用户选择的sku已经在购物车中
// 那么我们需要做的就是修改购物车中sku的数量
// 因为我们编写的修改数量的方法是直接赋值给数据库,
// 所以要赋的值需要在java代码中计算好
omsCart.setQuantity(omsCart.getQuantity()+cartDTO.getQuantity());
// 调用修改购物车数量的方法
omsCartMapper.updateQuantityById(omsCart);
}
}
@Override
public JsonPage<CartStandardVO> listCarts(Integer page, Integer pageSize) {
return null;
}
@Override
public void removeCart(Long[] ids) {
}
@Override
public void removeAllCarts() {
}
@Override
public void removeUserCarts(OmsCart omsCart) {
}
@Override
public void updateQuantity(CartUpdateDTO cartUpdateDTO) {
}
// 业务逻辑层中获得用户信息的方法
// 目标是从SpringSecurity上下文中获取由JWT解析而来的对象
public CsmallAuthenticationInfo getUserInfo(){
// 声明SpringSecurity上下文对象
UsernamePasswordAuthenticationToken authenticationToken=
(UsernamePasswordAuthenticationToken)
SecurityContextHolder.getContext().getAuthentication();
// 为了保险起见,判断一下这个对象是否为空
if(authenticationToken==null){
throw new CoolSharkServiceException(ResponseCode.UNAUTHORIZED,"没有登录");
}
// 从上下文中获取登录用户的信息
// 这个信息是由JWT解析获得的
CsmallAuthenticationInfo csmallAuthenticationInfo=
(CsmallAuthenticationInfo) authenticationToken.getCredentials();
// 返回登录信息
return csmallAuthenticationInfo;
}
// 业务逻辑层大多数方法都是只需要用户的ID,所以专门编写一个方法返回id
public Long getUserId(){
return getUserInfo().getId();
}
}
1.1.2开发控制层
创建OmsCartController
@RestController
@RequestMapping("/oms/cart")
@Api(tags = "购物车管理模块")
public class OmsCartController {
@Autowired
private IOmsCartService omsCartService;
@PostMapping("/add")
@ApiOperation("新增购物车信息")
// 判断过滤器中对JWT解析出来,并保存在SpringSecurity上下文中的用户
// 是否具备指定的权限
// 如果具备,表示当前用户已经登录
// sso服务器用户登录时,代码中已经写好会保存一个ROLE_user的权限
@PreAuthorize("hasAuthority('ROLE_user')")
// @Validated注解是激活SpringValidation框架的验证功能
// 如果cartAddDTO参数有不符合的值,会抛出BindException异常
// 这个异常会在统一异常处理类中处理
public JsonResult addCart(@Validated CartAddDTO cartAddDTO){
omsCartService.addCart(cartAddDTO);
return JsonResult.ok("新增sku到购物车完成!");
}
}
先注意sso模块application-test.yml的地址和端口号和密码(密码有两个)
也要注意order模块application-test.yml的地址和端口号和密码
都保证正确的前提下
先启动Nacos/Seata
启动 passport/order
sso:10002
order:10005
先访问10002前台用户登录获得JWT 用户名jackson密码123456
先登录看到JWT 然后复制JWT
转到10005 order模块 文档管理->全局参数设置->添加参数
参数名:Authorization
参数值:Bearer [粘贴JWT]
然后刷新当前10005的界面
然后进行发送请求即可成功!
如果测试结果中包含一个错误
错误信息里有"xml/bind"的错误信息
order/sso模块需要添加下面依赖即可解决
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
1.2开发查询购物车功能
1.2.1开发持久层
OmsCartMapper添加方法如下
// 根据用户id查询购物车中sku信息
List<CartStandardVO> selectCartByUserId(Long userId);
OmsCartMapper.xml添加对应内容
<!-- 根据用户id查询购物车中sku信息 -->
<!--
resultType指定一个类做返回值,编写这个类的全类名
自动映射列名和属性名
user_id -> userId
sku_id -> skuId
price -> price
-->
<select id="selectCartByUserId"
resultType="cn.tedu.mall.pojo.order.vo.CartStandardVO">
select
<include refid="SimpleQueryFields" />
from
oms_cart
where
user_id=#{id}
</select>
1.2.2开发业务逻辑层
OmsCartServiceImpl业务实现
返回值支持分页结果,按分页条件查询
// 根据用户id分页查询当前用户购物车sku商品列表
@Override
public JsonPage<CartStandardVO> listCarts(Integer page, Integer pageSize) {
// 从SpringSecurity上下文中获得用户id
Long userId=getUserId();
// PageHelper框架设置分页条件
PageHelper.startPage(page,pageSize);
// 执行查询,会自动在查询sql语句末尾,添加limit关键字
List<CartStandardVO> list=omsCartMapper.selectCartByUserId(userId);
// 将分页结果返回,实例化PageInfo对象,转换为JsonPage
return JsonPage.restPage(new PageInfo<>(list));
}
1.2.3开发控制层
下面开发控制层,调用方法进行测试
OmsCartController添加方法如下
// 根据用户Id分页查询购物车sku列表
@GetMapping("/list")
@ApiOperation("根据用户Id分页查询购物车sku列表")
@ApiImplicitParams({
@ApiImplicitParam(value = "页码",name="page",dataType = "int"),
@ApiImplicitParam(value = "每页条数",name="pageSize",dataType = "int")
})
@PreAuthorize("hasAuthority('ROLE_user')")
public JsonResult<JsonPage<CartStandardVO>> listCartByPage(
// 在控制器参数位置添加@RequestParam注解
// 可以设置当前参数为空时的默认值,WebConsts.DEFAULT_PAGE是事先定义好的常量
@RequestParam(required = false,defaultValue = WebConsts.DEFAULT_PAGE)
Integer page,
@RequestParam(required = false,defaultValue = WebConsts.DEFAULT_PAGE_SIZE)
Integer pageSize){
// 常规调用业务逻辑层并返回
JsonPage<CartStandardVO>
jsonPage=omsCartService.listCarts(page,pageSize);
return JsonResult.ok(jsonPage);
}
在上面测试了新增购物车环境的基础上
重启order模块
再次测试http://localhost:10005/doc.html
1.3删除\清空购物车
1.3.1删除购物车的持久层
我们删除购物车的功能支持同时删除一个或多个购物车中的商品
基本思路就是将要删除的购物车商品的id数组传入到Mapper中进行删除
在OmsCartMapper中添加方法
// 根据用户选中的id,删除购物车中的商品(支持删除多个商品)
int deleteCartsByIds(Long[] ids);
OmsCartMapper.xml新增代码
<!-- 根据用户选中的id,删除购物车中的商品(支持删除多个商品) -->
<!-- collection属性array可以更换为ids -->
<delete id="deleteCartsByIds">
delete from
oms_cart
where
id in
<foreach collection="array" item="id" separator=","
open="(" close =")">
#{id}
</foreach>
</delete>
1.3.2删除购物车的业务逻辑层
OmsCartServiceImpl添加方法
@Override
public void removeCart(Long[] ids) {
// 调用mapper删除的方法,完成按ids数组删除购物车的功能
int rows=omsCartMapper.deleteCartsByIds(ids);
if(rows==0){
throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,
"您要删除的商品不存在");
}
}
1.3.3删除购物车的控制层
OmsCartController
@PostMapping("/delete")
@ApiOperation("根据id数组删除购物车中的sku信息")
@ApiImplicitParam(value = "要删除的id数组",name="ids",
required = true,dataType = "array")
// 当@PreAuthorize注解后括号中判断参数为hasRole时
// 相当于在做针对角色(role)的判断,这个写法的效果是对判断内容前(左侧)自动添加"ROLE_"
// 既@PreAuthorize("hasRole('user')") 写法的最终效果就等价于
// @PreAuthorize("hasAuthority('ROLE_user')")
@PreAuthorize("hasRole('user')")
public JsonResult removeCartsByIds(Long[] ids){
omsCartService.removeCart(ids);
return JsonResult.ok("运行了删除功能!");
}
其它程序没有变化的前提下
重启Order模块,测试删除功能
**课上作业: **
开发清空当前登录用户购物车的功能
<delete id="deleteCartsByUserId">
delete from
oms_cart
where
user_id=#{userId}
</delete>
@Override
public void removeAllCarts() {
}
1.3.4清空购物车的功能
OmsCartMapper
// 清空指定用户购物车中的所有sku商品
int deleteCartsByUserId(Long userId);
OmsCartMapper.xml
<!-- 清空指定用户购物车中的所有sku商品 -->
<delete id="deleteCartsByUserId">
delete from
oms_cart
where
user_id=#{userId}
</delete>
OmsCartServiceImpl
@Override
public void removeAllCarts() {
Long userId=getUserId();
int rows=omsCartMapper.deleteCartsByUserId(userId);
if(rows==0){
throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,
"您的购物车已经是空的了!");
}
}
OmsCartController
// 清空当前用户的购物车
@PostMapping("/delete/all")
@ApiOperation("清空当前用户的购物车")
@PreAuthorize("hasRole('user')")
public JsonResult removeCartsByUserId(){
omsCartService.removeAllCarts();
return JsonResult.ok("购物车已清空");
}
测试略!
1.4修改购物车商品数量
开发修改购物车数量的业务逻辑层
因为之前开发新增购物车功能时,我们已经完成了修改购物车数量的持久层,所以不需要再编写了,直接从业务层开始
@Override
public void updateQuantity(CartUpdateDTO cartUpdateDTO) {
// 当前方法参数是CartUpdateDTO
// 这个类型中只有id和quantity两个属性
// 要想实现修改数量,需要将这个类型转换为OmsCart
// 实例化OmsCart
OmsCart omsCart=new OmsCart();
// 同名属性赋值
BeanUtils.copyProperties(cartUpdateDTO,omsCart);
// 调用mapper方法进行修改
omsCartMapper.updateQuantityById(omsCart);
}
控制层OmsCartController
// 修改购物车数量
@PostMapping("/update/quantity")
@ApiOperation("修改购物车数量")
@PreAuthorize("hasRole('user')")
public JsonResult updateQuantity(@Validated CartUpdateDTO cartUpdateDTO){
omsCartService.updateQuantity(cartUpdateDTO);
return JsonResult.ok("修改完成!");
}
重启order测试清空和修改购物车数量的功能
1.5新增订单
1.5.1新增订单业务逻辑分析
用户选中购物车中的商品后,点击添加订单
我们要收集订单信息(sku商品信息,价格信息,优惠和运费信息等)然后才能执行生成订单操作
具体步骤如下
1.首先将用户选中的sku库存减少相应的数量
2.用户购物车要删除对应的商品
3.对应oms_order表执行新增,也就是创建一个订单
4.在新增订单成功后,我们还要将订单中的每种商品和订单关系添加在oms_order_item表中
除了理解业务之外我们还要确定要使用的技术
除了之前一直使用的Nacos\Dubbo之外,创建订单的业务在减少库存时,是Dubbo调用的pms中的sku表,这就涉及了分布式事务seata,删除购物车,新增订单和新增订单项是order模块的功能
减少库存的功能是product模块写好的
1.6开发删除选中的购物车商品的功能
本次删除我们使用用户id和skuId来指定要删除的购物车商品
之前没有写过,在OmsCartMapper编写
// 根据用户id和skuId删除商品
int deleteCartByUserIdAndSkuId(OmsCart omsCart);
对应的mapper.xml
<!-- 根据用户id和skuId删除商品 -->
<delete id="deleteCartByUserIdAndSkuId">
delete from
oms_cart
where
user_id=#{userId}
and
sku_id=#{skuId}
</delete>
当前删除购物车商品的功能是为生成订单准备的
所以只需要开发出业务逻辑层即可不需要控制层的代码
// 生成订单时,从购物车中删除订单中选择的商品
@Override
public void removeUserCarts(OmsCart omsCart) {
// OmsCart是包含userId和skuId的,直接调用即可
omsCartMapper.deleteCartByUserIdAndSkuId(omsCart);
}
1.7开始编写新增订单功能
1.7.1编写新增order_item的持久层
order_item表中保存每张订单包含什么商品的信息
我们新增这个表,要包含订单号,商品id和相关信息
mapper下创建OmsOrderItemMapper
@Repository
public interface OmsOrderItemMapper {
// 新增订单项(order_item)的方法
// 一个订单可以包含多个订单项,为了减少连接数据库新增订单项的次数
// 我们新增订单项方法的参数可以是List
int insertOrderItems(List<OmsOrderItem> omsOrderItems);
}
OmsOrderItemMapper.xml文件添加内容
<!-- 新增订单项(order_item)的方法 -->
<insert id="insertOrderItems">
insert into oms_order_item(
id,
order_id,
sku_id,
title,
bar_code,
data,
main_picture,
price,
quantity
) values
<foreach collection="list" item="ooi" separator="," >
(
#{ooi.id},
#{ooi.orderId},
#{ooi.skuId},
#{ooi.title},
#{ooi.barCode},
#{ooi.data},
#{ooi.mainPicture},
#{ooi.price},
#{ooi.quantity}
)
</foreach>
</insert>
1.7.2编写新增order的持久层
mapper包下再创建OmsOrderMapper
添加新增Order的方法
@Repository
public interface OmsOrderMapper {
// 新增订单的mapper方法
int insertOrder(OmsOrder order);
}
OmsOrderMapper.xml中添加方法
<!-- 新增订单的mapper -->
<insert id="insertOrder">
insert into oms_order(
id,
sn,
user_id,
contact_name,
mobile_phone,
telephone,
province_code,
province_name,
city_code,
city_name,
district_code,
district_name,
street_code,
street_name,
detailed_address,
tag,
payment_type,
state,
reward_point,
amount_of_original_price,
amount_of_freight,
amount_of_discount,
amount_of_actual_pay,
gmt_pay,
gmt_order,
gmt_create,
gmt_modified
) values (
#{id},
#{sn},
#{userId},
#{contactName},
#{mobilePhone},
#{telephone},
#{provinceCode},
#{provinceName},
#{cityCode},
#{cityName},
#{districtCode},
#{districtName},
#{streetCode},
#{streetName},
#{detailedAddress},
#{tag},
#{paymentType},
#{state},
#{rewardPoint},
#{amountOfOriginalPrice},
#{amountOfFreight},
#{amountOfDiscount},
#{amountOfActualPay},
#{gmtPay},
#{gmtOrder},
#{gmtCreate},
#{gmtModified}
)
</insert>
2.Leaf
2.1什么是Leaf
leaf是叶子的意思
我们使用的Leaf是美团公司开源的一个分布式序列号(id)生成系统
我们可以在Github网站上下载项目直接使用
2.2为什么需要Leaf
上面的图片中
是一个实际开发中常见的读写分离的数据库部署格式
专门进行数据更新(写)的有两个数据库节点
它们同时新增数据可能产生相同的id
一旦生成相同的id,数据同步就会有问题
会产生id冲突,甚至引发异常
我们为了在这种多数据库节点的环境下能够产生唯一id
可以使用Leaf来生成
2.3Leaf的工作原理
Leaf底层支持通过"雪花算法"生成不同id
我们使用的是单纯的序列
要想使用,需要事先设置好leaf的起始值和缓存id数
举例,从1000开始缓存500
也就是从id1000~1499这些值,都会保存在Leaf的内存中,当有服务需要时,直接取出下一个值
取出过的值不会再次生成
leaf要想设置起始值和缓存数
需要给leaf创建一个指定格式的数据库表
运行过程中会从数据库表获取信息
我们当前的信息保存在leafdb.leaf_alloc表中