Prisma与GraphQL游标分页实战:基于Relay规范的高性能实现

news2026/5/13 5:59:59
1. 项目概述与核心价值如果你正在用 Prisma 和 GraphQL 构建后端服务并且需要实现一个高性能、体验流畅的分页功能那么zoontek/prisma-cursor-pagination这个库很可能就是你一直在找的“瑞士军刀”。分页尤其是基于游标的分页是任何现代应用列表功能的核心但它的实现细节往往充满了陷阱——比如性能瓶颈、边界条件处理、以及与 Relay 规范的兼容性问题。这个库的核心价值就是把这些复杂、重复且容易出错的逻辑封装成几行简洁、类型安全的 API让你能专注于业务逻辑本身。我自己在多个生产级 GraphQL API 项目中都深度使用过这个库。它解决的问题非常具体如何将 GraphQL 查询中 Relay 风格的游标分页参数first,after,last,before无缝地转换成 Prisma ORM 的findMany查询参数并最终生成符合 Relay Connection 规范的数据结构。听起来简单但手动实现时你需要处理游标的解码与编码、skip和take的正负值转换、以及边界情况下pageInfo的准确计算。这个库把这些脏活累活都干了而且干得非常漂亮。它特别适合的开发者是那些已经在使用 Prisma 作为 ORM并且其 GraphQL API 遵循或希望遵循 Relay 连接规范的团队。无论你是要构建一个社交媒体的动态流、一个电商平台的商品列表还是一个 SaaS 后台的管理表格基于游标的分页都是提供无限滚动或“加载更多”体验的最佳实践。这个库能让你在几分钟内就搭建起一个生产就绪的分页解决方案而不是花几天时间去调试边界情况。2. 核心设计思路与方案选型解析2.1 为什么选择游标分页而非偏移分页在深入这个库之前我们必须先理解它背后的设计哲学为什么是游标分页Cursor-based Pagination传统的偏移分页使用skip和limit在数据量小的时候没问题但它有两个致命的缺陷。首先是性能问题。当你使用OFFSET 10000 LIMIT 20这样的查询时数据库实际上需要先扫描并跳过前 10000 条记录然后才返回接下来的 20 条。随着OFFSET值的增大查询会越来越慢因为数据库的工作量在线性增长。这对于用户翻到列表很靠后的页面时体验是灾难性的。其次是数据一致性问题。想象一个动态列表比如微博时间线。用户第一页看到了 20 条动态当他请求第二页OFFSET 20时如果在这期间有新的动态被插入到列表头部那么原来第 21 到 40 条的数据就会整体“向前移动一位”。用户第二页的第一条记录就会是原来他第一页的最后一条导致重复同时原来第 20 条的数据会被挤到第三页导致丢失。这就是偏移分页在动态数据集上的“漂移”问题。游标分页完美地解决了这两个问题。它不依赖数据的位置偏移量而是依赖一条具体记录的某个唯一且有序的字段通常是id或createdAt作为“游标”。查询时我们告诉数据库“给我id大于cursor_id的 20 条记录”。数据库可以利用索引比如在id字段上的 B-tree 索引进行高效的范围查询性能几乎恒定与翻到第几页无关。同时由于锚定的是具体的数据即使有新数据插入也不会影响当前游标之后数据的相对位置保证了分页的稳定性。2.2 Relay 连接规范一种优雅的标准化方案理解了游标分页的优势后下一个问题就是如何设计 API。Facebook 的 Relay 框架提出了一套名为“连接模式Connections”的 GraphQL 分页规范如今它已成为事实上的行业标准被 Apollo Client 等众多工具广泛支持。Relay 连接规范的核心数据结构如下type Query { users(first: Int, after: String, last: Int, before: String): UserConnection! } type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! # 还可以扩展自定义字段如 totalCount } type UserEdge { node: User! cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }edges: 一个边Edge数组每个边包含真正的数据节点node和该节点的游标cursor。pageInfo: 包含分页的元信息最重要的是hasNextPage和hasPreviousPage用于前端判断是否显示“加载更多”按钮。参数first/after组合用于向前翻页last/before组合用于向后翻页。这种对称性支持了双向分页。prisma-cursor-pagination库的设计目标就是充当 Prisma 与这套 Relay 规范之间的“翻译官”。它接收标准的 Relay 分页参数生成 Prisma 能理解的findMany参数再将 Prisma 返回的结果“包装”成标准的 Connection 对象。这个设计非常巧妙它没有侵入你的业务模型只是提供了一个纯函数式的转换层保持了极佳的灵活性和可测试性。2.3 库的核心工作流程拆解让我们通过一个具体的用户查询场景来拆解这个库内部的工作流程参数解析与验证你的 GraphQL 解析器Resolver收到客户端请求例如{ users(first: 10, after: cursor_abc123) }。你将这些args直接传递给parsePaginationArgs函数。生成 Prisma 查询参数parsePaginationArgs函数内部会进行逻辑判断如果使用了first和after它会将after游标解码通常是 base64 解码得到具体的id值然后生成findManyArgs为{ cursor: { id: decodedId }, skip: 1, take: 10 }。这里的skip: 1是为了跳过游标指向的那条记录本身。如果使用了last和before逻辑类似但take会是-10负数Prisma 会理解这是“获取游标之前的 N 条记录”。它会确保first和last不会同时使用等边界情况。执行数据库查询你将生成的findManyArgs直接用于prisma.user.findMany(findManyArgs)。这一步完全由你控制你可以在其中加入where、orderBy、include等任何 Prisma 支持的查询条件灵活性极高。结果转换查询返回用户数组后你调用toConnection(users)。这个函数会为数组中的每个用户创建一个Edge对象包含node用户数据和cursor该用户id的 base64 编码。计算pageInfo通过比较返回数组的长度与请求的first/last数量来判断是否还有下一页或上一页。返回标准化数据最终你将这个 Connection 对象返回给 GraphQL 层客户端会收到一个完全符合 Relay 规范的数据结构。整个流程清晰、解耦库只负责最复杂的分页逻辑转换而数据查询和业务过滤完全留给你这种设计非常值得称赞。3. 核心 API 深度解析与实战要点3.1parsePaginationArgs参数解析引擎这是整个库的入口和大脑。它的函数签名在文档中已经给出但在实战中有几个关键细节和选项需要深刻理解。输入参数 (args)你必须传递一个包含 Relay 标准分页参数的对象。库会检查这些参数的组合是否有效。一个常见的误区是试图传递整个 GraphQL resolver 的args对象而其中可能还包含其他过滤参数。你需要从中解构出分页参数。更安全的做法是在你的 GraphQL Schema 中明确定义分页参数的类型。配置选项 (options)目前只有一个connectionName选项用于在抛出错误时提供更友好的错误信息。例如如果你在users连接中传递了无效参数错误信息会是Invalid pagination arguments for users connection。这在调试多连接查询时非常有用。输出对象函数返回一个包含findManyArgs和toConnection的对象。这是“关注点分离”的典范findManyArgs用于获取数据toConnection用于格式化数据。注意findManyArgs中的take值。当使用last进行向后分页时库生成的take是负数如-10。这是 Prisma 的一个特性表示“获取游标之前的记录”。你不需要手动处理这个负号直接传给prisma.findMany即可。但如果你需要基于这个值进行其他计算比如记录日志请留意它可能是负数。3.2findManyArgs与 Prisma 的无缝对接findManyArgs对象可以直接展开到prisma.findMany()调用中这是库设计最精妙的地方之一。它意味着你可以在分页的基础上无缝地加入所有你需要的 Prisma 查询选项。const { findManyArgs, toConnection } parsePaginationArgs(args); const projects await prisma.project.findMany({ ...findManyArgs, // 包含 cursor, skip, take where: { status: PUBLISHED, // 添加你的业务过滤条件 title: { contains: searchTerm } // 支持搜索 }, orderBy: { createdAt: desc }, // 指定排序规则这对游标分页至关重要 include: { author: true, // 支持关联数据的加载 }, });关键点orderBy是游标分页的命脉游标分页的核心假设是数据有一个稳定、唯一的排序顺序。parsePaginationArgs默认使用id字段作为游标和排序依据。如果你的业务需求是按createdAt或updatedAt分页你必须显式地在findMany调用中通过orderBy指定并且确保游标cursor与排序字段一致。库的默认行为是基于id这是一个安全且通用的选择因为id通常唯一且递增。但如果你需要其他排序就需要自定义游标逻辑这超出了当前库的范围通常需要自己封装一层。3.3toConnection数据格式化的魔法toConnection函数接收一个由Prisma.findMany返回的数组并输出标准的 Relay Connection 对象。它内部主要做两件事构建edges遍历输入数组为每个元素创建一个Edge对象。node是元素本身cursor默认是该元素id字段的 Base64 编码。例如id为25的记录其游标可能是“MjU”。这种编码使得游标对客户端是不透明的字符串避免了暴露内部 ID 格式。计算pageInfo这是逻辑的核心。hasNextPage: 如果客户端请求了first: N并且返回的项目数等于N那么很可能还有下一页因为可能刚好取满。库会判断items.length first来决定是否为true。对于last请求逻辑类似但相反。startCursor和endCursor: 分别是第一条和最后一条边的cursor。hasPreviousPage: 逻辑与hasNextPage对称。一个重要的扩展点添加额外字段Connection 类型不仅仅是edges和pageInfo。一个非常常见的需求是返回总记录数totalCount用于前端显示“共 X 条结果”。toConnection返回的是一个纯对象你可以轻松地扩展它const [totalCount, users] await Promise.all([ prisma.user.count({ where: { /* 可复用相同的过滤条件 */ } }), prisma.user.findMany({ ...findManyArgs, where: { /* 过滤条件 */ } }), ]); return { totalCount, // 添加自定义字段 ...toConnection(users), // 展开标准的 edges 和 pageInfo };这样你的 GraphQL 类型定义中UserConnection就可以包含一个totalCount: Int!字段。这种模式兼顾了标准性和灵活性。4. 完整集成实战从零构建 GraphQL 分页 API让我们抛开简单的示例构建一个更贴近真实生产环境的场景一个任务管理应用我们需要一个可以按状态过滤、按截止日期排序并且能显示总任务数的分页查询。4.1 定义 GraphQL Schema首先我们在 GraphQL Schema 中定义我们的查询和类型。type Query { tasks( first: Int after: String last: Int before: String status: TaskStatus # 过滤参数 ): TaskConnection! } enum TaskStatus { TODO IN_PROGRESS DONE } type Task { id: ID! title: String! description: String status: TaskStatus! dueDate: DateTime! project: Project! } type TaskConnection { edges: [TaskEdge!]! pageInfo: PageInfo! totalCount: Int! # 自定义的扩展字段 } type TaskEdge { node: Task! cursor: String! } # PageInfo 是 Relay 标准类型通常放在公共类型定义中 type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }4.2 实现 Resolver 逻辑接下来在我们的 Apollo Server 或类似框架的 resolver 中实现tasks查询。import { parsePaginationArgs } from prisma-cursor-pagination; import { PrismaClient } from prisma/client; const prisma new PrismaClient(); const resolvers { Query: { tasks: async (_, args) { // 1. 解构出分页参数和其他过滤参数 const { first, after, last, before, status } args; const paginationArgs { first, after, last, before }; // 2. 解析分页参数生成 Prisma 查询基础 const { findManyArgs, toConnection } parsePaginationArgs(paginationArgs, { connectionName: tasks, // 可选的友好错误提示 }); // 3. 构建 Prisma 查询的 where 条件 const whereClause: any {}; if (status) { whereClause.status status; } // 这里可以添加更多过滤逻辑如根据用户权限过滤项目等 // 4. 并行执行总数统计和分页数据查询提升性能 const [totalCount, taskItems] await Promise.all([ prisma.task.count({ where: whereClause }), prisma.task.findMany({ ...findManyArgs, // 展开 cursor, skip, take where: whereClause, // 加入过滤条件 orderBy: [ { dueDate: asc }, // 主排序按截止日期升序 { id: asc } // 次排序确保顺序绝对唯一稳定 ], include: { project: true, // 包含关联的 project 数据 }, }), ]); // 5. 将结果转换为 Relay Connection 格式并加入 totalCount const connection toConnection(taskItems); return { ...connection, totalCount, }; }, }, };关键实操要点并行查询使用Promise.all同时执行count和findMany这比串行执行快得多。复合排序orderBy是一个数组。我们首先按dueDate排序但对于dueDate相同的任务我们使用id作为决胜字段tie-breaker。这对于游标分页的稳定性是必须的因为游标需要唯一标识一条记录在排序序列中的位置。如果仅按dueDate排序两个相同日期的任务就无法确定先后顺序。where条件复用确保count和findMany使用完全相同的where条件否则totalCount将失去意义。4.3 处理自定义排序与游标上面的例子有一个潜在问题parsePaginationArgs默认使用id字段来生成和解析游标。但我们的排序是[{ dueDate: asc }, { id: asc }]。如果我们直接使用库生成的游标基于id在dueDate相同的情况下分页逻辑仍然是正确的因为id是排序的一部分。这通常是可接受的。然而如果你的排序完全不包含id字段例如只按title字母排序或者你的游标需要包含多个字段的信息比如dueDate和id的组合那么你就需要自定义游标的编码和解码逻辑。prisma-cursor-pagination目前不直接支持这种高级定制。在这种情况下你可能需要放弃使用parsePaginationArgs的cursor生成在findManyArgs中手动指定cursor。自己实现一个toConnection函数根据你的排序规则生成复合游标例如将dueDate和id用分隔符拼接后 base64 编码。在解析器里手动解码客户端传来的游标并构造 Prisma 的cursor条件。这显然更复杂。因此在大多数情况下强烈建议在排序条件中包含唯一字段如id、createdAt作为最后一道保障并继续使用该库这是复杂度和功能性的最佳平衡点。5. 常见问题、性能优化与排查技巧实录在实际使用中你会遇到各种各样的问题。下面是我踩过坑后总结出来的经验。5.1 性能瓶颈排查与优化问题1count查询在数据量大时非常慢。这是最常见的问题。SELECT COUNT(*)在 InnoDB 引擎下对于大表且带有复杂WHERE条件的查询可能需要进行全表扫描。解决方案考虑是否真的需要精确的totalCount对于无限滚动的 feed 流用户通常只关心“还有更多吗”即hasNextPage而不是精确的总数。如果可以直接移除totalCount查询。使用估算值对于显示“约 10k 条结果”的场景MySQL 的SHOW TABLE STATUS或 PostgreSQL 的pg_class可以提供快速的行数估算。分页计数优化如果过滤条件导致计数很慢考虑是否有索引可以优化。为where条件中常用的字段添加复合索引。例如上例中如果经常按status过滤索引(status, dueDate, id)会对findMany和count都有巨大帮助。延迟加载或异步计算在页面首次加载时不返回totalCount而是通过一个单独的查询或 WebSocket 在后台计算并推送给前端。问题2findMany查询使用了skip。即使使用游标在“向后分页”last/before时库内部为了跳过游标记录本身可能会生成skip: 1。对于某些数据库和复杂查询skip仍可能有一定开销。解决方案确保cursor字段默认是id上有索引。Prisma 生成的cursor: { id: someId }查询会利用主键索引效率极高skip: 1的影响微乎其微。对于自定义排序确保排序字段组合有索引。例如对于orderBy: [{ dueDate: asc }, { id: asc }]创建索引(dueDate, id)。5.2 边界条件与错误处理问题客户端传递了矛盾的参数如first: 10和last: 10同时存在。parsePaginationArgs函数内部会进行验证并抛出清晰的错误。你应该在你的 GraphQL 服务器全局错误处理中捕获这些错误并将其转换为对客户端友好的 GraphQL 错误。// 在你的 resolver 中 try { const { findManyArgs, toConnection } parsePaginationArgs(args); } catch (error) { if (error.message.includes(Invalid pagination arguments)) { throw new UserInputError(分页参数无效请检查 first/after 或 last/before 的组合。, { invalidArgs: [first, last, after, before], }); // Apollo Server 的 UserInputError } throw error; // 重新抛出其他未知错误 }问题游标无效或指向不存在的记录。客户端可能传递一个伪造的或过期的游标。parsePaginationArgs会解码游标并用于构建cursor条件。如果该id在数据库中不存在findMany会从该游标之后开始查找。如果这个id非常大可能返回空列表hasPreviousPage或hasNextPage会相应地计算正确。这通常是预期行为不需要特殊处理。如果游标格式完全错误非法的 base64函数会抛出错误。5.3 与前端客户端的协作技巧游标的存储与传递前端从edges[n].cursor或pageInfo.endCursor获取的游标是一个不透明的字符串。在下次请求时直接将它作为after或before参数传回即可。不要尝试解码或修改它。理解pageInfohasNextPage: true意味着如果你用当前的endCursor和first参数再次请求很可能还能拿到数据。即使hasNextPage是false返回的edges数组也可能为空例如已经翻到最后一页。前端需要同时检查hasNextPage和edges长度来决定是否显示“加载更多”按钮或提示“没有更多数据”。实现“加载更多”与“回到顶部”向前加载下一页使用first: N, after: endCursor。向后加载上一页使用last: N, before: startCursor。这通常用于实现“回到顶部”或查看历史记录的功能。注意同时支持双向分页会增加前端状态管理的复杂性。许多应用只实现单向的“加载更多”向前分页这已经能满足绝大多数场景。5.4 高级场景基于时间的分页与数据归档有时我们需要按时间片分页比如“获取 2023 年 10 月 1 日之后创建的任务”。这本质上是将createdAt作为游标。虽然prisma-cursor-pagination默认不支持但我们可以利用其灵活性来实现。思路是不再依赖库生成的cursor而是手动处理。Schema 定义可以增加一个createdAfter或createdBefore参数。Resolver 实现tasks: async (_, { first, createdAfter, ...otherArgs }) { const where { createdAt: { gt: new Date(createdAfter) } }; const items await prisma.task.findMany({ take: first, where, orderBy: { createdAt: asc }, // 必须排序 }); // 手动构建 Connection 对象 const edges items.map(node ({ node, cursor: Buffer.from(node.createdAt.toISOString()).toString(base64), // 用时间做游标 })); const hasNextPage items.length first; return { edges, pageInfo: { hasNextPage, hasPreviousPage: false, // 单向分页 startCursor: edges[0]?.cursor, endCursor: edges[edges.length - 1]?.cursor, } }; }这种方式放弃了last/before但实现了基于时间范围的高效分页。对于日志、动态等按时间排序的场景非常有效。6. 总结与个人实践心得经过在多个项目中的实践zoontek/prisma-cursor-pagination已经成为了我 Prisma GraphQL 技术栈中的标配。它用极简的 API 解决了一个非常复杂的问题将开发者从繁琐的分页逻辑中解放出来。我个人的几点深刻体会是第一索引是游标分页的性能基石。无论库多么优秀如果数据库层没有正确的索引性能都会崩塌。务必为用作游标的字段默认是id以及你orderBy子句中的字段建立索引。复合排序就需要复合索引。在上线前用EXPLAIN语句分析你的分页查询是一个好习惯。第二理解“游标”的本质是稳定排序中的唯一位置标识。一旦你理解了这一点就能从容应对各种自定义排序的需求。当你不确定时在排序末尾加上id或createdAt这类唯一字段总是最保险的做法。第三totalCount是可选的美化而非必需的核心。在数据量巨大的表中获取精确总数代价高昂。在设计 API 时与产品经理沟通看是否可以用“是否有更多” (hasNextPage) 来代替精确的数字这往往能带来巨大的性能提升。最后这个库的最佳使用方式是“按需定制”。它提供了完美的 80% 的解决方案。对于另外 20% 的特殊需求如复合游标、非id游标不要试图魔改库本身而是在它的基础上进行封装。例如你可以写一个自己的createConnection函数内部根据不同的排序参数决定是调用parsePaginationArgs还是使用你自己的游标逻辑。它可能不是功能最全的分页库但在“做好一件事”这个哲学上它做到了极致。如果你的需求是标准的 Relay 游标分页并且在使用 Prisma那么直接引入它可以为你节省数天的开发调试时间并换来一个稳健、高效的分页基础。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…