单机环境下一人一单

news2024/9/21 22:55:10

优惠券秒杀

添加优惠卷

店铺发布优惠券又分为平价券和特价券, 平价券可以任意购买而特价券需要秒杀抢购(限制数量和时间)

tb_voucher(平价券): 优惠券的基本信息

在这里插入图片描述

tb_seckill_voucher(秒杀券): 有voucher_id字段表示具有优惠卷的基本信息,此外还有库存,开始抢购时间,结束抢购时间等特殊字段

在这里插入图片描述

VoucherController提供了一个接口方法用来添加普通优惠券

/**
 * 新增普通券
 * @param voucher 优惠券信息
 * @return 优惠券id
 */
@PostMapping
public Result addVoucher(@RequestBody Voucher voucher) {
    // 直接将普通券的信息保存到普通券表中
    voucherService.save(voucher);
    return Result.ok(voucher.getId());
}

VoucherController提供了一个接口方法用来添加秒杀券,在VoucherService中的addSeckillVoucher方法实现添加秒杀券的业务逻辑

/**
 * 新增秒杀券
 * @param voucher 优惠券信息,包含秒杀券信息(库存,生效时间,失效时间)
 * @return 优惠券id
 */
@PostMapping("seckill")
public Result addSeckillVoucher(@RequestBody Voucher voucher) {
    voucherService.addSeckillVoucher(voucher);
    return Result.ok(voucher.getId());
}
// 新增秒杀券就是在数据库中新增普通卷和秒杀券的信息
@Override
@Transactional// 因为是操作两张表所以需要添加事务
public void addSeckillVoucher(Voucher voucher) {
    // 将秒杀券的基本信息保存到普通券表中,如果没有指定Id会自动生成
    save(voucher);
    // 保存秒杀信息
    SeckillVoucher seckillVoucher = new SeckillVoucher();
    // 关联普通券id
    seckillVoucher.setVoucherId(voucher.getId());
    // 设置库存
    seckillVoucher.setStock(voucher.getStock());
    // 设置开始时间
    seckillVoucher.setBeginTime(voucher.getBeginTime());
    // 设置结束时间
    seckillVoucher.setEndTime(voucher.getEndTime());
    // 保存信息到秒杀券表中
    seckillVoucherService.save(seckillVoucher);
}

添加秒杀券: 由于没有后台管理页面,使用Postman模拟发送POST请求http://localhost:8081/voucher/seckill来新增秒杀券(截止日期要超过当前日期否则不显示)

{
    "shopId":1,
    "title":"100元代金券",
    "subTitle":"周一至周五可用",
    "rules":"全场通用\\n无需预约\\n可无限叠加",
    // 数据库中金额的单位是分
    "payValue":8000,
    "actualValue":10000,
    "type":1,
    "stock":100,
    "beginTime":"2022-01-01T00:00:00",
    "endTime":"2023-10-31T23:59:59"
}

实现秒杀下单

当我们点击抢购时会触发右侧的请求,我们只需要在VoucherOrderController编写对应的Controller处理请求即可

在这里插入图片描述

当用户开始进行下单我们应当提交优惠券Id去查询优惠卷信息,然后判断是否满足秒杀条件即秒杀是否开始和库存是否充足

在这里插入图片描述

@RestController
@RequestMapping("/voucher-order")
public class VoucherOrderController {
    @Autowired
    private IVoucherOrderService voucherOrderService;
    @PostMapping("/seckill/{id}")
    public Result seckillVoucher(@PathVariable("id") Long voucherId) {
        return voucherOrderService.seckillVoucher(voucherId);
    }
}

public interface IVoucherOrderService extends IService<VoucherOrder> {
    Result seckillVoucher(Long voucherId);
}

@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Autowired
    private RedisIdWorker redisIdWorker;
    @Override
    @Transactional// 操作两张表应该加上事务
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券的基本信息
        //SeckillVoucher seckillVouche = seckillVoucherService.getById(voucherId)
        LambdaQueryWrapper<SeckillVoucher> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SeckillVoucher::getVoucherId, voucherId);
        SeckillVoucher seckillVoucher = seckillVoucherService.getOne(queryWrapper);
        //2.判断秒杀时间是否开始
        if (LocalDateTime.now().isBefore(seckillVoucher.getBeginTime())) {
            return Result.fail("秒杀还未开始,请耐心等待");
        }
        //3.判断秒杀时间是否结束
        if (LocalDateTime.now().isAfter(seckillVoucher.getEndTime())) {
            return Result.fail("秒杀已经结束!");
        }
        //4.判断库存是否充足
        if (seckillVoucher.getStock() < 1) {
            return Result.fail("优惠券已被抢光了哦,下次记得手速快点");
        }
        //5.扣减库存
        boolean success = seckillVoucherService.update()
            .setSql("stock = stock - 1")
            .eq("voucher_id",voucherId)
            .update();
        if (!success) {
            return Result.fail("库存不足");
        }
        //6. 创建订单	
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1 设置生成的订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //6.2 设置用户id
        Long id = UserHolder.getUser().getId();
        voucherOrder.setUserId(id);
        //6.3 设置代金券id
        voucherOrder.setVoucherId(voucherId);
        //7. 将订单数据保存到订单表中
        save(voucherOrder);
        //8. 返回订单id
        return Result.ok(orderId);
    }

库存超卖问题

当遇到高并发场景时会出现超卖现象,我们可以用Jmeter开200个线程来模拟抢100张优惠券的场景,结果优惠券库存为负数表示出现了超卖现象

  • 添加请求的信息头管理器携带我们登录的token(可能会过期),然后发起POST请求http://localhost:8081/voucher-order/seckill/voucher_id

在这里插入图片描述

在这里插入图片描述

乐观锁的两种实现方式

超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁(悲观锁或乐观锁)

  • 悲观锁: 悲观锁比较适合插入数据,简单粗暴但是性能一般
  • 乐观锁: 比较适合更新数据, 性能好但是成功率低(多个线程同时执行时只有一个可以执行成功),还需要访问数据库造成数据库压力过大

在这里插入图片描述

版本号法: 给数据库表增加一个版本号version字段,每次操作表中的数据时会查询版本号,修改数据时再次验证版本号有没有变化,没有变化才可以更新数据

在这里插入图片描述

CAS(Compare-And-Switch): 首先查询要修改字段的值,在修改数据时再次验证字段值有没有发生变化(或满足某种条件),没有变化(或满足条件)才会更新字段的值

在这里插入图片描述

乐观锁解决超卖问题

使用stock来充当版本号,VoucherOrderServiceImpl在扣减库存时比较查询到的优惠券库存和实际数据库中优惠券库存是否相同

  • 假设100个线程同时都拿到了100的库存, 但是100个人中只有1个人能扣减成功(其他的人在扣减时库存发现库存已经被修改过了,所以不再执行扣减操作)
boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1") //set stock = stock -1
            .eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update(); //where id = ? and stock = ?

使用stock>0 充当判断条件, 在扣减库存时只要判断是否有剩余优惠券,即只要数据库中的库存大于0都能顺利完成扣减库存操作

boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).gt("stock",0).update(); where voucher_id = ? and stock > 0

单机环境下一人一单(悲观锁)

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

  • 如果时间和库存都充足,还需要根据优惠卷id和用户id查询是否已经下过这个订单,如果下过这个订单则不能再下单

在这里插入图片描述

在VoucherOrderServiceImpl中库存和时间都充足时即将扣减库存之前再增加一人一单逻辑

一个用户开了多个线程抢优惠券,在判断库存充足之后和执行一人一单逻辑之前间如果进来了多个线程,此时它们都在数据库中查询不到订单然后都会执行扣减操作

// 4. 判断库存是否充足
if (seckillVoucher.getStock() < 1) {
    return Result.fail("优惠券已被抢光了哦,下次记得手速快点");
}

// 5.根据用户id查询用户对应的订单是否存在
Long userId = UserHolder.getUser().getId();
int count = query().eq("voucher_id", voucherId).eq("user_id", userId).count();
if (count > 0) {
    // 用户已经下过单
    return Result.fail("您已经抢过优惠券了哦");
}
// 6.使用stock>0充当判断条件,在扣减库存时只要判断是否有剩余优惠券,即只要数据库中的库存大于0都能顺利完成扣减库存操作
boolean success = seckillVoucherService.update()
    .setSql("stock= stock -1")
    .eq("voucher_id", voucherId).gt("stock",0).update(); //where voucher_id = ? and stock > 0
if (!success) {
    return Result.fail("库存不足");
}

把一人一单逻辑之后生成订单记录的代码都提取到一个createVoucherOrder方法中(ctrl + alt + m)然后加悲观锁synchronized(悲观锁适合插入数据)

  • 不管哪一个线程运行到这个方法时都要检查有没有其它线程正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程结束
  • 把锁加在createVoucherOrder方法上锁的范围太大(粒度太粗)会导致每个线程进来都会锁住,锁的对象是this所有用户都公用一把锁串行执行会导致效率很低
@Transactional
public synchronized Result createVoucherOrder(Long voucherId) {
    // 5.根据用户id查询用户对应的订单是否存在
    Long userId = UserHolder.getUser().getId();
    int count = query().eq("voucher_id", voucherId).eq("user_id", userId).count();
    // 判断用户是否下过单
    if (count > 0) {
        return Result.fail("您已经抢过优惠券了哦");
    }
    // 6.使用stock>0充当判断条件,在扣减库存时只要判断是否有剩余优惠券,即只要数据库中的库存大于0都能顺利完成扣减库存操作
    boolean success = seckillVoucherService.update()
        .setSql("stock= stock -1")
        .eq("voucher_id", voucherId).gt("stock",0).update(); //where voucher_id = ? and stock > 0
    if (!success) {
        return Result.fail("库存不足");
    }
    //7. 创建订单
    VoucherOrder voucherOrder = new VoucherOrder();
    //7.1 设置订单id
    long orderId = redisIdWorker.nextId("order");
    voucherOrder.setId(orderId);
    //7.2 设置用户id
    Long id = UserHolder.getUser().getId();
    voucherOrder.setUserId(id);
    //7.3 设置代金券id
    voucherOrder.setVoucherId(voucherId);
    //8. 将订单数据保存到表中
    save(voucherOrder);
    //9. 返回订单id
    return Result.ok(orderId);
}

要完成一人一单的业务应该把这个锁只加在单个用户上(用户标识可以用userId), 如果我们直接使用userId.toString()每次锁住的都不是同一个String对象

方法名功能
String intern()从常量池中拿数据,如果字符串常量池中已经包含了一个等于这个String对象的字符串(由equals方法确定)将返回池中的字符串
如果没有则将此String对象添加到池中并返回对此String对象的引用
public static String toString(long i) {
    if (i == Long.MIN_VALUE)
        return "-9223372036854775808";
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
    Long userId = UserHolder.getUser().getId();
    // toString的源码是new String所以userId.toString()拿到的也不是同一个String对象即不是同一个用户/不是同一把锁
    synchronized (userId.toString().intern()) {
        // 5.根据用户id查询用户对应的订单是否存在
        int count = query().eq("voucher_id", voucherId).eq("user_id", userId).count();
        // 判断用户是否下过单
        if (count > 0) {
            return Result.fail("您已经抢过优惠券了哦");
        }
        // 6.使用stock>0充当判断条件,在扣减库存时只要判断是否有剩余优惠券,即只要数据库中的库存大于0都能顺利完成扣减库存操作
        boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).gt("stock",0).update(); //where voucher_id = ? and stock > 0
        if (!success) {
            return Result.fail("库存不足");
        }
        //7. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1 设置订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2 设置用户id
        Long id = UserHolder.getUser().getId();
        voucherOrder.setUserId(id);
        //7.3 设置代金券id
        voucherOrder.setVoucherId(voucherId);
        //8. 将订单数据保存到表中
        save(voucherOrder);
        //9. 返回订单id
        return Result.ok(orderId);

    }
    //执行到这里锁已经被释放了但是可能当前事务还未提交,如果此时有线程进来不能确保事务不出问题
}

createVoucherOrder方法被Spring的事务控制,如果你在方法内部加锁可能会导致当前方法事务还没有提交但是锁已经释放了,此时新增的订单还没有写入数据库

  • 在seckillVoucher方法中将createVoucherOrder方法整体包裹起来, 保证事务提交之后才会释放锁确保数据库中有订单存在,同时控制锁的粒度
@Override
public Result seckillVoucher(Long voucherId) {
    LambdaQueryWrapper<SeckillVoucher> queryWrapper = new LambdaQueryWrapper<>();
    //1. 查询优惠券
    queryWrapper.eq(SeckillVoucher::getVoucherId, voucherId);
    SeckillVoucher seckillVoucher = seckillVoucherService.getOne(queryWrapper);
    //2. 判断秒杀时间是否开始
    if (LocalDateTime.now().isBefore(seckillVoucher.getBeginTime())) {
        return Result.fail("秒杀还未开始,请耐心等待");
    }
    //3. 判断秒杀时间是否结束
    if (LocalDateTime.now().isAfter(seckillVoucher.getEndTime())) {
        return Result.fail("秒杀已经结束!");
    }
    //4. 判断库存是否充足
    if (seckillVoucher.getStock() < 1) {
        return Result.fail("优惠券已被抢光了哦,下次记得手速快点");
    }
    // 获取用户id
    Long userId = UserHolder.getUser().getId();
    synchronized (userId.toString().intern()) {
        return createVoucherOrder(voucherId);
    }
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
    // toString的源码是new String所以userId.toString()拿到的也不是同一个String对象即不是同一个用户/不是同一把锁
    synchronized (userId.toString().intern()) {
        // 5.根据用户id查询用户对应的订单是否存在
        int count = query().eq("voucher_id", voucherId).eq("user_id", userId).count();
        // 判断用户是否下过单
        if (count > 0) {
            return Result.fail("您已经抢过优惠券了哦");
        }
        // 6.使用stock>0充当判断条件,在扣减库存时只要判断是否有剩余优惠券,即只要数据库中的库存大于0都能顺利完成扣减库存操作
        boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).gt("stock",0).update(); //where voucher_id = ? and stock > 0
        if (!success) {
            return Result.fail("库存不足");
        }
        //7. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1 设置订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2 设置用户id
        Long id = UserHolder.getUser().getId();
        voucherOrder.setUserId(id);
        //7.3 设置代金券id
        voucherOrder.setVoucherId(voucherId);
        //8. 将订单数据保存到表中
        save(voucherOrder);
        //9. 返回订单id
        return Result.ok(orderId);

    }
    //执行到这里锁已经被释放了但是可能当前事务还未提交,如果此时有线程进来不能确保事务不出问题
}

由于seckillVoucher方法没有加事务注解,所以调用createVoucherOrder方法是this.的方式调用的,this此时是VoucherOrderServiceImpl没有事务功能

  • 事务想要生效需要利用VoucherOrderServiceImpl的代理对象,所以我们需要获得原始的事务对象来操作事务

  • 使用AopContext.currentProxy()获取当前对象的代理对象(具有事务功能),然后再用代理对象调用方法底层需要使用aspectjweaver依赖

  • 获取事务的代理对象需要在IVoucherOrderService中创建createVoucherOrder方法

  • 在启动类上加上@EnableAspectJAutoProxy(exposeProxy = true)注解

public interface IVoucherOrderService extends IService<VoucherOrder> {
    Result seckillVoucher(Long voucherId);
    Result createVoucherOrder(Long voucherId);
}
Long userId = UserHolder.getUser().getId();
synchronized (userId.toString().intern()) {
    IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
    return proxy.createVoucherOrder(voucherId);
}
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class HmDianPingApplication {
    public static void main(String[] args) {
        SpringApplication.run(HmDianPingApplication.class, args);
    }
}

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

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

相关文章

世界第一个语言不通的人是如何沟通的?

引言&#xff1a;语言是人类交流的重要工具&#xff0c;但在人类历史的某个时刻&#xff0c;肯定会有这样一位勇敢的先驱&#xff0c;他成为了世界上第一个语言不通的人。那么在他面临交流难题时&#xff0c;他是如何与他人沟通的呢&#xff1f;本文将对此进行探索。主体&#…

Nginx+Tomcat实现负载均衡和动静分离

目录 前瞻 动静分离和负载均衡原理 实现方法 实验&#xff08;七层代理&#xff09; 部署Nginx负载均衡服务器(192.168.75.50:80) 部署第一台Tomcat应用服务器&#xff08;192.168.75.60:8080&#xff09; 多实例部署第二台Tomcat应用服务器&#xff08;192.168.75.70:80…

【超图】SuperMap iClient3D for WebGL/WebGPU —— 单体gltf模型与Blender中的方向对应关系

作者&#xff1a;taco 在很多包含动画的场景中&#xff0c;像模拟小人的行走、模拟火车的轨迹运行&#xff0c;又或者是模拟风力发电等等等。我们通常会加一些动画模型到里面。而有的时候可能会出现&#xff0c;这火车怎么倒着走啊&#xff01;这人怎么头朝下啊。这种方向的问题…

【MySQL】MySQL库的增删查改

文章目录 1.库的操作1.1创建数据库1.2创建数据库案例 2.字符集和校验规则2.1查看系统默认字符集以及校验规则2.2查看数据库支持的字符集2.3查看数据库支持的字符集校验规则2.4校验规则对数据库的影响 3.操纵数据库3.1查看数据库3.2显示创建语句3.3修改数据库3.4数据库删除3.5备…

DevOps搭建(五)-JDK安装详细步骤

1、官网下载 官方网站下载JDK&#xff0c;这里我们安装JDK8 https://docs.oracle.com/javase/8/docs/technotes/guides/install/install_overview.html 点击上图中的Java SE Downloads项目&#xff0c;也可直接点击下面链接进入&#xff1a; Java Downloads | Oracle 往下滚…

Android14创建Pixel6 Pro模拟器(一百七十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

C++设计模式-Builder 构建器

通过“对象创建” 模式绕开new&#xff0c;来避免对象创建&#xff08;new&#xff09;过程中所导致的紧耦合&#xff08;依赖具体类&#xff09;&#xff0c;从而支持对象创建的稳定。它是接口抽象之后的第一步工作。 一、动机 在软件系统中&#xff0c;有时候面临着“一个复…

STM32--Wi-Fi插座_风扇_灯

项目需求 两个互相通信的双方&#xff0c;波特率必须相同!!!!!! 通过 ESP8266 模块&#xff0c;实现手机控制 wifi 插座 / 风扇 / 灯。 项目设计 串口 1 用于与 ESP8266 通讯&#xff0c;串口 2 连接 PC &#xff0c;用于打印 log &#xff0c;查看系统状态。 项目实现 注意&a…

网络安全项目实战(四)--报文检测

8. TCP/UDP 段 目标 了解 TCP 段头的组织结构了解 UDP 段头的组织结构掌握 TCP/UDP 段的解析方式 8.1. UDP 段格式 下图是UDP的段格式&#xff08;该图出自[TCPIP]&#xff09;。 8.2. UDP头部 //UDP头部&#xff0c;总长度8字节// /usr/include/linux/udp.h struct udphdr …

拦截器实现指定的IP白名单进行访问规定的Controller

需求&#xff1a;只允许内网的IP&#xff08;也就是IP白名单&#xff09;进行访问VideoController和ImgController&#xff0c;其余的FontController可以随便访问不做限制 总体的项目结构&#xff1a; 1、先写好业务代码三个Controller 访问的路径分别是&#xff1a; /api/…

【三视图】咒语 生成人物

revAnimated_v122.safetensors 杰作&#xff0c;最佳质量&#xff0c;角色设计&#xff0c;三视图&#xff0c;前视图&#xff0c;侧视图&#xff0c;后视觉&#xff0c;呆萌&#xff0c;可爱&#xff0c;简单的背景&#xff0c; (badhandv4:1.4),ng_deepnegative_v1_75t,negat…

qt-C++笔记之模拟实现一个linux终端窗口

qt-C笔记之模拟实现一个linux终端窗口 code review! 文章目录 qt-C笔记之模拟实现一个linux终端窗口一.运行二.main.cpp三.不足&#xff0c;待改进点 一.运行 二.main.cpp 代码 #include <QApplication> #include <QPlainTextEdit> #include <QLineEdit>…

新版Spring Security6.2 - Digest Authentication

前言&#xff1a; 书接上文&#xff0c;上次翻译basic的这页&#xff0c;这次翻译Digest Authentication这页。 摘要认证-Digest Authentication 官网的警告提示&#xff1a;不应在应用程序中使用摘要式身份验证&#xff0c;因为它不被认为是安全的。最明显的问题是您必须以…

聊聊Java中的异常

什么是异常 关于Java的异常&#xff0c;我们认为符合大致分为以下几种情况: 程序逻辑运行结果不符合预期。程序执行时抛出各种exception。因为各种原因导致服务崩溃。 Java异常类体系结构如下图所示: Exception和Error的区别 Exception Exception或者Exception自类的异常对…

有关爬虫http/https的请求与响应

简介 HTTP协议&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;&#xff1a;是一种发布和接收 HTML页面的方法。 HTTPS&#xff08;Hypertext Transfer Protocol over Secure Socket Layer&#xff09;简单讲是HTTP的安全版&#xff0c;在HTT…

【亲测】获取百度智能云access_token并存储,百度智能云access_token有效期

百度智能云服务内置很多api接口&#xff08;文字识别&#xff0c;企业信息识别&#xff0c;等&#xff09;&#xff0c;所有百度智能云自带的接口都会用到百度的access_token 第一步&#xff1a;登录百度智能云管理中心 第二步&#xff1a;创建账户&#xff0c;完整身份认证 …

Binder IPC通讯流程 摘要

一次完整的 Binder IPC 通信过程通常是这样&#xff1a; 首先 Binder 驱动在内核空间创建一个数据接收缓存区&#xff1b;接着在内核空间开辟一块内核缓存区&#xff0c;建立内核缓存区和内核中数据接收缓存区之间的映射关系&#xff0c;以及内核中数据接收缓存区和接收进程用…

Leetcode2048. 下一个更大的数值平衡数

Every day a Leetcode 题目来源&#xff1a;2048. 下一个更大的数值平衡数 解法1&#xff1a;枚举 这种题不能想复杂了&#xff0c;枚举大法好。 代码&#xff1a; /** lc appleetcode.cn id2048 langcpp** [2048] 下一个更大的数值平衡数*/// lc codestart class Soluti…

中医电子处方管理系统软件,中医配方模板一键生成软件操作教程

一、前言&#xff1a; 在中医开电子处方时&#xff0c;如果能够使用配方模板功能&#xff0c;则可以节省很多时间。使用配方模板一键导入&#xff0c;几秒即可完成开单。 下面就以佳易王电子处方管理系统软件V17.1版本为例说明&#xff0c;其他版本可以参考&#xff0c;软件下…

SpringBoot整合Lucene实现全文检索【详细步骤】【附源码】

笑小枫的专属目录 1. 项目背景2. 什么是Lucene3. 引入依赖&#xff0c;配置索引3.1 引入Lucene依赖和分词器依赖3.2 表结构和数据准备3.3 创建索引3.4 修改索引3.5删除索引 4. 数据检索4.1 基础搜索4.2 一个关键词&#xff0c;在多个字段里面搜索4.3 搜索结果高亮显示4.4 分页检…