给自己复盘的tjxt笔记day9

news2024/9/23 23:34:50

优惠券管理

开发流程

需求分析,接口统计,数据库设计,创建分支,创建新模块(依赖,配置,启动类),生成代码,引入枚举状态

优惠券管理

增删改查的业务代码,没有新的知识点

新增优惠券

@Override
    @Transactional
    public void saveCoupon(CouponFormDTO dto) {
        // 1.保存优惠券
        // 1.1.转PO
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        // 1.2.保存
        save(coupon);

        if (!dto.getSpecific()) {
            // 没有范围限定
            return;
        }
        Long couponId = coupon.getId();
        // 2.保存限定范围
        List<Long> scopes = dto.getScopes();
        if (CollUtils.isEmpty(scopes)) {
            throw new BadRequestException("限定范围不能为空");
        }
        // 2.1.转换PO
        List<CouponScope> list = scopes.stream()
                .map(bizId -> new CouponScope().setBizId(bizId).setCouponId(couponId))
                .collect(Collectors.toList());
        // 2.2.保存
        scopeService.saveBatch(list);
    }

分页查询优惠券

@Override
public PageDTO<CouponPageVO> queryCouponByPage(CouponQuery query) {
    Integer status = query.getStatus();
    String name = query.getName();
    Integer type = query.getType();
    // 1.分页查询
    Page<Coupon> page = lambdaQuery()
            .eq(type != null, Coupon::getDiscountType, type)
            .eq(status != null, Coupon::getStatus, status)
            .like(StringUtils.isNotBlank(name), Coupon::getName, name)
            .page(query.toMpPageDefaultSortByCreateTimeDesc());
    // 2.处理VO
    List<Coupon> records = page.getRecords();
    if (CollUtils.isEmpty(records)) {
        return PageDTO.empty(page);
    }
    List<CouponPageVO> list = BeanUtils.copyList(records, CouponPageVO.class);
    // 3.返回
    return PageDTO.of(page, list);
}

修改优惠券(练习)

 @Override
    public void updateById(CouponFormDTO dto, Long id) {
        //1.校验参数
        Long dtoId = dto.getId();
        //如果dto的id和路径id都存在但id不一致,或者都不存在,则抛出异常
        if((dtoId!=null && id!=null && !dtoId.equals(id)) || (dtoId==null&&id==null)){
            throw new BadRequestException("参数错误");
        }
        //2.更新优惠券基本信息
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        //只更新状态为1的优惠券基本信息,如果失败则是状态已修改
        boolean update = lambdaUpdate().eq(Coupon::getStatus, 1).update(coupon);
        //基本信息更新失败则无需更新优惠券范围信息
        if(!update){
            return;
        }
        //3.更新优惠券范围信息
        List<Long> scopeIds = dto.getScopes();
        //3.1只要是优惠券状态不为1,或者优惠券范围为空,则不更新优惠券范围信息
        //3.2个人写法是先删除优惠券范围信息,再重新插入
        List<Long> ids = scopeService.lambdaQuery().select(CouponScope::getId).eq(CouponScope::getCouponId, dto.getId()).list()
                .stream().map(CouponScope::getId).collect(Collectors.toList());
        scopeService.removeByIds(ids);
        //3.3删除成功后,并且有范围再插入
        if(CollUtils.isNotEmpty(scopeIds)){
            List<CouponScope> lis = scopeIds.stream().map(i -> new CouponScope().setCouponId(dto.getId()).setType(1).setBizId(i)).collect(Collectors.toList());
            scopeService.saveBatch(lis);
        }
    }

删除优惠券(练习)

    @Override
    public void deleteById(Long id) {
        //1.查询优惠券是否存在并删除
        boolean remove = lambdaUpdate()
                .eq(Coupon::getId, id)
                .eq(Coupon::getStatus, 1)
                .remove();
        if(!remove){
            throw new BadRequestException("删除失败,当前优惠券状态非待发放状态");
        }
        //2.查询优惠券范围信息并删除
        scopeService.lambdaUpdate()
                .eq(CouponScope::getCouponId, id)
                .remove();
    }

根据id查询优惠券(练习)

   @Override
    public CouponDetailVO queryById(Long id) {
        //1.查询优惠券基本信息
        Coupon coupon = lambdaQuery()
                .eq(Coupon::getId, id)
                .one();
        //2.查询优惠券范围列表
        List<CouponScope> couponScopes = scopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();
        //3.查询范围信息<分类id,分类名称>
        Map<Long, String> cateMap = categoryClient.getAllOfOneLevel().stream().collect(Collectors.toMap(CategoryBasicDTO::getId, CategoryBasicDTO::getName));
        //4.封装范围信息到范围列表
        List<CouponScopeVO> vos = couponScopes.stream().map(i -> new CouponScopeVO().setName(cateMap.get(i.getBizId())).setId(i.getBizId())).collect(Collectors.toList());
        //5.封装优惠券详细信息
        CouponDetailVO couponDetailVO = BeanUtils.copyBean(coupon, CouponDetailVO.class);
        couponDetailVO.setScopes(vos);
        return couponDetailVO;
    }

优惠券发放

发放优惠券


@Transactional
@Override
public void beginIssue(CouponIssueFormDTO dto) {
    // 1.查询优惠券
    Coupon coupon = getById(dto.getId());
    if (coupon == null) {
        throw new BadRequestException("优惠券不存在!");
    }
    // 2.判断优惠券状态,是否是暂停或待发放
    if(coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != PAUSE){
        throw new BizIllegalException("优惠券状态错误!");
    }
    // 3.判断是否是立刻发放
    LocalDateTime issueBeginTime = dto.getIssueBeginTime();
    LocalDateTime now = LocalDateTime.now();
    boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now);
    // 4.更新优惠券
    // 4.1.拷贝属性到PO
    Coupon c = BeanUtils.copyBean(dto, Coupon.class);
    // 4.2.更新状态
    if (isBegin) {
        c.setStatus(ISSUING);
        c.setIssueBeginTime(now);
    }else{
        c.setStatus(UN_ISSUE);
    }
    // 4.3.写入数据库
    updateById(c);

    // TODO 兑换码生成
}

兑换码生成算法

兑换码的需求

算法分析

要满足唯一性,很多同学会想到以下技术:

  • UUID

  • Snowflake

  • 自增id

我们的兑换码要求是24个大写字母和8个数字。而以上算法最终生成的结果都是数值类型,并不符合我们的需求!

有没有什么办法,可以把数字转为我们要求的格式呢?

Base32转码

假如我们将24个字母和8个数字放到数组中,如下:

角标

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

字符

A

B

C

D

E

F

G

H

J

K

L

M

N

P

Q

R

角标

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

字符

S

T

U

V

W

X

Y

Z

2

3

4

5

6

7

8

9

这样,0~31的角标刚好对应了我们的32个字符!而2的5次幂刚好就是32,因此5位二进制数的范围就是0~31

那因此,只要我们让数字转为二进制的形式,然后每5个二进制位为一组,转10进制的结果是不是刚好对应一个角标,就能找到一个对应的字符呢?

这样是不是就把一个数字转为我们想要的字符个数了。这种把二进制数经过加密得到字符的算法就是Base32法

我们最终要求字符不能超过10位,而每个字符对应5个bit位,因此二进制数不能超过50个bit位

UUID和Snowflake算法得到的结果,一个是128位,一个是64位,都远远超出了我们的要求。

自增id算法符合我们的需求呢?

自增id从1增加到Integer的最大值,可以达到40亿以上个数字,而占用的字节仅仅4个字节,也就是32个bit位,距离50个bit位的限制还有很大的剩余,符合要求

重兑校验算法

那重兑问题该如何判断呢?此处有两种方案:

  • 基于数据库:我们在设计数据库时有一个字段就是标示兑换码状态,每次兑换时可以到数据库查询状态,避免重兑。

    • 优点:简单

    • 缺点:对数据库压力大

  • 基于BitMap:兑换或没兑换就是两个状态,对应0和1,而兑换码使用的是自增id.我们如果每一个自增id对应一个bit位,用每一个bit位的状态表示兑换状态,是不是完美解决问题。而这种算法恰好就是BitMap的底层实现,而且Redis中的BitMap刚好能支持2^32个bit位。

    • 优点:简答、高效、性能好

    • 缺点:依赖于Redis

防刷校验算法

我们也可以模拟JWT的token的思路:

  • 首先准备一个秘钥

  • 然后利用秘钥对自增id做加密生成签名

  • 签名、自增id利用Base32转码后生成兑换码

只要秘钥不泄露,就没有人能伪造兑换码。只要兑换码被篡改,就会导致验签不通过。

这里我们必须采用一种特殊的签名算法。由于我们的兑换码核心是自增id,也就是数字,因此这里我们打算采用按位加权的签名算法:

  • 将自增id(32位)每4位分为一组,共8组,都转为10进制

  • 每一组给不同权重

  • 把每一组数加权求和,得到的结果就是签名

为了避免秘钥被人猜测出规律,我们可以准备16组秘钥。在兑换码自增id前拼接一个4位的新鲜值,可以是随机的。这个值是多少,就取第几组秘钥。

异步生成兑换码

判断是否需要生成兑换码,要同时满足两个要求:

  • 领取方式必须是兑换码方式

  • 之前的状态必须是待发放,不能是暂停

由于生成兑换码的数量较多,可能比较耗时,这里推荐基于线程池异步生成

@Slf4j
@Configuration
public class PromotionConfig {

    @Bean
    public Executor generateExchangeCodeExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 1.核心线程池大小
        executor.setCorePoolSize(2);
        // 2.最大线程池大小
        executor.setMaxPoolSize(5);
        // 3.队列大小
        executor.setQueueCapacity(200);
        // 4.线程名称
        executor.setThreadNamePrefix("exchange-code-handler-");
        // 5.拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

实现思路

代码:

@Transactional
@Override
public void beginIssue(CouponIssueFormDTO dto) {
    // 1.查询优惠券
    Coupon coupon = getById(dto.getId());
    if (coupon == null) {
        throw new BadRequestException("优惠券不存在!");
    }
    // 2.判断优惠券状态,是否是暂停或待发放
    if(coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != PAUSE){
        throw new BizIllegalException("优惠券状态错误!");
    }
    // 3.判断是否是立刻发放
    LocalDateTime issueBeginTime = dto.getIssueBeginTime();
    LocalDateTime now = LocalDateTime.now();
    boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now);
    // 4.更新优惠券
    // 4.1.拷贝属性到PO
    Coupon c = BeanUtils.copyBean(dto, Coupon.class);
    // 4.2.更新状态
    if (isBegin) {
        c.setStatus(ISSUING);
        c.setIssueBeginTime(now);
    }else{
        c.setStatus(UN_ISSUE);
    }
    // 4.3.写入数据库
    updateById(c);

    // 5.判断是否需要生成兑换码,优惠券类型必须是兑换码,优惠券状态必须是待发放
    if(coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT){
        coupon.setIssueEndTime(c.getIssueEndTime());
        codeService.asyncGenerateCode(coupon);
    }
  @Override
    @Async("generateExchangeCodeExecutor")
    public void asyncGenerateCode(Coupon coupon) {
        // 发放数量
        Integer totalNum = coupon.getTotalNum();
        // 1.获取Redis自增序列号
        Long result = serialOps.increment(totalNum);
        if (result == null) {
            return;
        }
        int maxSerialNum = result.intValue();
        List<ExchangeCode> list = new ArrayList<>(totalNum);
        for (int serialNum = maxSerialNum - totalNum + 1; serialNum <= maxSerialNum; serialNum++) {
            // 2.生成兑换码
            String code = CodeUtil.generateCode(serialNum, coupon.getId());
            ExchangeCode e = new ExchangeCode();
            e.setCode(code);
            e.setId(serialNum);
            e.setExchangeTargetId(coupon.getId());
            e.setExpiredTime(coupon.getIssueEndTime());
            list.add(e);
        }
        // 3.保存数据库
        saveBatch(list);

        // 4.写入Redis缓存,member:couponId,score:兑换码的最大序列号
        redisTemplate.opsForZSet().add(COUPON_RANGE_KEY, coupon.getId().toString(), maxSerialNum);
    }

暂停发放(练习)

@Override
    @Transactional
    public void pauseIssue(Long id) {
        // 1.查询旧优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }

        // 2.当前券状态必须是未开始或进行中
        CouponStatus status = coupon.getStatus();
        if (status != UN_ISSUE && status != ISSUING) {
            // 状态错误,直接结束
            return;
        }

        // 3.更新状态
        boolean success = lambdaUpdate()
                .set(Coupon::getStatus, PAUSE)
                .eq(Coupon::getId, id)
                .in(Coupon::getStatus, UN_ISSUE, ISSUING)
                .update();
        if (!success) {
            // 可能是重复更新,结束
            log.error("重复暂停优惠券");
        }

        // 4.删除缓存
        redisTemplate.delete(PromotionConstants.COUPON_CACHE_KEY_PREFIX + id);
    }

 查询兑换码(练习)

   @Override
    public PageDTO<ExchangeCodeVO> queryCodePage(CodeQuery query) {
        // 1.分页查询兑换码
        Page<ExchangeCode> page = lambdaQuery()
                .eq(ExchangeCode::getStatus, query.getStatus())
                .eq(ExchangeCode::getExchangeTargetId, query.getCouponId())
                .page(query.toMpPage());
        // 2.返回数据
        return PageDTO.of(page, c -> new ExchangeCodeVO(c.getId(), c.getCode()));
    }

定时开始发放优惠券 (练习)


    @XxlJob("couponIssueJobHandler")
    public void handleCouponIssueJob(){
        // 1.获取分片信息,作为页码,每页最多查询 20条
        int index = XxlJobHelper.getShardIndex() + 1;
        int size = Integer.parseInt(XxlJobHelper.getJobParam());
        // 2.查询<<未开始>>的优惠券
        Page<Coupon> page = couponService.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.UN_ISSUE)
                .le(Coupon::getTermBeginTime, LocalDateTime.now())
                .page(new Page<>(index, size));
        // 3.发放优惠券
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return;
        }
        couponService.beginIssueBatch(records);
    }

 @Override
    public void beginIssueBatch(List<Coupon> coupons) {
        // 1.更新券状态
        for (Coupon c : coupons) {
            c.setStatus(CouponStatus.ISSUING);
        }
        updateBatchById(coupons);
        // 2.批量缓存
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            StringRedisConnection src = (StringRedisConnection) connection;
            for (Coupon coupon : coupons) {
                // 2.1.组织数据
                Map<String, String> map = new HashMap<>(4);
                map.put("issueBeginTime", String.valueOf(DateUtils.toEpochMilli(coupon.getIssueBeginTime())));
                map.put("issueEndTime", String.valueOf(DateUtils.toEpochMilli(coupon.getIssueEndTime())));
                map.put("totalNum", String.valueOf(coupon.getTotalNum()));
                map.put("userLimit", String.valueOf(coupon.getUserLimit()));
                // 2.2.写缓存
                src.hMSet(PromotionConstants.COUPON_CACHE_KEY_PREFIX + coupon.getId(), map);
            }
            return null;
        });
    }

定时结束发放优惠券 (练习)

回头再补上

面试

1

面试官:你们优惠券支持兑换码的方式是吧,哪兑换码是如何生成的呢?(请设计一个优惠券兑换码生成方案,可以支持20亿以上的唯一兑换码,兑换码长度不超过10,只能包含字母数字,并且要保证生成和校验算法的高效)

答:

首先要考虑兑换码的验证的高效性,最佳的方案肯定是用自增序列号。因为自增序列号可以借助于BitMap验证兑换状态,完全不用查询数据库,效率非常高。

要满足20亿的兑换码需求,只需要31个bit位就够了,也就是在Integer的取值范围内,非常节省空间。我们就按32位来算,支持42亿数据规模。

不过,仅仅使用自增序列还不够,因为容易被人爆刷。所以还需要设计一个加密验签算法。算法有很多,比如可以使用按位加权方案

32位的自增序列,可以每4位一组,转为10进制,这样就有8个数字。提前准备一个长度为8的加权数组,作为秘钥。对自增序列的8个数字按位加权求和,得到的结果作为签名。

当然,考虑到秘钥的安全性,我们也可以准备多组加权数组,比如准备16组。然后生成兑换码时随机生成一个4位的新鲜值取值范围刚好是0~15,新鲜值是几,我们就取第几组加权数组作为秘钥。然后把新鲜值、自增序列拼接后按位加权求和,得到签名。

最后把签名值的后14位新鲜值(4位)自增序列(32位)拼接,得到一个50位二进制数,然后与一个较大的质数做异或运算加以混淆再基于Base32或Base64转码,即可的对兑换码。

如果是基于Base32转码,得到的兑换码恰好10位,符合要求。

需要注意的是,用来做异或的大质数加权数组都属于秘钥,千万不能泄露。如有必要,也可以定期更换。

当我们要验签的时候,首先将结果 利用Base32转码为数字。然后与大质数异或得到原始数值。

接着取高14位,得到签名;取后36位得到新鲜值与自增序列的拼接结果取中4位得到新鲜值。

根据新鲜值找到对应的秘钥(加权数组),然后再次对后36位加权求和,得到签名。与高14位的签名比较是否一致,如果不一致证明兑换码被篡改过,属于无效兑换码。如果一致,证明是有效兑换码。

接着,取出低32位,得到兑换码的自增序列号利用BitMap验证兑换状态,是否兑换过即可

整个验证过程完全不用访问数据库,效率非常高。

2

面试官:你在项目中哪些地方用到过线程池

答:很多地方,比如我在实现优惠券的兑换码生成的时候。

当我们在发放优惠券的时候,会判断优惠券的领取方式,我们有基于页面手动领取,基于兑换码兑换领取等多种方式。

如果发现是兑换码领取,则会在发放的同时,生成兑换码。但由于兑换码数量比较多,如果在发放优惠券的同时生成兑换码,业务耗时会比较久。

因此,我们会采用线程池异步生成兑换码的方式。

3

面试官可能会追问:那你的线程池参数是怎么设置的?

答:线程池的常见参数包括:核心线程、最大线程、队列、线程名称、拒绝策略等。

这里核心线程数我们配置的是2,最大线程数是CPU核数。之所以这么配置是因为发放优惠券并不是高频业务,这里基于线程池做异步处理仅仅是为了减少业务耗时,提高用户体验。所以线程数无需特别高。

队列的大小设置的是200,而拒绝策略采用的是交给调用线程处理的方式。

由于业务访问频率较低,所以基本不会出现线程耗尽的情况,如果真的出现了,就交给调用线程处理,让客户稍微等待一下也行。

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

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

相关文章

vagrant 创建虚拟机

创建一个名为 “Vagrantfile” 的文件&#xff0c;修改如下内容&#xff1a; Vagrant.configure("2") do |config|(1..3).each do |i|config.vm.define "k8s-node#{i}" do |node|# 设置虚拟机的Boxnode.vm.box "centos/7"# 设置虚拟机的主机名…

Behave使用体验

behaveuiautomator2jenkins 同理&#xff0c;Behave也可以和Appium/AirTest框架结合 运行环境 pip install uiautomator2 behave behave2cucumber 意事项&#xff1a;behave版本号建议1.2.5&#xff0c;因为1.2.6和Jenkins Cucumber Report插件不兼容 生成报告 html报告 …

各位,请入局AI大模型,现在!立刻!马上!!

AI 大模型人才供不应求 2024年&#xff0c;AI 在国内市场全面大爆发&#xff0c;不断涌现出新的算法、模型和应用场景&#xff0c;各行各业的垂类大模型应用也迎来井喷期。 无论是华为、 百度、阿里、字节等互联网巨头&#xff0c; 还是中小型的科技公司都在高薪挖 AI 大模型人…

易盾空间推理 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我…

Codeforces Round 968 (Div. 2) ABC题详细题解(C++,Python)

前言&#xff1a; 本文为Codeforces Round 968 (Div. 2)的ABC详细题解&#xff0c;包含C,Python语言描述&#xff0c;觉得有帮助或者写的不错可以点个赞 感觉D题说的好抽象&#xff0c;看不懂&#xff0c;之后实力够了更新 目录 题A: 题目大意和解题思路: 代码实现(C): 代…

全国大学生数学建模大赛——黄花鱼的最优捕捞策略

渔业管理部门规定&#xff0c;每年只允许在产卵孵化期前的8个 月进行捕捞作业。如果每年投入的捕捞能力(如渔船数、下网 次数等)固定不变&#xff0c;这时单位时间捕捞量将与各年龄组鱼群条 数成正比&#xff0c;比例系数不妨设为捕捞强度系数。通常使用 13mm 网眼的拉网&…

中国高校发表科技论文及著作数量数据集(2009-2022年)

中国各地区的高校科技产出数据&#xff0c;包括27个指标&#xff0c;科技论文发表、著作出版、专利申请、专利转让、国家标准项等。这些指标综合反映了各地区高校在科学研究和技术开发方面的活跃程度及创新能力 一、数据介绍 数据名称&#xff1a;中国地区高校发表科技论文、…

Android App启动流程

1.通过 Launcher 启动应用时&#xff0c;点击应用图标后&#xff0c;Launcher 调用 startActivity 启动应用。 2.Launcher Activity 最终调用 Instrumentation 的 execStartActivity 来启动应用。 3.Instrumentation 调用 ActivityManagerProxy (ActivityManagerService 在应…

计算机类-本科毕业设计快速通关攻略-(选题-创新点-论文框架-论文绘图)

一、推荐选题 大多数人都没有什么基础&#xff0c;不推荐做系统类的&#xff0c;建议走深度学习方向&#xff0c;简单易上手&#xff0c;下面将给出几个我认为不错的方向。 1、目标检测类 目标检测是每年深度学习毕业设计的主流&#xff0c;如Faster R-CNN、YOLO、SSD等算法…

程序员心理健康测试问卷

当然&#xff0c;以下是一份针对程序员设计的心理健康测试问卷。这份问卷旨在帮助程序员评估自己的心理健康状态&#xff0c;并识别可能存在的心理问题。请注意&#xff0c;此问卷仅供参考&#xff0c;不能替代专业心理咨询或诊断。 程序员心理健康测试问卷 基本信息 姓名&am…

为什么不让程序员直接对接客户,而是通过产品经理?

从客户的角度来说&#xff0c;客户需要的是对自身业务的解决方案以及实现&#xff0c;即能够实现解决方案的软件或系统。从程序员的角度来讲&#xff0c;甲方说啥&#xff0c;我按着做就行了&#xff0c;即实现甲方说的软件或系统。但是甲方不懂技术&#xff0c;程序员不懂业务…

备忘录删除了怎么恢复?备忘录失而复得,掌握这3招!

备忘录是我们手机中的秘密花园&#xff0c;守护着每一个重要的思绪、待办事项与未来规划。它们不仅是日常工作的得力助手&#xff0c;更是心灵慰藉的小角落。但有时候&#xff0c;一个不经意的滑动或点击&#xff0c;这些珍贵的笔记和计划可能就会消失。备忘录删除了怎么恢复&a…

jpeg转pdf,分享5种图片转PDF的方法!

在日常工作和学习中&#xff0c;将JPEG图片转换为PDF文件是一项常见的需求。无论是为了保持文件的清晰度&#xff0c;还是为了方便分享和打印&#xff0c;JPEG转PDF都是一项非常实用的操作。本文将分享五种不同的JPEG转PDF的方法&#xff0c;帮助大家轻松完成这一任务。 方法一…

百度搜索的RLHF性能优化实践

作者 | 搜索架构部 导读 本文大语言模型在未经标注的大量文本上进行预训练后&#xff0c;可能产生包含偏见、泄露隐私甚至对人类构成威胁的内容。OpenAI 最先提出了基于人类反馈的强化学习算法(Reinforcement Learning fromHuman Feedback, RLHF)&#xff0c;将人类偏好引入到…

五种多目标优化算法(MOAHA、NSGA2、NSGA3、SPEA2、MODA)性能对比,包含47个多目标测试函数,6种评价指标,MATLAB代码

一、五种多目标算法及六种评价指标简介 NSGA-III&#xff1a; NSGA-III是Deb在2013年提出的&#xff0c;用于解决高维多目标优化问题。它采用参考点基于的非支配排序方法&#xff0c;并引入了种群的自适应标准化和关联操作&#xff0c;以提高算法在高维问题上的性能和多样性 。…

线程 --- 同步与生产消费者模型

序言 在上一篇的内容中&#xff0c;我们学习了使用互斥锁来保护共享资源&#xff0c;避免多个线程竞争&#xff0c;造成数据不一致等问题。在这一篇文章中&#xff0c;我们将继续深入&#xff0c;学习多线程同步以及生产消费者模型。 1. 线程同步 1.1 什么是线程同步 线程互斥…

操作系统常见面试题总结

文章目录 1 操作系统基础1.1 什么是操作系统&#xff1f;1.2 操作系统主要有哪些功能&#xff1f;1.3 用户态和内核态1.3.1 什么是用户态和内核态&#xff1f;1.3.2 为什么要有用户态和内核态&#xff1f;只有一个内核态不行么&#xff1f;1.3.3 用户态和内核态是如何切换的&am…

HICOOL 2024全球创业大赛角出200个获奖项目

近日&#xff0c;经过超过200天的紧张竞争&#xff0c;HICOOL 2024全球创业大赛从124个国家和地区的7406个创新项目中脱颖而出&#xff0c;共选出了200个获奖项目。 其中&#xff0c;“新一代智能光学显微成像仪器与全流程解决方案”和“基于多尺度深度学习的功能型生物分子设…

Excel下拉框多选

记录一下学会一个新的知识&#xff01; 两种方式 第一种方式&#xff1a;先在表格里写好需要的值&#xff0c;再在数据关联里面直接引入。 1.新建excel表格&#xff0c;输入下拉框需要的值。 2.点击——数据>有效性 3.选择——序列 4.数据来源——框住刚才写好的数据——…

IMU用于动物行为监测

近日&#xff0c;由比利时和法国组成的科研团队开展了一项创行性的研究&#xff0c;通过在牛颈部安装IMU&#xff08;惯性测量单元&#xff09;&#xff0c;实现了对牛吃草行为的实时监测。该技术通过捕捉牛咀嚼时的微小动作&#xff0c;并结合机器学习算法&#xff0c;智能区分…