54 KiB
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 多租户处理
明确要求:不考虑多租户
实施措施:
- 所有数据表均不包含 tenant_id 字段
- 所有实体类继承 BaseEntity(非 TenantEntity)
- 配置文件设置
tenant.enable: false - 所有业务表加入
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: 关联区域IDremark: 备注信息(新增需求)
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_time和end_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 配置
主键策略: 雪花ID(ASSIGN_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;
}
关键点:
@TableName(autoResultMap = true)- 启用自动结果映射@TableField(typeHandler = JacksonTypeHandler.class)- 使用Jackson处理器- 字段类型使用
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 数据库初始化
- 创建数据库:
ry-vue-plus - 执行脚本:
script/sql/mysql/ry_all_5.5.1.sql - 执行业务表创建语句(已通过代码自动创建)
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_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 后续优化建议
-
性能优化
- 为常用查询字段添加索引
- 步骤树形结构考虑使用缓存
- 大数据量导出使用异步任务
-
功能增强
- 步骤执行过程的WebSocket实时推送
- 任务执行进度可视化
- 执行报告自动生成
- 数据统计分析
-
安全加固
- 文件上传类型限制
- 文件大小限制
- 敏感数据加密
- 操作审计日志
-
运维监控
- 接口性能监控
- 异常告警
- 数据备份策略
- 日志归档策略
文档变更记录
| 版本 | 日期 | 变更内容 | 作者 |
|---|---|---|---|
| 1.0 | 2025-01-13 | 初始版本,完成全部设计文档 | Claude Code |
文档结束