Files
aiot-platform-cloud/openspec/changes/add-video-module-wvp-migration/design.md
2026-04-21 10:15:02 +08:00

15 KiB
Raw Blame History

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/                            #   MapperMediaServer/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 逻辑删除:

// 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<DeviceDO>()
    .eq(DeviceDO::getDeviceId, deviceId));

3.1.2 唯一索引调整

逻辑删除后,被"删除"的记录仍然存在。如果表有唯一索引,再次插入相同数据会冲突。需要将 deleted 加入联合唯一索引:

-- 改造前
UNIQUE INDEX uk_device_id (device_id)

-- 改造后
UNIQUE INDEX uk_device_id (device_id, deleted)

需要调整唯一索引的核心表:

  • wvp_devicedevice_id
  • wvp_device_channeldevice_db_id + channel_id
  • wvp_platformserver_gb_id
  • wvp_media_serverid(业务主键)
  • wvp_stream_proxyapp + stream
  • wvp_stream_pushapp + stream
  • wvp_common_groupdevice_id
  • wvp_common_regiondevice_id

3.2 改造模式

// 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/updateTimeTenantBaseDO 提供)
    ...
}

// 改造后(共享表 — 无租户,有逻辑删除)
@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 特殊处理

  • DeviceChannel86 字段):字段多但改造模式一致,批量处理
  • 关联查询结果 DTO:不继承 VideoBaseDO保持为普通 POJO因为是 JOIN 查询结果,不对应单表)
  • WVP 的 int id:保持 Integer id,不改 Long。数据库也保持 INT,等稳定后再逐步升级

4. Mapper 改造设计

4.1 简单 CRUD → BaseMapperX 内置方法

// 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<MediaServerDO> {
    // 简单查询直接用继承方法
    // selectById(), insert(), updateById() 等
    
    default MediaServerDO selectByServerId(String id) {
        return selectOne(MediaServerDO::getId, id);
    }
}

4.2 复杂查询 → 保留 @Select 注解

// 多表 JOIN、动态 SQL 保留注解方式
@Mapper
public interface DeviceChannelMapper extends BaseMapperX<DeviceChannelDO> {
    
    // 复杂 JOIN 保留 @Select
    @Select("<script>" +
            "SELECT dc.*, d.device_id as parent_device_id " +
            "FROM wvp_device_channel dc " +
            "LEFT JOIN wvp_device d ON dc.device_db_id = d.id " +
            "<where>" +
            "<if test='query != null'> AND dc.name LIKE CONCAT('%',#{query},'%')</if>" +
            "</where>" +
            "</script>")
    List<DeviceChannelDO> selectWithDevice(@Param("query") String query);
}

4.3 分页改造

// WVP 原始PageHelper
PageHelper.startPage(page, count);
List<Device> devices = deviceMapper.getDevices(query);
return new PageInfo<>(devices);

// 改造后MyBatis Plus
Page<DeviceDO> page = new Page<>(pageNo, pageSize);
IPage<DeviceDO> result = deviceMapper.selectPage(page, 
    new LambdaQueryWrapperX<DeviceDO>()
        .likeIfPresent(DeviceDO::getName, query));
return new PageResult<>(result.getRecords(), result.getTotal());

5. 认证替换设计

5.1 SecurityConfiguration 更新

@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 用户获取替换

// WVP: SecurityUtils.getUserInfo() 返回 LoginUser
// ↓ 替换为
// viewsh: SecurityFrameworkUtils.getLoginUserId() 返回 Long

6. 数据库设计

6.1 独立数据库 aiot-video

# 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 中配置:

viewsh:
  tenant:
    ignore-tables:
      - wvp_media_server

7. RedisTemplate 隔离设计

7.1 问题

viewsh 框架的 RedisTemplate 使用 Jackson 序列化(RedisSerializer.json()WVP 的 RedisTemplate 使用 FastJSON2 序列化(GenericFastJsonRedisSerializer)。两者 Bean 名冲突会导致其他模块 Redis 崩溃。

7.2 方案

// WVP 原始 RedisTemplateConfig
@Bean
public RedisTemplate<Object, Object> redisTemplate(...) { ... }

// 改造后:独立 Bean 名称
@Configuration
public class VideoRedisTemplateConfig {

    @Bean("videoRedisTemplate")
    public RedisTemplate<Object, Object> videoRedisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> 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+ 处)需要改为:

// 改造前
@Autowired
private RedisTemplate<Object, Object> redisTemplate;

// 改造后
@Autowired
@Qualifier("videoRedisTemplate")
private RedisTemplate<Object, Object> redisTemplate;

主要涉及:RedisRpcConfigRedisCatchStorageImplRedisMsgListenConfig、各 Listener 等。

8. Java 版本兼容性

WVP pom.xml 编译目标是 JDK 21经扫描确认 WVP 未使用 Java 21 专有语法。仅用了 String.isBlank()Java 11 特性JDK 17 完全兼容。

处理方式:不需要语法降级,只需确保 video-server 的 pom.xml 不设置 <source>21</source>(继承根 pom 的 JDK 17 配置即可)。

9. 网关路由补充

Hook 回调需要额外路由ZLM 直接调用 video-server不经过网关认证

# viewsh-gateway application.yaml
- id: video-hook
  uri: grayLb://video-server
  predicates:
    - Path=/index/hook/**