Redis实战——优惠券秒杀(一人一单业务)

news2024/11/27 17:46:39

需求:修改秒杀业务,要求同一个优惠券,一个用户只能下一单

我们只需要在增加订单之前,拿用户id和优惠券id判断订单是否已经存在,如果存在,说明用户已经购买。

代码实现:

package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService iSeckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.获取优惠券信息
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);

        //2.判断是否已经开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())){
            Result.fail("秒杀尚未开始!");
        }
        //3.判断是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())){
            Result.fail("秒杀已经结束了!");
        }
        //4.判断库存是否充足
        if (voucher.getStock() < 1) {
            Result.fail("库存不充足!");
        }
        //5.扣减库存
        boolean success = iSeckillVoucherService.update()
                .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0)
                .update();
        if (!success){
            Result.fail("库存不充足!");
        }
         Long userId = UserHolder.getUser().getId();
        //6.根据优惠券id和用户id判断订单是否已经存在
        //如果存在,则返回错误信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("用户已经购买!");
        }
        //7. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1添加订单id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2添加用户id
        voucherOrder.setUserId(userId);
        //7.3添加优惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回订单id
        return Result.ok(orderId);
    }
}

 但是,还没完,这种代码逻辑,在高并发的情况下还是会出现一个人购买购买多个的情况:

就是同一时间,多个线程来查询数据,都没有查到订单,都去创建了订单(高并发的情况下)

类似超卖问题,所以我们要进行上锁

这次就用悲观锁。

最简单的实现方法,就是把从查询订单是否存在到保存订单返回订单id这一段代码块进行封装成一个方法,然后在这个方法上加上synchronized关键字和spring事务。

如下:

package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService iSeckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.获取优惠券信息
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);

        //2.判断是否已经开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())){
            Result.fail("秒杀尚未开始!");
        }
        //3.判断是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())){
            Result.fail("秒杀已经结束了!");
        }
        //4.判断库存是否充足
        if (voucher.getStock() < 1) {
            Result.fail("库存不充足!");
        }
        //5.扣减库存
        boolean success = iSeckillVoucherService.update()
                .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0)
                .update();
        if (!success){
            Result.fail("库存不充足!");
        }
       
        return createVoucherOrder(voucherId);
        
    }

    @Transactional
    public synchronized Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //6.根据优惠券id和用户id判断订单是否已经存在
        //如果存在,则返回错误信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("用户已经购买!");
        }
        //7. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1添加订单id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2添加用户id
        voucherOrder.setUserId(userId);
        //7.3添加优惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回订单id
        return Result.ok(orderId);
    }
}

但是,这个方法就是使用了悲观锁,锁的对象是整个类对象,所有用户公用一把锁,就会导致串行执行,从而性能大大降低。

我们可以只锁上用户id,让他每个用户获得一把锁。

package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService iSeckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.获取优惠券信息
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);

        //2.判断是否已经开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())){
            Result.fail("秒杀尚未开始!");
        }
        //3.判断是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())){
            Result.fail("秒杀已经结束了!");
        }
        //4.判断库存是否充足
        if (voucher.getStock() < 1) {
            Result.fail("库存不充足!");
        }
        //5.扣减库存
        boolean success = iSeckillVoucherService.update()
                .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0)
                .update();
        if (!success){
            Result.fail("库存不充足!");
        }
        Long userId = UserHolder.getUser().getId();
        
        return createVoucherOrder(voucherId);
       
    }

    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //6.根据优惠券id和用户id判断订单是否已经存在
        synchronized (userId.toString().intern()){
            //如果存在,则返回错误信息
            int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            if (count > 0) {
                return Result.fail("用户已经购买!");
            }
            //7. 创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //7.1添加订单id
            Long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //7.2添加用户id
            voucherOrder.setUserId(userId);
            //7.3添加优惠券id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //8.返回订单id
            return Result.ok(orderId);
        } 
    }
}

 这里锁上userid时,除了用toString方法转成字符串,还使用intern方法的原因是:

toString方法的底层原理其实是new一个String对象,然后将其变成字符串,如果只锁上了加toString方法的userid,就有可能出现相同的userid,但是toString底层new出来的String对象不同,而多分了锁。所以使用intern方法来直接判断常量池中的string值是否一致,值一样的共用一把锁,这样就不会导致多分锁了。

但是但是,还没完因为这里我们是加了锁和事务,但是因为这个事务时Spring进行管理的,它会在我们代码块结束后才会去执行事务,也就是我们释放锁的时候,才会执行事务。这个时候,锁放开了,就会有其他线程进来,就很有可能出现事务提交带上了其他线程

我们可以这样进行改进:在本个方法上进行加锁。

package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService iSeckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.获取优惠券信息
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);

        //2.判断是否已经开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())){
            Result.fail("秒杀尚未开始!");
        }
        //3.判断是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())){
            Result.fail("秒杀已经结束了!");
        }
        //4.判断库存是否充足
        if (voucher.getStock() < 1) {
            Result.fail("库存不充足!");
        }
        //5.扣减库存
        boolean success = iSeckillVoucherService.update()
                .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0)
                .update();
        if (!success){
            Result.fail("库存不充足!");
        }
        Long userId = UserHolder.getUser().getId();
        synchronized (userId.toString().intern()){
            return createVoucherOrder(voucherId);
        }
    }

    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //6.根据优惠券id和用户id判断订单是否已经存在
        //如果存在,则返回错误信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("用户已经购买!");
        }
        //7. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1添加订单id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2添加用户id
        voucherOrder.setUserId(userId);
        //7.3添加优惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回订单id
        return Result.ok(orderId);
    }
}

但是但是但是,还没完。哈哈

我们只给创建订单这个方法(createVoucherOrder)加了事务,但是没给上面判断条件的方法加上事务,而我们锁代码块里执行的方法,其实是this.createVoucherOrder()方法,是没有加事务的方法调用的createVoucherOrder()方法,这个this可不是spring的事务代理对象,这就会导致事务失效。

解决方法就是,我们只需要拿到代理对象,然后通过代理对象调用我们这个加了事务的方法,也就是createVoucherOrder()方法。

使用 AopContext.currentProxy();方法来拿到代理对象

温馨提示 :使用这个方法前要先做两件事~

1. 记得在配置类似加上@EnableAspectJAutoProxy(exposeProxy = true)注解来暴露这个代理对象

2. 加上依赖:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

完整代码;: 

package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService iSeckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.获取优惠券信息
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);

        //2.判断是否已经开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())){
            Result.fail("秒杀尚未开始!");
        }
        //3.判断是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())){
            Result.fail("秒杀已经结束了!");
        }
        //4.判断库存是否充足
        if (voucher.getStock() < 1) {
            Result.fail("库存不充足!");
        }
        //5.扣减库存
        boolean success = iSeckillVoucherService.update()
                .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0)
                .update();
        if (!success){
            Result.fail("库存不充足!");
        }
        Long userId = UserHolder.getUser().getId();
        synchronized (userId.toString().intern()){
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }
    }

    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //6.根据优惠券id和用户id判断订单是否已经存在
        //如果存在,则返回错误信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("用户已经购买!");
        }
        //7. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1添加订单id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2添加用户id
        voucherOrder.setUserId(userId);
        //7.3添加优惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回订单id
        return Result.ok(orderId);
    }
}

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

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

相关文章

狂神说Go语言学习笔记(二)

一、匿名变量 匿名变量的特点是一个下划线 “_”&#xff0c;它本身就是一个特殊的标识符。它可以像其他标识符那样用于变量的声明或赋值&#xff08;任何类型都可以赋值给它&#xff09;&#xff0c;但任何赋给这个标识符的值都将被抛弃&#xff0c;因此这些值不能在后续的代…

共享雨伞app开发,提高资源利用率

共享经济无疑是当下热门行业之一&#xff0c;生活中随处可见的共享自行车、共享汽车、共享充电宝等一系列设备都是共享经济的产物。互联网时代下的共享经济已经呈现出多元化的发展趋势&#xff0c;其中在日常出行方面&#xff0c;共享雨伞的出现可以很好的解决用户偶遇的突发天…

Centos7 + kubenetes 一键安装实战

1. kubernetes部署环境要求 一台或多台机器&#xff0c;操作系统CentOS7.x-86_x64硬件配置&#xff1a;内存2GB或2G&#xff0c;CPU 2核或CPU 2核集群内各个机器之间能相互通信集群内各个机器可以访问外网&#xff0c;保证网络畅通&#xff08;最好能访问谷歌&#xff09;,需要…

【STM32笔记】HAL库中的SPI传输(可利用中断或DMA进行连续传输)

【STM32笔记】HAL库中的SPI传输&#xff08;可利用中断或DMA进行连续传输&#xff09; SPI 是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。 SPI&#xff0c;是一种高速的&…

优秀软件测试报告

Document number 文档编号 Confidentiality level 密级 Test Report-01 [绝密/秘密/内部公开] Document version 文档版本 Total 10 pages 共 10 页 V1.0 测试报告 Prepared by 拟制 Date 日期 yyyy-mm-dd Reviewed by 评审人 Date 日期 yyyy-mm-dd Approved b…

CSS基础(超详解)

目录 1 css基础知识 1.1基础知识 1.1 概述 1.2 优势 1.3应用css的步骤 1.2 css基本语法 1.3 css使用方法 1.3.1 内嵌样式 1.3.2 内部样式 1.3.3 外部样式 1.3.4 使用import引入外部样式文件 1.3.5 叠层样式优先级 1.3.6 注释 2 CSS选择器 2.1 元素选择器 2.2 类…

Linux crontab定时任务设置

1.检查是否安装了crontab # rpm -qa | grep crontab 2.重启crontab服务 一定重启&#xff0c;这样确保了crontab服务的开启 # /etc/init.d/crond restart 3.我们直接以root用户进行操作。所以直接进到root的cron配置文件。 # cd /var/spool/cron/ 4.可以查看一下 root 用户…

VisDrone数据集之集群检测(一)

VisDrone坐标信息 VisDrone数据集格式: txt标签内容为&#xff1a;bbox_left&#xff0c;bbox_top&#xff0c;bbox_width&#xff0c;bbox_height&#xff0c;score,object_category&#xff0c;truncation&#xff0c;occlusion 类别&#xff1a; ignored regions(0), pede…

【一】曾经那些错误,你又踩坑了吗?

巩固基础和不断练习&#xff0c;我们才会进步&#xff01;这一期开始&#xff0c;我们就要看看我们之前的学过的知识到底掌握了没有&#xff0c;再通过练习去巩固&#xff01; 目录 一、for循环 二、指针有关内容 1.*&#xff08;解引用&#xff09;和其他运算符 2.指针变…

碰到CTS问题我该如何处理?

一、什么是CTS&#xff1f; CTS测试全称为系统兼容测试&#xff08;Compatibility Test suite&#xff09;&#xff0c;CTS是为了测试手机是否符合google定义的兼容性规范&#xff08;Compatibility Definition&#xff09;。从而基于Android的应用程序能在基于同一个api版本的…

网页JS自动化脚本(四)修改元素的尺寸颜色显隐状态

修改元素尺寸 在定位到了元素之后, 我们就可以对元素进行一些修改了,我们先来修改元素泊宽度以及高度 window.onloadfunction(){var theElementdocument.querySelector("img.undertips-link-lefticon");theElement.style.width"100px";theElement.style.…

BL200EC如何与欧姆龙相连

1、欧姆龙 NX1P2 的 Port1 接到电脑的网口&#xff0c;Port2 接到 BL200EC 的 ETH1 网口。 欧姆龙 NX1P2 和 BL200EC 通电。 2、打开 Sysmac Studio 软件&#xff0c;新建工程&#xff0c;选择 NX1P2-9024DT&#xff0c;点击创建。 ​ 编辑切换为居中 添加图片注释&#xf…

【云原生】服务网格kiali开发环境搭建问题解析

文章目录背景环境准备参考连接问题1&#xff1a;windows如何配置kiali开发环境问题2&#xff1a;kiali如何配置k8s连接问题3&#xff1a;kiali启动报错关键词&#xff1a;方便你们找到我背景 kiali&#xff08;凯莉&#xff09;是istio技术对应的管理系统&#xff0c;其前端为…

概率论与数理统计(1)——基本概念

引言 确定性&#xff08;必然&#xff09;&#xff1a;一定发生与一定不发生 随机现象&#xff1a;可能发生也可能不发生 随机事件 实验&#xff1a;对事物进行观察测量 随机试验&#xff1a;&#xff08;1&#xff09;在相同的条件下可以重复&#xff1b;&#xff08;2&…

oracle使用小结

1.安装时oracle12C去除勾选为创建为容器数据库 2.cmd命令 <1>.sqlplus/nolog // –运行sqlplus命令&#xff0c;进入sqlplus环境&#xff0c;nolog参数表示不登录&#xff1b; <2>.conn/as sysdba // **–以系统管理员(sysdba)**的身份连接数据库&#xff1b; …

[附源码]计算机毕业设计springboot健康医疗体检

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

记录--用JS轻松实现一个录音、录像、录屏的工具库

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 最近项目遇到一个要在网页上录音的需求&#xff0c;在一波搜索后&#xff0c;发现了 react-media-recorder 这个库。今天就跟大家一起研究一下这个库的源码吧&#xff0c;从 0 到 1 来实现一个 R…

EL 与 JSTL(1)( EL 表达式基础知识)

EL 表达式用美元符号“$”定界&#xff0c;内容包含在一对花括号“{}”中&#xff0c;例如&#xff1a;${expression}。 EL 表达式语法很简单&#xff0c;它最大的特点就是使用很方便。 1.EL 表达式特点 &#xff08;1&#xff09;在 EL 表达式中可以获得命名空间&#xff08;…

uni-app入门:全局数据共享方案之mobx

1.全局数据共享介绍 2.准备工作 3.使用说明 3.1 page页面进行全局数据共享 3.2 component组件进行全局数据共享 1.全局数据共享介绍全局数据共享也叫作状态管理,主要用于组件间数据共享问题的处理.实际开发中常用的实现方案:vuex、redux、mobx&…

西山科技将于12月6日上会:年收入2亿元,耗材收入成为新增长点

11月29日&#xff0c;上海证券交易所科创板披露的信息显示&#xff0c;重庆西山科技股份有限公司&#xff08;下称“西山科技”&#xff09;将于2022年12月6日接受科创板上市委的现场审议。目前&#xff0c;西山科技已经更新了招股书&#xff08;上会稿&#xff09;。 据贝多财…