88 | * 使用注解控制事务方法的优点: 89 | * 1:开发团队达成一致约定,明确标注事务方法的变成风格。 90 | * 2:保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部。 91 | * 3:不是所有的方法都需要事务,如:只有一条修改操作,只读操作不需要事务控制。 92 | *93 | */ 94 | @Transactional(rollbackFor = Exception.class) 95 | @Override 96 | public SeckillExecution executeSeckill(String seckillId, String userPhone, String md5) 97 | throws SeckillException, RepeatKillException, SeckillCloseException { 98 | if (StrUtil.isBlank(md5) || !md5.equals(getMd5(seckillId))) { 99 | // md5不同,秒杀的数据被改写 100 | throw new SeckillException("seckill data rewrite"); 101 | } 102 | 103 | try { 104 | // 执行秒杀逻辑:减库存 + 记录购买行为 105 | // 减库存 106 | int updateCount = seckillMapper.reduceNumber(seckillId, new Date()); 107 | if (updateCount <= 0) { 108 | // 没有更新到记录,秒杀结束 109 | throw new SeckillCloseException("seckill is closed"); 110 | } 111 | 112 | // 记录用户购买行为,唯一:seckillId,userPhone 113 | int insertCount = successKilledMapper.insertSuccessKilled(seckillId, userPhone); 114 | if (insertCount <= 0) { 115 | // 重复秒杀 116 | throw new RepeatKillException("seckill repeated"); 117 | } 118 | // 秒杀成功 119 | SuccessKilled successKilled = successKilledMapper.findBySeckillId(seckillId, userPhone); 120 | // 商品秒杀状态标识(-1:无效,0:成功,1:已付款) 121 | return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled); 122 | } catch (SeckillCloseException e1) { 123 | throw e1; 124 | } catch (RepeatKillException e2) { 125 | throw e2; 126 | } catch (SeckillException e3) { 127 | throw e3; 128 | } catch (Exception e) { 129 | Log.error(e.getMessage(), e); 130 | // 所有编译器异常,转换为运行期异常 131 | throw new SeckillException("seckill inner error: " + e.getMessage()); 132 | } 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/cn/colg/service/impl/SuccessKilledServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.colg.service.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | 6 | import cn.colg.dao.SuccessKilledMapper; 7 | import cn.colg.entity.SuccessKilled; 8 | import cn.colg.service.SuccessKilledService; 9 | 10 | @Service 11 | public class SuccessKilledServiceImpl implements SuccessKilledService { 12 | 13 | @Autowired 14 | private SuccessKilledMapper successKilledMapper; 15 | 16 | @Override 17 | public int insertSuccessKilled(String seckillId, String userPhone) { 18 | return successKilledMapper.insertSuccessKilled(seckillId, userPhone); 19 | } 20 | 21 | 22 | @Override 23 | public SuccessKilled findBySeckillId(String seckillId, String userPhone) { 24 | return successKilledMapper.findBySeckillId(seckillId, userPhone); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/colg/web/SeckillController.java: -------------------------------------------------------------------------------- 1 | package cn.colg.web; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.ui.Model; 7 | import org.springframework.web.bind.annotation.CookieValue; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | 14 | import cn.colg.core.BaseController; 15 | import cn.colg.dto.Exposer; 16 | import cn.colg.dto.SeckillExecution; 17 | import cn.colg.dto.SeckillResult; 18 | import cn.colg.entity.Seckill; 19 | import cn.colg.enums.SeckillStatEnum; 20 | import cn.colg.exception.RepeatKillException; 21 | import cn.colg.exception.SeckillCloseException; 22 | import cn.colg.exception.SeckillException; 23 | import cn.hutool.core.util.StrUtil; 24 | import cn.hutool.log.Log; 25 | import cn.hutool.log.LogFactory; 26 | 27 | /** 28 | * // url:模块/资源/{id}/细分 /seckill/list 29 | * 30 | * @author colg 31 | */ 32 | @Controller 33 | @RequestMapping("/seckill") 34 | public class SeckillController extends BaseController { 35 | 36 | private static final Log log = LogFactory.get(); 37 | 38 | /** 39 | * 商品列表页面 40 | * 41 | * @param model 42 | * @return 43 | */ 44 | @GetMapping("/list") 45 | public String list(Model model) { 46 | // 获取列表页 47 | List
序号 | 22 |名称 | 23 |库存 | 24 |开始时间 | 25 |结束时间 | 26 |创建时间 | 27 |详情页 | 28 |
---|---|---|---|---|---|---|
${status.count } | 34 |${sk.name } | 35 |${sk.number } | 36 |商品详情 | 40 |
序号 | 15 |名称 | 16 |值 | 17 |
---|---|---|
{{index + 1}} | 22 |{{key}} | 23 |{{value}} | 24 |
17 | * spring-test, junit 18 | * 配置 Spring 和 Junit 整合 19 | *20 | * 21 | * @author colg 22 | */ 23 | @RunWith(SpringJUnit4ClassRunner.class) 24 | @ContextConfiguration(locations = { "classpath:spring/spring-*.xml" }) 25 | public abstract class BaseTester { 26 | 27 | /** 28 | * 注入service 29 | */ 30 | @Autowired 31 | protected SeckillService seckillService; 32 | @Autowired 33 | protected SuccessKilledService successKilledService; 34 | 35 | /** 36 | * 注入mapper 37 | */ 38 | @Autowired 39 | protected SeckillMapper seckillMapper; 40 | @Autowired 41 | protected SuccessKilledMapper successKilledMapper; 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/cn/colg/dao/SeckillMapperTest.java: -------------------------------------------------------------------------------- 1 | package cn.colg.dao; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.junit.Test; 7 | 8 | import cn.colg.core.BaseTester; 9 | import cn.colg.entity.Seckill; 10 | import cn.hutool.log.Log; 11 | import cn.hutool.log.LogFactory; 12 | 13 | /** 14 | * 配置spring和junit整合 15 | * 16 | * spring-test, junit 17 | * 18 | * @author colg 19 | */ 20 | public class SeckillMapperTest extends BaseTester { 21 | 22 | private static final Log log = LogFactory.get(); 23 | 24 | @Test 25 | public void testReduceNumber() { 26 | String id = "c18c12a838c311e89fa754ee75c6aeb0"; 27 | int updateCount = seckillMapper.reduceNumber(id, new Date()); 28 | log.info("updateCount:{}", updateCount); 29 | } 30 | 31 | @Test 32 | public void testFindById() { 33 | String id = "c18c12a838c311e89fa754ee75c6aeb0"; 34 | Seckill seckill = seckillMapper.findById(id); 35 | log.info("seckill: {}", seckill); 36 | } 37 | 38 | @Test 39 | public void testQuerySeckill() { 40 | List