opt:md文件优化

This commit is contained in:
2026-04-16 14:01:59 +08:00
parent b87d2d6cea
commit 993f6ffe45
12 changed files with 2473 additions and 44 deletions

View File

@@ -0,0 +1,537 @@
# NL Admin System 开发手册
## 1. 开发环境搭建
### 1.1 环境要求
| 软件 | 版本 | 说明 |
| :--- | :--- | :--- |
| JDK | 1.8 | 必须使用 Java 8 |
| Maven | 3.6+ | 依赖管理工具 |
| MySQL | 5.7+ | 主数据库 |
| Redis | 3.2+ | 缓存与会话管理 |
### 1.2 JDK 配置
```bash
# 设置环境变量
export JAVA_HOME=/path/to/jdk1.8
export PATH=$JAVA_HOME/bin:$PATH
```
### 1.3 Maven 配置
`settings.xml`
```xml
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
```
---
## 2. 项目结构详解
### 2.1 模块划分
```
src/main/java/org/nl/
├── AppRun.java # Spring Boot 启动类
├── acs/ # 设备控制模块(核心业务)
│ ├── apiAdd/ # 地址管理
│ ├── autoThread/ # 自动线程管理
│ ├── device/ # 设备管理
│ ├── iot/ # IoT数据处理
│ ├── layout/ # 布局管理
│ ├── log/ # 日志管理
│ └── task/ # 任务调度
├── common/ # 通用模块(工具、配置、异常等)
│ ├── annotation/ # 自定义注解
│ ├── base/ # 基础类DTO、Mapper等
│ ├── db/ # 数据库工具
│ ├── domain/ # 通用领域模型
│ ├── enums/ # 枚举类
│ ├── exception/ # 异常处理
│ ├── logging/ # 日志切面
│ ├── mnt/ # 监控与维护
│ ├── security/ # 安全配置
│ └── utils/ # 工具类
├── config/ # 配置类
│ ├── jackson/ # JSON序列化配置
│ ├── language/ # 国际化配置
│ ├── lucene/ # 全文搜索配置
│ ├── mybatis/ # MyBatis配置
│ ├── redis/ # Redis配置
│ ├── saconfig/ # Sa-Token配置
│ └── thread/ # 线程池配置
├── extInterface/ # 外部接口
│ ├── agvKit/ # AGV系统对接
│ └── wms/ # WMS系统对接
└── system/ # 系统管理
└── controller/ # 系统控制器
```
### 2.2 核心模块说明
| 模块 | 职责 | 说明 |
| :--- | :--- | :--- |
| `acs/` | 设备控制核心 | 包含设备管理、任务调度、IoT通信等核心业务 |
| `common/` | 通用工具 | 提供基础工具类、异常处理、日志等通用能力 |
| `config/` | 配置管理 | Spring配置类、第三方组件配置 |
| `extInterface/` | 外部对接 | AGV、WMS等外部系统接口 |
| `system/` | 系统管理 | 用户、角色、权限等系统管理功能 |
---
## 3. 代码规范
### 3.1 命名规范
| 类型 | 规范 | 示例 |
| :--- | :--- | :--- |
| 包名 | 全小写,使用点分隔 | `org.nl.acs.device` |
| 类名 | 大驼峰命名法 | `DeviceController` |
| 方法名 | 小驼峰命名法 | `getDeviceById` |
| 变量名 | 小驼峰命名法 | `deviceName` |
| 常量名 | 全大写,下划线分隔 | `MAX_PAGE_SIZE` |
| 接口名 | 大驼峰,以 `I` 开头 | `IDeviceService` |
### 3.2 格式规范
- 使用 4 个空格缩进(禁止使用 Tab
- 每行代码不超过 120 字符
- 方法之间空一行
- 类成员变量与方法之间空两行
### 3.3 注释规范
```java
/**
* 获取设备详情
* @param id 设备ID
* @return 设备信息
*/
public DeviceDto getDeviceById(Long id) {
// 业务逻辑
}
```
### 3.4 异常处理规范
```java
// 错误示例
try {
// 业务逻辑
} catch (Exception e) {
e.printStackTrace(); // 禁止直接打印堆栈
}
// 正确示例
try {
// 业务逻辑
} catch (Exception e) {
log.error("获取设备失败, id: {}", id, e);
throw new BadRequestException("获取设备失败");
}
```
---
## 4. 开发流程
### 4.1 需求分析
1. 明确需求范围和业务场景
2. 分析与现有模块的依赖关系
3. 设计数据模型和接口
### 4.2 编码流程
```
需求分析 → 设计文档 → 创建DTO → 创建Entity → 创建Mapper → 创建Service → 创建Controller → 单元测试
```
### 4.3 Git 工作流
```bash
# 1. 从主分支拉取最新代码
git checkout develop
git pull origin develop
# 2. 创建特性分支
git checkout -b feature/xxx-feature
# 3. 开发并提交
git add .
git commit -m "feat: 实现xxx功能"
# 4. 推送到远程
git push origin feature/xxx-feature
# 5. 创建 Pull Request
```
---
## 5. 数据库设计规范
### 5.1 命名规范
| 对象 | 规范 | 示例 |
| :--- | :--- | :--- |
| 表名 | 小写,下划线分隔,前缀 `sys_` | `sys_device` |
| 字段名 | 小写,下划线分隔 | `device_name` |
| 主键 | 统一命名为 `id`,自增 | `id BIGINT PRIMARY KEY AUTO_INCREMENT` |
### 5.2 字段类型选择
| 类型 | 用途 | 示例 |
| :--- | :--- | :--- |
| BIGINT | 主键、ID字段 | `id`, `user_id` |
| VARCHAR | 字符串最大255 | `name`, `code` |
| TEXT | 长文本 | `description` |
| DATETIME | 日期时间 | `create_time`, `update_time` |
| INT | 整数 | `status`, `sort` |
| DECIMAL | 精确小数 | `price`, `quantity` |
### 5.3 通用字段
```sql
CREATE TABLE example (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
create_by VARCHAR(64) COMMENT '创建人',
create_time DATETIME COMMENT '创建时间',
update_by VARCHAR(64) COMMENT '更新人',
update_time DATETIME COMMENT '更新时间',
is_deleted TINYINT DEFAULT 0 COMMENT '删除标记(0-未删除,1-已删除)',
remark VARCHAR(255) COMMENT '备注'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='示例表';
```
---
## 6. API 开发规范
### 6.1 RESTful 风格
| 操作 | HTTP方法 | 路径 |
| :--- | :--- | :--- |
| 查询列表 | GET | `/api/device` |
| 查询单个 | GET | `/api/device/{id}` |
| 新增 | POST | `/api/device` |
| 更新 | PUT | `/api/device/{id}` |
| 删除 | DELETE | `/api/device/{id}` |
### 6.2 统一响应格式
```java
// 成功响应
{
"code": 200,
"message": "操作成功",
"data": {...},
"timestamp": 1620000000000
}
// 失败响应
{
"code": 400,
"message": "参数错误",
"data": null,
"timestamp": 1620000000000
}
```
### 6.3 Controller 示例
```java
@RestController
@RequestMapping("/api/device")
public class DeviceController {
@Autowired
private IDeviceService deviceService;
@GetMapping
public ResponseEntity<Object> getAll(DeviceQuery query) {
List<DeviceDto> devices = deviceService.queryAll(query);
return ResponseEntity.ok(devices);
}
@GetMapping("/{id}")
public ResponseEntity<Object> getById(@PathVariable Long id) {
DeviceDto device = deviceService.getById(id);
return ResponseEntity.ok(device);
}
@PostMapping
public ResponseEntity<Object> create(@RequestBody DeviceDto dto) {
deviceService.create(dto);
return ResponseEntity.ok("创建成功");
}
@PutMapping("/{id}")
public ResponseEntity<Object> update(@PathVariable Long id, @RequestBody DeviceDto dto) {
deviceService.update(id, dto);
return ResponseEntity.ok("更新成功");
}
@DeleteMapping("/{id}")
public ResponseEntity<Object> delete(@PathVariable Long id) {
deviceService.delete(id);
return ResponseEntity.ok("删除成功");
}
}
```
---
## 7. Service 层开发规范
### 7.1 接口定义
```java
public interface IDeviceService {
/**
* 查询设备列表
*/
List<DeviceDto> queryAll(DeviceQuery query);
/**
* 根据ID查询设备
*/
DeviceDto getById(Long id);
/**
* 创建设备
*/
void create(DeviceDto dto);
/**
* 更新设备
*/
void update(Long id, DeviceDto dto);
/**
* 删除设备
*/
void delete(Long id);
}
```
### 7.2 实现类规范
```java
@Service
public class DeviceServiceImpl implements IDeviceService {
@Autowired
private DeviceMapper deviceMapper;
@Autowired
private RedisUtils redisUtils;
@Override
@Transactional(rollbackFor = Exception.class)
public void create(DeviceDto dto) {
// 参数校验
if (StrUtil.isEmpty(dto.getName())) {
throw new BadRequestException("设备名称不能为空");
}
// 转换为实体
Device entity = ConvertUtil.convert(dto, Device.class);
// 保存到数据库
deviceMapper.insert(entity);
// 更新缓存
redisUtils.set("device:" + entity.getId(), entity);
}
}
```
---
## 8. 安全规范
### 8.1 权限控制
使用 Sa-Token 进行权限控制:
```java
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@GetMapping("/users")
public ResponseEntity<Object> getUsers() {
return ResponseEntity.ok(userService.getAll());
}
}
```
### 8.2 参数校验
使用 `@Valid` 注解进行参数校验:
```java
public class DeviceDto {
@NotBlank(message = "设备名称不能为空")
@Size(max = 100, message = "设备名称不能超过100字符")
private String name;
@Min(value = 0, message = "状态值不能为负数")
private Integer status;
}
```
### 8.3 SQL 注入防护
```java
// 正确:使用参数化查询
QueryWrapper<Device> wrapper = new QueryWrapper<>();
wrapper.eq("device_code", deviceCode);
deviceMapper.selectOne(wrapper);
// 错误拼接SQL
String sql = "SELECT * FROM device WHERE code = '" + deviceCode + "'";
```
---
## 9. 日志规范
### 9.1 日志级别
| 级别 | 用途 |
| :--- | :--- |
| DEBUG | 详细的调试信息,生产环境关闭 |
| INFO | 重要的业务流程日志 |
| WARN | 警告信息,需要关注但不影响运行 |
| ERROR | 错误信息,需要立即处理 |
### 9.2 日志格式
```java
// 错误示例
log.info("设备ID: " + id + ", 名称: " + name);
// 正确示例
log.info("创建设备成功, id: {}, name: {}", id, name);
log.error("创建设备失败, id: {}, error: {}", id, e.getMessage(), e);
```
---
## 10. 测试规范
### 10.1 单元测试
```java
@SpringBootTest
class DeviceServiceTest {
@Autowired
private IDeviceService deviceService;
@Test
void testCreate() {
DeviceDto dto = new DeviceDto();
dto.setName("测试设备");
dto.setCode("TEST001");
deviceService.create(dto);
DeviceDto result = deviceService.getById(1L);
assertEquals("测试设备", result.getName());
}
}
```
### 10.2 测试覆盖率
- 核心业务逻辑覆盖率 ≥ 80%
- 工具类覆盖率 ≥ 90%
- 新增代码必须有对应的单元测试
---
## 11. 部署与发布
### 11.1 配置管理
```bash
# 开发环境
java -jar nlsso-server.jar --spring.profiles.active=dev
# 生产环境
java -jar nlsso-server.jar --spring.profiles.active=prod
```
### 11.2 启动脚本
```bash
#!/bin/bash
APP_NAME=nlsso-server
APP_DIR=/opt/app
cd $APP_DIR
# 停止旧进程
PID=$(ps -ef | grep $APP_NAME | grep -v grep | awk '{print $2}')
if [ -n "$PID" ]; then
kill -9 $PID
sleep 2
fi
# 启动新进程
nohup java -jar $APP_DIR/$APP_NAME.jar --spring.profiles.active=prod > /dev/null 2>&1 &
echo "服务已启动"
```
---
## 12. 常见问题
### 12.1 依赖冲突
问题:启动时报 `NoSuchMethodError`
解决方案:检查 `pom.xml` 中的依赖版本,使用 `mvn dependency:tree` 分析依赖树,排除冲突的依赖。
### 12.2 数据库连接失败
问题:无法连接数据库
解决方案:
1. 检查数据库服务是否启动
2. 检查 `application.yml` 中的数据库配置
3. 检查数据库用户名和密码
### 12.3 Redis 连接失败
问题:缓存操作失败
解决方案:
1. 检查 Redis 服务是否启动
2. 检查 Redis 配置(主机、端口、密码)
3. 检查防火墙规则
### 12.4 权限不足
问题:访问接口时返回 403
解决方案:
1. 检查用户是否登录
2. 检查用户是否具有相应权限
3. 检查权限配置是否正确
---

View File

@@ -1,2 +1,202 @@
# nladmin
# NL Admin System
诺力智能仓储管理ACS系统后端服务
## 项目简介
本项目是诺力智能仓储管理ACS系统后端服务标准版基于 Spring Boot 框架构建提供完整的用户认证、设备管理、任务调度、AGV调度、设备管理等核心功能。
## 技术栈
| 技术 | 版本 | 说明 |
| :--- | :--- | :--- |
| Java | 1.8 | 开发语言 |
| Spring Boot | 2.2.x | 应用框架 |
| Sa-Token | 1.31.0 | 权限认证框架 |
| MyBatis Plus | 3.4.0 | ORM框架 |
| Redis | - | 缓存与会话管理 |
| MySQL/Oracle | - | 数据库 |
| Lucene | 8.2.0 | 全文搜索 |
| WebSocket | - | 实时通信 |
## 项目结构
```
nladmin-system/
├── nlsso-server/ # 后端服务模块
│ ├── src/main/java/org/nl/
│ │ ├── AppRun.java # 启动类
│ │ ├── acs/ # 设备控制模块
│ │ │ ├── apiAdd/ # 地址管理
│ │ │ ├── autoThread/ # 自动线程管理
│ │ │ ├── device/ # 设备管理
│ │ │ ├── iot/ # IoT数据处理
│ │ │ ├── layout/ # 布局管理
│ │ │ ├── log/ # 日志管理
│ │ │ └── task/ # 任务调度
│ │ ├── common/ # 通用模块
│ │ │ ├── annotation/ # 自定义注解
│ │ │ ├── base/ # 基础类
│ │ │ ├── db/ # 数据库工具
│ │ │ ├── domain/ # 通用领域模型
│ │ │ ├── enums/ # 枚举类
│ │ │ ├── exception/ # 异常处理
│ │ │ ├── logging/ # 日志切面
│ │ │ ├── mnt/ # 监控与维护
│ │ │ ├── security/ # 安全配置
│ │ │ └── utils/ # 工具类
│ │ ├── config/ # 配置类
│ │ │ ├── jackson/ # JSON配置
│ │ │ ├── language/ # 国际化配置
│ │ │ ├── lucene/ # 搜索配置
│ │ │ ├── mybatis/ # MyBatis配置
│ │ │ ├── redis/ # Redis配置
│ │ │ ├── saconfig/ # Sa-Token配置
│ │ │ └── thread/ # 线程池配置
│ │ ├── extInterface/ # 外部接口
│ │ │ ├── agvKit/ # AGV接口
│ │ │ └── wms/ # WMS接口
│ │ └── system/ # 系统管理
│ │ └── controller/ # 系统控制器
│ └── pom.xml # Maven配置
└── README.md # 项目说明
```
## 快速开始
### 环境要求
- JDK 1.8+
- Maven 3.6+
- Redis 3.2+
- MySQL 5.7+ / Oracle 11g+
### 配置说明
#### 1. 数据库配置
修改 `src/main/resources/application.yml`
```yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/nladmin?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: admin
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
```
#### 2. Redis 配置
```yaml
spring:
redis:
host: localhost
port: 6379
password:
timeout: 10000ms
```
#### 3. Sa-Token 配置
```yaml
sa-token:
token-name: Authorization
timeout: 86400000
active-timeout: -1
is-concurrent: true
is-share: true
token-style: uuid
is-log: false
```
### 启动方式
```bash
java -jar target/nlsso-server.jar
```
## 核心功能
### 1. 用户认证
- 登录/登出
- 在线用户管理
- Token 验证
- 权限校验
### 2. 设备管理
- 设备信息管理
- 设备协议管理
- 货位管理
- 设备扩展信息
### 3. 任务调度
- 任务创建与执行
- 任务历史记录
- 任务指令管理
- 任务优先级调度
### 4. 外部接口
- AGV 系统对接
- WMS 系统对接
- 数据交互协议
### 5. 日志管理
- 操作日志记录
- 设备执行日志
- Lucene 全文检索
## API 接口
### 认证接口
| 接口 | 方法 | 描述 |
| :--- | :--- | :--- |
| `/api/auth/login` | POST | 用户登录 |
| `/api/auth/logout` | POST | 用户登出 |
| `/api/auth/online` | GET | 获取在线用户列表 |
### 设备接口
| 接口 | 方法 | 描述 |
| :--- | :--- | :--- |
| `/api/device` | GET | 获取设备列表 |
| `/api/device/{id}` | GET | 获取设备详情 |
| `/api/device` | POST | 创建设备 |
| `/api/device/{id}` | PUT | 更新设备 |
| `/api/device/{id}` | DELETE | 删除设备 |
### 任务接口
| 接口 | 方法 | 描述 |
| :--- | :--- | :--- |
| `/api/task` | GET | 获取任务列表 |
| `/api/task/{id}` | GET | 获取任务详情 |
| `/api/task` | POST | 创建任务 |
| `/api/task/{id}/execute` | POST | 执行任务 |
## 安全说明
1. **密码加密**:采用 RSA 非对称加密传输MD5 加盐存储
2. **Token 管理**:基于 Sa-Token 的会话管理,支持多端登录控制
3. **权限控制**:基于注解的细粒度权限控制
4. **请求限流**:支持接口限流配置
5. **SQL 注入防护**:使用 MyBatis Plus 参数化查询
## 开发规范
### 命名规范
- 包名:小写字母,使用点分隔
- 类名大驼峰命名法PascalCase
- 方法名小驼峰命名法camelCase
- 变量名小驼峰命名法camelCase
### 代码规范
- 遵循阿里巴巴 Java 开发手册
- 使用 Lombok 简化代码
- 异常处理统一使用全局异常处理器
- 日志记录使用 SLF4J
## 许可证
Apache License 2.0

View File

@@ -2,7 +2,7 @@ package org.nl.acs.autoThread.autoRun;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.nl.acs.autoThread.autoRunThread.service.enums.ThreadUsedStatusEnum;
import org.nl.acs.autoThread.service.enums.ThreadUsedStatusEnum;
import org.nl.acs.autoThread.service.enums.ThreadStatusEnum;
import java.util.Date;

View File

@@ -1,7 +1,7 @@
package org.nl.acs.autoThread.service.dto;
import lombok.Data;
import org.nl.acs.autoThread.autoRunThread.service.enums.ThreadUsedStatusEnum;
import org.nl.acs.autoThread.service.enums.ThreadUsedStatusEnum;
import org.nl.acs.autoThread.service.enums.ThreadStatusEnum;
import java.util.Date;

View File

@@ -1,4 +1,4 @@
package org.nl.acs.autoThread.autoRunThread.service.enums;
package org.nl.acs.autoThread.service.enums;
/**
* Demo class
*

View File

@@ -7,7 +7,7 @@ import org.apache.commons.lang3.StringUtils;
import org.nl.ApplicationAutoInitial;
import org.nl.acs.autoThread.autoRun.AbstractAutoRunnable;
import org.nl.acs.autoThread.StartConfig;
import org.nl.acs.autoThread.autoRunThread.service.enums.ThreadUsedStatusEnum;
import org.nl.acs.autoThread.service.enums.ThreadUsedStatusEnum;
import org.nl.acs.autoThread.service.AutoRunService;
import org.nl.acs.autoThread.service.dto.AutoRunDto;
import org.nl.common.exception.BadRequestException;

View File

@@ -1,15 +1,415 @@
### appInit
项目启动时候对项目进行初始化初始化器包括6项目
1.AutoRunService :根据配置判断哪些自动线程需要启动
2.自动线程继承AbstractAutoRunnable目前系统一共有4个自动线程
#### DeviceExecuteAutoRun
设备自动线程,完成每个设备驱动的加载
设备驱动通过while循环判断信号状态
#### NDCAutoRun
NDC自动线程通过该线程与NDC创建socket通讯做phace逻辑交互
该版本废弃统一改agvKit交互
#### DeviceOpcSynchronizeAutoRun
同步所有OpcService信号
该版本固定使用KEP-SERVER 进行信号同步
#### SocketListenerAutoRun
socket同步监听测试类在startconfig中配置不用
# 初始化自动线程文档
## 1. 自动线程系统概述
自动线程系统是 NL Admin System 的核心组件,负责管理系统启动时自动运行的后台任务。采用 **模板方法模式** + **服务发现机制**,实现线程的自动注册、启动和管理。
### 1.1 整体架构
```
┌─────────────────────────────────────────────────────────────┐
│ 应用启动阶段 │
├─────────────────────────────────────────────────────────────┤
│ ApplicationAutoInitial (Spring自动初始化接口) │
│ │ │
│ ▼ │
│ AutoRunServiceImpl.autoInitial() │
│ │ │
│ ├──► 扫描所有 AbstractAutoRunnable Bean │
│ ├──► 过滤检查 (code空/重复/禁止列表) │
│ ├──► 注册到 code_autoRun_Map │
│ └──► 创建线程并启动 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ AbstractAutoRunnable.run() │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ before() → autoRun() → after() │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ 前置处理 核心循环 后置清理 │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 1.2 核心组件
| 组件 | 职责 | 说明 |
|------|------|------|
| `ApplicationAutoInitial` | 自动初始化接口 | Spring启动时自动调用 `autoInitial()` |
| `AutoRunServiceImpl` | 线程管理器 | 负责线程的注册、启动、停止、状态查询 |
| `AbstractAutoRunnable` | 抽象基类 | 定义线程生命周期模板 |
| `StartConfig` | 启动配置 | 控制全局开关和禁止列表 |
---
## 2. 初始化流程
### 2.1 启动流程详解
```java
// AutoRunServiceImpl.autoInitial()
@Override
public void autoInitial() throws Exception {
// 1. 全局开关检查
if (StartConfig.Need_Stop) {
System.out.println("---未开启自动线程AutoRun初始化终止---");
return; // 全局禁止启动
}
// 2. 遍历所有AbstractAutoRunnable Bean
for (AbstractAutoRunnable autoRunnable : abstractAutoRunnableList) {
// 检查1: code不能为空
if (StringUtils.isEmpty(autoRunnable.getCode())) {
autoRunnable.setMessage("code为空未加载");
continue;
}
// 检查2: code不能重复
if (partialJudg.contains(autoRunnable.getCode())) {
autoRunnable.setMessage("code重复而未加载");
continue;
}
// 检查3: 不在禁止列表中
if (StartConfig.AutoRun_Ban.contains(autoRunnable.getCode())) {
autoRunnable.setMessage("配置了禁止启动");
continue;
}
// 通过所有检查注册到Map
autoRunnable.setUsedStatus(ThreadUsedStatusEnum.used);
code_autoRun_Map.put(autoRunnable.getCode(), autoRunnable);
}
// 3. 启动所有已注册的线程
for (AbstractAutoRunnable autoRunnable : code_autoRun_Map.values()) {
final Thread thread = new Thread(autoRunnable);
code_Thread_Map.put(autoRunnable.getCode(), thread);
thread.start();
}
}
```
### 2.2 过滤规则
| 检查项 | 条件 | 处理方式 |
|--------|------|----------|
| **code为空** | `StringUtils.isEmpty(code)` | 跳过,记录消息 |
| **code重复** | `partialJudg.contains(code)` | 跳过,记录消息 |
| **在禁止列表** | `StartConfig.AutoRun_Ban.contains(code)` | 跳过,记录消息 |
---
## 3. AbstractAutoRunnable 抽象基类
### 3.1 类结构
```java
@Data
public abstract class AbstractAutoRunnable implements Runnable {
private ThreadStatusEnum status; // 线程执行状态 (run/stop)
private Date startTime; // 线程开始时间
private Date stopTime; // 线程结束时间
private String message; // 状态消息
private ThreadUsedStatusEnum usedStatus; // 初始化状态 (used/unUsed)
@Override
public void run() {
this.setStatus(ThreadStatusEnum.run);
this.setStartTime(new Date());
try {
this.setMessage("自动线程初始化中");
this.before(); // 前置处理
this.autoRun(); // 核心业务(子类实现死循环)
this.setMessage("自动线程正常启动");
} catch (Throwable throwable) {
log.error(this.getCode() + "自动线程执行异常", throwable);
} finally {
this.setStopTime(new Date());
this.setMessage("自动线程关闭");
this.setStatus(ThreadStatusEnum.stop);
this.after(); // 后置清理
}
}
// 子类必须实现的方法
public abstract String getCode(); // 返回线程唯一标识
public abstract String getName(); // 返回线程名称
public abstract void autoRun() throws Exception; // 核心业务逻辑
// 可选覆盖方法
public void before() throws Exception {} // 前置处理
public void after() {} // 后置清理
public void stop() { this.after(); } // 停止线程
}
```
### 3.2 生命周期方法
| 方法 | 调用时机 | 用途 | 是否必须实现 |
|------|----------|------|--------------|
| `before()` | `run()` 开始时 | 初始化资源、建立连接 | 否 |
| `autoRun()` | `before()` 之后 | 核心业务逻辑(死循环) | **是** |
| `after()` | `run()` 结束时 | 释放资源、清理 | 否 |
| `stop()` | 手动停止时 | 触发停止流程 | 否 |
---
## 4. 启动配置
### 4.1 StartConfig 配置类
```java
public class StartConfig {
/**
* 全局开关true=禁止所有自动线程启动false=允许启动
*/
public static Boolean Need_Stop = Boolean.TRUE;
/**
* 禁止启动的线程code列表
*/
public static List<String> AutoRun_Ban = new ArrayList<>();
static {
AutoRun_Ban.add("TaskFeedbackAutoRun"); // 任务反馈线程(测试用)
AutoRun_Ban.add("SocketListenerAutoRun"); // Socket监听线程测试用
}
}
```
### 4.2 配置说明
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `Need_Stop` | Boolean | `true` | 全局开关,设为`false`时启动所有自动线程 |
| `AutoRun_Ban` | List | 包含2个元素 | 精确匹配线程code匹配到的线程不会启动 |
---
## 5. 现有自动线程
### 5.1 线程列表
| 线程类 | Code | 职责 | 状态 |
|--------|------|------|------|
| `DeviceExecuteAutoRunable` | `DeviceExecuteAutoRun` | 设备执行线程,执行所有设备驱动 | **运行中** |
| `OpcSignalSyncAutoRunnable` | `OpcSignalSyncAutoRun` | OPC信号同步线程 | **运行中** |
| `SocketListenerAutoRun` | `SocketListenerAutoRun` | Socket监听测试线程 | **已禁止** |
### 5.2 DeviceExecuteAutoRunable
**职责**:定期执行所有设备驱动的自动任务
```java
public class DeviceExecuteAutoRunable extends AbstractAutoRunnable {
@Override
public void autoRun() throws Exception {
// 等待OPC同步完成最多60秒
for (int i = 0; !OpcConfig.OPC_START_TAG; ++i) {
Thread.sleep(1000L);
if (i > 60) break;
}
// 死循环执行设备任务
while (true) {
Thread.sleep(100);
List<BaseDeviceDriver> deviceDrivers = deviceAppService.findDeviceDriver(BaseDeviceDriver.class);
for (BaseDeviceDriver driver : deviceDrivers) {
executorService.submit(() -> driver.executeAuto());
}
}
}
}
```
### 5.3 OpcSignalSyncAutoRunnable
**职责**同步所有OPC服务器的信号到系统内存
```java
public class OpcSignalSyncAutoRunnable extends AbstractAutoRunnable {
@Override
public void autoRun() throws Exception {
// 同步OPC信号逻辑
while (true) {
// 读取OPC服务器数据
// 更新到统一数据访问器(UDW)
Thread.sleep(50); // 控制同步频率
}
}
}
```
---
## 6. 线程管理 API
### 6.1 AutoRunService 接口
```java
public interface AutoRunService {
/**
* 启动指定线程
* @param code 线程唯一标识
*/
void startThread(String code);
/**
* 停止指定线程
* @param code 线程唯一标识
*/
void stopThread(String code);
/**
* 获取所有线程状态
* @return 线程状态列表
*/
List<AutoRunDto> findAll();
}
```
### 6.2 使用示例
```java
// 注入线程服务
@Autowired
private AutoRunService autoRunService;
// 启动线程
autoRunService.startThread("DeviceExecuteAutoRun");
// 停止线程
autoRunService.stopThread("DeviceExecuteAutoRun");
// 查询所有线程状态
List<AutoRunDto> threads = autoRunService.findAll();
for (AutoRunDto dto : threads) {
System.out.println("线程: " + dto.getName() + ", 状态: " + dto.getStatus());
}
```
### 6.3 AutoRunDto 数据结构
| 字段 | 类型 | 说明 |
|------|------|------|
| `code` | String | 线程唯一标识 |
| `name` | String | 线程名称 |
| `status` | String | 执行状态 (run/stop) |
| `startTime` | Date | 启动时间 |
| `stopTime` | Date | 停止时间 |
| `message` | String | 状态消息 |
| `thread_alive` | Boolean | 线程是否存活 |
| `thread_name` | String | JVM线程名称 |
| `thread_id` | String | JVM线程ID |
| `thread_state` | String | JVM线程状态 |
---
## 7. 扩展开发指南
### 7.1 创建新的自动线程
1. **继承 AbstractAutoRunnable**
```java
@Component
public class MyCustomAutoRun extends AbstractAutoRunnable {
@Override
public String getCode() {
return "MyCustomAutoRun"; // 唯一标识
}
@Override
public String getName() {
return "我的自定义线程";
}
@Override
public void autoRun() throws Exception {
// 死循环执行业务逻辑
while (true) {
// 执行任务...
Thread.sleep(1000); // 控制执行频率
}
}
@Override
public void before() throws Exception {
// 初始化资源(可选)
super.before();
}
@Override
public void after() {
// 清理资源(可选)
super.after();
}
}
```
2. **配置控制**
```java
// 在 StartConfig 中配置(如需禁止启动)
static {
AutoRun_Ban.add("TaskFeedbackAutoRun");
AutoRun_Ban.add("SocketListenerAutoRun");
// AutoRun_Ban.add("MyCustomAutoRun"); // 如需禁止,取消注释
}
```
### 7.2 注意事项
- **线程安全**`autoRun()` 中的共享资源需要加锁保护
- **异常处理**:建议在 `autoRun()` 内部捕获异常,避免线程意外终止
- **退出机制**:建议使用 `volatile` 标志位控制循环退出
---
## 8. 关键设计要点
### 8.1 服务发现机制
Spring 自动扫描所有 `AbstractAutoRunnable` 子类:
```java
@Autowired(required = false)
private List<AbstractAutoRunnable> abstractAutoRunnableList;
```
### 8.2 线程安全
```java
@Override
public synchronized void startThread(String code) { ... }
@Override
public synchronized void stopThread(String threadCode) { ... }
```
### 8.3 状态管理
使用枚举类管理状态:
```java
// ThreadStatusEnum - 执行状态
public enum ThreadStatusEnum {
run, // 运行中
stop // 已停止
}
// ThreadUsedStatusEnum - 初始化状态
public enum ThreadUsedStatusEnum {
used, // 已使用(已注册)
unUsed // 未使用(未注册)
}
```
---
**文档版本**: v1.0
**创建日期**: 2026年4月
**适用模块**: NL Admin System - 自动线程模块

View File

@@ -1,15 +1,509 @@
### appInit
项目启动时候对项目进行初始化初始化器包括6项目
1.AutoRunService :根据配置判断哪些自动线程需要启动
2.自动线程继承AbstractAutoRunnable目前系统一共有4个自动线程
#### DeviceExecuteAutoRun
设备自动线程,完成每个设备驱动的加载
设备驱动通过while循环判断信号状态
#### NDCAutoRun
NDC自动线程通过该线程与NDC创建socket通讯做phace逻辑交互
该版本废弃统一改agvKit交互
#### DeviceOpcSynchronizeAutoRun
同步所有OpcService信号
该版本固定使用KEP-SERVER 进行信号同步
#### SocketListenerAutoRun
socket同步监听测试类在startconfig中配置不用
# 设备信息管理文档
## 1. 设备信息加载流程
### 1.1 整体架构
设备信息加载采用 **服务发现 + 驱动绑定** 的设计模式:
```
┌─────────────────────────────────────────────────────────────────┐
│ 应用启动阶段 │
├─────────────────────────────────────────────────────────────────┤
│ ApplicationAutoInitial (自动初始化接口) │
│ │ │
│ ▼ │
│ DeviceAppServiceImpl.autoInitial() │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ reload() 方法 │ │
│ │ 1. 从数据库查询设备基本信息 (DeviceMapper) │ │
│ │ 2. 关联设备扩展信息 (DeviceExtraMapper) │ │
│ │ 3. 关联OPC/PLC配置 (OpcMapper/OpcPlcMapper) │ │
│ │ 4. 绑定设备驱动 (DeviceDriverDefination) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 内存缓存 (devices List) │ │
│ │ - devices: 设备对象列表 │ │
│ │ - code_indexs: 设备编码索引 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 1.2 核心代码流程
#### ① 自动初始化触发
```java
// DeviceAppServiceImpl.java
@Override
public void autoInitial() throws Exception {
log.info("加载所有设备");
this.reload(); // 核心加载方法
}
```
#### ② 设备信息查询与组装
```java
@Override
public synchronized void reload() {
// 1. 查询所有设备(包含扩展信息)
List<DeviceManageDto> deviceManageDtos = this.queryAllWithExtra();
// 2. 初始化内存缓存
this.devices = new ArrayList();
this.code_indexs = new ArrayList();
// 3. 遍历设备创建Device对象并绑定驱动
for (DeviceManageDto deviceManage : deviceManageDtos) {
Device device = new Device();
BeanUtil.copyProperties(deviceManage, device);
this.devices.add(device);
this.code_indexs.add(device.getDevice_code());
// 4. 绑定设备驱动(关键步骤)
if (!StrUtil.isEmpty(device.getDriver_code())) {
DeviceDriverDefination defination =
deviceDriverDefinationAppService.getDeviceDriverDefination(deviceManage.getDriver_code());
if (defination != null) {
device.setDeviceDriverDefination(defination);
DeviceDriver driver = defination.getDriverInstance(device); // 创建驱动实例
device.setDeviceDriver(driver);
}
}
}
}
```
#### ③ 数据库查询逻辑
`queryAllWithExtra()` 方法执行以下查询:
| 数据表 | 查询内容 | 用途 |
|--------|----------|------|
| `acs_device` | 设备基本信息 | 设备编码、名称、类型、地址等 |
| `acs_device_extra` | 设备扩展属性 | 站点管理、自定义配置等 |
| `acs_opc` | OPC服务器配置 | 设备通信连接信息 |
| `acs_opc_plc` | PLC配置 | 设备PLC连接信息 |
### 1.3 设备驱动绑定机制
设备驱动采用 **策略模式**,通过 `DeviceDriverDefination` 动态创建驱动实例:
```java
// DeviceDriverDefination.getDriverInstance(device)
// 根据驱动定义,为每个设备创建对应的驱动实例
DeviceDriver driver = defination.getDriverInstance(device);
device.setDeviceDriver(driver);
```
## 2. 设备信息数据结构
### 2.1 Device 实体
```java
public class Device {
private String device_code; // 设备编码(唯一标识)
private String device_name; // 设备名称
private String device_type; // 设备类型
private String address; // 设备地址
private String driver_code; // 驱动编码
private String opc_server_id; // OPC服务器ID
private String opc_plc_id; // PLC ID
private DeviceDriver deviceDriver; // 设备驱动实例
private DeviceDriverDefination deviceDriverDefination; // 驱动定义
}
```
### 2.2 DeviceDriver 抽象类
```java
public abstract class DeviceDriver {
private Device device; // 关联设备
private String deviceCode; // 设备编码
private DeviceDriverDefination deviceDriverDefination; // 驱动定义
private boolean stop; // 停止标志
public DeviceDriver init(Device device, DeviceDriverDefination defination) {
this.device = device;
this.deviceCode = device.getDevice_code();
this.deviceDriverDefination = defination;
return this;
}
}
```
## 3. 设备信息使用方式
### 3.1 基础查询接口
```java
// DeviceAppService 提供的查询方法
// 1. 查询所有设备
List<Device> devices = deviceAppService.findAllDevice();
// 2. 根据设备编码查询
Device device = deviceAppService.findDeviceByCode("DEVICE001");
// 3. 根据设备地址查询
Device device = deviceAppService.findDeviceByAddress("A01-01");
// 4. 根据设备类型查询
List<Device> devices = deviceAppService.findDeviceByType(DeviceType.STANDARD_INSPECT);
// 5. 获取设备驱动列表(按类型筛选)
List<BaseDeviceDriver> drivers = deviceAppService.findDeviceDriver(BaseDeviceDriver.class);
```
### 3.2 设备驱动调用
通过设备驱动执行具体业务操作:
```java
// 获取设备驱动
Device device = deviceAppService.findDeviceByCode("DEVICE001");
DeviceDriver driver = device.getDeviceDriver();
// 执行设备操作(具体方法由子类实现)
if (driver instanceof BaseDeviceDriver) {
BaseDeviceDriver baseDriver = (BaseDeviceDriver) driver;
baseDriver.executeAuto(); // 执行自动任务
}
```
### 3.3 自动线程调用
`DeviceExecuteAutoRunable` 自动线程定期执行设备任务:
```java
// DeviceExecuteAutoRunable.autoRun()
public void autoRun() throws Exception {
// 等待OPC同步完成最多60秒
for (int i = 0; !OpcConfig.OPC_START_TAG; ++i) {
Thread.sleep(1000L);
if (i > 60) break;
}
// 循环执行设备任务
while (true) {
Thread.sleep(100);
List<BaseDeviceDriver> deviceDrivers = deviceAppService.findDeviceDriver(BaseDeviceDriver.class);
for (BaseDeviceDriver driver : deviceDrivers) {
// 提交到线程池执行
executorService.submit(() -> driver.executeAuto());
}
}
}
```
### 3.4 设备驱动插件体系
设备驱动采用 **插件模式**,实现两个核心接口:
| 接口 | 职责 | 核心方法 |
|------|------|----------|
| `DriverExecutePlugin` | 设备执行逻辑 | `execute()` - 执行具体业务 |
| `DeviceStageMonitorPlugin` | 设备状态监控 | `getDeviceStatusName()` - 获取状态 |
```java
// DriverExecutePlugin 接口
public interface DriverExecutePlugin {
default void executeAuto() {
try {
this.execute(); // 调用子类实现
} catch (Exception ex) {
ex.printStackTrace();
}
}
void execute() throws Exception; // 子类必须实现
}
// DeviceStageMonitorPlugin 接口
public interface DeviceStageMonitorPlugin {
JSONObject getDeviceStatusName() throws Exception; // 获取设备状态
void setDeviceStatus(JSONObject data); // 设置设备状态
}
```
### 3.5 OPC设备驱动使用
`AbstractOpcDeviceDriver` 是 OPC 设备驱动的基类,提供以下核心能力:
#### ① OPC信号读取
```java
// 获取设备驱动
Device device = deviceAppService.findDeviceByCode("DEVICE001");
AbstractOpcDeviceDriver opcDriver = (AbstractOpcDeviceDriver) device.getDeviceDriver();
// 读取单个OPC信号值
Integer value = opcDriver.getIntegeregerValue("heartbeat"); // 获取心跳值
Object rawValue = opcDriver.getValue("running"); // 获取原始值
// 获取所有OPC点位配置
List<OpcItemDto> opcItems = opcDriver.getOpcItems();
for (OpcItemDto item : opcItems) {
String itemCode = item.getItem_code();
String deviceCode = item.getDevice_code();
// 处理OPC点位
}
```
#### ② OPC信号写入
```java
// 构建要写入的信号数据
Map<String, Object> itemValues = new HashMap<>();
itemValues.put("opc_server.plc.device001.start", 1); // 启动信号
itemValues.put("opc_server.plc.device001.speed", 100); // 速度设置
// 调用控制方法(带重试机制)
opcDriver.checkcontrol(itemValues);
// 或者直接调用control方法
boolean result = opcDriver.control(itemValues);
if (!result) {
throw new RuntimeException("OPC写入失败");
}
```
#### ③ 获取设备扩展信息
```java
// 获取设备扩展配置
Map<String, Object> extraValues = opcDriver.getExtraValue();
String stationManager = (String) extraValues.get("station_manager");
// 获取设备连接信息
String opcServer = opcDriver.getOpcServer(); // OPC服务器编码
String opcPlc = opcDriver.getOpcPlc(); // PLC编码
String deviceCode = opcDriver.getDeviceCode(); // 设备编码
```
### 3.6 驱动执行上下文
每个驱动实例持有设备信息和统一数据访问器:
```java
// 获取关联设备
Device device = driver.getDevice();
// 获取驱动定义
DeviceDriverDefination defination = driver.getDriverDefination();
// 获取统一数据访问器用于读写OPC数据
UnifiedDataAccessor opcUdw = opcDriver.getOpcValueAccessor();
Object value = opcUdw.getValue("opc_server.plc.device001.status");
```
## 4. 业务场景示例
### 4.1 场景一:设备状态监控
```java
// 查询所有设备状态
List<Device> devices = deviceAppService.findAllDevice();
for (Device device : devices) {
DeviceDriver driver = device.getDeviceDriver();
if (driver instanceof AbstractOpcDeviceDriver) {
AbstractOpcDeviceDriver opcDriver = (AbstractOpcDeviceDriver) driver;
List<OpcItemDto> opcItems = opcDriver.getOpcItems();
// 处理OPC信号数据
}
// 获取设备状态名称(故障、联机等)
if (driver instanceof DeviceStageMonitorPlugin) {
DeviceStageMonitorPlugin monitor = (DeviceStageMonitorPlugin) driver;
JSONObject status = monitor.getDeviceStatusName();
String statusName = status.getString("statusName");
System.out.println("设备 " + device.getDevice_code() + " 状态: " + statusName);
}
}
```
### 4.2 场景二:设备指令执行
```java
// 根据设备编码执行指令
String deviceCode = "CONVEYOR001";
Device device = deviceAppService.findDeviceByCode(deviceCode);
if (device != null && device.getDeviceDriver() instanceof BaseDeviceDriver) {
BaseDeviceDriver driver = (BaseDeviceDriver) device.getDeviceDriver();
driver.executeAuto(); // 执行设备自动任务
}
```
### 4.3 场景三:设备动态管理
```java
// 运行时添加设备
deviceAppService.addDevice("NEW_DEVICE_001");
// 运行时移除设备
deviceAppService.removeDevice("DEVICE_TO_REMOVE");
// 重新加载所有设备
deviceAppService.reload();
```
### 4.4 场景四OPC信号批量写入
```java
// 批量控制多个设备
List<String> deviceCodes = Arrays.asList("CONVEYOR001", "CONVEYOR002", "CONVEYOR003");
for (String deviceCode : deviceCodes) {
Device device = deviceAppService.findDeviceByCode(deviceCode);
if (device != null && device.getDeviceDriver() instanceof AbstractOpcDeviceDriver) {
AbstractOpcDeviceDriver opcDriver = (AbstractOpcDeviceDriver) device.getDeviceDriver();
// 构建控制信号
Map<String, Object> signals = new HashMap<>();
signals.put(opcDriver.getItem("start"), 1);
signals.put(opcDriver.getItem("speed"), 50);
// 执行写入(带重试机制)
try {
opcDriver.checkcontrol(signals);
log.info("设备 {} 控制成功", deviceCode);
} catch (Exception e) {
log.error("设备 {} 控制失败: {}", deviceCode, e.getMessage());
}
}
}
```
### 4.5 场景五:设备状态同步
```java
// 从OPC读取设备状态并更新到系统
String deviceCode = "LIFT001";
Device device = deviceAppService.findDeviceByCode(deviceCode);
if (device != null && device.getDeviceDriver() instanceof AbstractOpcDeviceDriver) {
AbstractOpcDeviceDriver opcDriver = (AbstractOpcDeviceDriver) device.getDeviceDriver();
// 读取多个信号
Integer heartbeat = opcDriver.getIntegeregerValue("heartbeat");
Integer running = opcDriver.getIntegeregerValue("running");
Integer fault = opcDriver.getIntegeregerValue("fault");
// 构建状态数据
JSONObject statusData = new JSONObject();
statusData.put("deviceCode", deviceCode);
statusData.put("heartbeat", heartbeat);
statusData.put("running", running == 1);
statusData.put("fault", fault == 1);
// 更新设备状态
DeviceStageMonitorPlugin monitor = (DeviceStageMonitorPlugin) opcDriver;
monitor.setDeviceStatus(statusData);
// 存储到数据库或缓存
deviceStatusService.updateStatus(deviceCode, statusData);
}
```
### 4.6 场景六:自定义驱动执行
```java
// 创建自定义任务执行器
public class CustomDeviceTask implements Runnable {
private BaseDeviceDriver driver;
public CustomDeviceTask(BaseDeviceDriver driver) {
this.driver = driver;
}
@Override
public void run() {
try {
// 执行前置检查
if (!checkDeviceReady(driver)) {
log.warn("设备 {} 未就绪", driver.getDeviceCode());
return;
}
// 执行驱动逻辑
driver.execute();
// 记录执行日志
log.info("设备 {} 任务执行完成", driver.getDeviceCode());
} catch (Exception e) {
log.error("设备 {} 任务执行失败: {}", driver.getDeviceCode(), e.getMessage());
}
}
private boolean checkDeviceReady(BaseDeviceDriver driver) {
// 检查设备状态、连接状态等
return true;
}
}
// 使用示例
BaseDeviceDriver driver = deviceAppService.findDeviceDriver("CONVEYOR001");
if (driver != null) {
executorService.submit(new CustomDeviceTask(driver));
}
```
## 5. 关键设计要点
### 5.1 内存缓存策略
| 设计点 | 说明 |
|--------|------|
| **缓存时机** | 应用启动时加载,运行时动态更新 |
| **缓存结构** | `devices` 列表 + `code_indexs` 索引 |
| **线程安全** | 使用 `synchronized``Collections.synchronizedList()` |
| **更新机制** | 提供 `reload()``addDevice()``removeDevice()` 方法 |
### 5.2 驱动解耦设计
- **策略模式**:通过 `DeviceDriverDefination` 解耦驱动定义与实现
- **依赖注入**Spring 自动扫描驱动实现类
- **动态创建**:根据 `driver_code` 动态创建对应驱动实例
### 5.3 配置校验
在加载设备时进行多重校验:
```java
// queryAllWithExtra() 中的校验逻辑
if (opc == null) {
log.info("设备:{},配置的OPC数据源不存在", device_code);
continue; // 跳过无效配置
}
if (opcPlc == null) {
log.info("设备:{},配置的PLC数据源不存在", device_code);
continue; // 跳过无效配置
}
```
## 6. 扩展开发指南
### 6.1 添加新设备类型
1. 在数据库中添加设备记录(`acs_device` 表)
2. 配置设备扩展信息(`acs_device_extra` 表)
3. 配置 OPC/PLC 连接信息(`acs_opc``acs_opc_plc` 表)
4. 确保对应的驱动已注册
### 6.2 添加新设备驱动
1. 继承 `DeviceDriver` 抽象类
2. 实现具体的业务逻辑方法
3.`DeviceDriverDefination` 中注册驱动
---
**文档版本**: v1.0
**创建日期**: 2026年4月
**适用模块**: NL Admin System - 设备管理模块

View File

@@ -1,7 +1,334 @@
### storageMgt
货位管理模块主要是AGV取放货的点
1.货位编码与上位系统保持一致
2.AGV地址有2个正常逻辑取放地址是同一个,如果取放货位不一样需要配合adress1共同使用
3.offset货位偏移量主要用于ACSKit套件对接AGV到达取放货位时会请求取放货这时通过offset偏移量来下发给kit
4.has:有无货状态AGV取放货完成后更新该状态
# 设备协议管理文档
## 1. 设备协议概述
设备协议模块负责管理设备与PLC之间的通信协议配置主要功能包括
- 设备协议信息查询
- 协议数据导出支持多种PLC类型
- 协议格式校验与转换
### 1.1 协议数据结构
设备协议数据存储在 `acs_device_extra` 表中,核心字段:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| `device_id` | VARCHAR | 关联设备ID |
| `extra_code` | VARCHAR | 协议编码格式OPC_SERVER.PLC.DEVICE.CODE |
| `extra_name` | VARCHAR | 协议名称PLC地址如 DB100.B1 |
| `extra_value` | VARCHAR | 协议值 |
| `filed_type` | VARCHAR | 字段类型02-输入点位03-输出点位) |
### 1.2 协议编码格式
协议编码采用 **四级分层结构**
```
OPC_SERVER_CODE.PLC_CODE.DEVICE_CODE.POINT_CODE
↓ ↓ ↓ ↓
RD1 RD1 A1 mode
(OPC服务器) (PLC编码) (设备编码) (点位编码)
```
示例:`RD1.RD1.A1.mode`
---
## 2. 核心服务接口
### 2.1 DeviceProtocolService
```java
public interface DeviceProtocolService {
/**
* 分页查询设备协议
* @param whereJson 查询条件
* @param page 分页参数
* @return 分页结果
*/
Map<String, Object> queryDeviceProtocol(Map whereJson, Pageable page);
/**
* 查询设备协议列表(不分页)
* @param whereJson 查询条件
* @return 协议列表
*/
JSONArray queryDeviceProtocol(Map whereJson);
/**
* 导出设备DB协议Excel格式
* @param jsonarr 协议数据
* @param response HTTP响应
*/
void downDeviceDBload(JSONArray jsonarr, HttpServletResponse response) throws IOException;
/**
* 导出Smart设备协议CSV
* @param queryDeviceProtocol 协议数据
* @param response HTTP响应
*/
void downDeviceDBloadSmartCSV(JSONArray queryDeviceProtocol, HttpServletResponse response);
/**
* 导出FX5U设备协议CSV
* @param queryDeviceProtocol 协议数据
* @param response HTTP响应
*/
void downDeviceDBloadFX5UCSV(JSONArray queryDeviceProtocol, HttpServletResponse response);
/**
* 导出通用CSV西门子1200/1500
* @param queryDeviceProtocol 协议数据
* @param response HTTP响应
*/
void downDeviceDBloadCSV(JSONArray queryDeviceProtocol, HttpServletResponse response);
}
```
### 2.2 查询条件说明
| 参数名 | 类型 | 说明 |
|--------|------|------|
| `device_types` | String | 设备类型筛选 |
| `blurry` | String | 模糊搜索(设备编码或名称) |
| `opc_id` | String | OPC服务器ID筛选 |
---
## 3. 协议导出功能
### 3.1 支持的PLC类型
| PLC类型 | 导出方法 | 数据类型映射 |
|---------|----------|--------------|
| **西门子200 Smart** | `downDeviceDBloadSmartCSV()` | W→Word, D→DWord, 含"."→Boolean |
| **三菱FX5U** | `downDeviceDBloadFX5UCSV()` | D→Long, 含"."→Boolean |
| **西门子1200/1500** | `downDeviceDBloadCSV()` | DBx.By→Byte/Boolean, W→Word, D→DWord |
### 3.2 CSV导出格式
导出的CSV文件包含以下列
| 列名 | 说明 | 示例 |
|------|------|------|
| **Tag Name** | 点位编码 | `mode` |
| **Address** | PLC地址 | `DB100.B1.0` |
| **Data Type** | 数据类型 | `Boolean` / `Word` / `DWord` |
| **Respect Data Type** | 保留字段 | `1` |
| **Client Access** | 访问权限 | `R/W` |
| **Scan Rate** | 扫描速率(ms) | `100` |
### 3.3 协议格式校验
在导出前会进行格式校验:
```java
// 校验协议编码格式必须包含3个点
int num = countStr(extra_code, ".");
if (num != 3) {
throw new BadRequestException("协议编码格式不正确: " + extra_code);
}
```
---
## 4. 数据类型映射规则
### 4.1 西门子200 Smart 映射规则
```java
String datatype = jo.getString("extra_name");
if (!datatype.contains(".")) {
String[] split = datatype.split("");
if (split[1].equals("W")) {
datatype = "Word"; // 如 VW100 → Word
} else if (split[1].equals("D")) {
datatype = "DWord"; // 如 VD100 → DWord
}
} else {
datatype = "Boolean"; // 如 I0.0 → Boolean
}
```
### 4.2 三菱FX5U 映射规则
```java
if (!datatype.contains(".")) {
String[] split = datatype.split("");
if (split[1].equals("D")) {
datatype = "Long"; // 如 D100 → Long
}
} else {
datatype = "Boolean"; // 如 X0.0 → Boolean
}
```
### 4.3 西门子1200/1500 映射规则
```java
if (datatype.startsWith("DB")) {
String str1 = datatype.substring(0, datatype.indexOf("."));
String headtype = datatype.substring(str1.length() + 1);
if (headtype.startsWith("B")) {
if (headtype.contains(".")) {
datatype = "Boolean"; // DB100.B1.0 → Boolean
} else {
datatype = "Byte"; // DB100.B1 → Byte
}
} else if (headtype.startsWith("W")) {
datatype = "Word"; // DB100.W1 → Word
} else if (headtype.startsWith("D")) {
datatype = "DWord"; // DB100.D1 → DWord
} else if (headtype.startsWith("S")) {
datatype = "String"; // DB100.S1 → String
} else if (headtype.startsWith("REAL")) {
datatype = "REAL"; // DB100.REAL1 → REAL
}
}
```
---
## 5. 使用示例
### 5.1 查询设备协议
```java
// 构建查询条件
Map<String, Object> whereJson = new HashMap<>();
whereJson.put("device_types", "CONVEYOR"); // 筛选设备类型
whereJson.put("blurry", "A1"); // 模糊搜索
whereJson.put("opc_id", "OPC_SERVER_01"); // 筛选OPC服务器
// 分页查询
Pageable page = PageRequest.of(0, 10);
Map<String, Object> result = deviceProtocolService.queryDeviceProtocol(whereJson, page);
// 不分页查询
JSONArray protocols = deviceProtocolService.queryDeviceProtocol(whereJson);
```
### 5.2 导出协议CSV
```java
// 查询协议数据
JSONArray protocols = deviceProtocolService.queryDeviceProtocol(whereJson);
// 导出西门子1200/1500格式
deviceProtocolService.downDeviceDBloadCSV(protocols, response);
// 或者导出西门子200 Smart格式
deviceProtocolService.downDeviceDBloadSmartCSV(protocols, response);
// 或者导出三菱FX5U格式
deviceProtocolService.downDeviceDBloadFX5UCSV(protocols, response);
```
### 5.3 导出协议Excel
```java
JSONArray protocols = deviceProtocolService.queryDeviceProtocol(whereJson);
deviceProtocolService.downDeviceDBload(protocols, response);
```
---
## 6. 业务场景
### 6.1 场景一批量导出PLC配置
```java
// 查询所有设备协议
Map<String, Object> whereJson = new HashMap<>();
JSONArray protocols = deviceProtocolService.queryDeviceProtocol(whereJson);
// 根据PLC类型选择导出方法
String plcType = "SIEMENS_1200";
switch (plcType) {
case "SIEMENS_200SMART":
deviceProtocolService.downDeviceDBloadSmartCSV(protocols, response);
break;
case "MITSUBISHI_FX5U":
deviceProtocolService.downDeviceDBloadFX5UCSV(protocols, response);
break;
case "SIEMENS_1200":
case "SIEMENS_1500":
default:
deviceProtocolService.downDeviceDBloadCSV(protocols, response);
break;
}
```
### 6.2 场景二:按设备类型导出
```java
// 只导出特定类型设备的协议
Map<String, Object> whereJson = new HashMap<>();
whereJson.put("device_types", "LIFT"); // 只导出提升机设备
JSONArray protocols = deviceProtocolService.queryDeviceProtocol(whereJson);
deviceProtocolService.downDeviceDBloadCSV(protocols, response);
```
### 6.3 场景三前端API调用
```javascript
// 查询协议列表
fetch('/api/deviceProtocol', {
method: 'GET',
params: {
device_types: 'CONVEYOR',
blurry: 'A1'
}
}).then(res => res.json())
.then(data => console.log(data));
// 导出CSV
window.open('/api/deviceProtocol/export?plcType=SIEMENS_1200');
```
---
## 7. 关键设计要点
### 7.1 协议编码规范
协议编码必须遵循以下格式:
```
OPC_SERVER.PLC.DEVICE.POINT
```
**示例**
- `RD1.RD1.A1.mode` - A1设备的mode点位
- `OPC_SRV01.PLC01.CONV001.start` - CONV001设备的start点位
### 7.2 数据类型自动识别
系统根据协议名称自动识别数据类型:
| 协议名称特征 | 识别的数据类型 |
|--------------|----------------|
| 包含 `.` 且以数字结尾 | Boolean如 DB100.B1.0 |
| 以 `W` 开头(不含`.` | Word如 VW100 |
| 以 `D` 开头(不含`.` | DWord 或 Long |
| 以 `DB` 开头 | 根据后续字符判断 |
### 7.3 协议分类
根据 `filed_type` 字段区分协议类型:
| filed_type | 含义 | 用途 |
|------------|------|------|
| `01` | 设备属性 | 设备基础配置(不参与协议导出) |
| `02` | 输入点位 | PLC输入信号如传感器状态 |
| `03` | 输出点位 | PLC输出信号如控制指令 |
---
**文档版本**: v1.0
**创建日期**: 2026年4月
**适用模块**: NL Admin System - 设备协议模块

View File

@@ -11,10 +11,10 @@
加载所有设备信息devices至本地内存
#### 3.StorageCellServiceImpl
加载货位信息至内存
加载货位信息至内存
#### NDCAutoRun
NDC自动线程通过该线程与NDC创建socket通讯做phace逻辑交互
该版本废弃统一改agvKit交互
NDC自动线程通过该线程与NDC创建socket通讯做phace逻辑交互
该版本废弃统一改agvKit交互
#### DeviceOpcSynchronizeAutoRun
同步所有OpcService信号
该版本固定使用KEP-SERVER 进行信号同步

View File

@@ -0,0 +1,470 @@
# 前端开发手册
## 1. 项目概述
### 1.1 项目简介
本项目是 **EL-ADMIN** 前端管理系统,基于 Vue 2.x 技术栈构建,是一个功能完善的企业级后台管理系统。
### 1.2 技术栈
| 技术 | 版本 | 说明 |
|------|------|------|
| Vue | 2.6.10 | 前端框架 |
| Vue Router | 3.0.2 | 路由管理 |
| Vuex | 3.1.0 | 状态管理 |
| Element UI | 2.15.8 | UI组件库 |
| Axios | 0.18.1 | HTTP客户端 |
| ECharts | 4.2.1 | 图表库 |
| LogicFlow | 1.2.1 | 流程图编辑器 |
### 1.3 主要功能模块
- **系统管理**:用户管理、角色管理、菜单管理、部门管理、数据字典等
- **ACS管理**:设备管理、任务管理、区域管理、路线规划等
- **布局管理**:地图管理、设备布局等
- **监控管理**:日志监控、在线监控、大屏展示等
- **轨迹管理**:轨迹配置、轨迹后台管理
---
## 2. 开发环境配置
### 2.1 环境要求
| 工具 | 最低版本 | 推荐版本 |
|------|----------|----------|
| Node.js | >= 8.9 | >= 14.x |
| npm | >= 3.0.0 | >= 6.x |
### 2.2 安装依赖
```bash
# 进入项目目录
cd d:\诺力git\stand_acs\acs2\nladmin-ui
# 安装依赖
npm install
```
### 2.3 开发命令
```bash
# 启动开发服务器
npm run dev
# 生产环境构建
npm run build:prod
# 测试环境构建
npm run build:stage
# ESLint代码检查
npm run lint
# 单元测试
npm run test:unit
# 生成新页面(模板)
npm run new
```
---
## 3. 项目结构
```
src/
├── api/ # API接口定义
│ ├── acs/ # ACS模块接口
│ ├── layout/ # 布局模块接口
│ ├── system/ # 系统模块接口
│ ├── monitor/ # 监控模块接口
│ └── ...
├── assets/ # 静态资源
│ ├── icons/ # SVG图标
│ ├── images/ # 图片资源
│ ├── styles/ # 全局样式
│ └── js/ # 第三方JS库
├── components/ # 公共组件
│ ├── Crud/ # CRUD组件
│ ├── Dict/ # 数据字典组件
│ ├── Permission/ # 权限组件
│ └── IconSelect/ # 图标选择组件
├── layout/ # 布局组件
│ ├── components/ # 布局子组件
│ └── mixin/ # 布局混入
├── router/ # 路由配置
│ ├── index.js # 路由权限控制
│ └── routers.js # 路由定义
├── store/ # Vuex状态管理
│ ├── modules/ # 状态模块
│ ├── getters.js # 计算属性
│ └── index.js # 状态入口
├── utils/ # 工具函数
│ ├── request.js # Axios封装
│ ├── auth.js # 认证工具
│ └── nladmin.js # 通用工具
├── views/ # 页面视图
│ ├── system/ # 系统管理页面
│ ├── acs/ # ACS管理页面
│ ├── monitor/ # 监控页面
│ └── dashboard/ # 仪表盘
├── i18n/ # 国际化配置
├── App.vue # 根组件
├── main.js # 入口文件
└── settings.js # 系统配置
```
---
## 4. 核心模块说明
### 4.1 入口文件 (main.js)
**职责**:项目初始化、全局配置、第三方库注册
**关键配置**
- Element UI 全局配置(默认尺寸、表格边框等)
- 全局方法挂载parseTime、resetForm、selectDictLabel等
- 国际化配置
### 4.2 路由管理
#### 4.2.1 路由定义 (router/routers.js)
路由采用异步路由加载方式,支持动态菜单加载:
```javascript
{
path: '/system/user',
component: Layout,
redirect: '/system/user/index',
name: 'User',
meta: { title: 'user', icon: 'user', roles: ['admin', 'user'] },
children: [
{
path: 'index',
component: () => import('@/views/system/user/index'),
name: 'UserIndex',
meta: { title: 'userList', icon: 'user', roles: ['admin', 'user'] }
}
]
}
```
#### 4.2.2 权限控制 (router/index.js)
实现了完整的路由守卫机制:
- 登录状态检查
- 动态菜单加载
- 权限验证
### 4.3 HTTP请求封装 (utils/request.js)
**核心特性**
- 请求拦截器自动添加Token、语言标识
- 响应拦截器统一错误处理、401/403状态码处理
- 支持blob下载错误处理
```javascript
// 请求拦截器
service.interceptors.request.use(
config => {
if (getToken()) {
config.headers['Authorization'] = 'Bearer ' + getToken()
}
config.headers['Accept-Language'] = localStorage.getItem('lang')
return config
}
)
```
### 4.4 状态管理 (store/)
| 模块 | 职责 |
|------|------|
| app.js | 应用基础状态(侧边栏、设备信息等) |
| permission.js | 权限路由状态 |
| settings.js | 系统设置(主题、布局等) |
| tagsView.js | 标签页状态 |
| user.js | 用户信息状态 |
---
## 5. 开发规范
### 5.1 代码规范
#### 5.1.1 ESLint规则
项目使用 `eslint-plugin-vue` 进行代码检查,配置文件:`.eslintrc.js`
**核心规则**
- 使用 4 空格缩进
- 语句结尾必须有分号
- 单引号优先
- 禁止未使用的变量
- Vue组件必须有name属性
#### 5.1.2 Git钩子
通过 `husky``lint-staged` 实现提交前代码检查:
```javascript
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
}
```
### 5.2 组件开发规范
#### 5.2.1 组件命名
- **组件名**大驼峰命名PascalCase`UserList.vue`
- **组件文件**:放在对应模块目录下
#### 5.2.2 组件结构
```vue
<template>
<!-- 模板内容 -->
</template>
<script>
export default {
name: 'ComponentName',
components: {},
props: {},
data() {
return {}
},
computed: {},
watch: {},
created() {},
mounted() {},
methods: {}
}
</script>
<style scoped>
/* 样式 */
</style>
```
### 5.3 API接口规范
#### 5.3.1 接口定义
每个模块对应一个API文件统一放在 `src/api/` 目录下:
```javascript
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/api/system/user',
method: 'get',
params
})
}
```
#### 5.3.2 接口命名
| 操作 | 方法 | 命名示例 |
|------|------|----------|
| 查询列表 | GET | `getList` |
| 查询详情 | GET | `getInfo` |
| 新增 | POST | `add` |
| 更新 | PUT | `update` |
| 删除 | DELETE | `delete` |
### 5.4 国际化规范
项目支持中文、英文、印尼语三种语言,配置文件位于 `src/i18n/langs/`
**使用方式**
```vue
<template>
<span>{{ $t('user.userName') }}</span>
</template>
<script>
export default {
mounted() {
console.log(this.$t('common.success'))
}
}
</script>
```
---
## 6. 常用工具函数
### 6.1 时间处理
```javascript
// 格式化时间
this.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
```
### 6.2 数据字典
```javascript
// 获取字典标签
this.selectDictLabel(dictList, value)
// 获取多个字典标签
this.selectDictLabels(dictList, values, ',')
```
### 6.3 表单操作
```javascript
// 重置表单
this.resetForm('formName')
// 添加日期范围
this.addDateRange(params, startDate, endDate)
```
### 6.4 树结构处理
```javascript
// 处理树形数据
const treeData = this.handleTree(data, 'id', 'parentId')
```
---
## 7. CRUD开发指南
### 7.1 使用Crud组件
项目提供了通用的Crud组件简化增删改查操作
```vue
<template>
<div class="app-container">
<Crud ref="crud" :option="crudOption" />
</div>
</template>
<script>
import { crud } from '@/mixins/crud'
export default {
name: 'User',
mixins: [crud],
data() {
return {
crudOption: {
title: 'user',
url: '/api/system/user',
columns: [
{ label: 'userName', prop: 'username' },
{ label: 'email', prop: 'email' }
]
}
}
}
}
</script>
```
### 7.2 自定义表单
```vue
<template>
<el-dialog :title="title" :visible.sync="visible">
<el-form ref="form" :model="form" label-width="100px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="submit">确定</el-button>
</div>
</el-dialog>
</template>
```
---
## 8. 权限管理
### 8.1 按钮权限
使用 `v-permission` 指令控制按钮显示:
```vue
<el-button v-permission="['admin', 'user:edit']" type="primary">编辑</el-button>
```
### 8.2 角色权限
路由配置中通过 `meta.roles` 控制访问权限:
```javascript
{
path: 'index',
component: () => import('@/views/system/user/index'),
meta: { roles: ['admin', 'user'] }
}
```
---
## 9. 构建部署
### 9.1 环境变量
| 文件 | 环境 | 说明 |
|------|------|------|
| .env.development | 开发环境 | 本地开发配置 |
| .env.production | 生产环境 | 生产部署配置 |
### 9.2 构建命令
```bash
# 生产构建
npm run build:prod
# 构建产物目录dist/
```
### 9.3 部署方式
1.`dist/` 目录部署到静态服务器
2. 配置Nginx反向代理
```nginx
server {
listen 80;
server_name your-domain.com;
location / {
root /path/to/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend-server:8080;
proxy_set_header Host $host;
}
}
```
---