API接口开发设计

news/2025/1/23 21:49:52/文章来源:https://www.cnblogs.com/liucs/p/18688604

写接口看似是一个很简单的事情,但是往往越简单的事情越不容易做好,让我们看看如何写好一个接口。

01. 什么是接口

接口其实是一种规范,在生活中随处可见,比如:不同厂商的水管使用统一的水管接口对接、电脑厂商和配件厂商按照统一的 USB 接口标准进行生产完成配对、应用程序之间通过 API 进行信息交互。

亚马逊集团创始人贝佐斯规定,亚马逊的所有系统、团队之间的交互,都必须通过服务接口,现在云计算技术非常火爆,其起源实际上是亚马逊的这种 API 文化,可以说,API 构建了当前数字化世界的通路。

  1. 所有的团队必须通过服务来对外暴露数据和功能。
  2. 所有的团队之间的功能,必须通过这些服务接口来进行交互。
  3. 网络上不允许任何除服务接口的其它交互方式。(不能通过 Excel 等文件传输)
  4. 不管具体使用任何的技术,都必须符合上面的3条原则
  5. 所有的服务接口必须都被外部化。
  6. 任何不遵循这个指令的人将会被解雇。

所以,软件世界中到处存在的 API,到底应该如何实现?有哪些设计技巧?接下来让我们一起探索吧。

02. 接口设计

契约式设计原则

Design by Contract (DbC)是一种设计计算机软件的方法。这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式,可以说它是 API 设计的指导书,一个好的 API 实现之前,先要回答三个关键问题:

  1. API 期望什么?
  2. API 要保证什么?
  3. API 要保持不变的什么?

首先,API 必须保证输入是接收者期望的输入条件。比如:API 需要 A、B、C 三个参数,那么使用者要提前准备好这三个参数,当条件不满足时,会拒绝请求,当条件满足时,才会处理请求。
其次,API 要保证输出结果的正确性。API 在处理过程中遇到各种异常或错误,需要在内部做好处理,并最终将正确或期望的结果输出给使用者,而不应该把错误抛给其他系统去处理。
最后,API 必须要保证处理过程中的一致性。比如,同一个 API 被部署在10个服务器上,当输入条件发生改变时,所有服务器的内部的会话、状态、API 的输出也应该保持一致。
契约式设计原则作为指导思想,那么我们在设计 API 过程中,到底应该注意哪些细节?以下是我总结的六大设计原则:

2.1 关注点分离

作为面向对象 SOLID 原则中的“I”,对我们的 API 接口提供了非常有实战意义的指导价值,所以当你在设计一个 API 的时候,不要在一个接口中杂糅多种功能,更不要用很多个 Flag 联动多个入参,这样只会让你的接口变得异常混乱。举个例子,客户只需要一个 USB 接口,而你却给了他一个交换机,他怎么知道那根线插哪里?要插多少根?排列组合都有 N 种,客户直接原地崩溃。所以说,我们在设计接口时,要力争减少使用者的认知负担,提供单一纯粹的功能。

// bad 
type UserService interface {CreateUser(name string, age int) errorGetUser(id int) (User, error)UpdateUser(id int, name string, age int) errorDeleteUser(id int) errorSendEmail(userID int, message string) error // 发送邮件的职责与用户管理不相关
}// good
type UserService interface {CreateUser(name string, age int) errorGetUser(id int) (User, error)UpdateUser(id int, name string, age int) errorDeleteUser(id int) error
}type EmailService interface {SendEmail(userID int, message string) error
}

2.2  自我表达

 一个好的接口,客户在拿到后能很简单的读懂,在开发联调过程中,能根据错误信息自己修正,那么这个接口就具备了良好的自我表达能力,你可以从以下两大方面去考虑:

接口命名

接口命名应该给使用者提供一种简单且直观的体验,从接口名即可了解此接口的具体功能。
使用被大家接受的通用缩写,如:Information->info,delete->del,message->msg 等。
通过命名含义描述接口,比如获取用户列表:GetUsers。注意此方式适用于 RPC 协议接口风格,RESTful 风格不使用此形式,REST 使用名词 URI+动词形式。

错误信息

具有自表达的错误信息字段,且应该包含两点内容:
A:明确的错误指示。例如:“amount金额字段格式有误”。
B:错误后的解决方案。例如:“token字段缺失,请先登录”。

错误码设计

A:屏蔽内部逻辑。不要轻易暴露内部处理逻辑规律,防止接口被攻击。
B:便于快速定位问题。

错误码定义可以参考 HTTP 状态码,采用分段式,形成“肌肉记忆”,方便定位问题,也易于扩展。

 2.3 互斥穷举

接口之间应该尽量遵守MECE(互斥穷举)原则,不应该提供相互叠加的接口。例如:如果 PUT /orders/1/items/1 接口用于修改订单项,那接口 PUT /orders/1 就不应该具备处理某个 order-item 的能力。这样做的好处是不会存在功能重复的接口,造成维护和理解上的复杂性。如何做到完全穷举和彼此独立呢?

方案一:使用在线表格设计,标出每个URI的能力。

方案二:使用 DDD 领域建模,聚合根作为根 URL,实体作为二级 URI 设计。聚合根之间无任何联系,实体和聚合根之间责任明确。

举例:聚合根和实体定义

Order 聚合根

  • 包含多个 OrderItem 实体
  • 负责管理订单的生命周期和相关操作

2.4 快速失败

接口要快速验证条件,不满足要返回失败,不要等到已经击穿到应用最底部后,再去报错。这是“防御式编程”的一种理念,一个具体的实践就是对于接口入参进行校验。golang 的 validation 插件案例:

// 枚举控制
enum OperaType {Get = 0;  // 获取值Set = 1;  // 设置值
}
OperaType opera_type = 2[(validate.rules).enum.defined_only = true]; // 操作类型// 范围控制
int32 page_num = 3 [(validate.rules).int32.gte = 0]; //页码

给接口设置超时时间,异常情况快速失败,也能提高接口并发,但是会造成一些不可用,需要权衡利弊。

2.5 幂等性

什么是幂等?简单来说,就是当一个操作多次执行所产生的影响均与一次执行的影响相同,则它是幂等的。现代服务逐渐往分布式架构发展,服务是分散的,如何保证并行调用的幂等?

以下是常见的几种方式:

使用唯一标识符
为每个请求生成一个唯一的标识符,并在服务端进行验证。如果服务端已经处理过该标识符对应的请求,则直接返回之前的结果,而不进行重复处理。

使用状态机
对于一些有明确状态的资源(如订单、支付等),可以通过状态机来确保操作的幂等性。每个状态转换只能发生一次,重复请求不会改变状态。

使用数据库唯一约束
在数据库层面使用唯一约束来防止重复数据插入。如果重复请求尝试插入相同的数据,数据库会抛出异常,服务端可以捕获并处理该异常

使用乐观锁
我们在更新资源时检查版本号是否匹配。如果不匹配,说明资源已经被其他事务修改过,当前事务应该回滚。

使用悲观锁
悲观锁假设数据在大多数时间会发生冲突,读取数据时就加锁,防止其他事务修改数据,通常通过数据库的行级锁来实现,如使用“for update”。

使用分布式锁
分布式锁与悲观锁本质上相似,都通过串行化请求处理来实现幂等性。与悲观锁不同的是,分布式锁更轻量。在系统接收请求后,首先尝试获取分布式锁。如果成功获取锁,则执行业务逻辑;如果获取失败,则立即拒绝请求。

2.6 版本化

一个对外开放的服务,极大的概率会发生变化。业务变化可能修改 API 参数或响应数据结构,以及资源之间的关系。一般来说,字段的增加不会影响旧的客户端运行。但是当存在一些破坏性修改时,就需要使用新的版本将数据导向到新的资源地址。
版本信息传输通常有以下几种方式:

URI 前缀控制
这是最常见和直接的版本化方法,通过在 URL 路径中包含版本号来区分不同版本的 API。

Header 控制
通过在 HTTP 请求头中包含版本信息来实现版本化。这种方法可以保持URL的简洁。

示例:

  • 使用自定义头:X-API-Version: 1
  • 使用 Accept 头:Accept: application/vnd.example.v1+json

Query 控制

通过在 URL 中添加查询参数来指定版本号。

总结
比较推荐的做法是使用 URI 前缀。常见的反模式是通过增加 URI 后缀来实现的,例如/users/1/updateV2。这样做的缺陷是版本信息侵入到业务逻辑中,对路由的统一管理带来不便。
使用 Header 和 Query 发送版本信息则较为相似,不同之处在于,使用 URI 前缀在 MVC 框架中实现相对简单,只需要定义好路由即可。使用 Header 和 Query 还需要编写额外的拦截器。

03. 接口优化

3.1 批量

示例:

  • Redis 单次查询使用 Mget,Pipline 等方式,批量查询。
  • 循环调用 RPC 接口耗时严重,优化 RPC 接口支持批量查询,替代循环调用。

 本质上是减少不必要的网络 IO,但会对下游服务造成一定压力,需要综合考虑,不可滥用。

3.2 异步

示例:

  • 用户请求接口中,需要做业务操作,发站内通知,可以同步执行操作写库,然后异步发通知。
  • 区分核心和非核心操作,核心逻辑同步执行,非核心逻辑异步执行。通常异步方案有:线程/协程池,消息队列,任务调度框架,任务回调。

3.3 并行

示例:

  • 两个操作任务,各耗时200ms,顺序调用需要花费400ms,改为协程调用,总花费只需要200ms。
  • 将单个任务或串行任务拆分为多个子任务,利用多核处理器或分布式系统进行并行处理,提升处理效率。Golang 中可以使用 go 关键字轻松实现并行调用,本文不做赘述。

3.4 空间换时间

示例:

  • 用户信息访问频繁,变更小,通过 redis 缓存用户信息,用内存空间来提升响应速度。
  • 代码中用列表存储要素,循环判断某个要素是否存在,改为使用字典存储,虽然空间占用变大,但是时间复杂度由O(n)降为O(1)。
  • MySQL 数据库额外存储一份索引数据也是一种空间换时间的方案,提升查找了速度。
  • 空间换时间是一种常见的优化策略,通过额外的内存、磁盘等存储介质,减少程序的执行时间。

3.5 池化

示例:

  • 通常我们在使用 MySQL 时,会采用池化技术,复用 MySQL 链接,来达到重复利用的效果,避免了不必要的创建和销毁所带来的性能损耗。
  • 在高并发场景下,Golang 使用协程池来实现协程的管理,提升程序性能,如经典的高性能协程池开源库 ants:https://github.com/panjf2000/ants。

通过预先创建一定数量的连接对象,当需要使用连接时,从池中获取一个可用的连接对象,使用完毕后归还给池,而不是每次都创建和销毁连接对象,从而提高系统性能和资源利用率。

3.6 预处理
示例:

  • 用户打完王者荣耀对局后,通过客服入口举报违规玩家,我们通过预处理,调用各种数据接口和模型计算,并将信息存储下来,当工单流转到工作台时,接口可瞬时将信息拉取出来。
  • 类似空间换时间的缓存思想,但是关注点不一样,我们可以将想要的数据提前计算好,放到缓存或数据库中,当真正需要的时候再进行取用,而不是实时计算,以此来提升接口性能。

3.7 SQL 优化
接口开发过程中难免会和数据库打交道,但数据库非本文的介绍重点,不对数据库的具体优化方案进行详细介绍,只提供一些优化思路,感兴趣请自行查阅相关资料。

深分页

select * from t_records where uid = '{my_uid}' limit X, 30;

这是一常见的分页操作,看起来没啥问题,但是加入 X=5000000 时,数据库 server 调用 innodb 接口,在非主键索引中获取第0到第(5000000+30)条数据,返回给 server 之后,根据 offset=30,逐个抛弃,留下后面的30条,返回给业务。

优化方案:

// 基于主键或索引的优化
SELECT * FROM t_records WHERE id > last_seen_id ORDER BY id ASC LIMIT 30// 使用子查询优化
SELECT * FROM t_records 
WHERE id IN (SELECT id FROM t_records WHERE uid = '{my_uid}' ORDER BY id LIMIT 100000, 30
) ORDER BY id;// 延迟关联(Deferred JoinSELECT a.* FROM t_records a
JOIN (SELECT id FROM t_records WHERE uid = '{my_uid}' ORDER BY id LIMIT 100000, 30
) b ON a.id = b.id;// 反向分页 适用于查最新内容的场景
SELECT * FROM t_records ORDER BY id DESC LIMIT 30;

如果并发较高,可以考虑使用缓存的方案解决性能问题。

避免大事务
为了提高接口并发量,需要避免大事务。当需要更新多条数据时,避免一次性更新过多的数据。因为 update,delete 语句会对索引加锁,如果更新的记录数过多,会锁住太多的数据,由于执行时间较长,会严重限制数据库的并发量。因此,要考虑多个操作是否真的有必要放在一个事务中,将核心操作放到同一事务中,非核心操作在事务外面单独执行。

索引
加索引能大大提高数据查询效率,这个在接口设计之初也会考虑到,其中的一些索引设计和优化技巧,这里不做过多赘述。

分库分表
分库:是为了解决数据库连接资源不足问题,和磁盘 IO 的性能瓶颈问题。
分表:是为了解决单表数据量太大,sql 语句查询数据时,即使走了索引也非常耗时问题,此外还可以解决消耗 cpu 资源问题。
不过,分库分表的成本太高,非必要尽量不要采用。单表千万级也不一定要分,要看字段和索引的具体的存储情况而定。

归档历史数据
在设计数据库表之初,就要结合业务发展,考虑数据库量级增长趋势,考虑好归档方案,防止库表膨胀过大,对接口性能产生影响。

3.8 锁粒度控制

为解决高并发场景下,多个线程同时修改数据时会造成数据不一致情况,通常我们会:加锁。但是如果锁加的不好,导致粒度太粗,也会影响接口性能,比如你要用卫生间,就不要把客厅门也锁上。

 

// 同步执行保存操作
func (f *FileOperations) DoSave(fileUrl string) {f.mu.Lock()defer f.mu.Unlock()// 创建目录
        f.mkdir()// 上传文件
  f.uploadFile(fileUrl)//发送消息
  f.sendMessage(fileUrl)
}

 

为了避免高并发场景下创建相同目录,第二次创建目录失败影响业务功能,我们加锁,但是也对上传文件和发送消息上锁,导致粒度太粗,影响了接口性能,降低了并发,好的做法是只对 mkdir 函数进行加锁,减小锁粒度。

 

3.9 合适的存储

MySQL 换 Redis:当查询压力较大时,可以使用 redis 缓存数据。

Redis 换本地缓存:Redis 相比传统数据库速度要快很多,大概是毫秒级别,但是使用本地缓存的耗时可以忽略不计,而且从 redis 取数据会存在大量的序列化和反序列化过程,增加 CPU 负担。

Redis 换 Memcached:Redis 基于单线程,可能存在阻塞导致性能降低,但是 Memcached 是多线程模型,在某些高并发场景下,性能可能会更高。

MySQL 换 ES:后台系统假如需要复杂的查询,使用索引的查询场景比较灵活,使用 MySQL 可能就存在瓶颈了,可以考虑同步数据到 ES 中,提高查询检索性能。

MySQL 换 HBase:如果 MySQL 中的数据过于庞大,可以考虑将历史数据转存至 HBase 中,以提供高效的查询性能,通过设置合理的 RowKey 能进一步提高查询效率。

3.10 压缩传输内容

示例:

  • protobuf 协议相较于 json 或 xml,有更高的性能是因为其采用 varints 编码算法进行了二进制数据压缩。
  • HTTP 协议中的一个头部字段 Content-Encoding,服务端可采用 Gzip、Deflate、Br 等算法压缩,提升传输效率。

04. 接口安全

4.1 加密

敏感数据在传输过程中可能被抓包,想要保证数据安全,需要对数据进行加密,常见的加密方式有对称加密和非对称加密:
对称加密:加密和解密过程中,使用相同的密钥,常见的对称加密算法有:DES、AES。优点是计算速度快,缺点为任一方密钥泄露都会导致信息不安全。
非对称加密:服务端生成一对密钥,私钥放在服务端,公钥发布给其他人使用,这种方式比较安全,但是速度会慢一些。

4.2 加签验签

数据加签即由服务端用传输数据生产一串无法伪造的字符串,保证数据在传输过程中不被篡改。通常使用比较多的是 MD5 算法,将提交的数据通过某种方式组合,使用 MD5 生成加密串作为签名。当服务端接受请求时,就可以使用特定的组合方式生成签名进行对比,判断请求是否合法,此外可以将时间戳带入,增加超时校验,防止被抓包恶意攻击。

4.3 token授权

客户端输入用户名和密码,服务端校验成功后返回一个唯一值 Token 并缓存在服务端,后续客户端访问需要携带此 Token,服务端验证如果存在,则表明请求合法。同时我们要对 Token 设置合理的有效期,避免被抓包爬取数据。但是 Token 方案有缺点,比如:在分布式系统中,Token 方案依赖分布式缓存,会让我们的系统变得复杂。现在大部分系统会使用 JWT 来生成 Token,这种方式是无状态的,天然支持分布式。

4.4 防重放

某些特定场合下要保证一个请求只能处理一次,如支付场景。这是我们可以使用 timestamp+nonce 方案,每次请求会生成 nonce 到集合中,服务端处理请求时会先判断该 nonce 是否在集合中,如果存在则为非法请求。永久保存 nonce 代价非常大,可以结合 timestamp 优化,比如超过 3min 认定为非法请求。

4.5 限流

用户是真实用户,但是想频繁调用接口搞垮你的系统,或者调用量很大的情况下保证系统不宕机,这时我们可以使用限流方案来保护系统。常见的限流算法:令牌桶算法、漏桶算法、滑动窗口、计数器等。

4.6 黑白名单机制

对用户进行分析,将恶意用户纳入黑名单,避免系统被频繁攻击。白名单类似,比如对某个用户开放特殊调用权限,通过加白实现。

4.7 数据脱敏掩码

密码、手机号、身份证等敏感信息一般需要脱敏掩码展示,在保存时也不能明文存储,一般使用哈希算法进行加密,常见的如 SHA-256,也可加盐来增加安全性。

4.8 防SQL注入

常见的 SQL 注入方式

  • 基于字符串的 SQL 注入:' OR '1'='1',这种方式通常用于绕过登录验证。
  • 基于联合查询的 SQL 注入:使用 UNION 关键字将恶意查询与原始语句结合,获取数据库敏感信息。如:' UNION SELECT username, password FROM user'
  • 基于盲注的SQL注入:攻击者无法直接看到查询结果,通过观察响应来推断数据库信息。
  • 基于堆叠查询的 SQL 注入:在一个语句中插入多个 SQL 语句,如:'; DROP TABLE users;'

防 SQL 注入应对方案
使用参数化查询:

  • 使用参数化查询(也称为预处理语句)可以有效防止 SQL 注入,因为参数化查询会将输入数据与 SQL 语句分开处理。
  • 在 Go 语言中,可以使用 database/sql 包的 Prepare 和 Exec 方法。

使用 ORM 框架:

  • 使用 ORM 框架(如 GORM)可以自动处理参数化查询,减少手动拼接 SQL 的风险。

输入验证和过滤:

  • 对用户输入进行严格的验证和过滤,拒绝包含特殊字符的输入。
  • 可以使用正则表达式来限制输入格式。

最小权限原则:

  • 数据库用户应仅具有执行所需操作的最小权限,防止在 SQL 注入成功后造成更大损害。

使用存储过程:

  • 将 SQL 逻辑封装在存储过程中,限制直接执行 SQL 语句的机会。

错误信息隐藏:

  • 在生产环境中隐藏详细的错误信息,防止攻击者获取数据库结构信息。

4.9 权限控制

我们需要对用户进行权限校验,来保证接口能被正确的使用。一般有三种粒度,第一校验是否登录,第二校验接口功能权限,第三校验接口数据权限。通常内网系统,我们会使用 RBAC 来进行权限控制。

4.10  使用HTTPS

  • 数据加密:HTTPS 使用 SSL/TLS 协议对数据进行加密,确保数据在传输过程中的机密性和完整性,防止数据被窃取或篡改。

  • 身份验证:通过 SSL 证书验证服务器的身份,确保用户访问的是真实的网站,防止中间人攻击。

  • 完整性保护:确保数据在传输过程中不被篡改,如果数据被篡改,接收方可以检测到这种篡改,并拒绝接受这些数据。

05. 接口维护

5.1 文档

本人强烈反对单独使用文档维护接口,因为一旦文档和代码割裂只会逐渐缺失维护不再更新,要善于利用工具让代码和文档关联,自动更新。比如:开源的 swagger。

5.2 日志

  1. 合理区分和使用 error、warn、info、debug、trace5 种日志级别。error 较严重,对业务有影响,warn 是警告,对业务影响不大,需要开发关注。

  2. 日志要打印方法、入参和出参,方便定位问题,携带关键的信息参数,如 userId,traceId 等关键信息。

  3. 统一使用合理的日志格式,包含基础的信息如:当前时间戳、日志级别、线程 ID 等。

  4. 遇到条件分支,如有必要,可以在分支首行打印日志,方便定位问题,理清逻辑走向。

  5. 使用异步方式输出日志,可以提升接口性能。

  6. 禁止在线上开启 DEBUG,可能会打满磁盘影响业务系统运行。

  7. 日志文件可以考虑单招等级分离,比如 Nginx 的 access.log 和 error.log。

  8. 核心功能模块建议打印完整的日志,方便快速定位问题。

06. 总结

以上只是个人的一些总结,接口开发是一件比较复杂的事情,以上技巧仅供大家在写接口的时候作为参考。希望大家都能写出性能高、安全稳定易维护的好接口!

 

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

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

相关文章

FZU ACM寒假集训专题一

只有前四题是自己做的,都ac。 做题思路: 前三题比较简单。第四题想法是,一个一个读字母,按照asc码值分别存储个数,用轮数减个数得出还缺几个,最后加在一起。 中间因为不清楚scanf,让getchar读到换行符出错了。 学习总结: 本专题学习了时空复杂度的计算,c++语法糖,还有…

谷歌泰坦:Transformer之后的AI时代?

介绍 2017年,谷歌发布了一篇具有革命性意义的论文,题为《Attention is All You Need》(注意力是你所需要的一切)。这篇论文引发了我们今天所经历的AI革命,并引入了Transformer模型。Transformer已经成为如今几乎所有顶级大型语言模型(LLM)的核心架构。 Transformer的优势…

2025.1.23冠词

错误分析: 对于冠词知识点掌握不透彻 需掌握知识点: ‌冠词‌是英语语法中的重要概念,主要分为不定冠词(a/an)和定冠词(the),此外还有零冠词。冠词本身不能单独使用,也没有词义,主要用于帮助指明名词的含义。‌ 不定冠词(a/an) ‌用法‌:不定冠词用于单数可数名词…

2025多校冲刺省选模拟赛7

2025多校冲刺省选模拟赛7\(T1\) A. 三色卡(card) \(0pts\)如果存在一个小矩形和大矩形的大小相同,此时另外两个矩形可以任意放,贡献是容易计算的。否则至少需要一个小矩形覆盖大矩形的两个角,通过交换长、宽钦定完全覆盖行的矩形比完全覆盖列的矩形的数量多。完全覆盖行的矩…

重试机制与 CompletableFuture 拓展

重试机制与 CompletableFuture 拓展 禁止转载。 本文旨在讨论重试机制的特点和策略,分析常用重试类库的实现,讨论为 CompletableFuture 添加重试机制的方法。文章首发同名公众号,欢迎关注。 重试示例 以下是一个常见的使用异步重试的例子,当我们需要重试功能时,只需调用 r…

DL00765-光伏故障检测高分辨率无人机热红外图像细粒度含数据集4000+张

光伏发电作为清洁能源的重要组成部分,近年来得到了广泛应用。然而,随着光伏电站规模的扩大,光伏组件在运行过程中可能会出现各种故障,如热斑、遮挡、接线盒故障等。这些故障不仅会影响光伏电站的发电效率,还可能导致更严重的安全隐患。因此,准确、及时地检测并分类这些故…

VMware安装RHEL7.9

VMware安装 可以选择官网下载或者使用其他网盘资源下载。 需要注意的是,现在官网下载需要注册其账号。 下面是安装的详细步骤: 1.找到文件所在路径。双击打开之后,可能会出现环境初始化重启。重启即可。然后再次双击打开此软件。2.勾选《我接受许可协议中的条款》。3.选择安…

【二叉树】用数组给出二叉树层序遍历序列,建树以及遍历问题

传递悄悄话层序遍历数组形式的下标如下#include <algorithm> #include <cstring> #include <iostream>using namespace std;const int N = 1010, M = N * 2;int n; int h[N], e[M], ne[M], idx; int v[N], dist[N]; bool st[N];void add(int a, int b) {e[id…

关闭 Visual Studio 2022 的 Browser Link 功能

http://blog.tool90.com/330.html什么是 Browser Link 功能? Browser Link 是 Visual Studio 的一个功能,它允许 Visual Studio 与正在运行的 ASP.NET 应用程序建立一个实时通信通道。这意味着您可以在不刷新浏览器的情况下立即查看代码更改的效果。这个功能在进行前端开发时…

2025dsfz集训Day11:数位DP、状态压缩DP、单调队列优化DP

Day11:数位DP、状压DP、单调队列优化DP 经典题目:AccodersP2195 |【一本通提高数位动态规划】Amount of Degrees 题意: 求出区间 \([x,y]\) 中满足下面条件的所有的数:这个数 \(x\) 可以用 \(k\) 个不相等的 \(b\) 的整数幂之和。 首先这个区间是满足区间减法的。因此我们可…

PO报错

这个报错是报文结构不匹配导致,找了好久的问题--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------…

2025dsfz集训Day11: 单调队列优化DP

单调队列优化DP 单调队列队列是单调的,递增或递减 只能在队首或者队尾进行操作 队列中维护所有在窗口中的元素,有一些元素是没用的,以区间最大值为例:所以从左到右尝试加入队列,弹出队尾比当前数更小的元素,弹出队首已经出窗口的元素,再队尾压入当前数 这样,队首就是窗口…