opt:md文件优化
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
acs2/.idea/
|
acs2/.idea/
|
||||||
|
acs2/nladmin-system/logPath_IS_UNDEFINED/
|
||||||
|
|||||||
537
acs2/nladmin-system/DEVELOPMENT_GUIDE.md
Normal file
537
acs2/nladmin-system/DEVELOPMENT_GUIDE.md
Normal 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. 检查权限配置是否正确
|
||||||
|
|
||||||
|
---
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.nl.acs.autoThread.autoRun;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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 org.nl.acs.autoThread.service.enums.ThreadStatusEnum;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.nl.acs.autoThread.service.dto;
|
package org.nl.acs.autoThread.service.dto;
|
||||||
|
|
||||||
import lombok.Data;
|
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 org.nl.acs.autoThread.service.enums.ThreadStatusEnum;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.nl.acs.autoThread.autoRunThread.service.enums;
|
package org.nl.acs.autoThread.service.enums;
|
||||||
/**
|
/**
|
||||||
* Demo class
|
* Demo class
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.nl.ApplicationAutoInitial;
|
import org.nl.ApplicationAutoInitial;
|
||||||
import org.nl.acs.autoThread.autoRun.AbstractAutoRunnable;
|
import org.nl.acs.autoThread.autoRun.AbstractAutoRunnable;
|
||||||
import org.nl.acs.autoThread.StartConfig;
|
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.AutoRunService;
|
||||||
import org.nl.acs.autoThread.service.dto.AutoRunDto;
|
import org.nl.acs.autoThread.service.dto.AutoRunDto;
|
||||||
import org.nl.common.exception.BadRequestException;
|
import org.nl.common.exception.BadRequestException;
|
||||||
|
|||||||
@@ -1,15 +1,415 @@
|
|||||||
### appInit
|
# 初始化自动线程文档
|
||||||
项目启动时候对项目进行初始化,初始化器包括6项目
|
|
||||||
1.AutoRunService :根据配置判断哪些自动线程需要启动
|
## 1. 自动线程系统概述
|
||||||
2.自动线程继承AbstractAutoRunnable,目前系统一共有4个自动线程
|
|
||||||
#### DeviceExecuteAutoRun
|
自动线程系统是 NL Admin System 的核心组件,负责管理系统启动时自动运行的后台任务。采用 **模板方法模式** + **服务发现机制**,实现线程的自动注册、启动和管理。
|
||||||
设备自动线程,完成每个设备驱动的加载
|
|
||||||
设备驱动通过while循环判断信号状态
|
### 1.1 整体架构
|
||||||
#### NDCAutoRun
|
|
||||||
NDC自动线程,通过该线程与NDC创建socket通讯,做phace逻辑交互
|
```
|
||||||
该版本废弃:统一改agvKit交互
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
#### DeviceOpcSynchronizeAutoRun
|
│ 应用启动阶段 │
|
||||||
同步所有OpcService信号,
|
├─────────────────────────────────────────────────────────────┤
|
||||||
该版本固定使用KEP-SERVER 进行信号同步
|
│ ApplicationAutoInitial (Spring自动初始化接口) │
|
||||||
#### SocketListenerAutoRun
|
│ │ │
|
||||||
socket同步监听测试类,在startconfig中配置不用
|
│ ▼ │
|
||||||
|
│ 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 - 自动线程模块
|
||||||
|
|||||||
@@ -1,15 +1,509 @@
|
|||||||
### appInit
|
# 设备信息管理文档
|
||||||
项目启动时候对项目进行初始化,初始化器包括6项目
|
|
||||||
1.AutoRunService :根据配置判断哪些自动线程需要启动
|
## 1. 设备信息加载流程
|
||||||
2.自动线程继承AbstractAutoRunnable,目前系统一共有4个自动线程
|
|
||||||
#### DeviceExecuteAutoRun
|
### 1.1 整体架构
|
||||||
设备自动线程,完成每个设备驱动的加载
|
|
||||||
设备驱动通过while循环判断信号状态
|
设备信息加载采用 **服务发现 + 驱动绑定** 的设计模式:
|
||||||
#### NDCAutoRun
|
|
||||||
NDC自动线程,通过该线程与NDC创建socket通讯,做phace逻辑交互
|
```
|
||||||
该版本废弃:统一改agvKit交互
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
#### DeviceOpcSynchronizeAutoRun
|
│ 应用启动阶段 │
|
||||||
同步所有OpcService信号,
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
该版本固定使用KEP-SERVER 进行信号同步
|
│ ApplicationAutoInitial (自动初始化接口) │
|
||||||
#### SocketListenerAutoRun
|
│ │ │
|
||||||
socket同步监听测试类,在startconfig中配置不用
|
│ ▼ │
|
||||||
|
│ 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 - 设备管理模块
|
||||||
|
|||||||
@@ -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 - 设备协议模块
|
||||||
|
|||||||
470
acs2/nladmin-ui/FE_DEVELOPMENT_GUIDE.md
Normal file
470
acs2/nladmin-ui/FE_DEVELOPMENT_GUIDE.md
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
Reference in New Issue
Block a user