# 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) ```sql 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) ```sql 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) ```sql 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) ```sql 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) ```sql 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) ```sql 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_time` 和 `end_time` #### 3.2.7 步骤执行记录表 (ar_step_record) ```sql 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) ```sql 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 配置 **主键策略:** 雪花ID(ASSIGN_ID) ```java @TableId(value = "id") private Long id; ``` **逻辑删除:** ```java @TableLogic private Integer delFlag; ``` **JSON字段处理:** ```java @TableName(value = "ar_region", autoResultMap = true) public class ArRegion extends BaseEntity { @TableField(typeHandler = JacksonTypeHandler.class) private Map regionData; } ``` #### 4.3.2 MapStruct-Plus 对象映射 **实体与Bo互转:** ```java @Data @AutoMapper(target = ArDevice.class, reverseConvertGenerate = false) public class ArDeviceBo extends BaseEntity { // fields... } ``` **实体转Vo:** ```java @Data @AutoMapper(target = ArDevice.class) public class ArDeviceVo implements Serializable { // fields... } ``` **使用方式:** ```java ArDevice entity = MapstructUtils.convert(bo, ArDevice.class); ``` #### 4.3.3 Sa-Token 权限控制 **权限注解:** ```java @SaCheckPermission("inspection:device:list") @GetMapping("/list") public TableDataInfo list(ArDeviceBo bo, PageQuery pageQuery) { return arDeviceService.queryPageList(bo, pageQuery); } ``` **权限标识格式:** `模块:功能:操作` #### 4.3.4 数据验证 **分组验证:** ```java // 新增时验证 @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层标准实现 ```java @Validated @RequiredArgsConstructor @RestController @RequestMapping("/inspection/device") public class ArDeviceController extends BaseController { private final IArDeviceService arDeviceService; // 1. 分页查询列表 @SaCheckPermission("inspection:device:list") @GetMapping("/list") public TableDataInfo 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 list = arDeviceService.queryList(bo); ExcelUtil.exportExcel(list, "AR设备", ArDeviceVo.class, response); } // 3. 查询详情 @SaCheckPermission("inspection:device:query") @GetMapping("/{id}") public R 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 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 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 remove(@PathVariable Long[] ids) { return toAjax(arDeviceService.deleteWithValidByIds(Arrays.asList(ids), true)); } } ``` #### 5.2.2 Service层标准实现 ```java @RequiredArgsConstructor @Service public class ArDeviceServiceImpl implements IArDeviceService { private final ArDeviceMapper baseMapper; @Override public ArDeviceVo queryById(Long id) { return baseMapper.selectVoById(id); } @Override public TableDataInfo queryPageList(ArDeviceBo bo, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); return TableDataInfo.build(result); } @Override public List queryList(ArDeviceBo bo) { return baseMapper.selectVoList(buildQueryWrapper(bo)); } private LambdaQueryWrapper buildQueryWrapper(ArDeviceBo bo) { LambdaQueryWrapper 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 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 ids, Boolean isValid) { if (isValid) { List list = baseMapper.selectByIds(ids); if (list.size() != ids.size()) { throw new ServiceException("您没有删除权限!"); } } return baseMapper.deleteByIds(ids) > 0; } } ``` ### 5.3 特殊模块设计 #### 5.3.1 步骤管理(树形结构) **树形VO设计:** ```java @Data public class ArStepTreeVo implements Serializable { private Long id; private Long taskId; private Long parentId; private String stepName; // ... 其他字段 // 核心:子节点列表 private List children; } ``` **树形查询接口:** ```java @GetMapping("/tree/{taskId}") public R> tree(@PathVariable("taskId") Long taskId) { return R.ok(arStepService.queryStepTree(taskId)); } ``` **递归构建树形结构:** ```java @Override public List queryStepTree(Long taskId) { // 1. 查询所有步骤 LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(ArStep::getTaskId, taskId); lqw.orderByAsc(ArStep::getOrderNum); List allSteps = baseMapper.selectVoList(lqw); // 2. 递归构建树 return buildStepTree(allSteps, 0L); } private List buildStepTree(List allSteps, Long parentId) { List tree = new ArrayList<>(); for (ArStepVo step : allSteps) { if (step.getParentId().equals(parentId)) { ArStepTreeVo treeNode = new ArStepTreeVo(); BeanUtils.copyProperties(step, treeNode); // 递归查找子节点 List children = buildStepTree(allSteps, step.getId()); treeNode.setChildren(children); tree.add(treeNode); } } return tree; } ``` **自动维护ancestors字段:** ```java @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; } ``` **级联删除:** ```java @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if (isValid) { List list = baseMapper.selectByIds(ids); if (list.size() != ids.size()) { throw new ServiceException("您没有删除权限!"); } } // 级联删除子步骤 for (Long id : ids) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(ArStep::getParentId, id); long count = baseMapper.selectCount(lqw); if (count > 0) { List children = baseMapper.selectList(lqw); List childrenIds = children.stream() .map(ArStep::getId) .collect(Collectors.toList()); // 递归删除子节点 deleteWithValidByIds(childrenIds, false); } } return baseMapper.deleteByIds(ids) > 0; } ``` #### 5.3.2 任务执行管理(状态管理) **自动生成执行编号:** ```java @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; } ``` **状态变化自动设置时间戳:** ```java @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 步骤记录管理(自动计算耗时) ```java @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 媒体文件管理(自动设置上传时间) ```java @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} # 删除设备 ``` **查询参数示例:** ```json { "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} # 删除区域 ``` **新增请求体示例:** ```json { "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} # 删除点位 ``` **新增请求体示例:** ```json { "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} # 删除步骤(级联) ``` **树形结构返回示例:** ```json [ { "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": [] } ] } ] ``` **新增步骤请求体示例:** ```json { "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} # 删除执行记录 ``` **新增执行记录示例:** ```json { "taskId": 1, "regionId": 1, "deviceId": 1, "operatorId": 1001, "operatorName": "张三", "custodianId": 1002, "custodianName": "李四", "status": "pending" } ``` **更新状态示例:** ```json { "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} # 删除步骤记录 ``` **新增步骤记录示例:** ```json { "executionId": 1, "stepId": 1, "startTime": "2025-01-13 10:00:00" } ``` **完成步骤示例:** ```json { "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} # 删除媒体文件 ``` **新增媒体文件示例:** ```json { "stepRecordId": 1, "mediaType": "image", "fileUrl": "https://oss.example.com/inspection/20250113/abc123.jpg", "fileName": "配电柜照片.jpg", "fileSize": 2048576 // 自动设置uploadTime } ``` ### 6.3 统一响应格式 **成功响应:** ```json { "code": 200, "msg": "操作成功", "data": { // 响应数据 } } ``` **分页响应:** ```json { "code": 200, "msg": "查询成功", "rows": [ // 数据列表 ], "total": 100 } ``` **失败响应:** ```json { "code": 500, "msg": "设备编号已存在!" } ``` --- ## 七、关键技术实现 ### 7.1 多租户禁用方案 #### 7.1.1 配置文件修改 **application.yml:** ```yaml # 多租户配置 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: ```java @Data @EqualsAndHashCode(callSuper = true) @TableName("ar_device") public class ArDevice extends BaseEntity { // 不包含tenantId字段 } ``` ### 7.2 JSON字段处理方案 #### 7.2.1 实体类配置 ```java @Data @EqualsAndHashCode(callSuper = true) @TableName(value = "ar_region", autoResultMap = true) // 必须设置autoResultMap public class ArRegion extends BaseEntity { @TableField(typeHandler = JacksonTypeHandler.class) // 指定类型处理器 private Map regionData; } ``` **关键点:** 1. `@TableName(autoResultMap = true)` - 启用自动结果映射 2. `@TableField(typeHandler = JacksonTypeHandler.class)` - 使用Jackson处理器 3. 字段类型使用 `Map` 或自定义POJO #### 7.2.2 数据库字段类型 MySQL使用 `json` 类型: ```sql `region_data` json DEFAULT NULL COMMENT '区域数据(JSON格式)' ``` #### 7.2.3 使用示例 **新增数据:** ```java ArRegion region = new ArRegion(); region.setRegionName("A区"); Map data = new HashMap<>(); data.put("area", 1000); data.put("building", "主楼"); region.setRegionData(data); baseMapper.insert(region); ``` **查询数据:** ```java ArRegion region = baseMapper.selectById(1L); Map data = region.getRegionData(); Integer area = (Integer) data.get("area"); ``` ### 7.3 树形结构实现方案 #### 7.3.1 数据库字段设计 ```sql `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 ```java 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 递归构建树形结构 ```java public List queryStepTree(Long taskId) { // 1. 一次性查询所有步骤 LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(ArStep::getTaskId, taskId); lqw.orderByAsc(ArStep::getOrderNum); List allSteps = baseMapper.selectVoList(lqw); // 2. 递归构建树(从根节点parentId=0开始) return buildStepTree(allSteps, 0L); } private List buildStepTree(List allSteps, Long parentId) { List tree = new ArrayList<>(); for (ArStepVo step : allSteps) { if (step.getParentId().equals(parentId)) { // 找到当前parentId的子节点 ArStepTreeVo treeNode = new ArStepTreeVo(); BeanUtils.copyProperties(step, treeNode); // 递归查找子节点的子节点 List children = buildStepTree(allSteps, step.getId()); treeNode.setChildren(children); tree.add(treeNode); } } return tree; } ``` #### 7.3.5 级联删除 ```java public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { // 权限校验... // 级联删除所有子节点 for (Long id : ids) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(ArStep::getParentId, id); long count = baseMapper.selectCount(lqw); if (count > 0) { // 查询所有子节点 List children = baseMapper.selectList(lqw); List childrenIds = children.stream() .map(ArStep::getId) .collect(Collectors.toList()); // 递归删除子节点 deleteWithValidByIds(childrenIds, false); } } return baseMapper.deleteByIds(ids) > 0; } ``` ### 7.4 自动字段管理方案 #### 7.4.1 自动生成执行编号 ```java @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 状态变化自动设置时间戳 ```java @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 自动计算耗时 ```java @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 自动设置上传时间 ```java @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 单字段唯一性校验 ```java private void validEntityBeforeSave(ArDevice entity) { // 校验设备编号唯一性 if (StringUtils.isNotBlank(entity.getDeviceNo())) { LambdaQueryWrapper 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 联合唯一性校验 ```java private void validEntityBeforeSave(ArPoint entity) { // 校验点位代码在同一区域内唯一 if (entity.getRegionId() != null && StringUtils.isNotBlank(entity.getPointCode())) { LambdaQueryWrapper 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 后端启动 ```bash # 1. 编译项目 mvn clean install -DskipTests # 2. 运行主应用(开发环境) cd ruoyi-admin mvn spring-boot:run # 或者使用IDE运行 # 主类:org.dromara.DromaraApplication ``` 访问地址:`http://localhost:8080` #### 8.1.2 前端启动 ```bash # 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 后端打包 ```bash # 使用生产环境配置打包 mvn clean package -Pprod -DskipTests # 生成的jar包位置 # ruoyi-admin/target/ruoyi-admin.jar ``` #### 8.2.2 后端运行 ```bash # 启动应用 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 前端打包 ```bash cd plus-ui # 构建生产环境 npm run build:prod # 生成的静态文件位置 # plus-ui/dist/ ``` #### 8.2.4 Nginx配置 ```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(后端) ```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(前端) ```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 ```yaml 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_progress(start_time自动设置) - [ ] 修改状态为completed(end_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 | --- **文档结束**