有了 Prisma,就别用 TypeORM 了

要说2024 年 Node.js 的 ORM 框架应该选择哪个?毫无疑问选 Prisma。至于为何,请听我细细道来。

<!-- truncate -->

本文面向的对象是饱受 TypeORM 折磨的资深用户(说的便是我自己)。只对这两个 ORM 框架从开发体验上进行对比,你也可以到 [这里](https://www.prisma.io/docs/orm/more/comparisons/prisma-and-typeorm) 查看 Prisma 官方对这两个 ORM 框架的对比。

## 整体对比

### 更新频率&下载量

TypeORM 距离上次更新已经几近半年了(下图来源 24 年 1 月 1 日,没想到年初竟然还复活的),

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fcb78ff6275947e499ab9657eadc59e0~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1212\&h=152\&s=27202\&e=png\&b=fdfdfd)

从下载量以及 star 数来看,如今 Prisma 已经超过 TypeORM,这很大一部分的功劳归功于像 Next.js、Nuxt.js 这样的全栈框架。

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/04c8c6d1360d44dd82616a5c3600c93e~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1306\&h=519\&s=57932\&e=png\&b=fefefe)

上图来源 <https://npmtrends.com/prisma-vs-typeorm>

而在 Nest.js 的 [Discord 社区](https://discord.com/channels/520622812742811698/1156124199874732033) 讨论之中,Prisma 也成为诸多 Nest.js 开发者首选的 ORM 框架,因为它有着更好的开发体验。

在大势所趋之下相信你内心已经有一份属于自己的答案。

### 文档&生态

从文档的细致程度上 Prisma 比 TypeORM 要清晰详尽。在 [Get started](https://www.prisma.io/docs/getting-started) 花个数十分钟了解 Prisma 基本使用,到 [playground.prisma.io](https://playground.prisma.io/) 中在线尝试,到 [learn](https://www.prisma.io/learn) 查看官方所提供的免费教程。

此外 Prisma 不仅仅只支持 js/ts 生态,还支持其他语言。丰富的[生态](https://www.prisma.io/ecosystem)下,加之 Prisma 开发团队的背后是由商业公司维护,无需担心担心夭折同时还能事半功倍。

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/40d016645e404bf99cdc694cacb5e467~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1210\&h=548\&s=72569\&e=png\&b=ffffff)

## 开发体验对比

在从开发体验上对比之前,我想先说说 TypeORM 都有哪些坑(不足)。

### findOne(undefined) 所查询到的却是第一条记录

首先 TypeORM 有个天坑,你可以在 这个 [Issue](https://github.com/typeorm/typeorm/issues/2500) 中查看详情或查看 [这篇文章](https://pietrzakadrian.com/blog/how-to-hack-your-nodejs-application-which-uses-typeorm) 是如何破解使用 TypeORM 的 Node.js 应用。

当你使用 `userRepository.findOne({ where: { id: null } })` 时,从开发者的预期来看所返回的结果应该为 null 才对,但结果却是大跌眼镜,结果所返回的是 user 表中的第一个数据记录!

你可能会说,这不是 bug 吗?为何官方还不修。事实上确实是 bug,而事实上官方到目前也还没修复该 bug。再结合上文提到的更新频率,哦,那没事了。

目前解决方法则是用 `createQueryBuilder().where({ id }).getOne()` 平替上一条语句或者确保查询参数不为 undefined。但从此而言也可以看的出,TypeORM 在现今或许并不是一个很好的选择。

### synchronize: true 导致数据丢失

`synchronize` 表示数据库的结构是否和代码保持同步,官方提及到请不要在生产环境中使用,但在开发阶段这也并不是一个很好的做法。举个例子,有这么一个实体

```tsx
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number

  @Column()
  name: string
}
```

当你将 `name` 更改为 `title` 时,会发现原有的 `name` 下的数据全都丢失了!

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6c06b207b44a4c02943e93f77c447f49~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=581\&h=175\&s=19146\&e=png\&b=fefefe)

因为 TypeORM 针对上述操作的 sql 语句是这样的

```tsx
ALTER TABLE `user` CHANGE `name` `title` varchar(255) NOT NULL
ALTER TABLE `user` DROP COLUMN `title`
ALTER TABLE `user` ADD `title` varchar(255) NOT NULL
```

也就是说,当你在开发环境中,修改某个字段(包括名字,属性)时,该字段原有的数据便会清空。

因此针对数据库更新的操作最正确的做法是使用迁移(migrate)。

### 接入成本

在 Nest 项目中,Prisma 的接入成本远比 TypeORM 来的容易许多。

相信你一定有在 `xxx.module.ts` 中通过 `TypeOrmModule.forFeature([xxxEntity])` 的经历。就像下面代码这样:

```jsx
@Module({
  imports: [TypeOrmModule.forFeature([UserEntity])],
  controllers: [UserController],
  providers: [UserService],
  exports: [TypeOrmModule, UserService],
})
export class xxxModule {}
```

对于初学者而言,很大程度上会忘记 导入这段语句 就会出现这样的报错

```jsx

Potential solutions:
 - Is DeptModule a valid NestJS module?
 - If "UserEntityRepository" is a provider, is it part of the current DeptModule?
 - If "UserEntityRepository" is exported from a separate @Module, is that module imported within DeptModule?
   @Module({
     imports: [ /* the Module containing "UserEntityRepository" */ ]
   })

Error: Nest can't resolve dependencies of the userService (?). Please make sure that the argument "UserEntityRepository" at index [0] is available in the UserModule context.
```

此外这还不是最繁琐的,你还需要再各个 service 中,通过下面的代码来注入 userRepository

```tsx
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>
```

实体一多,要注入的 Repository 也就更多,无疑不是对开发者心智负担的加深。

再来看看 Prisma 是怎么导入的,你可以使用 [nestjs-prisma](https://nestjs-prisma.dev/docs/basic-usage/) 或者按照官方文档中[创建 PrismaService](https://docs.nestjs.com/recipes/prisma#use-prisma-client-in-your-nestjs-services)。

然后在 service 上,注入 PrismaService 后,就可以通过 `this.prisma[model]` 来调用模型(实体) ,就像这样

```jsx
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'nestjs-prisma';

@Injectable()
export class AppService {
  constructor(private prisma: PrismaService) {}

  users() {
    return this.prisma.user.findMany();
  }

  user(userId: string) {
    return this.prisma.user.findUnique({
      where: { id: userId },
    });
  }
}
```

哪怕创建其他新的实体,只需要重新生成 PrismaClient,都无需再导入额外服务。

### 更好的类型安全

Prisma 的贡献者中有 [ts-toolbelt](https://github.com/millsp/ts-toolbelt) 的[作者](https://github.com/millsp),正因此 Prisma 的类型推导十分强大,能够自动生成几乎所有的类型。

而反观 TypeORM 虽说使用 Typescript 所编写,但它的类型推导真是一言难尽。我举几个例子:

在 TypeORM 中,你需要 select 选择某个实体的几个字段,你可以这么写

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8400b57c63b3478eab0afc7e78a5eb92~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=533\&h=242\&s=34692\&e=png\&b=23272e)

你会发现 post 对象的类型提示依旧还是 postEntity,没有任何变化。但从开发者的体验角度而言,\*\*既然我选择查询 id 和 title 两个字段,那么你所返回的 post 类型应该也只有 id 与 title 才更符合预期。\*\*而后续代码中由于允许 post 有 body 属性提示,那么 post.body 为 null 这样不必要的结果。

再来看看 Prisma,你就会发现 post 对象的类型提示信息才符合开发者的预期。像这样的细节在 Prisma 有非常多。

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8f1e2ce71e5a44b088f155d230aa6de0~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=546\&h=248\&s=29202\&e=png\&b=24282f)

这还不是最关键的,当 TypeORM 通过需要使用 `createQueryBuilder` 方法来构造 sql 语句才能够满足开发者所要查询的预期,而当你使用了该方法,你就会发现你所编写的代码与 js 无疑,我贴几张图给大伙看看。

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/86472c339b1d48b4b63b8b97eb082c2e~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=393\&h=139\&s=17812\&e=png\&b=252930)

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/90dbe32970fe4d62aae7caac79511c9e~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=443\&h=180\&s=31501\&e=png\&b=23272e)

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a316e3305ebb4a2ba72f60e9031027b6~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=437\&h=133\&s=23430\&e=png\&b=242930)

这无疑会诱发一些潜在 bug,我就多次因为要 select 某表中的某个字段,而因为拼写错误导致查询失败。

### 创建实体

在 TypeORM 中,假设你要创建一个 User 实体,你需要这么做

```jsx
const newUser = new User()
newUser.name = 'kuizuo'
newUser.email = 'hi@kuizuo.cn'
const user = userRepository.save(newUser)
```

当然你可以对 User 实体中做点手脚,像下面这样加一个构造函数

```jsx
@Entity({ name: 'user' })
export class UserEntity  {
    @PrimaryGeneratedColumn()
  id: number

  @Column({ unique: true })
  username: string

  @Column()
  email: string

    constructor(partial?: Partial<UserEntity>) {
    Object.assign(this, partial)
  }
}
```

```jsx
const newUser = new User({
  name: 'kuizuo',
  email: 'hi@kuizuo.cn',
})
const user = userRepository.save(newUser)
```

于是你就可以传递一个 js 对象到 User 实体,而不是 newUser.xxx = xxx 像 Java 版的写法。

而要是涉及到多个关联的数据,往往需要先查询到关联数据,然后再像上面这样赋值+保存。这里就不展开了,使用过 TypeORM 的应该深有体会。

而在 Prisma 中,绝大多数的操作你都只需要一条代码语句外加一个对象结构,像上述 TypeORM 的操作对应 Prisma 的代码语句如下

```tsx
const user = await prisma.user.create({
  data: {
    name: 'kuizuo',
    email: 'hi@kuizuo.cn',
  },
})
```

### 根据条件来创建还是更新

在数据库中操作经常需要判断数据库中是否有某条记录,以此来决定是更改该记录还是创建新的一条记录,而在 Prisma 中,完全可以使用 upsert,就像下面这样

```tsx
const user = await prisma.user.upsert({
  where: { id: 1 },
  update: { email: 'example@prisma.io' },
  create: { email: 'example@prisma.io' },
})
```

### 聚合函数

在 TypeORM 中,假设你需要使用聚合函数来查询的话,通常会这么写

```tsx
const raw = await this.userRepository
  .createQueryBuilder('user')
  .select('SUM(user.id)', 'sum')
  .getRawOne()

const sum = raw.sum
```

如果只是像上面这样,单纯查询 sum,那么 raw 的值是 `{ sum: 1 }` , 但最要命的就是 `select` 配合 `getRawOne` 还要额外查询 user 实体的属性,所得到的结果就像这样

```tsx
const raw = await this.userRepository
  .createQueryBuilder('user')
  .select('SUM(user.id)', 'sum')
  .addSelect('user')
  .where('user.id = :id', { id: 1 })
  .getRawOne()
```

```tsx
{
    user_id: 1,
    user_name: 'kuizuo',
    user_email: 'hi@kuizuo.cn',
    sum: '1'
}
```

所有 user 的属性都会带有 `user_` 前缀,这看上去有点不是那么合理,但如果考虑要联表查询的情况下,就会存在相同名称的字段,通过添加表名(别名)前缀就可以避免这种情况,这样来看貌似又有点合理了。

但还是回到熟悉的类型安全,这里的所返回的 raw 对象是个 any 类型,一样不会有任何提示。

而在 Prisma 中,提供了 专门用于聚合的方法 [aggregate](https://www.prisma.io/docs/orm/reference/prisma-client-reference#aggregate),可以特别轻松的实现聚合函数查询。

```tsx
const minMaxAge = await prisma.user.aggregate({
  _count: {
    _all: true,
  },
  _max: {
    profileViews: true,
  },
  _min: {
    profileViews: true,
  },
})
```

```tsx
{
  _count: { _all: 29 },
  _max: { profileViews: 90 },
  _min: { profileViews: 0 }
}
```

***

看到这里,你若是长期使用 TypeORM 的用户必定会感同身受如此糟糕的体验。那种开发体验真的是无法用言语来形容的。

### Prisma 生态

### 分页

在 Prisma 你要实现分页,只需要在 prismaClient 继承 [prisma-extension-pagination](https://github.com/deptyped/prisma-extension-pagination) 这个库。就可像下面这样,便可在 model 中使用paginate方法来实现分页,如下代码。

```tsx
import { PrismaClient } from '@prisma/client'
import { pagination } from 'prisma-extension-pagination'

const prisma = new PrismaClient().$extends(pagination())
```

```jsx
const [users, meta] = prisma.user
  .paginate()
  .withPages({
    limit: 10,
    page: 2,
    includePageCount: true,
  });

// meta contains the following
{
  currentPage: 2,
  isFirstPage: false,
  isLastPage: false,
  previousPage: 1,
  nextPage: 3,
  pageCount: 10, // the number of pages is calculated
  totalCount: 100, // the total number of results is calculated
}
```

支持页数(page)或光标(cursor)。

::: 两种分页的使用场景

按页查询通常

光标查询 则用于流式查看,例如无限下拉滚动

:::

而在 TypeORM 你通常需要自己封装一个 paginate方法,就如下面代码所示(以下写法借用 [nestjs-typeorm-paginate](https://www.npmjs.com/package/nestjs-typeorm-paginate))

```tsx
async function paginate<T>(
  queryBuilder: SelectQueryBuilder<T>,
  options: IPaginationOptions,
): Promise<Pagination<T>> {
  const { page, limit } = options

  queryBuilder.take(limit).skip((page - 1) * limit)

  const [items, total] = await queryBuilder.getManyAndCount()

  return createPaginationObject<T>({
    items,
    totalItems: total,
    currentPage: page,
    limit,
  })
}

// example
const queryBuilder = userRepository.createQueryBuilder('user')
const { items, meta } = paginate(queryBuilder, { page, limit })
```

当然也可以自定义userRepository,为其添加 paginate 方法,支持链式调用。但这无疑增添了开发成本。

### 根据 Schema 自动生成数据验证

得益于 Prisma 强大的数据建模 dsl,通过 [generators](https://www.prisma.io/docs/orm/prisma-schema/overview/generators) 生成我们所需要的内容(文档,类型),比如可以使用 [zod-prisma-types](https://github.com/chrishoermann/zod-prisma-types) 根据 Schema 生成 [zod](https://github.com/colinhacks/zod) 验证器\*\*。\*\*

举个例子,可以为 schema.prisma 添加一条 generator,长下面这样

```tsx
generator client {
  provider = "prisma-client-js"
  output   = "./client"
}

generator zod {
  provider                         = "zod-prisma-types"
  output                           = "./zod"
  createModelTypes                 = true
    // ...rest of config
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id         String      @id @default(uuid())
  email      String      @unique
  name       String?
}
```

执行构建命令后,这将会自动生成 zod/index.ts 文件,将包含 UserSchema 信息,其中片段代码如下

```tsx
export const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string(),
  name: z.string().nullable(),
})

export type User = z.infer<typeof UserSchema>
```

再通过 createZodDto,将 zod 验证器转化为 dto 类,就像下面这样

![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/47bc4d5d5f9343a6af7d804769e6be2d~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=574\&h=191\&s=32905\&e=png\&b=22272d)

当然你可能并不想在 nestjs 项目中使用 zod,而是希望使用传统的 [class-validator](https://www.npmjs.com/package/class-validator) 来编写 dto。可以使用社区提供的 [prisma-class-generator](https://github.com/kimjbstar/prisma-class-generator) 根据已有 model 生成 dto。

***

合理来说,Prisma 并不是一个传统的 ORM,它的工作原理并不是将表映射到编程语言中的模型类,为处理关系数据库提供了一种面向对象的方式。而是在 Prisma Schema 中定义模型。在应用程序代码中,您可以使用 Prisma Client 以类型安全的方式读取和写入数据库中的数据,而无需管理复杂模型实例的开销。

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5842c27c892a4adfaf309eb0f13924ce~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=650\&h=308\&s=21358\&e=png\&a=1\&b=a7b3c4)

总而言之,你若想要[更好的类型](https://www.prisma.io/docs/orm/prisma-client/type-safety),简洁的[实体声明语法](https://www.prisma.io/docs/orm/prisma-schema/data-model/database-mapping#prismas-default-naming-conventions-for-indexes-and-constraints),况且带有[可视化桌面端应用](https://www.prisma.io/studio),以及更好的[生态完备](https://www.prisma.io/ecosystem),那么你就应该选 Prisma。

## 总结

在写这篇文章时,我也是彻底的将 Nestjs 项目中由 TypeORM 迁移到 Prisma ,这期间给我最大的变化就是在极少的代码量却又能实现强大的功能。许多涉及多表的 CRUD操作可以通过一条简洁的表达式来完成,而在使用 TypeORM 时,常常需要编写繁琐臃肿的 queryBuilder。

TypeORM 有种被 nestjs 深度绑定的模样,一提到 TypeORM,想必第一印象就是 Nestjs 中所用到的 ORM 框架。然而,Prisma 却不同,是一个全能通用的选择,可以在任何的 js/ts 框架中使用。

从开发体验的角度不接受任何选择 TypeORM 的反驳,有了更优优秀的选择,便不愿意也不可能在回去了。如果你还未尝试过 Prisma,我强烈建议你亲身体验一番。
 

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

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

相关文章

SRM供应商招标采购管理系统(源码)

软件相关资料获取&#xff1a;点我获取 一、SRM供应商在线采购 SRM供应商在线采购是指企业通过互联网平台&#xff0c;实现对供应商的在线招募、选择、关系管理等一系列活动。这种采购方式具有高效、透明、便于管理的特点&#xff0c;能够帮助企业降低采购成本&#xff0c;提…

陶瓷碗口缺口检测-图像形态学

图像形态学 对得到的灰度图像&#xff0c;需要进行二值化处理和区域填充。二值化涉及两个步骤&#xff0c;第一&#xff0c;对图像行图像分割&#xff0c;将图像分割成目标和和背景&#xff1b;第二&#xff0c;对分割后图像进行区域填充。本例中的背景为黑色&#xff0c;可以…

全自动网页生成系统网站源码重构版

源码优点: 所有模板经过精心审核与修改&#xff0c;完美兼容小屏手机大屏手机&#xff0c;以及各种平板端、电脑端和360浏览器、谷歌浏览器、火狐浏览器等等各大浏览器显示。 免费制作 为用户使用方便考虑&#xff0c;全自动网页制作系统无需繁琐的注册与登入&#xff0c;直…

独立按键控制继电器开关

/*----------------------------------------------- 内容&#xff1a;对应的继电器接口需用杜邦线连接到uln2003继电器控制端 ------------------------------------------------*/ #include<reg52.h> //包含头文件&#xff0c;一般情况不需要改动&#xff0…

x-cmd pkg | csview - 美观且高性能的 csv 数据查看工具

目录 介绍首次用户功能特点类似工具与竞品进一步阅读 介绍 csview 是一个用于在命令行中查看 CSV 文件的工具&#xff0c;采用 Rust 语言编写的&#xff0c;支持中日韩/表情符号。它允许用户在终端中以表格形式查看 CSV 数据&#xff0c;可以对数据进行排序、过滤、搜索等操作…

软件领域新手方向

新手入门指南 —.系统软件 驱动程序&#xff0c;操作系统(大企业要)&#xff1b; 二.C/S架构软件 大型(client客戸端需要下載安装&#xff0c;server服务端与客户交互)&#xff0c;小型(单机游戏)&#xff1b;桌面应用 三.B/S架构软件&#xff08;分前端&#xff0c;后端&…

x-cmd pkg | fx - Warp 支持的 JSON 查看和处理工具

目录 简介首次用户功能特点类似工具与竞品进一步探索 简介 fx 是一款由专为 JSON 定制的双用途命令行工具&#xff0c;提供基于终端的 JSON 查看器和 JSON 处理实用程序。虽然 JSON 查看器是用 Go 编写的&#xff0c;并且无需外部依赖即可运行&#xff0c;但 JSON 处理工具是用…

桌面显示器type-c接口方案6020

TYPE-C接口桌面显示器&#xff0c;与传统的显示器不同的是 新一类的显示器不仅仅支持视频传输&#xff0c;还可以利用显示器的DC电源转成PD协议充电给设备端&#xff08;笔记本&#xff0c;任天堂等HOST设备&#xff09;充电。 这种新型的TYPE-C接口桌面显示器&#xff0c;不仅…

什么是集成测试?

什么是集成测试 集成测试&#xff08;Integration Testing&#xff09;&#xff0c;也叫组装测试或联合测试。在单元测试的基础上&#xff0c;将所有模块按照设计要求&#xff08;如根据结构图&#xff09;组装成为子系统或系统&#xff0c;进行集成测试。 集成测试&#xff…

Logstash配置详解

一、配置文件 Logstash配置文件位于Logstash安装目录下bin/logstash.conf 启动命令: logstash -f logstash.conf文件描述logstash.yml配置Logstash的yml。pipelines.yml包含在单个Logstash实例中运行多个管道的框架和说明。jvm.options配置Logstash的JVM&#xff0c;使用此文…

确定性网络技术怎样实现网络的可靠性?

确定性网络技术通过采用特定的协议、机制和策略&#xff0c;有助于提高网络的可靠性。本文通过一些关键的方面&#xff0c;来说明确定性网络技术如何实现这一目标。 时钟同步机制 时钟同步机制是确定性网络中的核心角色。为了实现高度可靠的通信&#xff0c;需要采用先进的时钟…

算法学习系列(二十):树与图的DFS与BFS

目录 引言一、图的存储1.邻接矩阵2.邻接表 二、图的DFS1.模板2. 例题&#xff1a;树的重心 三、图的BFS1.模板2.例题&#xff1a;图中点的层次 引言 关于这个树与图考察的还是比较多的&#xff0c;其实就是图&#xff0c;树就是一种特殊的图&#xff0c;树是一种无环无向图&am…

brpc: a little source code

之前在https://www.yuque.com/treblez/qksu6c/nqe8ip59cwegl6rk?singleDoc# 《olap/clickhouse-编译器优化与向量化》中我谈过brpc的汇编控制bthread。本文就来看一下brpc作为一个高性能的rpc实现&#xff0c;除了自定义线程栈之外&#xff0c;代码还有什么优秀之处。 因为时间…

三国杀移动版武将台词大全-神

三国杀移动版武将台词大全-魏-CSDN博客 三国杀移动版武将台词大全-蜀-CSDN博客 三国杀移动版武将台词大全-吴-CSDN博客 三国杀移动版武将台词大全-群-CSDN博客 三国杀移动版武将台词大全-神-CSDN博客 # 神&#xff08;17/17&#xff09;&#xff1a; # 神刘备 神华佗 …

Vue3 不同版本的Pinia如何做持久化存储

不同版本的pinia支持的存储插件不同&#xff0c;高/低版本的持久化存储插件整合如下&#xff0c;都是我实践过的&#xff0c;请放心使用。&#x1f60a; 一、Vue3.2&#xff0c;pinia < 2.0.5&#xff0c;用pinia-plugin-persist 提示&#xff1a;不要去下最新的pinia-plug…

QT基础篇(5)QT5主窗口

1.QT5主窗口 QT5主窗口由以下几个部分构成&#xff1a; 标题栏&#xff1a;位于窗口的顶部&#xff0c;显示窗口的标题和控制按钮&#xff08;最小化、最大化、关闭&#xff09;。菜单栏&#xff1a;位于窗口的顶部&#xff0c;包含一系列菜单和菜单项&#xff0c;用于添加和…

K8S后渗透横向节点与持久化隐蔽方式探索

前言 通常在红蓝对抗中&#xff0c;我们可能会通过各种方法如弱口令、sql注入、web应用漏洞导致的RCE等方法获得服务器的权限&#xff1b;在当前云原生迅猛发展的时代&#xff0c;这台服务器很可能是一个容器&#xff0c;在后续的后渗透由传统的提权变为容器逃逸&#xff0c;内…

easyexcel上传校验的方法封装

easyexcel版本3.1.5 使用自定义注解的方式来定义校验的类型&#xff0c;避免冗余代码。 //校验value不能为空&#xff0c;且长度最大为30 RowCheck(value {RowCheckType.EMPTY,RowCheckType.LENGTH},max 30) private String value; 具体代码&#xff1a; 首先定义校验类型…

RTSP协议实现发送ACC音频数据

一.AAC音频格式介绍 AAC音频格式&#xff1a;Advanced Audio Coding&#xff08;高级音频解码&#xff09;&#xff0c;是一种由MPEG—4标准定义的有损音频压缩格式。音频压缩编码的输出码流&#xff0c;以音频帧的形式存在。每个音频帧包含若干个音频采样的压缩数据&#xff0…

Linux常用命令之cp、rm、touch、mv

cp: 复制文件或目录 -f 覆盖目标同名文件或目录时不进行提醒&#xff0c;而直接强制复制。-i 覆盖目标同名文件或目录时提醒用户确认。-p 复制时保持源文件的权限、属主及时间标记等属性不变&#xff08;默认权限属主是变化的&#xff09;。-r 复制目录时必须使用此选项&a…