# Design: WVP-Platform 迁移技术设计 **Change ID**: `add-video-module-wvp-migration` --- ## 1. 目录结构设计 ``` viewsh-module-video/ ├── viewsh-module-video-api/ # 契约层 │ └── com.viewsh.module.video/ │ ├── api/ # Feign RPC 接口(供其他模块调用) │ └── enums/ # ApiConstants, ErrorCodeConstants │ └── viewsh-module-video-server/ # 业务层 └── com.viewsh.module.video/ ├── VideoServerApplication.java # 启动类 │ ├── gb28181/ # ← WVP gb28181/ 原样保留内部结构 │ ├── SipLayer.java # SIP 协议栈初始化 │ ├── auth/ # Digest 认证 │ ├── bean/ # → 改名为 DO,加注解 │ ├── controller/admin/ # → 移到 admin 包下 │ ├── dao/ # → Mapper 改继承 BaseMapperX │ ├── event/ # 事件驱动 │ ├── service/ # 核心服务 │ ├── session/ # SIP 会话管理 │ ├── task/ # 设备心跳、订阅任务 │ ├── transmit/ # SIP 信令处理(保持不动) │ └── utils/ # GB28181 工具类 │ ├── media/ # ← WVP media/ 原样保留 │ ├── bean/ # MediaServer, StreamInfo │ ├── event/ # 媒体事件 │ ├── service/ # IMediaServerService │ ├── zlm/ # ZLMediaKit 集成 │ │ └── ZLMHttpHookListener.java # Hook @RestController │ └── abl/ # ABL 适配器 │ ├── aiot/ # ← WVP aiot/ AI 模块 │ ├── bean/ │ ├── controller/admin/ │ ├── dao/ │ └── service/ │ ├── storager/ # ← Redis 缓存存储(RedisCatchStorageImpl) │ ├── dao/ # Mapper(MediaServer/RecordPlan/CloudRecord 等) │ └── dto/ # DTO │ ├── streamProxy/ # ← 拉流代理 ├── streamPush/ # ← 推流管理 │ ├── vmanager/ # ← 管理功能 │ ├── cloudRecord/ │ ├── recordPlan/ │ ├── rtp/ │ └── server/ │ ├── service/ # ← 通用服务 ├── common/ # ← 公共模块 ├── utils/ # ← 工具类 │ └── framework/ # 框架配置层 ├── sip/ │ └── SipConfig.java ├── media/ │ └── MediaConfig.java ├── redis/ │ ├── RedisTemplateConfig.java │ ├── RedisMsgListenConfig.java │ └── RedisRpcConfig.java ├── websocket/ │ └── WebSocketConfig.java ├── security/config/ │ └── SecurityConfiguration.java └── config/ ├── UserSetting.java ├── ThreadPoolTaskConfig.java ├── ScheduleConfig.java └── CivilCodeFileConf.java ``` ## 2. 包名映射表 ``` com.genersoft.iot.vmp.gb28181.* → com.viewsh.module.video.gb28181.* com.genersoft.iot.vmp.media.* → com.viewsh.module.video.media.* com.genersoft.iot.vmp.aiot.* → com.viewsh.module.video.aiot.* com.genersoft.iot.vmp.streamProxy.* → com.viewsh.module.video.streamProxy.* com.genersoft.iot.vmp.streamPush.* → com.viewsh.module.video.streamPush.* com.genersoft.iot.vmp.vmanager.* → com.viewsh.module.video.vmanager.* com.genersoft.iot.vmp.service.* → com.viewsh.module.video.service.* com.genersoft.iot.vmp.common.* → com.viewsh.module.video.common.* com.genersoft.iot.vmp.utils.* → com.viewsh.module.video.utils.* com.genersoft.iot.vmp.conf.SipConfig → com.viewsh.module.video.framework.sip.SipConfig com.genersoft.iot.vmp.conf.MediaConfig → com.viewsh.module.video.framework.media.MediaConfig com.genersoft.iot.vmp.conf.UserSetting → com.viewsh.module.video.framework.config.UserSetting com.genersoft.iot.vmp.conf.redis.* → com.viewsh.module.video.framework.redis.* com.genersoft.iot.vmp.conf.websocket.* → com.viewsh.module.video.framework.websocket.* com.genersoft.iot.vmp.conf.ThreadPool* → com.viewsh.module.video.framework.config.* com.genersoft.iot.vmp.conf.Schedule* → com.viewsh.module.video.framework.config.* com.genersoft.iot.vmp.storager.* → com.viewsh.module.video.storager.* ``` ## 3. DO 改造设计 ### 3.1 DO 基类选择 直接使用框架标准基类,统一逻辑删除,不创建自定义 VideoBaseDO。 | 基类 | 适用表 | 提供字段 | |------|--------|---------| | **TenantBaseDO** | wvp_device, wvp_device_channel, wvp_device_alarm, wvp_device_mobile_position, wvp_platform, wvp_platform_channel, wvp_platform_group, wvp_platform_region, wvp_common_group, wvp_common_region, wvp_stream_proxy, wvp_stream_push, wvp_cloud_record, wvp_record_plan, wvp_record_plan_item, wvp_ai_* | tenantId, createTime, updateTime, creator, updater, deleted | | **BaseDO** | wvp_media_server | createTime, updateTime, creator, updater, deleted(无 tenantId) | ### 3.1.1 硬删除改逻辑删除 WVP 原有 39 处 `DELETE FROM` 硬删除,统一改为 MyBatis Plus 逻辑删除: ```java // WVP 原始(硬删除) @Delete("DELETE FROM wvp_device WHERE device_id = #{deviceId}") int delete(@Param("deviceId") String deviceId); // 改造后(逻辑删除 — 自动改写为 UPDATE SET deleted=1) // 直接用 BaseMapperX 内置方法 mapper.delete(new LambdaQueryWrapperX() .eq(DeviceDO::getDeviceId, deviceId)); ``` ### 3.1.2 唯一索引调整 逻辑删除后,被"删除"的记录仍然存在。如果表有唯一索引,再次插入相同数据会冲突。需要将 `deleted` 加入联合唯一索引: ```sql -- 改造前 UNIQUE INDEX uk_device_id (device_id) -- 改造后 UNIQUE INDEX uk_device_id (device_id, deleted) ``` 需要调整唯一索引的核心表: - `wvp_device` — `device_id` - `wvp_device_channel` — `device_db_id + channel_id` - `wvp_platform` — `server_gb_id` - `wvp_media_server` — `id`(业务主键) - `wvp_stream_proxy` — `app + stream` - `wvp_stream_push` — `app + stream` - `wvp_common_group` — `device_id` - `wvp_common_region` — `device_id` ### 3.2 改造模式 ```java // WVP 原始 @Data @Schema(description = "国标设备") public class Device { private int id; private String deviceId; private String name; private String createTime; // String 类型 private String updateTime; // String 类型 ... } // 改造后(业务表 — 多租户 + 逻辑删除) @Data @TableName("wvp_device") @EqualsAndHashCode(callSuper = true) public class DeviceDO extends TenantBaseDO { // TenantBaseDO 提供: tenantId, createTime, updateTime, creator, updater, deleted @TableId(type = IdType.AUTO) private Integer id; // 保持 Integer,不改 Long(降低改造风险) private String deviceId; private String name; // 删除原有的 createTime/updateTime(TenantBaseDO 提供) ... } // 改造后(共享表 — 无租户,有逻辑删除) @Data @TableName("wvp_media_server") @EqualsAndHashCode(callSuper = true) public class MediaServerDO extends BaseDO { // BaseDO 提供: createTime, updateTime, creator, updater, deleted // 无 tenantId @TableId(type = IdType.AUTO) private Integer id; ... } ``` ### 3.3 特殊处理 - **DeviceChannel**(86 字段):字段多但改造模式一致,批量处理 - **关联查询结果 DTO**:不继承 VideoBaseDO,保持为普通 POJO(因为是 JOIN 查询结果,不对应单表) - **WVP 的 `int id`**:保持 `Integer id`,不改 Long。数据库也保持 `INT`,等稳定后再逐步升级 ## 4. Mapper 改造设计 ### 4.1 简单 CRUD → BaseMapperX 内置方法 ```java // WVP 原始 @Select("SELECT * FROM wvp_media_server WHERE id = #{id}") MediaServer queryOne(String id); @Insert("INSERT INTO wvp_media_server (...) VALUES (...)") int add(MediaServer mediaServer); // 改造后 @Mapper public interface MediaServerMapper extends BaseMapperX { // 简单查询直接用继承方法 // selectById(), insert(), updateById() 等 default MediaServerDO selectByServerId(String id) { return selectOne(MediaServerDO::getId, id); } } ``` ### 4.2 复杂查询 → 保留 @Select 注解 ```java // 多表 JOIN、动态 SQL 保留注解方式 @Mapper public interface DeviceChannelMapper extends BaseMapperX { // 复杂 JOIN 保留 @Select @Select("") List selectWithDevice(@Param("query") String query); } ``` ### 4.3 分页改造 ```java // WVP 原始(PageHelper) PageHelper.startPage(page, count); List devices = deviceMapper.getDevices(query); return new PageInfo<>(devices); // 改造后(MyBatis Plus) Page page = new Page<>(pageNo, pageSize); IPage result = deviceMapper.selectPage(page, new LambdaQueryWrapperX() .likeIfPresent(DeviceDO::getName, query)); return new PageResult<>(result.getRecords(), result.getTotal()); ``` ## 5. 认证替换设计 ### 5.1 SecurityConfiguration 更新 ```java @Configuration("videoSecurityConfiguration") public class SecurityConfiguration { @Bean("videoAuthorizeRequestsCustomizer") public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { return new AuthorizeRequestsCustomizer() { @Override public void customize(...registry) { // 原有放行... // WVP Hook 回调(ZLM → WVP,无用户认证) registry.requestMatchers("/index/hook/**").permitAll(); // 设备快照公开访问 registry.requestMatchers("/video/device/query/snap/**").permitAll(); // SSE 推送 registry.requestMatchers("/video/sse/**").permitAll(); } }; } } ``` ### 5.2 用户获取替换 ```java // WVP: SecurityUtils.getUserInfo() 返回 LoginUser // ↓ 替换为 // viewsh: SecurityFrameworkUtils.getLoginUserId() 返回 Long ``` ## 6. 数据库设计 ### 6.1 独立数据库 `aiot-video` ```yaml # application-local.yaml spring: datasource: dynamic: datasource: master: url: jdbc:mysql://${MYSQL_HOST}:3306/aiot-video?... ``` ### 6.2 表结构改造规则 每张 WVP 表需要: 1. 主键 `id INT AUTO_INCREMENT` 保持不变(不改 BIGINT,降低风险) 2. 业务表新增 `tenant_id BIGINT NOT NULL DEFAULT 0`(多租户隔离,共享表除外) 3. 新增 `creator VARCHAR(64) DEFAULT ''` 4. 新增 `updater VARCHAR(64) DEFAULT ''` 5. 新增 `deleted BIT(1) NOT NULL DEFAULT 0`(逻辑删除) 6. `create_time` / `update_time` 保留(类型改为 `DATETIME DEFAULT CURRENT_TIMESTAMP`) 7. 含唯一索引的表,将 `deleted` 加入联合唯一索引(避免逻辑删除后重复插入冲突) 共享表(wvp_media_server)不加 `tenant_id`,需在 application.yaml 中配置: ```yaml viewsh: tenant: ignore-tables: - wvp_media_server ``` ## 7. RedisTemplate 隔离设计 ### 7.1 问题 viewsh 框架的 RedisTemplate 使用 Jackson 序列化(`RedisSerializer.json()`),WVP 的 RedisTemplate 使用 FastJSON2 序列化(`GenericFastJsonRedisSerializer`)。两者 Bean 名冲突会导致其他模块 Redis 崩溃。 ### 7.2 方案 ```java // WVP 原始 RedisTemplateConfig @Bean public RedisTemplate redisTemplate(...) { ... } // 改造后:独立 Bean 名称 @Configuration public class VideoRedisTemplateConfig { @Bean("videoRedisTemplate") public RedisTemplate videoRedisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(factory); // FastJSON2 序列化(WVP 内部使用) GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(); template.setKeySerializer(RedisSerializer.string()); template.setValueSerializer(serializer); template.setHashKeySerializer(RedisSerializer.string()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } } ``` ### 7.3 影响范围 WVP 内部所有注入 RedisTemplate 的地方(约 30+ 处)需要改为: ```java // 改造前 @Autowired private RedisTemplate redisTemplate; // 改造后 @Autowired @Qualifier("videoRedisTemplate") private RedisTemplate redisTemplate; ``` 主要涉及:`RedisRpcConfig`、`RedisCatchStorageImpl`、`RedisMsgListenConfig`、各 Listener 等。 ## 8. Java 版本兼容性 WVP pom.xml 编译目标是 JDK 21,但**经扫描确认 WVP 未使用 Java 21 专有语法**。仅用了 `String.isBlank()`(Java 11 特性),JDK 17 完全兼容。 处理方式:不需要语法降级,只需确保 video-server 的 pom.xml 不设置 `21`(继承根 pom 的 JDK 17 配置即可)。 ## 9. 网关路由补充 Hook 回调需要额外路由(ZLM 直接调用 video-server,不经过网关认证): ```yaml # viewsh-gateway application.yaml - id: video-hook uri: grayLb://video-server predicates: - Path=/index/hook/** ```