一、购物车
1、购物车需求
1、需求描述:
-
用户可以在登录状态下将商品添加到购物车【用户购物车/在线购物车】
- 放入数据库
- mongodh
- 放入redis(采用)
登录以后,会将临时购物车的数据全部合并过来,并清空临时购物车;
-
用户可以在未登录状态下将商品添加到购物车【游客购物车/高线购物车/临时购物车】
- 放入localstorage (客户端存储,后台不存)
- cookie-WebsQL
- 放入redis(采用)
浏览器即使关闭,下次进入,临时购物车数据都在
-
用户可以使用购物车一起结算下单
-
给购物车添加商品
-
用户可以查询自己的购物车
-
用户可以在购物车中修改购买商品的数量。
-
用户可以在购物车中删除商品。
-
选中不选择商品
-
在购物车中展示商品优惠信息
-
提示购物车商品价格变化
2、数据结构
因此每一个购物项信息,都是一个对象,基本字段包括:
{
skuld:2131241,
check:true,
title:"Apple Iphone...…",
defaultlmage:"..",
price:4999,
count:1,
totalPrice:4999,
skuSale/o:{...}
}
另外,购物车中不止一条数据,因此最终会是对象的数组。即:
[
{...},{...},{...}
]
Redis.有5种不同数据结构,这里选择哪一种比较合适呢?Map<String,List
-
首先不同用户应该有独立的购物车,因此购物车应该以用户的作为key来存储,Value是用户的所有购物车信息。这样看来基本的kv结构就可以了。
-
但是,我们对购物车中的商品进行增、删、改操作,基本都需要根据商品id进行判断,为了方便后期处理,我们的购物车也应该是kv结构,key是商品id,value才是这个商品的购物车信息。
综上所述,我们的购物车结构是一个双层Map:Map<String,Map<String,String>>
Map<String k1,Map<String k2,CartItemInfo>>
k1:标识每一个用户的购物车
k2:购物项的商品id
在redis中
key:用户标识
value:Hash(k:商品id,v:购物项详情)
3、数据共享
4、项目实例
interceptor 根据是否临时用户、登录与否,设置身份信息
import com.atguigu.common.constant.AuthServerConstant;
import com.atguigu.common.constant.CartConstant;
import com.atguigu.common.vo.MemberRespVo;
import com.atguigu.gulimall.cart.vo.UserInfoTo;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;
/**
* 在执行目标方法之前,判断用户的登录状态。并封装传递给controller目标请求
*/
public class CartInterceptor implements HandlerInterceptor {
public static ThreadLocal<UserInfoTo> toThreadLocal = new ThreadLocal<>();
/**
* 目标方法执行之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
UserInfoTo userInfoTo = new UserInfoTo();
HttpSession session = request.getSession();
MemberRespVo member = (MemberRespVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
if (member!=null){
//用户登录
userInfoTo.setUserId(member.getId());
}
Cookie[] cookies = request.getCookies();
if (cookies!=null && cookies.length>0){
for (Cookie cookie : cookies) {
//user-key
String name = cookie.getName();
if (name.equals(CartConstant.TEMP_USER_COOKIE_NAME));{
userInfoTo.setUserKey(cookie.getValue());
userInfoTo.setTempUser(true);
}
}
}
//如果没有临时用户一定分配一个临时用户
if (StringUtils.isEmpty(userInfoTo.getUserKey())){
String uuid = UUID.randomUUID().toString();
userInfoTo.setUserKey(uuid);
}
//目标方法执行之前
toThreadLocal.set(userInfoTo);
return true;
}
/**
* 业务执行之后;分配临时用户,让浏览器保存
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
UserInfoTo userInfoTo = toThreadLocal.get();
//如果没有一个临时用户一定保存一个临时用户
if (!userInfoTo.isTempUser()){
//持续的延长临时用户的过期时间
Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
cookie.setDomain("gulimall");
cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
response.addCookie(cookie);
}
}
}
controller
import com.atguigu.gulimall.cart.service.CartService;
import com.atguigu.gulimall.cart.vo.Cart;
import com.atguigu.gulimall.cart.vo.CartItem;
import com.atguigu.gulimall.cart.vo.UserInfoTo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpSession;
import java.util.concurrent.ExecutionException;
@Controller
public class CartController {
@Autowired
CartService cartService;
/**
* 删除购物项
* @param skuId
* @return
*/
@GetMapping("/deleteItem")
public String deleteItem(@RequestParam("skuId") Long skuId) {
cartService.deleteItem(skuId);
return "redirect:http://cart.gulimall.com/cart.html";
}
/**
* 修改购物项数量
* @param skuId
* @param num
* @return
*/
@GetMapping("/countItem")
public String countItem(@RequestParam("skuId") Long skuId,
@RequestParam("num") Integer num){
cartService.changeItemCount(skuId,num);
return "redirect:http://cart.gulimall.com/cart.html";
}
/**
* 勾选一个购物项
* @return
*/
@GetMapping("/checkItem")
public String checkItem(@RequestParam("skuId") Long skuId,
@RequestParam("check") Integer check){
cartService.checkItem(skuId,check);
return "redirect:http://cart.gulimall.com/cart.html";
}
/**
* 浏览器有一个cookie; user-key; 标识用户身份,一个月后过期
*如果第一次使用jd的购物车功能,都会给一个临时的用户身份;
* 浏览器以后保存,每次访问都会带上这个cookie;
*
*登录: session有
* 没登录:按照cookie里面带来user-key来做
*第一次: 如果没有临时用户,帮忙创建一个临时用户。
* @return
*/
@GetMapping("/cart.html")
public String cartListPage(Model model) throws ExecutionException, InterruptedException {
//1、快速得到用户信息,id,user-key
Cart cart = cartService.getCart();
model.addAttribute("cart",cart);
return "cartList";
}
/**
* RedirectAttributes ra
* ra.addFlashAttribute();将数据放在session里面,可以在页面取出,但是只能取一次
* ra.addAttribute(); 将数据放在url后面
* 添加商品到购物车
* @return
*/
@GetMapping("/addToCart")
public String addToCart(@RequestParam("skuId") Long skuId,
@RequestParam("num") Integer num,
RedirectAttributes ra) throws ExecutionException, InterruptedException {
cartService.addToCart(skuId,num);
// model.addAttribute("skuId",skuId);
ra.addAttribute("skuId",skuId); //自动将skuId房子url后面
return "redirect:http://cart.gulimall.com/addToCartSuccess.html";
}
/**
* 跳转到购物车成功页
* @param skuId
* @param model
* @return
*/
@GetMapping("/addToCartSuccess.html")
public String addToCartSuccessPage(@RequestParam("skuId") Long skuId,Model model){
//重定向到成功页面。再次查询购物车数据即可
CartItem item = cartService.getCartItem(skuId);
model.addAttribute("item",item);
return "success";
}
}
service
public interface CartService {
/**
* 获取购物车某个购物项
* @param skuId
* @return
*/
CartItem getCartItem(Long skuId);
/**
* 添加商品到购物车
* @param skuId
* @param num
* @return
*/
CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException;
/**
* 获取整个购物车
* @return
*/
Cart getCart() throws ExecutionException, InterruptedException;
/**
* 清空购物车数据
* @param cartkey
*/
void clearCart(String cartkey);
/**
* 勾选购物项
* @param skuId
* @param check
*/
void checkItem(Long skuId, Integer check);
/**
* 修改购物项数量
* @param skuId
* @param num
*/
void changeItemCount(Long skuId, Integer num);
/**
* 删除购物项
* @param skuId
*/
void deleteItem(Long skuId);
}
serviceImpl
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.cart.feign.ProductFeignService;
import com.atguigu.gulimall.cart.interceptor.CartInterceptor;
import com.atguigu.gulimall.cart.service.CartService;
import com.atguigu.gulimall.cart.vo.Cart;
import com.atguigu.gulimall.cart.vo.CartItem;
import com.atguigu.gulimall.cart.vo.SkuInfoVo;
import com.atguigu.gulimall.cart.vo.UserInfoTo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
@Slf4j
@Service
public class CartServiceImpl implements CartService {
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
ProductFeignService productFeignService;
@Autowired
ThreadPoolExecutor executor;
private final String CART_PREFIX = "gulimall:cart:";
/**
* 获取购物车某个购物项
* @param skuId
* @return
*/
@Override
public CartItem getCartItem(Long skuId) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
String str = (String) cartOps.get(skuId.toString());
CartItem cartItem = JSON.parseObject(str, CartItem.class);
return cartItem;
}
/**
* 添加商品到购物车
* @param skuId
* @param num
* @return
*/
@Override
public CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
String res = (String) cartOps.get(skuId.toString());
if (StringUtils.isEmpty(res)){
//购物车无此商品
//2、添加新商品到购物车
CartItem cartItem = new CartItem();
//1、远程查询当前要添加的商品的信息
CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
R skuInfo = productFeignService.getSkuInfo(skuId);
SkuInfoVo data = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
cartItem.setCheck(true);
cartItem.setCount(num);
cartItem.setImage(data.getSkuDefaultImg());
cartItem.setTitle(data.getSkuTitle());
cartItem.setSkuId(skuId);
cartItem.setPrice(data.getPrice());
}, executor);
//2、远程查询sku的组合信息
CompletableFuture<Void> getSkuSaleAttrValues = CompletableFuture.runAsync(() -> {
List<String> values = productFeignService.getSkuSaleAttrValues(skuId);
cartItem.setSkuAttr(values);
}, executor);
CompletableFuture.allOf(getSkuInfoTask,getSkuSaleAttrValues).get();
String s = JSON.toJSONString(cartItem); //利用fastJosn 把对象实体类转化为json
cartOps.put(skuId.toString(),s);
return cartItem;
}else {
//购物车有此商品,修改数量
CartItem cartItem = JSON.parseObject(res, CartItem.class);
cartItem.setCount(cartItem.getCount()+num);
cartOps.put(skuId.toString(),JSON.toJSONString(cartItem));
return cartItem;
}
}
/**
* 获取整个购物车
* @return
*/
@Override
public Cart getCart() throws ExecutionException, InterruptedException {
Cart cart = new Cart();
UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
if (userInfoTo.getUserId()!=null){
//1、登录
String cartKey = CART_PREFIX + userInfoTo.getUserId();
//2、如果临时购物车的数据还没有进行合并【合并购物车】
String tempCartKey = CART_PREFIX + userInfoTo.getUserKey();
List<CartItem> tempCartItems = getCartItems(tempCartKey);
if (tempCartItems!=null){
//临时购物车有数据,需要合并
for (CartItem item : tempCartItems) {
addToCart(item.getSkuId(),item.getCount());
}
//清除临时购物车的数据
clearCart(tempCartKey);
}
//3、获取登录后的购物车的数据【包含合并过来的临时购物车的数据,和登录后的购物车的数据】
List<CartItem> cartItems = getCartItems(cartKey);
cart.setItems(cartItems);
}else {
//2、没登录
String cartKey = CART_PREFIX + userInfoTo.getUserKey();
//获取临时购物车的所有购物项
List<CartItem> cartItems = getCartItems(cartKey);
cart.setItems(cartItems);
}
return cart;
}
/**
* 获取到我们要操作的购物车
* @return
*/
private BoundHashOperations<String, Object, Object> getCartOps() {
UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
String cartKey = "";
if(userInfoTo.getUserId()!=null){
//gulimall:cart:19
cartKey = CART_PREFIX+userInfoTo.getUserId();
}else {
cartKey = CART_PREFIX+userInfoTo.getUserKey();
}
BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
return operations;
}
private List<CartItem> getCartItems(String cartKey){
BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(cartKey);
List<Object> values = hashOps.values();
if (values!=null && values.size()>0){
List<CartItem> collect = values.stream().map((obj) -> {
String str = (String) obj;
CartItem cartItem = JSON.parseObject(str, CartItem.class);
return cartItem;
}).collect(Collectors.toList());
return collect;
}
return null;
}
/**
* 购物车清空
* @param cartkey
*/
@Override
public void clearCart(String cartkey){
redisTemplate.delete(cartkey);
}
/**
* 勾选购物项
* @param skuId
* @param check
*/
@Override
public void checkItem(Long skuId, Integer check) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
CartItem cartItem = getCartItem(skuId);
cartItem.setCheck(check==1?true:false);
String s = JSON.toJSONString(cartItem);
cartOps.put(skuId.toString(),s);
}
/**
* 修改购物项数量
* @param skuId
* @param num
*/
@Override
public void changeItemCount(Long skuId, Integer num) {
CartItem cartItem = getCartItem(skuId);
cartItem.setCount(num);
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
cartOps.put(skuId.toString(),JSON.toJSONString(cartItem));
}
/**
* 删除购物项
* @param skuId
*/
@Override
public void deleteItem(Long skuId) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
cartOps.delete(skuId.toString());
}
}
二、订单
1、订单中心
电商系统涉及到3流,分别时信息流,资金流,物流,而订单系统作为中枢将三者有机的集合起来。
订单模块是电商系统的枢纽,在订单这个环节上需求获取多个模块的数据和信息,同时对这些信息进行加工处理后流向下个环节,这一系列就构成了订单的信息流通。
1、订单构成
1、用户信息
用户信息包括用户账号、用户等级、用户的收货地址、收货人、收货人电话等组成,用户账户需要绑定手机号码,但是用户绑定的手机号码不一定是收货信息上的电话。用户可以添加多个收货信息,用户等级信息可以用来和促销系统进行匹配,获取商品折扣,同时用户等级还可以获取积分的奖励等。
2、订单基础信息
订单基础信息是订单流转的核心,其包括订单类型、父/子订单、订单编号、订单状态、订单流转的时间等。
(1)订单类型包括实体商品订单和虚拟订单商品等,这个根据商城商品和服务类型进行区分。
(2)同时订单都需要做父子订单处理,之前在初创公司一直只有一个订单,没有做父子订单处理后期需要逃行拆单的时候就比较麻烦,尤其是多商户商场,和不同仓库商品的时候,父子订单就是为后期做拆单准备的。
(3)订单编号不多说了,需要强调的一点是父子订单都需要有订单编号,需要完善的时候可以对订单编号的每个字段进行统一定义和诠释。
(4)订单状态记录订单每次流转过程,后面会对订单状态进行单独的说明。
(5)订单流转时间需要记录下单时间,支付时间,发货时间,结束时间/关闭时间等等3、商品信息
商品信息从商品库中获取商品的SKU信息、图片、名称、属性规格、商品单价、商户信息等,从用户下单行为记录的用户下单数量,商品合计价格等。
4.优惠信息
优惠信息记录用户参与的优惠活动,包括优惠促销活动,比如满减、满赠、秒杀等,用户使用的优惠券信息,优惠券满足条件的优惠券需要默认展示出来,具体方式已在之前的优惠券篇章做过详细介绍,另外还虚拟币抵扣信息等进行记录。
5.支付信息
(1)支付流水单号,这个流水单号是在唤起网关支付后支付通道返回给电商业务平台的支付流水号,财务通过订单号和流水单号与支付通道进行对账使用。
(2)支付方式用户使用的支付方式,比如微信支付、支付宝支付、钱包支付、快捷支付等。
支付方式有时候可能有两个——余额支付+第三方支付。
(3)商品总金额,每个商品加总后的金额,运费,物流产生的费用;优惠总金额,包括促销活动的优惠金额,优惠券优惠金额,虚拟积分或者虚拟币抵扣的金额,会员折扣的金额等之和;实付金额,用户实际需要付款的金额。
用户实付金额=商品总金额+运费优惠总金额
6.物流信息
物流信息包括配送方式,物流公司,物流单号,物流状态,物流状态可以通过第三方接口来获取和向用户展示物流每个状态节点。
2、订单状态
1.待付款
用户提交订单后,订单进行预下单,目前主流电商网站都会唤起支付,便于用户快速完成支付,需要注意的是待付款状态下可以对库存进行锁定,锁定库存需要配置支付超时时间,超时后将自动取消订单,订单变更关闭状态。
2.已付款/待发货
用户完成订单支付,订单系统需要记录支付时间,支付流水单号便于对账,订单下放到WMS系统,仓库进行调拨,配货,分拣,出库等操作。
3.待收货/已发货
仓储将商品出库后,订单进入物流环节,订单系统需要同步物流信息,便于用户实时知悉物品物流状态
4.已完成
用户确认收觉后,订单交易完成。后续支付侧进行结算,如果订单存在问题进入售后状态
5.已取消
付款之前取消订单。包括超时未付款或用户商户取消订单都会产生这种订单状态。
6.售后中
用户在付款后申请退款,或商家发货后用户申请退换货。
售后也同样存在各种状态,当发起售后申请后生成售后订单,售后订单状态为待审核,等待商家审核,商家审核通过后订单状态变更为待退货,等待用户将商品寄回,商家收货后订单状态更新为待退款状态,退款到用户原账户后订单状态更新为售后成功。
2、订单流程
订单流程是指从订单产生到完成整个流转的过程,从而行程了一套标准流程规则。而不同的产品类型或业务类型在系统中的流程会千差万别,比如上面提到的线上实物订单和虚拟订单的流程,线上实物订单与O2O订单等,所以需要根据不同的类型进行构建订单流程。
不管类型如何订单都包括正向流程和逆向流程,对应的场景就是购买商品和退换货流程,正向流程就是一个正常的网购步骤;订单生成->支付订单->卖家发货->确认收货>交易成功。
而每个步骤的背后,订单是如何在多系统之间交互流转的,可概括如下图
1、订单创建与支付
(1)、订单创建前需要预览订单,选择收货信息等
(2)、订单创建需要锁定库存,库存有才可创建,否则不能创建
(3)、订单创建后超时未支付需要解锁库存
(4)、支付成功后,需要进行拆单,根据商品打包方式,所在仓库,物流等进行拆单
(5)、支付的每笔流水都需要记录,以待查账
(6)、订单创建,支付成功等状态都需要给MQ发送消息,方便其他系统感知订阅
2、逆向流程
(1)、修改订单,用户没有提交订单,可以对订单一些信息进行修改,比如配送信息,优惠信息,及其他一些订单可修改范围的内容,此时只需对数据进行变更即可。
(2)、订单取消,用户主动取消订单和用户超时未支付,两种情况下订单都会取消订单,而超时情况是系统自动关闭订单,所以在订单支付的响应机制上面要做支付的
3、幂等性处理
参照幂等性文档
4、订单业务
1、搭建环境
订单服务引入页面,nginx配置动静分离,上传静态资源到nginx。编写controller 跳转逻辑
2、订单确认页
OrderConfirmVo
@Data public class OrderConfirmVo{
//收货地址,ums_member_receive_address表
private List<MemberReceiveAddressEntity> addresses;
//购物清单,根据购物车页面传递过来的skulds查询
private List<OrderitemVo> orderltems;
//可用积分,ums member 表中的integration字段
private Integer bounds;
5、问题
问题一:
解决方法详细见package com.atguigu.gulimall.order.config.GuliFeignConfig.java
package com.atguigu.gulimall.order.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Configuration
public class GuliFeignConfig {
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor(){
return new RequestInterceptor(){
@Override
public void apply(RequestTemplate template) {
//1、RequestContextHolder 拿到刚进来的这个请求
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest(); //老请求
//同步请求头数据,Cookie
String cookie = request.getHeader("Cookie");
//给新请求同步了老请求的cookie
template.header("Cookie",cookie);
}
};
}
}
三、秒杀
1、秒杀业务
秒杀具有瞬间高并发的特点,针对这一特点,必须要做限流+异步+缓存(页面静态化)
+独立部署。
限流方式:
1.前端限流,一些高并发的网站直接在前端页面开始限流,例如:小米的验证码设计
2.nginx限流,直接负载部分请求到错误的静态页面:令牌算法 漏斗算法
3.网关限流,限流的过滤器
4.代码中使用分布式信号量
5.rabbitmg限流(能者多劳:chanelbasicQos(1)),保证发挥所有服务器的性能。
2、秒杀流程
见秒杀流程图
3、秒杀问题
四、定时任务与分布式调度
一、定时任务
1、cron表达式
语法:秒 分 时 日 月 周 年(Spring不支持年)
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html
特殊字符:
, :枚举;
(cron="7,9,23****?):任意时刻的7,,9,23秒启动这个任务;
- :范围:
(cron="7-20****?"):任意时刻的7-20秒之间,每秒启动一次
* :任意;指定位置的任意时刻都可以
/ :步长;
(cron="7/5****?"):第7秒启动,每5秒一次;
(cron="*/5****?"):任意秒启动,每5秒一次;
? :(出现在日和周几的位置):为了防止日和周冲突,在周和日上如果要写通配符使用?
(cron="***1*?"):每月的1号,而且必须是周二然后启动这个任务;
L: (出现在日和周的位置)",
last:最后一个
(cron="***?*3L"):每月的最后一个周二
W:
Work Day:工作日
(cron="***W*?"):每个月的工作日触发
(cron="***LW*?"):每个月的最后一个工作日触发
: 第几个
(cron="***?*5#2”):每个月的第2个周4
2、cron示例
3、springboot整合定时任务
package com.atguigu.gulimall.seckill.scheduled;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
/**
* 定时任务:
* 1、@EnableScheduling 开启定时任务
* 2、@Scheduled 开启一个定时任务
* 3、自动配置类 TaskSchedulingAutoConfiguration
* 异步任务:
* 1、@EnableAsync 开启异步任务功能
* 2、@Async 给希望异步执行的方法上标注
* 3、自动配置类 TaskExecutionAutoConfiguration 属性绑定在TaskExecutionProperties
*/
@Slf4j
@Component
@EnableAsync //开启异步
@EnableScheduling //开启定时调度
public class HelloSchedule {
/**
* 1、Spring中6位组成,不允许第7位的年
* 2、在周几的位置,1-7代表周一到周日; MON-SUN
* 3、定时任务不应该阻塞。 默认是阻塞的
* 1)、可以让业务运行以异步的方式,自己提交到线程池
* CompletableFuture.runAsync(()->{
* xxxxService.hello();
* },executor);
* 2)、支持定时任务线程池; 设置 TaskSchedulingProperties
* spring.task.scheduling.pool.size=5 (不好使)
* 3)、让定时任务异步执行
* 异步任务;
*
* 解决: 使用异步 + 定时任务来完成定时任务不阻塞的功能
*
*/
@Async
@Scheduled(cron = "* * * * * ?")
public void hello() throws InterruptedException {
log.info("hello...");
Thread.sleep(3000);
}
}