update:功能

This commit is contained in:
2025-12-01 08:12:11 +08:00
parent d20f559563
commit a9399f5497
51 changed files with 2319 additions and 58 deletions

View File

@@ -1 +1,66 @@
# 项目架构
# 项目架构
## 概述
本项目是一个基于 Spring Boot 的多模块 Maven 项目,采用分层架构设计,旨在提供一个可扩展和可维护的应用程序结构。项目使用 Java 21 版本,并集成了 Spring Security、MyBatis 等主流框架。
## 模块结构
项目由以下核心模块组成:
### ski-dashboard-model
- **作用**: 数据模型层,定义了应用程序的核心数据结构和实体类
- **依赖**: 无外部依赖,仅依赖父项目
### ski-dashboard-common
- **作用**: 公共组件层,包含通用工具类、公共配置和数据访问接口
- **主要功能**:
- 工具类和辅助函数
- MyBatis Mapper 接口定义
- JWT 认证相关工具
- **依赖**:
- ski-dashboard-model
- MyBatis Spring Boot Starter
### ski-dashboard-service
- **作用**: 业务逻辑层,实现核心业务逻辑和服务接口
- **主要功能**:
- 业务逻辑处理
- 数据访问服务实现
- **依赖**:
- ski-dashboard-model
- ski-dashboard-common
### ski-dashboard-admin
- **作用**: 应用入口和管理控制台,提供 REST API 和 Web 管理界面
- **主要功能**:
- RESTful API 接口
- Swagger API 文档
- Web 安全配置
- **依赖**:
- ski-dashboard-model
- ski-dashboard-common
- ski-dashboard-service
- SpringDoc OpenAPI UI
## 技术栈
- **核心框架**: Spring Boot 3.5.7
- **编程语言**: Java 21
- **安全框架**: Spring Security
- **Web 框架**: Spring Web MVC
- **持久层框架**: MyBatis
- **数据库**: PostgreSQL
- **会话管理**: Spring Session JDBC
- **API 文档**: SpringDoc OpenAPI
- **JWT 认证**: java-jwt 4.4.0
- **构建工具**: Maven
- **代码简化**: Lombok
## 架构特点
1. **分层架构**: 清晰分离数据模型、公共组件、业务逻辑和应用入口
2. **模块化设计**: 各模块职责明确,便于独立开发和维护
3. **松耦合**: 模块间通过接口依赖,降低耦合度
4. **可扩展性**: 易于添加新功能模块或替换现有组件
5. **安全性**: 集成 Spring Security 和 JWT 实现认证授权

66
absolute/path/to/file Normal file
View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ski.lichuan.mapper.TrailPositionMapper">
<resultMap id="BaseResultMap" type="com.ski.lichuan.model.dashboard.TrailPosition">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="trail_id" jdbcType="INTEGER" property="trailId"/>
<result column="position" jdbcType="VARCHAR" property="position"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="snow_machine_id" jdbcType="INTEGER" property="snowMachineId"/>
<result column="snow_machine_status" jdbcType="INTEGER" property="snowMachineStatus"/>
</resultMap>
<select id="selectByTrailId" resultMap="BaseResultMap">
SELECT * FROM trail_position WHERE trail_id = #{trailId}
</select>
<select id="selectById" resultMap="BaseResultMap">
SELECT * FROM trail_position WHERE id = #{id}
</select>
<select id="selectAll" resultMap="BaseResultMap">
SELECT * FROM trail_position
</select>
<!-- 获取所有未绑定雪机的雪道位置信息 -->
<select id="selectUnboundPositions" resultMap="BaseResultMap">
SELECT * FROM trail_position WHERE snow_machine_id IS NULL
</select>
<select id="selectBoundPositions" resultMap="BaseResultMap">
SELECT * FROM trail_position WHERE snow_machine_id IS NOT NULL
</select>
<insert id="insert" keyColumn="id" keyProperty="id" useGeneratedKeys="true">
INSERT INTO trail_position (trail_id, position, name)
VALUES (#{trailId}, #{position}, #{name})
</insert>
<update id="update">
UPDATE trail_position
SET trail_id = #{trailId}, position = #{position}, name = #{name}
WHERE id = #{id}
</update>
<update id="clearSnowMachine">
UPDATE trail_position
SET snow_machine_status = 0, snow_machine_id = NULL
WHERE id = #{id}
</update>
<update id="updateSnowMachine">
UPDATE trail_position
SET snow_machine_status = #{snowMachineStatus}, snow_machine_id = #{snowMachineId}
WHERE id = #{id}
</update>
<!-- 根据雪道位置ID列表查询造雪机设备ID列表 -->
<select id="selectDeviceIdsByTrailPositionIds" resultType="java.lang.Integer">
SELECT snow_machine_id FROM trail_position WHERE id IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
</mapper>

23
pom.xml
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
@@ -22,10 +22,11 @@
<developer/>
</developers>
<modules>
<module>ski-dashboard-admin</module>
<module>ski-dashboard-service</module>
<module>ski-dashboard-common</module>
<!-- 修改模块顺序以确保正确的构建顺序 -->
<module>ski-dashboard-model</module>
<module>ski-dashboard-common</module>
<module>ski-dashboard-service</module>
<module>ski-dashboard-admin</module>
</modules>
<scm>
<connection/>
@@ -40,10 +41,22 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
@@ -121,4 +134,4 @@
</plugins>
</build>
</project>
</project>

View File

@@ -24,13 +24,13 @@
<version>2.8.14</version>
</dependency>
<dependency>
<groupId>com.ski.lichuan</groupId>
<groupId>com.ski</groupId>
<artifactId>ski-dashboard-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.ski.lichuan</groupId>
<groupId>com.ski</groupId>
<artifactId>ski-dashboard-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>

View File

@@ -3,9 +3,11 @@ package com.ski.lichuan.admin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(scanBasePackages = {"com.ski.lichuan"})
@MapperScan("com.ski.lichuan.mapper")
@EnableScheduling
public class SkiDashboardAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SkiDashboardAdminApplication.class, args);

View File

@@ -53,6 +53,7 @@ public class SecurityConfig {
.authorizeHttpRequests(auth -> auth
// 放行 Swagger UI 相关路径
.requestMatchers(
"/**",
"/api/auth/login",
"/swagger-ui.html",
"/swagger-ui/**",

View File

@@ -0,0 +1,97 @@
package com.ski.lichuan.admin.controller;
import com.ski.lichuan.model.common.HttpResponseData;
import com.ski.lichuan.model.dashboard.TrailPosition;
import com.ski.lichuan.model.device.Impl.SnowMachineDevice;
import com.ski.lichuan.services.SnowMachineDeviceService;
import com.ski.lichuan.services.TrailPositionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/options")
@CrossOrigin(origins = "*")
@Tag(name = "公共选项", description = "提供公共的选项数据")
public class OptionsController {
@Autowired
private TrailPositionService trailPositionService;
@Autowired
private SnowMachineDeviceService snowMachineDeviceService;
/**
* 获取所有已绑定雪机的雪道位置信息
*
* @return 已绑定雪机的雪道位置列表
*/
@GetMapping("/boundTrailPositions")
@Operation(summary = "获取已绑定雪机的雪道位置信息", description = "获取所有已绑定雪机的雪道位置信息,用于下拉框选项")
public HttpResponseData<List<TrailPosition>> getBoundTrailPositions() {
HttpResponseData<List<TrailPosition>> responseData = new HttpResponseData<>();
try {
List<TrailPosition> boundPositions = trailPositionService.getBoundTrailPositions();
return responseData.success(boundPositions);
} catch (Exception e) {
return responseData.error("获取已绑定雪机的雪道位置信息失败: " + e.getMessage());
}
}
/**
* 获取所有未绑定位置的雪机设备信息
*
* @return 未绑定位置的雪机设备列表
*/
@GetMapping("/unboundSnowMachines")
@Operation(summary = "获取未绑定位置的雪机设备信息", description = "获取所有未绑定位置的雪机设备信息,用于下拉框选项")
public HttpResponseData<List<SnowMachineDevice>> getUnboundSnowMachines() {
HttpResponseData<List<SnowMachineDevice>> responseData = new HttpResponseData<>();
try {
List<SnowMachineDevice> unboundMachines = snowMachineDeviceService.selectUnboundMachines();
return responseData.success(unboundMachines);
} catch (Exception e) {
return responseData.error("获取未绑定位置的雪机设备信息失败: " + e.getMessage());
}
}
/**
* 获取所有未绑定雪机的雪道位置信息
*
* @return 未绑定雪机的雪道位置列表
*/
@GetMapping("/unboundTrailPositions")
@Operation(summary = "获取未绑定雪机的雪道位置信息", description = "获取所有未绑定雪机的雪道位置信息,用于下拉框选项")
public HttpResponseData<List<TrailPosition>> getUnboundTrailPositions() {
HttpResponseData<List<TrailPosition>> responseData = new HttpResponseData<>();
try {
List<TrailPosition> unboundPositions = trailPositionService.getUnboundTrailPositions();
return responseData.success(unboundPositions);
} catch (Exception e) {
return responseData.error("获取未绑定雪机的雪道位置信息失败: " + e.getMessage());
}
}
/**
* 获取所有雪机设备信息
*
* @return 所有雪机设备列表
*/
@GetMapping("/snowMachineDevices")
@Operation(summary = "获取所有雪机设备信息", description = "获取所有雪机设备信息,用于下拉框选项")
public HttpResponseData<List<SnowMachineDevice>> getAllSnowMachineDevices() {
HttpResponseData<List<SnowMachineDevice>> responseData = new HttpResponseData<>();
try {
List<SnowMachineDevice> allDevices = snowMachineDeviceService.selectAllDevices();
return responseData.success(allDevices);
} catch (Exception e) {
return responseData.error("获取所有雪机设备信息失败: " + e.getMessage());
}
}
}

View File

@@ -77,7 +77,7 @@ public class AuthController {
* @return 用户信息
*/
@GetMapping("/userinfo")
@Operation(summary = "获取当前用户信息", description = "根据JWT Token获取当前登录用户的基本信息",)
@Operation(summary = "获取当前用户信息", description = "根据JWT Token获取当前登录用户的基本信息")
public ResponseEntity<Map<String, Object>> getUserInfo() {
log.info("获取用户信息请求");

View File

@@ -1,7 +1,96 @@
package com.ski.lichuan.admin.controller.device;
import com.ski.lichuan.model.common.HttpResponseData;
import com.ski.lichuan.model.device.Impl.SnowMachineDevice;
import com.ski.lichuan.services.SnowMachineDeviceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/device/snowmachine")
@CrossOrigin(origins = "*") // 根据实际需要调整CORS策略
@Tag(name = "造雪机设备", description = "造雪机设备的基本信息管理")
public class SnowMachineController {
@Autowired
private SnowMachineDeviceService snowMachineDeviceService;
/**
* 1.造雪机设备的基本信息管理,增删查改
* 添加造雪机设备
*
* @param snowMachineDevice 造雪机设备信息
* @return 添加结果
*/
}
@PostMapping("/")
@Operation(summary = "添加造雪机设备")
public HttpResponseData<Boolean> addSnowMachine(@RequestBody SnowMachineDevice snowMachineDevice) {
// 调用服务层添加设备
int result = snowMachineDeviceService.addSnowMachineDevice(snowMachineDevice);
return HttpResponseData.ok(result > 0);
}
/**
* 更新造雪机设备信息
*
* @param id 设备ID
* @param snowMachineDevice 更新的设备信息
* @return 更新结果
*/
@PutMapping("/{id}")
@Operation(summary = "更新造雪机设备信息")
public HttpResponseData<Boolean> updateSnowMachine(@PathVariable Integer id, @RequestBody SnowMachineDevice snowMachineDevice) {
snowMachineDevice.setId(id);
int result = snowMachineDeviceService.updateSnowMachineDevice(snowMachineDevice);
return HttpResponseData.ok(result > 0);
}
/**
* 删除造雪机设备
*
* @param id 设备ID
* @return 删除结果
*/
@DeleteMapping("/{id}")
@Operation(summary = "删除造雪机设备")
public HttpResponseData<Boolean> deleteSnowMachine(@PathVariable Integer id) {
// 调用服务层删除设备
int result = snowMachineDeviceService.deleteSnowMachineDevice(id);
return HttpResponseData.ok(result > 0);
}
/**
* 根据ID获取造雪机设备信息
*
* @param id 设备ID
* @return 设备信息
*/
@GetMapping("/{id}")
@Operation(summary = "根据ID获取造雪机设备信息")
public HttpResponseData<SnowMachineDevice> getSnowMachineById(@PathVariable Integer id) {
// 调用服务层获取设备信息
SnowMachineDevice result = snowMachineDeviceService.getSnowMachineDeviceById(id);
log.info("根据ID获取造雪机设备信息ID{}", id);
return HttpResponseData.ok(result);
}
/**
* 获取所有造雪机设备列表
*
* @return 设备列表
*/
@GetMapping("/")
@Operation(summary = "获取所有造雪机设备列表")
public HttpResponseData<List<SnowMachineDevice>> getAllSnowMachines() {
// 调用服务层获取所有设备
List<SnowMachineDevice> result = snowMachineDeviceService.getAllSnowMachineDevices();
return HttpResponseData.ok(result);
}
}

View File

@@ -1,10 +1,177 @@
package com.ski.lichuan.admin.controller.monitor;
public class SkiResortController {
/**
* 1.获取滑雪场雪道和造雪机关联信息
* 2.提供滑雪场雪道和造雪机的关联移除(上下机)接口
* 3.提供滑雪场造雪机的群控功能(只对滑雪场内的造雪机生效)
*/
import com.ski.lichuan.model.common.DeviceEnum;
import com.ski.lichuan.model.common.HttpResponseData;
import com.ski.lichuan.model.dashboard.*;
import com.ski.lichuan.model.device.Impl.SnowMachineDevice;
import com.ski.lichuan.model.device.config.SnowMachineDeviceParams;
import com.ski.lichuan.model.device.config.SnowMachineDevicePitchHorizontalParams;
import com.ski.lichuan.services.TrailPositionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/dashboard")
@CrossOrigin(origins = "*") // 根据实际需要调整CORS策略
@Tag(name = "首页", description = "首页信息展示")
public class SkiResortController {
@Autowired
private TrailPositionService trailPositionService;
@GetMapping("/")
@Operation(summary = "首页-获取滑雪场雪道和造雪机关联信息")
public HttpResponseData<List<List<TrailPosition>>> getDashboardInfo() {
HttpResponseData<List<List<TrailPosition>>> responseData = new HttpResponseData<>();
List<List<TrailPosition>> result = trailPositionService.getAllTrailPositions();
return responseData.success(result);
}
@GetMapping("/positionInfo")
@Operation(summary = "获取雪道坑位的详细信息(造雪机)")
public HttpResponseData<TrailPositionInfo> getPositionInfo(Integer trailPositionId) {
HttpResponseData<TrailPositionInfo> responseData = new HttpResponseData<>();
TrailPositionInfo result = new TrailPositionInfo();
Integer trailId = trailPositionId / 10;
Integer position = trailPositionId % 10;
// 雪机设备
SnowMachineDevice snowMachineDevice = new SnowMachineDevice();
snowMachineDevice.setLora("1234567890123456");
snowMachineDevice.setId(1000000000 + trailPositionId);
snowMachineDevice.setName("造雪机-" + trailPositionId);
snowMachineDevice.setType(DeviceEnum.SNOW_MAKER.getValue());
// 雪道位置信息
TrailPosition trailPosition = new TrailPosition();
trailPosition.setId(trailPositionId);
trailPosition.setTrailId(trailId);
trailPosition.setPosition(position);
trailPosition.setName(trailId + "-" + position);
trailPosition.setSnowMachineStatus(1);
trailPosition.setSnowMachineId(snowMachineDevice.getId());
// 雪道位置状态信息
TrailPositionMonitorInfo trailPositionMonitorInfo = new TrailPositionMonitorInfo();
trailPositionMonitorInfo.setTrailPosition(trailPosition);
trailPositionMonitorInfo.setSnowMakerName(snowMachineDevice.getName());
trailPositionMonitorInfo.setPriority(1);
trailPositionMonitorInfo.setEnvironmentTemperature(-3.5);
trailPositionMonitorInfo.setEnvironmentHumidity(50.0);
trailPositionMonitorInfo.setWetBulbTemperature(0.5);
trailPositionMonitorInfo.setCommunicationStatus(true);
trailPositionMonitorInfo.setIsNormal(true);
trailPositionMonitorInfo.setIsRunning(true);
trailPositionMonitorInfo.setTemperatureConditionSatisfied(true);
trailPositionMonitorInfo.setWindSpeed(5.0);
trailPositionMonitorInfo.setWindDirection(180.0);
// 造雪机设备状态
SnowMachineDeviceInfo snowMachineDeviceInfo = new SnowMachineDeviceInfo();
snowMachineDeviceInfo.setSnowMachineDevice(snowMachineDevice);
snowMachineDeviceInfo.setFrontWaterPressure(0.5 + Math.random() * 0.3); // 前端水压 0.5-0.8 MPa
snowMachineDeviceInfo.setBackWaterPressure(0.4 + Math.random() * 0.2); // 后端水压 0.4-0.6 MPa
snowMachineDeviceInfo.setPressure(0.8 + Math.random() * 0.2); // 气压 0.8-1.0 MPa
snowMachineDeviceInfo.setTemperature(2.5 + Math.random() * 2.0); // 水温 2.5-4.5 ℃
snowMachineDeviceInfo.setGivenAngle(45.0 + Math.random() * 10.0); // 给定开度 45-55°
snowMachineDeviceInfo.setActualAngle(44.0 + Math.random() * 12.0); // 实际开度 44-56°
snowMachineDeviceInfo.setIsNormal(Math.random() > 0.1); // 是否正常 90%概率正常
snowMachineDeviceInfo.setMode((int)(Math.random() * 3) + 1); // 模式 1-3随机
snowMachineDeviceInfo.setCompressorStatus((int)(Math.random() * 2) + 1); // 空压机状态 1-2随机
snowMachineDeviceInfo.setFanStatus((int)(Math.random() * 2) + 1); // 风机状态 1-2随机
snowMachineDeviceInfo.setHeatingRingStatus((int)(Math.random() * 2) + 1); // 加热环状态 1-2随机
snowMachineDeviceInfo.setValveStatus((int)(Math.random() * 2) + 1); // 电磁阀状态1 1-2随机
snowMachineDeviceInfo.setValveStatus2((int)(Math.random() * 2) + 1); // 电磁阀状态2 1-2随机
snowMachineDeviceInfo.setValveStatus3((int)(Math.random() * 2) + 1); // 电磁阀状态3 1-2随机
snowMachineDeviceInfo.setPitchMode((int)(Math.random() * 3) + 1); // 俯仰模式 1-3随机
snowMachineDeviceInfo.setPitchAngle(15.0 + Math.random() * 20.0); // 俯仰角度 15-35°
snowMachineDeviceInfo.setPitchStatus((int)(Math.random() * 2) + 1); // 俯仰状态 1-2随机
snowMachineDeviceInfo.setYawMode((int)(Math.random() * 3) + 1); // 摆头模式 1-3随机
snowMachineDeviceInfo.setYawAngle(30.0 + Math.random() * 40.0); // 摆头角度 30-70°
snowMachineDeviceInfo.setYawStatus((int)(Math.random() * 2) + 1); // 摆头状态 1-2随机
// 造雪机配置
SnowMachineDeviceParams snowMakerDeviceParams = new SnowMachineDeviceParams();
snowMakerDeviceParams.setWorkMode((int)(Math.random() * 3) + 1); // 工作模式 1-3随机
snowMakerDeviceParams.setPriority((int)(Math.random() * 5) + 1); // 优先级 1-5随机
snowMakerDeviceParams.setSnowQuality((int)(Math.random() * 5) + 1); // 雪质 1-5随机
snowMakerDeviceParams.setStartTemperature(-5.0 + Math.random() * 10.0); // 启动温度 -5.0 ~ 5.0
snowMakerDeviceParams.setStopTemperature(-5.0 + Math.random() * 10.0); // 停止温度 -5.0 ~ 5.0
snowMakerDeviceParams.setFlowAdjustment((int)(Math.random() * 50)); // 流量调节 0 ~ 50
// 数据统计
TrailPositionStatisticsInfo trailPositionStatisticsInfo = new TrailPositionStatisticsInfo();
trailPositionStatisticsInfo.setRunningDuration(120.5 + Math.random() * 100.0); // 运行时长 120.5-220.5分钟
trailPositionStatisticsInfo.setFlowStatistics(1000.0 + Math.random() * 500.0); // 流量统计 1000-1500 L/H
trailPositionStatisticsInfo.setSnowArea(500.0 + Math.random() * 300.0); // 造雪面积 500-800 平方米
result.setTrailPositionMonitorInfo(trailPositionMonitorInfo);
result.setSnowMakerInfo(snowMachineDeviceInfo);
result.setSnowMakerDeviceParams(snowMakerDeviceParams);
result.setTrailPositionStatisticsInfo(trailPositionStatisticsInfo);
return responseData.success(result);
}
@PostMapping("/connectDevice")
@Operation(summary = "雪道造雪机(上机)")
public HttpResponseData<Boolean> connectDevice(Integer trailPositionId, Integer deviceId) {
HttpResponseData<Boolean> responseData = new HttpResponseData<>();
return responseData.success(trailPositionService.connectDevice(trailPositionId, deviceId));
}
@PostMapping("/disconnectDevice")
@Operation(summary = "雪道造雪机(下机)")
public HttpResponseData<Boolean> disconnectDevice(Integer trailPositionId) {
HttpResponseData<Boolean> responseData = new HttpResponseData<>();
return responseData.success(trailPositionService.disconnectDevice(trailPositionId));
}
@PostMapping("/groupControl")
@Operation(summary = "雪道造雪机(群控)")
public HttpResponseData<Boolean> groupControl(@RequestBody GroupControlRequest request) {
HttpResponseData<Boolean> responseData = new HttpResponseData<>();
return responseData.success(trailPositionService.groupControl(request.getTrailPositionList(), request.getParams()));
}
// 俯仰水平控制
@PostMapping("/pitchHorizontalControl")
@Operation(summary = "雪道造雪机俯仰水平控制")
public HttpResponseData<Boolean> pitchHorizontalControl(@RequestBody PitchHorizontalControlRequest request) {
HttpResponseData<Boolean> responseData = new HttpResponseData<>();
return responseData.success(trailPositionService.pitchHorizontalControl(request.getTrailPosition(), request.getParams()));
}
@PostMapping("/saveSnowMakerDeviceParams")
@Operation(summary = "保存造雪机参数")
public HttpResponseData<Boolean> groupControl(@RequestBody GroupControlSingleRequest request) {
HttpResponseData<Boolean> responseData = new HttpResponseData<>();
return responseData.success(trailPositionService.saveSnowMakerDeviceParams(request.getTrailPosition(), request.getParams()));
}
@Data
public static class GroupControlRequest {
private List<Integer> trailPositionList;
private SnowMachineDeviceParams params;
}
@Data
public static class GroupControlSingleRequest {
private Integer trailPosition;
private SnowMachineDeviceParams params;
}
@Data
public static class PitchHorizontalControlRequest {
private Integer trailPosition;
private SnowMachineDevicePitchHorizontalParams params;
}
}

View File

@@ -1,10 +1,12 @@
package com.ski.lichuan.admin.filter;
import com.ski.lichuan.common.utils.JwtUtils;
import com.ski.lichuan.model.auth.SysUser;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -15,6 +17,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
@@ -36,6 +39,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
// 2. 验证 Token 并获取用户名
if (jwtUtils.validateToken(token)) {
// 鉴权通过,从 Token 中获取用户名
String username = jwtUtils.getUsernameFromToken(token);
// 3. 加载用户信息并设置认证状态
UserDetails userDetails = userDetailsService.loadUserByUsername(username);

View File

@@ -0,0 +1,21 @@
spring.application.name=ski-dashboard-admin
server.port=8080
# postgresql
spring.datasource.url=jdbc:postgresql://localhost:5432/ski_dashboard
spring.datasource.username=postgres
spring.datasource.password=tanlifan
# Spring Session
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
spring.session.jdbc.table-name=SPRING_SESSION
JWT_SECRET=vf4JZhcyfdK7tJs0GZ3Qjf0dSv4BId9ITjsM2fol26gOBxM17nUySiMcV0Lo2u0Y
# Logging levels for development
logging.level.com.ski.lichuan=DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.org.mybatis=DEBUG

View File

@@ -0,0 +1,21 @@
spring.application.name=ski-dashboard-admin
server.port=8080
# postgresql
spring.datasource.url=jdbc:postgresql://localhost:8081/ski_dashboard
spring.datasource.username=postgres
spring.datasource.password=postgres
# Spring Session
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
spring.session.jdbc.table-name=SPRING_SESSION
JWT_SECRET=vf4JZhcyfdK7tJs0GZ3Qjf0dSv4BId9ITjsM2fol26gOBxM17nUySiMcV0Lo2u0Y
# Logging levels for production
logging.level.com.ski.lichuan=INFO
logging.level.org.springframework.web=WARN
logging.level.org.mybatis=WARN

View File

@@ -1,13 +1,16 @@
spring.application.name=ski-dashboard-admin
# ??????? postgresql
spring.datasource.url=jdbc:postgresql://localhost:5432/ski_dashboard
spring.datasource.username=postgres
spring.datasource.password=tanlifan
spring.profiles.active=prod
# Spring Session??
# Spring Session
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
spring.session.jdbc.table-name=SPRING_SESSION
JWT_SECRET=vf4JZhcyfdK7tJs0GZ3Qjf0dSv4BId9ITjsM2fol26gOBxM17nUySiMcV0Lo2u0Y
# MyBatis configuration - enable camel case mapping
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.mapper-locations=classpath*:mapper/*.xml
thingsboard.client.url=https://tb.ski.bkiiot.com
thingsboard.client.username=tenant@thingsboard.org
thingsboard.client.password=tenant

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定义日志文件的存储路径 -->
<property name="LOG_HOME" value="logs" />
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- INFO级别的日志输出到 info.log -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/info.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- ERROR级别的日志输出到 error.log -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 根日志配置 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
</configuration>

View File

@@ -8,8 +8,6 @@
<artifactId>ski-dashboard</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.ski.lichuan</groupId>
<artifactId>ski-dashboard-common</artifactId>
<properties>
@@ -38,4 +36,16 @@
<version>3.0.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 排除Spring Boot Maven插件因为这是一个数据模型模块不是可执行应用程序 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,17 +0,0 @@
package com.ski.lichuan.common;
//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
public class Main {
public static void main(String[] args) {
//TIP 当文本光标位于高亮显示的文本处时按 <shortcut actionId="ShowIntentionActions"/>
// 查看 IntelliJ IDEA 建议如何修正。
System.out.printf("Hello and welcome!");
for (int i = 1; i <= 5; i++) {
//TIP 按 <shortcut actionId="Debug"/> 开始调试代码。我们已经设置了一个 <icon src="AllIcons.Debugger.Db_set_breakpoint"/> 断点
// 但您始终可以通过按 <shortcut actionId="ToggleLineBreakpoint"/> 添加更多断点。
System.out.println("i = " + i);
}
}
}

View File

@@ -0,0 +1,28 @@
package com.ski.lichuan.common.exception;
public class BusinessException extends RuntimeException {
private Integer code;
public BusinessException(String message) {
super(message);
this.code = 500;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}

View File

@@ -0,0 +1,85 @@
package com.ski.lichuan.common.exception;
import com.ski.lichuan.model.common.HttpResponseData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理自定义业务异常
*/
@ExceptionHandler(BusinessException.class)
public HttpResponseData handleBusinessException(BusinessException e) {
log.error("业务异常: ", e);
return new HttpResponseData<>().error(e.getCode(), e.getMessage());
}
/**
* 处理参数验证异常(MethodArgumentNotValidException)
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public HttpResponseData handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("参数校验异常: ", e);
BindingResult bindingResult = e.getBindingResult();
StringBuilder errorMsg = new StringBuilder();
if (bindingResult.hasErrors()) {
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
fieldErrors.forEach(error ->
errorMsg.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; ")
);
}
return new HttpResponseData<>().error(400, "参数校验失败: " + errorMsg.toString());
}
/**
* 处理参数绑定异常(BindException)
*/
@ExceptionHandler(BindException.class)
public HttpResponseData handleBindException(BindException e) {
log.error("参数绑定异常: ", e);
BindingResult bindingResult = e.getBindingResult();
StringBuilder errorMsg = new StringBuilder();
if (bindingResult.hasErrors()) {
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
fieldErrors.forEach(error ->
errorMsg.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; ")
);
}
return new HttpResponseData<>().error(400, "参数绑定失败: " + errorMsg.toString());
}
/**
* 处理IllegalArgumentException异常
*/
@ExceptionHandler(IllegalArgumentException.class)
public HttpResponseData handleIllegalArgumentException(IllegalArgumentException e) {
log.error("非法参数异常: ", e);
return new HttpResponseData<>().error(400, e.getMessage());
}
/**
* 处理其他所有异常
*/
@ExceptionHandler(Exception.class)
public HttpResponseData handleException(Exception e) {
log.error("系统异常: ", e);
return new HttpResponseData<>().error(500, "系统内部错误,请联系管理员");
}
}

View File

@@ -0,0 +1,49 @@
package com.ski.lichuan.common.utils;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* MD5加密工具类
*/
public class MD5Util {
/**
* 对字符串进行MD5加密
* @param input 待加密的字符串
* @return 加密后的十六进制字符串
*/
public static String encrypt(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes(StandardCharsets.UTF_8));
BigInteger no = new BigInteger(1, messageDigest);
StringBuilder hashText = new StringBuilder(no.toString(16));
// 补齐前面的0确保是32位
while (hashText.length() < 32) {
hashText.insert(0, "0");
}
return hashText.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 algorithm not found", e);
}
}
/**
* 验证明文与MD5密文是否匹配
* @param rawText 明文
* @param md5Text MD5密文
* @return 是否匹配
*/
public static boolean verify(String rawText, String md5Text) {
if (rawText == null || md5Text == null) {
return false;
}
return md5Text.equals(encrypt(rawText));
}
}

View File

@@ -16,5 +16,26 @@
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
<version>2.2.38</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 排除Spring Boot Maven插件因为这是一个数据模型模块不是可执行应用程序 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,30 @@
package com.ski.lichuan.mapper;
import com.ski.lichuan.model.device.Impl.SnowMachineDevice;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SnowMachineMapper {
int insert(SnowMachineDevice snowMachineDevice);
int insertById(SnowMachineDevice snowMachineDevice);
List<SnowMachineDevice> selectAll();
SnowMachineDevice selectById(Integer id);
SnowMachineDevice selectByLora(String lora);
List<SnowMachineDevice> selectUnboundMachines();
int count();
int updateName(SnowMachineDevice snowMachineDevice);
int updateNameAndLora(SnowMachineDevice snowMachineDevice);
int deleteById(Integer id);
}

View File

@@ -0,0 +1,39 @@
package com.ski.lichuan.mapper;
import com.ski.lichuan.model.dashboard.TrailPosition;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface TrailPositionMapper {
List<TrailPosition> selectByTrailId(Integer trailId);
TrailPosition selectById(Integer id);
List<TrailPosition> selectAll();
/**
* 获取所有未绑定雪机的雪道位置信息
*/
List<TrailPosition> selectUnboundPositions();
List<TrailPosition> selectBoundPositions();
int insert(TrailPosition trailPosition);
int update(TrailPosition trailPosition);
int clearSnowMachine(Integer id);
int updateSnowMachine(Integer id, Integer snowMachineId);
/**
* 根据雪道位置ID列表查询造雪机设备ID列表
*
* @param trailPositionIds 雪道位置ID列表
* @return 造雪机设备ID列表
*/
List<Integer> selectDeviceIdsByTrailPositionIds(List<Integer> trailPositionIds);
}

View File

@@ -7,12 +7,20 @@ import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema
@Data
public class SysUser implements UserDetails {
@Schema(description = "用户ID")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
private String password;
@Schema(description = "昵称")
private String nickname;
@Schema(description = "状态")
private Integer status; // 1-正常0-禁用
// 实现 UserDetails 接口方法

View File

@@ -0,0 +1,18 @@
package com.ski.lichuan.model.common;
import lombok.Getter;
@Getter
public enum DeviceEnum {
SNOW_MAKER("雪机", 1),
WATER_PUMP("水泵", 2),
;
private final String name;
private final Integer value;
DeviceEnum(String name, Integer value) {
this.name = name;
this.value = value;
}
}

View File

@@ -2,7 +2,6 @@ package com.ski.lichuan.model.common;
import lombok.Data;
import org.springframework.http.ResponseEntity;
@Data
public class HttpResponseData<T> {
@@ -45,4 +44,17 @@ public class HttpResponseData<T> {
response.setMessage(message);
return response;
}
}
// 增加静态方法便于使用
public static <T> HttpResponseData<T> ok(T data) {
return new HttpResponseData<>(data);
}
public static <T> HttpResponseData<T> fail(String message) {
return new HttpResponseData<T>().error(message);
}
public static <T> HttpResponseData<T> fail(Integer code, String message) {
return new HttpResponseData<T>().error(code, message);
}
}

View File

@@ -0,0 +1,53 @@
package com.ski.lichuan.model.dashboard;
import com.ski.lichuan.model.device.Impl.SnowMachineDevice;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "造雪机设备状态信息")
@Data
public class SnowMachineDeviceInfo {
@Schema(description = "造雪机设备基本信息")
public SnowMachineDevice snowMachineDevice;
@Schema(description = "前端水压")
public Double frontWaterPressure;
@Schema(description = "后端水压")
public Double backWaterPressure;
@Schema(description = "气压")
public Double pressure;
@Schema(description = "水温")
public Double temperature;
@Schema(description = "给定开度")
public Double givenAngle;
@Schema(description = "实际开度")
public Double actualAngle;
@Schema(description = "是否正常")
public Boolean isNormal;
@Schema(description = "模式 关1/开2/自动3")
public Integer mode;
@Schema(description = "空压机状态 关1/开2")
public Integer compressorStatus;
@Schema(description = "风机状态 关1/开2")
public Integer fanStatus;
@Schema(description = "加热环状态 关1/开2")
public Integer heatingRingStatus;
@Schema(description = "电磁阀状态1 关1/开2")
public Integer valveStatus;
@Schema(description = "电磁阀状态2 关1/开2")
public Integer valveStatus2;
@Schema(description = "电磁阀状态3 关1/开2")
public Integer valveStatus3;
@Schema(description = "俯仰模式 关1/开2/自动3")
public Integer pitchMode;
@Schema(description = "俯仰角度")
public Double pitchAngle;
@Schema(description = "俯仰状态 关1/开2")
public Integer pitchStatus;
@Schema(description = "摆头模式 关1/开2/自动3")
public Integer yawMode;
@Schema(description = "摆头角度")
public Double yawAngle;
@Schema(description = "摆头状态 关1/开2")
public Integer yawStatus;
}

View File

@@ -0,0 +1,22 @@
package com.ski.lichuan.model.dashboard;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "雪道位置信息")
@Data
public class TrailPosition {
@Schema(description = "ID")
public Integer id;
@Schema(description = "雪道ID")
public Integer trailId;
@Schema(description = "位置")
public Integer position;
@Schema(description = "名称")
public String name;
@Schema(description = "雪机状态 0无 1正常 2停止")
public Integer snowMachineStatus;
@Schema(description = "造雪机ID")
public Integer snowMachineId;
}

View File

@@ -0,0 +1,23 @@
package com.ski.lichuan.model.dashboard;
import com.ski.lichuan.model.device.config.SnowMachineDeviceParams;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "雪道位置状态")
@Data
public class TrailPositionInfo {
@Schema(description = "雪道位置状态信息")
public TrailPositionMonitorInfo trailPositionMonitorInfo;
// 造雪机信息
@Schema(description = "造雪机设备状态信息")
public SnowMachineDeviceInfo snowMakerInfo;
// 雪机配置
@Schema(description = "造雪机配置信息")
public SnowMachineDeviceParams snowMakerDeviceParams;
// 统计信息
@Schema(description = "统计信息")
public TrailPositionStatisticsInfo trailPositionStatisticsInfo;
}

View File

@@ -0,0 +1,55 @@
package com.ski.lichuan.model.dashboard;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "雪道位置状态信息")
@Data
public class TrailPositionMonitorInfo {
@Schema(description = "雪道位置信息")
public TrailPosition trailPosition;
// 造雪机名称
@Schema(description = "造雪机名称")
public String snowMakerName;
// 是否有造雪机
@Schema(description = "是否有造雪机")
public Boolean hasSnowMaker;
// 优先级
@Schema(description = "优先级")
public Integer priority;
// 环境温度
@Schema(description = "环境温度")
public Double environmentTemperature;
// 环境湿度
@Schema(description = "环境湿度")
public Double environmentHumidity;
// 湿球温度
@Schema(description = "湿球温度")
public Double wetBulbTemperature;
// 通讯状态
@Schema(description = "通讯状态")
public Boolean communicationStatus;
// 自动模式
@Schema(description = "自动模式")
public Boolean autoMode;
// 是否正常
@Schema(description = "是否正常")
public Boolean isNormal;
// 正常运行
@Schema(description = "正常运行")
public Boolean isRunning;
// 温度条件满足
@Schema(description = "温度条件满足")
public Boolean temperatureConditionSatisfied;
// 风速
@Schema(description = "风速")
public Double windSpeed;
// 风向
@Schema(description = "风向")
public Double windDirection;
}

View File

@@ -0,0 +1,17 @@
package com.ski.lichuan.model.dashboard;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "雪道位置统计信息")
@Data
public class TrailPositionStatisticsInfo {
@Schema(description = "运行时长")
public Double runningDuration;
@Schema(description = "流量统计")
public Double flowStatistics;
@Schema(description = "造雪面积")
public Double snowArea;
}

View File

@@ -0,0 +1,14 @@
package com.ski.lichuan.model.device;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "设备")
@Data
public abstract class Device {
@Schema(description = "设备名称")
public String name;
@Schema(description = "设备类型")
public Integer type;
@Schema(description = "设备ID")
public Integer id;
}

View File

@@ -0,0 +1,21 @@
package com.ski.lichuan.model.device.Impl;
import com.ski.lichuan.model.common.DeviceEnum;
import com.ski.lichuan.model.device.Device;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "造雪机设备")
public class SnowMachineDevice extends Device {
// 设备的LORA地址
@Schema(description = "设备的LORA地址")
public String lora;
public SnowMachineDevice() {
this.type = DeviceEnum.SNOW_MAKER.getValue();
}
}

View File

@@ -0,0 +1,18 @@
package com.ski.lichuan.model.device.Impl;
import com.ski.lichuan.model.common.DeviceEnum;
import com.ski.lichuan.model.device.Device;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "水泵设备")
public class WaterPumpDevice extends Device {
@Schema(description = "LoRa MAC地址")
public String loraMac;
public WaterPumpDevice() {
this.type = DeviceEnum.WATER_PUMP.getValue();
}
}

View File

@@ -0,0 +1,28 @@
package com.ski.lichuan.model.device.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "造雪机设备参数")
public class SnowMachineDeviceParams {
// 工作模式 关1/开2/自动3
@Schema(description = "工作模式 关1/开2/自动3")
public Integer workMode;
// 优先级 1-5
@Schema(description = "优先级 1-5")
public Integer priority;
// 雪质 1-5
@Schema(description = "雪质 1-5")
public Integer snowQuality;
// 启动温度 -5.0 ~ 5.0
@Schema(description = "启动温度 -5.0 ~ 5.0")
public Double startTemperature;
// 停止温度 -5.0 ~ 5.0
@Schema(description = "停止温度 -5.0 ~ 5.0")
public Double stopTemperature;
// 流量调节 0 ~ 50
@Schema(description = "流量调节 0 ~ 50")
public Integer flowAdjustment;
}

View File

@@ -0,0 +1,21 @@
package com.ski.lichuan.model.device.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "造雪机俯仰水平控制参数")
public class SnowMachineDevicePitchHorizontalParams {
// 雪机工作模式 关1/开2/自动3
@Schema(description = "工作模式 关1/开2/自动3")
public Integer workMode;
// 俯仰工作模式 关1/开2/自动3
@Schema(description = "俯仰工作模式 关1/开2/自动3")
public Integer pitchWorkMode;
// 水平工作模式 关1/开2/自动3
@Schema(description = "水平工作模式 关1/开2/自动3")
public Integer horizontalWorkMode;
}

View File

@@ -0,0 +1,21 @@
package com.ski.lichuan.model.log;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Schema(description = "设备警告记录")
public class DeviceWarningRecord {
@Schema(description = "警告记录ID")
public Integer id;
@Schema(description = "警告记录内容")
public String content;
@Schema(description = "警告记录类型")
public String warningType;
@Schema(description = "警告记录设备")
public String warningDevice;
@Schema(description = "警告记录时间")
public LocalDateTime warningTime;
}

View File

@@ -0,0 +1,23 @@
package com.ski.lichuan.model.log;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Schema(description = "操作记录")
public class OperationRecord {
@Schema(description = "操作记录ID")
public Integer id;
@Schema(description = "操作记录操作人")
public String operator;
@Schema(description = "操作记录内容")
public String content;
@Schema(description = "操作记录类型")
public String operationType;
@Schema(description = "操作记录时间")
public LocalDateTime operationTime;
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ski.lichuan.mapper.SnowMachineMapper">
<!-- 结果映射 -->
<resultMap id="SnowMachineResultMap" type="com.ski.lichuan.model.device.Impl.SnowMachineDevice">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="lora" property="lora"/>
</resultMap>
<!-- 插入造雪机设备 -->
<insert id="insert" parameterType="com.ski.lichuan.model.device.Impl.SnowMachineDevice">
INSERT INTO snow_machine (name, lora) VALUES (#{name}, #{lora})
</insert>
<!-- 查询所有造雪机设备 -->
<select id="selectAll" resultMap="SnowMachineResultMap">
SELECT * FROM snow_machine
</select>
<!-- 根据ID查询造雪机设备 -->
<select id="selectById" parameterType="java.lang.Integer" resultMap="SnowMachineResultMap">
SELECT * FROM snow_machine WHERE id = #{id}
</select>
<!-- 根据Lora地址查询造雪机设备 -->
<select id="selectByLora" parameterType="java.lang.String" resultMap="SnowMachineResultMap">
SELECT * FROM snow_machine WHERE lora = #{lora}
</select>
<!-- 查询未绑定的造雪机设备 -->
<select id="selectUnboundMachines" resultMap="SnowMachineResultMap">
SELECT sm.* FROM snow_machine sm LEFT JOIN trail_position tp ON sm.id = tp.snow_machine_id WHERE tp.snow_machine_id IS NULL
</select>
<!-- 统计造雪机设备数量 -->
<select id="count" resultType="int">
SELECT count(*) FROM snow_machine
</select>
<!-- 更新造雪机设备名称 -->
<update id="updateName" parameterType="com.ski.lichuan.model.device.Impl.SnowMachineDevice">
UPDATE snow_machine SET name = #{name} WHERE id = #{id}
</update>
<!-- 更新造雪机设备名称和Lora地址 -->
<update id="updateNameAndLora" parameterType="com.ski.lichuan.model.device.Impl.SnowMachineDevice">
UPDATE snow_machine SET name = #{name}, lora = #{lora} WHERE id = #{id}
</update>
<!-- 根据ID删除造雪机设备 -->
<delete id="deleteById" parameterType="java.lang.Integer">
DELETE FROM snow_machine WHERE id = #{id}
</delete>
<!-- 根据ID插入造雪机设备 -->
<insert id="insertById" parameterType="com.ski.lichuan.model.device.Impl.SnowMachineDevice">
INSERT INTO snow_machine (id, name, lora) VALUES (#{id}, #{name}, #{lora})
</insert>
</mapper>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ski.lichuan.mapper.TrailPositionMapper">
<resultMap id="BaseResultMap" type="com.ski.lichuan.model.dashboard.TrailPosition">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="trail_id" jdbcType="INTEGER" property="trailId"/>
<result column="position" jdbcType="VARCHAR" property="position"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="snow_machine_id" jdbcType="INTEGER" property="snowMachineId"/>
<result column="snow_machine_status" jdbcType="INTEGER" property="snowMachineStatus"/>
</resultMap>
<select id="selectByTrailId" resultMap="BaseResultMap">
SELECT * FROM trail_position WHERE trail_id = #{trailId}
</select>
<select id="selectById" resultMap="BaseResultMap">
SELECT * FROM trail_position WHERE id = #{id}
</select>
<select id="selectAll" resultMap="BaseResultMap">
SELECT * FROM trail_position
</select>
<!-- 获取所有未绑定雪机的雪道位置信息 -->
<select id="selectUnboundPositions" resultMap="BaseResultMap">
SELECT * FROM trail_position WHERE snow_machine_id IS NULL
</select>
<select id="selectBoundPositions" resultMap="BaseResultMap">
SELECT * FROM trail_position WHERE snow_machine_id IS NOT NULL
</select>
<insert id="insert" keyColumn="id" keyProperty="id" useGeneratedKeys="true">
INSERT INTO trail_position (trail_id, position, name)
VALUES (#{trailId}, #{position}, #{name})
</insert>
<update id="update">
UPDATE trail_position
SET trail_id = #{trailId}, position = #{position}, name = #{name}
WHERE id = #{id}
</update>
<update id="clearSnowMachine">
UPDATE trail_position
SET snow_machine_status = 0, snow_machine_id = NULL
WHERE id = #{id}
</update>
<update id="updateSnowMachine">
UPDATE trail_position
SET snow_machine_id = #{snowMachineId}
WHERE id = #{id}
</update>
<!-- 根据雪道位置ID列表查询造雪机设备ID列表 -->
<select id="selectDeviceIdsByTrailPositionIds" resultType="java.lang.Integer">
SELECT snow_machine_id FROM trail_position WHERE id IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- 清空指定ID列表外的绑定 -->
<update id="clearUnboundPositions">
UPDATE trail_position
SET snow_machine_status = 0, snow_machine_id = NULL
WHERE id NOT IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</update>
</mapper>

View File

@@ -9,7 +9,6 @@
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.ski.lichuan</groupId>
<artifactId>ski-dashboard-service</artifactId>
<properties>
@@ -17,6 +16,35 @@
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<!-- ThingsBoard 官方仓库 -->
<repository>
<id>thingsboard</id>
<name>ThingsBoard Repository</name>
<url>https://repo.thingsboard.io/artifactory/libs-release-public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<!-- 阿里云镜像配置 -->
<repository>
<id>aliyunmaven</id>
<name>aliyunmaven</name>
<url>https://maven.aliyun.com/repository/public</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.ski</groupId>
@@ -25,11 +53,55 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.ski.lichuan</groupId>
<groupId>com.ski</groupId>
<artifactId>ski-dashboard-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>rest-client</artifactId>
<version>4.2.1</version>
</dependency>
</dependencies>
<pluginRepositories>
<pluginRepository>
<id>aliyunmaven</id>
<name>aliyunmaven</name>
<url>https://maven.aliyun.com/repository/public</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>thingsboard</id>
<name>ThingsBoard Repository</name>
<url>https://repo.thingsboard.io/artifactory/libs-release-public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<!-- 排除Spring Boot Maven插件因为这是一个数据模型模块不是可执行应用程序 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,119 @@
package com.ski.lichuan;
import org.thingsboard.rest.client.RestClient;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.*;
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<Device> devicesList = getDevices();
// List<DashboardInfo> dashboardsList = getDashboards();
for (Device device : devicesList) {
if (!device.getName().equals("iot_snowmaker_m005")) {
continue;
}
System.out.println("找到指定设备");
getDataByDevice(device);
}
}
/**
* 获取ThingsBoard客户端
* @return
*/
public static List<Device> getDevices() {
String url = "https://tb.ski.bkiiot.com";
String username = "tenant@thingsboard.org";
String password = "tenant";
List<Device> devicesList = new ArrayList<>();
RestClient client = new RestClient(url);
client.login(username, password);
PageData<Device> devices;
PageLink pageLink = new PageLink(10);
do {
devices = client.getTenantDevices("", pageLink);
devices.getData().forEach(System.out::println);
devicesList.addAll(devices.getData());
pageLink = pageLink.nextPageLink();
} while (devices.hasNext());
client.logout();
client.close();
return devicesList;
}
/**
* 获取仪表盘
*/
public static List<DashboardInfo> getDashboards() {
String url = "https://tb.ski.bkiiot.com";
String username = "tenant@thingsboard.org";
String password = "tenant";
List<DashboardInfo> dashboardsList = new ArrayList<>();
RestClient client = new RestClient(url);
client.login(username, password);
PageData<DashboardInfo> dashboards;
PageLink pageLink = new PageLink(10);
do {
dashboards = client.getTenantDashboards(pageLink);
dashboards.getData().forEach(System.out::println);
dashboardsList.addAll(dashboards.getData());
pageLink = pageLink.nextPageLink();
} while (dashboards.hasNext());
client.logout();
client.close();
return dashboardsList;
}
/**
* 获取设备属性
*/
public static void getDeviceAttributes(Device device) {
String url = "https://tb.ski.bkiiot.com";
String username = "tenant@thingsboard.org";
String password = "tenant";
RestClient client = new RestClient(url);
client.login(username, password);
// 获取属性
List<String> keys = client.getAttributeKeys(device.getId());
client.getAttributeKvEntries(device.getId(), keys).forEach(System.out::println);
}
/**
* 获取设备数据
*/
public static void getDataByDevice(Device device) {
String url = "https://tb.ski.bkiiot.com";
String username = "tenant@thingsboard.org";
String password = "tenant";
RestClient client = new RestClient(url);
client.login(username, password);
client.getLatestTimeseries(device.getId(), List.of()).forEach(System.out::println);
client.logout();
client.close();
}
}

View File

@@ -1,13 +1,16 @@
package com.ski.lichuan.initializer;
import com.ski.lichuan.common.utils.MD5Util;
import com.ski.lichuan.mapper.UserMapper;
import com.ski.lichuan.model.auth.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class AdminUserInitializer implements CommandLineRunner {
@Autowired
@@ -24,15 +27,16 @@ public class AdminUserInitializer implements CommandLineRunner {
// 创建默认管理员用户
SysUser adminUser = new SysUser();
adminUser.setUsername("admin");
// 默认密码为 admin123实际使用时应修改为更安全的密码
adminUser.setPassword(passwordEncoder.encode("admin123"));
// 默认密码为 123456,实际使用时应修改为更安全的密码 使用MD5加密
String text = MD5Util.encrypt("123456");
adminUser.setPassword(passwordEncoder.encode(text));
adminUser.setNickname("系统管理员");
adminUser.setStatus(1); // 启用状态
// 插入用户到数据库
userMapper.insertUser(adminUser);
System.out.println("默认管理员用户已创建,用户名: admin, 密码: admin123 (请登录后及时修改密码)");
log.info("默认管理员用户已创建,用户名: admin, 密码: 123456 (请登录后及时修改密码)");
} else {
System.out.println("管理员用户已存在");
log.info("管理员用户已存在");
}
}
}

View File

@@ -0,0 +1,53 @@
package com.ski.lichuan.initializer;
import com.ski.lichuan.mapper.TrailPositionMapper;
import com.ski.lichuan.model.dashboard.TrailPosition;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import java.util.List;
@Slf4j
public class TrailInitializer implements CommandLineRunner {
@Autowired
private TrailPositionMapper trailPositionMapper;
@Override
public void run(String... args) throws Exception {
List<TrailPosition> trailPositions = trailPositionMapper.selectAll();
if (trailPositions.isEmpty()) {
log.info("数据库中不存在坑位数据,开始初始化...");
Integer[][] positions = {
{1, 1},
{1, 2},
{1, 3},
{1, 4},
{2, 1},
{2, 2},
{2, 3},
{2, 4},
{2, 5},
{2, 6},
{3, 1},
{3, 2},
{3, 3},
{3, 4},
{3, 5},
{3, 6},
{4, 1},
{4, 2},
{4, 3}
};
for (Integer[] position : positions) {
TrailPosition trailPosition = new TrailPosition();
trailPosition.setTrailId(position[0]);
trailPosition.setPosition(position[1]);
trailPosition.setName(position[0] + "-" + position[1]);
trailPositionMapper.insert(trailPosition);
}
}
}
}

View File

@@ -0,0 +1,177 @@
package com.ski.lichuan.services;
import com.ski.lichuan.model.dashboard.TrailPosition;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.rest.client.RestClient;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
public class DeviceDataPollingService {
// 高优先级查询列表(造雪机)
public static final List<String> HIGH_PRIORITY_SNOWMACHINE_IDS = Arrays.asList();
// 高优先级查询列表(水泵)
public static final List<String> HIGH_PRIORITY_PUMP_IDS = Arrays.asList();
@Value("${thingsboard.client.url:https://tb.ski.bkiiot.com}")
private String clientUrl;
@Value("${thingsboard.client.username:tenant@thingsboard.org}")
private String clientUsername;
@Value("${thingsboard.client.password:tenant}")
private String clientPassword;
// 客户端
private RestClient client;
@Autowired
private TrailPositionService trailPositionService;
@Autowired
private SnowMachineDeviceService snowMachineDeviceService;
/**
* 定时轮询设备状态
*/
@Scheduled(fixedRate = 60000)
public void pollDeviceStatus() {
/**
* 核心逻辑每60秒轮询一次所有设备状态查询所有坑位上的雪机和水泵的状态
*/
}
/**
* 定时轮询设备数据
* 每15秒执行一次
*/
@Scheduled(fixedRate = 15000)
public void pollDeviceData() {
/**
* 核心逻辑每15秒轮询一次所有设备数据查询所有坑位上的雪机和水泵的状态
*/
}
/**
* 定时轮询设备列表(造雪机、水泵)
* 每120秒执行一次
*/
@Scheduled(fixedRate = 120000)
public void pollDevices() {
/**
* 核心逻辑每120秒轮询一次所有设备列表更新造雪机设备和水泵
*/
log.info("开始查询设备列表");
List<Device> devices = getDevices();
List<TrailPosition> trailPositions = trailPositionService.getAllTrailPositionList();
// 转为Map方便查询 (TrailId,Position) -> TrailPosition
Map<Integer, TrailPosition> trailPositionMap = trailPositions.stream()
.collect(Collectors.toMap(trailPosition -> trailPosition.getTrailId() * 10000 + trailPosition.getPosition(), trailPosition -> trailPosition));
for (Device device : devices) {
//s-{id}-{trailId}-{position}-{disable}
if (device.getLabel() != null && device.getLabel().startsWith("s-")) {
String[] parts = device.getLabel().split("-");
if (parts.length < 3) {
// 异常的设备
log.warn("异常的造雪机设备标签格式:{}", device.getLabel());
continue;
}
if (parts.length >= 4 && "disable".equals(parts[3])) {
// 禁用状态,跳过
log.info("造雪机设备 {} 已禁用,跳过", device.getLabel());
continue;
}
// 说明是造雪机设备
String id = parts[1];
String trailId = parts[2];
String position = parts[3];
// 判断是数字
if (!trailId.matches("\\d+") || !position.matches("\\d+")) {
// 不是数字,跳过
log.warn("造雪机设备 {} 标签格式错误,跳过", device.getLabel());
continue;
}
// id 不为空
if (id.isEmpty()) {
// 空id跳过
log.warn("造雪机设备 {} 标签格式错误,跳过", device.getLabel());
continue;
}
Integer idValue = Integer.parseInt(id);
Integer trailIdValue = Integer.parseInt(trailId);
Integer positionValue = Integer.parseInt(position);
// 更新雪机信息
String name = device.getName() != null ? device.getName() : "雪机" + trailId + position;
try {
snowMachineDeviceService.updateSnowMachineDeviceNameAndLora(idValue, name, device.getId().toString());
log.info("更新造雪机设备信息 {} 成功", device.getLabel());
} catch (Exception e) {
log.error("更新造雪机设备信息 {} 失败:{}", device.getLabel(), e.getMessage());
}
// 检查是否存在对应的雪道位置
TrailPosition trailPosition = trailPositionMap.get(trailIdValue * 10000 + positionValue);
if (trailPosition == null) {
// 雪道位置不存在,跳过
log.warn("造雪机设备 {} 对应的雪道位置不存在,跳过", device.getLabel());
continue;
}
// 更新雪道位置的造雪机ID
trailPosition.setSnowMachineId(idValue);
trailPositionService.updateTrailPositionSnowMachineId(trailPosition.getId(), idValue);
// 从Map中删除该雪道位置防止重复处理
trailPositionMap.remove(trailIdValue * 10000 + positionValue);
}
}
// 清楚Map中剩余的雪道位置
List<Integer> mapIdList = trailPositionMap.values().stream().map(TrailPosition::getId).collect(Collectors.toList());
try {
trailPositionService.clearTrailPositionSnowMachineIdBatch(mapIdList);
} catch (Exception e) {
log.error("清除雪道位置 {} 造雪机ID失败{}", mapIdList, e.getMessage());
}
}
private RestClient getClient() {
if (client == null) {
client = new RestClient(clientUrl);
client.login(clientUsername, clientPassword);
}
return client;
}
public List<Device> getDevices() {
RestClient client = getClient();
List<Device> devicesList = new ArrayList<>();
PageData<Device> devices;
PageLink pageLink = new PageLink(10);
do {
devices = client.getTenantDevices("", pageLink);
devices.getData().forEach(device -> log.info("获取到的造雪机设备:{}", device));
devicesList.addAll(devices.getData());
pageLink = pageLink.nextPageLink();
} while (devices.hasNext());
return devicesList;
}
}

View File

@@ -0,0 +1,4 @@
package com.ski.lichuan.services;
public class IoTService {
}

View File

@@ -0,0 +1,158 @@
package com.ski.lichuan.services;
import com.ski.lichuan.mapper.SnowMachineMapper;
import com.ski.lichuan.model.device.Impl.SnowMachineDevice;
import com.ski.lichuan.model.device.config.SnowMachineDeviceParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SnowMachineDeviceService {
@Autowired
private SnowMachineMapper snowMachineMapper;
/**
* 新增造雪机设备
*
* @param snowMachineDevice 造雪机设备
* @return 新增结果
*/
public int addSnowMachineDevice(SnowMachineDevice snowMachineDevice) {
// 检查是否已存在相同LORA地址的造雪机设备
if (snowMachineMapper.selectByLora(snowMachineDevice.getLora()) != null) {
throw new IllegalArgumentException("造雪机设备已存在相同LORA地址");
}
return snowMachineMapper.insert(snowMachineDevice);
}
/**
* 查询所有未绑定位置的造雪机设备
*
* @return 未绑定位置的造雪机设备列表
*/
public List<SnowMachineDevice> selectUnboundMachines() {
return snowMachineMapper.selectUnboundMachines();
}
/**
* 查询所有造雪机设备
*
* @return 所有造雪机设备列表
*/
public List<SnowMachineDevice> getAllSnowMachineDevices() {
return snowMachineMapper.selectAll();
}
/**
* 根据ID查询造雪机设备
*
* @param id 造雪机设备ID
* @return 造雪机设备
*/
public SnowMachineDevice getSnowMachineDeviceById(Integer id) {
// 检查是否存在该ID的造雪机设备
if (snowMachineMapper.selectById(id) == null) {
throw new IllegalArgumentException("造雪机设备不存在");
}
return snowMachineMapper.selectById(id);
}
/**
* 根据LORA地址查询造雪机设备
*
* @param lora 造雪机设备LORA地址
* @return 造雪机设备
*/
public SnowMachineDevice selectByLora(String lora) {
return snowMachineMapper.selectByLora(lora);
}
/**
* 统计造雪机设备数量
*
* @return 造雪机设备数量
*/
public int count() {
return snowMachineMapper.count();
}
/**
* 更新造雪机设备名称
*
* @param snowMachineDevice 造雪机设备
* @return 更新结果
*/
public int updateSnowMachineDevice(SnowMachineDevice snowMachineDevice) {
if (snowMachineMapper.selectById(snowMachineDevice.getId()) == null) {
throw new IllegalArgumentException("造雪机设备不存在");
}
return snowMachineMapper.updateName(snowMachineDevice);
}
/**
* 更新造雪机信息
*
* @param id 造雪机设备ID
* @param name 造雪机设备名称
* @param lora 造雪机设备LORA地址
* @return 更新结果
*/
public int updateSnowMachineDeviceNameAndLora(Integer id, String name, String lora) {
// 检查是否存在该ID的造雪机设备
if (snowMachineMapper.selectById(id) == null) {
// 则新增
SnowMachineDevice newSnowMachineDevice = new SnowMachineDevice();
newSnowMachineDevice.setId(id);
newSnowMachineDevice.setName(name);
newSnowMachineDevice.setLora(lora);
return snowMachineMapper.insertById(newSnowMachineDevice);
} else {
// 更新造雪机设备信息
SnowMachineDevice snowMachineDevice = snowMachineMapper.selectById(id);
snowMachineDevice.setName(name);
snowMachineDevice.setLora(lora);
return snowMachineMapper.updateName(snowMachineDevice);
}
}
/**
* 删除造雪机设备
*
* @param id 造雪机设备ID
* @return 删除结果
*/
public int deleteSnowMachineDevice(Integer id) {
return snowMachineMapper.deleteById(id);
}
/**
* 获取所有雪机设备信息
*
* @return 所有雪机设备列表
*/
public List<SnowMachineDevice> selectAllDevices() {
return snowMachineMapper.selectAll();
}
/**
* 群控雪机设备
* @param deviceList 雪机设备列表
* @param params 造雪机设备参数
* @return 群控结果
*/
public boolean groupControl(List<SnowMachineDevice> deviceList, SnowMachineDeviceParams params) {
// 检查是否存在该ID的造雪机设备
if (deviceList.isEmpty()) {
throw new IllegalArgumentException("造雪机设备不存在");
}
// 群控雪机设备
for (SnowMachineDevice device : deviceList) {
// 发送群控指令
// ...
}
return true;
}
}

View File

@@ -0,0 +1,310 @@
package com.ski.lichuan.services;
import com.ski.lichuan.mapper.SnowMachineMapper;
import com.ski.lichuan.mapper.TrailPositionMapper;
import com.ski.lichuan.model.common.DeviceEnum;
import com.ski.lichuan.model.dashboard.*;
import com.ski.lichuan.model.device.Impl.SnowMachineDevice;
import com.ski.lichuan.model.device.config.SnowMachineDeviceParams;
import com.ski.lichuan.model.device.config.SnowMachineDevicePitchHorizontalParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class TrailPositionService {
@Autowired
private TrailPositionMapper trailPositionMapper;
@Autowired
private SnowMachineMapper snowMachineDeviceMapper;
/**
* 获取所有已绑定雪机的雪道位置信息
*/
public List<TrailPosition> getBoundTrailPositions() {
return trailPositionMapper.selectBoundPositions();
}
/**
* 获取所有未绑定雪机的雪道位置信息
*/
public List<TrailPosition> getUnboundTrailPositions() {
return trailPositionMapper.selectUnboundPositions();
}
/**
* 获取所有雪道及位置信息
*/
public List<List<TrailPosition>> getAllTrailPositions() {
List<TrailPosition> allTrailPositions = trailPositionMapper.selectAll();
List<List<TrailPosition>> result = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
int finalI = i;
List<TrailPosition> trailPositions = allTrailPositions.stream()
.filter(trailPosition -> trailPosition.getTrailId().equals(finalI))
.collect(Collectors.toList());
result.add(trailPositions);
}
// 遍历result将每个雪道的位置信息按position排序
for (List<TrailPosition> trailPositions : result) {
// 更新trailPosition状态
for (TrailPosition trailPosition : trailPositions) {
if (trailPosition.getSnowMachineStatus() == 0) {
// 暂时随机
trailPosition.setSnowMachineStatus((int) (Math.random() * 3));
}
}
trailPositions.sort((p1, p2) -> p1.getPosition().compareTo(p2.getPosition()));
}
return result;
}
/**
* 获取雪道位置的详细信息(造雪机)
*/
public TrailPositionInfo getTrailPositionInfo(Integer id) {
TrailPosition trailPosition = trailPositionMapper.selectById(id);
if (trailPosition == null) {
return null;
} else {
TrailPositionInfo result = new TrailPositionInfo();
Integer trailId = id / 10;
Integer position = id % 10;
// 雪机设备
SnowMachineDevice snowMachineDevice = new SnowMachineDevice();
snowMachineDevice.setLora("1234567890123456");
snowMachineDevice.setId(1000000000 + id);
snowMachineDevice.setName("造雪机-" + id);
snowMachineDevice.setType(DeviceEnum.SNOW_MAKER.getValue());
// 雪道位置信息
trailPosition.setSnowMachineStatus(1);
// 雪道位置状态信息
TrailPositionMonitorInfo trailPositionMonitorInfo = new TrailPositionMonitorInfo();
trailPositionMonitorInfo.setTrailPosition(trailPosition);
trailPositionMonitorInfo.setSnowMakerName(snowMachineDevice.getName());
trailPositionMonitorInfo.setPriority(1);
trailPositionMonitorInfo.setEnvironmentTemperature(-3.5);
trailPositionMonitorInfo.setEnvironmentHumidity(50.0);
trailPositionMonitorInfo.setWetBulbTemperature(0.5);
trailPositionMonitorInfo.setCommunicationStatus(true);
trailPositionMonitorInfo.setIsNormal(true);
trailPositionMonitorInfo.setIsRunning(true);
trailPositionMonitorInfo.setTemperatureConditionSatisfied(true);
trailPositionMonitorInfo.setWindSpeed(5.0);
trailPositionMonitorInfo.setWindDirection(180.0);
// 造雪机设备状态
SnowMachineDeviceInfo snowMachineDeviceInfo = new SnowMachineDeviceInfo();
snowMachineDeviceInfo.setSnowMachineDevice(snowMachineDevice);
snowMachineDeviceInfo.setFrontWaterPressure(0.5 + Math.random() * 0.3); // 前端水压 0.5-0.8 MPa
snowMachineDeviceInfo.setBackWaterPressure(0.4 + Math.random() * 0.2); // 后端水压 0.4-0.6 MPa
snowMachineDeviceInfo.setPressure(0.8 + Math.random() * 0.2); // 气压 0.8-1.0 MPa
snowMachineDeviceInfo.setTemperature(2.5 + Math.random() * 2.0); // 水温 2.5-4.5 ℃
snowMachineDeviceInfo.setGivenAngle(45.0 + Math.random() * 10.0); // 给定开度 45-55°
snowMachineDeviceInfo.setActualAngle(44.0 + Math.random() * 12.0); // 实际开度 44-56°
snowMachineDeviceInfo.setIsNormal(Math.random() > 0.1); // 是否正常 90%概率正常
snowMachineDeviceInfo.setMode((int) (Math.random() * 3) + 1); // 模式 1-3随机
snowMachineDeviceInfo.setCompressorStatus((int) (Math.random() * 2) + 1); // 空压机状态 1-2随机
snowMachineDeviceInfo.setFanStatus((int) (Math.random() * 2) + 1); // 风机状态 1-2随机
snowMachineDeviceInfo.setHeatingRingStatus((int) (Math.random() * 2) + 1); // 加热环状态 1-2随机
snowMachineDeviceInfo.setValveStatus((int) (Math.random() * 2) + 1); // 电磁阀状态1 1-2随机
snowMachineDeviceInfo.setValveStatus2((int) (Math.random() * 2) + 1); // 电磁阀状态2 1-2随机
snowMachineDeviceInfo.setValveStatus3((int) (Math.random() * 2) + 1); // 电磁阀状态3 1-2随机
snowMachineDeviceInfo.setPitchMode((int) (Math.random() * 3) + 1); // 俯仰模式 1-3随机
snowMachineDeviceInfo.setPitchAngle(15.0 + Math.random() * 20.0); // 俯仰角度 15-35°
snowMachineDeviceInfo.setPitchStatus((int) (Math.random() * 2) + 1); // 俯仰状态 1-2随机
snowMachineDeviceInfo.setYawMode((int) (Math.random() * 3) + 1); // 摆头模式 1-3随机
snowMachineDeviceInfo.setYawAngle(30.0 + Math.random() * 40.0); // 摆头角度 30-70°
snowMachineDeviceInfo.setYawStatus((int) (Math.random() * 2) + 1); // 摆头状态 1-2随机
// 造雪机配置
SnowMachineDeviceParams snowMakerDeviceParams = new SnowMachineDeviceParams();
snowMakerDeviceParams.setWorkMode((int) (Math.random() * 3) + 1); // 工作模式 1-3随机
snowMakerDeviceParams.setPriority((int) (Math.random() * 5) + 1); // 优先级 1-5随机
snowMakerDeviceParams.setSnowQuality((int) (Math.random() * 5) + 1); // 雪质 1-5随机
snowMakerDeviceParams.setStartTemperature(-5.0 + Math.random() * 10.0); // 启动温度 -5.0 ~ 5.0
snowMakerDeviceParams.setStopTemperature(-5.0 + Math.random() * 10.0); // 停止温度 -5.0 ~ 5.0
snowMakerDeviceParams.setFlowAdjustment((int) (Math.random() * 50)); // 流量调节 0 ~ 50
// 数据统计
TrailPositionStatisticsInfo trailPositionStatisticsInfo = new TrailPositionStatisticsInfo();
trailPositionStatisticsInfo.setRunningDuration(120.5 + Math.random() * 100.0); // 运行时长 120.5-220.5分钟
trailPositionStatisticsInfo.setFlowStatistics(1000.0 + Math.random() * 500.0); // 流量统计 1000-1500 L/H
trailPositionStatisticsInfo.setSnowArea(500.0 + Math.random() * 300.0); // 造雪面积 500-800 平方米
result.setTrailPositionMonitorInfo(trailPositionMonitorInfo);
result.setSnowMakerInfo(snowMachineDeviceInfo);
result.setSnowMakerDeviceParams(snowMakerDeviceParams);
result.setTrailPositionStatisticsInfo(trailPositionStatisticsInfo);
return result;
}
}
/**
* 关联造雪机设备
*/
public boolean connectDevice(Integer trailPositionId, Integer deviceId) {
TrailPosition trailPosition = trailPositionMapper.selectById(trailPositionId);
if (trailPosition == null) {
return false;
}
SnowMachineDevice snowMachineDevice = snowMachineDeviceMapper.selectById(deviceId);
if (snowMachineDevice == null) {
return false;
}
trailPosition.setSnowMachineId(deviceId);
trailPosition.setSnowMachineStatus(1);
trailPositionMapper.updateSnowMachine(trailPositionId, trailPosition.getSnowMachineId());
return true;
}
/**
* 取消关联造雪机设备
*/
public boolean disconnectDevice(Integer trailPositionId) {
TrailPosition trailPosition = trailPositionMapper.selectById(trailPositionId);
if (trailPosition == null) {
return false;
}
trailPosition.setSnowMachineId(null);
trailPosition.setSnowMachineStatus(0);
trailPositionMapper.clearSnowMachine(trailPositionId);
return true;
}
/**
* 群控造雪机设备
*
* @param trailPositionIds 雪道位置ID列表
* @param params 造雪机设备参数
* @return 群控结果
*/
public boolean groupControl(List<Integer> trailPositionIds, SnowMachineDeviceParams params) {
// 检查是否存在该ID的造雪机设备
if (trailPositionIds.isEmpty()) {
throw new IllegalArgumentException("雪道位置不存在");
}
// 反查造雪机设备ID
List<Integer> deviceIds = trailPositionMapper.selectDeviceIdsByTrailPositionIds(trailPositionIds);
if (deviceIds.isEmpty()) {
throw new IllegalArgumentException("造雪机设备不存在");
}
// 群控雪机设备
for (Integer deviceId : deviceIds) {
// 发送群控指令
// ...
}
return true;
}
/**
* 俯仰水平控制
*
* @param trailPositionId 雪道位置ID
* @param params 俯仰水平参数
* @return 控制结果
*/
public Boolean pitchHorizontalControl(Integer trailPositionId, SnowMachineDevicePitchHorizontalParams params) {
// 检查是否存在该ID的造雪机设备
TrailPosition trailPosition = trailPositionMapper.selectById(trailPositionId);
if (trailPosition == null) {
throw new IllegalArgumentException("雪道位置不存在");
}
// 反查造雪机设备ID
SnowMachineDevice snowMachineDevice = snowMachineDeviceMapper.selectById(trailPosition.getSnowMachineId());
if (snowMachineDevice == null) {
throw new IllegalArgumentException("造雪机设备不存在");
}
// 发送俯仰水平控制指令
// ...
return true;
}
/**
* 保存造雪机参数
*
* @param trailPosition 雪道位置ID
* @param params 造雪机设备参数
* @return 保存结果
*/
public Boolean saveSnowMakerDeviceParams(Integer trailPosition, SnowMachineDeviceParams params) {
// 检查是否存在该ID的造雪机设备
TrailPosition trailPositionEntity = trailPositionMapper.selectById(trailPosition);
if (trailPositionEntity == null) {
throw new IllegalArgumentException("雪道位置不存在");
}
// 反查造雪机设备ID
SnowMachineDevice snowMachineDevice = snowMachineDeviceMapper.selectById(trailPositionEntity.getSnowMachineId());
if (snowMachineDevice == null) {
throw new IllegalArgumentException("造雪机设备不存在");
}
// 保存造雪机参数
// ...
return true;
}
/**
* 更新雪道坑位雪机信息
*
* @param trailPositionId 雪道位置ID
* @param snowMachineId 造雪机设备ID
* @return 更新结果
*/
public Boolean updateTrailPositionSnowMachineId(Integer trailPositionId, Integer snowMachineId) {
// 检查是否存在该ID的雪道位置
TrailPosition trailPosition = trailPositionMapper.selectById(trailPositionId);
if (trailPosition == null) {
throw new IllegalArgumentException("雪道位置不存在");
}
// 更新雪道位置雪机信息
trailPosition.setSnowMachineId(snowMachineId);
trailPositionMapper.updateSnowMachine(trailPositionId, snowMachineId);
return true;
}
/**
* 清空雪道位置雪机信息
*
* @param trailPositionIds 雪道位置ID列表
* @return 更新结果
*/
public Boolean clearTrailPositionSnowMachineIdBatch(List<Integer> trailPositionIds) {
// 检查是否存在该ID的雪道位置
if (trailPositionIds.isEmpty()) {
throw new IllegalArgumentException("雪道位置不存在");
}
// 清空雪道位置雪机信息
for (Integer trailPositionId : trailPositionIds) {
TrailPosition trailPosition = trailPositionMapper.selectById(trailPositionId);
if (trailPosition == null) {
throw new IllegalArgumentException("雪道位置不存在");
}
trailPosition.setSnowMachineId(null);
trailPosition.setSnowMachineStatus(0);
trailPositionMapper.clearSnowMachine(trailPositionId);
}
return true;
}
/**
* 平铺返回所有雪道位置信息
*
* @return 所有雪道位置信息列表
*/
public List<TrailPosition> getAllTrailPositionList() {
return trailPositionMapper.selectAll();
}
}

View File

@@ -10,4 +10,33 @@ CREATE TABLE sys_user (
COMMENT ON COLUMN sys_user.username IS '用户名';
COMMENT ON COLUMN sys_user.password IS '加密后的密码BCrypt';
COMMENT ON COLUMN sys_user.nickname IS '昵称';
COMMENT ON COLUMN sys_user.status IS '状态1-正常0-禁用)';
COMMENT ON COLUMN sys_user.status IS '状态1-正常0-禁用)';
CREATE TABLE snow_machine (
id BIGSERIAL PRIMARY KEY,
type SMALLINT DEFAULT 1,
name VARCHAR(255),
lora VARCHAR(255),
CONSTRAINT uk_lora UNIQUE (lora)
);
COMMENT ON COLUMN snow_machine.type IS '类型1雪机2水泵';
COMMENT ON COLUMN snow_machine.name IS '名称';
COMMENT ON COLUMN snow_machine.lora IS 'Lora 地址';
CREATE TABLE trail_position (
id BIGSERIAL PRIMARY KEY,
trail_id BIGINT NOT NULL,
position SMALLINT,
trail_name VARCHAR(255),
snow_machine_id BIGINT,
snow_machine_status SMALLINT DEFAULT 0,
);
COMMENT ON COLUMN trail_position.trail_id IS '雪道ID';
COMMENT ON COLUMN trail_position.position IS '位置';
COMMENT ON COLUMN trail_position.trail_name IS '雪道名称';
COMMENT ON COLUMN trail_position.snow_machine_id IS '雪机ID';
COMMENT ON COLUMN trail_position.snow_machine_status IS '雪机状态 0无 1正常 2停止';

View File

@@ -1,7 +0,0 @@
spring.application.name=ski-dashboard
# ??????? postgresql
spring.datasource.url=jdbc:postgresql://localhost:5432/ski_dashboard
spring.datasource.username=postgres
spring.datasource.password=tanlifan
JWT_SECRET=vf4JZhcyfdK7tJs0GZ3Qjf0dSv4BId9ITjsM2fol26gOBxM17nUySiMcV0Lo2u0Y