秒杀项目之商品展示及商品秒杀

目录

登录方式调整

生成秒杀订单

绑定秒杀商品

查看秒杀商品

订单秒杀

移除seata相关

生成秒杀订单

前端页面秒杀测试


登录方式调整

第1步:从zmall-common的pom.xml中移除spring-session-data-redis依赖

 

注意:

1)本章节中不采用spring-session方式,改用redis直接存储用户登录信息,主要是为了方便之后的jmeter压测;

2)这里只注释调用spring-session的依赖,保留redis的依赖;

第2步:在zmall-common公共模块中定义RedisConfig配置类

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> restTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
        //String类型Key序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //String类型Value序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //Hash类型Key序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //Hash类型Value序列化
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

这里一定要注意,最后在将RedisConnectionFactory设置到RedisTemplate中,不要在最前做该步操作,不然会导致String和Hash类型的序列化无效,将采用默认的JdkSerializationRedisSerializer进行序列化,从而导致保存的key前缀出现乱码问题。细心!!!细心!!!细心!!!o(╥﹏╥)o

参考地址:RedisTemplate写入Redis数据出现无意义乱码前缀\xac\xed\x00\x05_hunger_wang的博客-CSDN博客_\xac

第3步:在zmall-common公共模块中配置redis相关服务

IRedisServcie

public interface IRedisService {

    /**
     * 将登陆用户对象保存到Redis中,并以token来命名
     * @param token
     * @param user
     */
    void setUserToRedis(String token, User user);

    /**
     * 根据token令牌从Redis中获取User对象
     * @param token
     * @return
     */
    User getUserByToken(String token);
}

RedisServcieImple

@Service
public class RedisServiceImpl implements IRedisService {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Override
    public void setUserToRedis(String token, User user) {
        String key="user:"+token;
        redisTemplate.boundValueOps(key).set(user,7200,TimeUnit.SECONDS);
    }

    @Override
    public User getUserByToken(String token) {
        return (User) redisTemplate.opsForValue().get("user:"+token);
    }
}

用户登录成功后,将用户对象保存到Redis中,并设置超时时间7200秒。

第4步:在zmall-common公共模块中配置,配置自定义参数解析UserArgumentResolver、WebConfig

UserArgumentResolver

/**
 * 自定义用户参数类
 */
@Component
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

    @Autowired
    private IRedisService redisService;

    /**
     * 只有supportsParameter方法执行返回true,才能执行下面的resolveArgument方法
     * @param methodParameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        Class<?> type = methodParameter.getParameterType();
        return type== User.class;
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest,
                                  WebDataBinderFactory webDataBinderFactory) throws Exception {
        HttpServletRequest req= (HttpServletRequest) nativeWebRequest.getNativeRequest();
        //从cookie获取token令牌
        String token = CookieUtils.getCookieValue(req, "token");
        //判断cookie中的token令牌是否为空
        if(StringUtils.isEmpty(token))
            throw new BusinessException(JsonResponseStatus.TOKEN_ERROR);
        //根据token令牌获取redis中存储的user对象,方便jmeter测试
        User user = redisService.getUserByToken(token);
        if(null==user)
            throw new BusinessException(JsonResponseStatus.TOKEN_ERROR);
        return user;
    }
}

WebConfig

@Component
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private UserArgumentResolver userArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(userArgumentResolver);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //添加静态资源访问映射
        //registry.addResourceHandler("/static/**")
        //        .addResourceLocations("classpath:/static/");
    }
}

第5步:用户登录业务调整,将spring-session方式更改为redis方式存储登录用户信息。

//5.通过UUID生成token令牌并保存到cookie中
String token= UUID.randomUUID().toString().replace("-","");
//将随机生成的Token令牌保存到Cookie中,并设置1800秒超时时间
CookieUtils.setCookie(req,resp,"token",token,7200);
//6.将token令牌与spring session进行绑定并存入redis中
//HttpSession session = req.getSession();
//session.setAttribute(token,us);
//将token令牌与user绑定后存储到redis中,方便jmeter测试
redisService.setUserToRedis(token,us);

这里采用Redis方式直接存储登录用户信息,只为后续使用Jmeter压测时提供便利。正常运行使用项目还是可以使用spring-session方式。

第6步:修改商品服务zmall-product模块中的index方法,将之前从HttpSession中获取登录用户信息改换成User对象参数方式

@RequestMapping("/index.html")
public String index(Model model, User user){
    System.out.println(user);
}

在调用index方法之前,先由自定义的参数解析器进行参数解析并返回解析结果User,所以在这里可直接在方法参数中获取的User对象。

第7步:重启zmall-user和zmall-product模块,完成用户登录后,直接在浏览器地址栏输入:http://zmall.com/product-serv/index.html,查看zmall-product模块中的控制台是否已经获取到登录用户对象信息。

 

生成秒杀订单

绑定秒杀商品

添加sellDetail.html页面到zmall-product模块中;实现首页秒杀商品展示,必须保证秒杀商品状态为已激活、且秒杀商品的活动时间为有效时间范围之内。

index.html

 <#if kills??>
     <#list kills as g>
         <div class="sell_${g_index?if_exists+1}">
             <div class="sb_img"><a href="${ctx}/sellDetail.html?pid=${g.item_id}"><img src="${g.fileName}" width="242" height="356" /></a></div>
             <div class="s_price">¥<span>${g.price}</span></div>
             <div class="s_name">
             <h2><a href="${ctx}/sellDetail.html?pid=${g.item_id}">${g.name}</a></h2>
             倒计时:<span>1200</span> 时 <span>30</span> 分 <span>28</span> 秒
             </div>
         </div>
     </#list>
 </#if>

sellDetail.html

<table border="0" style="width:100%; margin-bottom:50px;" cellspacing="0" cellpadding="0">
                  <tr valign="top">
                    <td width="315">
                    	<div class="lim_name">${(prod.name)!}</div>
                        <div class="lim_price">
                        	<span class="ch_txt">¥${(prod.price)!}</span>
                       		<a href="javascript:void(0);" class="ch_a" pid="${(prod.item_id)!}" price="${(prod.price)!}">抢购</a>
                        </div>
                        <div class="lim_c">
                        	<table border="0" style="width:100%; color:#888888;" cellspacing="0" cellpadding="0">
                              <tr>
                                <td width="35%">市场价 </td>
                                <td width="65%">折扣</td>
                              </tr>
                              <tr style="font-family:'Microsoft YaHei';">
                                <td style="text-decoration:line-through;">¥${(prod.price)!}</td>
                                <td>8.0</td>
                              </tr>
                            </table>
                        </div>
                        <div class="lim_c">
                        	<div class="des_choice">
                                <span class="fl">型号:</span>
                                <ul>
                                    <li class="checked">30ml<div class="ch_img"></div></li>
                                    <li>50ml<div class="ch_img"></div></li>
                                    <li>100ml<div class="ch_img"></div></li>
                                </ul>
                            </div>
                            <div class="des_choice">
                                <span class="fl">颜色:</span>
                                <ul>
                                    <li>红色<div class="ch_img"></div></li>
                                    <li class="checked">白色<div class="ch_img"></div></li>
                                    <li>黑色<div class="ch_img"></div></li>
                                </ul>
                            </div>
                        </div>
                        <div class="lim_c">
                        	<span class="fl">数量:</span><input type="text" value="${(prod.total)!}" class="lim_ipt" />
                        </div>
                        <div class="lim_clock">
                        	距离团购结束还有<br />
                            <span>1200 时 30 分 28 秒</span>
                        </div>
                    </td>
                    <td width="525" align="center" style="border-left:1px solid #eaeaea;"><img src="${(prod.fileName)!}" width="460" height="460" /></td>
                  </tr>
                </table>

web层

@RequestMapping("/index.html")
    public ModelAndView toIndex(User user){
        System.out.println("user:"+ JSON.toJSONString(user));
        ModelAndView mv=new ModelAndView();
        //获取热卖商品列表
        List<Product> hot = productService.list(new QueryWrapper<Product>()
                .orderByDesc("hot")
                .last("limit 4"));

        //获取显示秒杀商品
        List<Map<String, Object>> maps = productService.queryKillProdNews();

        mv.addObject("kills",maps);
        mv.addObject("hots",hot);
        mv.setViewName("index");


        return mv;
    }

service层

@Override
    public List<Map<String, Object>> queryKillProdNews() {
        return productMapper.queryKillProdNews();
    }

    @Override
    public Map<String, Object> queryKillProdById(Integer pid) {
        return productMapper.queryKillProdById(pid);
    }
public interface IProductService extends IService<Product> {
    void updateStock(Integer pid,Integer num);

    /**
     * 首页显示秒杀商品查询
     * @return
     */
    List<Map<String,Object>> queryKillProdNews();

    /**
     * 根据商品ID查询秒杀商品信息
     * @param pid 秒杀商品ID
     * @return
     */
    Map<String,Object> queryKillProdById(Integer pid);
}

 mapper层

@Repository
public interface ProductMapper extends BaseMapper<Product> {
//    @MapKey("queryKillProdNews")
    List<Map<String,Object>> queryKillProdNews();

    Map<String,Object> queryKillProdById(Integer pid);
}

productMapper.xml

<select id="queryKillProdNews" resultType="java.util.Map">
        select
        k.id,k.item_id,p.name,p.price,p.fileName
        from
        zmall_kill k,zmall_product p
        where k.item_id=p.id and
        k.is_active=1 and
        (now() between start_time and end_time)
        order by k.create_time desc
        limit 4
    </select>
    <select id="queryKillProdById" resultType="java.util.Map">
        select
        k.id,k.item_id,k.total,p.name,p.price,p.fileName
        from
        zmall_kill k,zmall_product p
        where k.item_id=p.id and k.is_active=1 and item_id=#{value}
    </select>

查看秒杀商品

点击限时秒杀中的秒杀商品,根据秒杀商品ID查询秒杀商品详情信息并跳转到sellDetail.html页面展示秒杀商品信息。

@RequestMapping("/sellDetail.html")
    public String sellDetail(@RequestParam Integer pid, Model model){
        //根据商品ID查询秒杀商品信息
        Map<String, Object> prod = productService.queryKillProdById(pid);
        model.addAttribute("prod",prod);
        return "sellDetails";
    }

订单秒杀

移除seata相关

第1步:先注释掉zmall-order和zmall-product模块中的seata依赖

第2步:分别删掉zmall-order和zmall-product模块中resources目录下的bootstrap.xml和register.conf文件

seata分布式事务,进行jmeter压测秒杀订单接口效率太低(1000个并发请求,吞吐量为4.5/s)o(╥﹏╥)o

生成秒杀订单

将SnowFlake雪花ID生成工具类导入到zmall-common模块中utils,然后再生成秒杀订单时使用雪花ID来充当秒杀订单编号;在zmall-order模块中完成秒杀订单生成工作。

IOrderService

public interface IOrderService extends IService<Order> {
    Order createOrder(Integer pid,Integer num);

    /**
     * 生成秒杀订单
     * @param user 登陆用户对象
     * @param pid  秒杀商品ID
     * @param price 秒杀商品价格
     * @return
     */
    JsonResponseBody<?> createKillOrder(User user, Integer pid, Float price);
}

OrderServiceImpl

@Autowired
private KillServiceImpl killService;
@Autowired
private OrderDetailServiceImpl orderDetailService;

@Transactional
    @Override
    public JsonResponseBody<?> createKillOrder(User user, Integer pid, Float price) {
        //1.根据秒杀商品编号获取秒杀商品库存是否为空
        Kill kill = killService.getOne(new QueryWrapper<Kill>().eq("item_id",pid));
        if(kill.getTotal()<1)
            throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);
        //2.秒杀商品库存减一
        killService.update(new UpdateWrapper<Kill>()
                .eq("item_id",pid)
                .setSql("total=total-1"));
        //3.生成秒杀订单及订单项
        SnowFlake snowFlake=new SnowFlake(2,3);
        Long orderId=snowFlake.nextId();
        int orderIdInt = new Long(orderId).intValue();
        //创建订单
        Order order=new Order();
        order.setUserId(user.getId());
        order.setLoginName(user.getLoginName());
        order.setCost(price);
        order.setSerialNumber(orderIdInt+"");
        this.save(order);
        //创建订单项
        OrderDetail orderDetail=new OrderDetail();
        orderDetail.setOrderId(orderIdInt);
        orderDetail.setProductId(pid);
        orderDetail.setQuantity(1);
        orderDetail.setCost(price);
        orderDetailService.save(orderDetail);
        return new JsonResponseBody();
    }

OrderController

@RequestMapping("/createKillOrder/{pid}/{price}")
    @ResponseBody
    public JsonResponseBody<?> createKillOrder(User user,
                                               @PathVariable("pid") Integer pid,
                                               @PathVariable("price") Float price){
        return orderService.createKillOrder(user,pid,price);
    }

前端页面秒杀测试

在sellDetail.html页面中添加订单秒杀JS方法。

<script>
    $(function(){
        $('.ch_a').click(function(){
            let pid=$(this).attr('alt');
            console.log(pid);
            $.post('http://zmall.com/order-serv/createKillOrder',{pid:pid},function(rs){
                console.log(rs);
                if(rs.code===200)
                    alert('秒杀成功');
                else
                    alert(rs.msg);
            },'json');
        });
    });
</script>

 这里虽然已经能正常展示秒杀效果,但是还是存在很多问题,比如:重复抢购问题等等问题。

注意:

$.post('http://user.zmall.com/userLogin',{
                loginName:loginName,
                password:password
            },function(rs){
                console.log(rs);
                if(rs.code===200){
                    location.href='http://product.zmall.com/index.html';
                }else{
                    alert(rs.msg);
                }
            },'json');

post方式不能跨二级域名发送请求,location.href可以跨二级域名发送请求;

$(function(){
       $('.ch_a').click(function(){
            let pid=$(this).attr("pid");
            let price=$(this).attr("price");
            console.log("pid=%s,price=%s",pid,price);
           $.post('http://zmall.com/order-serv/createKillOrder/'+pid+'/'+price,{},function(rs){
               console.log(rs);
               if(rs.code===200)
                   alert('秒杀成功');
               else
                   alert(rs.msg);
           },'json');
       });
    });

$.post('http://zmall.com/order-serv/createKillOrder/'+pid+'/'+price,{},function(rs){});能够正常访问;

$.post('http://order.zmall.com/createKillOrder/'+pid+'/'+price,{},function(rs){});则会出现跨域问题;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酒醉猫(^・ェ・^)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值