开发微服务电商项目演示(五)

news2024/11/29 12:40:52

登录方式调整

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

注意:本章节中不采用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
参考地址: https://blog.csdn.net/hunger_wang/article/details/118713579?spm=1001.2014.3001.5501

第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层

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);
}
@Override
    public List<Map<String, Object>> queryKillProdNews() {
        return productMapper.queryKillProdNews();
    }

    @Override
    public Map<String, Object> queryKillProdById(Integer pid) {
        return productMapper.queryKillProdById(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){});则会出现跨域问题;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/344750.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用ArcGIS转换坐标

1.概述大家都知道ArcGIS提供了坐标转换功能&#xff0c;在我们手里的数据坐标系千差万别&#xff0c;经常会遇到转换坐标的时候&#xff0c;那么是否可以用ArcGIS进行转换&#xff1f;答案是肯定的&#xff0c;但是转换的过程比较复杂&#xff0c;这里为大家介绍一下转换的方法…

微软 new Bing 通过 ChatGPT 加持后搜索体验暴增,国内该如何申请使用那?

就在近期微软公布了自家的 Bing 将加持 ChatGPT 推出新版&#xff0c;消息一出 Bing 的下载量直接翻了 10 倍以上&#xff0c;48小时内就已经有 100 万名新用户申请加入了 还可以申请试用 new bing 吗&#xff1f; 目前用户还无法直接访问新版 bing &#xff0c;还是要前往申…

从一次有趣的漏洞分析到一个有趣的PHP后门

起因 事情的起因很有趣&#xff0c;前几天我正对着电脑发呆的时候&#xff0c;突然有个安全交流群的群友来找我交流一个问题 大概的意思就是&#xff0c;他在挖SRC的时候&#xff0c;发现一处资产存在目录遍历漏洞&#xff0c;它通过这个漏洞&#xff0c;找到目标资产使用了一…

基于图像的相机定位:概述

虚拟现实、增强现实、机器人和自动驾驶最近引起了学术界和工业界的广泛关注&#xff0c;其中基于图像的相机定位是一项关键任务。然而&#xff0c;还没有关于基于图像的相机定位的完整评论。迫切需要映射这个主题&#xff0c;使个人能够快速进入该领域。在本文中&#xff0c;概…

【Airplay_BCT】Bonjour conformance tests苹果IOT

从Airplay开始&#xff0c;接触到BCT&#xff0c;这是什么&#xff1f;被迫从安卓变成ios用户和开发。。。开始我的学习之旅&#xff0c;记录成长过程&#xff0c;不定时更新 Bonjour 下面是苹果官网关于bonjour的解释 Bonjour, also known as zero-configuration networking, …

pinia实战 购物车(自定义插件实现pinia持久化)

目录 一、实例 二、需求 三. 代码解析 shop.vue shop.ts 四、持久化插件 插件介绍 持久化实现思路 一、实例 二、需求 单选全选功能&#xff0c;并且可以互相联动 小计功能 总计功能 商品加减&#xff0c;数量为零时不能在减 三. 代码解析 shop.vue 1.获取shop模块实…

AttributeError: module ‘lib‘ has no attribute ‘OpenSSL_add_all_algorithms

pip安装crackmapexec后,运行crackmapexec 遇到报错 AttributeError: module lib has no attribute OpenSSL_add_all_algorithms 直接安装 pip3 install crackmapexec 解决 通过 python3 -m pip install --upgrade openssl 或者 python3 -m pip install openssl>22.1.…

YOLOv5/v7 Flask Web 车牌识别 | YOLOv7 + EasyOCR 实现车牌识别

YOLOv7 Flask Web 车牌识别图片效果展示 本篇博文只包含源码以及使用方式,目前不同提供详细开发教程。 YOLOv7 Flask Web 车牌识别视频效果展示 YOLOv7 + EasyOCR 实现车牌识别 什么是Flask? 简介 Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更…

【Opencv实战】几十年前的Vlog火了:黑白老照片如何上色?这黑科技操作一定要知道,复原度超高,竟美的出奇~(图像修复神级代码)

导语 哈喽大家好呀&#xff01;我是每天疯狂赶代码的木木子吖&#xff5e;情人节快乐呀&#xff01; 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 我们都知道&#xff0c;有很多经典的老照片…

云原生时代顶流消息中间件Apache Pulsar部署实操-上

文章目录安装运行时Java版本推荐Locally Standalone集群启动验证部署分布式集群部署说明初始化集群元数据部署BookKeeper部署BrokerAdmin客户端和验证Tiered Storage(层级存储)概述支持分级存储何时使用工作原理安装 运行时Java版本推荐 Locally Standalone集群 启动 # 下载…

Eureka集群搭建教程

前言&#xff1a; 为了提升注册中心稳定性&#xff0c;防止注册中心宕机后&#xff0c;服务不可用的情况&#xff0c;我们可以通过将Eureka注册中心搭建成集群模式&#xff0c;当一台注册中心微服务宕机后&#xff0c;另一台依然可以支持服务的注册与发现。本文将讲解下如何搭…

在Spring-boot中操作MongoDB

MongoDB是一个基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系数据库的。在使用spring-boot操作MongoDB数据…

项目介绍 + 定长内存池设计及实现

你好&#xff0c;我是安然无虞。 文章目录项目介绍当前项目做的是什么?技术栈内存池是什么?池化技术内存池内存池主要解决的问题malloc定长内存池学习目的定长内存池设计项目介绍 当前项目做的是什么? 这个项目是实现一个高并发的内存池, 它的原型是 Google 的一个开源项…

C++——哈希3|位图

目录 常见哈希函数 位图 位图扩展题 位图的应用 常见哈希函数 1. 直接定址法--(常用) 这种方法不存在哈希冲突 取关键字的某个线性函数为散列地址&#xff1a;Hash&#xff08;Key&#xff09; A*Key B 优点&#xff1a;简单、均匀 缺点&#xff1a;需要事先知道关键字的…

C语言学习笔记(八): 自定义数据类型

结构体变量 什么是结构体 C语言允许用户自己建立由不同类型数据组成的组合型的数据结构&#xff0c;它称为结构体 结构体的成员可以是任何类型的变量&#xff0c;如整数&#xff0c;字符串&#xff0c;浮点数&#xff0c;其他结构体&#xff0c;指针等 struct Student //s…

streamlit自定义组件教程和组件开发环境配置

About create your own component&#xff1a; you can follow this tutorial streamlit tutorial 重要&#xff01;以下步骤都是在教程的基础上更改的。这个教程做的很棒。 Component development environment configuration&#xff1a; 根据文章 https://streamlit-com…

【iOS】APP IM聊天框架的设计(基于第三方SDK)

【iOS】APP IM聊天框架的设计&#xff08;基于第三方SDK&#xff09; 前言 在开发社交聊天类型的APP的时候&#xff0c;IM是必不可少的功能&#xff0c;而且很多公司的IM服务都是接的第三方的&#xff0c;很少用自研的&#xff0c;国内的IM厂商也都很成熟&#xff0c;本文所有…

基于文心大模型套件ERNIEKit实现文本匹配算法,模块化方便应用落地

文心大模型,产业级知识增强大模型介绍 官网:https://wenxin.baidu.com/ 文心大模型开发套件ERNIEKit,面向NLP工程师,提供全流程大模型开发与部署工具集,端到端、全方位发挥大模型效能。 提供业界效果领先的ERNIE 3.0系列开源模型和基于ERNIE的前沿任务模型,满足企业和开…

暴力破解(new)

数据来源 本文仅用于信息安全的学习&#xff0c;请遵守相关法律法规&#xff0c;严禁用于非法途径。若观众因此作出任何危害网络安全的行为&#xff0c;后果自负&#xff0c;与本人无关。 01 暴力破解介绍及应用场景 》暴力破解介绍 》暴力破解字典 GitHub - k8gege/Passwor…

QT(56)-动态链接库-windows-导出变量-导出类

1.导出变量 1.1不使用_declspec(dllimport) _declspec(dllexport) 使用_declspec(dllimport) _declspec(dllexport) 1.2win32 mydllwin32 myexe 1.3win32 mydllqt myexe 2.导出类 使用_declspec(dllimport) _declspec(dllexport) 2.1不用关键…