From 35751b4f008b23aa7b49fe01fa3225df6442be43 Mon Sep 17 00:00:00 2001
From: jzf
Date: Wed, 11 Nov 2026 18:30:31 +0800
Subject: [PATCH] 1111
---
dev/.env.example | 10 +-
dev/RuoYi-Vue-master/pom.xml | 8 +
dev/RuoYi-Vue-master/ruoyi-admin/pom.xml | 6 +
.../ops/controller/AptRouteController.java | 52 --
.../src/main/resources/application.yml | 37 +-
dev/RuoYi-Vue-master/ruoyi-apt/pom.xml | 64 ++
.../java/com/ruoyi/apt/common/AptContext.java | 0
.../com/ruoyi/apt/common/AptErrorCode.java | 26 +
.../com/ruoyi/apt/common/AptException.java | 0
.../ruoyi/apt/common/AptPrefixResolver.java | 0
.../com/ruoyi/apt/common/audit/AptAudit.java | 0
.../ruoyi/apt/common/page/AptPageSupport.java | 0
.../apt/common/page/AptTableDataInfo.java | 18 +-
.../service/AptDispatchScanServiceImpl.java | 18 +-
.../apt/framework/audit/AptAuditAspect.java | 12 +-
.../apt/framework/audit/AptAuditWriter.java | 33 +
.../config/AptMasterClientMockGuard.java | 44 +
.../apt/framework/config/AptProperties.java | 560 +++++++++++++
.../security/AptAccessDeniedHandler.java | 0
.../security/AptAuthenticationEntryPoint.java | 0
.../apt/framework/web/AptBaseController.java | 0
.../framework/web/AptContextInterceptor.java | 26 +-
.../web/AptInfraExceptionHandler.java | 29 +
.../apt/framework/web/AptWebMvcConfig.java | 0
.../connectivity/AptConnectivityService.java | 0
.../apt/infra/connectivity/ProbeResult.java | 0
.../AptMasterVersionController.java | 67 ++
.../master/controller/AptRouteController.java | 153 ++++
.../apt/master/domain/AptMasterVersion.java | 51 ++
.../com/ruoyi/apt/master/domain/AptRoute.java | 69 ++
.../apt/master/domain/AptRouteCommand.java | 69 ++
.../apt/master/domain/AptRouteRecalc.java | 37 +
.../master/domain/AptRouteStatusCommand.java | 20 +
.../master/mapper/AptMasterVersionMapper.java | 21 +
.../apt/master/mapper/AptRouteMapper.java | 40 +
.../service/AptMasterVersionServiceImpl.java | 96 +++
.../master/service/AptRouteServiceImpl.java | 279 +++++++
.../service/IAptMasterVersionService.java | 21 +
.../apt/master/service/IAptRouteService.java | 32 +
.../controller/AptConnectivityController.java | 0
.../ruoyi/apt/ops/domain/AptOperationLog.java | 0
.../ruoyi/apt/ops/mapper/AptAuditMapper.java | 0
.../stay/controller/AptRoomController.java | 154 ++++
.../com/ruoyi/apt/stay/domain/AptRoom.java | 74 ++
.../ruoyi/apt/stay/domain/AptRoomCommand.java | 40 +
.../ruoyi/apt/stay/domain/AptRoomDetail.java | 17 +
.../apt/stay/domain/AptRoomStatusCommand.java | 28 +
.../apt/stay/domain/AptRoomStatusResult.java | 16 +
.../apt/stay/domain/AptRoomStatusSummary.java | 25 +
.../ruoyi/apt/stay/mapper/AptRoomMapper.java | 64 ++
.../apt/stay/service/AptRoomServiceImpl.java | 334 ++++++++
.../apt/stay/service/IAptRoomService.java | 30 +
.../com/ruoyi/apt/voice/ami/AmiAction.java | 118 +++
.../com/ruoyi/apt/voice/ami/AmiClient.java | 409 +++++++++
.../ruoyi/apt/voice/ami/AmiClientFactory.java | 25 +
.../apt/voice/ami/AmiConnectionManager.java | 363 ++++++++
.../ruoyi/apt/voice/ami/AmiEventListener.java | 23 +
.../com/ruoyi/apt/voice/ami/AmiException.java | 58 ++
.../com/ruoyi/apt/voice/ami/AmiFrame.java | 96 +++
.../com/ruoyi/apt/voice/ami/AmiProtocol.java | 75 ++
.../apt/voice/domain/AptAmiInstance.java | 290 +++++++
.../apt/voice/mapper/AptVoiceMapper.java | 91 ++
.../voice/service/AptAmiEventDispatcher.java | 109 +++
.../apt/voice/service/AptAmiEventHandler.java | 273 ++++++
.../voice/service/AptAmiInstanceService.java | 118 +++
.../voice/service/AptCallFlowServiceImpl.java | 330 ++++++++
.../voice/service/IAptCallFlowService.java | 26 +
.../src/main/resources/apt-defaults.yml | 86 ++
.../resources/mapper/apt/AptAuditMapper.xml | 0
.../mapper/apt/AptMasterVersionMapper.xml | 37 +
.../resources/mapper/apt/AptRoomMapper.xml | 173 ++++
.../resources/mapper/apt/AptRouteMapper.xml | 101 +++
.../resources/mapper/apt/AptVoiceMapper.xml | 208 +++++
.../src/main/resources/static/apt/.gitkeep | 0
.../com/ruoyi/apt/common/AptContextTest.java | 0
.../ruoyi/apt/common/AptPageSupportTest.java | 0
.../apt/common/AptPrefixResolverTest.java | 0
.../framework/AptAccessDeniedHandlerTest.java | 0
.../apt/framework/AptAuditAspectTest.java | 13 +-
.../apt/framework/AptAuthInterceptorTest.java | 4 +
.../apt/framework/AptErrorResponseTest.java | 0
.../AptInfraExceptionHandlerTest.java | 0
.../apt/framework/AptIt2SeedMarkersTest.java | 94 +++
.../apt/framework/AptMapperGyidArchTest.java | 30 +-
.../AptMasterClientMockGuardTest.java | 45 +
.../master/AptMasterVersionServiceTest.java | 121 +++
.../ruoyi/apt/master/AptRouteServiceTest.java | 152 ++++
.../ops/AptConnectivityControllerTest.java | 0
.../ruoyi/apt/stay/AptRoomServiceTest.java | 280 +++++++
.../com/ruoyi/apt/voice/AmiClientTest.java | 133 +++
.../com/ruoyi/apt/voice/AmiProtocolTest.java | 84 ++
.../com/ruoyi/apt/voice/FakeAmiServer.java | 175 ++++
.../apt/framework/config/AptProperties.java | 126 ---
.../framework/config/SecurityConfig.java | 19 +-
.../com/ruoyi/apt/master/domain/AptRoute.java | 42 -
.../apt/master/mapper/AptRouteMapper.java | 14 -
.../resources/mapper/apt/AptRouteMapper.xml | 27 -
dev/RuoYi-Vue2-master/src/api/apt/checkin.js | 59 ++
dev/RuoYi-Vue2-master/src/api/apt/face.js | 43 +
dev/RuoYi-Vue2-master/src/api/apt/jcxx.js | 60 ++
dev/RuoYi-Vue2-master/src/api/apt/master.js | 28 +
dev/RuoYi-Vue2-master/src/api/apt/ops.js | 30 +
dev/RuoYi-Vue2-master/src/api/apt/room.js | 48 ++
dev/RuoYi-Vue2-master/src/api/apt/route.js | 65 ++
.../components/AptDisabledAction/index.vue | 32 +
dev/RuoYi-Vue2-master/src/router/index.js | 28 +
dev/RuoYi-Vue2-master/src/utils/apt.js | 340 ++++++++
dev/RuoYi-Vue2-master/src/utils/request.js | 14 +-
.../src/views/apt/checkin/index.vue | 778 ++++++++++++++++++
.../src/views/apt/face/reg/index.vue | 601 ++++++++++++++
.../src/views/apt/jcxx/index.vue | 550 +++++++++++++
.../src/views/apt/master/version/index.vue | 304 +++++++
.../src/views/apt/ops/connectivity/index.vue | 269 ++++++
.../src/views/apt/room/detail.vue | 169 ++++
.../src/views/apt/room/index.vue | 562 +++++++++++++
.../src/views/apt/route/detail.vue | 110 +++
.../src/views/apt/route/index.vue | 626 ++++++++++++++
dev/scripts/apply-it2-sql.ps1 | 109 +++
dev/sql/apt_rev01/10_it2_columns.sql | 55 ++
dev/sql/apt_rev01/11_it2_seed.sql | 109 +++
dev/sql/apt_rev01/12_it2_permissions.sql | 58 ++
dev/sql/apt_rev01/13_it2_frontend_menus.sql | 75 ++
dev/sql/apt_rev01/14_it3_columns.sql | 111 +++
dev/sql/apt_rev01/15_it3_seed.sql | 56 ++
dev/sql/apt_rev01/16_it3_permissions.sql | 89 ++
dev/sql/apt_rev01/17_it4_columns.sql | 178 ++++
dev/sql/apt_rev01/18_it4_permissions.sql | 80 ++
dev/sql/apt_rev01/19_it5_columns.sql | 114 +++
dev/sql/apt_rev01/20_it5_permissions.sql | 97 +++
dev/sql/apt_rev01/21_ami_instance.sql | 156 ++++
dev/sql/apt_rev01/99_verify.sql | 149 ++++
docs/README.md | 34 +
docs/公寓端/02-技术基线与工程结构.md | 79 ++
docs/公寓端/03-数据设计.md | 9 +-
docs/公寓端/05-关键流程.md | 4 +-
docs/公寓端/11-人员入住作业流程.md | 6 +-
docs/公寓端/13-公寓端小步迭代开发计划.md | 5 +
docs/公寓端/14-第一轮开发(IT-0)交付说明.md | 6 +-
docs/公寓端/15-第二轮开发(IT-1)交付说明.md | 6 +
docs/公寓端/16-第三轮开发(IT-2)交付说明.md | 545 ++++++++++++
docs/公寓端/17-第四轮开发(IT-4)交付说明.md | 128 +++
docs/公寓端/18-第五轮开发(IT-5)交付说明.md | 217 +++++
docs/公寓端/README.md | 4 +-
docs/设计变更/03-三端功能设计修订(CR-02).md | 4 +
144 files changed, 13905 insertions(+), 342 deletions(-)
delete mode 100644 dev/RuoYi-Vue-master/ruoyi-admin/src/main/java/com/ruoyi/apt/ops/controller/AptRouteController.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/pom.xml
rename dev/RuoYi-Vue-master/{ruoyi-common => ruoyi-apt}/src/main/java/com/ruoyi/apt/common/AptContext.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-common => ruoyi-apt}/src/main/java/com/ruoyi/apt/common/AptErrorCode.java (57%)
rename dev/RuoYi-Vue-master/{ruoyi-common => ruoyi-apt}/src/main/java/com/ruoyi/apt/common/AptException.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-common => ruoyi-apt}/src/main/java/com/ruoyi/apt/common/AptPrefixResolver.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-common => ruoyi-apt}/src/main/java/com/ruoyi/apt/common/audit/AptAudit.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-common => ruoyi-apt}/src/main/java/com/ruoyi/apt/common/page/AptPageSupport.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-common => ruoyi-apt}/src/main/java/com/ruoyi/apt/common/page/AptTableDataInfo.java (54%)
rename dev/RuoYi-Vue-master/{ruoyi-framework => ruoyi-apt}/src/main/java/com/ruoyi/apt/framework/audit/AptAuditAspect.java (93%)
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/audit/AptAuditWriter.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/config/AptMasterClientMockGuard.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/config/AptProperties.java
rename dev/RuoYi-Vue-master/{ruoyi-framework => ruoyi-apt}/src/main/java/com/ruoyi/apt/framework/security/AptAccessDeniedHandler.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-framework => ruoyi-apt}/src/main/java/com/ruoyi/apt/framework/security/AptAuthenticationEntryPoint.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-framework => ruoyi-apt}/src/main/java/com/ruoyi/apt/framework/web/AptBaseController.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-framework => ruoyi-apt}/src/main/java/com/ruoyi/apt/framework/web/AptContextInterceptor.java (85%)
rename dev/RuoYi-Vue-master/{ruoyi-framework => ruoyi-apt}/src/main/java/com/ruoyi/apt/framework/web/AptInfraExceptionHandler.java (86%)
rename dev/RuoYi-Vue-master/{ruoyi-framework => ruoyi-apt}/src/main/java/com/ruoyi/apt/framework/web/AptWebMvcConfig.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-framework => ruoyi-apt}/src/main/java/com/ruoyi/apt/infra/connectivity/AptConnectivityService.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-framework => ruoyi-apt}/src/main/java/com/ruoyi/apt/infra/connectivity/ProbeResult.java (100%)
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/controller/AptMasterVersionController.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/controller/AptRouteController.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptMasterVersion.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRoute.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteCommand.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteRecalc.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteStatusCommand.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/mapper/AptMasterVersionMapper.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/mapper/AptRouteMapper.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/AptMasterVersionServiceImpl.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/AptRouteServiceImpl.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/IAptMasterVersionService.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/IAptRouteService.java
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/main/java/com/ruoyi/apt/ops/controller/AptConnectivityController.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-system => ruoyi-apt}/src/main/java/com/ruoyi/apt/ops/domain/AptOperationLog.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-system => ruoyi-apt}/src/main/java/com/ruoyi/apt/ops/mapper/AptAuditMapper.java (100%)
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/controller/AptRoomController.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoom.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomCommand.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomDetail.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomStatusCommand.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomStatusResult.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomStatusSummary.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/mapper/AptRoomMapper.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/service/AptRoomServiceImpl.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/service/IAptRoomService.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/ami/AmiAction.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/ami/AmiClient.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/ami/AmiClientFactory.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/ami/AmiConnectionManager.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/ami/AmiEventListener.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/ami/AmiException.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/ami/AmiFrame.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/ami/AmiProtocol.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/domain/AptAmiInstance.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/mapper/AptVoiceMapper.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/service/AptAmiEventDispatcher.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/service/AptAmiEventHandler.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/service/AptAmiInstanceService.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/service/AptCallFlowServiceImpl.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/voice/service/IAptCallFlowService.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/resources/apt-defaults.yml
rename dev/RuoYi-Vue-master/{ruoyi-system => ruoyi-apt}/src/main/resources/mapper/apt/AptAuditMapper.xml (100%)
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/resources/mapper/apt/AptMasterVersionMapper.xml
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/resources/mapper/apt/AptRoomMapper.xml
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/resources/mapper/apt/AptRouteMapper.xml
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/resources/mapper/apt/AptVoiceMapper.xml
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/main/resources/static/apt/.gitkeep
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/common/AptContextTest.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/common/AptPageSupportTest.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/common/AptPrefixResolverTest.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/framework/AptAccessDeniedHandlerTest.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/framework/AptAuditAspectTest.java (92%)
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/framework/AptAuthInterceptorTest.java (95%)
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/framework/AptErrorResponseTest.java (100%)
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/framework/AptInfraExceptionHandlerTest.java (100%)
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/test/java/com/ruoyi/apt/framework/AptIt2SeedMarkersTest.java
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/framework/AptMapperGyidArchTest.java (68%)
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/test/java/com/ruoyi/apt/framework/AptMasterClientMockGuardTest.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/test/java/com/ruoyi/apt/master/AptMasterVersionServiceTest.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/test/java/com/ruoyi/apt/master/AptRouteServiceTest.java
rename dev/RuoYi-Vue-master/{ruoyi-admin => ruoyi-apt}/src/test/java/com/ruoyi/apt/ops/AptConnectivityControllerTest.java (100%)
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/test/java/com/ruoyi/apt/stay/AptRoomServiceTest.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/test/java/com/ruoyi/apt/voice/AmiClientTest.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/test/java/com/ruoyi/apt/voice/AmiProtocolTest.java
create mode 100644 dev/RuoYi-Vue-master/ruoyi-apt/src/test/java/com/ruoyi/apt/voice/FakeAmiServer.java
delete mode 100644 dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/config/AptProperties.java
delete mode 100644 dev/RuoYi-Vue-master/ruoyi-system/src/main/java/com/ruoyi/apt/master/domain/AptRoute.java
delete mode 100644 dev/RuoYi-Vue-master/ruoyi-system/src/main/java/com/ruoyi/apt/master/mapper/AptRouteMapper.java
delete mode 100644 dev/RuoYi-Vue-master/ruoyi-system/src/main/resources/mapper/apt/AptRouteMapper.xml
create mode 100644 dev/RuoYi-Vue2-master/src/api/apt/checkin.js
create mode 100644 dev/RuoYi-Vue2-master/src/api/apt/face.js
create mode 100644 dev/RuoYi-Vue2-master/src/api/apt/jcxx.js
create mode 100644 dev/RuoYi-Vue2-master/src/api/apt/master.js
create mode 100644 dev/RuoYi-Vue2-master/src/api/apt/ops.js
create mode 100644 dev/RuoYi-Vue2-master/src/api/apt/room.js
create mode 100644 dev/RuoYi-Vue2-master/src/api/apt/route.js
create mode 100644 dev/RuoYi-Vue2-master/src/components/AptDisabledAction/index.vue
create mode 100644 dev/RuoYi-Vue2-master/src/utils/apt.js
create mode 100644 dev/RuoYi-Vue2-master/src/views/apt/checkin/index.vue
create mode 100644 dev/RuoYi-Vue2-master/src/views/apt/face/reg/index.vue
create mode 100644 dev/RuoYi-Vue2-master/src/views/apt/jcxx/index.vue
create mode 100644 dev/RuoYi-Vue2-master/src/views/apt/master/version/index.vue
create mode 100644 dev/RuoYi-Vue2-master/src/views/apt/ops/connectivity/index.vue
create mode 100644 dev/RuoYi-Vue2-master/src/views/apt/room/detail.vue
create mode 100644 dev/RuoYi-Vue2-master/src/views/apt/room/index.vue
create mode 100644 dev/RuoYi-Vue2-master/src/views/apt/route/detail.vue
create mode 100644 dev/RuoYi-Vue2-master/src/views/apt/route/index.vue
create mode 100644 dev/scripts/apply-it2-sql.ps1
create mode 100644 dev/sql/apt_rev01/10_it2_columns.sql
create mode 100644 dev/sql/apt_rev01/11_it2_seed.sql
create mode 100644 dev/sql/apt_rev01/12_it2_permissions.sql
create mode 100644 dev/sql/apt_rev01/13_it2_frontend_menus.sql
create mode 100644 dev/sql/apt_rev01/14_it3_columns.sql
create mode 100644 dev/sql/apt_rev01/15_it3_seed.sql
create mode 100644 dev/sql/apt_rev01/16_it3_permissions.sql
create mode 100644 dev/sql/apt_rev01/17_it4_columns.sql
create mode 100644 dev/sql/apt_rev01/18_it4_permissions.sql
create mode 100644 dev/sql/apt_rev01/19_it5_columns.sql
create mode 100644 dev/sql/apt_rev01/20_it5_permissions.sql
create mode 100644 dev/sql/apt_rev01/21_ami_instance.sql
create mode 100644 docs/公寓端/16-第三轮开发(IT-2)交付说明.md
create mode 100644 docs/公寓端/17-第四轮开发(IT-4)交付说明.md
create mode 100644 docs/公寓端/18-第五轮开发(IT-5)交付说明.md
diff --git a/dev/.env.example b/dev/.env.example
index 0895c94..6a45e55 100644
--- a/dev/.env.example
+++ b/dev/.env.example
@@ -1,7 +1,10 @@
-# 公寓端本地运行环境变量模板(IT-0 / IT-1)
-# 用法:复制为 dev/.env(或 dev/scripts/.env)后填写真实值。
+# 公寓端本地运行环境变量模板(IT-0 / IT-1 / IT-2)
+# 用法:复制为 dev/.env 后填写真实值。
# .env 已被 .gitignore 忽略,禁止提交;本模板不得写入任何真实口令。
# 严禁把明文口令写入数据库配置文件、CI 脚本或提交记录。
+# 填好 APT_DB_PASSWORD 后执行:
+# powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it2-sql.ps1
+# 无凭据时可先预览:追加 -DryRun
# ---- MySQL(apt_rev01)----
APT_DB_HOST=127.0.0.1
@@ -35,6 +38,9 @@ APT_HTTP_PORT=8080
APT_UPLOAD_PATH=./uploadPath
APT_PROBE_ENABLED=true
APT_PROBE_TIMEOUT_MS=2000
+# Table 9-16: Mock master downlink is only allowed with Spring profile 'local'.
+# Keep false in production; a non-local profile with true fails startup.
+APT_MASTER_CLIENT_MOCK=false
# ---- Druid console (disabled by default) ----
APT_DRUID_CONSOLE=false
diff --git a/dev/RuoYi-Vue-master/pom.xml b/dev/RuoYi-Vue-master/pom.xml
index 1a1c13a..b618f79 100644
--- a/dev/RuoYi-Vue-master/pom.xml
+++ b/dev/RuoYi-Vue-master/pom.xml
@@ -170,6 +170,13 @@
${ruoyi.version}
+
+
+ com.ruoyi
+ ruoyi-apt
+ ${ruoyi.version}
+
+
@@ -180,6 +187,7 @@
ruoyi-quartz
ruoyi-generator
ruoyi-common
+ ruoyi-apt
pom
diff --git a/dev/RuoYi-Vue-master/ruoyi-admin/pom.xml b/dev/RuoYi-Vue-master/ruoyi-admin/pom.xml
index a4e5736..a0b3573 100644
--- a/dev/RuoYi-Vue-master/ruoyi-admin/pom.xml
+++ b/dev/RuoYi-Vue-master/ruoyi-admin/pom.xml
@@ -42,6 +42,12 @@
ruoyi-framework
+
+
+ com.ruoyi
+ ruoyi-apt
+
+
com.ruoyi
diff --git a/dev/RuoYi-Vue-master/ruoyi-admin/src/main/java/com/ruoyi/apt/ops/controller/AptRouteController.java b/dev/RuoYi-Vue-master/ruoyi-admin/src/main/java/com/ruoyi/apt/ops/controller/AptRouteController.java
deleted file mode 100644
index 88f8ef6..0000000
--- a/dev/RuoYi-Vue-master/ruoyi-admin/src/main/java/com/ruoyi/apt/ops/controller/AptRouteController.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.ruoyi.apt.ops.controller;
-
-import java.util.List;
-import com.ruoyi.apt.common.AptContext;
-import com.ruoyi.apt.common.audit.AptAudit;
-import com.ruoyi.apt.common.page.AptTableDataInfo;
-import com.ruoyi.apt.framework.web.AptBaseController;
-import com.ruoyi.apt.master.domain.AptRoute;
-import com.ruoyi.apt.master.mapper.AptRouteMapper;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * IT-1 / M0 业务表只读查询骨架:仅用于验证鉴权、gyid 隔离、分页上限与审计链路。
- * 按表 13-11「不含业务表 CRUD」,本类不提供新增 / 编辑 / 删除 / 状态命令接口。
- *
- * @author apt-rev01
- */
-@RestController
-@RequestMapping(value = "/apt/route", produces = "application/json;charset=UTF-8")
-public class AptRouteController extends AptBaseController
-{
- private final AptRouteMapper routeMapper;
-
- @Autowired
- public AptRouteController(AptRouteMapper routeMapper)
- {
- this.routeMapper = routeMapper;
- }
-
- /**
- * GET /apt/route/list
- * 请求参数:cc、jlid、state、pageNum、pageSize、orderByColumn、isAsc
- * 返回:{code,msg,rows,total,pageSize,traceId};pageSize 上限 100。
- */
- @PreAuthorize("@ss.hasPermi('apt:route:list')")
- @AptAudit(module = "ROUTE", action = "LIST")
- @GetMapping("/list")
- public AptTableDataInfo list(
- @RequestParam(required = false) String cc,
- @RequestParam(required = false) String jlid,
- @RequestParam(required = false) String state)
- {
- startAptPage();
- List rows = routeMapper.selectRouteList(AptContext.getGyId(), cc, jlid, state);
- return getAptDataTable(rows);
- }
-}
diff --git a/dev/RuoYi-Vue-master/ruoyi-admin/src/main/resources/application.yml b/dev/RuoYi-Vue-master/ruoyi-admin/src/main/resources/application.yml
index 691cb8f..f8e4347 100644
--- a/dev/RuoYi-Vue-master/ruoyi-admin/src/main/resources/application.yml
+++ b/dev/RuoYi-Vue-master/ruoyi-admin/src/main/resources/application.yml
@@ -26,7 +26,6 @@ server:
logging:
level:
com.ruoyi: debug
- com.ruoyi.apt: info
org.springframework: warn
user:
@@ -35,6 +34,10 @@ user:
lockTime: 10
spring:
+ # 公寓端自有配置(apt.*、springdoc 分组、com.ruoyi.apt 日志级别)由 ruoyi-apt 模块提供;
+ # optional: 保证该模块不在 classpath 时后台仍可启动。主配置同键优先级更高。
+ config:
+ import: 'optional:classpath:apt-defaults.yml'
messages:
basename: i18n/messages
profiles:
@@ -74,6 +77,7 @@ token:
expireTime: 30
mybatis:
+ # 通配覆盖 ruoyi-apt 模块内的 mapper/apt/**/*Mapper.xml
typeAliasesPackage: com.ruoyi.**.domain
mapperLocations: classpath*:mapper/**/*Mapper.xml
configLocation: classpath:mybatis/mybatis-config.xml
@@ -90,20 +94,7 @@ springdoc:
enabled: true
path: /swagger-ui.html
tags-sorter: alpha
- group-configs:
- - group: 'apt'
- display-name: '公寓管理端 /apt'
- paths-to-match: '/apt/**'
- packages-to-scan: com.ruoyi.apt.**
- - group: 'duty'
- display-name: '值班台 /duty'
- paths-to-match: '/duty/**'
- packages-to-scan: com.ruoyi.apt.**
- - group: 'open'
- display-name: '设备侧 /open/v1'
- paths-to-match: '/open/v1/**'
- packages-to-scan: com.ruoyi.apt.**
- # /platform/** 属平台管理端,公寓端不提供,故不分组
+ # group-configs 由 ruoyi-apt 的 apt-defaults.yml 引入
referer:
enabled: false
@@ -113,19 +104,3 @@ xss:
enabled: true
excludes: /system/notice
urlPatterns: /system/*,/monitor/*,/tool/*
-
-# ---- 公寓端基座自定义配置(IT-0 / IT-1)----
-apt:
- # 一进程一 gyid(D5):只从部署环境注入,不接受请求参数
- gy-id: ${APT_GY_ID:APT-REV01-001}
- probe:
- enabled: ${APT_PROBE_ENABLED:true}
- # 单次探针超时(毫秒),超过即判定 TIMEOUT
- timeout-ms: ${APT_PROBE_TIMEOUT_MS:2000}
- redis-key-prefix: ${APT_REDIS_KEY_PREFIX:apt:rev01:}
- # 探针数据存活时间(秒),避免残留脏数据
- redis-ttl-seconds: 60
- # 探针表名(只写探针专用表,不触碰任何业务表)
- table: apt_ops_probe
- page:
- max-size: 100
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/pom.xml b/dev/RuoYi-Vue-master/ruoyi-apt/pom.xml
new file mode 100644
index 0000000..1aeb152
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/pom.xml
@@ -0,0 +1,64 @@
+
+
+
+ ruoyi
+ com.ruoyi
+ 3.9.2
+
+ 4.0.0
+ jar
+ ruoyi-apt
+
+
+ 公寓端集中模块:四前缀接口、业务逻辑、审计与鉴权、配置项、Mapper XML 与前端静态资源挂载点
+
+
+
+
+
+
+ com.ruoyi
+ ruoyi-common
+
+
+
+
+ com.ruoyi
+ ruoyi-framework
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+
+
+
+
+ org.springframework
+ spring-tx
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.2
+
+
+
+
+
diff --git a/dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/AptContext.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/AptContext.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/AptContext.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/AptContext.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/AptErrorCode.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/AptErrorCode.java
similarity index 57%
rename from dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/AptErrorCode.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/AptErrorCode.java
index b983220..ad39e6d 100644
--- a/dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/AptErrorCode.java
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/AptErrorCode.java
@@ -29,6 +29,32 @@ public enum AptErrorCode
FACE_REG_PENDING(40914, "注册申请未获值班员确认"),
TRANSFER_TIME_ILLEGAL(40915, "换乘时刻非法"),
TABLET_TASK_EXPIRED(40916, "平板任务已派发或过期"),
+ /** 设备越出可见范围(IT-3 登记,设备侧执行在 IT-7) */
+ FACE_SCOPE_DENIED(40917, "设备越出人脸可见范围"),
+ /** 同 work_no 已有已确认人员(未决重复申请走原 regId 幂等,不报此码) */
+ FACE_PERSON_DUPLICATE(40918, "该工号已存在人脸注册"),
+ FACE_MEDIA_MISSING(40919, "照片或特征值缺失"),
+ FACE_MEDIA_CHECKSUM_MISMATCH(40920, "媒体校验和不符"),
+ /** 算法不在白名单:允许入档,但不得下发该设备(不静默截断) */
+ FACE_ALGO_UNSUPPORTED(40921, "人脸特征算法不受支持"),
+ FACE_MEDIA_TOO_LARGE(40922, "人脸媒体超过大小上限"),
+ /** 媒体未同步完成(区别于 40914 注册未确认) */
+ FACE_NOT_SYNCED(40923, "人脸媒体未同步完成,禁止下发"),
+ FACE_REG_STATE_INVALID(40924, "注册态不允许该操作"),
+ /** IT-4:入住流程态冲突(非当前态 / bizVersion 过期 / 已入住后换房) */
+ CHECKIN_STATE_CONFLICT(40926, "入住状态冲突,请刷新后重试"),
+ /** IT-4:设备侧人脸缓存版本落后(需重新拉取核验) */
+ FACE_CACHE_STALE(40927, "人脸缓存版本过期,请重新核验"),
+ /** IT-4:房间不可住 / 已满 / 不属于本公寓 */
+ ROOM_NOT_AVAILABLE(40928, "房间不可住或已满"),
+ /** IT-4:N-38 核验会话超过 5 分钟 */
+ VERIFY_SESSION_EXPIRED(40929, "核验会话已过期,请重新刷脸"),
+ /** IT-4:核验结论非 MATCHED 或与申请人不一致 */
+ FACE_VERIFY_MISMATCH(40930, "核验不符"),
+ /** IT-4:交路要素不合法(jlid/cc/jch 与主数据不一致或缺失) */
+ ROUTE_INVALID(40931, "交路要素不合法"),
+ /** IT-4:同一人员已有在住记录 */
+ DUPLICATE_STAY(40932, "该人员已在住,请先办理退宿"),
FEATURE_DISABLED(40940, "能力未启用"),
RECORDING_PURGED(41001, "录音已清理"),
PROTOCOL_NOT_CONFIRMED(50101, "现场协议未确认,暂不支持"),
diff --git a/dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/AptException.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/AptException.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/AptException.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/AptException.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/AptPrefixResolver.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/AptPrefixResolver.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/AptPrefixResolver.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/AptPrefixResolver.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/audit/AptAudit.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/audit/AptAudit.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/audit/AptAudit.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/audit/AptAudit.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/page/AptPageSupport.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/page/AptPageSupport.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/page/AptPageSupport.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/page/AptPageSupport.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/page/AptTableDataInfo.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/page/AptTableDataInfo.java
similarity index 54%
rename from dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/page/AptTableDataInfo.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/page/AptTableDataInfo.java
index bd289a2..45293f3 100644
--- a/dev/RuoYi-Vue-master/ruoyi-common/src/main/java/com/ruoyi/apt/common/page/AptTableDataInfo.java
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/common/page/AptTableDataInfo.java
@@ -1,10 +1,15 @@
package com.ruoyi.apt.common.page;
+import java.util.ArrayList;
+import java.util.List;
import com.ruoyi.common.core.page.TableDataInfo;
/**
* 在若依 TableDataInfo 的 {code,msg,rows,total} 之上追加
- * 归一化 pageSize 与 traceId(追加字段,不破坏既有约定 公寓端/04 §1.2)。
+ * 归一化 pageSize、traceId 与 warnings[](公寓端/04 §1.2、表 4-7 约束 1)。
+ *
+ * warnings 用于承载「楼栋字段未启用」等降级提示,默认空数组,
+ * 不破坏既有列表响应约定。
*
* @author apt-rev01
*/
@@ -14,6 +19,7 @@ public class AptTableDataInfo extends TableDataInfo
private Integer pageSize;
private String traceId;
+ private List warnings = new ArrayList<>();
public Integer getPageSize()
{
@@ -34,4 +40,14 @@ public class AptTableDataInfo extends TableDataInfo
{
this.traceId = traceId;
}
+
+ public List getWarnings()
+ {
+ return warnings;
+ }
+
+ public void setWarnings(List warnings)
+ {
+ this.warnings = warnings == null ? new ArrayList<>() : warnings;
+ }
}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/dispatch/service/AptDispatchScanServiceImpl.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/dispatch/service/AptDispatchScanServiceImpl.java
index 5b2da49..e136dc4 100644
--- a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/dispatch/service/AptDispatchScanServiceImpl.java
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/dispatch/service/AptDispatchScanServiceImpl.java
@@ -10,6 +10,7 @@ import com.ruoyi.apt.common.AptException;
import com.ruoyi.apt.dispatch.mapper.AptJcxxMapper;
import com.ruoyi.apt.dispatch.tablet.service.IAptTabletDispatchService;
import com.ruoyi.apt.framework.config.AptProperties;
+import com.ruoyi.apt.voice.service.IAptCallFlowService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@@ -31,16 +32,19 @@ public class AptDispatchScanServiceImpl implements IAptDispatchScanService
private final IAptDutyStateService stateService;
private final AptJcxxMapper jcxxMapper;
private final AptProperties properties;
+ private final IAptCallFlowService callFlowService;
public AptDispatchScanServiceImpl(IAptTabletDispatchService tabletDispatchService,
IAptDutyStateService stateService,
AptJcxxMapper jcxxMapper,
- AptProperties properties)
+ AptProperties properties,
+ IAptCallFlowService callFlowService)
{
this.tabletDispatchService = tabletDispatchService;
this.stateService = stateService;
this.jcxxMapper = jcxxMapper;
this.properties = properties;
+ this.callFlowService = callFlowService;
}
@Override
@@ -80,13 +84,21 @@ public class AptDispatchScanServiceImpl implements IAptDispatchScanService
}
}
+ // SIP 到点发起(IT-7):房间腿 Originate + 值班台腿;AMI 未就绪时自动跳过
+ int dialed = 0;
+ if (properties.getVoice().isEnabled() && properties.getVoice().isSipCallEnabled())
+ {
+ dialed = callFlowService.dialDue(properties.getVoice().getMaxCallsPerScan());
+ }
+
Map result = new LinkedHashMap<>();
result.put("expiredTasks", expired);
result.put("generatedTasks", generated);
result.put("sipTimeouts", timedOut);
+ result.put("dialedCalls", dialed);
result.put("scannedAt", new Date());
- log.info("叫乘扫描完成:过期任务 {},新生成 {},SIP 超时 {}",
- expired, generated, timedOut);
+ log.info("叫乘扫描完成:过期任务 {},新生成 {},SIP 超时 {},新发起呼叫 {}",
+ expired, generated, timedOut, dialed);
return result;
}
finally
diff --git a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/audit/AptAuditAspect.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/audit/AptAuditAspect.java
similarity index 93%
rename from dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/audit/AptAuditAspect.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/audit/AptAuditAspect.java
index 94bb82f..03d2302 100644
--- a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/audit/AptAuditAspect.java
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/audit/AptAuditAspect.java
@@ -8,7 +8,6 @@ import com.alibaba.fastjson2.JSON;
import com.ruoyi.apt.common.AptContext;
import com.ruoyi.apt.common.audit.AptAudit;
import com.ruoyi.apt.ops.domain.AptOperationLog;
-import com.ruoyi.apt.ops.mapper.AptAuditMapper;
import com.ruoyi.common.utils.SecurityUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
@@ -24,7 +23,8 @@ import org.springframework.web.context.request.ServletRequestAttributes;
* 审计切面:把带 @AptAudit 的操作落到 duty_operation_log。
*
* 与若依 @Log(落 sys_oper_log)并行,不改上游表;日志写入失败只记 WARN,
- * 绝不反向影响业务结果。detail 用 JSON 承载 Q-40 冻结前的扩展要素。
+ * 绝不反向影响业务结果。detail 用 JSON 承载 Q-40 冻结前的扩展要素。
+ * IT-2 起经 AptAuditWriter 以 REQUIRES_NEW 独立事务写入,业务回滚不丢留痕。
*
* @author apt-rev01
*/
@@ -34,11 +34,11 @@ public class AptAuditAspect
{
private static final Logger log = LoggerFactory.getLogger(AptAuditAspect.class);
- private final AptAuditMapper auditMapper;
+ private final AptAuditWriter auditWriter;
- public AptAuditAspect(AptAuditMapper auditMapper)
+ public AptAuditAspect(AptAuditWriter auditWriter)
{
- this.auditMapper = auditMapper;
+ this.auditWriter = auditWriter;
}
@Around("@annotation(aptAudit)")
@@ -109,7 +109,7 @@ public class AptAuditAspect
entry.setDetail(JSON.toJSONString(detail));
entry.setTraceId(AptContext.getTraceId());
entry.setOccurredAt(new Date());
- auditMapper.insertOperationLog(entry);
+ auditWriter.write(entry);
}
catch (Exception e)
{
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/audit/AptAuditWriter.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/audit/AptAuditWriter.java
new file mode 100644
index 0000000..8738829
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/audit/AptAuditWriter.java
@@ -0,0 +1,33 @@
+package com.ruoyi.apt.framework.audit;
+
+import com.ruoyi.apt.ops.domain.AptOperationLog;
+import com.ruoyi.apt.ops.mapper.AptAuditMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 审计独立事务写入口(IT-2 补齐 IT-1 遗留项)。
+ *
+ * 业务写操作与审计同事务时,业务回滚会连带回滚审计留痕;
+ * 这里用 REQUIRES_NEW 让 duty_operation_log 独立提交。
+ * 切面仍对写库异常降级为 WARN,不反向影响业务结果。
+ *
+ * @author apt-rev01
+ */
+@Service
+public class AptAuditWriter
+{
+ private final AptAuditMapper auditMapper;
+
+ public AptAuditWriter(AptAuditMapper auditMapper)
+ {
+ this.auditMapper = auditMapper;
+ }
+
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void write(AptOperationLog entry)
+ {
+ auditMapper.insertOperationLog(entry);
+ }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/config/AptMasterClientMockGuard.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/config/AptMasterClientMockGuard.java
new file mode 100644
index 0000000..ccadb9f
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/config/AptMasterClientMockGuard.java
@@ -0,0 +1,44 @@
+package com.ruoyi.apt.framework.config;
+
+import jakarta.annotation.PostConstruct;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+/**
+ * Mock 主数据下行开关的生产护栏(公寓端/09 表 9-16、13 IR-10)。
+ *
+ * apt.master-client.mock=true 只在 local profile 允许;
+ * 其它 profile 启动即失败,避免把 source='LOCAL' 的 Mock 数据带到生产。
+ *
+ * @author apt-rev01
+ */
+@Component
+public class AptMasterClientMockGuard
+{
+ private final AptProperties properties;
+ private final Environment environment;
+
+ public AptMasterClientMockGuard(AptProperties properties, Environment environment)
+ {
+ this.properties = properties;
+ this.environment = environment;
+ }
+
+ @PostConstruct
+ public void validate()
+ {
+ if (!properties.getMasterClient().isMock())
+ {
+ return;
+ }
+ for (String profile : environment.getActiveProfiles())
+ {
+ if ("local".equalsIgnoreCase(profile))
+ {
+ return;
+ }
+ }
+ throw new IllegalStateException(
+ "apt.master-client.mock=true is only allowed with Spring profile 'local'");
+ }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/config/AptProperties.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/config/AptProperties.java
new file mode 100644
index 0000000..2683ddd
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/config/AptProperties.java
@@ -0,0 +1,560 @@
+package com.ruoyi.apt.framework.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 公寓端基座配置(application.yml 的 apt.* 段)。
+ *
+ * @author apt-rev01
+ */
+@Component
+@ConfigurationProperties(prefix = "apt")
+public class AptProperties
+{
+ /** 一进程一 gyid(D5),只从部署环境注入 */
+ private String gyId;
+
+ private Probe probe = new Probe();
+ private Page page = new Page();
+ private MasterClient masterClient = new MasterClient();
+ private Device device = new Device();
+ private Features features = new Features();
+ private Dispatch dispatch = new Dispatch();
+ private Voice voice = new Voice();
+
+ public String getGyId()
+ {
+ return gyId;
+ }
+
+ public void setGyId(String gyId)
+ {
+ this.gyId = gyId;
+ }
+
+ public Probe getProbe()
+ {
+ return probe;
+ }
+
+ public void setProbe(Probe probe)
+ {
+ this.probe = probe;
+ }
+
+ public Page getPage()
+ {
+ return page;
+ }
+
+ public void setPage(Page page)
+ {
+ this.page = page;
+ }
+
+ public MasterClient getMasterClient()
+ {
+ return masterClient;
+ }
+
+ public void setMasterClient(MasterClient masterClient)
+ {
+ this.masterClient = masterClient;
+ }
+
+ public Device getDevice()
+ {
+ return device;
+ }
+
+ public void setDevice(Device device)
+ {
+ this.device = device;
+ }
+
+ public Features getFeatures()
+ {
+ return features;
+ }
+
+ public void setFeatures(Features features)
+ {
+ this.features = features;
+ }
+
+ public Dispatch getDispatch()
+ {
+ return dispatch;
+ }
+
+ public void setDispatch(Dispatch dispatch)
+ {
+ this.dispatch = dispatch;
+ }
+
+ public Voice getVoice()
+ {
+ return voice;
+ }
+
+ public void setVoice(Voice voice)
+ {
+ this.voice = voice;
+ }
+
+ public static class Probe
+ {
+ private boolean enabled = true;
+ /** 单次探针超时判定阈值(毫秒) */
+ private long timeoutMs = 2000;
+ private String redisKeyPrefix = "apt:rev01:";
+ private int redisTtlSeconds = 60;
+ /** 探针专用表,不触碰任何业务表 */
+ private String table = "apt_ops_probe";
+
+ public boolean isEnabled()
+ {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+ public long getTimeoutMs()
+ {
+ return timeoutMs;
+ }
+
+ public void setTimeoutMs(long timeoutMs)
+ {
+ this.timeoutMs = timeoutMs;
+ }
+
+ public String getRedisKeyPrefix()
+ {
+ return redisKeyPrefix;
+ }
+
+ public void setRedisKeyPrefix(String redisKeyPrefix)
+ {
+ this.redisKeyPrefix = redisKeyPrefix;
+ }
+
+ public int getRedisTtlSeconds()
+ {
+ return redisTtlSeconds;
+ }
+
+ public void setRedisTtlSeconds(int redisTtlSeconds)
+ {
+ this.redisTtlSeconds = redisTtlSeconds;
+ }
+
+ public String getTable()
+ {
+ return table;
+ }
+
+ public void setTable(String table)
+ {
+ this.table = table;
+ }
+ }
+
+ public static class Page
+ {
+ private int maxSize = 100;
+
+ public int getMaxSize()
+ {
+ return maxSize;
+ }
+
+ public void setMaxSize(int maxSize)
+ {
+ this.maxSize = maxSize;
+ }
+ }
+
+ /** 表 9-16:Mock 主数据下行只允许本地 profile 开启,生产强制 false */
+ public static class MasterClient
+ {
+ private boolean mock = false;
+
+ public boolean isMock()
+ {
+ return mock;
+ }
+
+ public void setMock(boolean mock)
+ {
+ this.mock = mock;
+ }
+ }
+
+ /**
+ * IT-3 设备侧鉴权(表 10-24 #1):设备编码 + HMAC + 时间窗 + nonce 四要素。
+ * 仅作用于 {@code /open/v1/**};{@code /apt/**} 与 {@code /duty/**} 不走此配置。
+ */
+ public static class Device
+ {
+ /** 关闭后只校验 X-Device-Code(本地联调用),生产必须为 true */
+ private boolean hmacEnabled = true;
+ /** HMAC-SHA256 共享密钥,只从部署环境注入,不落库、不打日志 */
+ private String hmacSecret;
+ /** 允许的时钟偏移(秒) */
+ private long timeWindowSeconds = 300;
+ /** nonce 去重窗口(秒),须不小于 timeWindowSeconds */
+ private long nonceTtlSeconds = 600;
+ /** 特征值算法白名单;不在名单内允许入档但不下发 */
+ private String[] faceAlgoWhitelist = { "FEAT_V1", "FEAT_V2", "PHOTO_REF" };
+ private int photoMaxBytes = 524288;
+ private int featureMaxBytes = 65536;
+
+ public boolean isHmacEnabled()
+ {
+ return hmacEnabled;
+ }
+
+ public void setHmacEnabled(boolean hmacEnabled)
+ {
+ this.hmacEnabled = hmacEnabled;
+ }
+
+ public String getHmacSecret()
+ {
+ return hmacSecret;
+ }
+
+ public void setHmacSecret(String hmacSecret)
+ {
+ this.hmacSecret = hmacSecret;
+ }
+
+ public long getTimeWindowSeconds()
+ {
+ return timeWindowSeconds;
+ }
+
+ public void setTimeWindowSeconds(long timeWindowSeconds)
+ {
+ this.timeWindowSeconds = timeWindowSeconds;
+ }
+
+ public long getNonceTtlSeconds()
+ {
+ return nonceTtlSeconds;
+ }
+
+ public void setNonceTtlSeconds(long nonceTtlSeconds)
+ {
+ this.nonceTtlSeconds = nonceTtlSeconds;
+ }
+
+ public String[] getFaceAlgoWhitelist()
+ {
+ return faceAlgoWhitelist;
+ }
+
+ public void setFaceAlgoWhitelist(String[] faceAlgoWhitelist)
+ {
+ this.faceAlgoWhitelist = faceAlgoWhitelist;
+ }
+
+ public int getPhotoMaxBytes()
+ {
+ return photoMaxBytes;
+ }
+
+ public void setPhotoMaxBytes(int photoMaxBytes)
+ {
+ this.photoMaxBytes = photoMaxBytes;
+ }
+
+ public int getFeatureMaxBytes()
+ {
+ return featureMaxBytes;
+ }
+
+ public void setFeatureMaxBytes(int featureMaxBytes)
+ {
+ this.featureMaxBytes = featureMaxBytes;
+ }
+ }
+
+ /**
+ * 能力开关(公寓端/05 表 5-18 / CR-C23):TTL 能力未确认前保持默认值,
+ * {@code tabletCall=false} 时平板链路整体回落 SIP,且平板不发 #24。
+ */
+ public static class Features
+ {
+ /** 平板叫乘总开关;关闭后派发任务不生成、已有 TABLET 记录回落 SIP */
+ private boolean tabletCall = true;
+ /** #24 语音质量摘要只由 SIP 链路发送(硬约束,配置仅作显式声明) */
+ private boolean voiceSummary = true;
+
+ public boolean isTabletCall()
+ {
+ return tabletCall;
+ }
+
+ public void setTabletCall(boolean tabletCall)
+ {
+ this.tabletCall = tabletCall;
+ }
+
+ public boolean isVoiceSummary()
+ {
+ return voiceSummary;
+ }
+
+ public void setVoiceSummary(boolean voiceSummary)
+ {
+ this.voiceSummary = voiceSummary;
+ }
+ }
+
+ /**
+ * 叫乘执行参数(公寓端/05 §9、Q-29 默认口径):
+ * 平板签到窗口 60s / 时钟偏差阈值 1s / SIP 无入会 30s / 复叫 35s。
+ */
+ public static class Dispatch
+ {
+ private int signTimeoutSec = 60;
+ private long clockSkewThresholdMs = 1000;
+ private int sipNoAnswerSeconds = 30;
+ private int recallSeconds = 35;
+ /** 平板任务过期时间(秒),从 jcsj 起算 */
+ private int tabletTaskTtlSeconds = 900;
+ /** 单次扫描批大小 */
+ private int scanBatchSize = 50;
+ /** TTS 模板编码(平板本地合成,文本不下发图像) */
+ private String ttsTemplate = "CALL_ROOM";
+ private String ttsText = "请到门口集合,准备乘车";
+
+ public int getSignTimeoutSec()
+ {
+ return signTimeoutSec;
+ }
+
+ public void setSignTimeoutSec(int signTimeoutSec)
+ {
+ this.signTimeoutSec = signTimeoutSec;
+ }
+
+ public long getClockSkewThresholdMs()
+ {
+ return clockSkewThresholdMs;
+ }
+
+ public void setClockSkewThresholdMs(long clockSkewThresholdMs)
+ {
+ this.clockSkewThresholdMs = clockSkewThresholdMs;
+ }
+
+ public int getSipNoAnswerSeconds()
+ {
+ return sipNoAnswerSeconds;
+ }
+
+ public void setSipNoAnswerSeconds(int sipNoAnswerSeconds)
+ {
+ this.sipNoAnswerSeconds = sipNoAnswerSeconds;
+ }
+
+ public int getRecallSeconds()
+ {
+ return recallSeconds;
+ }
+
+ public void setRecallSeconds(int recallSeconds)
+ {
+ this.recallSeconds = recallSeconds;
+ }
+
+ public int getTabletTaskTtlSeconds()
+ {
+ return tabletTaskTtlSeconds;
+ }
+
+ public void setTabletTaskTtlSeconds(int tabletTaskTtlSeconds)
+ {
+ this.tabletTaskTtlSeconds = tabletTaskTtlSeconds;
+ }
+
+ public int getScanBatchSize()
+ {
+ return scanBatchSize;
+ }
+
+ public void setScanBatchSize(int scanBatchSize)
+ {
+ this.scanBatchSize = scanBatchSize;
+ }
+
+ public String getTtsTemplate()
+ {
+ return ttsTemplate;
+ }
+
+ public void setTtsTemplate(String ttsTemplate)
+ {
+ this.ttsTemplate = ttsTemplate;
+ }
+
+ public String getTtsText()
+ {
+ return ttsText;
+ }
+
+ public void setTtsText(String ttsText)
+ {
+ this.ttsText = ttsText;
+ }
+ }
+
+ /**
+ * 语音链路开关与上层行为(AMI 连接参数在 {@code apt_ast_instance} 表,随公寓实例走)。
+ *
+ * 默认关闭:未接 Asterisk 的环境不得因缺少依赖而启动失败或静默改状态
+ * (公寓端/06 §7「依赖缺失显式降级」)。
+ */
+ public static class Voice
+ {
+ /** AMI 连接与语音链路总开关 */
+ private boolean enabled = false;
+ /** 到 jcsj 自动发起 SIP 叫乘(关闭时只能由值班台命令触发) */
+ private boolean sipCallEnabled = true;
+ /** 话机通道技术:PJSIP / SIP(与 Asterisk 侧 endpoint 类型一致) */
+ private String channelTech = "PJSIP";
+ /** TTS 播报拨号计划上下文(Asterisk 侧 TTS 应用入口) */
+ private String ttsContext = "apt-tts";
+ /** TTS 播报分机(Local/@) */
+ private String ttsExtension = "s";
+ /** 提示语音文本(只下发文本,音频由 Asterisk 侧合成) */
+ private String ttsText = "请到门口集合,准备乘车";
+ /** 播放媒体前缀(Asterisk sounds 目录下的文件名前缀) */
+ private String ttsMediaPrefix = "apt/call-prompt";
+ /** 呼叫超时(毫秒),同时下发 Originate 的 Timeout */
+ private int dialTimeoutMs = 30000;
+ /** 事件处理队列容量(单实例单线程保序消费;满了丢弃并告警) */
+ private int eventQueueCapacity = 2000;
+ /** 值班台分机列表(双腿呼叫的第二腿) */
+ private java.util.List dutyExtensions = new java.util.ArrayList<>();
+ /** 单次扫描最多发起的 SIP 呼叫数,避免补跑时瞬间打爆 Asterisk */
+ private int maxCallsPerScan = 10;
+
+ public boolean isEnabled()
+ {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+ public boolean isSipCallEnabled()
+ {
+ return sipCallEnabled;
+ }
+
+ public void setSipCallEnabled(boolean sipCallEnabled)
+ {
+ this.sipCallEnabled = sipCallEnabled;
+ }
+
+ public String getChannelTech()
+ {
+ return channelTech;
+ }
+
+ public void setChannelTech(String channelTech)
+ {
+ this.channelTech = channelTech;
+ }
+
+ public String getTtsContext()
+ {
+ return ttsContext;
+ }
+
+ public void setTtsContext(String ttsContext)
+ {
+ this.ttsContext = ttsContext;
+ }
+
+ public String getTtsExtension()
+ {
+ return ttsExtension;
+ }
+
+ public void setTtsExtension(String ttsExtension)
+ {
+ this.ttsExtension = ttsExtension;
+ }
+
+ public String getTtsText()
+ {
+ return ttsText;
+ }
+
+ public void setTtsText(String ttsText)
+ {
+ this.ttsText = ttsText;
+ }
+
+ public String getTtsMediaPrefix()
+ {
+ return ttsMediaPrefix;
+ }
+
+ public void setTtsMediaPrefix(String ttsMediaPrefix)
+ {
+ this.ttsMediaPrefix = ttsMediaPrefix;
+ }
+
+ public int getDialTimeoutMs()
+ {
+ return dialTimeoutMs;
+ }
+
+ public void setDialTimeoutMs(int dialTimeoutMs)
+ {
+ this.dialTimeoutMs = dialTimeoutMs;
+ }
+
+ public int getEventQueueCapacity()
+ {
+ return eventQueueCapacity;
+ }
+
+ public void setEventQueueCapacity(int eventQueueCapacity)
+ {
+ this.eventQueueCapacity = eventQueueCapacity;
+ }
+
+ public java.util.List getDutyExtensions()
+ {
+ return dutyExtensions;
+ }
+
+ public void setDutyExtensions(java.util.List dutyExtensions)
+ {
+ this.dutyExtensions = dutyExtensions;
+ }
+
+ public int getMaxCallsPerScan()
+ {
+ return maxCallsPerScan;
+ }
+
+ public void setMaxCallsPerScan(int maxCallsPerScan)
+ {
+ this.maxCallsPerScan = maxCallsPerScan;
+ }
+ }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/security/AptAccessDeniedHandler.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/security/AptAccessDeniedHandler.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/security/AptAccessDeniedHandler.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/security/AptAccessDeniedHandler.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/security/AptAuthenticationEntryPoint.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/security/AptAuthenticationEntryPoint.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/security/AptAuthenticationEntryPoint.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/security/AptAuthenticationEntryPoint.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptBaseController.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptBaseController.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptBaseController.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptBaseController.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptContextInterceptor.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptContextInterceptor.java
similarity index 85%
rename from dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptContextInterceptor.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptContextInterceptor.java
index 7b129e0..b9deb31 100644
--- a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptContextInterceptor.java
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptContextInterceptor.java
@@ -6,10 +6,12 @@ import com.ruoyi.apt.common.AptException;
import com.ruoyi.apt.common.AptPrefixResolver;
import com.ruoyi.apt.common.AptPrefixResolver.Prefix;
import com.ruoyi.apt.framework.config.AptProperties;
+import com.ruoyi.apt.framework.device.AptDeviceSigner;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.utils.SecurityUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
@@ -21,7 +23,7 @@ import org.springframework.web.servlet.HandlerInterceptor;
* 请求中一旦出现与上下文不一致的 gyid,立即拒绝,避免跨公寓数据泄露。
* 分区策略:
* /apt/**、/duty/** -> 需登录态(@Anonymous 例外)
- * /open/v1/** -> 设备凭据骨架(HMAC / 时间窗 / nonce 在 IT-3 落地)
+ * /open/v1/** -> 设备编码 + HMAC + 时间窗 + nonce(IT-3 已落地)
* /platform/** -> 本端不提供,显式 40401
* 其余路径不在注册范围内。
*
@@ -31,10 +33,19 @@ import org.springframework.web.servlet.HandlerInterceptor;
public class AptContextInterceptor implements HandlerInterceptor
{
private final AptProperties properties;
+ private final AptDeviceSigner deviceSigner;
+ /** 保留单参构造器:既有拦截器单测不感知设备签名组件 */
public AptContextInterceptor(AptProperties properties)
+ {
+ this(properties, new AptDeviceSigner(properties));
+ }
+
+ @Autowired
+ public AptContextInterceptor(AptProperties properties, AptDeviceSigner deviceSigner)
{
this.properties = properties;
+ this.deviceSigner = deviceSigner;
}
@Override
@@ -51,7 +62,8 @@ public class AptContextInterceptor implements HandlerInterceptor
throw new AptException(AptErrorCode.NOT_FOUND, "PREFIX_NOT_SERVED",
"平台管理端接口不在公寓端提供");
case OPEN:
- assertDeviceCredential(request);
+ // IT-3:设备编码 + HMAC + 时间窗 + nonce 四要素校验(DV-3)
+ deviceSigner.verify(request);
break;
case APT:
case DUTY:
@@ -95,16 +107,6 @@ public class AptContextInterceptor implements HandlerInterceptor
}
}
- private void assertDeviceCredential(HttpServletRequest request)
- {
- String deviceCode = request.getHeader("X-Device-Code");
- if (deviceCode == null || deviceCode.isBlank())
- {
- throw new AptException(AptErrorCode.UNAUTHORIZED, "DEVICE_CREDENTIAL_MISSING",
- "缺少设备凭据 X-Device-Code");
- }
- }
-
private void assertNoGyidOverride(HttpServletRequest request, Prefix prefix)
{
String requested = request.getParameter("gyid");
diff --git a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptInfraExceptionHandler.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptInfraExceptionHandler.java
similarity index 86%
rename from dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptInfraExceptionHandler.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptInfraExceptionHandler.java
index 467785f..46d234c 100644
--- a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptInfraExceptionHandler.java
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptInfraExceptionHandler.java
@@ -24,6 +24,10 @@ import org.springframework.data.redis.RedisSystemException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.security.access.AccessDeniedException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
@@ -114,6 +118,31 @@ public class AptInfraExceptionHandler
return handle(AptErrorCode.FORBIDDEN_PERM, "PERMISSION_DENIED", "无权限点", e, request);
}
+ /**
+ * 参数校验异常(@Valid / BindException):公寓端 40001 且 msg 指出字段名(表 7-3 M1),
+ * 非公寓端保持若依 GlobalExceptionHandler 的字段错误口径。
+ */
+ @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
+ public AjaxResult handleValidation(Exception e, HttpServletRequest request)
+ {
+ BindingResult binding = e instanceof MethodArgumentNotValidException
+ ? ((MethodArgumentNotValidException) e).getBindingResult()
+ : ((BindException) e).getBindingResult();
+ String message = validationMessage(binding);
+ if (!AptPrefixResolver.isAptManaged(request.getRequestURI()))
+ {
+ return AjaxResult.error(message);
+ }
+ return build(AptErrorCode.BAD_REQUEST, "PARAM_INVALID", message, request);
+ }
+
+ private String validationMessage(BindingResult binding)
+ {
+ FieldError field = binding.getFieldError();
+ return field == null ? "参数校验失败"
+ : "参数校验失败: " + field.getField() + " " + field.getDefaultMessage();
+ }
+
/**
* 若依 ServiceException:非公寓端保留其自定义 code(GlobalExceptionHandler 口径),
* 避免 @Order(HIGHEST_PRECEDENCE) 吞掉若依后台业务错误码;公寓端走统一响应体。
diff --git a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptWebMvcConfig.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptWebMvcConfig.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/framework/web/AptWebMvcConfig.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/framework/web/AptWebMvcConfig.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/infra/connectivity/AptConnectivityService.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/infra/connectivity/AptConnectivityService.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/infra/connectivity/AptConnectivityService.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/infra/connectivity/AptConnectivityService.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/infra/connectivity/ProbeResult.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/infra/connectivity/ProbeResult.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-framework/src/main/java/com/ruoyi/apt/infra/connectivity/ProbeResult.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/infra/connectivity/ProbeResult.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/controller/AptMasterVersionController.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/controller/AptMasterVersionController.java
new file mode 100644
index 0000000..575dbf0
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/controller/AptMasterVersionController.java
@@ -0,0 +1,67 @@
+package com.ruoyi.apt.master.controller;
+
+import java.util.List;
+import com.ruoyi.apt.common.audit.AptAudit;
+import com.ruoyi.apt.framework.web.AptBaseController;
+import com.ruoyi.apt.master.domain.AptMasterVersion;
+import com.ruoyi.apt.master.service.IAptMasterVersionService;
+import com.ruoyi.common.core.domain.AjaxResult;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 主数据快照版本(IT-2 / 任务 5,公寓端/04 表 4-17 #1/#2)。
+ *
+ * 权限点说明:表 4-17 权威口径为 apt:ops:health,IT-2 权限点种子另建
+ * apt:master:version(3015)。此处用 hasAnyPermi 兼容二者,偏离在交付说明登记。
+ *
+ * @author apt-rev01
+ */
+@RestController
+@RequestMapping(value = "/apt/master", produces = "application/json;charset=UTF-8")
+public class AptMasterVersionController extends AptBaseController
+{
+ private static final String VERSION_PERMISSION =
+ "@ss.hasAnyPermi('apt:ops:health,apt:master:version')";
+
+ private final IAptMasterVersionService versionService;
+
+ public AptMasterVersionController(IAptMasterVersionService versionService)
+ {
+ this.versionService = versionService;
+ }
+
+ @PreAuthorize(VERSION_PERMISSION)
+ @AptAudit(module = "MASTER", action = "VERSION_LIST")
+ @GetMapping("/versions")
+ public AjaxResult versions()
+ {
+ List versions = versionService.list();
+ AjaxResult result = AjaxResult.success(versions);
+ if (versions.stream().anyMatch(AptMasterVersion::isLocalMock))
+ {
+ result.put("mockNotice", "非平台下发(IT-2 Mock:source=LOCAL / source_version=0)");
+ }
+ return result;
+ }
+
+ @PreAuthorize(VERSION_PERMISSION)
+ @GetMapping("/versions/{domain}")
+ public AjaxResult detail(@PathVariable String domain)
+ {
+ return AjaxResult.success(versionService.detail(domain));
+ }
+
+ /** 平台未就绪前不开放全量重拉,恒返回 40940,不产生假成功的版本记录 */
+ @PreAuthorize("@ss.hasPermi('apt:ops:reconcile')")
+ @PostMapping("/{masterType}/resync")
+ public AjaxResult resync(@PathVariable String masterType)
+ {
+ versionService.resync(masterType);
+ return AjaxResult.success();
+ }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/controller/AptRouteController.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/controller/AptRouteController.java
new file mode 100644
index 0000000..8ad91f6
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/controller/AptRouteController.java
@@ -0,0 +1,153 @@
+package com.ruoyi.apt.master.controller;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.UUID;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.apt.common.AptContext;
+import com.ruoyi.apt.common.audit.AptAudit;
+import com.ruoyi.apt.common.page.AptTableDataInfo;
+import com.ruoyi.apt.framework.audit.AptAuditWriter;
+import com.ruoyi.apt.framework.web.AptBaseController;
+import com.ruoyi.apt.master.domain.AptRoute;
+import com.ruoyi.apt.master.domain.AptRouteCommand;
+import com.ruoyi.apt.master.domain.AptRouteRecalc;
+import com.ruoyi.apt.master.domain.AptRouteStatusCommand;
+import com.ruoyi.apt.master.service.IAptRouteService;
+import com.ruoyi.apt.ops.domain.AptOperationLog;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.SecurityUtils;
+import jakarta.validation.Valid;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 交路管理(IT-2 / M1,公寓端/04 表 4-5 接口 1~7)。
+ *
+ * IT-1 的只读骨架已按表 13-21 迁入 com.ruoyi.apt.master.controller;
+ * 本轮补齐 CRUD / 启停 / 复算校验。D7 未签前新增与编辑默认由权限点控制,
+ * 不开放给普通角色;停用原因显式写 duty_operation_log(含 reason)。
+ *
+ * @author apt-rev01
+ */
+@RestController
+@RequestMapping(value = "/apt/route", produces = "application/json;charset=UTF-8")
+public class AptRouteController extends AptBaseController
+{
+ private final IAptRouteService routeService;
+ private final AptAuditWriter auditWriter;
+
+ public AptRouteController(IAptRouteService routeService, AptAuditWriter auditWriter)
+ {
+ this.routeService = routeService;
+ this.auditWriter = auditWriter;
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:route:list')")
+ @AptAudit(module = "MASTER", action = "ROUTE_LIST")
+ @GetMapping("/list")
+ public AptTableDataInfo list(
+ @RequestParam(required = false) String cc,
+ @RequestParam(required = false) String jlid,
+ @RequestParam(required = false) String state)
+ {
+ startAptPage();
+ return getAptDataTable(routeService.list(cc, jlid, state));
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:route:query')")
+ @AptAudit(module = "MASTER", action = "ROUTE_DETAIL")
+ @GetMapping("/{id}")
+ public AjaxResult detail(@PathVariable Long id)
+ {
+ return AjaxResult.success(routeService.detail(id));
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:route:list')")
+ @GetMapping("/options")
+ public AjaxResult options(@RequestParam(required = false) String keyword)
+ {
+ return AjaxResult.success(routeService.options(keyword));
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:route:add')")
+ @AptAudit(module = "MASTER", action = "ROUTE_ADD")
+ @PostMapping
+ public AjaxResult add(@Valid @RequestBody AptRouteCommand command)
+ {
+ AjaxResult result = AjaxResult.success();
+ result.put("id", routeService.add(command));
+ return result;
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:route:edit')")
+ @AptAudit(module = "MASTER", action = "ROUTE_EDIT")
+ @PutMapping("/{id}")
+ public AjaxResult edit(@PathVariable Long id, @Valid @RequestBody AptRouteCommand command)
+ {
+ routeService.edit(id, command);
+ return AjaxResult.success();
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:route:changeStatus')")
+ @PutMapping("/{id}/status")
+ public AjaxResult changeStatus(@PathVariable Long id,
+ @Valid @RequestBody AptRouteStatusCommand command)
+ {
+ routeService.changeStatus(id, command);
+ writeReasonAudit(command.getEnabled() ? "ROUTE_ENABLE" : "ROUTE_DISABLE", id, command.getReason());
+ return AjaxResult.success();
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:route:query')")
+ @PostMapping("/{id}/recalc-check")
+ public AjaxResult recalcCheck(@PathVariable Long id,
+ @RequestParam(required = false)
+ @DateTimeFormat(pattern = "yyyy-MM-dd") Date baseDate)
+ {
+ AptRouteRecalc result = routeService.recalcCheck(id, baseDate);
+ return AjaxResult.success(result);
+ }
+
+ /** 启停原因必须进审计 detail(表 7-3 M1「停用审计」) */
+ private void writeReasonAudit(String action, Long routeId, String reason)
+ {
+ Map detail = new LinkedHashMap<>();
+ detail.put("reason", reason);
+ detail.put("routeId", routeId);
+ detail.put("status", "SUCCESS");
+ detail.put("traceId", AptContext.getTraceId());
+
+ AptOperationLog entry = new AptOperationLog();
+ entry.setLogId(UUID.randomUUID().toString().replace("-", ""));
+ entry.setGyId(AptContext.getGyIdOrNull());
+ entry.setOperatorRef(operatorRef());
+ entry.setModule("MASTER");
+ entry.setAction(action);
+ entry.setDetail(JSON.toJSONString(detail));
+ entry.setTraceId(AptContext.getTraceId());
+ entry.setOccurredAt(new Date());
+ auditWriter.write(entry);
+ }
+
+ private String operatorRef()
+ {
+ try
+ {
+ return SecurityUtils.getLoginUser().getUsername();
+ }
+ catch (Exception e)
+ {
+ return "ANONYMOUS";
+ }
+ }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptMasterVersion.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptMasterVersion.java
new file mode 100644
index 0000000..3279f2c
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptMasterVersion.java
@@ -0,0 +1,51 @@
+package com.ruoyi.apt.master.domain;
+
+import java.util.Date;
+
+/**
+ * 主数据快照版本(IT-2 / 任务 5,公寓端/09 表 9-16、04 表 4-17 #1)。
+ *
+ * Mock 灌入的数据恒为 source='LOCAL' / source_version=0 / state='APPLIED',
+ * 响应必须显式标识「非平台下发」,不得冒充平台下行。
+ *
+ * @author apt-rev01
+ */
+public class AptMasterVersion
+{
+ private Long id;
+ private String gyid;
+ private String domain;
+ private Long sourceVersion;
+ private String source;
+ private String state;
+ private Date appliedAt;
+ private String remark;
+ /** 该域当前快照记录数,用于表 9-16 的落库断言 */
+ private Long itemCount;
+ private boolean localMock;
+ /** 页面展示文案:非平台下发 / 平台下发 */
+ private String sourceLabel;
+
+ public Long getId() { return id; }
+ public void setId(Long id) { this.id = id; }
+ public String getGyid() { return gyid; }
+ public void setGyid(String gyid) { this.gyid = gyid; }
+ public String getDomain() { return domain; }
+ public void setDomain(String domain) { this.domain = domain; }
+ public Long getSourceVersion() { return sourceVersion; }
+ public void setSourceVersion(Long sourceVersion) { this.sourceVersion = sourceVersion; }
+ public String getSource() { return source; }
+ public void setSource(String source) { this.source = source; }
+ public String getState() { return state; }
+ public void setState(String state) { this.state = state; }
+ public Date getAppliedAt() { return appliedAt; }
+ public void setAppliedAt(Date appliedAt) { this.appliedAt = appliedAt; }
+ public String getRemark() { return remark; }
+ public void setRemark(String remark) { this.remark = remark; }
+ public Long getItemCount() { return itemCount; }
+ public void setItemCount(Long itemCount) { this.itemCount = itemCount; }
+ public boolean isLocalMock() { return localMock; }
+ public void setLocalMock(boolean localMock) { this.localMock = localMock; }
+ public String getSourceLabel() { return sourceLabel; }
+ public void setSourceLabel(String sourceLabel) { this.sourceLabel = sourceLabel; }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRoute.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRoute.java
new file mode 100644
index 0000000..5425084
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRoute.java
@@ -0,0 +1,69 @@
+package com.ruoyi.apt.master.domain;
+
+import java.util.Date;
+
+/**
+ * 交路主数据(apt_master_route)。
+ *
+ * IT-2 字段映射:设计文档表 4-5 的 jch/qd/zd 分别对应 DDL 的
+ * route_name/start_station/end_station;enabled 对应 state='ACTIVE';
+ * sourceVersion 对应 source_version。DTO 与 VO 对外只用设计字段名,
+ * 不改 59 张表的口径(公寓端/15 表 15-15 #11)。
+ *
+ * @author apt-rev01
+ */
+public class AptRoute
+{
+ private Long id;
+ private String gyid;
+ private String jlid;
+ private String cc;
+ private String jch;
+ private String qd;
+ private String zd;
+ private Date kcsj;
+ private Date jcsj;
+ private Date lysj;
+ private String cclx;
+ private String bz;
+ private String state;
+ private Integer enabled;
+ private String stopReason;
+ private Long sourceVersion;
+ private Date createTime;
+
+ public Long getId() { return id; }
+ public void setId(Long id) { this.id = id; }
+ public String getGyid() { return gyid; }
+ public void setGyid(String gyid) { this.gyid = gyid; }
+ public String getJlid() { return jlid; }
+ public void setJlid(String jlid) { this.jlid = jlid; }
+ public String getCc() { return cc; }
+ public void setCc(String cc) { this.cc = cc; }
+ public String getJch() { return jch; }
+ public void setJch(String jch) { this.jch = jch; }
+ public String getQd() { return qd; }
+ public void setQd(String qd) { this.qd = qd; }
+ public String getZd() { return zd; }
+ public void setZd(String zd) { this.zd = zd; }
+ public Date getKcsj() { return kcsj; }
+ public void setKcsj(Date kcsj) { this.kcsj = kcsj; }
+ public Date getJcsj() { return jcsj; }
+ public void setJcsj(Date jcsj) { this.jcsj = jcsj; }
+ public Date getLysj() { return lysj; }
+ public void setLysj(Date lysj) { this.lysj = lysj; }
+ public String getCclx() { return cclx; }
+ public void setCclx(String cclx) { this.cclx = cclx; }
+ public String getBz() { return bz; }
+ public void setBz(String bz) { this.bz = bz; }
+ public String getState() { return state; }
+ public void setState(String state) { this.state = state; }
+ public Integer getEnabled() { return enabled; }
+ public void setEnabled(Integer enabled) { this.enabled = enabled; }
+ public String getStopReason() { return stopReason; }
+ public void setStopReason(String stopReason) { this.stopReason = stopReason; }
+ public Long getSourceVersion() { return sourceVersion; }
+ public void setSourceVersion(Long sourceVersion) { this.sourceVersion = sourceVersion; }
+ public Date getCreateTime() { return createTime; }
+ public void setCreateTime(Date createTime) { this.createTime = createTime; }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteCommand.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteCommand.java
new file mode 100644
index 0000000..f244f17
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteCommand.java
@@ -0,0 +1,69 @@
+package com.ruoyi.apt.master.domain;
+
+import java.util.Date;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+
+/**
+ * 交路新增 / 编辑入参(公寓端/04 表 4-5)。
+ *
+ * sourceVersion / createTime 是平台快照只读字段,若入参显式携带则
+ * 由 Service 返回 40904(表 4-5 校验规则 5)。
+ *
+ * @author apt-rev01
+ */
+public class AptRouteCommand
+{
+ @NotBlank(message = "jlid 不能为空")
+ @Size(max = 64, message = "jlid 长度不能超过 64")
+ private String jlid;
+
+ @NotBlank(message = "cc 不能为空")
+ @Size(max = 32, message = "cc 长度不能超过 32")
+ private String cc;
+
+ @NotNull(message = "kcsj 不能为空")
+ private Date kcsj;
+
+ @NotNull(message = "jcsj 不能为空")
+ private Date jcsj;
+
+ @NotNull(message = "lysj 不能为空")
+ private Date lysj;
+
+ private String jch;
+ private String qd;
+ private String zd;
+ private String cclx;
+ private String bz;
+
+ /** 只读保护:非空即视为试图改平台快照 */
+ private Long sourceVersion;
+ private Date createTime;
+
+ public String getJlid() { return jlid; }
+ public void setJlid(String jlid) { this.jlid = jlid; }
+ public String getCc() { return cc; }
+ public void setCc(String cc) { this.cc = cc; }
+ public Date getKcsj() { return kcsj; }
+ public void setKcsj(Date kcsj) { this.kcsj = kcsj; }
+ public Date getJcsj() { return jcsj; }
+ public void setJcsj(Date jcsj) { this.jcsj = jcsj; }
+ public Date getLysj() { return lysj; }
+ public void setLysj(Date lysj) { this.lysj = lysj; }
+ public String getJch() { return jch; }
+ public void setJch(String jch) { this.jch = jch; }
+ public String getQd() { return qd; }
+ public void setQd(String qd) { this.qd = qd; }
+ public String getZd() { return zd; }
+ public void setZd(String zd) { this.zd = zd; }
+ public String getCclx() { return cclx; }
+ public void setCclx(String cclx) { this.cclx = cclx; }
+ public String getBz() { return bz; }
+ public void setBz(String bz) { this.bz = bz; }
+ public Long getSourceVersion() { return sourceVersion; }
+ public void setSourceVersion(Long sourceVersion) { this.sourceVersion = sourceVersion; }
+ public Date getCreateTime() { return createTime; }
+ public void setCreateTime(Date createTime) { this.createTime = createTime; }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteRecalc.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteRecalc.java
new file mode 100644
index 0000000..5d23687
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteRecalc.java
@@ -0,0 +1,37 @@
+package com.ruoyi.apt.master.domain;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 交路本地复算校验结果(不落库,表 4-5 #7)。
+ * computedJcsj 为公寓复算权威值(D8'),diffMs 为与平台基线 jcsj 的偏差。
+ */
+public class AptRouteRecalc
+{
+ private Long routeId;
+ private String jlid;
+ private Date baseDate;
+ private Date computedJcsj;
+ private Date computedFjsj;
+ private Long ruleVersion;
+ private Long diffMs;
+ private final List warnings = new ArrayList<>();
+
+ public Long getRouteId() { return routeId; }
+ public void setRouteId(Long routeId) { this.routeId = routeId; }
+ public String getJlid() { return jlid; }
+ public void setJlid(String jlid) { this.jlid = jlid; }
+ public Date getBaseDate() { return baseDate; }
+ public void setBaseDate(Date baseDate) { this.baseDate = baseDate; }
+ public Date getComputedJcsj() { return computedJcsj; }
+ public void setComputedJcsj(Date computedJcsj) { this.computedJcsj = computedJcsj; }
+ public Date getComputedFjsj() { return computedFjsj; }
+ public void setComputedFjsj(Date computedFjsj) { this.computedFjsj = computedFjsj; }
+ public Long getRuleVersion() { return ruleVersion; }
+ public void setRuleVersion(Long ruleVersion) { this.ruleVersion = ruleVersion; }
+ public Long getDiffMs() { return diffMs; }
+ public void setDiffMs(Long diffMs) { this.diffMs = diffMs; }
+ public List getWarnings() { return warnings; }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteStatusCommand.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteStatusCommand.java
new file mode 100644
index 0000000..d57766a
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/domain/AptRouteStatusCommand.java
@@ -0,0 +1,20 @@
+package com.ruoyi.apt.master.domain;
+
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+
+/** 交路启停入参:reason 必填并落 duty_operation_log(表 4-5 校验规则 4)。 */
+public class AptRouteStatusCommand
+{
+ @NotNull(message = "enabled 不能为空")
+ private Boolean enabled;
+
+ @NotNull(message = "reason 不能为空")
+ @Size(min = 1, max = 512, message = "reason 长度必须为 1~512")
+ private String reason;
+
+ public Boolean getEnabled() { return enabled; }
+ public void setEnabled(Boolean enabled) { this.enabled = enabled; }
+ public String getReason() { return reason; }
+ public void setReason(String reason) { this.reason = reason; }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/mapper/AptMasterVersionMapper.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/mapper/AptMasterVersionMapper.java
new file mode 100644
index 0000000..39d9506
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/mapper/AptMasterVersionMapper.java
@@ -0,0 +1,21 @@
+package com.ruoyi.apt.master.mapper;
+
+import java.util.List;
+import com.ruoyi.apt.master.domain.AptMasterVersion;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 主数据快照版本只读访问(IT-2 / 任务 5)。
+ *
+ * 只读:业务模块不得写 apt_master_version(公寓端/03 表 3-1);
+ * 每条 SQL 必带 gyid(AptMapperGyidArchTest 断言)。
+ *
+ * @author apt-rev01
+ */
+public interface AptMasterVersionMapper
+{
+ List selectVersions(@Param("gyid") String gyid);
+
+ AptMasterVersion selectVersionByDomain(@Param("gyid") String gyid,
+ @Param("domain") String domain);
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/mapper/AptRouteMapper.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/mapper/AptRouteMapper.java
new file mode 100644
index 0000000..f0f980a
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/mapper/AptRouteMapper.java
@@ -0,0 +1,40 @@
+package com.ruoyi.apt.master.mapper;
+
+import java.util.List;
+import java.util.Map;
+import com.ruoyi.apt.master.domain.AptRoute;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 交路主数据访问(IT-2 / M1)。
+ * 硬约束:每条 SQL 必须带 gyid = #{gyid}(AptMapperGyidArchTest 断言)。
+ *
+ * @author apt-rev01
+ */
+public interface AptRouteMapper
+{
+ List selectRouteList(@Param("gyid") String gyid,
+ @Param("cc") String cc,
+ @Param("jlid") String jlid,
+ @Param("state") String state);
+
+ AptRoute selectRouteById(@Param("gyid") String gyid, @Param("id") Long id);
+
+ List selectRouteOptions(@Param("gyid") String gyid, @Param("keyword") String keyword);
+
+ int countByJlid(@Param("gyid") String gyid, @Param("jlid") String jlid,
+ @Param("excludeId") Long excludeId);
+
+ int insertRoute(AptRoute route);
+
+ int updateRoute(AptRoute route);
+
+ int updateRouteStatus(@Param("gyid") String gyid, @Param("id") Long id,
+ @Param("state") String state, @Param("stopReason") String stopReason);
+
+ /** 当前有效的叫乘提前量规则(CALL_TIME),返回 ruleVersion / ruleBody */
+ Map selectCallTimeRule(@Param("gyid") String gyid);
+
+ /** 指定日期是否为节假日 */
+ int countHoliday(@Param("gyid") String gyid, @Param("holidayDate") String holidayDate);
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/AptMasterVersionServiceImpl.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/AptMasterVersionServiceImpl.java
new file mode 100644
index 0000000..3f0c53d
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/AptMasterVersionServiceImpl.java
@@ -0,0 +1,96 @@
+package com.ruoyi.apt.master.service;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import com.ruoyi.apt.common.AptContext;
+import com.ruoyi.apt.common.AptErrorCode;
+import com.ruoyi.apt.common.AptException;
+import com.ruoyi.apt.master.domain.AptMasterVersion;
+import com.ruoyi.apt.master.mapper.AptMasterVersionMapper;
+import org.springframework.stereotype.Service;
+
+/**
+ * 主数据快照版本只读服务(IT-2 / 任务 5,公寓端/09 表 9-16)。
+ *
+ * 把 source='LOCAL' 显式标成「非平台下发」,供页面按 R-03 展示;
+ * 域白名单与 apt_master_version 的 6 个 Mock 域一致。
+ *
+ * @author apt-rev01
+ */
+@Service
+public class AptMasterVersionServiceImpl implements IAptMasterVersionService
+{
+ private static final Set DOMAINS = Set.of(
+ "APARTMENT", "ROOM", "PERSON", "ROUTE", "RULE", "HOLIDAY");
+
+ private final AptMasterVersionMapper versionMapper;
+
+ public AptMasterVersionServiceImpl(AptMasterVersionMapper versionMapper)
+ {
+ this.versionMapper = versionMapper;
+ }
+
+ @Override
+ public List list()
+ {
+ List versions = versionMapper.selectVersions(gyid());
+ if (versions == null)
+ {
+ return List.of();
+ }
+ versions.forEach(this::markSource);
+ return versions;
+ }
+
+ @Override
+ public AptMasterVersion detail(String domain)
+ {
+ if (domain == null || domain.isBlank())
+ {
+ throw missing("domain");
+ }
+ String key = domain.trim().toUpperCase(Locale.ROOT);
+ if (!DOMAINS.contains(key))
+ {
+ throw invalid("domain 仅允许 " + String.join("/", DOMAINS));
+ }
+ AptMasterVersion version = versionMapper.selectVersionByDomain(gyid(), key);
+ if (version == null)
+ {
+ throw new AptException(AptErrorCode.NOT_FOUND, "MASTER_VERSION_NOT_FOUND",
+ "该域尚无主数据版本记录");
+ }
+ markSource(version);
+ return version;
+ }
+
+ @Override
+ public void resync(String masterType)
+ {
+ throw new AptException(AptErrorCode.FEATURE_DISABLED, "MASTER_RESYNC_DISABLED",
+ "平台下行未就绪,本期不开放主数据全量重拉(表 9-16 不允许假成功)");
+ }
+
+ private void markSource(AptMasterVersion version)
+ {
+ boolean local = "LOCAL".equalsIgnoreCase(version.getSource());
+ version.setLocalMock(local);
+ version.setSourceLabel(local ? "非平台下发" : "平台下发");
+ }
+
+ private AptException missing(String field)
+ {
+ return new AptException(AptErrorCode.BAD_REQUEST, "PARAM_INVALID", "缺少必填字段: " + field);
+ }
+
+ private AptException invalid(String message)
+ {
+ return new AptException(AptErrorCode.BAD_REQUEST, "PARAM_INVALID", message);
+ }
+
+ private String gyid()
+ {
+ return AptContext.getGyId();
+ }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/AptRouteServiceImpl.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/AptRouteServiceImpl.java
new file mode 100644
index 0000000..55d5787
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/AptRouteServiceImpl.java
@@ -0,0 +1,279 @@
+package com.ruoyi.apt.master.service;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import com.ruoyi.apt.common.AptContext;
+import com.ruoyi.apt.common.AptErrorCode;
+import com.ruoyi.apt.common.AptException;
+import com.ruoyi.apt.master.domain.AptRoute;
+import com.ruoyi.apt.master.domain.AptRouteCommand;
+import com.ruoyi.apt.master.domain.AptRouteRecalc;
+import com.ruoyi.apt.master.domain.AptRouteStatusCommand;
+import com.ruoyi.apt.master.mapper.AptRouteMapper;
+import org.springframework.stereotype.Service;
+
+/**
+ * 交路主数据领域服务(IT-2 / M1)。
+ *
+ * 校验口径(公寓端/04 表 4-5):必填 cc/kcsj/jcsj/lysj、时序 jcsj<kcsj 与
+ * lysj<=kcsj、(gyid,jlid) 唯一、停用原因必填、快照字段只读。
+ * gyid 恒取自 AptContext,服务签名不接受任何客户端传入的公寓标识。
+ *
+ * @author apt-rev01
+ */
+@Service
+public class AptRouteServiceImpl implements IAptRouteService
+{
+ private static final long MINUTE_MS = 60_000L;
+ private static final int DEFAULT_ADVANCE_MINUTES = 120;
+ private static final int DEFAULT_HOLIDAY_ADVANCE_MINUTES = 180;
+ private static final int DEFAULT_WAKE_MINUTES = 30;
+ private static final long DIFF_WARN_THRESHOLD_MS = 5 * MINUTE_MS;
+
+ private final AptRouteMapper routeMapper;
+
+ public AptRouteServiceImpl(AptRouteMapper routeMapper)
+ {
+ this.routeMapper = routeMapper;
+ }
+
+ @Override
+ public List list(String cc, String jlid, String state)
+ {
+ return routeMapper.selectRouteList(gyid(), cc, jlid, state);
+ }
+
+ @Override
+ public AptRoute detail(Long id)
+ {
+ return mustExist(id);
+ }
+
+ @Override
+ public List options(String keyword)
+ {
+ return routeMapper.selectRouteOptions(gyid(), keyword);
+ }
+
+ @Override
+ public Long add(AptRouteCommand command)
+ {
+ validate(command, null);
+ AptRoute route = new AptRoute();
+ fill(route, command);
+ route.setGyid(gyid());
+ route.setState("ACTIVE");
+ route.setSourceVersion(0L);
+ routeMapper.insertRoute(route);
+ return route.getId();
+ }
+
+ @Override
+ public void edit(Long id, AptRouteCommand command)
+ {
+ mustExist(id);
+ validate(command, id);
+ AptRoute route = new AptRoute();
+ route.setId(id);
+ fill(route, command);
+ route.setGyid(gyid());
+ routeMapper.updateRoute(route);
+ }
+
+ @Override
+ public void changeStatus(Long id, AptRouteStatusCommand command)
+ {
+ mustExist(id);
+ if (command == null || command.getEnabled() == null)
+ {
+ throw missing("enabled");
+ }
+ String reason = command.getReason();
+ if (reason == null || reason.isBlank())
+ {
+ throw missing("reason");
+ }
+ if (reason.length() > 512)
+ {
+ throw invalid("reason 长度不能超过 512");
+ }
+ boolean enable = command.getEnabled();
+ routeMapper.updateRouteStatus(gyid(), id,
+ enable ? "ACTIVE" : "STOPPED", enable ? null : reason.trim());
+ }
+
+ @Override
+ public AptRouteRecalc recalcCheck(Long id, Date baseDate)
+ {
+ AptRoute route = mustExist(id);
+ Date effectiveBase = baseDate == null ? new Date() : baseDate;
+ String day = new SimpleDateFormat("yyyy-MM-dd").format(effectiveBase);
+
+ Map rule = routeMapper.selectCallTimeRule(gyid());
+ boolean holiday = routeMapper.countHoliday(gyid(), day) > 0;
+ String body = rule == null ? null : stringValue(rule.get("ruleBody"));
+ int advance = readIntRule(body,
+ holiday ? "holidayAdvanceMinutes" : "advanceMinutes",
+ holiday ? DEFAULT_HOLIDAY_ADVANCE_MINUTES : DEFAULT_ADVANCE_MINUTES);
+ int wake = readIntRule(body, "wakeMinutes", DEFAULT_WAKE_MINUTES);
+
+ AptRouteRecalc result = new AptRouteRecalc();
+ result.setRouteId(id);
+ result.setJlid(route.getJlid());
+ result.setBaseDate(effectiveBase);
+ result.setRuleVersion(rule == null ? null : longValue(rule.get("ruleVersion")));
+ if (rule == null)
+ {
+ result.getWarnings().add("CALL_TIME 规则缺失,采用默认提前量");
+ }
+ if (holiday)
+ {
+ result.getWarnings().add("已按节假日口径调整提前量:" + advance + " 分钟");
+ }
+ if (route.getKcsj() == null)
+ {
+ result.getWarnings().add("kcsj 缺失,无法复算");
+ return result;
+ }
+
+ long computed = route.getKcsj().getTime() - advance * MINUTE_MS;
+ result.setComputedJcsj(new Date(computed));
+ result.setComputedFjsj(new Date(computed - wake * MINUTE_MS));
+ if (route.getJcsj() == null)
+ {
+ result.getWarnings().add("jcsj 基线缺失,diffMs 为空");
+ }
+ else
+ {
+ long diff = computed - route.getJcsj().getTime();
+ result.setDiffMs(diff);
+ if (Math.abs(diff) > DIFF_WARN_THRESHOLD_MS)
+ {
+ result.getWarnings().add("复算值与 jcsj 基线偏差超过 5 分钟");
+ }
+ }
+ return result;
+ }
+
+ private void validate(AptRouteCommand command, Long excludeId)
+ {
+ if (command == null)
+ {
+ throw missing("请求体");
+ }
+ if (command.getSourceVersion() != null || command.getCreateTime() != null)
+ {
+ throw new AptException(AptErrorCode.SNAPSHOT_READONLY, "SNAPSHOT_READONLY",
+ "sourceVersion / createTime 为平台快照只读字段");
+ }
+ requireText(command.getJlid(), "jlid");
+ requireText(command.getCc(), "cc");
+ if (command.getCc().length() > 32)
+ {
+ throw invalid("cc 长度不能超过 32");
+ }
+ requireDate(command.getKcsj(), "kcsj");
+ requireDate(command.getJcsj(), "jcsj");
+ requireDate(command.getLysj(), "lysj");
+ if (!command.getJcsj().before(command.getKcsj()))
+ {
+ throw invalid("jcsj 必须早于 kcsj");
+ }
+ if (command.getLysj().after(command.getKcsj()))
+ {
+ throw invalid("lysj 不能晚于 kcsj");
+ }
+ if (routeMapper.countByJlid(gyid(), command.getJlid(), excludeId) > 0)
+ {
+ throw new AptException(AptErrorCode.DUPLICATE_KEY, "DUPLICATE_KEY",
+ "jlid 已存在:" + command.getJlid());
+ }
+ }
+
+ private AptRoute mustExist(Long id)
+ {
+ if (id == null)
+ {
+ throw missing("id");
+ }
+ AptRoute route = routeMapper.selectRouteById(gyid(), id);
+ if (route == null)
+ {
+ throw new AptException(AptErrorCode.NOT_FOUND, "ROUTE_NOT_FOUND", "交路不存在");
+ }
+ return route;
+ }
+
+ private void fill(AptRoute route, AptRouteCommand command)
+ {
+ route.setJlid(command.getJlid());
+ route.setCc(command.getCc());
+ route.setKcsj(command.getKcsj());
+ route.setJcsj(command.getJcsj());
+ route.setLysj(command.getLysj());
+ route.setJch(command.getJch());
+ route.setQd(command.getQd());
+ route.setZd(command.getZd());
+ route.setCclx(command.getCclx());
+ route.setBz(command.getBz());
+ }
+
+ private void requireText(String value, String field)
+ {
+ if (value == null || value.isBlank())
+ {
+ throw missing(field);
+ }
+ }
+
+ private void requireDate(Date value, String field)
+ {
+ if (value == null)
+ {
+ throw missing(field);
+ }
+ }
+
+ private AptException missing(String field)
+ {
+ return new AptException(AptErrorCode.BAD_REQUEST, "PARAM_INVALID", "缺少必填字段: " + field);
+ }
+
+ private AptException invalid(String message)
+ {
+ return new AptException(AptErrorCode.BAD_REQUEST, "PARAM_INVALID", message);
+ }
+
+ private int readIntRule(String body, String key, int defaultValue)
+ {
+ if (body == null)
+ {
+ return defaultValue;
+ }
+ Matcher matcher = Pattern.compile("\"" + key + "\"\\s*:\\s*(\\d+)").matcher(body);
+ return matcher.find() ? Integer.parseInt(matcher.group(1)) : defaultValue;
+ }
+
+ private String stringValue(Object value)
+ {
+ return value == null ? null : value.toString();
+ }
+
+ private Long longValue(Object value)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+ return value instanceof Number ? ((Number) value).longValue() : Long.valueOf(value.toString());
+ }
+
+ private String gyid()
+ {
+ return AptContext.getGyId();
+ }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/IAptMasterVersionService.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/IAptMasterVersionService.java
new file mode 100644
index 0000000..8ab94f2
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/IAptMasterVersionService.java
@@ -0,0 +1,21 @@
+package com.ruoyi.apt.master.service;
+
+import java.util.List;
+import com.ruoyi.apt.master.domain.AptMasterVersion;
+
+/**
+ * 主数据快照版本只读服务(IT-2 / 任务 5)。
+ *
+ * 服务签名不接受客户端 gyid;resync 在平台未就绪前恒拒绝,不允许假成功。
+ *
+ * @author apt-rev01
+ */
+public interface IAptMasterVersionService
+{
+ List list();
+
+ AptMasterVersion detail(String domain);
+
+ /** 表 4-17 #2:本期不开放全量重拉,调用即抛 40940 */
+ void resync(String masterType);
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/IAptRouteService.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/IAptRouteService.java
new file mode 100644
index 0000000..dd6aacf
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/master/service/IAptRouteService.java
@@ -0,0 +1,32 @@
+package com.ruoyi.apt.master.service;
+
+import java.util.Date;
+import java.util.List;
+import com.ruoyi.apt.master.domain.AptRoute;
+import com.ruoyi.apt.master.domain.AptRouteCommand;
+import com.ruoyi.apt.master.domain.AptRouteRecalc;
+import com.ruoyi.apt.master.domain.AptRouteStatusCommand;
+
+/**
+ * 交路主数据领域服务(IT-2 / M1,公寓端/04 表 4-5)。
+ * gyid 一律由 AptContext 注入,服务签名不接受该参数。
+ *
+ * @author apt-rev01
+ */
+public interface IAptRouteService
+{
+ List list(String cc, String jlid, String state);
+
+ AptRoute detail(Long id);
+
+ List options(String keyword);
+
+ Long add(AptRouteCommand command);
+
+ void edit(Long id, AptRouteCommand command);
+
+ void changeStatus(Long id, AptRouteStatusCommand command);
+
+ /** 本地复算校验,不落库 */
+ AptRouteRecalc recalcCheck(Long id, Date baseDate);
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-admin/src/main/java/com/ruoyi/apt/ops/controller/AptConnectivityController.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/ops/controller/AptConnectivityController.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-admin/src/main/java/com/ruoyi/apt/ops/controller/AptConnectivityController.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/ops/controller/AptConnectivityController.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-system/src/main/java/com/ruoyi/apt/ops/domain/AptOperationLog.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/ops/domain/AptOperationLog.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-system/src/main/java/com/ruoyi/apt/ops/domain/AptOperationLog.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/ops/domain/AptOperationLog.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-system/src/main/java/com/ruoyi/apt/ops/mapper/AptAuditMapper.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/ops/mapper/AptAuditMapper.java
similarity index 100%
rename from dev/RuoYi-Vue-master/ruoyi-system/src/main/java/com/ruoyi/apt/ops/mapper/AptAuditMapper.java
rename to dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/ops/mapper/AptAuditMapper.java
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/controller/AptRoomController.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/controller/AptRoomController.java
new file mode 100644
index 0000000..f25963d
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/controller/AptRoomController.java
@@ -0,0 +1,154 @@
+package com.ruoyi.apt.stay.controller;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.apt.common.AptContext;
+import com.ruoyi.apt.common.AptErrorCode;
+import com.ruoyi.apt.common.AptException;
+import com.ruoyi.apt.common.audit.AptAudit;
+import com.ruoyi.apt.common.page.AptTableDataInfo;
+import com.ruoyi.apt.framework.audit.AptAuditWriter;
+import com.ruoyi.apt.framework.web.AptBaseController;
+import com.ruoyi.apt.ops.domain.AptOperationLog;
+import com.ruoyi.apt.stay.domain.AptRoomCommand;
+import com.ruoyi.apt.stay.domain.AptRoomStatusCommand;
+import com.ruoyi.apt.stay.domain.AptRoomStatusResult;
+import com.ruoyi.apt.stay.service.IAptRoomService;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.SecurityUtils;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 房间管理(IT-2 / M2,公寓端/04 表 4-7 接口 1~6)。
+ *
+ * 楼栋字段未启用(G-01)时 building 降级为楼层前缀匹配并在 warnings[] 标注;
+ * 停用/恢复原因显式写 duty_operation_log;新增房间在 G-01/G-04 签字前恒返回 40940。
+ *
+ * @author apt-rev01
+ */
+@RestController
+@RequestMapping(value = "/apt/room", produces = "application/json;charset=UTF-8")
+public class AptRoomController extends AptBaseController
+{
+ private final IAptRoomService roomService;
+ private final AptAuditWriter auditWriter;
+
+ public AptRoomController(IAptRoomService roomService, AptAuditWriter auditWriter)
+ {
+ this.roomService = roomService;
+ this.auditWriter = auditWriter;
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:room:list')")
+ @AptAudit(module = "STAY", action = "ROOM_LIST")
+ @GetMapping("/list")
+ public AptTableDataInfo list(
+ @RequestParam(required = false) String building,
+ @RequestParam(required = false) String roomFloor,
+ @RequestParam(required = false) String roomNo,
+ @RequestParam(required = false) List roomStatus,
+ @RequestParam(required = false) String roomType)
+ {
+ startAptPage();
+ AptTableDataInfo data = getAptDataTable(
+ roomService.list(building, roomFloor, roomNo, roomStatus, roomType));
+ if (building != null && !building.isBlank())
+ {
+ data.getWarnings().add("楼栋字段未启用(G-01),已降级为楼层前缀匹配");
+ }
+ return data;
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:room:list')")
+ @GetMapping("/status-summary")
+ public AjaxResult statusSummary()
+ {
+ return AjaxResult.success(roomService.statusSummary());
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:room:query')")
+ @AptAudit(module = "STAY", action = "ROOM_DETAIL")
+ @GetMapping("/{id}")
+ public AjaxResult detail(@PathVariable Long id)
+ {
+ return AjaxResult.success(roomService.detail(id));
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:room:edit')")
+ @AptAudit(module = "STAY", action = "ROOM_EDIT")
+ @PutMapping("/{id}")
+ public AjaxResult edit(@PathVariable Long id, @RequestBody AptRoomCommand command)
+ {
+ roomService.edit(id, command);
+ return AjaxResult.success();
+ }
+
+ @PreAuthorize("@ss.hasPermi('apt:room:changeStatus')")
+ @PutMapping("/{id}/status")
+ public AjaxResult changeStatus(@PathVariable Long id,
+ @Valid @RequestBody AptRoomStatusCommand command)
+ {
+ AptRoomStatusResult result = roomService.changeStatus(id, command);
+ writeStatusAudit(command, result, id);
+ return AjaxResult.success(result);
+ }
+
+ /** 表 4-7 #6:G-01/G-04 未签字前不开放本地新增,权限点 3014 亦保持置灰 */
+ @PreAuthorize("@ss.hasPermi('apt:room:add')")
+ @PostMapping
+ public AjaxResult add()
+ {
+ throw new AptException(AptErrorCode.FEATURE_DISABLED, "ROOM_ADD_DISABLED",
+ "楼栋与房间新增口径(G-01/G-04)未定稿,本期不开放新增房间");
+ }
+
+ /** 停用/恢复原因必须进审计 detail(表 7-3 M2「恢复触发重算」取证) */
+ private void writeStatusAudit(AptRoomStatusCommand command, AptRoomStatusResult result, Long roomId)
+ {
+ Map detail = new LinkedHashMap<>();
+ detail.put("reason", command.getReason());
+ detail.put("commandId", command.getCommandId());
+ detail.put("roomId", roomId);
+ detail.put("roomStatus", result.getRoomStatus());
+ detail.put("stateVersion", result.getStateVersion());
+ detail.put("duplicated", result.isDuplicated());
+ detail.put("status", "SUCCESS");
+ detail.put("traceId", AptContext.getTraceId());
+
+ AptOperationLog entry = new AptOperationLog();
+ entry.setLogId(UUID.randomUUID().toString().replace("-", ""));
+ entry.setGyId(AptContext.getGyIdOrNull());
+ entry.setOperatorRef(operatorRef());
+ entry.setModule("STAY");
+ entry.setAction("ROOM_" + command.getAction().trim().toUpperCase());
+ entry.setDetail(JSON.toJSONString(detail));
+ entry.setTraceId(AptContext.getTraceId());
+ entry.setOccurredAt(new Date());
+ auditWriter.write(entry);
+ }
+
+ private String operatorRef()
+ {
+ try
+ {
+ return SecurityUtils.getLoginUser().getUsername();
+ }
+ catch (Exception e)
+ {
+ return "ANONYMOUS";
+ }
+ }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoom.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoom.java
new file mode 100644
index 0000000..9d36dc4
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoom.java
@@ -0,0 +1,74 @@
+package com.ruoyi.apt.stay.domain;
+
+/**
+ * 房间档案 + 房态(IT-2 / M2,公寓端/04 表 4-7)。
+ *
+ * gy_rooms 是运行时事实,apt_master_room 是平台下发基线(只读)。
+ * room_status 只能由 apt-stay 房态重算服务写入,管理端只提交停用/恢复命令。
+ *
+ * @author apt-rev01
+ */
+public class AptRoom
+{
+ private Long id;
+ private String gyid;
+ private String roomId;
+ private String roomNo;
+ private String roomFloor;
+ private String roomDir;
+ private String roomFace;
+ private String bedNumbers;
+ private String roomStatus;
+ private String roomStatusLabel;
+ private Integer livePerson;
+ private String personName;
+ private String roomType;
+ private String roomDept;
+ private Integer roomOrder;
+ private Integer bedCount;
+ private Long stateVersion;
+ private String disabledReason;
+ private Long sourceVersion;
+ private Integer inPersonCount;
+
+ public Long getId() { return id; }
+ public void setId(Long id) { this.id = id; }
+ public String getGyid() { return gyid; }
+ public void setGyid(String gyid) { this.gyid = gyid; }
+ public String getRoomId() { return roomId; }
+ public void setRoomId(String roomId) { this.roomId = roomId; }
+ public String getRoomNo() { return roomNo; }
+ public void setRoomNo(String roomNo) { this.roomNo = roomNo; }
+ public String getRoomFloor() { return roomFloor; }
+ public void setRoomFloor(String roomFloor) { this.roomFloor = roomFloor; }
+ public String getRoomDir() { return roomDir; }
+ public void setRoomDir(String roomDir) { this.roomDir = roomDir; }
+ public String getRoomFace() { return roomFace; }
+ public void setRoomFace(String roomFace) { this.roomFace = roomFace; }
+ public String getBedNumbers() { return bedNumbers; }
+ public void setBedNumbers(String bedNumbers) { this.bedNumbers = bedNumbers; }
+ public String getRoomStatus() { return roomStatus; }
+ public void setRoomStatus(String roomStatus) { this.roomStatus = roomStatus; }
+ public String getRoomStatusLabel() { return roomStatusLabel; }
+ public void setRoomStatusLabel(String roomStatusLabel) { this.roomStatusLabel = roomStatusLabel; }
+ public Integer getLivePerson() { return livePerson; }
+ public void setLivePerson(Integer livePerson) { this.livePerson = livePerson; }
+ public String getPersonName() { return personName; }
+ public void setPersonName(String personName) { this.personName = personName; }
+ public String getRoomType() { return roomType; }
+ public void setRoomType(String roomType) { this.roomType = roomType; }
+ public String getRoomDept() { return roomDept; }
+ public void setRoomDept(String roomDept) { this.roomDept = roomDept; }
+ public Integer getRoomOrder() { return roomOrder; }
+ public void setRoomOrder(Integer roomOrder) { this.roomOrder = roomOrder; }
+ public Integer getBedCount() { return bedCount; }
+ public void setBedCount(Integer bedCount) { this.bedCount = bedCount; }
+ public Long getStateVersion() { return stateVersion; }
+ public void setStateVersion(Long stateVersion) { this.stateVersion = stateVersion; }
+ public String getDisabledReason() { return disabledReason; }
+ public void setDisabledReason(String disabledReason) { this.disabledReason = disabledReason; }
+ public Long getSourceVersion() { return sourceVersion; }
+ public void setSourceVersion(Long sourceVersion) { this.sourceVersion = sourceVersion; }
+ public Integer getInPersonCount() { return inPersonCount; }
+ public void setInPersonCount(Integer inPersonCount) { this.inPersonCount = inPersonCount; }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomCommand.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomCommand.java
new file mode 100644
index 0000000..16eda2b
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomCommand.java
@@ -0,0 +1,40 @@
+package com.ruoyi.apt.stay.domain;
+
+/**
+ * 房间本地属性编辑入参(表 4-7 #4)。
+ *
+ * bedNumbers / roomFloor / sourceVersion 属平台快照字段,
+ * 入参携带即返回 40904 SNAPSHOT_READONLY(表 4-7 实现约束 4)。
+ *
+ * @author apt-rev01
+ */
+public class AptRoomCommand
+{
+ private String roomType;
+ private String roomDept;
+ private Integer roomOrder;
+ private String roomDir;
+ private String roomFace;
+
+ /** 只读保护字段 */
+ private String bedNumbers;
+ private String roomFloor;
+ private Long sourceVersion;
+
+ public String getRoomType() { return roomType; }
+ public void setRoomType(String roomType) { this.roomType = roomType; }
+ public String getRoomDept() { return roomDept; }
+ public void setRoomDept(String roomDept) { this.roomDept = roomDept; }
+ public Integer getRoomOrder() { return roomOrder; }
+ public void setRoomOrder(Integer roomOrder) { this.roomOrder = roomOrder; }
+ public String getRoomDir() { return roomDir; }
+ public void setRoomDir(String roomDir) { this.roomDir = roomDir; }
+ public String getRoomFace() { return roomFace; }
+ public void setRoomFace(String roomFace) { this.roomFace = roomFace; }
+ public String getBedNumbers() { return bedNumbers; }
+ public void setBedNumbers(String bedNumbers) { this.bedNumbers = bedNumbers; }
+ public String getRoomFloor() { return roomFloor; }
+ public void setRoomFloor(String roomFloor) { this.roomFloor = roomFloor; }
+ public Long getSourceVersion() { return sourceVersion; }
+ public void setSourceVersion(Long sourceVersion) { this.sourceVersion = sourceVersion; }
+}
diff --git a/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomDetail.java b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomDetail.java
new file mode 100644
index 0000000..a2b840a
--- /dev/null
+++ b/dev/RuoYi-Vue-master/ruoyi-apt/src/main/java/com/ruoyi/apt/stay/domain/AptRoomDetail.java
@@ -0,0 +1,17 @@
+package com.ruoyi.apt.stay.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/** 房间详情:房间全字段 + 分机绑定 + 在寓人员 + 最近保洁任务(表 4-7 #2)。 */
+public class AptRoomDetail extends AptRoom
+{
+ private final List