mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
Merge pull request #999 from xinnan-tech/auto-ota-update
Auto ota update
This commit is contained in:
+23
-5
@@ -122,11 +122,11 @@ http://localhost:8002/xiaozhi/doc.html
|
|||||||
|
|
||||||
这时,你需要使用浏览器,打开`智控台`,链接:http://127.0.0.1:8002 ,注册第一个用户。第一个用户即是超级管理员,以后的用户都是普通用户。普通用户只能绑定设备和配置智能体;超级管理员可以进行模型管理、用户管理、参数配置等功能。
|
这时,你需要使用浏览器,打开`智控台`,链接:http://127.0.0.1:8002 ,注册第一个用户。第一个用户即是超级管理员,以后的用户都是普通用户。普通用户只能绑定设备和配置智能体;超级管理员可以进行模型管理、用户管理、参数配置等功能。
|
||||||
|
|
||||||
接下来要做两件重要的事情:
|
接下来要做三件重要的事情:
|
||||||
|
|
||||||
### 第一件重要的事情
|
### 第一件重要的事情
|
||||||
|
|
||||||
使用超级管理员账号,登录系统,在顶部菜单找到`参数管理`,找到列表中第三条数据,参数编码是`server.secret`,复制它到`参数值`。
|
使用超级管理员账号,登录系统,在顶部菜单找到`参数管理`,找到列表中第一条数据,参数编码是`server.secret`,复制它到`参数值`。
|
||||||
|
|
||||||
`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。
|
`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。
|
||||||
|
|
||||||
@@ -138,7 +138,12 @@ manager-api:
|
|||||||
secret: 你的server.secret值
|
secret: 你的server.secret值
|
||||||
```
|
```
|
||||||
1、把你刚才从`智控台`复制过来的`server.secret`的`参数值`复制到`.config.yaml`文件里的`secret`里。
|
1、把你刚才从`智控台`复制过来的`server.secret`的`参数值`复制到`.config.yaml`文件里的`secret`里。
|
||||||
2、注意,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
|
|
||||||
|
2、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
|
||||||
|
|
||||||
|
3、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
|
||||||
|
|
||||||
|
4、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
|
||||||
|
|
||||||
类似这样的效果
|
类似这样的效果
|
||||||
```
|
```
|
||||||
@@ -182,6 +187,12 @@ Websocket接口:
|
|||||||
ws://你电脑局域网的ip:8000/xiaozhi/v1/
|
ws://你电脑局域网的ip:8000/xiaozhi/v1/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 第三件重要的事情
|
||||||
|
|
||||||
|
使用超级管理员账号,登录系统,在顶部菜单找到`参数管理`,找到参数编码是`server.websocket`,输入你的`Websocket接口`。
|
||||||
|
|
||||||
|
使用超级管理员账号,登录系统,在顶部菜单找到`参数管理`,找到数编码是`server.ota`,输入你的`OTA接口`。
|
||||||
|
|
||||||
接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
|
接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
|
||||||
|
|
||||||
1、 [编译自己的esp32固件](firmware-build.md)了。
|
1、 [编译自己的esp32固件](firmware-build.md)了。
|
||||||
@@ -353,7 +364,7 @@ pip install -r requirements.txt
|
|||||||
|
|
||||||
## 8.配置项目文件
|
## 8.配置项目文件
|
||||||
|
|
||||||
使用超级管理员账号,登录智控台 http://127.0.0.1:8002 ,在顶部菜单找到`参数管理`,找到列表中第三条数据,参数编码是`server.secret`,复制它到`参数值`。
|
使用超级管理员账号,登录智控台 http://127.0.0.1:8002 ,在顶部菜单找到`参数管理`,找到列表中第一条数据,参数编码是`server.secret`,复制它到`参数值`。
|
||||||
|
|
||||||
`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。
|
`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。
|
||||||
|
|
||||||
@@ -394,7 +405,7 @@ python app.py
|
|||||||
25-02-23 12:01:09[core.websocket_server] - INFO - =======================================================
|
25-02-23 12:01:09[core.websocket_server] - INFO - =======================================================
|
||||||
```
|
```
|
||||||
|
|
||||||
由于你是全模块部署,因此你有两个重要的接口需要写入到esp32中。
|
由于你是全模块部署,因此你有两个重要的接口。
|
||||||
|
|
||||||
OTA接口:
|
OTA接口:
|
||||||
```
|
```
|
||||||
@@ -406,6 +417,13 @@ Websocket接口:
|
|||||||
ws://你电脑局域网的ip:8000/xiaozhi/v1/
|
ws://你电脑局域网的ip:8000/xiaozhi/v1/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
请你务必把以上两个接口地址写入到智控台中:他们将会影响websocket地址发放和自动升级功能。
|
||||||
|
|
||||||
|
1、使用超级管理员账号,登录系统,在顶部菜单找到`参数管理`,找到参数编码是`server.websocket`,输入你的`Websocket接口`。
|
||||||
|
|
||||||
|
2、使用超级管理员账号,登录系统,在顶部菜单找到`参数管理`,找到数编码是`server.ota`,输入你的`OTA接口`。
|
||||||
|
|
||||||
|
|
||||||
接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
|
接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
|
||||||
|
|
||||||
1、 [编译自己的esp32固件](firmware-build.md)了。
|
1、 [编译自己的esp32固件](firmware-build.md)了。
|
||||||
|
|||||||
@@ -36,24 +36,24 @@ ws://192.168.1.25:8000/xiaozhi/v1/
|
|||||||
|
|
||||||
## 第4步 修改OTA地址
|
## 第4步 修改OTA地址
|
||||||
|
|
||||||
找到`OTA_VERSION_URL`的`default`的内容,把`https://api.tenclass.net/xiaozhi/ota/`
|
找到`OTA_URL`的`default`的内容,把`https://api.tenclass.net/xiaozhi/ota/`
|
||||||
改成你自己的地址,例如,我的接口地址是`http://192.168.1.25:8002/xiaozhi/ota/`,就把内容改成这个。
|
改成你自己的地址,例如,我的接口地址是`http://192.168.1.25:8002/xiaozhi/ota/`,就把内容改成这个。
|
||||||
|
|
||||||
修改前:
|
修改前:
|
||||||
```
|
```
|
||||||
config OTA_VERSION_URL
|
config OTA_URL
|
||||||
string "OTA Version URL"
|
string "Default OTA URL"
|
||||||
default "https://api.tenclass.net/xiaozhi/ota/"
|
default "https://api.tenclass.net/xiaozhi/ota/"
|
||||||
help
|
help
|
||||||
The application will access this URL to check for updates.
|
The application will access this URL to check for new firmwares and server address.
|
||||||
```
|
```
|
||||||
修改后:
|
修改后:
|
||||||
```
|
```
|
||||||
config OTA_VERSION_URL
|
config OTA_URL
|
||||||
string "OTA Version URL"
|
string "Default OTA URL"
|
||||||
default "http://192.168.1.25:8002/xiaozhi/ota/"
|
default "http://192.168.1.25:8002/xiaozhi/ota/"
|
||||||
help
|
help
|
||||||
The application will access this URL to check for updates.
|
The application will access this URL to check for new firmwares and server address.
|
||||||
```
|
```
|
||||||
|
|
||||||
## 第4步 设置编译参数
|
## 第4步 设置编译参数
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package xiaozhi.common.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RestTemplate配置
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RestTemplateConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,6 +89,11 @@ public interface Constant {
|
|||||||
*/
|
*/
|
||||||
String SERVER_WEBSOCKET = "server.websocket";
|
String SERVER_WEBSOCKET = "server.websocket";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ota地址
|
||||||
|
*/
|
||||||
|
String SERVER_OTA = "server.ota";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否允许用户注册
|
* 是否允许用户注册
|
||||||
*/
|
*/
|
||||||
|
|||||||
+13
@@ -7,6 +7,7 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
|
|||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@@ -79,4 +80,16 @@ public class DeviceController {
|
|||||||
return new Result<Void>();
|
return new Result<Void>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/enableOta/{id}/{status}")
|
||||||
|
@Operation(summary = "启用/关闭OTA自动升级")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Void> enableOtaUpgrade(@PathVariable String id, @PathVariable Integer status) {
|
||||||
|
DeviceEntity entity = deviceService.selectById(id);
|
||||||
|
if (entity == null) {
|
||||||
|
return new Result<Void>().error("设备不存在");
|
||||||
|
}
|
||||||
|
entity.setAutoUpdate(status);
|
||||||
|
deviceService.updateById(entity);
|
||||||
|
return new Result<Void>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,18 +3,14 @@ package xiaozhi.modules.device.service;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.service.BaseService;
|
||||||
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||||
|
|
||||||
public interface DeviceService {
|
public interface DeviceService extends BaseService<DeviceEntity> {
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据Mac地址获取设备信息
|
|
||||||
*/
|
|
||||||
DeviceEntity getDeviceById(String macAddress);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查设备是否激活
|
* 检查设备是否激活
|
||||||
|
|||||||
+23
-20
@@ -13,7 +13,6 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
@@ -55,13 +54,6 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final OtaService otaService;
|
private final OtaService otaService;
|
||||||
|
|
||||||
@Override
|
|
||||||
public DeviceEntity getDeviceById(String deviceId) {
|
|
||||||
LambdaQueryWrapper<DeviceEntity> queryWrapper = new LambdaQueryWrapper<>();
|
|
||||||
queryWrapper.eq(DeviceEntity::getId, deviceId);
|
|
||||||
return deviceDao.selectOne(queryWrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean deviceActivation(String agentId, String activationCode) {
|
public Boolean deviceActivation(String agentId, String activationCode) {
|
||||||
if (StringUtils.isBlank(activationCode)) {
|
if (StringUtils.isBlank(activationCode)) {
|
||||||
@@ -105,6 +97,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
deviceEntity.setMacAddress(macAddress);
|
deviceEntity.setMacAddress(macAddress);
|
||||||
deviceEntity.setUserId(user.getId());
|
deviceEntity.setUserId(user.getId());
|
||||||
deviceEntity.setCreator(user.getId());
|
deviceEntity.setCreator(user.getId());
|
||||||
|
deviceEntity.setAutoUpdate(1);
|
||||||
deviceEntity.setCreateDate(currentTime);
|
deviceEntity.setCreateDate(currentTime);
|
||||||
deviceEntity.setUpdater(user.getId());
|
deviceEntity.setUpdater(user.getId());
|
||||||
deviceEntity.setUpdateDate(currentTime);
|
deviceEntity.setUpdateDate(currentTime);
|
||||||
@@ -123,16 +116,22 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
DeviceReportRespDTO response = new DeviceReportRespDTO();
|
DeviceReportRespDTO response = new DeviceReportRespDTO();
|
||||||
response.setServer_time(buildServerTime());
|
response.setServer_time(buildServerTime());
|
||||||
|
|
||||||
String type = deviceReport.getBoard() == null ? null : deviceReport.getBoard().getType();
|
DeviceEntity deviceById = getDeviceByMacAddress(macAddress);
|
||||||
DeviceReportRespDTO.Firmware firmware = buildFirmwareInfo(type, macAddress);
|
|
||||||
response.setFirmware(firmware);
|
if (deviceById == null || deviceById.getAutoUpdate() != 0) {
|
||||||
|
String type = deviceReport.getBoard() == null ? null : deviceReport.getBoard().getType();
|
||||||
|
DeviceReportRespDTO.Firmware firmware = buildFirmwareInfo(type);
|
||||||
|
response.setFirmware(firmware);
|
||||||
|
} else {
|
||||||
|
response.setFirmware(null);
|
||||||
|
}
|
||||||
|
|
||||||
// 添加WebSocket配置
|
// 添加WebSocket配置
|
||||||
DeviceReportRespDTO.Websocket websocket = new DeviceReportRespDTO.Websocket();
|
DeviceReportRespDTO.Websocket websocket = new DeviceReportRespDTO.Websocket();
|
||||||
// 从系统参数获取WebSocket URL,如果未配置则使用默认值
|
// 从系统参数获取WebSocket URL,如果未配置则使用默认值
|
||||||
String wsUrl = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true);
|
String wsUrl = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true);
|
||||||
if (StringUtils.isBlank(wsUrl) || wsUrl.equals("null")) {
|
if (StringUtils.isBlank(wsUrl) || wsUrl.equals("null")) {
|
||||||
log.error("WebSocket URL is not configured");
|
log.error("WebSocket地址未配置,请登录智控台,在参数管理找到【server.websocket】配置");
|
||||||
wsUrl = "ws://xiaozhi.server.com:8000/xiaozhi/v1/";
|
wsUrl = "ws://xiaozhi.server.com:8000/xiaozhi/v1/";
|
||||||
websocket.setUrl(wsUrl);
|
websocket.setUrl(wsUrl);
|
||||||
} else {
|
} else {
|
||||||
@@ -141,14 +140,13 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
// 随机选择一个WebSocket URL
|
// 随机选择一个WebSocket URL
|
||||||
websocket.setUrl(wsUrls[RandomUtil.randomInt(0, wsUrls.length)]);
|
websocket.setUrl(wsUrls[RandomUtil.randomInt(0, wsUrls.length)]);
|
||||||
} else {
|
} else {
|
||||||
log.error("WebSocket URL list is empty");
|
log.error("WebSocket地址未配置,请登录智控台,在参数管理找到【server.websocket】配置");
|
||||||
websocket.setUrl("ws://xiaozhi.server.com:8000/xiaozhi/v1/");
|
websocket.setUrl("ws://xiaozhi.server.com:8000/xiaozhi/v1/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.setWebsocket(websocket);
|
response.setWebsocket(websocket);
|
||||||
|
|
||||||
DeviceEntity deviceById = getDeviceById(macAddress);
|
|
||||||
if (deviceById != null) { // 如果设备存在,则更新上次连接时间
|
if (deviceById != null) { // 如果设备存在,则更新上次连接时间
|
||||||
deviceById.setLastConnectedAt(new Date());
|
deviceById.setLastConnectedAt(new Date());
|
||||||
deviceDao.updateById(deviceById);
|
deviceDao.updateById(deviceById);
|
||||||
@@ -301,7 +299,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DeviceReportRespDTO.Firmware buildFirmwareInfo(String type, String macAddress) {
|
private DeviceReportRespDTO.Firmware buildFirmwareInfo(String type) {
|
||||||
if (StringUtils.isBlank(type)) {
|
if (StringUtils.isBlank(type)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -311,14 +309,19 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
String downloadUrl = null;
|
String downloadUrl = null;
|
||||||
|
|
||||||
if (ota != null) {
|
if (ota != null) {
|
||||||
// 获取当前请求的URL
|
|
||||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
|
String otaUrl = sysParamsService.getValue(Constant.SERVER_OTA, true);
|
||||||
.getRequest();
|
if (StringUtils.isBlank(otaUrl) || otaUrl.equals("null")) {
|
||||||
String requestUrl = request.getRequestURL().toString();
|
log.error("OTA地址未配置,请登录智控台,在参数管理找到【server.ota】配置");
|
||||||
|
// 尝试从请求中获取
|
||||||
|
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
|
||||||
|
.getRequest();
|
||||||
|
otaUrl = request.getRequestURL().toString();
|
||||||
|
}
|
||||||
// 将URL中的/ota/替换为/otaMag/download/
|
// 将URL中的/ota/替换为/otaMag/download/
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
redisUtils.set(RedisKeys.getOtaIdKey(uuid), ota.getId());
|
redisUtils.set(RedisKeys.getOtaIdKey(uuid), ota.getId());
|
||||||
downloadUrl = requestUrl.replace("/ota/", "/otaMag/download/") + uuid;
|
downloadUrl = otaUrl.replace("/ota/", "/otaMag/download/") + uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
firmware.setVersion(ota == null ? null : ota.getVersion());
|
firmware.setVersion(ota == null ? null : ota.getVersion());
|
||||||
|
|||||||
+52
@@ -4,6 +4,8 @@ import java.util.Map;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -12,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
@@ -47,6 +50,7 @@ import xiaozhi.modules.sys.utils.WebSocketValidator;
|
|||||||
public class SysParamsController {
|
public class SysParamsController {
|
||||||
private final SysParamsService sysParamsService;
|
private final SysParamsService sysParamsService;
|
||||||
private final ConfigService configService;
|
private final ConfigService configService;
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
@GetMapping("page")
|
@GetMapping("page")
|
||||||
@Operation(summary = "分页")
|
@Operation(summary = "分页")
|
||||||
@@ -97,6 +101,9 @@ public class SysParamsController {
|
|||||||
// 验证WebSocket地址列表
|
// 验证WebSocket地址列表
|
||||||
validateWebSocketUrls(dto.getParamCode(), dto.getParamValue());
|
validateWebSocketUrls(dto.getParamCode(), dto.getParamValue());
|
||||||
|
|
||||||
|
// 验证OTA地址
|
||||||
|
validateOtaUrl(dto.getParamCode(), dto.getParamValue());
|
||||||
|
|
||||||
sysParamsService.update(dto);
|
sysParamsService.update(dto);
|
||||||
configService.getConfig(false);
|
configService.getConfig(false);
|
||||||
return new Result<Void>();
|
return new Result<Void>();
|
||||||
@@ -118,6 +125,11 @@ public class SysParamsController {
|
|||||||
}
|
}
|
||||||
for (String url : wsUrls) {
|
for (String url : wsUrls) {
|
||||||
if (StringUtils.isNotBlank(url)) {
|
if (StringUtils.isNotBlank(url)) {
|
||||||
|
// 检查是否包含localhost或127.0.0.1
|
||||||
|
if (url.contains("localhost") || url.contains("127.0.0.1")) {
|
||||||
|
throw new RenException("WebSocket地址不能使用localhost或127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
// 验证WebSocket地址格式
|
// 验证WebSocket地址格式
|
||||||
if (!WebSocketValidator.validateUrlFormat(url)) {
|
if (!WebSocketValidator.validateUrlFormat(url)) {
|
||||||
throw new RenException("WebSocket地址格式不正确: " + url);
|
throw new RenException("WebSocket地址格式不正确: " + url);
|
||||||
@@ -131,6 +143,46 @@ public class SysParamsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证OTA地址
|
||||||
|
*/
|
||||||
|
private void validateOtaUrl(String paramCode, String url) {
|
||||||
|
if (!paramCode.equals(Constant.SERVER_OTA)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(url) || url.equals("null")) {
|
||||||
|
throw new RenException("OTA地址不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含localhost或127.0.0.1
|
||||||
|
if (url.contains("localhost") || url.contains("127.0.0.1")) {
|
||||||
|
throw new RenException("OTA地址不能使用localhost或127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证URL格式
|
||||||
|
if (!url.toLowerCase().startsWith("http")) {
|
||||||
|
throw new RenException("OTA地址必须以http或https开头");
|
||||||
|
}
|
||||||
|
if (!url.endsWith("/ota/")) {
|
||||||
|
throw new RenException("OTA地址必须以/ota/结尾");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 发送GET请求
|
||||||
|
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||||
|
if (response.getStatusCode() != HttpStatus.OK) {
|
||||||
|
throw new RenException("OTA接口访问失败,状态码:" + response.getStatusCode());
|
||||||
|
}
|
||||||
|
// 检查响应内容是否包含OTA相关信息
|
||||||
|
String body = response.getBody();
|
||||||
|
if (body == null || !body.contains("OTA")) {
|
||||||
|
throw new RenException("OTA接口返回内容格式不正确,可能不是一个真实的OTA接口");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RenException("OTA接口验证失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/delete")
|
@PostMapping("/delete")
|
||||||
@Operation(summary = "删除")
|
@Operation(summary = "删除")
|
||||||
@LogOperation("删除")
|
@LogOperation("删除")
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
-- 删除server.ip和server.port,改成在xiaozhi-server中配置,方便运行多个websocket
|
|
||||||
delete from `sys_params` where id = 100;
|
|
||||||
delete from `sys_params` where id = 101;
|
|
||||||
|
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (106, 'server.websocket', 'null', 'string', 1, 'websocket地址,多个用;分隔');
|
|
||||||
|
|
||||||
+9
-6
@@ -1,8 +1,9 @@
|
|||||||
-- 本文件用于初始化固件信息表,无需手动执行,在项目启动时会自动执行
|
-- 增加server.ota,用于配置ota地址
|
||||||
-- -------------------------------------------------------
|
delete from `sys_params` where id = 107;
|
||||||
-- 初始化固件信息表
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (107, 'server.ota', 'null', 'string', 1, 'ota地址');
|
||||||
drop table if exists `ai_ota`;
|
|
||||||
CREATE TABLE `ai_ota` (
|
-- 增加固件信息表
|
||||||
|
CREATE TABLE IF NOT EXISTS `ai_ota` (
|
||||||
`id` varchar(32) NOT NULL COMMENT 'ID',
|
`id` varchar(32) NOT NULL COMMENT 'ID',
|
||||||
`firmware_name` varchar(100) DEFAULT NULL COMMENT '固件名称',
|
`firmware_name` varchar(100) DEFAULT NULL COMMENT '固件名称',
|
||||||
`type` varchar(50) DEFAULT NULL COMMENT '固件类型',
|
`type` varchar(50) DEFAULT NULL COMMENT '固件类型',
|
||||||
@@ -16,4 +17,6 @@ CREATE TABLE `ai_ota` (
|
|||||||
`creator` bigint DEFAULT NULL COMMENT '创建者',
|
`creator` bigint DEFAULT NULL COMMENT '创建者',
|
||||||
`create_date` datetime DEFAULT NULL COMMENT '创建时间',
|
`create_date` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='固件信息表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='固件信息表';
|
||||||
|
|
||||||
|
update ai_device set auto_update = 1;
|
||||||
@@ -58,13 +58,6 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202504181536.sql
|
path: classpath:db/changelog/202504181536.sql
|
||||||
- changeSet:
|
|
||||||
id: 202504180758
|
|
||||||
author: jiangkunyin
|
|
||||||
changes:
|
|
||||||
- sqlFile:
|
|
||||||
encoding: utf8
|
|
||||||
path: classpath:db/changelog/202504180758.sql
|
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 202504221135
|
id: 202504221135
|
||||||
author: John
|
author: John
|
||||||
@@ -72,13 +65,6 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202504221135.sql
|
path: classpath:db/changelog/202504221135.sql
|
||||||
- changeSet:
|
|
||||||
id: 202504221646
|
|
||||||
author: John
|
|
||||||
changes:
|
|
||||||
- sqlFile:
|
|
||||||
encoding: utf8
|
|
||||||
path: classpath:db/changelog/202504221646.sql
|
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 202504221555
|
id: 202504221555
|
||||||
author: John
|
author: John
|
||||||
@@ -86,3 +72,10 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202504221555.sql
|
path: classpath:db/changelog/202504221555.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202504251421
|
||||||
|
author: jiangkunyin
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202504251421.sql
|
||||||
@@ -51,4 +51,20 @@ export default {
|
|||||||
});
|
});
|
||||||
}).send();
|
}).send();
|
||||||
},
|
},
|
||||||
|
enableOtaUpgrade(id, status, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/device/enableOta/${id}/${status}`)
|
||||||
|
.method('PUT')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('更新OTA状态失败:', err)
|
||||||
|
this.$message.error(err.msg || '更新OTA状态失败')
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.enableOtaUpgrade(id, status, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -15,15 +15,9 @@
|
|||||||
<div class="content-panel">
|
<div class="content-panel">
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<el-card class="device-card" shadow="never">
|
<el-card class="device-card" shadow="never">
|
||||||
<el-table
|
<el-table ref="deviceTable" :data="paginatedDeviceList" class="transparent-table"
|
||||||
ref="deviceTable"
|
:header-cell-class-name="headerCellClassName" v-loading="loading" element-loading-text="拼命加载中"
|
||||||
:data="paginatedDeviceList"
|
element-loading-spinner="el-icon-loading" element-loading-background="rgba(255, 255, 255, 0.7)">
|
||||||
class="transparent-table"
|
|
||||||
:header-cell-class-name="headerCellClassName"
|
|
||||||
v-loading="loading"
|
|
||||||
element-loading-text="拼命加载中"
|
|
||||||
element-loading-spinner="el-icon-loading"
|
|
||||||
element-loading-background="rgba(255, 255, 255, 0.7)">
|
|
||||||
<el-table-column label="选择" align="center" width="120">
|
<el-table-column label="选择" align="center" width="120">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
||||||
@@ -34,7 +28,7 @@
|
|||||||
{{ getFirmwareTypeName(scope.row.model) }}
|
{{ getFirmwareTypeName(scope.row.model) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="固件版本" prop="firmwareVersion" align="center" ></el-table-column>
|
<el-table-column label="固件版本" prop="firmwareVersion" align="center"></el-table-column>
|
||||||
<el-table-column label="Mac地址" prop="macAddress" align="center"></el-table-column>
|
<el-table-column label="Mac地址" prop="macAddress" align="center"></el-table-column>
|
||||||
<el-table-column label="绑定时间" prop="bindTime" align="center"></el-table-column>
|
<el-table-column label="绑定时间" prop="bindTime" align="center"></el-table-column>
|
||||||
<el-table-column label="最近对话" prop="lastConversation" align="center"></el-table-column>
|
<el-table-column label="最近对话" prop="lastConversation" align="center"></el-table-column>
|
||||||
@@ -53,8 +47,8 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="OTA升级" align="center">
|
<el-table-column label="OTA升级" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-switch v-model="scope.row.otaSwitch" size="mini" active-color="#13ce66"
|
<el-switch v-model="scope.row.otaSwitch" size="mini" active-color="#13ce66" inactive-color="#ff4949"
|
||||||
inactive-color="#ff4949"></el-switch>
|
@change="handleOtaSwitchChange(scope.row)"></el-switch>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center">
|
<el-table-column label="操作" align="center">
|
||||||
@@ -326,6 +320,16 @@ export default {
|
|||||||
const firmwareType = FIRMWARE_TYPES.find(item => item.key === type);
|
const firmwareType = FIRMWARE_TYPES.find(item => item.key === type);
|
||||||
return firmwareType ? firmwareType.name : type;
|
return firmwareType ? firmwareType.name : type;
|
||||||
},
|
},
|
||||||
|
handleOtaSwitchChange(row) {
|
||||||
|
Api.device.enableOtaUpgrade(row.device_id, row.otaSwitch ? 1 : 0, ({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success(row.otaSwitch ? '已设置成自动升级' : '已关闭自动升级')
|
||||||
|
} else {
|
||||||
|
row.otaSwitch = !row.otaSwitch
|
||||||
|
this.$message.error(data.msg || '操作失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -472,12 +476,13 @@ export default {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
::v-deep .el-card__body {
|
|
||||||
padding: 15px;
|
::v-deep .el-card__body {
|
||||||
display: flex;
|
padding: 15px;
|
||||||
flex-direction: column;
|
display: flex;
|
||||||
flex: 1;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table_bottom {
|
.table_bottom {
|
||||||
@@ -691,5 +696,4 @@ export default {
|
|||||||
::v-deep .el-table::before {
|
::v-deep .el-table::before {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user