微服务项目【秒杀商品展示及商品秒杀】

本文介绍了如何从zmall-common的pom.xml中移除spring-session-data-redis依赖,改用Redis直接存储用户登录信息以优化jmeter压测。具体步骤包括配置RedisTemplate,创建RedisService接口及其实现,自定义参数解析器UserArgumentResolver,以及调整用户登录业务逻辑。此外,还涉及到了移除Seata分布式事务、更换数据库连接池为Hikari以及生成秒杀订单的流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

登录方式调整

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EGgFVLjU-1676371517002)(images\20220817224735.jpg)]

注意:
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;
    }
}

第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步:配置自定义参数解析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模块中的控制台是否已经获取到登录用户对象信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nf3XjJz0-1676371517006)(images\20220817233131.jpg)]

生成秒杀订单

绑定秒杀商品

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

 <#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.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.id}">${g.name}</a></h2>
             倒计时:<span>1200</span> 时 <span>30</span> 分 <span>28</span> 秒
             </div>
         </div>
     </#list>
 </#if>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A1zL4Qix-1676371517008)(images\2022-08-18_164403.png)]

查看秒杀商品

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWNimZH4-1676371517017)(images\2022-08-18_164751.png)]

订单秒杀

移除seata相关

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

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

第3步:移除zmall-order中分布式事务案例中的@GlobalTransactional注解

第4步:删除DataSourceProxyConfig该类

第5步:移除zmall-order中启动类上的注解参数(exclude = DataSourceAutoConfiguration.class)

//更改前:
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
//更改后:
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan({"com.zking.zmall.mapper"})
@EnableFeignClients
public class ZmallOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZmallOrderApplication.class, args);
    }

}

第6步:更换zmall-order和zmall-product中的application.yml配置中的数据库连接池

datasource:
   #type连接池类型 DBCP,C3P0,Hikari,Druid,默认为Hikari
   #更改前:
   #type: com.alibaba.druid.pool.DruidDataSource
   #更改后:
   type: com.zaxxer.hikari.HikariDataSource

生成秒杀订单

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

IOrderService

public interface IOrderService extends IService<Order> {

    JsonResponseBody<?> createKillOrder(User user, Integer pid);
}

OrderServiceImpl

@Transactional
@Override
public JsonResponseBody<?> createKillOrder(User user, Integer pid) {
    //1.判断用户是否登录
    if(null==user)
    	throw new BusinessException(JsonResponseStatus.TOKEN_ERROR);
    //根据用户ID和秒杀商品Id判断。。。。
    //2.根据秒杀商品编号获取秒杀商品库存是否为空
    Kill kill = killService.getOne(new QueryWrapper<Kill>().eq("item_id",pid));
    if(kill.getTotal()<1)
    	throw new BusinessException(JsonResponseStatus.STOCK_EMPTY);
    //3.根据商品ID获取商品
    Product product = productService.getProductById(pid);
    //4.秒杀商品库存减一
    killService.updateKillStockById(pid);
    //5.生成秒杀订单及订单项
    SnowFlake snowFlake=new SnowFlake(2,3);
    Long orderId=snowFlake.nextId();
    //创建订单
    Order order=new Order();
    order.setUserId(user.getId());
    order.setLoginName(user.getLoginName());
    order.setCost(product.getPrice());
    order.setSerialNumber(orderId);
    this.save(order);
    //创建订单项
    OrderDetail orderDetail=new OrderDetail();
    orderDetail.setOrderId(orderId);
    orderDetail.setProductId(product.getId());
    orderDetail.setQuantity(1);
    orderDetail.setCost(product.getPrice());
    orderDetailService.save(orderDetail);
    return new JsonResponseBody();
}

前端页面秒杀测试

在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>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LM7Ibg8f-1676371517019)(images\2022-08-18_171847.png)]

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

utils:https://pan.baidu.com/s/1ExaC4GgEg_ofKsARkYhHXw
提取码:kq20

该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值