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 初始化流程示意 🌀
三、基础模型与 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
中展示。
- 关键点:
Where((UserNode u) => u.Name == name)
自动生成参数化 Cypher,避免注入风险。OrderBy("f.name")
保证分页结果稳定,有利于前端分页。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
方法中已演示。
- 要点:
- 使用
Merge()
而不是直接Create()
,保证幂等性。 - 在事务内顺序执行节点与关系创建,最后
CommitAsync()
。若任何步骤失败,RollbackAsync()
。 - 避免在事务中执行长时间查询或阻塞操作,以减少锁冲突。
- 使用
六、推荐封装模式
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 时回滚事务失败");}}}
}
- 🔑 设计要点:
- 构造函数不直接开启事务,只有在调用
BeginTransactionAsync()
时才创建。 - 提供
CommitAsync()
/RollbackAsync()
方法,业务调用方根据需要自行控制事务边界。 - 在
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>();}
}
- ✅ 此版本支持:
StartMatch
+AppendMatch
分别用于“从头开始”或“在现有查询上追加”。- 完整的链式方法:
Where
、OrderBy
、Skip
、Limit
,方便构建复杂查询。 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
相关文章:
ABP VNext 与 Neo4j:构建基于图数据库的高效关系查询
ABP VNext 与 Neo4j:构建基于图数据库的高效关系查询 🚀 在社交网络、权限图谱、推荐系统等应用场景中,关系链深度和复杂度远超传统关系型数据库的表达能力。本文基于 ABP VNext 框架,集成 Neo4j 图数据库,构建一套高…...
数论——同余问题全家桶3 __int128和同余方程组
数论——同余问题全家桶3 __int128和同余方程组 快速读写和__int128快速读写__int128 中国剩余定理和线性同余方程组中国剩余定理(CRT)中国剩余定理OJ示例模板题曹冲养猪 - 洛谷模板题猜数字 - 洛谷 扩展中国剩余定理扩展中国剩余定理OJ示例模板题扩展中国剩余定理(…...
leetcode47.全排列II:HashSet层去重与used数组枝去重的双重保障
一、题目深度解析与重复排列问题 题目描述 给定一个可能包含重复数字的数组nums,返回其所有不重复的全排列。解集不能包含重复的排列,且排列可以按任意顺序返回。例如: 输入:nums [1,1,2]输出:[[1,1,2],[1,2,1],[2…...

5.Nginx+Tomcat负载均衡群集
Tomcat服务器应用场景:tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。一般来说,Tomcat虽然和Apache或…...

React项目的状态管理:Redux Toolkit
目录 1、搭建环境 2、Redux Toolkit 包含了什么 3、使用示例 (1)创建user切片 (2)合并切片得到store (3)配置store和使用store 使用js来编写代码,方便理解一些 1、搭建环境 首先…...

跨界破局者鲁力:用思辨与创新重塑汽车流通行业标杆
来源:投资家 在汽车流通行业深度变革的浪潮中,东莞东风南方汽车销售服务有限公司塘厦分公司总经理鲁力历经近二十年行业深耕,构建了一条从汽车销售顾问到区域运营掌舵者的进阶范本。作为东风日产体系内兼具理论建构与实战穿透力的标杆管理者…...
Druid连接池实现自定义数据库密码加解密功能详解
Druid连接池实现自定义数据库密码加解密功能详解 在企业级应用开发中,数据库密码的明文存储是一个显著的安全隐患。Druid作为阿里巴巴开源的高性能数据库连接池组件,提供了灵活的密码加密与解密功能,允许开发者通过自定义逻辑实现数据库密码…...

OS11.【Linux】vim文本编辑器
目录 1.四种模式 命令模式 几个命令 插入模式 底行模式 一图展示三种模式之间的关系 2.分屏(多文件操作) 3.配置vim的原理 4.脚本一键配置vim CentOS 7 x86_64 其他发行版 5.NeoVim(推荐) vim文本编辑器是一个多模式的编辑器,因此先介绍它的四种模式 附vim的官网:…...

基于SFC的windows系统损坏修复程序
前言 在平时使用Windows操作系统时会遇到很多因为系统文件损坏而出现的错误 例如:系统应用无法打开 系统窗口(例如开始菜单)无法使用 电脑蓝屏或者卡死 是如果想要修复很多人只能想到重装系统。但其实Windows有一个内置的系统文件检查器可以修复此类错误。 原理 SFC命令…...
强化学习基础概念图文版笔记
📘 强化学习基础概念图文版笔记 1️⃣ 基本框架:Agent 与 Environment 🧠 核心角色: Agent(智能体):做出决策的“大脑”,根据当前状态选择动作。Environment(环境&…...
k8s下离线搭建elasticsearch
前提 已经完成k8s安装 已经完成相关组件如helm的安装 下载es的chart包 如下地址 https://helm.elastic.co/helm/elasticsearch/elasticsearch-版本号.tgz 如6.8.10 https://helm.elastic.co/helm/elasticsearch/elasticsearch-6.8.10.tgz 修改配置 修改value.yaml文件…...

WAF绕过,网络层面后门分析,Windows/linux/数据库提权实验
一、WAF绕过文件上传漏洞 win7:10.0.0.168 思路:要想要绕过WAF,第一步是要根据上传的内容找出来被拦截的原因。对于文件上传有三个可以考虑的点:文件后缀名,文件内容,文件类型。 第二步是根据找出来的拦截原…...
Oracle杀进程注意事项
文章目录 一、哪些后台进程杀死会导致数据库重启二、杀死哪些后台进程会导致数据库关闭三、杀死哪些后台进程对数据库没有影响 一、哪些后台进程杀死会导致数据库重启 CKPT:检查点进程,checkpoint 检查点,检查点事件的责任是:标志…...

Vue 3 弹出式计算器组件(源码 + 教程)
🧮 Vue 3 弹出式计算器组件(源码 教程) 📌 建议收藏 点赞 关注,本组件支持加减乘除、双向绑定、计算过程展示,适用于表单辅助输入场景。 🔧 一、完整源码(复制即用) …...

监测预警系统重塑隧道安全新范式
在崇山峻岭的脉络间延伸的隧道,曾是交通安全的薄弱环节。智慧隧道监测预警系统的诞生,正在彻底改变这种被动防御格局,通过数字神经网络的构建,为地下交通动脉注入智能守护基因。 一、安全防控体系的质变升级 1.风险感知维度革命…...
solidity中sar和>>的区别
sar和>>都是右移操作,其区别简而言之前者保留符号位,后者不保留。要解释清楚这个问题,需要从有符号数和无符号数讲起: 有符号数和无符号数 打个比方int8和uint8 uint8(无符号 8 位整数) 取值范围:…...
ESP32与STM32
ESP32与STM32深度对比:物联网与嵌入式开发的王者之争 一、核心架构对比 1.1 ESP32 - 无线物联网霸主 // 典型双核架构配置 #include "freertos/FreeRTOS.h" #include "freertos/task.h"void app_main() {// 核心0执行无线通信任务xTaskCreat…...
vue在打包的时候能不能固定assets里的js和css文件名称
在 Vue 项目中(特别是使用 Vue CLI 构建的项目),打包时生成的 assets 目录下的 .js 和 .css 文件默认会带有哈希值(如 app.123abc.js),这是为了缓存优化。但你可以配置固定名称,方法如下&#x…...
用设计模式重新思考(类FSM)验证:从混乱到优雅
在数字设计的世界里,Finite-State Machine(FSM)就像一个城市的交通信号系统。每个状态都有自己的规则,每个转换都需要精确的条件。而对于验证工程师来说,如何优雅地验证这些状态机,一直是个让人头疼的问题。…...

技巧小结:外部总线访问FPGA寄存器
概述 需求:stm32的fsmc总线挂载fpga,stm32需要访问fpga内部寄存器 1、分散加载文件将变量存放到指定地址即FPGA寄存器地址 sct文件指定变量存储地址,从而可以直接访问外设,(28335也可以,不过用的是cmd文件…...
Qt客户端技巧 -- 窗口美化 -- 圆角窗口
不解析,直接给代码例子 利用窗口重绘事件处理函数paintEvent main.cpp #include <QtCore/qglobal.h> #if QT_VERSION > 0x050000 #include <QtWidgets/QApplication> #else #include <QtGui/QApplication> #endif#include "roundedwin…...
Go语言爬虫系列教程5:HTML解析技术以及第三方库选择
Go语言爬虫系列教程5:HTML解析技术以及第三方库选择 在上一章中,我们使用正则表达式提取网页内容,但这种方法有局限性。对于复杂的HTML结构,我们需要使用专门的HTML解析库。在这一章中,我们将介绍HTML解析技术以及如何…...
理解JavaScript中map和parseInt的陷阱:一个常见的面试题解析
前言 在JavaScript面试中,map和parseInt的组合常常被用作考察候选人对这两个方法理解深度的题目。让我们通过一个简单的例子来深入探讨其中的原理。 问题现象 [1, 2, 3].map(parseInt) // 输出结果是什么?很多人可能会预期输出[1, 2, 3],但…...
文件上传漏洞深度解析:检测与绕过技术矩阵
文件上传漏洞深度解析:检测与绕过技术矩阵 引言:无处不在的文件上传风险 在当今的Web应用生态系统中,文件上传功能几乎无处不在。从社交媒体分享图片到企业文档管理系统,用户上传文件已成为现代Web应用的核心功能之一。然而&…...
3.2 HarmonyOS NEXT跨设备任务调度与协同实战:算力分配、音视频协同与智能家居联动
HarmonyOS NEXT跨设备任务调度与协同实战:算力分配、音视频协同与智能家居联动 在万物互联的全场景时代,设备间的高效协同是释放分布式系统潜力的关键。HarmonyOS NEXT通过分布式任务调度技术,实现了跨设备算力动态分配与任务无缝流转&#…...
Elasticsearch 海量数据写入与高效文本检索实践指南
Elasticsearch 海量数据写入与高效文本检索实践指南 一、引言 在大数据时代,企业和组织面临着海量数据的存储与检索需求。Elasticsearch(以下简称 ES)作为一款基于 Lucene 的分布式搜索和分析引擎,凭借其高可扩展性、实时搜索和…...

jenkins集成gitlab发布到远程服务器
jenkins集成gitlab发布到远程服务器 前面我们讲了通过创建maven项目部署在jenkins本地服务器,这次实验我们将部署在远程服务器,再以nginx作为前端项目做一个小小的举例 1、部署nginx服务 [rootweb ~]# docker pull nginx [rootweb ~]# docker images …...
AI问答-vue3+ts+vite:http://www.abc.com:3022/m-abc-pc/#/snow 这样的项目 在服务器怎么部署
为什么记录有子路径项目的部署,因为,通过子路径可以区分项目,那么也就可以实现微前端架构,并且具有独特优势,每个项目都是绝对隔离的。 要将 Vue3 项目(如路径为 http://www.abc.com:3022/m-saas-pc/#/sno…...

当主观认知遇上机器逻辑:减少大模型工程化中的“主观性”模糊
一、人类与机器的认知差异 当自动驾驶汽车遇到紧急情况需要做出选择时,人类的决策往往充满矛盾:有人会优先保护儿童和老人,有人坚持"不主动变道"的操作原则。这种差异背后,体现着人类特有的情感判断与价值选择。而机器的…...

会计 - 金融负债和权益工具
一、金融负债和权益工具区分的基本原则 (1)是否存在无条件地避免交付现金或其他金融资产的合同义务 如果企业不能无条件地避免以交付现金或其他金融资产来履行一项合同义务,则该合同义务符合金融负债的义务。 常见的该类合同义务情形包括:- 不能无条件避免的赎回; -强制…...