laravel 入门(8)查询构建器

使用 DB 门面执行原生 SQL 语句#

原生 Statement 语句#

我们可以通过 DB 门面提供的 statement 方法执行原生的 SQL Statement 语句,比如创建、删除、修改数据表操作:

Copy Highlighter-hljs
DB::statement('drop table `users`'); DB::statement('create table `users` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL)');

只不过在 Laravel 中,我们不推荐这么做,因为这些对数据表结构的操作可以通过数据库迁移功能来实现,而且那样做的话可维护性更好。

原生查询语句#

DB 门面提供了一个 select 语句帮助我们对数据表进行查询:

Copy Highlighter-hljs
$users = DB::select('select * from `users`');

该方法返回包含所有查询结果的 stdClass 对象数组

如果你想要进一步指定查询条件,此时就要考虑 SQL 语句的安全性,比如规避 SQL 注入攻击,尤其是这个查询条件是用户通过请求参数指定的。由于 Laravel 数据库功能底层基于 PHP 的 PDO 实现,因此我们可以借助 PDO 的参数绑定功能来防范 SQL 注入,所以对于指定查询条件的 SQL 查询语句,可以这么实现:

Copy Highlighter-hljs
$name = '学院君'; $users = DB::select('select * from `users` where `name` = ?', [$name]);

我们还可以对绑定参数进行命名以便更加明确绑定了哪个参数:

Copy Highlighter-hljs
$users = DB::select('select * from `users` where `name` = :name', ['name' => $name]);

如果你要设置多个查询条件,添加多个绑定参数即可。

原生插入语句#

想要在数据库中插入一条记录,通过 DB 门面提供的 insert 语句即可:

Copy Highlighter-hljs
$flag = DB::insert('insert into `users` (`name`, `email`, `password`) values (?, ?, ?)', [$name, $email, $password]);

如果插入成功,返回 true,插入失败,则抛出 QueryException 异常。

原生更新语句#

要修改数据表记录,可以通过 DB 门面提供的 update 方法:

Copy Highlighter-hljs
$affectedRows = DB::update('update `users` set `name` = ? where id = ?', [$name, $id]);

如果更新成功,返回受影响行数,如果更新数据与原记录数据一样,则返回0,如果更新出错,则抛出 QueryException 异常。

原生删除语句#

要删除数据表记录,可以通过 DB 门面的 delete 方法实现:

Copy Highlighter-hljs
$affectedRows = DB::delete('delete from `users` where id = ?', [$id]);

和更新语句一样,如果删除成功,该方法返回受影响行数,删除记录不存在,返回 0,删除出错,抛出 QueryException 异常

使用查询构建器进行增删改查#

查询记录#

要查询指定数据表中的所有记录

Copy Highlighter-hljs
$users = DB::table('users')->get();

该方法返回的是一个包含所有查询结果的 stdClass 集合:

如果要指定查询条件,可以通过 where 实现:

Copy Highlighter-hljs
$users = DB::table('users')->where('name', $name)->get();

使用查询构建器进行查询,无需手动设置参数绑定来规避 SQL 注入攻击,因为 Laravel 底层会帮助我们自动实现参数绑定,所以推荐使用查询构建器进行数据库操作。

有时候我们可能希望返回查询结果中的第一条记录,这可以通过将 get 方法替换为 first 方法来实现:

Copy Highlighter-hljs
$user = DB::table('users')->where('name', $name)->first();

返回的就是一个单个 stdClass 对象了

默认返回所有字段,要指定查询的字段,可以通过 select 方法来实现:

Copy Highlighter-hljs
$user = DB::table('users')->select('id', 'name', 'email')->where('name', $name)->first();

插入记录#

要通过查询构建器插入一条记录,也很简单,通过 insert 方法即可:

Copy Highlighter-hljs
$flag = DB::table('users')->insert([ 'name' => str_random(10), 'email' => str_random(8) . '@163.com', 'password' => bcrypt('secret') ]);

如果想要在插入之后获取对应记录的主键 ID,将 insert 方法改为调用 insertGetId 方法:

Copy Highlighter-hljs
$userId = DB::table('users')->insertGetId([ 'name' => str_random(10), 'email' => str_random(8) . '@qq.com', 'password' => bcrypt('secret') ]);

查询构建器还支持一次插入多条记录:

Copy Highlighter-hljs
DB::table('users')->insert([ ['name' => str_random(10), 'email' => str_random(8) . '@qq.com', 'password' => bcrypt('123')], ['name' => str_random(10), 'email' => str_random(8) . '@qq.com', 'password' => bcrypt('456')], ['name' => str_random(10), 'email' => str_random(8) . '@qq.com', 'password' => bcrypt('789')], ]);

同样,如果插入出错,抛出 QueryException 异常,如果是一次插入多条记录的话,会整体中断,一条都不会插进去。

更新记录#

更新数据库记录通过 update 方法来完成,我们可以在该方法中传入待修改字段及对应修改值数组:

Copy Highlighter-hljs
$affectedRows = DB::table('users')->where('id', '>', $id)->update(['name' => str_random(8)]);

同样,该方法返回的也是受影响行数

注:where 方法第二个参数省略的话,默认是 =,如果不是相等条件,需要手动指定该参数值,比如 > 表示大于,< 表示小于,和比较运算符一致。

如果是数值字段的更新的话,Laravel 还为我们提供了 increment 和 decrement 方法用于快速进行数值增减,默认步长是 1,当然你可以通过第二个参数指定步长值:

Copy Highlighter-hljs
DB::table('posts')->where('id', 100)->increment('views'); // views+1 DB::table('posts')->where('id', 100)->increment('views', 5); // views+5 DB::table('posts')->where('id', 100)->decrement('votes'); // votes-1

删除记录#

通过查询构建器删除记录可以通过 delete 方法来实现:

Copy Highlighter-hljs
$affectedRows = DB::table('users')->where('id', '>=', $id)->delete();

delete 方法返回受影响行数

如果我们想要清空整张数据表,可以通过不指定 where 条件来实现:

Copy Highlighter-hljs
$affectedRows = DB::table('users')->delete();

如果我们还想在清空记录之后重置自增 ID,可以通过 truncate 方法来实现:

Copy Highlighter-hljs
$affectedRows = DB::table('users')->truncate();

复杂的查询#

查询小技巧#

有时候,我们想要获取的并不是一行或几行记录,而是某个字段的值

Copy Highlighter-hljs
$name = '学院君'; $email = DB::table('users')->where('name', $name)->value('email');

通过 value 方法返回的就是指定字段的值,无需做额外的判断和提取操作。

如果你想要判断某个字段值在数据库中是否存在对应记录,可以通过 exists 方法快速实现:

Copy Highlighter-hljs
$exists = DB::table('users')->where('name', $name)->exists();

如果存在,返回 true,否则返回 false。该方法还有一个与之相对的方法 doesntExist()。

从数据库获取指定查询结果后,以主键 ID 值为键,以某个字段值为值构建关联数组,以前,你可能不得不遍历查询结果构建数组才能解决这样的问题,在 Laravel 中,我们只需在查询构建器上调用 pluck 方法即可:

Copy Highlighter-hljs
$users = DB::table('users')->where('id', '<', 10)->pluck('name', 'id');

该查询返回的结果如下:

注意,我们在传递参数到 pluck 方法的时候,键对应的字段在后面,值对应的字段在前面。

有的时候,我们从数据库返回的结果集比较大,一次性返回进行处理有可能会超出 PHP 内存限制,这时候,我们可以借助 chunk 方法将其分割成多个的组块依次返回进行处理:

Copy Highlighter-hljs
$names = []; DB::table('users')->orderBy('id')->chunk(5, function ($users) use (&$names) { foreach ($users as $user) { $names[] = $user->name; } });

以上代码的意思是对 users 按照 id 字段升序排序,然后将获取的结果集每次返回5个进行处理,将用户名依次放到 $names 数组中。打印 $names,结果如下:

聚合函数#

在开发后台管理系统时,经常需要对数据进行统计、求和、计算平均值、最小值、最大值等,对应的方法名分别是 count、sum、avg、min、max:

Copy Highlighter-hljs
$num = DB::table('users')->count(); # 计数 9 $sum = DB::table('users')->sum('id'); # 求和 45 $avg = DB::table('users')->avg('id'); # 平均值 5 $min = DB::table('users')->min('id'); # 最小值 1 $max = DB::table('users')->max('id'); # 最大值 9

高级 Where 查询#

基本查询#

最基本的 WHERE 查询子句就是通过 where 方法进行简单查询了:

Copy Highlighter-hljs
DB::table('posts')->where('views', 0)->get(); # 此处等号可以省略 DB::table('posts')->where('views', '>', 0)->get(); DB::table('posts')->where('views', '<>', 0)->get();

第一个参数表示字段名,第二个参数表示运算符(支持SQL所有运算符),第三个参数表示比较值。

原生表达式

有时候你希望在查询中使用原生表达式,这些表达式将会以字符串的形式注入到查询中,所以要格外小心避免 SQL 注入。想要创建一个原生表达式,可以使用 DB::raw 方法:

Copy Highlighter-hljs
$users = DB::table('users') ->select(DB::raw('count(*) as user_count, status')) ->where('status', '<>', 1) ->groupBy('status') ->get();

注:原生语句会以字符串的形式注入查询,所以这里尤其要注意避免 SQL 注入攻击。

除了使用 DB::raw 外,你还可以使用以下方法来插入原生表达式到查询的不同部分。

selectRaw

selectRaw 方法可用于替代 select(DB::raw(...)),该方法接收一个可选的绑定数组作为第二个参数:

Copy Highlighter-hljs
$orders = DB::table('orders') ->selectRaw('price * ? as price_with_tax', [1.0825]) ->get();

whereRaw / orWhereRaw

whereRaw 和 orWhereRaw 方法可用于注入原生 where 子句到查询,这两个方法接收一个可选的绑定数组作为第二个参数:

Copy Highlighter-hljs
$orders = DB::table('orders') ->whereRaw('price > IF(state = "TX", ?, 100)', [200]) ->get();

注:whereRaw 与 where (\DB::raw ()) 的区别
用 where(DB::raw(''))的时候,结尾会被增加一个莫名其妙的 is null(),用toSql()发现的

havingRaw / orHavingRaw

havingRaw 和 orHavingRaw 方法可用于设置原生字符串作为 having 子句的值

Copy Highlighter-hljs
$orders = DB::table('orders') ->select('department', DB::raw('SUM(price) as total_sales')) ->groupBy('department') ->havingRaw('SUM(price) > 2500') ->get();

like查询

有时候我们可能会对字段进行模糊查询,尤其是字符串匹配的时候:

Copy Highlighter-hljs
DB::table('posts')->where('title', 'like', 'Laravel学院%')->get();

and查询

如果有多个 WHERE 条件怎么办?在查询构建器中,可以通过方法链轻松搞定

Copy Highlighter-hljs
DB::table('posts')->where('id', '<', 10)->where('views', '>', 0)->get();

上述代码表示获取 where id < 10 and views > 0 的数据库记录,更多的条件用更多的 where 方法即可。此外,我们还可以通过传入数组参数的方式实现上述代码同样的功能:

Copy Highlighter-hljs
DB::table('posts')->where([ ['id', '<', 10], ['views', '>', 0] ])->get();

or查询

在日常查询中,or 条件的查询也很常见,在查询构建器中,可以通过 orWhere 方法来实现:

Copy Highlighter-hljs
DB::table('posts')->where('id', '<', 10)->orWhere('views', '>', 0)->get();

上述代码表示获取 where id < 10 or views > 0 的数据库记录,多个 and 查询可以通过多个 where 方法连接,同理,多个 or 查询也可以通过多个 orWhere 方法连接。

between查询

在一些涉及数字和时间的查询中,BETWEEN 语句可以排上用场,用于获取在指定区间的记录。在查询构建器中,我们可以通过 whereBetween 方法来实现 between 查询:

Copy Highlighter-hljs
DB::table('posts')->whereBetween('views', [10, 100])->get();

上述代码表示获取 where view between 10 and 100 的数据库记录。与之相对的还有一个 whereNotBetween 方法,用于获取不在指定区间的数据库记录:

Copy Highlighter-hljs
DB::table('posts')->whereNotBetween('views', [10, 100])->get();

对应的 WHERE 条件是 where views not between 10 and 100。

你可以看出来 between 语句是可以通过 and/or 查询来替代的,只不过使用 between 语句会更简单明了。

in查询

IN 查询也很常见,比如我们需要查询的字段值是某个序列集合的子集的时候。IN 查询可以通过 whereIn 方法来实现:

Copy Highlighter-hljs
DB::table('posts')->whereIn('user_id', [1, 3, 5, 7, 9])->get();

对应的 WHERE 子句是 where user_id in (1, 3, 5, 7, 9)。使用该方法时,需要注意传递给 whereIn 的第二个参数不能是空数组,否则会报错。

同样,与之相对的,还有一个 whereNotIn 方法,表示与 whereIn 相反的查询条件。将上述代码中的 whereIn 方法改为 whereNotIn,对应的查询子句就是 where user_id not in (1, 3, 5, 7, 9)。

null查询

NULL 查询就是判断某个字段是否为空的查询,Laravel 查询构建器为我们提供了 whereNull 方法用于实现该查询:

Copy Highlighter-hljs
DB::table('users')->whereNull('email_verified_at')->get();

对应的 WHERE 查询子句是 where email_verified_at is null,同样,该方法也有与之相对的 whereNotNull 方法,例如,要进行 where email_verified_at is not null 查询,可以这么实现:

Copy Highlighter-hljs
DB::table('users')->whereNotNull('email_verified_at')->get();

日期查询

关于日常查询,查询构建器为我们提供了丰富的方法,从年月日到具体的时间都有覆盖:

Copy Highlighter-hljs
DB::table('posts')->whereYear('created_at', '2018')->get(); # 年 DB::table('posts')->whereMonth('created_at', '11')->get(); # 月 DB::table('posts')->whereDay('created_at', '28')->get(); # 一个月的第几天 DB::table('posts')->whereDate('created_at', '2018-11-28')->get(); # 具体日期 DB::table('posts')->whereTime('created_at', '14:00')->get(); # 时间

上面这几个方法同时还支持 orWhereYear、orWhereMonth、orWhereDay、orWhereDate、orWhereTime。

字段相等查询

有的时候,我们并不是在字段和具体值之间进行比较,而是在字段本身之间进行比较,查询构建器提供了 whereColumn 方法来实现这一查询:

Copy Highlighter-hljs
DB::table('posts')->whereColumn('updated_at', '>', 'created_at')->get();

对应的 WHERE 查询子句是 where updated_at > created_at。

JSON查询

从 MySQL 5.7 开始,数据库字段原生支持 JSON 类型,对于 JSON 字段的查询,和普通 where 查询并无区别,只是支持对指定 JSON 属性的查询:

Copy Highlighter-hljs
DB::table('users') ->where('options->language', 'en') ->get();

如果属性字段是个数组,还支持通过 whereJsonContains 方法对数组进行包含查询:

Copy Highlighter-hljs
DB::table('users') ->whereJsonContains('options->languages', 'en_US') ->get(); DB::table('users') ->whereJsonContains('options->languages', ['en_US', 'zh_CN']) ->get();

高级查询#

参数分组

除了以上这些常规的 WHERE 查询之外,查询构建器还支持更加复杂的查询语句,考虑下面这个 SQL 语句:

Copy Highlighter-hljs
select * from posts where id <= 10 or (views > 0 and created_at < '2018-11-28 14:00');

貌似我们通过前面学到的方法解决不了这个查询语句的构造,所以我们需要引入更复杂的构建方式,那就是引入匿名函数的方式(和连接查询中构建复杂的连接条件类似):

Copy Highlighter-hljs
DB::table('posts')->where('id', '<=', 10)->orWhere(function ($query) { $query->where('views', '>', 0) ->whereDate('created_at', '<', '2018-11-28') ->whereTime('created_at', '<', '14:00'); })->get();

在这个匿名函数中传入的 $query 变量也是一个查询构建器的实例。这一查询构建方式叫做「参数分组」,在带括号的复杂 WHERE 查询子句中都可以参考这种方式来构建查询语句。

WHERE EXISTS

此外,我们还可以通过查询构建器提供的 whereExists 方法构建 WHERE EXISTS 查询:

Copy Highlighter-hljs
DB::table('users') ->whereExists(function ($query) { $query->select(DB::raw(1)) ->from('posts') ->whereRaw('posts.user_id = users.id'); }) ->get();

对应的 SQL 语句是:

Copy Highlighter-hljs
select * from `users` where exists (select 1 from `posts` where posts.user_id = users.id);

用于查询发布过文章的用户。

注: select 1 from table 与Select * from table在用法上大同小异
都是查看是否有记录,一般是作条件用的
select 1 from 中的1是一常量,查到的所有行的值都是它,但从效率上来说,1>*,因为不用查字典表。

子查询

有时候,我们会通过子查询关联不同的表进行查询,考虑下面这个 SQL 语句:

Copy Highlighter-hljs
select * from posts where user_id in (select id from users where email_verified_at is not null);

对于这条 SQL 语句,我们可以通过查询构建器提供的子查询来实现:

Copy Highlighter-hljs
$users = DB::table('users')->whereNotNull('email_verified_at')->select('id'); $posts = DB::table('posts')->whereInSub('user_id', $users)->get();

连接查询#

SQL 连接查询通常分为以下几种类型:

  • 内连接:使用比较运算符进行表间的比较,查询与连接条件匹配的数据,可细分为等值连接和不等连接

    • 等值连接(=):如select * from posts p inner join users u on p.user_id = u.id
    • 不等连接(<、>、<>等):如 select * from posts p inner join users u on p.user_id <> u.id
  • 外链接:

    • 左连接:返回左表中的所有行,如果左表中的行在右表中没有匹配行,则返回结果中右表中的对应列返回空值,如 select * from posts p left join users u on p.user_id = u.id
    • 右连接:与左连接相反,返回右表中的所有行,如果右表中的行在左表中没有匹配行,则结果中左表中的对应列返回空值,如 select * from posts p right join users u on p.user_id = u.id
    • 全连接:返回左表和右表中的所有行。当某行在另一表中没有匹配行,则另一表中的列返回空值,如 select * from posts p full join users u on p.user_id = u.id
  • 交叉连接:也称笛卡尔积,不带 where 条件子句,它将会返回被连接的两个表的笛卡尔积,返回结果的行数等于两个表行数的乘积,如果带 where,返回的是匹配的行数。如 select * from posts p cross join users u on p.user_id = u.id

内连接
首先我们来看内连接在查询构建器中如何实现,以等值连接为例:

Copy Highlighter-hljs
$posts = DB::table('posts') ->join('users', 'users.id', '=', 'posts.user_id') ->select('posts.*', 'users.name', 'users.email') ->get();

对照前面的等值连接示例 SQL,很容易理解这段代码,其对应的 SQL 语句是:

Copy Highlighter-hljs
select posts.*, users.name, users.email from posts inner join users on users.id = posts.user_id;

注:当两张表有字段名相同的字段,并且这两个字段都包含在 select 方法指定的字段中,需要为其中一个字段取别名,否则会产生冲突,例如,假设 posts 表中也包含 name 字段,那么需要为 users.name 取个别名:select('posts.*', 'users.name as username', 'users.email')。

左连接
左连接也可称作左外连接,在查询构建器中,可以通过 leftJoin 方法实现:

Copy Highlighter-hljs
$posts = DB::table('posts') ->leftJoin('users', 'users.id', '=', 'posts.user_id') ->select('posts.*', 'users.name', 'users.email') ->get();

对应的 SQL 语句是:

Copy Highlighter-hljs
select posts.*, users.name, users.email from posts left join users on users.id = posts.user_id;

右连接
右连接也可称作右外链接,在查询构建器中,可以通过 rightJoin 方法实现:

Copy Highlighter-hljs
$posts = DB::table('posts') ->rightJoin('users', 'users.id', '=', 'posts.user_id') ->select('posts.*', 'users.name', 'users.email') ->get();

对应的 SQL 语句如下:

Copy Highlighter-hljs
select posts.*, users.name, users.email from posts right join users on users.id = posts.user_id;

更加复杂的连接条件

有时候,你的连接查询条件可能比较复杂,比如下面这种:

Copy Highlighter-hljs
select posts.*, users.name, users.email from posts inner join users on users.id = posts.user_id and users.email_verified_at is not null where posts.views > 0;

这个时候,我们可以通过匿名函数来组装连接查询的条件来构建上面的查询语句:

Copy Highlighter-hljs
$posts = DB::table('posts') ->join('users', function ($join) { $join->on('users.id', '=', 'posts.user_id') ->whereNotNull('users.email_verified_at'); }) ->select('posts.*', 'users.name', 'users.email') ->where('posts.views', '>', 0) ->get();

我们可以在匿名函数的 $join 实例上调用所有 Where 查询子句,以组装我们需要的连接查询条件。上述查询会将对应用户邮箱未验证的,文章浏览数为 0 的所以结果过滤掉。

联合查询

查询构建器还支持通过 union 方法合并多个查询结果:

Copy Highlighter-hljs
$posts_a = DB::table('posts')->where('views', 0); $posts_b = DB::table('posts')->where('id', '<=', 10)->union($posts_a)->get();

通过上面这段代码,我们将 views = 0 和 id <= 10 这两个查询结果合并到了一起
对应的 SQL 语句是:

Copy Highlighter-hljs
(select * from `posts` where `id` <= 10) union (select * from `posts` where `views` = 0)

此外,查询构建器也支持 UNION ALL 查询,对应的方法是 unionAll,该方法与 union 的区别是允许重复记录,将上述代码中的 union 方法改为 unionAll,会发现查询结果中包含一条重复记录

排序

Copy Highlighter-hljs
$users = DB::table('posts') ->orderBy('created_at', 'desc') ->get();

查询构建器还支持通过 inRandomOrder 方法进行随机排序

Copy Highlighter-hljs
DB::table('posts')->inRandomOrder()->get();

分组

查询构建器还提供了 groupBy 方法用于对结果集进行分组:

Copy Highlighter-hljs
$posts = DB::table('posts') ->groupBy('user_id') ->selectRaw('user_id, sum(views) as total_views') ->get();

上述代码对应的 SQL 语句是:

Copy Highlighter-hljs
select user_id, sum(views) as total_views from `posts` group by `user_id`;

用于从 user_id 维度统计每个用户发布文章的总浏览数

如果我们想要进一步对分组结果进行过滤,可以使用 having 方法,比如,要从上述分组结果中过滤出总浏览数大于等于 10 的记录,可以这么做:

Copy Highlighter-hljs
$posts = DB::table('posts') ->groupBy('user_id') ->selectRaw('user_id, sum(views) as total_views') ->having('total_views', '>=', 10) ->get();

对应的 SQL 语句是:

Copy Highlighter-hljs
select user_id, sum(views) as total_views from `posts` group by `user_id` having `total_views` >= 10;

分页

日常开发中,另一个常见的查询场景就是分页查询了,在查询构建器中提供了两种方式来进行分页查询。

第一种是通过 skip 方法和 take 方法组合进行分页,skip 方法传入的参数表示从第几条记录开始,take 传入的参数表示一次获取多少条记录:

Copy Highlighter-hljs
$posts = DB::table('posts')->orderBy('created_at', 'desc') ->where('views', '>', 0) ->skip(10)->take(5) ->get();

对应的 SQL 语句是:

Copy Highlighter-hljs
select * from `posts` where `views` > 0 order by `created_at` desc limit 5 offset 10;

该查询会先按照查询条件和排序条件进行过滤和排序,然后从第10条记录开始获取5条记录返回。

另一种是通过 offset 方法和 limit 方法组合进行分页查询,offset 表示从第几条记录开始,limit 表示一次获取多少条记录,使用方式和 skip 和 take 类似:

Copy Highlighter-hljs
$posts = DB::table('posts')->orderBy('created_at', 'desc') ->where('views', '>', 0) ->offset(10)->limit(5) ->get();

对应的 SQL 语句如下:

Copy Highlighter-hljs
select * from `posts` where `views` > 0 order by `created_at` desc limit 5 offset 10;

底层最终执行的 SQL 语句完全一样,所以,随便你选择哪种方式都是可以的。

posted @   caibaotimes  阅读(906)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示
CONTENTS