给自己复盘的tjxt笔记day9

优惠券管理

开发流程

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

优惠券管理

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

新增优惠券

@Override@Transactionalpublic void saveCoupon(CouponFormDTO dto) {// 1.保存优惠券// 1.1.转POCoupon 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.转换POList<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.处理VOList<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);
}

修改优惠券(练习)

 @Overridepublic 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);}}

删除优惠券(练习)

    @Overridepublic 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查询优惠券(练习)

   @Overridepublic 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.拷贝属性到POCoupon 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 {@Beanpublic 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.拷贝属性到POCoupon 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@Transactionalpublic 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);}

 查询兑换码(练习)

   @Overridepublic 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);}

 @Overridepublic 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.mzph.cn/web/51407.shtml

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

相关文章

NC 数组中的最长连续子序列

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定无序数组…

前端算法 === 力扣 111 二叉树的最小深度

目录 问题描述 DFS&#xff08;深度优先搜索&#xff09;方案 BFS&#xff08;广度优先搜索&#xff09;方案 总结 力扣&#xff08;LeetCode&#xff09;上的题目111是关于二叉树的最小深度问题。这个问题可以通过深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&…

科技温柔拥抱梦乡!康姿百德柔压磁性枕舒适与科技的甜蜜邂逅

解锁未来睡眠新姿势&#xff01;康姿百德柔压磁性枕&#xff0c;科技护航每一夜好梦 在现代家居产品的设计中&#xff0c;科技与舒适性的结合越来越受到人们的关注。康姿百德柔压磁性枕正是一款将科技与舒适结合的产品&#xff0c;为现代生活注入了新的活力。 康姿百德柔压磁性…

Jmeter下载、配置环境变量

Jmeter下载 下载地址&#xff1a;Apache JMeter - Download Apache JMeter 下载后无需安装&#xff0c;解压后即可使用。解压后目录如下 配置环境变量 JMETER_HOME 环境变量Path %JMETER_HOME%\bin 环境变量CLASSPATH %JMETER_HOME%\lib 验证是否配置成功 在cmd命令窗中 输入…

JetBrains Rider 2024 for Mac/Win:跨平台.NET IDE集成开发环境的全面解析

JetBrains Rider 2024作为一款专为Mac和Windows用户设计的跨平台.NET IDE集成开发环境&#xff0c;以其强大的功能和卓越的性能&#xff0c;在.NET开发领域脱颖而出。这款IDE不仅集成了IntelliJ IDEA的代码编辑优势&#xff0c;还融合了ReSharper的C#开发体验&#xff0c;为开发…

ES6笔记总结:第四天(ES6完结)

Xmind鸟瞰图&#xff1a; 简单文字总结&#xff1a; node的模块化&#xff1a; 1.CommonJS 规范&#xff1a;Node.js 遵循 CommonJS 模块规范&#xff0c;该规范定义了如何在服务器环境中实现模块化&#xff0c;包括如何定义模块、如何引入和使用模块。 2.模块的定义&…

LabVIEW便携涡流检测系统开发

针对便携式脉冲涡流检测系统的开发需求&#xff0c;使用LabVIEW进行软件设计与实现。系统需要集成对铁磁性和非铁磁性材料的检测功能&#xff0c;并提供友好的用户界面&#xff0c;便于操作与数据读取。 硬件选型&#xff1a; 脉冲涡流主机&#xff1a; 选择理由&#xff1a; …

Vue3搜索框(InputSearch)

效果如下图&#xff1a;在线预览 APIs InputSearch 参数说明类型默认值width搜索框宽度&#xff0c;单位 pxstring | number‘100%’icon搜索图标boolean | slottruesearch搜索按钮&#xff0c;默认时为搜索图标string | slotundefinedsearchProps设置搜索按钮的属性&#xf…

Nginx反向代理B

http协议反向代理 反向代理配置参数 proxy_pass; #用来设置将客户端请求转发给的后端服务器的主机 #可以是主机名(将转发至后端服务做为主机头首部)、IP地址&#xff1a;端口的方式 #也可以代理到预先设置的主机群组&#xff0c;需要模块ngx_http_upstream_module支持 #示例:…

无人机之穿越机基础知识

一、用途与性能 主要用于竞赛、娱乐和极限飞行&#xff0c;特点是速度快、机动性强、反应灵敏&#xff0c;能够在短时间内做出迅速的加速、转向和翻滚动作&#xff0c;具有极高的飞行灵活性和第一视角飞行体验&#xff08;FPV &#xff09;。 穿越机通常体积小&#xff0c;续…

Open3D mesh 泊松下采样

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1原始点云 3.2下采样后点云 Open3D点云算法汇总及实战案例汇总的目录地址&#xff1a; Open3D点云算法与点云深度学习案例汇总&#xff08;长期更新&#xff0…

【国产游戏的机遇与挑战】

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Git(分布式版本控制系统)

git介绍&#xff1a; git官网&#xff1a;https://gitee.com/ Git工具安装 Git 公司 (git-scm.com)https://git-scm.com/ git本地配置账号和邮箱 一般刚安装Git都要配置用户名、密码和邮箱&#xff0c;因为你提交代码到本地仓库&#xff08;上传代码到远程仓库&#xff09;时…

Nginx的Rewrite和Location配置

目录 一、Rewrite模块 1.功能概述 1.1URL重写 1.2URL重定向 1.3条件判断 1.4重写规则的执行顺序 2.语法格式 2.1Flag说明 3. Rewrite跳转实现 4.常用的Nginx正则表达式 二、Location模块 1.概述 2.分类 2.1精准匹配&#xff08;&#xff09; 2.2前缀匹配…

游戏如何对抗 IL2cppDumper逆向分析

众所周知&#xff0c;Unity引擎中有两种脚本编译器&#xff0c;分别是 Mono 和 IL2CPP 。相较于Mono&#xff0c;IL2CPP 具备执行效率高、跨平台支持等优势&#xff0c;已被大多数游戏采用。 IL2CPP 模式下&#xff0c;可以将游戏 C# 代码转换为 C 代码&#xff0c;然后编译为…

python爬虫——入门

一、概念 万维网之所以叫做网&#xff0c;是因为通过点击超链接或者进入URL&#xff0c;我们可以访问任何网络资源&#xff0c;从一个网页跳转到另一个网页&#xff0c;所有的相关资源连接在一起&#xff0c;就形成了一个网。 而爬虫呢&#xff0c;听名字就让人想起来一个黏糊…

string类题目(上)

string类题目 题目来源&#xff08;Leetcode&#xff09; 题目一&#xff1a;仅仅反转字母 分析 这个反转的特点在于只反转字母&#xff0c;不反转特殊字符。 法一&#xff1a;如果我们让一个正向迭代器指向第一个字符&#xff0c;让一个反向迭代器指向最后一个字符&#xf…

ch32v307vct6从头移植FreeRTOS

使用官方的ide可以直接创建带FreeRTOS的工程&#xff0c;但是不利于我们学习移植&#xff0c;所以特此记录怎么从头开始移植FreeRTOS到CH32V307VCT6芯片使用。 下载FreeRTOS源码 首先进入https://www.freertos.org/官网&#xff0c;然后找到如下Download字样&#xff0c;进入下…

华为云通过自定义域名访问桶内对象

问题&#xff1a;通过将自定义域名绑定至OBS桶实现在线预览文件 例如index.html入口文件 且记 自定义域名绑定暂时不支持HTTPS访问方式&#xff0c;只支持HTTP访问方式 自定义域名就先不用部署https证书。 配置完毕之后&#xff0c;将obs桶设置为公开的即可访问 如何在浏览…

Mysql 集群技术

Mysql在服务器中的部署方法 安装MySQL依赖性 rootmysql-node10 ~]# dnf install cmake gcc-c openssl-devel \ ncurses-devel.x86_64 libtirpc-devel-1.3.3-8.el9_4.x86_64.rpm rpcgen.x86_64 下载并解压源码包 使用命令tar zxf mysql-boost-5.7.44.tar.gz进行解压 源码编译安…