所有代码发布在 [https://github.com/hades0525/leyou]
购物车
4.5 修改购物车的数量
• controller
/**
*修改购物车商品数量
*@paramspuId
*@paramnum
*@return
*/
@PutMapping
publicResponseEntity<Void>updateCartNum(@RequestParam("id")LongspuId,@RequestParam("num")intnum){
cartService.updateCartNum(spuId,num);
returnResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
• service
/**
*修改购物车商品数量
*@paramspuId
*@paramnum
*/
publicvoidupdateCartNum(LongspuId,intnum){
//获取登录的用户
UserInfouser=UserInterceptor.getUser();
//key
Stringkey=KEY_PREFIX+user.getId();
//hashKey
StringhashKey=spuId.toString();
//获取操作
BoundHashOperations<String,Object,Object>operations=redisTemplate.boundHashOps(key);
//判断是否存在
if(!operations.hasKey(hashKey)){
thrownewLyException(ExceptionEnum.CART_NOT_FOUND);
}
//查询
Cartcart=JsonUtils.toBean(operations.get(hashKey).toString(),Cart.class);
cart.setNum(num);
//写回redis
operations.put(hashKey,JsonUtils.toString(cart));
}
4.6 删除购物车
• controller
/**
*删除购物车
*@paramskuId
*@return
*/
@DeleteMapping("{skuId}")
publicResponseEntity<Void>deleteCart(@PathVariable("skuId")LongskuId){
cartService.deleteCart(skuId);
returnResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
• service
/**
*删除购物车
*@paramskuId
*/
publicvoiddeleteCart(LongskuId){
//获取登录的用户
UserInfouser=UserInterceptor.getUser();
//key
Stringkey=KEY_PREFIX+user.getId();
//删除
redisTemplate.opsForHash().delete(key,skuId.toString());
}
订单微服务
- 订单微服务搭建
• pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.leyou.service</groupId>
<artifactId>ly-item-interface</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>ly-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.leyou.service</groupId>
<artifactId>ly-auth-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<!--<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
</dependency>-->
</dependencies>
• 配置文件
server:
port: 8089
spring:
application:
name: order-service
datasource:
url: jdbc:mysql://localhost:3306/yun61
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
jackson:
default-property-inclusion: non_null
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
registry-fetch-interval-seconds: 5
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
mybatis:
type-aliases-package: com.leyou.order.pojo
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
ly:
jwt:
pubKeyPath: D:/MY_IDEA/rsa/rsa.pub # 公钥地址
cookieName: LY_TOKEN # cookie的名称
worker:
workedId: 1
dataCenterId: 1
• 启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.leyou.order.mapper")
publicclassLyOrderApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(LyOrderApplication.class);
}
}
- 实体类
• Order
@Data
@Table(name="tb_order")
publicclassOrder{
@Id
privateLongorderId;//id
privateLongtotalPay;//总金额
privateLongactualPay;//实付金额
privateIntegerpaymentType;//支付类型,1、在线支付,2、货到付款
privateStringpromotionIds;//参与促销活动的id
privateLongpostFee=0L;//邮费todo实际项目中应该根据具体地点生成具体邮费
privateDatecreateTime;//创建时间
privateStringshippingName;//物流名称
privateStringshippingCode;//物流单号
privateLonguserId;//用户id
privateStringbuyerMessage;//买家留言
privateStringbuyerNick;//买家昵称
privateBooleanbuyerRate;//买家是否已经评价
privateStringreceiver;//收货人全名
privateStringreceiverMobile;//移动电话
privateStringreceiverState;//省份
privateStringreceiverCity;//城市
privateStringreceiverDistrict;//区/县
privateStringreceiverAddress;//收货地址,如:xx路xx号
privateStringreceiverZip;//邮政编码,如:310001
privateIntegerinvoiceType=0;//发票类型,0无发票,1普通发票,2电子发票,3增值税发票
privateIntegersourceType=1;//订单来源1:app端,2:pc端,3:M端,4:微信端,5:手机qq端
@Transient
privateOrderStatusorderStatus;
@Transient
privateList<OrderDetail>orderDetails;
}
• OrderDtail
@Data
@Table(name="tb_order_detail")
publicclassOrderDetail{
@Id
@KeySql(useGeneratedKeys=true)
privateLongid;
privateLongorderId;//订单id
privateLongskuId;//商品id
privateIntegernum;//商品购买数量
privateStringtitle;//商品标题
privateLongprice;//商品单价
privateStringownSpec;//商品规格数据
privateStringimage;//图片
}
• OrderStatus
@Data
@Table(name="tb_order_status")
publicclassOrderStatus{
@Id
privateLongorderId;
privateIntegerstatus;//1、未付款2、已付款,未发货3、已发货,未确认4、交易成功5、交易关闭6、已评价'
privateDatecreateTime;//创建时间
privateDatepaymentTime;//付款时间
privateDateconsignTime;//发货时间
privateDateendTime;//交易结束时间
privateDatecloseTime;//交易关闭时间
privateDatecommentTime;//评价时间
}
- DTO (页面接受的数据)
• OrderDto
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassOrderDto{
@NotNull
privateLongaddressId;//收获人地址id
@NotNull
privateIntegerpaymentType;//付款类型
@NotNull
privateList<CartDto>carts;//订单详情
}
• AddressDTO
@Data
publicclassAddressDTO{
privateLongid;
privateStringname;//收件人姓名
privateStringphone;//电话
privateStringstate;//省份
privateStringcity;//城市
privateStringdistrict;//区
privateStringaddress;//街道地址
privateStringzipCode;//邮编
privateBooleanisDefault;
}
• CartDto 放在ly-common中
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassCartDto{
privateLongskuId;//商品skuId
privateIntegernum;//购买数量
}
- 用户校验
• 把JwtProperties,UserInterceptor和MvcConfig放到ly-order中
- CartController
@RestController
@RequestMapping("order")
publicclassOrderController{
@Autowired
privateOrderServiceorderService;
/**
*创建订单
*@paramorderDto
*@return
*/
@PostMapping
publicResponseEntity<Long>createOrder(@RequestBodyOrderDtoorderDto){
//创建订单
returnResponseEntity.ok(orderService.createOrder(orderDto));
}
}
- 生成订单号的工具
• 订单数据非常庞大,将来一定会做分库分表。那么这种情况下, 要保证id的唯一,就不能靠数据库自增,而是自己来实现算法,生成唯一id。
• 在ly-common中IdWorker工具类 通过雪花算法
• IdWorker中需要的两个参数写在配置文件中
• IdWorkerProperties
@Data
@ConfigurationProperties(prefix="ly.worker")
publicclassIdWorkerProperties{
privatelongworkerId;//当前机器id
privatelongdataCenterId;//序列号
}
• IdWorkerConfig
@Configuration
@EnableConfigurationProperties(IdWorkerProperties.class)
publicclassIdWorkerConfig{
/**
*注册IdWorker
*@paramprop
*@return
*/
@Bean
publicIdWorkeridWorker(IdWorkerPropertiesprop){
returnnewIdWorker(prop.getWorkerId(),prop.getDataCenterId());
}
}
- CartService 生成订单的方法
@Slf4j
@Service
@Transactional
publicclassOrderService{
@Autowired
privateOrderMapperorderMapper;
@Autowired
privateOrderDetailMapperdetailMapper;
@Autowired
privateOrderStatusMapperstatusMapper;
@Autowired
privateGoodsClientgoodsClient;
@Autowired
privateIdWorkeridWorker;
@Transactional
publicLongcreateOrder(OrderDtoorderDto){
//1.新增订单
Order order = new Order();
//1.1订单编号基本信息
long orderId = idWorker.nextId();
order.setOrderId(orderId);
order.setCreateTime(newDate());
order.setPaymentType(orderDto.getPaymentType());
//1.2用户信息
UserInfo user = UserInterceptor.getUser();
order.setUserId(user.getId());
order.setBuyerNick(user.getUsername());
order.setBuyerRate(false);
//1.3收货人地址
AddressDTO addr = AddressClient.findById(orderDto.getAddressId());
order.setReceiver(addr.getName());
order.setReceiverAddress(addr.getAddress());
order.setReceiverDistrict(addr.getDistrict());
order.setReceiverCity(addr.getCity());
order.setReceiverState(addr.getState());
order.setReceiverMobile(addr.getPhone());
order.setReceiverZip(addr.getZipCode());
//1.4金额
//把cartdto转为一个map,key是skuId,value是num
Map<Long,Integer>numMap=orderDto.getCarts().stream()
.collect(Collectors.toMap(CartDto::getSkuId,CartDto::getNum));
//获取所有sku的id
Set<Long>ids=numMap.keySet();
//根据id查询sku
List<Sku> skus = goodsClient.querySkuByIds(newArrayList<>(ids));
//准备orderDtail集合
List<OrderDetail>details=newArrayList<>();
LongtotalPay=0L;
for(Skusku:skus){
totalPay=sku.getPrice()*numMap.get(sku.getId());
//封装orderDtail
OrderDetaildetail=newOrderDetail();
detail.setOrderId(orderId);
detail.setImage(StringUtils.substringBefore(sku.getImages(),","));
detail.setNum(numMap.get(sku.getId()));
detail.setOwnSpec(sku.getOwnSpec());
detail.setSkuId(sku.getId());
detail.setTitle(sku.getTitle());
detail.setPrice(sku.getPrice().longValue());
details.add(detail);
}
order.setTotalPay(totalPay);
//实付金额:总金额+邮费-优惠金额
order.setActualPay(totalPay+order.getPostFee()-0);
//1.5写入数据库
intcount=orderMapper.insertSelective(order);
if(count!=1){
log.error("[创建订单服务order]创建订单失败,orderId:{}",orderId);
thrownewLyException(ExceptionEnum.CREATE_ORDER_ERROR);
}
//2.新增订单详情
count=detailMapper.insertList(details);
if(count!=details.size()){
log.error("[创建订单服务detail]创建订单失败,orderId:{}",orderId);
thrownewLyException(ExceptionEnum.CREATE_ORDER_ERROR);
}
//3.新增订单状态
OrderStatusorderStatus=newOrderStatus();
orderStatus.setCreateTime(order.getCreateTime());
orderStatus.setOrderId(orderId);
orderStatus.setStatus(OrderStatusEnum.UN_PAY.value());
count=statusMapper.insertSelective(orderStatus);
if(count!=1){
log.error("[创建订单服务status]创建订单失败,orderId:{}",orderId);
thrownewLyException(ExceptionEnum.CREATE_ORDER_ERROR);
}
//4.减库存采用同步,在数据库判断
List<CartDto> cartDto s= orderDto.getCarts();
goodsClient.decreaseStock(cartDtos);
returnorderId;
}
- 收货人地址信息 造的假数据
publicabstractclassAddressClient{
publicstaticfinalList<AddressDTO>addressList=newArrayList<AddressDTO>(){
{
AddressDTOaddress=newAddressDTO();
address.setId(1L);
address.setAddress("航头镇航头路18号传智播客3号楼");
address.setCity("上海");
address.setDistrict("浦东新区");
address.setName("虎哥");
address.setPhone("15800000000");
address.setState("上海");
address.setZipCode("21000");
address.setIsDefault(true);
add(address);
AddressDTOaddress2=newAddressDTO();
address2.setId(2L);
address2.setAddress("天堂路3号楼");
address2.setCity("北京");
address2.setDistrict("朝阳区");
address2.setName("张三");
address2.setPhone("13600000000");
address2.setState("北京");
address2.setZipCode("100000");
address2.setIsDefault(false);
add(address2);
}
};
publicstaticAddressDTOfindById(Longid){
for(AddressDTOaddressDTO:addressList){
if(addressDTO.getId()==id)returnaddressDTO;
}
returnnull;
}
}
- 需要远程调用goods中的方法
• 在goodsApi中添加接口
/**
*根据spu的id集合查询下面所有sku
*@paramids
*@return
*/
@GetMapping("/sku/list/ids")
List<Sku>querySkuByIds(@RequestParam("ids")List<Long>ids);
• goodsClient
@FeignClient(value="item-service")
publicinterfaceGoodsClientextendsGoodsApi{
}
- 订单状态的枚举
publicenumOrderStatusEnum{
UN_PAY(1,"未付款"),
PAYED(2,"已付款,未确认"),
UN_CONFIRM(3,"已发货,未确认"),
SUCCESS(4,"已确认,交易成功"),
CLOSED(5,"已关闭,交易失败"),
RATED(6,"已评价"),
;
privateintcode;
privateStringdesc;
OrderStatusEnum(intcode,Stringdesc){
this.code=code;
this.desc=desc;
}
publicintvalue(){
returnthis.code;
}
}
- 减少库存 通过数据库里判断来解决事务问题
• GoodsController.decreaseStock
/**
*减少库存
*@paramcartDtos
*@return
*/
@PostMapping("stock/decrease")
publicResponseEntity<Void>decreaseStock(@RequestBodyList<CartDto>cartDtos){
goodsService.decreaseStock(cartDtos);
returnResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
• GoodsService.decreaseStock
/**
*减少库存
*@paramcartDtos
*/
@Transactional
publicvoiddecreaseStock(List<CartDto>cartDtos){
for(CartDtocartDto:cartDtos){
//减库存
intcount=stockMapper.decreaseStock(cartDto.getSkuId(),cartDto.getNum());
if(count!=1){
thrownewLyException(ExceptionEnum.STOCK_NOT_ENOUGH);
}
}
}
• StockMapper
publicinterfaceStockMapperextendsBaseMapper<Stock>{
@Update("updatetb_stocksetstock=stock-#{num}wheresku_id=#{skuId}andstock>=#{num}")
intdecreaseStock(@Param("skuId")LongskuId,@Param("num")Integernum);
}
• GoodsApi
/**
*减少库存
*@paramcartDtos
*@return
*/
@PostMapping("stock/decrease")
voiddecreaseStock(@RequestBodyList<CartDto>cartDtos);