当前位置: 首页 > news >正文

ABP VNext 与 Neo4j:构建基于图数据库的高效关系查询

ABP VNext 与 Neo4j:构建基于图数据库的高效关系查询 🚀

在社交网络、权限图谱、推荐系统等应用场景中,关系链深度和复杂度远超传统关系型数据库的表达能力。本文基于 ABP VNext 框架,集成 Neo4j 图数据库,构建一套高效、可复现、可维护的复杂关系查询系统,并实现推荐的性能与封装模式:GraphClientWrapper、NodeMapper、CypherBuilder。


预备知识 🛠

  • ABP CLI(abp)使用,以及 ABP VNext 项目结构。
  • Neo4jClient 库使用方式。
  • C# 反射与表达式树基础(若要深入了解 NodeMapper 与 CypherBuilder 的实现细节)。

📚 目录

  • ABP VNext 与 Neo4j:构建基于图数据库的高效关系查询 🚀
    • 预备知识 🛠
    • 一、项目环境配置
      • 1. 创建 ABP VNext 项目 🏗
      • 2. Docker 启动 Neo4j 🐳
      • 3. 配置文件(appsettings.json)示例 ⚙️
    • 二、配置文件与依赖注入
      • 1. 在 appsettings.json 中集中管理 Neo4j 连接 🔐
      • 2. Neo4jClient 初始化类(Neo4jInitializer) 🚀
      • 3. 在 Program.cs 中注册 Neo4jClient 🧩
      • 4. DI 初始化流程示意 🌀
    • 三、基础模型与 Repository 实现
      • 1. UserNode 节点模型 📦
      • 2. IUserGraphRepository 接口定义 🔍
      • 3. UserGraphRepository 实现 🎯
    • 四、索引与约束
      • 1. 执行时机与注意事项 ⏱
    • 五、性能与维护最佳实践
      • 1. 参数化查询与分页示例 📊
      • 2. 日志追踪与慢查询监控 📈
      • 3. 事务管理建议 🛡
    • 六、推荐封装模式
      • 1. GraphClientWrapper:连接复用与延迟事务 🧮
      • 2. NodeMapper:增强版类型映射(支持可空类型) ✨
      • 3. CypherBuilder:链式构建与参数化支持 🚧
    • 七、社交系统示例
      • 1. Neo4j CLI/Cypher 初始化脚本 📝
      • 2. 在代码中使用 Repository 查询示例 🔍


一、项目环境配置

1. 创建 ABP VNext 项目 🏗

# 使用 ABP CLI 创建一个 Web 应用模板
abp new AbpNeo4jDemo -t app

提示:执行前需安装 ABP CLI(dotnet tool install -g Volo.Abp.Cli),并确保版本与 ABP VNext 兼容,否则可能出现模板下载失败的问题。


2. Docker 启动 Neo4j 🐳

为了方便本地开发与测试,推荐使用 Docker 启动 Neo4j:

docker run \--name neo4j \-p 7474:7474 -p 7687:7687 \-v $PWD/neo4j/data:/data \-v $PWD/neo4j/logs:/logs \-e NEO4J_AUTH=neo4j/test123 \neo4j:5.11.0
  • 📌 neo4j:5.11.0:指定 Neo4j 版本,避免使用 latest 标签出现兼容性问题。
  • 📌 -v $PWD/neo4j/data:/data-v $PWD/neo4j/logs:/logs:将数据与日志目录映射到主机,实现数据持久化。
  • 🔑 NEO4J_AUTH=neo4j/test123:用户名 neo4j,密码 test123

注意:容器删除后,数据仍会保留在主机映射目录,避免丢失。


3. 配置文件(appsettings.json)示例 ⚙️

建议把 Neo4j 的连接信息放到 appsettings.json,避免硬编码:

{"ConnectionStrings": {"Default": "Server=localhost;Database=AbpNeo4jDemo;User Id=sa;Password=YourStrong!Passw0rd;"},"Neo4j": {"Uri": "bolt://localhost:7687","User": "neo4j","Password": "test123"},"Logging": {"LogLevel": {"Default": "Information","Neo4jClient": "Warning","Microsoft": "Warning"}}
}
  • 🔒 在生产环境可使用环境变量(例如 NEO4J__PASSWORD)来覆盖敏感信息,避免明文存储。

二、配置文件与依赖注入

1. 在 appsettings.json 中集中管理 Neo4j 连接 🔐

在项目中创建一个配置类 Neo4jOptions,用于映射配置段:

public class Neo4jOptions
{public const string SectionName = "Neo4j";public string Uri { get; set; }public string User { get; set; }public string Password { get; set; }
}

然后在 Program.cs 中绑定该配置段:

var builder = WebApplication.CreateBuilder(args);// 绑定 Neo4j 配置到 Neo4jOptions
builder.Services.Configure<Neo4jOptions>(builder.Configuration.GetSection(Neo4jOptions.SectionName)
);

2. Neo4jClient 初始化类(Neo4jInitializer) 🚀

为了避免在 DI 注册时直接 async/await,可封装一个初始化类,在参数配置准备好后再建立连接:

using Microsoft.Extensions.Options;
using Neo4jClient;public static class Neo4jInitializer
{/// <summary>/// 同步初始化 BoltGraphClient 并返回已连接的实例。/// </summary>public static IGraphClient Init(IOptions<Neo4jOptions> options){var cfg = options.Value;var client = new BoltGraphClient(cfg.Uri, cfg.User, cfg.Password);// 同步等待连接完成。若 Neo4j 未就绪会阻塞启动,建议确保先启动数据库。client.ConnectAsync().GetAwaiter().GetResult();return client;}
}

优化建议

  • 若担心启动阻塞,可改为“延迟连接”模式,或者在 IHostedService 中异步检测并重连,避免主线程长时间等待。

3. 在 Program.cs 中注册 Neo4jClient 🧩

Program.cs 中,将已连接的 IGraphClient 注册为 Singleton,并确保 ABP 框架初始化:

using Neo4jClient;
using Microsoft.Extensions.Options;var builder = WebApplication.CreateBuilder(args);// 1. 绑定 Neo4j 配置(前面已配置)
builder.Services.Configure<Neo4jOptions>(builder.Configuration.GetSection(Neo4jOptions.SectionName)
);// 2. 注册 Neo4jClient:先解析配置,再调用初始化方法
builder.Services.AddSingleton<IGraphClient>(sp =>
{var options = sp.GetRequiredService<IOptions<Neo4jOptions>>();return Neo4jInitializer.Init(options);
});// 3. 注册 Repository 层
builder.Services.AddScoped<IUserGraphRepository, UserGraphRepository>();// 4. 注册 GraphClientWrapper(延迟事务管理)
builder.Services.AddScoped<IGraphClientWrapper, GraphClientWrapper>();// 5. 注册 ABP 应用模块
builder.Services.AddApplication<AbpNeo4jDemoModule>();var app = builder.Build();// 🚀 必须在 UseRouting 之前调用
app.InitializeApplication();app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();app.UseEndpoints(endpoints => 
{endpoints.MapControllers();
});app.Run();

4. DI 初始化流程示意 🌀

应用启动 Program.cs
绑定配置 Configure
解析 IOptions
调用 Neo4jInitializer.Init(options)
创建 BoltGraphClient 实例
执行 ConnectAsync() 同步等待
连接成功,返回 IGraphClient
注册到 DI 容器 AddSingleton
后续 Repository/Wrapper 注入并使用 IGraphClient
ABP InitializeApplication 完成模块初始化

三、基础模型与 Repository 实现

1. UserNode 节点模型 📦

using Newtonsoft.Json;public class UserNode
{[JsonProperty("name")]public string Name { get; set; }[JsonProperty("email")]public string Email { get; set; }// 若后续需要存储创建时间、ID 等属性,可再扩展:// [JsonProperty("createdAt")]// public DateTime CreatedAt { get; set; }
}
  • ✨ 通过 [JsonProperty] 特性,确保 C# 属性与 Neo4j 节点属性精确映射。

2. IUserGraphRepository 接口定义 🔍

public interface IUserGraphRepository
{/// <summary>/// 获取指定用户名的一度好友列表(最多 limit 条)。/// </summary>Task<(List<UserNode> Friends, long TotalCount)> GetFriendsPagedAsync(string name, int skip, int limit);/// <summary>/// 创建用户及好友关系(事务演示)。/// </summary>Task CreateUserWithFriendAsync(string userName, string userEmail, string friendName, string friendEmail);
}
  • 🔄 将分页与事务示例合并到接口定义中,更贴近真实业务需求。

3. UserGraphRepository 实现 🎯

using Neo4jClient;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;public class UserGraphRepository : IUserGraphRepository
{private readonly ITransactionalGraphClient _txnClient;private readonly IGraphClient _client;private readonly ILogger<UserGraphRepository> _logger;public UserGraphRepository(IGraphClient client, ILogger<UserGraphRepository> logger){_client = client;_logger = logger;// 尝试转换为 ITransactionalGraphClient_txnClient = client as ITransactionalGraphClient;}/// <summary>/// 分页获取用户一度好友。/// </summary>public async Task<(List<UserNode> Friends, long TotalCount)> GetFriendsPagedAsync(string name, int skip, int limit){if (string.IsNullOrWhiteSpace(name) || skip < 0 || limit <= 0){_logger.LogWarning("GetFriendsPagedAsync 参数无效:name={Name}, skip={Skip}, limit={Limit}", name, skip, limit);return (new List<UserNode>(), 0);}try{// 1. 计算总数var countResult = await _client.Cypher.Match("(u:User)-[:FRIENDS_WITH]->(f:User)").Where((UserNode u) => u.Name == name).Return(f => f.Count()).ResultsAsync;var totalCount = countResult.SingleOrDefault();// 2. 分页查询,并按好友姓名排序var friends = await _client.Cypher.Match("(u:User)-[:FRIENDS_WITH]->(f:User)").Where((UserNode u) => u.Name == name).OrderBy("f.name").Skip(skip).Limit(limit).Return(f => f.As<UserNode>()).ResultsAsync;return (friends.ToList(), totalCount);}catch (Exception ex){_logger.LogError(ex, "分页查询用户 {Name} 好友失败", name);return (new List<UserNode>(), 0);}}/// <summary>/// 在一个事务中创建用户与好友关系示例。/// </summary>public async Task CreateUserWithFriendAsync(string userName, string userEmail, string friendName, string friendEmail){if (_txnClient == null){throw new InvalidOperationException("IGraphClient 未实现 ITransactionalGraphClient,无法使用事务");}// 唯一标识避免重复创建await _txnClient.Cypher.Merge("(u:User {name: $userName})").OnCreate().Set("u.email = $userEmail").WithParams(new { userName, userEmail }).ExecuteWithoutResultsAsync();await _txnClient.Cypher.Merge("(f:User {name: $friendName})").OnCreate().Set("f.email = $friendEmail").WithParams(new { friendName, friendEmail }).ExecuteWithoutResultsAsync();using var tx = await _txnClient.BeginTransactionAsync();try{await _txnClient.Cypher.Match("(u:User {name: $userName})", "(f:User {name: $friendName})").WithParams(new { userName, friendName }).Merge("(u)-[:FRIENDS_WITH {since: date()}]->(f)").ExecuteWithoutResultsAsync();await tx.CommitAsync();}catch (Exception ex){await tx.RollbackAsync();_logger.LogError(ex, "创建用户 {UserName} 与好友 {FriendName} 关系失败", userName, friendName);throw;}}
}
  • 🔑 核心:
    • 使用 MERGE 代替 CREATE,实现幂等创建节点与关系。
    • 在分页查询中加入 OrderBy("f.name"),保证分页稳定性。
    • 全程使用异步方法,避免同步等待(sync-over-async)带来的线程阻塞问题。

四、索引与约束

为了保证查询性能并避免重复节点,建议在 Neo4j 中提前创建索引与约束。使用 Neo4j 5.x+ 语法,保证幂等性:

// 创建 User 节点的 name 属性索引(仅在不存在时创建)
CREATE INDEX IF NOT EXISTS user_name_index FOR (u:User) ON (u.name);// 创建 User 节点的 email 属性唯一约束(仅在不存在时创建)
CREATE CONSTRAINT IF NOT EXISTS user_email_unique FOR (u:User) REQUIRE u.email IS UNIQUE;

1. 执行时机与注意事项 ⏱

  • 执行时机

    • 可在项目启动时通过 IHostedService 检测并执行,也可在 CI/CD 环境中统一执行初始化脚本。
    • 建议将以上 DDL 脚本保存到独立文件(如 init.cypher),并与应用部署流程绑定,保证环境一致。
  • 查询索引状态

    CALL db.indexes();
    
    • 可查看索引/约束的状态(ONLINE、POPULATING、FAILED 等),确保已就绪后再执行业务查询。

五、性能与维护最佳实践

1. 参数化查询与分页示例 📊

在实际业务中,需要根据不同场景做分页或多条件筛选。示例代码如上所示,已在 GetFriendsPagedAsync 中展示。

  • 关键点
    1. Where((UserNode u) => u.Name == name) 自动生成参数化 Cypher,避免注入风险。
    2. OrderBy("f.name") 保证分页结果稳定,有利于前端分页。
    3. Skip(skip).Limit(limit) 实现分页。

2. 日志追踪与慢查询监控 📈

对关键查询或写操作,建议记录耗时与结果量,便于定位性能瓶颈。示例:

public async Task<List<UserNode>> GetFriendsWithLoggingAsync(string name)
{var sw = System.Diagnostics.Stopwatch.StartNew();try{var result = await _client.Cypher.Match("(u:User)-[:FRIENDS_WITH]->(f:User)").Where((UserNode u) => u.Name == name).Return(f => f.As<UserNode>()).ResultsAsync;sw.Stop();_logger.LogInformation("查询用户 {Name} 好友 耗时 {Elapsed} ms,共 {Count} 条", name, sw.ElapsedMilliseconds, result.Count());return result.ToList();}catch (Exception ex){sw.Stop();_logger.LogError(ex, "查询用户 {Name} 好友 异常 耗时 {Elapsed} ms", name, sw.ElapsedMilliseconds);return new List<UserNode>();}
}
  • ⚠️ 可将“慢查询阈值”配置化(如 200 ms),若超过阈值触发告警或埋点。

3. 事务管理建议 🛡

在多步写操作时,务必使用显式事务,保证一致性与原子性。前文 CreateUserWithFriendAsync 方法中已演示。

  • 要点
    1. 使用 Merge() 而不是直接 Create(),保证幂等性。
    2. 在事务内顺序执行节点与关系创建,最后 CommitAsync()。若任何步骤失败,RollbackAsync()
    3. 避免在事务中执行长时间查询或阻塞操作,以减少锁冲突。

六、推荐封装模式

1. GraphClientWrapper:连接复用与延迟事务 🧮

using Neo4jClient;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;public interface IGraphClientWrapper : IDisposable
{IGraphClient Client { get; }ITransactionalGraphClient TransactionalClient { get; }Task BeginTransactionAsync();Task CommitAsync();Task RollbackAsync();
}public class GraphClientWrapper : IGraphClientWrapper
{private readonly IGraphClient _client;private readonly ILogger<GraphClientWrapper> _logger;private ITransaction _transaction;public IGraphClient Client => _client;public ITransactionalGraphClient TransactionalClient => _client as ITransactionalGraphClient;public GraphClientWrapper(IGraphClient client, ILogger<GraphClientWrapper> logger){_client = client;_logger = logger;}/// <summary>/// 延迟创建事务,直到真正需要写操作时调用。/// </summary>public async Task BeginTransactionAsync(){if (_transaction != null) return;if (_client is ITransactionalGraphClient txnClient){_transaction = await txnClient.BeginTransactionAsync();}else{throw new InvalidOperationException("IGraphClient 未实现 ITransactionalGraphClient,无法开启事务");}}public async Task CommitAsync(){if (_transaction == null) return;await _transaction.CommitAsync();await _transaction.DisposeAsync();_transaction = null;}public async Task RollbackAsync(){if (_transaction == null) return;await _transaction.RollbackAsync();await _transaction.DisposeAsync();_transaction = null;}public void Dispose(){if (_transaction != null){try{_transaction.RollbackAsync().GetAwaiter().GetResult();_transaction.DisposeAsync().GetAwaiter().GetResult();}catch (Exception ex){_logger.LogError(ex, "GraphClientWrapper Dispose 时回滚事务失败");}}}
}
  • 🔑 设计要点:
    1. 构造函数不直接开启事务,只有在调用 BeginTransactionAsync() 时才创建。
    2. 提供 CommitAsync() / RollbackAsync() 方法,业务调用方根据需要自行控制事务边界。
    3. Dispose() 中检查是否有未提交事务,若有则回滚,保证不会因忘记提交导致脏数据。

2. NodeMapper:增强版类型映射(支持可空类型) ✨

using System;
using System.Collections.Generic;
using System.Reflection;public static class NodeMapper<T> where T : class, new()
{/// <summary>/// 将 Neo4jClient 返回的属性字典映射到模型对象 T。/// 支持大小写忽略与 Nullable 类型转换。/// </summary>public static T Map(IDictionary<string, object> props){var obj = new T();var type = typeof(T);var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;foreach (var kv in props){try{var propInfo = type.GetProperty(kv.Key, flags);if (propInfo == null) continue;var targetType = propInfo.PropertyType;var value = kv.Value;if (value == null){propInfo.SetValue(obj, null);continue;}// 如果是 Nullable<T>,获取底层类型var underlying = Nullable.GetUnderlyingType(targetType);if (underlying != null){var nonNullVal = Convert.ChangeType(value, underlying);propInfo.SetValue(obj, nonNullVal);}else{var converted = Convert.ChangeType(value, targetType);propInfo.SetValue(obj, converted);}}catch{// 忽略映射失败的属性或记录日志continue;}}return obj;}
}
  • 🌟 优化点:
    • 忽略属性名称大小写差异。
    • 支持将数据库返回的数值转换为 Nullable 类型(如 int?DateTime?)。
    • 映射失败时不抛异常,以保证健壮性。

3. CypherBuilder:链式构建与参数化支持 🚧

using Neo4jClient;
using Neo4jClient.Cypher;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;public class CypherBuilder
{private readonly IGraphClient _client;private ICypherFluentQuery _query;public CypherBuilder(IGraphClient client){_client = client;Reset();}/// <summary>/// 重置内部查询,开始一个全新的 Cypher 构建/// </summary>private void Reset(){_query = _client.Cypher;}/// <summary>/// 添加 MATCH 子句,开始新的查询分支/// </summary>public CypherBuilder StartMatch(string pattern){Reset();_query = _query.Match(pattern);return this;}/// <summary>/// 在现有查询上追加 MATCH 子句(不重置)/// </summary>public CypherBuilder AppendMatch(string pattern){_query = _query.Match(pattern);return this;}/// <summary>/// 添加 WHERE 子句(支持参数化)/// 示例:Where("u.name = $name", new { name = "Alice" })/// </summary>public CypherBuilder Where(string condition, object parameters = null){if (parameters != null){_query = _query.Where(condition, parameters);}else{_query = _query.Where(condition);}return this;}/// <summary>/// 添加 ORDER BY 子句/// </summary>public CypherBuilder OrderBy(string orderClause){_query = _query.OrderBy(orderClause);return this;}/// <summary>/// 添加 SKIP 子句/// </summary>public CypherBuilder Skip(int skip){_query = _query.Skip(skip);return this;}/// <summary>/// 添加 LIMIT 子句/// </summary>public CypherBuilder Limit(int limit){_query = _query.Limit(limit);return this;}/// <summary>/// 执行 RETURN 子句,并将结果映射为 T。调用后自动 Reset()。/// 示例:ReturnAsync<UserNode>("f")/// </summary>public async Task<IEnumerable<T>> ReturnAsync<T>(string alias){var result = await _query.Return(alias).ResultsAsync;Reset(); // 每次调用后重置,避免状态残留return result.Cast<T>();}
}
  • ✅ 此版本支持:
    1. StartMatch + AppendMatch 分别用于“从头开始”或“在现有查询上追加”。
    2. 完整的链式方法:WhereOrderBySkipLimit,方便构建复杂查询。
    3. ReturnAsync<T> 调用后自动 Reset(),确保下一次查询从头开始。

七、社交系统示例

下面演示一个“社交系统”示例,包含 Neo4j 初始化脚本与代码调用。

1. Neo4j CLI/Cypher 初始化脚本 📝

// 1. 创建 User 节点及示例数据(使用 MERGE 保证幂等性)
MERGE (a:User {name: 'Alice'})ON CREATE SET a.email = 'alice@test.com';
MERGE (b:User {name: 'Bob'})ON CREATE SET b.email = 'bob@test.com';
MERGE (c:User {name: 'Carol'})ON CREATE SET c.email = 'carol@test.com';// 2. 建立好友关系(使用 MERGE 保证幂等性)
MERGE (a:User {name: 'Alice'}) 
MERGE (b:User {name: 'Bob'})
MERGE (a)-[:FRIENDS_WITH {since: date('2021-01-01')}]->(b);MERGE (a:User {name: 'Alice'})
MERGE (c:User {name: 'Carol'})
MERGE (a)-[:FRIENDS_WITH {since: date('2022-05-10')}]->(c);// 3. 创建索引与约束(使用 IF NOT EXISTS 保证幂等性)
CREATE INDEX IF NOT EXISTS user_name_index FOR (u:User) ON (u.name);
CREATE CONSTRAINT IF NOT EXISTS user_email_unique FOR (u:User) REQUIRE u.email IS UNIQUE;
  • 📌 将以上脚本保存至独立文件(如 init.cypher),并在 Neo4j Browser 或 CI/CD 环境执行,确保环境一致性。

2. 在代码中使用 Repository 查询示例 🔍

在某个服务或控制器中注入 IUserGraphRepository,调用分页查询方法获取好友列表:

using Microsoft.AspNetCore.Mvc;[ApiController]
[Route("api/[controller]")]
public class FriendsController : ControllerBase
{private readonly IUserGraphRepository _repo;private readonly ILogger<FriendsController> _logger;public FriendsController(IUserGraphRepository repo, ILogger<FriendsController> logger){_repo = repo;_logger = logger;}[HttpGet("{userName}")]public async Task<IActionResult> GetFriends(string userName, int skip = 0, int limit = 10){if (string.IsNullOrWhiteSpace(userName)){return BadRequest("用户名不能为空");}var (friends, totalCount) = await _repo.GetFriendsPagedAsync(userName, skip, limit);if (!friends.Any()){// 返回空数组而非 404,保持接口一致性return Ok(new { Total = totalCount, Data = Array.Empty<UserNode>() });}return Ok(new { Total = totalCount, Data = friends });}[HttpPost("create")]public async Task<IActionResult> CreateUserWithFriend([FromQuery] string userName, [FromQuery] string userEmail, [FromQuery] string friendName,[FromQuery] string friendEmail){if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(userEmail) || string.IsNullOrWhiteSpace(friendName) || string.IsNullOrWhiteSpace(friendEmail)){return BadRequest("参数缺失");}await _repo.CreateUserWithFriendAsync(userName, userEmail, friendName, friendEmail);return Ok("用户与好友关系创建成功");}
}
  • 🔄 在 /api/friends/{userName}?skip=0&limit=10 调用分页查询,返回示例:

    {"total": 2,"data": [{ "name": "Bob", "email": "bob@test.com" },{ "name": "Carol", "email": "carol@test.com" }]
    }
    
  • 💡 创建用户与好友关系示例:

    POST http://localhost:5000/api/friends/create?userName=Dave&userEmail=dave@test.com&friendName=Alice&friendEmail=alice@test.com
    

相关文章:

  • 第四讲:类和对象(下)
  • 一个WebRTC 分辨率动态爬升问题记录与解决过程
  • 第二十八章 RTC——实时时钟
  • 【Mini-F5265-OB开发板试用测评】显示RTC日历时钟
  • EasyRTC嵌入式音视频通信SDK助力物联网/视频物联网音视频打造全场景应用
  • 简约商务年终工作总结报告PPT模版分享
  • ingress-nginx 开启 Prometheus 监控 + Grafana 查看指标
  • 基于Selenium+Python的web自动化测试框架
  • 【Auto.js例程】华为备忘录导出到其他手机
  • leetcode sql50题
  • grafana-mcp-analyzer:基于 MCP 的轻量 AI 分析监控图表的运维神器!
  • PLSQLDeveloper配置OracleInstantClient连接Oracle数据库
  • F5 – TCP 连接管理:会话、池级和节点级操作
  • vue3:十五、管理员管理-页面搭建
  • MongoDB学习和应用(高效的非关系型数据库)
  • STM32实战: CAN总线数据记录仪设计方案
  • AndroidR车机TextToSpeech音频焦点异常问题分析
  • LLM Agent 如何颠覆股价预测的传统范式
  • 添加训练噪声以改进检测——去噪机制
  • C# WPF 左右布局实现学习笔记(1)
  • 胶州城乡建设局网站/b2b平台是什么意思
  • 大型商家进驻网站开发/今日新闻10条简短
  • 减肥药可以做网站吗/百度推广登录平台app
  • 网站搜索框代码怎么做/快速排名seo软件
  • 有全部公司的网站/结构优化设计
  • 一个网站做两级三级是什么意思/图片百度搜索