AspNetCore IOC

前言

AspNetCore IOC基本天天都在用,但是有一些东西还是需要总结一下。

1.基础知识

官网:https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0

ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖关系之间实现[控制反转 (IoC)]的技术。

概念比较抽象,如果觉得不好理解,我认为多熟悉AspNetCore的IOC的API也是一种进步。

依赖包:

Microsoft.Extensions.DependencyInjection:实现包,实现IOC的基本功能
Microsoft.Extensions.DependencyInjection.Abstractions:抽象包,用于扩展容器

核心接口:

  • Service:服务实例
  • ServiceDescriptor:用于描述服务的信息。比如服务名(ServiceType)、实现类(ImplementationType)、生命周期(Lifetime)。
  • IServiceCollection:是一个List集合,用于保存服务描述信息。
  • IServiceProvider:用于解析服务实列。
  • ActivatorUtilities:用于解析一个容器中不存在,但是依赖了容器中的服务的实列。

2. 服务注册

创建测试类:


public interface IDbConnection { }

public class SqlDbConnection : IDbConnection { }

public class DbContext 
{
    public IDbConnection DBConnection { get; set; }
    public string ConnectionString { get; set; }
    public DbContext(IDbConnection _DBConnection,string _connectionString)
    {
        this.DBConnection = _DBConnection;
        this.ConnectionString = _connectionString;
    }
}

public interface ILogger<T, T1> { }

public class ConsoleLogger<T, T1> : ILogger<T, T1> { }

创建服务:

IServiceCollection services = new ServiceCollection();

万能公式:

// 单例模式
// 由于IServiceCollection实现了IList<ServiceDescriptor> 接口
// 因此下面是一个万能公式,其它的都是扩展方法,本质调用的还是这个万能公式,包括委托的方式(他的实现类型是一个委托)
services.Add(new ServiceDescriptor(typeof(IDbConnection), typeof(SqlDbConnection), ServiceLifetime.Singleton));

泛型接口:

services.AddSingleton<IDbConnection, SqlDbConnection>();

反射:

services.AddSingleton(typeof(IDbConnection), typeof(SqlDbConnection));

委托方式:

// 委托方式
services.AddSingleton(sp =>
{
    var connection = sp.GetRequiredService<IDbConnection>();
    return new DbContext(connection, "c1");
});

// 高级用法
services.AddSingleton(sp =>
{
    return ActivatorUtilities.CreateInstance<DbContext>(sp, "c1");
});

泛型注册:

// 注册泛型时,只能使用反射接口,并且泛型参数不要写入,解析时来确立,如果有多个泛型参数使用逗号隔开
services.AddSingleton(typeof(ILogger<,>), typeof(ConsoleLogger<,>));

2.服务解析

构建容器:

IServiceCollection services = new ServiceCollection();

IServiceProvider container = services.BuildServiceProvider(new ServiceProviderOptions
{
    ValidateOnBuild = true,//构建时检查是否有依赖没有注册的服务
    ValidateScopes = true,//在解析服务时检查是否通过根容器来解析Scoped类型的实列
});

同一个服务多个实现,默认获取最后一个

services.AddSingleton<IDbConnection, SqlDbConnection>();
services.AddSingleton<IDbConnection, MySqlConnection>();

如果服务未注册,返回null

IDbConnection? connection = container.GetService<IDbConnection>();

服务不存在讲引发异常

IDbConnection connection1 = container.GetRequiredService<IDbConnection>();

获取IDbConnection所有实现

IEnumerable<IDbConnection> connections = container.GetServices<IDbConnection>();

假设DbContext依赖IDbConnection,并且需要一个name,但是容器没有注册

DbContext
var context = ActivatorUtilities.CreateInstance<DbContext>(container, "c1");

3.生命周期

  • Transient:瞬时生命周期, Transient服务在每次被请求时都会被创建一个新的对象。这种生命周期比较适用于轻量级的无状态服务。
  • Scoped: Scoped生命周期的服务是每次web请求被创建,局部单例对象, 在某个局部内是同一个对象(作用域单例,本质是容器单例);一次请求内是一个单例对象,多次请求则多个不同的单例对象.
  • Singleton: Singleton生命能够周期服务在第一被请求时创建,在后续的每个请求都会使用同一个实例。如果你的应用需要单例服务,推荐的做法是交给服务容器来负责单例的创建和生命周期管理,而不是自己来走这些事情。

测试类:

public class IOCLife : IDisposable
{
    public string ID { get; }
    public IOCLife()
    {
        ID = Guid.NewGuid().ToString();
    }

    public void Dispose()
    {
        Console.WriteLine(ID + ":已释放...");
    }
}

3.1Scoped

a1、a2同一个对象,a3、a4同一个对象,当执行完using的代码,a3、a4生命周期结束

image-20231110233228355

var services = new ServiceCollection();
// 在某个局部内是同一个对象(作用域单例,本质是容器单例);
services.AddScoped<IOCLife>();
IServiceProvider container = services.BuildServiceProvider(new ServiceProviderOptions()
{
    ValidateOnBuild = true,//构建时检查是否有依赖没有注册的服务
    ValidateScopes = false,//在解析服务时检查是否通过根容器来解析Scoped类型的实列
});
//a1:通过根容器创建,需要设ValidateScopes为false(危险)
var a1 = container.GetRequiredService<IOCLife>();
var a2 = container.GetRequiredService<IOCLife>();
using (var scope = container.CreateScope())
{
    var a3 = scope.ServiceProvider.GetRequiredService<IOCLife>();
    var a4 = scope.ServiceProvider.GetRequiredService<IOCLife>();
    Console.WriteLine("scop0:" + a1.ID);
    Console.WriteLine("scop1:" + a2.ID);
    Console.WriteLine("scop2:" + a3.ID);
    Console.WriteLine("scop3:" + a4.ID);
}

3.2Singleton

创建完a1后,a2、a3、a4每次都返回已生成的对象

image-20231110233434682

var services = new ServiceCollection();
// 在某个局部内是同一个对象(作用域单例,本质是容器单例);
// 单例
services.AddSingleton<IOCLife>();

IServiceProvider container = services.BuildServiceProvider(new ServiceProviderOptions()
{
    ValidateOnBuild = true,//构建时检查是否有依赖没有注册的服务
    ValidateScopes = false,//在解析服务时检查是否通过根容器来解析Scoped类型的实列
});

//a1:通过根容器创建,需要设ValidateScopes为false(危险)
var a1 = container.GetRequiredService<IOCLife>();
var a2 = container.GetRequiredService<IOCLife>();
using (var scope = container.CreateScope())
{
    var a3 = scope.ServiceProvider.GetRequiredService<IOCLife>();
    var a4 = scope.ServiceProvider.GetRequiredService<IOCLife>();
    Console.WriteLine("scop0:" + a1.ID);
    Console.WriteLine("scop1:" + a2.ID);
    Console.WriteLine("scop2:" + a3.ID);
    Console.WriteLine("scop3:" + a4.ID);
}

3.3 Transient

a1、a2、a3、a4每次都是重新生成的对象,当执行完using的代码,a3、a4生命周期结束

image-20231110233650181


var services = new ServiceCollection();
// 瞬时生命周期
services.AddTransient<IOCLife>();
IServiceProvider container = services.BuildServiceProvider(new ServiceProviderOptions()
{
    ValidateOnBuild = true,//构建时检查是否有依赖没有注册的服务
    ValidateScopes = false,//在解析服务时检查是否通过根容器来解析Scoped类型的实列
});
//a1:通过根容器创建,需要设ValidateScopes为false(危险)
var a1 = container.GetRequiredService<IOCLife>();
var a2 = container.GetRequiredService<IOCLife>();
using (var scope = container.CreateScope())
{
    var a3 = scope.ServiceProvider.GetRequiredService<IOCLife>();
    var a4 = scope.ServiceProvider.GetRequiredService<IOCLife>();
    Console.WriteLine("scop0:" + a1.ID);
    Console.WriteLine("scop1:" + a2.ID);
    Console.WriteLine("scop2:" + a3.ID);
    Console.WriteLine("scop3:" + a4.ID);
}

4.组件扫描

IServiceCollection扩展类

namespace Microsoft.Extensions.DependencyInjection
{
    [AttributeUsage(AttributeTargets.Class)]
    public class InjectionAttribute : Attribute
    {
        public Type? ServiceType { get; set; }
        public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient;
    }
    public static class InjectionIServiceCollectionExtensions
    {
        public static IServiceCollection AddServicesByInjection<T>(this IServiceCollection services)
        {
            var serviceTypes = typeof(T).Assembly.GetTypes()
                .Where(a => a.IsClass)
                .Where(a => a.GetCustomAttribute<InjectionAttribute>() != null)//扫描注解
                .Where(a => !a.IsAbstract);
            foreach (var item in serviceTypes)
            {
                var injection = item.GetCustomAttribute<InjectionAttribute>();
                if (injection!.ServiceType == null)
                {
                    services.Add(new ServiceDescriptor(item, item, injection.Lifetime));
                }
                else
                {
                    services.Add(new ServiceDescriptor(injection!.ServiceType, item, injection.Lifetime));
                }
            }
            return services;
        }
    }
}

测试

var services = new ServiceCollection();
services.AddServicesByInjection<Program>();
var bsp = services.BuildServiceProvider();
var connection = bsp.GetServices<IDbConnection>();

image-20231110235604195

5.IOC构造模式

  • 构造器的目的和构造函数一样,但是构造器可以提供丰富的api来简化对象的构造
  • 构造模式用于简化被构造对象的创建,通过提供一大堆的api来丰富简化构造过程,增加调用者的体验。
  • 构造者需要提供一个Build方法用于构建和返回将要构造的对象实列。
  • 在容器中一般需要提供一个公开的IServiceCollection类型的属性,用于注册服务。
  • IServiceCollection是构造者模式

image-20231111004401083



using Microsoft.Extensions.DependencyInjection;

//List<ServiceDescriptor> list = new List<ServiceDescriptor>();
//list.Add(new ServiceDescriptor(typeof(SqlDbConnection), ServiceLifeTime.Scoped));
//list.Add(new ServiceDescriptor(typeof(MySqlSqlDbConnection), ServiceLifeTime.Scoped));
//var container = new Container(list);

//var containerBuilder = new ContainerBuilder();
//containerBuilder.AddScoped<SqlDbConnection>();
//containerBuilder.AddScoped<MySqlSqlDbConnection>();
//var container1 = containerBuilder.Build();

IServiceCollection containerBuilder = new ServiceCollection();
IServiceProvider sp = containerBuilder.BuildServiceProvider();


// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
Console.ReadKey();


public interface IDbConnection { }
public class SqlDbConnection { }
public class MySqlSqlDbConnection { }

public enum ServiceLifeTime
{
    Transient,
    Scoped
}


public class ServiceDescriptor
{

    public Type ServiceType { get; set; }
    public ServiceLifeTime ServiceLifeTime { get; set; }
    public ServiceDescriptor(
        Type serviceType,
        ServiceLifeTime lifetime
    )
    {
        ServiceType = serviceType;
        ServiceLifeTime = lifetime;
    }

}

// 目标对象
public interface IContainer { }

// 如果直接创建成本很高,体验很差
public class Container : IContainer
{

    public List<ServiceDescriptor> _services = new();
    public Container(List<ServiceDescriptor> services)
    {
        _services = services;
    }
}

// 目标对象的构造者
public interface IContainerBuilder
{
    // 接口只提供一个通用方法,降低实现成本
    void Add(ServiceDescriptor descriptor);
    // 构造目标对象
    IContainer Build();
}

// 实现构造者
public class ContainerBuilder : IContainerBuilder
{
    private List<ServiceDescriptor> _services = new();
    public void Add(ServiceDescriptor descriptor)
    {
        _services.Add(descriptor);
    }

    public IContainer Build()
    {
        return new Container(_services);
    }
}

// 扩展构造者,提供更加便捷的api
public static class IContainerBuilderExtensions
{
    public static void AddTransient<T>(this IContainerBuilder builder)
    {
        builder.Add(new ServiceDescriptor(typeof(T), ServiceLifeTime.Transient));
    }

    public static void AddScoped<T>(this IContainerBuilder builder)
    {
        builder.Add(new ServiceDescriptor(typeof(T), ServiceLifeTime.Scoped));
    }
}

6.IOC工厂模式

  • 工厂模式侧重于对象的管理(创建销毁),一般提供一个Create方法,支持命名创建。
  • 通过上面的学习我们发现IOC有一个弊端,就是他是通过服务类型的解析服务的。有些情况下我们需要通过命名的方式来解析服务。此时可以使用工厂模式。
  • IServiceProvider也是工厂模式
// See https://aka.ms/new-console-template for more information
using Microsoft.Extensions.DependencyInjection;
using System;

var services = new ServiceCollection();
services.AddScoped<MySqlDbConnection>();
services.AddScoped<SqlDbConnection>();
services.AddSingleton(sp =>
{
    var connections = new Dictionary<string, Type>
    {
        { "s1", typeof(SqlDbConnection) },
        { "s2", typeof(MySqlDbConnection) }
    };
    return new DbConnectionFactory(sp, connections);
});
var sp = services.BuildServiceProvider();
var factory = sp.GetRequiredService<DbConnectionFactory>();
var s1 = factory.Create(sp, "s1");
var s2 = factory.Create(sp, "s2");

Console.WriteLine("Hello, World!");
Console.ReadKey();


public interface IDbConnection
{

}
public class MySqlDbConnection : IDbConnection
{

}
public class SqlDbConnection : IDbConnection
{

}


//如果是一个重量级的工厂,建议注册成单实例
public class DbConnectionFactory
{
    private IServiceProvider _serviceProvider;
    private Dictionary<string, Type> _connections;

    public DbConnectionFactory(
        IServiceProvider serviceProvider,
        Dictionary<string, Type> connections
    )
    {
        _serviceProvider = serviceProvider;
        _connections = connections;   
    }

    public IDbConnection? Create(IServiceProvider serviceProvider, string name)
    {
        if (_connections.TryGetValue(name, out Type? connectionType))
        {
            return serviceProvider.GetRequiredService(connectionType) as IDbConnection;
        }
        return default;
    }
}

7.提供模式

  • 如果看到提供者模式,说明我们可以提供多个方案,支持多实现
  • 一般通过工厂来管理提供者,用以支持命名实列

image-20231111193752293

// See https://aka.ms/new-console-template for more information
using System.Diagnostics;

//List<ILoggerProvider> providers = new List<ILoggerProvider>() { 
//    new ConsoleLoggerProvider(),
//    new DebuggerLoggerProvider()
//};
//var factory = new LoggerFactory(providers);

var factory = new LoggerFactory();
factory.AddProvider(new ConsoleLoggerProvider());
factory.AddProvider(new DebuggerLoggerProvider());

//LoggerFactory loggerFactory = LoggerFactory.Create(new Action<LoggerFactoryBuilder>((loggerBuilder) =>
//{
//    loggerBuilder.Add(new ConsoleLoggerProvider());
//    loggerBuilder.Add(new DebuggerLoggerProvider());
//}));

var logger = factory.Create("03IO提供模式");
logger.Info("666888");

Console.WriteLine("Hello, World!");
Console.ReadKey();

public interface ILogger
{
    void Info(string message);
}

public interface ILoggerProvider
{
    ILogger CreateLogger(string name);
}

// 日志提供方案1
public class ConsoleLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string name)
    {
      return new ConsoleLogger(name);
    }
    class ConsoleLogger : ILogger
    {
        private string _name;
        public ConsoleLogger(string name)
        {
            _name = name;
        }
        public void Info(string message)
        {
            Console.WriteLine($"{_name}:{message}");
        }
    }
}

// 日志提供方案2
public class DebuggerLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string name)
    {
        return new DebuggerLogger(name);
    }
    class DebuggerLogger : ILogger
    {
        private string _name;
        public DebuggerLogger(string name)
        {
            _name = name;
        }
        public void Info(string message)
        {
            Debug.WriteLine($"{_name}{message}");
        }
    }
}

// 代理模式、工厂模式、构造模式、提供模式
public class LoggerFactory
{
    private List<ILoggerProvider> _providers = new();

    public LoggerFactory() { }
    public LoggerFactory(List<ILoggerProvider> providers)
    {
        _providers = providers;
    }

    public static LoggerFactory Create(Action<LoggerFactoryBuilder> configure)
    {
        var builder = new LoggerFactoryBuilder();
        configure(builder);
        return builder.Build();
    }

    public void AddProvider(ILoggerProvider provider)
    {
        _providers.Add(provider);
    }

    public ILogger Create(string name)
    {
        var loggers = _providers.Select(s => s.CreateLogger(name));
        return new LoggerCollection(loggers);
    }

    // 代理模式
    class LoggerCollection : ILogger
    {
        private IEnumerable<ILogger> _loggers;
        public LoggerCollection(IEnumerable<ILogger> loggers)
        {
            _loggers = loggers;
        }

        public void Info(string message)
        {
            foreach (var logger in _loggers)
            {
                logger.Info(message);
            }
        }
    }
}

public class LoggerFactoryBuilder
{
    private List<ILoggerProvider> _providers = new();

    public void Add(ILoggerProvider provider)
    {
        _providers.Add(provider);
    }

    public LoggerFactory Build()
    {
        return new LoggerFactory(_providers);
    }

}

8.代理模式

  • 代理模式侧重于对目标对象进行加强,通过实现目标对象的接口具备目标对象的能力。
  • 一般通过实现和目标对象相同的接口来获得目标对象的能力
  • 代理可以通过目标对象来简化实现成本,代理只负责编写加强逻辑
  • 一般代理器只代理单个目标对象,我们把下面这个模式也可以归纳到代理模式,因为它能满足代理的许多特点比如加强、拥有目标对象的能力

8.1IList

实现IList:

image-20231111204552882

修改add()方法逻辑

image-20231111204659943

只添加偶数,所以只有2

image-20231111204719487

8.2例子

代理模式不负责实现,侧重目标对象的加强


public interface ILogger
{
    void Info(string message);
}

class LoggerCollection : ILogger
{
    private IEnumerable<ILogger> _loggers;
    public LoggerCollection(IEnumerable<ILogger> loggers)
    {
        _loggers = loggers;
    }

    public void Info(string message)
    {
        foreach (var logger in _loggers)
        {
            logger.Info(message);
        }
    }
}

9.IOC容器实现

实现容器有三个重要的对象,通过IContainerBuilder来构建Container实列。Container负责根据ServiceDescriptor(服务描述)来找到服务实现,通过服务实现的依赖来进行注入。

  • ServiceDescriptor:负责描述服务信息
  • IContainerBuilder:负责构建容器
  • IContainer:负责根据服务描述信息解析服务

image-20231111221029765

// See https://aka.ms/new-console-template for more information




IContainerBuilder builder = new ContainerBuilder();
//builder.Add(b => new DbConnection());
//builder.Add<DbConnection>();
builder.Add(b => new DbConnection());
builder.Add<DbContext>();
var container = builder.Build();
//var context = container.GetService(typeof(DbContext));
var context = container.GetService<DbContext>();

Console.WriteLine("Hello, World!");
Console.ReadKey();

// 服务描述信息类
public class ServiceDescriptor
{
    public Type ServiceType { get; }
    public Type ImplementionType { get; }
    public Object? Instance { get; }

    public ServiceDescriptor(
        Type serviceType,
        Type implementionType,
        object? instance = null)
    {
        ServiceType = serviceType;
        ImplementionType = implementionType;
        Instance = instance;
    }
}

// 容器实现接口
public interface IContainer
{
    object? GetService(Type serviceType);
    T GetService<T>();
}

// 容器生成接口
public interface IContainerBuilder
{
    void Add(ServiceDescriptor descriptor);
    IContainer Build();
}

// 容器实现类
public class Container : IContainer
{
    private IEnumerable<ServiceDescriptor> _services;
    public Container(IEnumerable<ServiceDescriptor> services)
    {
        _services = services;
    }
    public object? GetService(Type serviceType)
    {
        var descriptor = _services.FirstOrDefault(s => s.ServiceType == serviceType);
        if (descriptor == null)
        {
            throw new InvalidOperationException("服务未注册");
        }
        // 判断是否是委托(协变)
        var invokerType = typeof(Func<IContainer, object>);
        if (descriptor.Instance != null &&
            typeof(Func<IContainer, object>).IsInstanceOfType(descriptor.Instance))
        {
            var func = descriptor.Instance as Func<IContainer, object> ?? throw new ArgumentNullException();
            return func(this);
        }
        // 获取参数最多的构造函数
        var constructor = serviceType.GetConstructors().OrderByDescending(s => s.GetParameters().Length).FirstOrDefault() ?? throw new ArgumentNullException();
        // 递归解析依赖
        // IEnumerable<Object> 只有在ToArray()时执行递归
        var parameters = constructor.GetParameters().Select(s => GetService(s.ParameterType));
        // 反射
        return Activator.CreateInstance(descriptor.ImplementionType, parameters.ToArray());
    }

    public T GetService<T>()
    {
        return (T)GetService(typeof(T));
    }
}
// 容器生成类
public class ContainerBuilder : IContainerBuilder
{
    private List<ServiceDescriptor> _services = new();
    public void Add(ServiceDescriptor descriptor)
    {
        _services.Add(descriptor);
    }

    public IContainer Build()
    {
        return new Container(_services);
    }
}

// 容器生成类扩展方法
public static class IContainerBuilderExtensions
{
    public static void Add<TService>(this IContainerBuilder builder)
    {
        builder.Add(new ServiceDescriptor(typeof(TService), typeof(TService)));
    }
    public static void Add<TService, TImplement>(this IContainerBuilder builder)
    {
        builder.Add(new ServiceDescriptor(typeof(TService), typeof(TImplement)));
    }
    public static void Add<TService>(this IContainerBuilder builder, Func<IContainer, TService> func)
    {
        builder.Add(new ServiceDescriptor(typeof(TService), typeof(Func<IContainer, TService>), func));
    }
}

public class DbConnection { }

public class DbContext
{
    public DbConnection DbConnection { get; }
    public DbContext(DbConnection dbConnection)
    {
        DbConnection = dbConnection;
    }
}

10.协变逆变

10.1协变

子类继承父类,所以父类可以等于子类

但是泛型不可以

image-20231111222309367

协变

  • 我们说泛型是不完整的,当指定泛型参数时,才使得其完整。
  • 在接口或者委托上,在泛型参数上使用out关键字,使得完整泛型,完整的泛型满足泛型参数的多态

image-20231111222421027

IEnumerable:

public interface IEnumerable<out T> : IEnumerable
{    
    IEnumerator<T> GetEnumerator();
}

image-20231111222737900

Func:

委托

image-20231111223017784

image-20231111223029560

XieBian<ParentClass> parentClass = null;
XieBian<ChildClass> childClass = null;
parentClass = childClass;

ParentClass parentClass1 = null;
ChildClass childClass1 = null;
parentClass1 = childClass1;

IEnumerable<string> strs = null;
IEnumerable<object> objs = null;
objs = strs;

Func<ParentClass> parentClass2 = null;
Func<ChildClass> childClass2 = null;
parentClass2 = childClass2;

NiBian<ParentClass> parentClass3 = null;
NiBian<ChildClass> childClass3 = null;
childClass3 = parentClass3;

Console.WriteLine("Hello, World!");
Console.ReadKey();

public class ParentClass { }
public class ChildClass : ParentClass { }
public interface NiBian<in T> { }
public interface XieBian<out T> { 

10.2逆变

和协变相反,子类等于父类,关键字in

image-20231111223408719

11.Autofac

安装包

Autofac.Extensions.DependencyInjection

image-20231111224151297

var service = new ServiceCollection();
// 微软容器注册服务
service.AddScoped(typeof(ILogger<>), typeof(Logger<>));
// 创建Container容器
var builder = new ContainerBuilder();
// autofac容器注册服务
builder.RegisterType<CService>().PropertiesAutowired().As<CService>().InstancePerLifetimeScope();
// 将IServiceCollection中的服务注册到Autofac
builder.Populate(service);
// 使用AutofacServiceProvider的实现方案,创建容器
// 加载autofac中的服务
IServiceProvider container = new AutofacServiceProvider(builder.Build());
var logger = container.GetRequiredService<ILogger<Program>>();
var cservice = container.GetRequiredService<CService>();
posted @   peng_boke  阅读(157)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示