Doctrine ORM企业级实践:从数据访问层设计到性能优化全解析

news2026/5/14 3:18:13
1. 项目概述与核心价值最近在梳理一个老项目的技术债务发现其数据访问层DAL的代码写得相当混乱各种手写的SQL拼接、不一致的查询逻辑以及难以维护的关联关系处理让我头疼不已。这让我想起了多年前第一次接触Doctrine ORM时的惊艳感。fagemx/project-doctrine这个项目从命名上看很可能就是一个围绕Doctrine ORM对象关系映射进行深度定制、封装或提供最佳实践示例的PHP项目。对于任何使用Symfony、Laravel通过第三方包或纯PHP开发中大型应用的团队来说一个设计良好的数据访问层是架构的基石。Doctrine作为PHP生态中最强大、最成熟的ORM工具之一其正确使用方式直接关系到应用的性能、可维护性和开发效率。这个项目标题暗示的绝不仅仅是简单的Doctrine安装教程。它更可能是一个“项目级”的Doctrine实践样板涵盖了从实体Entity设计、仓储模式Repository封装、查询构建器QueryBuilder的最佳使用、复杂关联的处理、性能优化如延迟加载、分页、到事务管理、事件监听器Event Listeners以及数据库迁移Migrations等一整套解决方案。对于中级开发者它可能是一个快速上手的脚手架对于资深架构师它可能提供了一套值得借鉴的、应对复杂业务场景的领域模型与数据映射方案。接下来我将基于常见的企业级应用需求深度拆解在项目中系统化运用Doctrine的核心要点、实战技巧以及那些官方文档未必会明说的“坑”。2. 核心设计思路与架构选型2.1 领域模型与数据映射的权衡使用Doctrine的首要决策点是确定映射策略。Doctrine支持注解Annotations、XML、YAML等多种方式定义元数据。目前注解方式因其便捷性和代码内聚性已成为事实标准。在composer.json中引入doctrine/orm和doctrine/annotations后就可以在实体类中使用类似ORM\Entity的注解。但比技术选型更深层的是领域模型设计。Doctrine鼓励使用富领域模型Rich Domain Model即实体类不仅包含属性和映射关系还可以包含业务逻辑方法。这与贫血模型Anemic Domain Model仅有getter/setter形成对比。在project-doctrine这样的项目中我倾向于采用富模型。例如一个Order实体不应只是数据的容器它可以拥有place()、cancel()、addLineItem(Product $product, int $quantity)等方法这些方法内部封装了状态变更和业务规则校验。注意富领域模型需要警惕“上帝实体”问题。避免将过多的、不属于该实体核心职责的逻辑塞进去。关联实体的复杂业务逻辑应考虑转移到领域服务Domain Service中。2.2 仓储模式抽象数据访问的利器Doctrine为每个实体类自动生成一个基础的仓储Repository可以通过$entityManager-getRepository(Order::class)获取。然而直接使用这个基础仓储会使得业务层与Doctrine的API耦合过紧。一个更清晰的设计是定义自己的仓储接口。例如我们定义一个OrderRepositoryInterface声明findByUser(User $user)、findRecentOrders(int $days)等业务语义明确的方法。然后创建一个DoctrineOrderRepository类来实现这个接口内部使用Doctrine的EntityRepository或QueryBuilder来实现具体查询。这样业务层如应用服务只依赖于抽象的接口数据层实现的细节无论是Doctrine还是将来换用其他ORM都被隔离了。// 领域层接口 interface OrderRepositoryInterface { public function findByUser(User $user): array; public function save(Order $order): void; } // 基础设施层实现 use Doctrine\ORM\EntityManagerInterface; use App\Entity\Order; class DoctrineOrderRepository implements OrderRepositoryInterface { private EntityManagerInterface $entityManager; private ObjectRepository $repository; public function __construct(EntityManagerInterface $entityManager) { $this-entityManager $entityManager; $this-repository $entityManager-getRepository(Order::class); } public function findByUser(User $user): array { // 使用QueryBuilder构建类型安全、可复用的查询 return $this-repository-createQueryBuilder(o) -andWhere(o.user :user) -andWhere(o.status ! :cancelledStatus) -setParameter(user, $user) -setParameter(cancelledStatus, Order::STATUS_CANCELLED) -orderBy(o.createdAt, DESC) -getQuery() -getResult(); } public function save(Order $order): void { $this-entityManager-persist($order); // 通常不在仓储中直接flush由工作单元统一控制 } }这种模式是project-doctrine项目能体现架构价值的关键点之一。2.3 工作单元与事务边界管理Doctrine的EntityManager本质上实现了工作单元Unit of Work模式。它跟踪所有被管理实体的状态变化新建、更新、删除并在调用flush()时一次性同步到数据库。这引出了一个关键实践在应用层如控制器或应用服务控制事务边界而不是在仓储或实体内部。常见的做法是在应用服务方法开始时启动事务方法结束时提交异常时回滚。在Symfony中可以利用Transactional注解需要额外bundle或框架的事件监听器在纯PHP或Laravel中可以手动控制。class PlaceOrderService { private EntityManagerInterface $em; private OrderRepositoryInterface $orderRepo; public function __construct(EntityManagerInterface $em, OrderRepositoryInterface $orderRepo) { $this-em $em; $this-orderRepo $orderRepo; } public function execute(PlaceOrderCommand $command): void { // 开始事务在有些框架中可能是自动的 $this-em-beginTransaction(); try { // 1. 使用仓储获取领域对象 $order $this-orderRepo-find($command-orderId); // 2. 调用领域方法执行业务逻辑 $order-place($command-placedAt); // 3. 仓储保存仅persist $this-orderRepo-save($order); // 4. 发布领域事件如果需要 // $this-eventDispatcher-dispatch(new OrderPlaced($order)); // 提交事务flush所有变更 $this-em-flush(); $this-em-commit(); } catch (\Throwable $e) { $this-em-rollback(); throw $e; } } }实操心得避免在循环中频繁调用flush()。每次flush()都会触发Doctrine计算所有变更集并执行SQL性能开销大。应在业务逻辑单元完成后一次性flush()。3. 实体关系映射的深度解析与性能陷阱3.1 关联映射配置的精妙之处Doctrine的关联映射一对多、多对一、多对多、一对一功能强大但配置不当极易导致N1查询问题或数据不一致。以经典的Order订单和OrderLine订单行项的一对多关系为例。// App/Entity/Order.php /** * ORM\Entity */ class Order { // ... /** * ORM\OneToMany(targetEntityOrderLine::class, mappedByorder, cascade{persist, remove}) */ private Collection $lines; public function __construct() { $this-lines new ArrayCollection(); } public function addLine(OrderLine $line): void { if (!$this-lines-contains($line)) { $this-lines-add($line); $line-setOrder($this); // 维护双向关联的 owning side } } } // App/Entity/OrderLine.php class OrderLine { // ... /** * ORM\ManyToOne(targetEntityOrder::class, inversedBylines) * ORM\JoinColumn(nullablefalse, onDeleteCASCADE) */ private ?Order $order null; }关键点解析mappedBy与inversedBy在双向关系中必须指定哪一方是“拥有方”owning side。拥有方是存储外键的表对应的实体这里是OrderLine它使用inversedBy指向另一方的属性名。另一方Order使用mappedBy。所有关联的更新设置、移除都应在拥有方操作或通过辅助方法如上面的addLine同步维护。cascade操作cascade{persist}意味着当Order被persist时其关联的所有OrderLine也会自动被persist。这在聚合根Aggregate Root模式中非常有用。但需谨慎使用cascade{remove}它可能导致意外的级联删除。fetch模式这是性能的关键。默认是LAZY延迟加载即访问$order-getLines()时会触发额外的SQL查询来获取数据。如果明确知道需要立即使用关联数据应使用EAGER加载或在查询时使用JOIN主动抓取。3.2 解决N1查询问题这是Doctrine新手最常踩的坑。假设我们要列出10个订单及其所有行项。// 错误做法会导致1查询订单 10查询每个订单的行项 11次查询 $orders $entityManager-getRepository(Order::class)-findAll(); foreach ($orders as $order) { $lines $order-getLines(); // 每次循环都会触发一次查询 // ... 处理行项 }解决方案1在查询时使用JOIN FETCH这是最有效、最推荐的方法。它通过一个SQL查询将主实体和关联实体一次性加载。$query $entityManager-createQuery( SELECT o, l FROM App\Entity\Order o INNER JOIN o.lines l WHERE o.createdAt :date ); $query-setParameter(date, new \DateTime(-7 days)); $orders $query-getResult(); // 一次查询获取所有订单及其行项解决方案2在仓储方法中使用QueryBuilder的leftJoin与addSelect对于更复杂的动态查询使用QueryBuilder更灵活。public function findOrdersWithLines(?\DateTimeInterface $since null): array { $qb $this-createQueryBuilder(o); $qb-leftJoin(o.lines, l) -addSelect(l) // 关键将关联实体添加到SELECT子句 -orderBy(o.createdAt, DESC); if ($since) { $qb-andWhere(o.createdAt :since) -setParameter(since, $since); } return $qb-getQuery()-getResult(); }注意事项使用JOIN FETCH时如果关联是“一对多”可能导致结果集行数倍增笛卡尔积问题。Doctrine的Hydration机制会自动将重复的主实体去重但数据库传输的数据量会变大。对于多层嵌套或数据量大的情况需要评估性能。3.3 值对象Value Objects的嵌入领域驱动设计DDD中的值对象如Money、Address、DateRange是不可变的、没有标识符的对象。Doctrine通过Embeddable和Embedded注解支持值对象映射这能极大提升领域模型的表达力。// App/Domain/ValueObject/Money.php /** * ORM\Embeddable */ class Money { /** * ORM\Column(typedecimal, precision10, scale2) */ private string $amount; /** * ORM\Column(typestring, length3) */ private string $currency; public function __construct(string $amount, string $currency) { $this-amount $amount; $this-currency $currency; } // ... 不可变的getter和业务方法 } // App/Entity/OrderLine.php class OrderLine { // ... /** * ORM\Embedded(classApp\Domain\ValueObject\Money) */ private Money $price; }这样price在数据库中是price_amount和price_currency两列但在PHP对象模型中是一个完整的Money值对象可以封装货币转换、加减法等逻辑。4. 查询构建器与DQL的高级技巧4.1 构建可复用、类型安全的查询直接拼接DQL字符串容易出错且难以维护。QueryBuilder提供了流畅的接口。在project-doctrine中应建立一套查询规范。技巧1将复杂查询封装在仓储的独立方法中。技巧2使用参数化查询永远不要拼接用户输入。技巧3对于可选的过滤条件使用动态构建。class OrderRepository extends ServiceEntityRepository { public function createOrderListQueryBuilder( ?User $user null, ?string $status null, ?\DateTimeInterface $startDate null, ?\DateTimeInterface $endDate null ): QueryBuilder { $qb $this-createQueryBuilder(o) -orderBy(o.createdAt, DESC); if ($user) { $qb-andWhere(o.user :user) -setParameter(user, $user); } if ($status) { $qb-andWhere(o.status :status) -setParameter(status, $status); } if ($startDate) { $qb-andWhere(o.createdAt :startDate) -setParameter(startDate, $startDate); } if ($endDate) { $qb-andWhere(o.createdAt :endDate) -setParameter(endDate, $endDate); } return $qb; } public function findPaginated(QueryBuilder $qb, int $page 1, int $limit 20): Paginator { // 使用Doctrine的Paginator进行高效的分页避免内存溢出 $query $qb-getQuery() -setFirstResult(($page - 1) * $limit) -setMaxResults($limit); return new Paginator($query, true); // 第二个参数为true时会进行计数优化 } }4.2 处理分页与大数据集对于列表查询分页是必须的。不要使用getResult()获取全部数据再切片这会加载所有数据到内存。应使用setFirstResult()和setMaxResults()。但要注意在复杂JOIN查询中使用OFFSET分页在数据量极大时如几十万行之后性能会下降。对于深度分页可以考虑基于游标Cursor的分页即使用WHERE id :lastId ORDER BY id ASC的方式。4.3 原生SQL查询与结果映射当遇到极其复杂的报表查询或需要数据库特定函数优化时可能需要回退到原生SQL。Doctrine提供了ResultSetMapping或更简单的NativeQuery配合EntityManager::createNativeQuery()。但更推荐的方式是使用DBALDatabase Abstraction Layer的Connection直接执行SQL并将结果集手动水合hydrate为DTOData Transfer Object或数组而不是实体。这样可以避免Doctrine的元数据开销性能更高。class SalesReportRepository { private Connection $connection; public function __construct(Connection $connection) { $this-connection $connection; } public function getMonthlySales(int $year): array { $sql SELECT MONTH(o.created_at) as month, SUM(ol.quantity * ol.unit_price) as total_amount, COUNT(DISTINCT o.id) as order_count FROM orders o INNER JOIN order_lines ol ON o.id ol.order_id WHERE YEAR(o.created_at) :year GROUP BY MONTH(o.created_at) ORDER BY month ; $stmt $this-connection-prepare($sql); $result $stmt-executeQuery([year $year]); // 映射为标量数组或自定义的ReportDTO对象 return $result-fetchAllAssociative(); } }5. 性能优化与生产环境实战5.1 二级缓存策略Doctrine支持配置元数据缓存、查询缓存和结果缓存以减轻数据库压力。在生产环境中启用缓存至关重要。元数据缓存将实体映射信息缓存到APCu、Redis或文件系统中避免每次请求解析注解。查询缓存缓存DQL查询的SQL语句转换结果。结果缓存缓存查询结果集。需谨慎使用因为一旦底层数据变化缓存不会自动失效可能导致脏读。通常只用于很少变化的数据如国家列表、配置项。配置示例以Symfony为例# config/packages/doctrine.yaml doctrine: orm: metadata_cache_driver: type: pool pool: doctrine.system_cache_pool query_cache_driver: type: pool pool: doctrine.system_cache_pool result_cache_driver: type: pool pool: doctrine.result_cache_pool framework: cache: pools: doctrine.system_cache_pool: adapter: cache.app doctrine.result_cache_pool: adapter: cache.app5.2 批量处理与内存管理在命令行脚本或队列任务中处理大量数据时必须注意内存使用。常见的错误是循环读取大量实体而不清理。// 错误做法内存会持续增长直至耗尽 $users $entityManager-getRepository(User::class)-findAll(); foreach ($users as $user) { // ... 处理每个用户 // $entityManager-flush(); // 如果在这里flush每处理一个就执行一次SQL极慢 } // $entityManager-flush(); // 一次性flush但所有User实体都还在内存中 // 正确做法分批处理并定期清理 $batchSize 20; $i 0; $query $entityManager-createQuery(SELECT u FROM App\Entity\User u); $iterableResult $query-iterate(); foreach ($iterableResult as $row) { $user $row[0]; // ... 处理用户 if (($i % $batchSize) 0) { $entityManager-flush(); // 执行SQL $entityManager-clear(); // 分离所有实体释放内存这是关键。 // 注意clear()后之前处理的$user对象已分离不能再使用其关联。 } $i; } $entityManager-flush(); // 处理最后一批 $entityManager-clear();$entityManager-clear()会清空Unit of Work中的所有实体引用强制PHP垃圾回收器回收内存。处理关联实体时可能需要更精细的控制。5.3 数据库索引与查询计划分析Doctrine可以生成实体对应的数据库Schema但它不会自动为你创建最优索引。你需要根据实际查询模式手动在实体属性上定义索引或通过迁移文件添加。/** * ORM\Entity * ORM\Table(indexes{ORM\Index(columns{created_at, status})}) */ class Order { /** * ORM\Column(typedatetime, namecreated_at) */ private \DateTimeInterface $createdAt; /** * ORM\Column(typestring) */ private string $status; }对于慢查询一定要使用数据库的EXPLAIN命令或Doctrine的Doctrine\DBAL\Logging\DebugStack分析查询计划确保索引被正确使用。常见的陷阱包括在索引列上使用函数、不匹配的数据类型比较、OR条件导致索引失效等。6. 事件系统与扩展点Doctrine的事件系统非常强大允许你在实体生命周期的各个阶段如prePersist,postLoad,preUpdate,postRemove注入自定义逻辑。这是实现审计日志、自动更新“更新时间”字段、数据校验等横切关注点的理想位置。6.1 实现自动时间戳这是一个经典用例通过事件监听器自动设置createdAt和updatedAt。// App/EventListener/EntityTimestampListener.php use Doctrine\Persistence\Event\LifecycleEventArgs; use App\Entity\TimestampableInterface; // 自定义接口 class EntityTimestampListener { public function prePersist(LifecycleEventArgs $args): void { $entity $args-getObject(); if (!$entity instanceof TimestampableInterface) { return; } $now new \DateTimeImmutable(); if (null $entity-getCreatedAt()) { $entity-setCreatedAt($now); } $entity-setUpdatedAt($now); } public function preUpdate(LifecycleEventArgs $args): void { $entity $args-getObject(); if ($entity instanceof TimestampableInterface) { $entity-setUpdatedAt(new \DateTimeImmutable()); } } }然后在services.yaml中注册为Doctrine事件监听器。6.2 使用过滤器实现软删除软删除Soft Delete是一个常见需求即不真正从数据库删除记录而是标记一个deleted_at字段。Doctrine的过滤器Filter功能可以优雅地实现它。首先定义一个过滤器类// App/Filter/SoftDeleteFilter.php use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\Filter\SQLFilter; class SoftDeleteFilter extends SQLFilter { public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string { // 检查实体是否实现了SoftDeletableInterface接口 if (!$targetEntity-reflClass-implementsInterface(SoftDeletableInterface::class)) { return ; } return sprintf(%s.deleted_at IS NULL, $targetTableAlias); } }然后在配置中启用该过滤器。这样所有实现了SoftDeletableInterface的实体在执行SELECT查询时都会自动加上WHERE deleted_at IS NULL条件。要查询已删除的数据可以在特定查询中临时禁用过滤器$entityManager-getFilters()-disable(soft_delete)。7. 数据库迁移与版本控制使用Doctrine Migrations是管理数据库Schema变更的最佳实践。它允许你将数据库结构的变更像代码一样进行版本控制。工作流修改实体类如添加新字段、修改映射关系。使用命令行生成迁移文件php bin/console doctrine:migrations:diffSymfony或vendor/bin/doctrine-migrations migrations:diff。检查生成的迁移文件位于migrations/Version20230520120000.php确保SQL语句符合预期。有时需要手动调整例如重命名列时Doctrine可能会生成删除旧列并创建新列的语句导致数据丢失此时应手动修改为RENAME COLUMN。执行迁移php bin/console doctrine:migrations:migrate。重要提示永远不要在生产环境直接修改数据库Schema。必须通过迁移脚本进行。并且迁移脚本应该是幂等的idempotent或者至少在执行前要有完备的备份和回滚方案。对于重大变更如修改列类型、拆分表应制定分步迁移计划可能涉及数据迁移脚本、双写双读等策略以最小化停机时间和风险。8. 测试策略单元测试与集成测试一个健壮的project-doctrine必须包含测试。测试分为不同层次单元测试测试实体自身的业务逻辑、值对象计算等。由于不依赖数据库速度极快。可以使用PHPUnit模拟EntityManager或Repository。集成测试测试仓储层与真实数据库的交互。需要配置一个测试数据库如SQLite内存数据库。在每个测试用例前启动事务测试后回滚以保证测试隔离性。class DoctrineOrderRepositoryTest extends KernelTestCase { private EntityManagerInterface $em; private DoctrineOrderRepository $repository; protected function setUp(): void { $kernel self::bootKernel(); $this-em $kernel-getContainer()-get(doctrine)-getManager(); // 为测试准备干净的Schema可以加载Fixture $this-repository new DoctrineOrderRepository($this-em); $this-em-beginTransaction(); // 开始事务 } protected function tearDown(): void { $this-em-rollback(); // 回滚事务不污染数据库 parent::tearDown(); } public function testItFindsOrdersByUser(): void { // 1. 使用Fixtures或直接persist准备测试数据 $user new User(testexample.com); $this-em-persist($user); $order new Order($user); $this-em-persist($order); $this-em-flush(); $this-em-clear(); // 清除EntityManager模拟新请求 // 2. 执行被测方法 $foundOrders $this-repository-findByUser($user); // 3. 断言 $this-assertCount(1, $foundOrders); $this-assertEquals($order-getId(), $foundOrders[0]-getId()); } }功能测试在更高层面如API端点测试整个数据流确保从控制器到数据库的完整链路正确。9. 常见问题与排查实录问题1The association with X is not configured to cascade persist operations错误。原因你persist了一个新实体AA关联了一个新实体B例如新订单关联了新用户但B没有被persist且关联关系没有配置cascade{persist}。解决要么在persist(A)之前先persist(B)要么在A实体的关联映射注解上添加cascade{persist}要么通过$a-setB($b); $b-setA($a);确保双向关联都建立后只persist拥有方owning side。问题2更新实体后flush()不生效。原因最常见的原因是实体不在EntityManager的管理范围内处于“分离”状态。例如从缓存中反序列化了一个实体对象或者在一个请求中获取实体后关闭了EntityManager又在另一个请求中尝试更新它。解决使用$entityManager-merge($detachedEntity)将分离的实体重新纳入管理注意merge()在Doctrine 3.x中已废弃推荐使用$entityManager-find($class, $id)重新从数据库加载或显式地persist分离的实体。更好的做法是避免实体脱离管理上下文。问题3执行大量更新/插入时速度极慢。原因可能是每次循环都调用了flush()或者没有使用批量处理。解决参考第5.2节的批量处理模式定期flush()和clear()。另外对于纯数据导入可以考虑暂时禁用SQL日志$entityManager-getConnection()-getConfiguration()-setSQLLogger(null)和事件监听器以提升性能。问题4使用LEFT JOIN后分页Paginator计数不准。原因当LEFT JOIN导致主表行数膨胀时简单的COUNT(*)会出错。Doctrine的Paginator在构造时第二个参数传入true$fetchJoinCollection true时会使用更复杂但准确的计数查询。解决确保new Paginator($query, true)。但注意这可能会生成较慢的子查询。对于超复杂查询有时需要手动编写分页计数逻辑。问题5实体中使用了__construct()、__get()、__set()等魔术方法导致Doctrine代理Proxy对象无法正常工作。原因Doctrine为了支持延迟加载会生成实体的代理子类。如果实体类的方法不是public或存在特殊的构造逻辑代理类可能无法正确初始化。解决确保实体类遵循“Doctrine友好”的设计属性一般为private或protected通过公共的getter/setter访问构造函数最好是无参或所有参数都可选因为代理类需要无参构造避免使用可能干扰属性访问的魔术方法。构建一个成熟稳健的project-doctrine绝非一日之功它需要在清晰的架构指导下一砖一瓦地搭建。从领域模型设计、仓储抽象到关系映射、查询优化再到缓存、事件、迁移和测试每一个环节都考验着开发者对Doctrine乃至整个数据访问层设计的理解深度。我的经验是初期多花时间在模型设计和接口定义上中期严格遵循性能最佳实践后期则依靠完善的测试和监控来保证系统的稳定演进。当这套体系运转起来后你会发现数据层不再是业务的绊脚石而是支撑复杂业务快速、稳定迭代的坚实底盘。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…