Files
ar-inspection/AR-INSPECTION-DESIGN.md
2025-11-13 17:15:27 +08:00

54 KiB
Raw Permalink Blame History

AR智能巡检管理系统 - 技术设计文档

一、项目概述

1.1 项目简介

AR智能巡检管理系统是一个基于 RuoYi-Vue-Plus 5.5.1 框架开发的电力设施智能巡检管理平台通过AR设备实现智能化巡检作业流程管理。

1.2 技术栈

后端技术栈:

  • Spring Boot 3.5.7
  • JDK 17/21
  • MyBatis-Plus 3.5.x
  • Sa-Token 1.38.0(权限认证)
  • MySQL 5.7+
  • Redisson分布式缓存
  • MapStruct-Plus对象映射

前端技术栈:

  • Vue 3
  • TypeScript
  • Element Plus
  • Vite

1.3 核心特性

  • 无多租户架构 - 简化数据模型,适用于单租户场景
  • 树形步骤管理 - 支持任意深度的步骤层级结构
  • JSON灵活存储 - 区域数据、点位坐标、AI配置使用JSON格式
  • 执行状态追踪 - 完整的任务执行生命周期管理
  • 媒体文件管理 - 支持图片、视频、音频等多媒体文件

二、需求分析

2.1 功能模块

模块名称 功能描述 核心实体
AR设备管理 管理AR巡检设备基础信息 ArDevice
区域管理 管理巡检区域及区域数据 ArRegion
点位管理 管理巡检点位及位置坐标 ArPoint
任务模板管理 管理巡检任务模板 ArTask
步骤管理 管理任务步骤树形结构 ArStep
任务执行管理 管理任务执行记录及状态 ArExecution
步骤记录管理 管理步骤执行详细记录 ArStepRecord
媒体文件管理 管理步骤执行过程中的媒体文件 ArStepMedia

2.2 关键需求

2.2.1 设计决策

问题1步骤层级结构

  • 决策:支持任意深度的树形结构
  • 实现:使用 parent_id + ancestors 字段实现

问题2坐标数据存储

  • 决策使用JSON字符串存储
  • 实现:使用 JacksonTypeHandler 实现自动序列化/反序列化

问题3语音/AI字段性质

  • 决策:作为步骤配置项(非执行记录)
  • 实现:字段设计在 ar_step 表中

问题4执行记录追踪

  • 决策追踪状态、执行数据、AI识别结果、语音文本
  • 实现:在 ar_step_record 表中设计对应字段

2.2.2 数据库调整需求

根据实际需求,数据库设计进行了以下调整:

表名 调整内容
ar_device 新增 device_model 字段
ar_region 新增 region_data (JSON) 和 remark 字段
ar_point 字段 position 改名为 position_data (JSON),移除 direction新增 remark
ar_task 新增 remark 字段
ar_step 移除UI预设字段从点位获取新增 ai_data (JSON)
ar_step_media 移除 oss_id 字段

2.2.3 多租户处理

明确要求:不考虑多租户

实施措施:

  1. 所有数据表均不包含 tenant_id 字段
  2. 所有实体类继承 BaseEntity非 TenantEntity
  3. 配置文件设置 tenant.enable: false
  4. 所有业务表加入 tenant.excludes 列表

三、数据库设计

3.1 数据库表清单

系统共包含 8 张业务表所有表均使用雪花ID作为主键支持逻辑删除。

3.2 表结构设计

3.2.1 AR设备表 (ar_device)

CREATE TABLE `ar_device` (
  `id` bigint NOT NULL COMMENT '设备ID',
  `device_name` varchar(100) NOT NULL COMMENT '设备名称',
  `device_no` varchar(50) NOT NULL COMMENT '设备编号',
  `device_model` varchar(100) DEFAULT NULL COMMENT '设备型号',
  `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_device_no` (`device_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AR设备表';

核心字段说明:

  • device_no: 设备编号,全局唯一
  • device_model: 设备型号(新增需求)
  • status: 设备状态0正常 1停用

3.2.2 巡检区域表 (ar_region)

CREATE TABLE `ar_region` (
  `id` bigint NOT NULL COMMENT '区域ID',
  `region_name` varchar(100) NOT NULL COMMENT '区域名称',
  `region_code` varchar(50) NOT NULL COMMENT '区域代码',
  `region_data` json DEFAULT NULL COMMENT '区域数据(JSON格式)',
  `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_region_code` (`region_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检区域表';

核心字段说明:

  • region_code: 区域代码,全局唯一
  • region_data: 区域数据JSON格式存储新增需求
  • remark: 备注信息(新增需求)

3.2.3 巡检点位表 (ar_point)

CREATE TABLE `ar_point` (
  `id` bigint NOT NULL COMMENT '点位ID',
  `region_id` bigint NOT NULL COMMENT '所属区域ID',
  `point_name` varchar(100) NOT NULL COMMENT '点位名称',
  `point_code` varchar(50) NOT NULL COMMENT '点位代码',
  `position_data` json DEFAULT NULL COMMENT '位置数据(JSON格式,包含坐标等)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_region_point` (`region_id`, `point_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检点位表';

核心字段说明:

  • point_code: 点位代码,同一区域内唯一
  • position_data: 位置数据JSON格式字段改名需求
  • 移除了 direction 字段(按需求调整)
  • remark: 备注信息(新增需求)

3.2.4 巡检任务模板表 (ar_task)

CREATE TABLE `ar_task` (
  `id` bigint NOT NULL COMMENT '任务ID',
  `task_name` varchar(100) NOT NULL COMMENT '任务名称',
  `task_code` varchar(50) NOT NULL COMMENT '任务代码',
  `region_id` bigint NOT NULL COMMENT '关联区域ID',
  `task_type` varchar(50) DEFAULT NULL COMMENT '任务类型',
  `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_task_code` (`task_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检任务模板表';

核心字段说明:

  • task_code: 任务代码,全局唯一
  • region_id: 关联区域ID
  • remark: 备注信息(新增需求)

3.2.5 巡检步骤表 (ar_step)

CREATE TABLE `ar_step` (
  `id` bigint NOT NULL COMMENT '步骤ID',
  `task_id` bigint NOT NULL COMMENT '所属任务ID',
  `parent_id` bigint DEFAULT '0' COMMENT '父步骤ID(0表示根节点)',
  `ancestors` varchar(500) DEFAULT '0' COMMENT '祖级列表',
  `step_name` varchar(100) NOT NULL COMMENT '步骤名称',
  `step_content` text COMMENT '步骤内容',
  `content_voice` varchar(255) DEFAULT NULL COMMENT '内容语音URL',
  `order_num` int DEFAULT '0' COMMENT '显示顺序',
  `point_id` bigint DEFAULT NULL COMMENT '关联点位ID',

  -- 语音交互配置
  `need_voice_read` char(1) DEFAULT '0' COMMENT '是否需要语音播报(0否 1是)',
  `need_voice_rephrase` char(1) DEFAULT '0' COMMENT '是否需要复述(0否 1是)',
  `rephrase_content` text COMMENT '复述内容',
  `rephrase_voice` varchar(255) DEFAULT NULL COMMENT '复述语音URL',
  `need_voice_confirm` char(1) DEFAULT '0' COMMENT '是否需要语音确认(0否 1是)',
  `confirm_content` text COMMENT '确认内容',
  `confirm_voice` varchar(255) DEFAULT NULL COMMENT '确认语音URL',
  `confirm_word` varchar(100) DEFAULT NULL COMMENT '确认关键词',

  -- AI识别配置
  `need_ai` char(1) DEFAULT '0' COMMENT '是否需要AI识别(0否 1是)',
  `ai_target_name` varchar(100) DEFAULT NULL COMMENT 'AI识别目标名称',
  `ai_data` json DEFAULT NULL COMMENT 'AI配置数据(JSON格式)',

  `is_operation` char(1) DEFAULT '0' COMMENT '是否需要操作(0否 1是)',
  `is_leaf` char(1) DEFAULT '1' COMMENT '是否叶子节点(0否 1是)',

  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检步骤表';

核心字段说明:

  • parent_id + ancestors: 实现树形结构
  • 支持任意深度的步骤层级
  • 语音交互配置字段:复述、确认等
  • ai_data: AI配置数据JSON格式新增需求
  • 移除了UI预设字段birth_point, birth_direction, prefab 等,按需求调整)

3.2.6 任务执行记录表 (ar_execution)

CREATE TABLE `ar_execution` (
  `id` bigint NOT NULL COMMENT '执行ID',
  `task_id` bigint NOT NULL COMMENT '任务模板ID',
  `execution_code` varchar(50) NOT NULL COMMENT '执行编号',
  `region_id` bigint NOT NULL COMMENT '区域ID',
  `device_id` bigint DEFAULT NULL COMMENT '使用的AR设备ID',

  -- 执行角色
  `operator_id` bigint DEFAULT NULL COMMENT '操作人ID',
  `operator_name` varchar(50) DEFAULT NULL COMMENT '操作人姓名',
  `custodian_id` bigint DEFAULT NULL COMMENT '监护人ID',
  `custodian_name` varchar(50) DEFAULT NULL COMMENT '监护人姓名',
  `sender_id` bigint DEFAULT NULL COMMENT '送电人ID',
  `sender_name` varchar(50) DEFAULT NULL COMMENT '送电人姓名',
  `recipient_id` bigint DEFAULT NULL COMMENT '受电人ID',
  `recipient_name` varchar(50) DEFAULT NULL COMMENT '受电人姓名',
  `commander_id` bigint DEFAULT NULL COMMENT '指挥人ID',
  `commander_name` varchar(50) DEFAULT NULL COMMENT '指挥人姓名',

  `status` varchar(20) DEFAULT 'pending' COMMENT '执行状态(pending待执行 in_progress执行中 completed已完成 cancelled已取消)',
  `start_time` datetime DEFAULT NULL COMMENT '开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '结束时间',
  `total_steps` int DEFAULT '0' COMMENT '总步骤数',
  `completed_steps` int DEFAULT '0' COMMENT '已完成步骤数',

  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_execution_code` (`execution_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务执行记录表';

核心字段说明:

  • execution_code: 执行编号自动生成格式EXE-{timestamp}
  • 5种执行角色操作人、监护人、送电人、受电人、指挥人
  • status: 执行状态pending/in_progress/completed/cancelled
  • 自动设置 start_timeend_time

3.2.7 步骤执行记录表 (ar_step_record)

CREATE TABLE `ar_step_record` (
  `id` bigint NOT NULL COMMENT '记录ID',
  `execution_id` bigint NOT NULL COMMENT '任务执行ID',
  `step_id` bigint NOT NULL COMMENT '步骤ID',
  `status` varchar(20) DEFAULT 'pending' COMMENT '状态(pending待执行 completed已完成 skipped已跳过)',
  `is_done` char(1) DEFAULT '0' COMMENT '是否完成(0否 1是)',
  `start_time` datetime DEFAULT NULL COMMENT '开始时间',
  `completion_time` datetime DEFAULT NULL COMMENT '完成时间',
  `duration` int DEFAULT NULL COMMENT '耗时(秒)',
  `text_feedback` text COMMENT '文本反馈',
  `voice_text` text COMMENT '语音识别文本',
  `ai_result` json DEFAULT NULL COMMENT 'AI识别结果(JSON格式)',
  `executor_id` bigint DEFAULT NULL COMMENT '执行人ID',
  `executor_name` varchar(50) DEFAULT NULL COMMENT '执行人姓名',

  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='步骤执行记录表';

核心字段说明:

  • status: 步骤执行状态pending/completed/skipped
  • duration: 耗时,自动计算(完成时间 - 开始时间)
  • text_feedback: 文本反馈
  • voice_text: 语音识别文本
  • ai_result: AI识别结果JSON格式

3.2.8 步骤媒体文件表 (ar_step_media)

CREATE TABLE `ar_step_media` (
  `id` bigint NOT NULL COMMENT '媒体ID',
  `step_record_id` bigint NOT NULL COMMENT '步骤记录ID',
  `media_type` varchar(20) NOT NULL COMMENT '媒体类型(image图片 video视频 audio音频)',
  `file_url` varchar(500) NOT NULL COMMENT '文件URL',
  `file_name` varchar(200) NOT NULL COMMENT '文件名称',
  `file_size` bigint DEFAULT NULL COMMENT '文件大小(字节)',
  `upload_time` datetime DEFAULT NULL COMMENT '上传时间',

  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint DEFAULT NULL COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='步骤媒体文件表';

核心字段说明:

  • media_type: 媒体类型image/video/audio
  • 移除了 oss_id 字段(按需求调整)
  • upload_time: 上传时间,自动设置

3.3 表关系说明

ar_region (区域)
    ↓ 1:N
ar_point (点位)

ar_region (区域)
    ↓ 1:N
ar_task (任务模板)
    ↓ 1:N
ar_step (步骤) - 树形结构

ar_task (任务模板)
    ↓ 1:N
ar_execution (任务执行)
    ↓ 1:N
ar_step_record (步骤记录)
    ↓ 1:N
ar_step_media (媒体文件)

ar_device (设备)
    ↓ 1:N
ar_execution (任务执行)

四、技术架构设计

4.1 分层架构

┌─────────────────────────────────────┐
│         Controller Layer            │  REST API控制层
│   (ArDeviceController, etc.)        │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│          Service Layer              │  业务逻辑层
│  (IArDeviceService, Impl, etc.)     │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│          Mapper Layer               │  数据访问层
│    (ArDeviceMapper, etc.)           │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│         Database Layer              │  MySQL数据库
│         (ar_device, etc.)           │
└─────────────────────────────────────┘

4.2 对象转换流程

Client Request (JSON)
      ↓
  Controller
      ↓ @RequestBody
     Bo (Business Object)
      ↓ MapStructUtils.convert()
   Entity
      ↓ MyBatis-Plus
   Database
      ↓ MyBatis-Plus
     Vo (View Object)
      ↓ JSON Response
    Client

4.3 核心技术组件

4.3.1 MyBatis-Plus 配置

主键策略: 雪花IDASSIGN_ID

@TableId(value = "id")
private Long id;

逻辑删除:

@TableLogic
private Integer delFlag;

JSON字段处理

@TableName(value = "ar_region", autoResultMap = true)
public class ArRegion extends BaseEntity {
    @TableField(typeHandler = JacksonTypeHandler.class)
    private Map<String, Object> regionData;
}

4.3.2 MapStruct-Plus 对象映射

实体与Bo互转

@Data
@AutoMapper(target = ArDevice.class, reverseConvertGenerate = false)
public class ArDeviceBo extends BaseEntity {
    // fields...
}

实体转Vo

@Data
@AutoMapper(target = ArDevice.class)
public class ArDeviceVo implements Serializable {
    // fields...
}

使用方式:

ArDevice entity = MapstructUtils.convert(bo, ArDevice.class);

4.3.3 Sa-Token 权限控制

权限注解:

@SaCheckPermission("inspection:device:list")
@GetMapping("/list")
public TableDataInfo<ArDeviceVo> list(ArDeviceBo bo, PageQuery pageQuery) {
    return arDeviceService.queryPageList(bo, pageQuery);
}

权限标识格式: 模块:功能:操作

4.3.4 数据验证

分组验证:

// 新增时验证
@NotBlank(message = "设备名称不能为空", groups = {AddGroup.class})
private String deviceName;

// 编辑时验证
@NotNull(message = "设备ID不能为空", groups = {EditGroup.class})
private Long id;

五、模块设计

5.1 模块结构

ruoyi-modules/
└── ruoyi-inspection/
    ├── pom.xml
    └── src/main/java/org/dromara/inspection/
        ├── controller/         # 控制器层
        │   ├── ArDeviceController.java
        │   ├── ArRegionController.java
        │   ├── ArPointController.java
        │   ├── ArTaskController.java
        │   ├── ArStepController.java
        │   ├── ArExecutionController.java
        │   ├── ArStepRecordController.java
        │   └── ArStepMediaController.java
        ├── service/            # 服务接口
        │   ├── IArDeviceService.java
        │   └── impl/           # 服务实现
        │       └── ArDeviceServiceImpl.java
        ├── mapper/             # Mapper接口
        │   └── ArDeviceMapper.java
        └── domain/             # 领域对象
            ├── ArDevice.java           # 实体
            ├── bo/                      # 业务对象
            │   └── ArDeviceBo.java
            └── vo/                      # 视图对象
                └── ArDeviceVo.java

5.2 标准CRUD实现模式

5.2.1 Controller层标准实现

@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/inspection/device")
public class ArDeviceController extends BaseController {

    private final IArDeviceService arDeviceService;

    // 1. 分页查询列表
    @SaCheckPermission("inspection:device:list")
    @GetMapping("/list")
    public TableDataInfo<ArDeviceVo> list(
        @Validated(QueryGroup.class) ArDeviceBo bo,
        PageQuery pageQuery) {
        return arDeviceService.queryPageList(bo, pageQuery);
    }

    // 2. 导出Excel
    @SaCheckPermission("inspection:device:export")
    @Log(title = "AR设备", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(ArDeviceBo bo, HttpServletResponse response) {
        List<ArDeviceVo> list = arDeviceService.queryList(bo);
        ExcelUtil.exportExcel(list, "AR设备", ArDeviceVo.class, response);
    }

    // 3. 查询详情
    @SaCheckPermission("inspection:device:query")
    @GetMapping("/{id}")
    public R<ArDeviceVo> getInfo(@PathVariable("id") Long id) {
        return R.ok(arDeviceService.queryById(id));
    }

    // 4. 新增
    @SaCheckPermission("inspection:device:add")
    @Log(title = "AR设备", businessType = BusinessType.INSERT)
    @RepeatSubmit
    @PostMapping()
    public R<Void> add(@Validated(AddGroup.class) @RequestBody ArDeviceBo bo) {
        return toAjax(arDeviceService.insertByBo(bo));
    }

    // 5. 修改
    @SaCheckPermission("inspection:device:edit")
    @Log(title = "AR设备", businessType = BusinessType.UPDATE)
    @RepeatSubmit
    @PutMapping()
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ArDeviceBo bo) {
        return toAjax(arDeviceService.updateByBo(bo));
    }

    // 6. 删除
    @SaCheckPermission("inspection:device:remove")
    @Log(title = "AR设备", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public R<Void> remove(@PathVariable Long[] ids) {
        return toAjax(arDeviceService.deleteWithValidByIds(Arrays.asList(ids), true));
    }
}

5.2.2 Service层标准实现

@RequiredArgsConstructor
@Service
public class ArDeviceServiceImpl implements IArDeviceService {

    private final ArDeviceMapper baseMapper;

    @Override
    public ArDeviceVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    @Override
    public TableDataInfo<ArDeviceVo> queryPageList(ArDeviceBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<ArDevice> lqw = buildQueryWrapper(bo);
        Page<ArDeviceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    @Override
    public List<ArDeviceVo> queryList(ArDeviceBo bo) {
        return baseMapper.selectVoList(buildQueryWrapper(bo));
    }

    private LambdaQueryWrapper<ArDevice> buildQueryWrapper(ArDeviceBo bo) {
        LambdaQueryWrapper<ArDevice> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getDeviceName()),
                 ArDevice::getDeviceName, bo.getDeviceName());
        lqw.eq(StringUtils.isNotBlank(bo.getDeviceNo()),
               ArDevice::getDeviceNo, bo.getDeviceNo());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()),
               ArDevice::getStatus, bo.getStatus());
        return lqw;
    }

    @Override
    public Boolean insertByBo(ArDeviceBo bo) {
        ArDevice add = MapstructUtils.convert(bo, ArDevice.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    @Override
    public Boolean updateByBo(ArDeviceBo bo) {
        ArDevice update = MapstructUtils.convert(bo, ArDevice.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    private void validEntityBeforeSave(ArDevice entity) {
        // 唯一性校验:设备编号
        if (StringUtils.isNotBlank(entity.getDeviceNo())) {
            LambdaQueryWrapper<ArDevice> lqw = Wrappers.lambdaQuery();
            lqw.eq(ArDevice::getDeviceNo, entity.getDeviceNo());
            lqw.ne(entity.getId() != null, ArDevice::getId, entity.getId());
            long count = baseMapper.selectCount(lqw);
            if (count > 0) {
                throw new ServiceException("设备编号已存在!");
            }
        }
    }

    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            List<ArDevice> list = baseMapper.selectByIds(ids);
            if (list.size() != ids.size()) {
                throw new ServiceException("您没有删除权限!");
            }
        }
        return baseMapper.deleteByIds(ids) > 0;
    }
}

5.3 特殊模块设计

5.3.1 步骤管理(树形结构)

树形VO设计

@Data
public class ArStepTreeVo implements Serializable {
    private Long id;
    private Long taskId;
    private Long parentId;
    private String stepName;
    // ... 其他字段

    // 核心:子节点列表
    private List<ArStepTreeVo> children;
}

树形查询接口:

@GetMapping("/tree/{taskId}")
public R<List<ArStepTreeVo>> tree(@PathVariable("taskId") Long taskId) {
    return R.ok(arStepService.queryStepTree(taskId));
}

递归构建树形结构:

@Override
public List<ArStepTreeVo> queryStepTree(Long taskId) {
    // 1. 查询所有步骤
    LambdaQueryWrapper<ArStep> lqw = Wrappers.lambdaQuery();
    lqw.eq(ArStep::getTaskId, taskId);
    lqw.orderByAsc(ArStep::getOrderNum);
    List<ArStepVo> allSteps = baseMapper.selectVoList(lqw);

    // 2. 递归构建树
    return buildStepTree(allSteps, 0L);
}

private List<ArStepTreeVo> buildStepTree(List<ArStepVo> allSteps, Long parentId) {
    List<ArStepTreeVo> tree = new ArrayList<>();
    for (ArStepVo step : allSteps) {
        if (step.getParentId().equals(parentId)) {
            ArStepTreeVo treeNode = new ArStepTreeVo();
            BeanUtils.copyProperties(step, treeNode);

            // 递归查找子节点
            List<ArStepTreeVo> children = buildStepTree(allSteps, step.getId());
            treeNode.setChildren(children);

            tree.add(treeNode);
        }
    }
    return tree;
}

自动维护ancestors字段

@Override
public Boolean insertByBo(ArStepBo bo) {
    ArStep add = MapstructUtils.convert(bo, ArStep.class);

    // 自动设置ancestors
    if (add.getParentId() != null && add.getParentId() != 0) {
        ArStep parent = baseMapper.selectById(add.getParentId());
        if (parent != null) {
            add.setAncestors(parent.getAncestors() + "," + add.getParentId());
        } else {
            add.setAncestors("0," + add.getParentId());
        }
    } else {
        add.setAncestors("0");
    }

    // 设置初始状态
    if (StringUtils.isBlank(add.getIsLeaf())) {
        add.setIsLeaf("1");
    }

    boolean flag = baseMapper.insert(add) > 0;

    // 如果插入成功且有父节点更新父节点的isLeaf
    if (flag && add.getParentId() != null && add.getParentId() != 0) {
        ArStep parent = new ArStep();
        parent.setId(add.getParentId());
        parent.setIsLeaf("0");
        baseMapper.updateById(parent);
    }

    if (flag) {
        bo.setId(add.getId());
    }
    return flag;
}

级联删除:

@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
    if (isValid) {
        List<ArStep> list = baseMapper.selectByIds(ids);
        if (list.size() != ids.size()) {
            throw new ServiceException("您没有删除权限!");
        }
    }

    // 级联删除子步骤
    for (Long id : ids) {
        LambdaQueryWrapper<ArStep> lqw = Wrappers.lambdaQuery();
        lqw.eq(ArStep::getParentId, id);
        long count = baseMapper.selectCount(lqw);
        if (count > 0) {
            List<ArStep> children = baseMapper.selectList(lqw);
            List<Long> childrenIds = children.stream()
                .map(ArStep::getId)
                .collect(Collectors.toList());
            // 递归删除子节点
            deleteWithValidByIds(childrenIds, false);
        }
    }

    return baseMapper.deleteByIds(ids) > 0;
}

5.3.2 任务执行管理(状态管理)

自动生成执行编号:

@Override
public Boolean insertByBo(ArExecutionBo bo) {
    ArExecution add = MapstructUtils.convert(bo, ArExecution.class);

    // 自动生成执行编号
    if (StringUtils.isBlank(add.getExecutionCode())) {
        add.setExecutionCode("EXE-" + System.currentTimeMillis());
    }

    // 设置初始状态
    if (StringUtils.isBlank(add.getStatus())) {
        add.setStatus("pending");
    }

    boolean flag = baseMapper.insert(add) > 0;
    if (flag) {
        bo.setId(add.getId());
    }
    return flag;
}

状态变化自动设置时间戳:

@Override
public Boolean updateByBo(ArExecutionBo bo) {
    ArExecution update = MapstructUtils.convert(bo, ArExecution.class);

    // 状态变为in_progress时自动设置开始时间
    if ("in_progress".equals(update.getStatus()) && update.getStartTime() == null) {
        update.setStartTime(new Date());
    }

    // 状态变为completed或cancelled时自动设置结束时间
    if (("completed".equals(update.getStatus()) || "cancelled".equals(update.getStatus()))
        && update.getEndTime() == null) {
        update.setEndTime(new Date());
    }

    return baseMapper.updateById(update) > 0;
}

5.3.3 步骤记录管理(自动计算耗时)

@Override
public Boolean updateByBo(ArStepRecordBo bo) {
    ArStepRecord update = MapstructUtils.convert(bo, ArStepRecord.class);

    // 状态变为completed时自动设置完成时间并计算耗时
    if ("completed".equals(update.getStatus()) && update.getCompletionTime() == null) {
        update.setCompletionTime(new Date());
        update.setIsDone("1");

        // 自动计算耗时(秒)
        if (update.getStartTime() != null) {
            long duration = (update.getCompletionTime().getTime()
                           - update.getStartTime().getTime()) / 1000;
            update.setDuration((int) duration);
        }
    }

    return baseMapper.updateById(update) > 0;
}

5.3.4 媒体文件管理(自动设置上传时间)

@Override
public Boolean insertByBo(ArStepMediaBo bo) {
    ArStepMedia add = MapstructUtils.convert(bo, ArStepMedia.class);
    validEntityBeforeSave(add);

    // 自动设置上传时间
    if (add.getUploadTime() == null) {
        add.setUploadTime(new Date());
    }

    boolean flag = baseMapper.insert(add) > 0;
    if (flag) {
        bo.setId(add.getId());
    }
    return flag;
}

六、API接口设计

6.1 RESTful API规范

所有API遵循统一的RESTful风格

HTTP方法 路径 功能 权限标识
GET /inspection/{module}/list 分页查询列表 {module}:list
POST /inspection/{module}/export 导出Excel {module}:export
GET /inspection/{module}/{id} 查询详情 {module}:query
POST /inspection/{module} 新增 {module}:add
PUT /inspection/{module} 修改 {module}:edit
DELETE /inspection/{module}/{ids} 批量删除 {module}:remove

6.2 模块API列表

6.2.1 AR设备管理

GET    /inspection/device/list          # 分页查询设备列表
POST   /inspection/device/export        # 导出设备列表
GET    /inspection/device/{id}          # 查询设备详情
POST   /inspection/device               # 新增设备
PUT    /inspection/device               # 修改设备
DELETE /inspection/device/{ids}         # 删除设备

查询参数示例:

{
  "deviceName": "设备1",
  "deviceNo": "DEV001",
  "status": "0",
  "pageNum": 1,
  "pageSize": 10
}

6.2.2 区域管理

GET    /inspection/region/list          # 分页查询区域列表
POST   /inspection/region/export        # 导出区域列表
GET    /inspection/region/{id}          # 查询区域详情
POST   /inspection/region               # 新增区域
PUT    /inspection/region               # 修改区域
DELETE /inspection/region/{ids}         # 删除区域

新增请求体示例:

{
  "regionName": "A区",
  "regionCode": "REGION-A",
  "regionData": {
    "area": 1000,
    "building": "主楼",
    "floor": 3
  },
  "status": "0",
  "remark": "主要巡检区域"
}

6.2.3 点位管理

GET    /inspection/point/list           # 分页查询点位列表
POST   /inspection/point/export         # 导出点位列表
GET    /inspection/point/{id}           # 查询点位详情
POST   /inspection/point                # 新增点位
PUT    /inspection/point                # 修改点位
DELETE /inspection/point/{ids}          # 删除点位

新增请求体示例:

{
  "regionId": 1,
  "pointName": "配电柜A1",
  "pointCode": "POINT-A1",
  "positionData": {
    "x": 10.5,
    "y": 20.3,
    "z": 1.5,
    "rotation": {
      "x": 0,
      "y": 90,
      "z": 0
    }
  },
  "remark": "主配电柜"
}

6.2.4 任务模板管理

GET    /inspection/task/list            # 分页查询任务列表
POST   /inspection/task/export          # 导出任务列表
GET    /inspection/task/{id}            # 查询任务详情
POST   /inspection/task                 # 新增任务
PUT    /inspection/task                 # 修改任务
DELETE /inspection/task/{ids}           # 删除任务

6.2.5 步骤管理

GET    /inspection/step/list            # 分页查询步骤列表
GET    /inspection/step/tree/{taskId}   # 查询任务的步骤树
POST   /inspection/step/export          # 导出步骤列表
GET    /inspection/step/{id}            # 查询步骤详情
POST   /inspection/step                 # 新增步骤
PUT    /inspection/step                 # 修改步骤
DELETE /inspection/step/{ids}           # 删除步骤(级联)

树形结构返回示例:

[
  {
    "id": 1,
    "taskId": 1,
    "parentId": 0,
    "stepName": "开始巡检",
    "orderNum": 1,
    "children": [
      {
        "id": 2,
        "parentId": 1,
        "stepName": "检查电压",
        "orderNum": 1,
        "children": []
      },
      {
        "id": 3,
        "parentId": 1,
        "stepName": "检查电流",
        "orderNum": 2,
        "children": []
      }
    ]
  }
]

新增步骤请求体示例:

{
  "taskId": 1,
  "parentId": 0,
  "stepName": "检查配电柜",
  "stepContent": "检查配电柜外观及指示灯状态",
  "orderNum": 1,
  "pointId": 1,
  "needVoiceRead": "1",
  "needAi": "1",
  "aiTargetName": "配电柜指示灯",
  "aiData": {
    "modelName": "yolov8",
    "confidence": 0.8,
    "classes": ["红灯", "绿灯", "黄灯"]
  }
}

6.2.6 任务执行管理

GET    /inspection/execution/list       # 分页查询执行记录列表
POST   /inspection/execution/export     # 导出执行记录
GET    /inspection/execution/{id}       # 查询执行记录详情
POST   /inspection/execution            # 新增执行记录
PUT    /inspection/execution            # 修改执行记录
DELETE /inspection/execution/{ids}      # 删除执行记录

新增执行记录示例:

{
  "taskId": 1,
  "regionId": 1,
  "deviceId": 1,
  "operatorId": 1001,
  "operatorName": "张三",
  "custodianId": 1002,
  "custodianName": "李四",
  "status": "pending"
}

更新状态示例:

{
  "id": 1,
  "status": "in_progress"
  // 自动设置startTime
}

6.2.7 步骤记录管理

GET    /inspection/stepRecord/list      # 分页查询步骤记录列表
POST   /inspection/stepRecord/export    # 导出步骤记录
GET    /inspection/stepRecord/{id}      # 查询步骤记录详情
POST   /inspection/stepRecord           # 新增步骤记录
PUT    /inspection/stepRecord           # 修改步骤记录
DELETE /inspection/stepRecord/{ids}     # 删除步骤记录

新增步骤记录示例:

{
  "executionId": 1,
  "stepId": 1,
  "startTime": "2025-01-13 10:00:00"
}

完成步骤示例:

{
  "id": 1,
  "status": "completed",
  "textFeedback": "检查完成,设备状态正常",
  "voiceText": "设备状态正常",
  "aiResult": {
    "detected": true,
    "confidence": 0.95,
    "status": "正常"
  }
  // 自动设置completionTime和计算duration
}

6.2.8 媒体文件管理

GET    /inspection/stepMedia/list       # 分页查询媒体文件列表
POST   /inspection/stepMedia/export     # 导出媒体文件列表
GET    /inspection/stepMedia/{id}       # 查询媒体文件详情
POST   /inspection/stepMedia            # 新增媒体文件
PUT    /inspection/stepMedia            # 修改媒体文件
DELETE /inspection/stepMedia/{ids}      # 删除媒体文件

新增媒体文件示例:

{
  "stepRecordId": 1,
  "mediaType": "image",
  "fileUrl": "https://oss.example.com/inspection/20250113/abc123.jpg",
  "fileName": "配电柜照片.jpg",
  "fileSize": 2048576
  // 自动设置uploadTime
}

6.3 统一响应格式

成功响应:

{
  "code": 200,
  "msg": "操作成功",
  "data": {
    // 响应数据
  }
}

分页响应:

{
  "code": 200,
  "msg": "查询成功",
  "rows": [
    // 数据列表
  ],
  "total": 100
}

失败响应:

{
  "code": 500,
  "msg": "设备编号已存在!"
}

七、关键技术实现

7.1 多租户禁用方案

7.1.1 配置文件修改

application.yml

# 多租户配置
tenant:
  # 是否开启
  enable: false
  # 排除表(不进行多租户处理的表)
  excludes:
    - sys_menu
    - sys_tenant
    - sys_tenant_package
    - ar_device
    - ar_region
    - ar_point
    - ar_task
    - ar_step
    - ar_execution
    - ar_step_record
    - ar_step_media

application-dev.yml, application-prod.yml 同样设置 tenant.enable: false

7.1.2 实体类设计

所有实体类继承 BaseEntity 而非 TenantEntity

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("ar_device")
public class ArDevice extends BaseEntity {
    // 不包含tenantId字段
}

7.2 JSON字段处理方案

7.2.1 实体类配置

@Data
@EqualsAndHashCode(callSuper = true)
@TableName(value = "ar_region", autoResultMap = true)  // 必须设置autoResultMap
public class ArRegion extends BaseEntity {

    @TableField(typeHandler = JacksonTypeHandler.class)  // 指定类型处理器
    private Map<String, Object> regionData;
}

关键点:

  1. @TableName(autoResultMap = true) - 启用自动结果映射
  2. @TableField(typeHandler = JacksonTypeHandler.class) - 使用Jackson处理器
  3. 字段类型使用 Map<String, Object> 或自定义POJO

7.2.2 数据库字段类型

MySQL使用 json 类型:

`region_data` json DEFAULT NULL COMMENT '区域数据(JSON格式)'

7.2.3 使用示例

新增数据:

ArRegion region = new ArRegion();
region.setRegionName("A区");
Map<String, Object> data = new HashMap<>();
data.put("area", 1000);
data.put("building", "主楼");
region.setRegionData(data);
baseMapper.insert(region);

查询数据:

ArRegion region = baseMapper.selectById(1L);
Map<String, Object> data = region.getRegionData();
Integer area = (Integer) data.get("area");

7.3 树形结构实现方案

7.3.1 数据库字段设计

`parent_id` bigint DEFAULT '0' COMMENT '父步骤ID(0表示根节点)',
`ancestors` varchar(500) DEFAULT '0' COMMENT '祖级列表',
`is_leaf` char(1) DEFAULT '1' COMMENT '是否叶子节点(0否 1是)'

7.3.2 ancestors字段说明

ancestors存储从根节点到父节点的完整路径

  • 根节点:"0"
  • 一级节点:"0,1"
  • 二级节点:"0,1,2"
  • 三级节点:"0,1,2,3"

优势:

  • 快速查询所有祖先节点
  • 快速查询所有子孙节点
  • 支持任意深度

7.3.3 新增节点自动维护ancestors

public Boolean insertByBo(ArStepBo bo) {
    ArStep add = MapstructUtils.convert(bo, ArStep.class);

    // 自动设置ancestors
    if (add.getParentId() != null && add.getParentId() != 0) {
        ArStep parent = baseMapper.selectById(add.getParentId());
        if (parent != null) {
            // 继承父节点的ancestors + 父节点ID
            add.setAncestors(parent.getAncestors() + "," + add.getParentId());
        } else {
            add.setAncestors("0," + add.getParentId());
        }
    } else {
        add.setAncestors("0");
    }

    boolean flag = baseMapper.insert(add) > 0;

    // 更新父节点的isLeaf标识
    if (flag && add.getParentId() != null && add.getParentId() != 0) {
        ArStep parent = new ArStep();
        parent.setId(add.getParentId());
        parent.setIsLeaf("0");  // 有子节点,不是叶子节点
        baseMapper.updateById(parent);
    }

    return flag;
}

7.3.4 递归构建树形结构

public List<ArStepTreeVo> queryStepTree(Long taskId) {
    // 1. 一次性查询所有步骤
    LambdaQueryWrapper<ArStep> lqw = Wrappers.lambdaQuery();
    lqw.eq(ArStep::getTaskId, taskId);
    lqw.orderByAsc(ArStep::getOrderNum);
    List<ArStepVo> allSteps = baseMapper.selectVoList(lqw);

    // 2. 递归构建树从根节点parentId=0开始
    return buildStepTree(allSteps, 0L);
}

private List<ArStepTreeVo> buildStepTree(List<ArStepVo> allSteps, Long parentId) {
    List<ArStepTreeVo> tree = new ArrayList<>();

    for (ArStepVo step : allSteps) {
        if (step.getParentId().equals(parentId)) {
            // 找到当前parentId的子节点
            ArStepTreeVo treeNode = new ArStepTreeVo();
            BeanUtils.copyProperties(step, treeNode);

            // 递归查找子节点的子节点
            List<ArStepTreeVo> children = buildStepTree(allSteps, step.getId());
            treeNode.setChildren(children);

            tree.add(treeNode);
        }
    }

    return tree;
}

7.3.5 级联删除

public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
    // 权限校验...

    // 级联删除所有子节点
    for (Long id : ids) {
        LambdaQueryWrapper<ArStep> lqw = Wrappers.lambdaQuery();
        lqw.eq(ArStep::getParentId, id);
        long count = baseMapper.selectCount(lqw);

        if (count > 0) {
            // 查询所有子节点
            List<ArStep> children = baseMapper.selectList(lqw);
            List<Long> childrenIds = children.stream()
                .map(ArStep::getId)
                .collect(Collectors.toList());

            // 递归删除子节点
            deleteWithValidByIds(childrenIds, false);
        }
    }

    return baseMapper.deleteByIds(ids) > 0;
}

7.4 自动字段管理方案

7.4.1 自动生成执行编号

@Override
public Boolean insertByBo(ArExecutionBo bo) {
    ArExecution add = MapstructUtils.convert(bo, ArExecution.class);

    // 自动生成执行编号EXE-{时间戳}
    if (StringUtils.isBlank(add.getExecutionCode())) {
        add.setExecutionCode("EXE-" + System.currentTimeMillis());
    }

    // 设置初始状态
    if (StringUtils.isBlank(add.getStatus())) {
        add.setStatus("pending");
    }

    boolean flag = baseMapper.insert(add) > 0;
    if (flag) {
        bo.setId(add.getId());
    }
    return flag;
}

7.4.2 状态变化自动设置时间戳

@Override
public Boolean updateByBo(ArExecutionBo bo) {
    ArExecution update = MapstructUtils.convert(bo, ArExecution.class);

    // pending -> in_progress设置开始时间
    if ("in_progress".equals(update.getStatus()) && update.getStartTime() == null) {
        update.setStartTime(new Date());
    }

    // -> completed/cancelled设置结束时间
    if (("completed".equals(update.getStatus()) || "cancelled".equals(update.getStatus()))
        && update.getEndTime() == null) {
        update.setEndTime(new Date());
    }

    return baseMapper.updateById(update) > 0;
}

7.4.3 自动计算耗时

@Override
public Boolean updateByBo(ArStepRecordBo bo) {
    ArStepRecord update = MapstructUtils.convert(bo, ArStepRecord.class);

    // 状态变为completed自动设置完成时间、isDone标识、计算耗时
    if ("completed".equals(update.getStatus()) && update.getCompletionTime() == null) {
        update.setCompletionTime(new Date());
        update.setIsDone("1");

        // 计算耗时(秒)
        if (update.getStartTime() != null) {
            long durationMs = update.getCompletionTime().getTime()
                            - update.getStartTime().getTime();
            update.setDuration((int) (durationMs / 1000));
        }
    }

    return baseMapper.updateById(update) > 0;
}

7.4.4 自动设置上传时间

@Override
public Boolean insertByBo(ArStepMediaBo bo) {
    ArStepMedia add = MapstructUtils.convert(bo, ArStepMedia.class);

    // 自动设置上传时间
    if (add.getUploadTime() == null) {
        add.setUploadTime(new Date());
    }

    boolean flag = baseMapper.insert(add) > 0;
    if (flag) {
        bo.setId(add.getId());
    }
    return flag;
}

7.5 唯一性校验方案

7.5.1 单字段唯一性校验

private void validEntityBeforeSave(ArDevice entity) {
    // 校验设备编号唯一性
    if (StringUtils.isNotBlank(entity.getDeviceNo())) {
        LambdaQueryWrapper<ArDevice> lqw = Wrappers.lambdaQuery();
        lqw.eq(ArDevice::getDeviceNo, entity.getDeviceNo());
        // 编辑时排除自身
        lqw.ne(entity.getId() != null, ArDevice::getId, entity.getId());

        long count = baseMapper.selectCount(lqw);
        if (count > 0) {
            throw new ServiceException("设备编号已存在!");
        }
    }
}

7.5.2 联合唯一性校验

private void validEntityBeforeSave(ArPoint entity) {
    // 校验点位代码在同一区域内唯一
    if (entity.getRegionId() != null && StringUtils.isNotBlank(entity.getPointCode())) {
        LambdaQueryWrapper<ArPoint> lqw = Wrappers.lambdaQuery();
        lqw.eq(ArPoint::getRegionId, entity.getRegionId());
        lqw.eq(ArPoint::getPointCode, entity.getPointCode());
        lqw.ne(entity.getId() != null, ArPoint::getId, entity.getId());

        long count = baseMapper.selectCount(lqw);
        if (count > 0) {
            throw new ServiceException("该区域下点位代码已存在!");
        }
    }
}

八、部署说明

8.1 开发环境部署

8.1.1 后端启动

# 1. 编译项目
mvn clean install -DskipTests

# 2. 运行主应用(开发环境)
cd ruoyi-admin
mvn spring-boot:run

# 或者使用IDE运行
# 主类org.dromara.DromaraApplication

访问地址:http://localhost:8080

8.1.2 前端启动

# 1. 进入前端目录
cd plus-ui

# 2. 安装依赖(首次)
npm install --registry=https://registry.npmmirror.com

# 3. 启动开发服务器
npm run dev

访问地址:http://localhost:80

默认账号:admin / admin123

8.1.3 数据库初始化

  1. 创建数据库:ry-vue-plus
  2. 执行脚本:script/sql/mysql/ry_all_5.5.1.sql
  3. 执行业务表创建语句(已通过代码自动创建)

8.2 生产环境部署

8.2.1 后端打包

# 使用生产环境配置打包
mvn clean package -Pprod -DskipTests

# 生成的jar包位置
# ruoyi-admin/target/ruoyi-admin.jar

8.2.2 后端运行

# 启动应用
java -jar ruoyi-admin.jar

# 指定配置文件
java -jar ruoyi-admin.jar --spring.profiles.active=prod

# 后台运行
nohup java -jar ruoyi-admin.jar > server.log 2>&1 &

8.2.3 前端打包

cd plus-ui

# 构建生产环境
npm run build:prod

# 生成的静态文件位置
# plus-ui/dist/

8.2.4 Nginx配置

server {
    listen 80;
    server_name your-domain.com;

    # 前端静态文件
    location / {
        root /var/www/ar-inspection/dist;
        try_files $uri $uri/ /index.html;
    }

    # 后端API代理
    location /prod-api/ {
        proxy_pass http://localhost:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

8.3 Docker部署

8.3.1 Dockerfile后端

FROM openjdk:17-jdk-alpine
WORKDIR /app
COPY ruoyi-admin/target/ruoyi-admin.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

8.3.2 Dockerfile前端

FROM nginx:alpine
COPY plus-ui/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

8.3.3 docker-compose.yml

version: '3'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: ry-vue-plus
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  backend:
    build: .
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redis
    environment:
      SPRING_PROFILES_ACTIVE: prod

  frontend:
    build:
      context: .
      dockerfile: Dockerfile.frontend
    ports:
      - "80:80"
    depends_on:
      - backend

volumes:
  mysql-data:

8.4 环境配置检查清单

  • MySQL 5.7+ 已安装并启动
  • Redis 已安装并启动
  • JDK 17/21 已安装
  • Maven 3.6+ 已安装(开发环境)
  • Node.js 18+ 已安装(开发环境)
  • 数据库连接信息已配置
  • Redis连接信息已配置
  • 多租户已禁用tenant.enable: false
  • 所有业务表已加入tenant.excludes
  • 文件上传路径已配置
  • 日志路径已配置

九、系统测试

9.1 功能测试清单

9.1.1 AR设备管理

  • 新增设备(设备编号唯一性校验)
  • 修改设备信息
  • 删除设备
  • 查询设备列表(分页、条件查询)
  • 导出设备列表Excel

9.1.2 区域管理

  • 新增区域(区域代码唯一性校验)
  • 修改区域信息包含region_data JSON
  • 删除区域
  • 查询区域列表
  • 导出区域列表

9.1.3 点位管理

  • 新增点位(点位代码区域内唯一性校验)
  • 修改点位包含position_data JSON
  • 删除点位
  • 查询点位列表
  • 导出点位列表

9.1.4 任务模板管理

  • 新增任务(任务代码唯一性校验)
  • 修改任务
  • 删除任务
  • 查询任务列表
  • 导出任务列表

9.1.5 步骤管理

  • 新增根步骤parentId=0
  • 新增子步骤ancestors自动维护
  • 修改步骤包含ai_data JSON
  • 删除步骤(级联删除子步骤)
  • 查询步骤树形结构
  • 查询步骤列表
  • 导出步骤列表

9.1.6 任务执行管理

  • 新增执行记录execution_code自动生成
  • 修改状态为in_progressstart_time自动设置
  • 修改状态为completedend_time自动设置
  • 修改状态为cancelled
  • 删除执行记录
  • 查询执行记录列表
  • 导出执行记录

9.1.7 步骤记录管理

  • 新增步骤记录
  • 修改步骤记录
  • 完成步骤completion_time和duration自动计算
  • 删除步骤记录
  • 查询步骤记录列表
  • 导出步骤记录

9.1.8 媒体文件管理

  • 新增媒体文件upload_time自动设置
  • 修改媒体文件信息
  • 删除媒体文件
  • 查询媒体文件列表
  • 导出媒体文件列表

9.2 性能测试建议

  • 步骤树形结构查询性能(大数据量)
  • 分页查询性能
  • JSON字段查询性能
  • 级联删除性能

9.3 安全测试建议

  • 权限控制测试Sa-Token
  • SQL注入防护测试
  • XSS防护测试
  • 文件上传安全测试

十、附录

10.1 权限标识清单

模块 权限标识
AR设备管理 inspection:device:list / query / add / edit / remove / export
区域管理 inspection:region:list / query / add / edit / remove / export
点位管理 inspection:point:list / query / add / edit / remove / export
任务模板管理 inspection:task:list / query / add / edit / remove / export
步骤管理 inspection:step:list / query / add / edit / remove / export
任务执行管理 inspection:execution:list / query / add / edit / remove / export
步骤记录管理 inspection:stepRecord:list / query / add / edit / remove / export
媒体文件管理 inspection:stepMedia:list / query / add / edit / remove / export

10.2 数据字典

设备状态device.status

  • 0正常
  • 1停用

执行状态execution.status

  • pending待执行
  • in_progress执行中
  • completed已完成
  • cancelled已取消

步骤记录状态step_record.status

  • pending待执行
  • completed已完成
  • skipped已跳过

媒体类型step_media.media_type

  • image图片
  • video视频
  • audio音频

是否标识(通用)

  • 0
  • 1

10.3 常见问题FAQ

Q1: 为什么不使用多租户? A: 根据项目实际需求,系统面向单一组织使用,不需要多租户隔离,禁用多租户可以简化数据模型和查询逻辑。

Q2: JSON字段如何进行查询 A: MyBatis-Plus支持JSON字段的基本查询复杂查询可以使用原生SQL或MySQL的JSON函数。

Q3: 步骤树形结构支持多深? A: 理论上支持无限深度但建议不超过5层过深的层级会影响查询性能和用户体验。

Q4: 如何批量导入设备/区域等数据? A: 使用Excel导入功能需要前端实现或通过API批量调用新增接口。

Q5: 媒体文件存储在哪里? A: 系统支持MinIO、阿里云OSS等对象存储file_url存储访问地址。

10.4 后续优化建议

  1. 性能优化

    • 为常用查询字段添加索引
    • 步骤树形结构考虑使用缓存
    • 大数据量导出使用异步任务
  2. 功能增强

    • 步骤执行过程的WebSocket实时推送
    • 任务执行进度可视化
    • 执行报告自动生成
    • 数据统计分析
  3. 安全加固

    • 文件上传类型限制
    • 文件大小限制
    • 敏感数据加密
    • 操作审计日志
  4. 运维监控

    • 接口性能监控
    • 异常告警
    • 数据备份策略
    • 日志归档策略

文档变更记录

版本 日期 变更内容 作者
1.0 2025-01-13 初始版本,完成全部设计文档 Claude Code

文档结束