添加代码注释,去掉可能有安全问题代码
This commit is contained in:
parent
43e820fd3b
commit
4bafdf81da
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 仿真数据构建(Build)相关能力。
|
||||||
|
* <p>
|
||||||
|
* 该包用于将“项目拓扑/设备配置/事件/材料/关键参数”等多源输入组织成推演/计算可消费的结构化数据,
|
||||||
|
* 侧重于流程编排与数据组装,而非对外协议处理。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>输入:项目ID、场景配置、拓扑结构、设备与材料数据等。</li>
|
||||||
|
* <li>输出:推演上下文、推演请求体、结构化数据包等。</li>
|
||||||
|
* <li>约束:构建过程应保持确定性,避免引入与时间相关的隐式状态。</li>
|
||||||
|
* <li>健壮性:对缺失字段、非法引用、拓扑不一致等情况给出可定位错误。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 设计原则:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>分阶段构建:先解析与归一化,再校验与补全,最后生成输出。</li>
|
||||||
|
* <li>只读输入:避免在构建阶段修改持久化实体对象,减少副作用。</li>
|
||||||
|
* <li>可测试:关键构建步骤保持可单元测试(输入→输出可预测)。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.build;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 业务异常定义。
|
||||||
|
* <p>
|
||||||
|
* 用于表达业务处理过程中的可预期错误,区分于系统异常(NPE、网络故障、数据库不可用等)。
|
||||||
|
* 业务异常应具备明确语义,便于前端提示与测试用例覆盖。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>参数类错误:入参缺失、格式非法、越界、引用不存在等。</li>
|
||||||
|
* <li>状态类错误:对象状态不允许当前操作(例如任务状态机不允许跳转)。</li>
|
||||||
|
* <li>推演类错误:场景推演前置条件不满足、数据不一致、外部推演服务拒绝等。</li>
|
||||||
|
* <li>权限类错误:项目不可访问、资源无权限读写等(如需与安全框架集成)。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>异常消息:面向用户/测试可读,避免泄露敏感信息。</li>
|
||||||
|
* <li>异常类型:按场景区分类型,避免一个异常承担全部语义。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.common.exception;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Spring Boot 配置。
|
||||||
|
* <p>
|
||||||
|
* 该包集中放置与运行时装配相关的配置类,例如:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>MyBatis/MyBatis-Plus 配置:分页、类型处理器、拦截器等。</li>
|
||||||
|
* <li>OpenAPI/Swagger 配置:接口文档分组、鉴权头部描述等。</li>
|
||||||
|
* <li>RestTemplate/WebClient 配置:超时、拦截器、序列化策略等。</li>
|
||||||
|
* <li>WebSocket 配置:消息代理、端点、跨域策略等。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>配置类仅负责 Bean 装配,不承载业务逻辑。</li>
|
||||||
|
* <li>外部可变参数(地址、密钥、阈值等)通过配置注入,不硬编码在源码。</li>
|
||||||
|
* <li>默认值要保守,避免因配置缺失导致系统启动后产生危险行为。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.config;
|
||||||
@ -19,23 +19,58 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 算法字典管理接口。
|
||||||
|
* <p>
|
||||||
|
* 提供算法字典的基础 CRUD、按算法类型/名称查询,以及算法启用/停用等管理能力。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 字段约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>status:算法状态标识,"1" 表示启用,"0" 表示停用。</li>
|
||||||
|
* <li>modifier:最后修改人用户名,取自当前登录上下文。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/algorithms")
|
@RequestMapping("/algorithms")
|
||||||
@Tag(name = "算法接口", description = "算法字典的增删改查与搜索")
|
@Tag(name = "算法接口", description = "算法字典的增删改查与搜索")
|
||||||
public class AlgorithmController {
|
public class AlgorithmController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 算法字典服务(MyBatis-Plus Service)。
|
||||||
|
*/
|
||||||
@Autowired
|
@Autowired
|
||||||
private AlgorithmService algorithmService;
|
private AlgorithmService algorithmService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于记录 modifier 等审计字段。
|
||||||
|
*/
|
||||||
@Autowired
|
@Autowired
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据主键查询算法。
|
||||||
|
*
|
||||||
|
* @param id 算法主键
|
||||||
|
* @return 算法对象;不存在时返回 null
|
||||||
|
*/
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@Operation(summary = "根据算法ID获取算法", description = "路径参数传入算法ID,返回算法对象")
|
@Operation(summary = "根据算法ID获取算法", description = "路径参数传入算法ID,返回算法对象")
|
||||||
public Algorithm getAlgorithmById(@PathVariable String id) {
|
public Algorithm getAlgorithmById(@PathVariable String id) {
|
||||||
return algorithmService.getById(id);
|
return algorithmService.getById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据算法类型查询算法。
|
||||||
|
* <p>
|
||||||
|
* 当前实现返回满足条件的第一条记录(LIMIT 1)。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param type 算法类型(如 GPR)
|
||||||
|
* @return 算法对象;不存在时返回 null
|
||||||
|
*/
|
||||||
@GetMapping("/type/{type}")
|
@GetMapping("/type/{type}")
|
||||||
@Operation(summary = "根据算法类型获取算法", description = "路径参数传入算法类型(如GPR),返回算法对象")
|
@Operation(summary = "根据算法类型获取算法", description = "路径参数传入算法类型(如GPR),返回算法对象")
|
||||||
public Algorithm getAlgorithmByType(@PathVariable String type) {
|
public Algorithm getAlgorithmByType(@PathVariable String type) {
|
||||||
@ -49,6 +84,15 @@ public class AlgorithmController {
|
|||||||
@PreAuthorize("hasAuthority('algorithm:add')")
|
@PreAuthorize("hasAuthority('algorithm:add')")
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@Operation(summary = "新增算法", description = "请求体传入算法对象,返回是否新增成功")
|
@Operation(summary = "新增算法", description = "请求体传入算法对象,返回是否新增成功")
|
||||||
|
/**
|
||||||
|
* 新增算法。
|
||||||
|
* <p>
|
||||||
|
* 写入审计字段:modifier、createdAt、updatedAt。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param algorithm 算法对象(由请求体提供)
|
||||||
|
* @return 是否新增成功
|
||||||
|
*/
|
||||||
public boolean createAlgorithm(@RequestBody Algorithm algorithm) {
|
public boolean createAlgorithm(@RequestBody Algorithm algorithm) {
|
||||||
algorithm.setModifier(currentUsername());
|
algorithm.setModifier(currentUsername());
|
||||||
algorithm.setCreatedAt(LocalDateTime.now());
|
algorithm.setCreatedAt(LocalDateTime.now());
|
||||||
@ -60,6 +104,15 @@ public class AlgorithmController {
|
|||||||
@PreAuthorize("hasAuthority('algorithm:update')")
|
@PreAuthorize("hasAuthority('algorithm:update')")
|
||||||
@PutMapping
|
@PutMapping
|
||||||
@Operation(summary = "修改算法", description = "请求体传入算法对象(需包含主键),返回是否修改成功")
|
@Operation(summary = "修改算法", description = "请求体传入算法对象(需包含主键),返回是否修改成功")
|
||||||
|
/**
|
||||||
|
* 修改算法。
|
||||||
|
* <p>
|
||||||
|
* 仅更新 updatedAt 与 modifier,其他字段由请求体携带并覆盖更新。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param algorithm 算法对象(需包含主键)
|
||||||
|
* @return 是否修改成功
|
||||||
|
*/
|
||||||
public boolean updateAlgorithm(@RequestBody Algorithm algorithm) {
|
public boolean updateAlgorithm(@RequestBody Algorithm algorithm) {
|
||||||
algorithm.setModifier(currentUsername());
|
algorithm.setModifier(currentUsername());
|
||||||
algorithm.setUpdatedAt(LocalDateTime.now());
|
algorithm.setUpdatedAt(LocalDateTime.now());
|
||||||
@ -70,6 +123,12 @@ public class AlgorithmController {
|
|||||||
@PreAuthorize("hasAuthority('algorithm:del')")
|
@PreAuthorize("hasAuthority('algorithm:del')")
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@Operation(summary = "删除算法(单条)", description = "根据算法ID删除算法")
|
@Operation(summary = "删除算法(单条)", description = "根据算法ID删除算法")
|
||||||
|
/**
|
||||||
|
* 删除单条算法记录。
|
||||||
|
*
|
||||||
|
* @param id 算法主键
|
||||||
|
* @return 是否删除成功
|
||||||
|
*/
|
||||||
public boolean deleteAlgorithm(@PathVariable String id) {
|
public boolean deleteAlgorithm(@PathVariable String id) {
|
||||||
return algorithmService.removeById(id);
|
return algorithmService.removeById(id);
|
||||||
}
|
}
|
||||||
@ -78,6 +137,12 @@ public class AlgorithmController {
|
|||||||
@PreAuthorize("hasAuthority('algorithm:del')")
|
@PreAuthorize("hasAuthority('algorithm:del')")
|
||||||
@DeleteMapping
|
@DeleteMapping
|
||||||
@Operation(summary = "删除算法(批量)", description = "请求体传入算法ID列表,批量删除算法")
|
@Operation(summary = "删除算法(批量)", description = "请求体传入算法ID列表,批量删除算法")
|
||||||
|
/**
|
||||||
|
* 批量删除算法记录。
|
||||||
|
*
|
||||||
|
* @param ids 算法主键列表
|
||||||
|
* @return 是否删除成功
|
||||||
|
*/
|
||||||
public boolean deleteAlgorithms(@RequestBody List<String> ids) {
|
public boolean deleteAlgorithms(@RequestBody List<String> ids) {
|
||||||
return algorithmService.removeByIds(ids);
|
return algorithmService.removeByIds(ids);
|
||||||
}
|
}
|
||||||
@ -86,6 +151,12 @@ public class AlgorithmController {
|
|||||||
// @PreAuthorize("hasAuthority('algorithm:activate')")
|
// @PreAuthorize("hasAuthority('algorithm:activate')")
|
||||||
@PostMapping("/activate")
|
@PostMapping("/activate")
|
||||||
@Operation(summary = "激活算法", description = "激活当前算法类型")
|
@Operation(summary = "激活算法", description = "激活当前算法类型")
|
||||||
|
/**
|
||||||
|
* 启用算法(status=1)。
|
||||||
|
*
|
||||||
|
* @param algorithmId 算法主键
|
||||||
|
* @return 是否更新成功;算法不存在时返回 false
|
||||||
|
*/
|
||||||
public boolean activate(@RequestParam String algorithmId) {
|
public boolean activate(@RequestParam String algorithmId) {
|
||||||
Algorithm algorithm = algorithmService.getById(algorithmId);
|
Algorithm algorithm = algorithmService.getById(algorithmId);
|
||||||
if (algorithm == null) return false;
|
if (algorithm == null) return false;
|
||||||
@ -100,6 +171,12 @@ public class AlgorithmController {
|
|||||||
// @PreAuthorize("hasAuthority('algorithm:unactivate')")
|
// @PreAuthorize("hasAuthority('algorithm:unactivate')")
|
||||||
@PostMapping("/unactivate")
|
@PostMapping("/unactivate")
|
||||||
@Operation(summary = "关闭算法", description = "关闭当前算法类型")
|
@Operation(summary = "关闭算法", description = "关闭当前算法类型")
|
||||||
|
/**
|
||||||
|
* 停用算法(status=0)。
|
||||||
|
*
|
||||||
|
* @param algorithmId 算法主键
|
||||||
|
* @return 是否更新成功;算法不存在时返回 false
|
||||||
|
*/
|
||||||
public boolean unactivate(@RequestParam String algorithmId) {
|
public boolean unactivate(@RequestParam String algorithmId) {
|
||||||
Algorithm algorithm = algorithmService.getById(algorithmId);
|
Algorithm algorithm = algorithmService.getById(algorithmId);
|
||||||
if (algorithm == null) return false;
|
if (algorithm == null) return false;
|
||||||
@ -110,10 +187,9 @@ public class AlgorithmController {
|
|||||||
return algorithmService.updateById(algorithm);
|
return algorithmService.updateById(algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取激活的算法类型
|
|
||||||
/**
|
/**
|
||||||
* 获取所有激活的算法类型
|
* 获取所有启用状态的算法列表(status=1)。
|
||||||
* 输出参数:激活的算法类型列表
|
*
|
||||||
* @return 激活的算法类型列表
|
* @return 激活的算法类型列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/getActiveAlgorithms")
|
@GetMapping("/getActiveAlgorithms")
|
||||||
@ -125,12 +201,11 @@ public class AlgorithmController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据算法名称搜索并分页返回
|
* 根据算法名称搜索并分页返回。
|
||||||
* 输入参数:查询参数 name(算法名称关键词,可为空),pageNum(页码,默认1),pageSize(每页条数,默认10)
|
*
|
||||||
* 输出参数:算法分页列表
|
|
||||||
* @param name 算法名称关键词(可为空)
|
* @param name 算法名称关键词(可为空)
|
||||||
* @param pageNum 页码
|
* @param pageNum 页码(默认 1)
|
||||||
* @param pageSize 每页条数
|
* @param pageSize 每页条数(默认 20)
|
||||||
* @return 算法分页列表
|
* @return 算法分页列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/search")
|
@GetMapping("/search")
|
||||||
@ -147,6 +222,14 @@ public class AlgorithmController {
|
|||||||
return algorithmService.page(page, qw);
|
return algorithmService.page(page, qw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户名。
|
||||||
|
* <p>
|
||||||
|
* 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return 当前用户名或 "anonymous"
|
||||||
|
*/
|
||||||
private String currentUsername() {
|
private String currentUsername() {
|
||||||
try {
|
try {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|||||||
@ -3,9 +3,7 @@ package com.yfd.business.css.controller;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.yfd.business.css.domain.AlgorithmModel;
|
import com.yfd.business.css.domain.AlgorithmModel;
|
||||||
import com.yfd.business.css.domain.Algorithm;
|
|
||||||
import com.yfd.business.css.service.AlgorithmModelService;
|
import com.yfd.business.css.service.AlgorithmModelService;
|
||||||
import com.yfd.business.css.service.AlgorithmService;
|
|
||||||
import com.yfd.platform.system.service.IUserService;
|
import com.yfd.platform.system.service.IUserService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@ -18,35 +16,49 @@ import com.yfd.platform.annotation.Log;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 算法模型版本管理接口。
|
||||||
|
* <p>
|
||||||
|
* 提供算法模型版本的查询、分页筛选、当前版本获取、版本激活/切换以及删除等管理能力。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 关键约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>模型分组维度:algorithmType + deviceType + materialType。</li>
|
||||||
|
* <li>当前版本标识:isCurrent=1 表示当前激活版本。</li>
|
||||||
|
* <li>materialType 兼容:Mixed 与 MIX 视为同一类。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/algorithm-models")
|
@RequestMapping("/algorithm-models")
|
||||||
@Tag(name = "算法模型接口", description = "算法模型版本的增删改查、查询当前版本与在线训练")
|
@Tag(name = "算法模型接口", description = "算法模型版本的增删改查、查询当前版本与在线训练")
|
||||||
public class AlgorithmModelController {
|
public class AlgorithmModelController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 算法模型服务(MyBatis-Plus Service)。
|
||||||
|
*/
|
||||||
@Autowired
|
@Autowired
|
||||||
private AlgorithmModelService algorithmModelService;
|
private AlgorithmModelService algorithmModelService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于记录 modifier 等审计字段。
|
||||||
|
*/
|
||||||
@Autowired
|
@Autowired
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
@Autowired
|
|
||||||
private AlgorithmService algorithmService;
|
|
||||||
@Autowired
|
|
||||||
private ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据主键查询模型版本。
|
||||||
|
*
|
||||||
|
* @param id 模型主键
|
||||||
|
* @return 模型版本对象;不存在时返回 null
|
||||||
|
*/
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@Operation(summary = "根据模型ID获取模型版本", description = "路径参数传入模型ID,返回模型版本对象")
|
@Operation(summary = "根据模型ID获取模型版本", description = "路径参数传入模型ID,返回模型版本对象")
|
||||||
public AlgorithmModel getById(@PathVariable String id) {
|
public AlgorithmModel getById(@PathVariable String id) {
|
||||||
@ -57,6 +69,15 @@ public class AlgorithmModelController {
|
|||||||
// @PreAuthorize("hasAuthority('algorithmModel:add')")
|
// @PreAuthorize("hasAuthority('algorithmModel:add')")
|
||||||
// @PostMapping
|
// @PostMapping
|
||||||
@Operation(summary = "新增模型版本", description = "请求体传入模型版本对象,返回是否新增成功")
|
@Operation(summary = "新增模型版本", description = "请求体传入模型版本对象,返回是否新增成功")
|
||||||
|
/**
|
||||||
|
* 新增模型版本。
|
||||||
|
* <p>
|
||||||
|
* 当前方法仅负责落库与审计字段写入;接口映射若被关闭(@PostMapping 注释),则不会对外提供入口。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param model 模型版本对象
|
||||||
|
* @return 是否新增成功
|
||||||
|
*/
|
||||||
public boolean create(@RequestBody AlgorithmModel model) {
|
public boolean create(@RequestBody AlgorithmModel model) {
|
||||||
model.setModifier(currentUsername());
|
model.setModifier(currentUsername());
|
||||||
model.setCreatedAt(LocalDateTime.now());
|
model.setCreatedAt(LocalDateTime.now());
|
||||||
@ -68,6 +89,15 @@ public class AlgorithmModelController {
|
|||||||
// @PreAuthorize("hasAuthority('algorithmModel:update')")
|
// @PreAuthorize("hasAuthority('algorithmModel:update')")
|
||||||
// @PutMapping
|
// @PutMapping
|
||||||
@Operation(summary = "修改模型版本", description = "请求体传入模型版本对象(需包含主键),返回是否修改成功")
|
@Operation(summary = "修改模型版本", description = "请求体传入模型版本对象(需包含主键),返回是否修改成功")
|
||||||
|
/**
|
||||||
|
* 修改模型版本。
|
||||||
|
* <p>
|
||||||
|
* 当前方法仅负责落库与审计字段写入;接口映射若被关闭(@PutMapping 注释),则不会对外提供入口。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param model 模型版本对象(需包含主键)
|
||||||
|
* @return 是否修改成功
|
||||||
|
*/
|
||||||
public boolean update(@RequestBody AlgorithmModel model) {
|
public boolean update(@RequestBody AlgorithmModel model) {
|
||||||
model.setModifier(currentUsername());
|
model.setModifier(currentUsername());
|
||||||
model.setUpdatedAt(LocalDateTime.now());
|
model.setUpdatedAt(LocalDateTime.now());
|
||||||
@ -78,6 +108,12 @@ public class AlgorithmModelController {
|
|||||||
@PreAuthorize("hasAuthority('algorithmModel:del')")
|
@PreAuthorize("hasAuthority('algorithmModel:del')")
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@Operation(summary = "删除模型版本(单条)", description = "根据模型ID删除模型版本")
|
@Operation(summary = "删除模型版本(单条)", description = "根据模型ID删除模型版本")
|
||||||
|
/**
|
||||||
|
* 删除单条模型版本记录。
|
||||||
|
*
|
||||||
|
* @param id 模型主键
|
||||||
|
* @return 是否删除成功
|
||||||
|
*/
|
||||||
public boolean delete(@PathVariable String id) {
|
public boolean delete(@PathVariable String id) {
|
||||||
return algorithmModelService.removeById(id);
|
return algorithmModelService.removeById(id);
|
||||||
}
|
}
|
||||||
@ -86,11 +122,28 @@ public class AlgorithmModelController {
|
|||||||
@PreAuthorize("hasAuthority('algorithmModel:del')")
|
@PreAuthorize("hasAuthority('algorithmModel:del')")
|
||||||
@DeleteMapping
|
@DeleteMapping
|
||||||
@Operation(summary = "删除模型版本(批量)", description = "请求体传入模型ID列表,批量删除模型版本")
|
@Operation(summary = "删除模型版本(批量)", description = "请求体传入模型ID列表,批量删除模型版本")
|
||||||
|
/**
|
||||||
|
* 批量删除模型版本记录。
|
||||||
|
*
|
||||||
|
* @param ids 模型主键列表
|
||||||
|
* @return 是否删除成功
|
||||||
|
*/
|
||||||
public boolean deleteBatch(@RequestBody List<String> ids) {
|
public boolean deleteBatch(@RequestBody List<String> ids) {
|
||||||
return algorithmModelService.deleteBatchWithCheck(ids);
|
return algorithmModelService.deleteBatchWithCheck(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
//返回:该算法+设备类型+材料类型的版本列表
|
/**
|
||||||
|
* 查询模型版本列表(分页)。
|
||||||
|
*
|
||||||
|
* @param algorithmType 算法类型
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @param materialType 材料类型(支持 Mixed/MIX 兼容)
|
||||||
|
* @param versionTag 版本号
|
||||||
|
* @param isCurrent 是否为当前版本(1/0)
|
||||||
|
* @param pageNum 页码(默认 1)
|
||||||
|
* @param pageSize 每页条数(默认 20)
|
||||||
|
* @return 模型版本分页结果
|
||||||
|
*/
|
||||||
@GetMapping("/search")
|
@GetMapping("/search")
|
||||||
@Operation(summary = "查询模型版本列表", description = "按算法类型、设备类型与材料类型过滤并分页返回模型版本")
|
@Operation(summary = "查询模型版本列表", description = "按算法类型、设备类型与材料类型过滤并分页返回模型版本")
|
||||||
public Page<AlgorithmModel> search(@RequestParam(required = false) String algorithmType,
|
public Page<AlgorithmModel> search(@RequestParam(required = false) String algorithmType,
|
||||||
@ -118,6 +171,17 @@ public class AlgorithmModelController {
|
|||||||
return algorithmModelService.page(page, qw);
|
return algorithmModelService.page(page, qw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取模型版本下拉选项。
|
||||||
|
* <p>
|
||||||
|
* 返回包含 options(value=模型ID,label=版本号)与 currentModelId(当前激活模型ID)的结构,便于前端直接使用。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param algorithmType 算法类型
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @param materialType 材料类型(可选)
|
||||||
|
* @return 选项数据
|
||||||
|
*/
|
||||||
@GetMapping("/options")
|
@GetMapping("/options")
|
||||||
@Operation(summary = "获取模型版本选项列表", description = "用于界面下拉选择:按算法类型与设备类型返回模型版本列表(value=模型ID,label=版本号),并返回当前激活模型ID")
|
@Operation(summary = "获取模型版本选项列表", description = "用于界面下拉选择:按算法类型与设备类型返回模型版本列表(value=模型ID,label=版本号),并返回当前激活模型ID")
|
||||||
public Map<String, Object> options(@RequestParam String algorithmType,
|
public Map<String, Object> options(@RequestParam String algorithmType,
|
||||||
@ -158,7 +222,14 @@ public class AlgorithmModelController {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
//返回:该算法+设备类型+材料类型的当前激活版本
|
/**
|
||||||
|
* 获取当前激活版本(is_current=1)。
|
||||||
|
*
|
||||||
|
* @param algorithmType 算法类型
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @param materialType 材料类型(可选)
|
||||||
|
* @return 当前激活模型版本;不存在时返回 null
|
||||||
|
*/
|
||||||
@GetMapping("/current")
|
@GetMapping("/current")
|
||||||
@Operation(summary = "获取当前激活版本", description = "根据算法类型、设备类型与材料类型,返回 is_current=1 的模型版本")
|
@Operation(summary = "获取当前激活版本", description = "根据算法类型、设备类型与材料类型,返回 is_current=1 的模型版本")
|
||||||
public AlgorithmModel getCurrent(@RequestParam String algorithmType,
|
public AlgorithmModel getCurrent(@RequestParam String algorithmType,
|
||||||
@ -184,6 +255,15 @@ public class AlgorithmModelController {
|
|||||||
@PreAuthorize("hasAuthority('algorithmModel:activate')")
|
@PreAuthorize("hasAuthority('algorithmModel:activate')")
|
||||||
@PostMapping("/activate")
|
@PostMapping("/activate")
|
||||||
@Operation(summary = "激活模型版本", description = "将目标模型版本设为当前,并将同组(算法+设备+材料)其他版本设为非当前")
|
@Operation(summary = "激活模型版本", description = "将目标模型版本设为当前,并将同组(算法+设备+材料)其他版本设为非当前")
|
||||||
|
/**
|
||||||
|
* 激活指定模型版本。
|
||||||
|
* <p>
|
||||||
|
* 激活时会将同组(algorithmType+deviceType+materialType)其他版本置为非当前(isCurrent=0),再将指定版本置为当前。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param algorithmModelId 模型主键
|
||||||
|
* @return 是否更新成功;模型不存在时返回 false
|
||||||
|
*/
|
||||||
public boolean activate(@RequestParam String algorithmModelId) {
|
public boolean activate(@RequestParam String algorithmModelId) {
|
||||||
AlgorithmModel model = algorithmModelService.getById(algorithmModelId);
|
AlgorithmModel model = algorithmModelService.getById(algorithmModelId);
|
||||||
if (model == null) return false;
|
if (model == null) return false;
|
||||||
@ -212,148 +292,12 @@ public class AlgorithmModelController {
|
|||||||
return algorithmModelService.updateById(model);
|
return algorithmModelService.updateById(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Log(value = "在线训练(Excel)", module = "算法模型管理")
|
/**
|
||||||
// 在线训练(Excel 数据集)
|
* 归一化材料类型取值。
|
||||||
// @PostMapping("/train/excel")
|
*
|
||||||
@Operation(summary = "在线训练(Excel)", description = "传入算法类型、设备类型与Excel路径,训练完成新增模型版本记录,可选激活")
|
* @param raw 原始材料类型
|
||||||
public Map<String, Object> trainExcel(@RequestBody Map<String, Object> body) {
|
* @return 归一化后的材料类型;空白返回 null
|
||||||
String algorithmType = str(body.get("algorithm_type"));
|
*/
|
||||||
String deviceType = str(body.get("device_type"));
|
|
||||||
String materialType = normalizeMaterialType(str(body.getOrDefault("material_type", "")));
|
|
||||||
String datasetPath = str(body.get("dataset_path"));
|
|
||||||
String modelDir = str(body.getOrDefault("model_dir", ""));
|
|
||||||
boolean activate = bool(body.getOrDefault("activate", false));
|
|
||||||
String featureMapSnapshot = toJson(body.get("feature_map_snapshot"));
|
|
||||||
if (isBlank(algorithmType) || isBlank(deviceType) || isBlank(datasetPath)) {
|
|
||||||
return Map.of("code", 1, "msg", "algorithm_type/device_type/dataset_path 必填");
|
|
||||||
}
|
|
||||||
Algorithm algo = getAlgorithmByType(algorithmType);
|
|
||||||
if (algo == null || isBlank(algo.getTrainBaseUrl())) {
|
|
||||||
return Map.of("code", 1, "msg", "算法或训练URL未配置");
|
|
||||||
}
|
|
||||||
String baseUrl = algo.getTrainBaseUrl();
|
|
||||||
if (baseUrl != null && baseUrl.endsWith("/")) {
|
|
||||||
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
|
||||||
}
|
|
||||||
String url = baseUrl + "/v1/train/" + deviceType;
|
|
||||||
Map<String, Object> payload = new HashMap<>();
|
|
||||||
payload.put("dataset_path", datasetPath);
|
|
||||||
if (!isBlank(modelDir)) payload.put("model_dir", modelDir);
|
|
||||||
Map<String, Object> resp = httpPostJson(url, payload);
|
|
||||||
if (resp == null) return Map.of("code", 1, "msg", "训练接口无响应");
|
|
||||||
int code = 0;
|
|
||||||
Object codeObj = resp.get("code");
|
|
||||||
if (codeObj instanceof Number) {
|
|
||||||
code = ((Number) codeObj).intValue();
|
|
||||||
} else if (codeObj instanceof String) {
|
|
||||||
try {
|
|
||||||
code = Integer.parseInt((String) codeObj);
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
code = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (code != 0) return Map.of("code", 1, "msg", "训练失败: " + str(resp.get("msg")));
|
|
||||||
Map<String, Object> data = castMap(resp.get("data"));
|
|
||||||
String modelPath = str(data.get("model_path"));
|
|
||||||
String metrics = toJson(data.get("metrics"));
|
|
||||||
AlgorithmModel model = new AlgorithmModel();
|
|
||||||
model.setAlgorithmModelId(UUID.randomUUID().toString());
|
|
||||||
model.setAlgorithmType(algorithmType);
|
|
||||||
model.setDeviceType(deviceType);
|
|
||||||
model.setMaterialType(materialType);
|
|
||||||
model.setVersionTag(genVersionTag());
|
|
||||||
model.setModelPath(modelPath);
|
|
||||||
model.setFeatureMapSnapshot(isBlank(featureMapSnapshot) ? "{}" : featureMapSnapshot);
|
|
||||||
model.setMetrics(metrics);
|
|
||||||
model.setTrainedAt(LocalDateTime.now());
|
|
||||||
model.setIsCurrent(activate ? 1 : 0);
|
|
||||||
model.setCreatedAt(LocalDateTime.now());
|
|
||||||
model.setUpdatedAt(LocalDateTime.now());
|
|
||||||
model.setModifier(currentUsername());
|
|
||||||
if (activate) {
|
|
||||||
QueryWrapper<AlgorithmModel> qw = new QueryWrapper<>();
|
|
||||||
qw.eq("algorithm_type", algorithmType).eq("device_type", deviceType);
|
|
||||||
if (!isBlank(materialType)) {
|
|
||||||
if ("Mixed".equals(materialType)) {
|
|
||||||
qw.in("material_type", List.of("Mixed", "MIX"));
|
|
||||||
} else {
|
|
||||||
qw.eq("material_type", materialType);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qw.and(wrapper -> wrapper.isNull("material_type").or().eq("material_type", ""));
|
|
||||||
}
|
|
||||||
AlgorithmModel upd = new AlgorithmModel();
|
|
||||||
upd.setIsCurrent(0);
|
|
||||||
algorithmModelService.update(upd, qw);
|
|
||||||
}
|
|
||||||
algorithmModelService.save(model);
|
|
||||||
return Map.of("code", 0, "msg", "训练成功", "data", model);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Log(value = "在线训练(样本)", module = "算法模型管理")
|
|
||||||
// 在线训练(样本集合)
|
|
||||||
// @PostMapping("/train/samples")
|
|
||||||
@Operation(summary = "在线训练(样本集合)", description = "传入算法类型、设备类型与样本集,训练完成新增模型版本记录,可选激活")
|
|
||||||
public Map<String, Object> trainSamples(@RequestBody Map<String, Object> body) {
|
|
||||||
String algorithmType = str(body.get("algorithm_type"));
|
|
||||||
String deviceType = str(body.get("device_type"));
|
|
||||||
String materialType = normalizeMaterialType(str(body.getOrDefault("material_type", "")));
|
|
||||||
Object samples = body.get("samples"); // 期望为 List<Map>,由前端提供
|
|
||||||
String modelDir = str(body.getOrDefault("model_dir", ""));
|
|
||||||
boolean activate = bool(body.getOrDefault("activate", false));
|
|
||||||
String featureMapSnapshot = toJson(body.get("feature_map_snapshot"));
|
|
||||||
if (isBlank(algorithmType) || isBlank(deviceType) || samples == null) {
|
|
||||||
return Map.of("code", 1, "msg", "algorithm_type/device_type/samples 必填");
|
|
||||||
}
|
|
||||||
Algorithm algo = getAlgorithmByType(algorithmType);
|
|
||||||
if (algo == null || isBlank(algo.getTrainBaseUrl())) {
|
|
||||||
return Map.of("code", 1, "msg", "算法或训练URL未配置");
|
|
||||||
}
|
|
||||||
String url = "";//trimSlash(algo.getTrainBaseUrl()) + "/v1/train/" + deviceType + "/from-samples";
|
|
||||||
Map<String, Object> payload = new HashMap<>();
|
|
||||||
payload.put("samples", samples);
|
|
||||||
if (!isBlank(modelDir)) payload.put("model_dir", modelDir);
|
|
||||||
Map<String, Object> resp = httpPostJson(url, payload);
|
|
||||||
if (resp == null) return Map.of("code", 1, "msg", "训练接口无响应");
|
|
||||||
int code = 0;//intVal(resp.get("code"));
|
|
||||||
if (code != 0) return Map.of("code", 1, "msg", "训练失败: " + str(resp.get("msg")));
|
|
||||||
Map<String, Object> data = castMap(resp.get("data"));
|
|
||||||
String modelPath = str(data.get("model_path"));
|
|
||||||
String metrics = toJson(data.get("metrics"));
|
|
||||||
AlgorithmModel model = new AlgorithmModel();
|
|
||||||
model.setAlgorithmModelId(UUID.randomUUID().toString());
|
|
||||||
model.setAlgorithmType(algorithmType);
|
|
||||||
model.setDeviceType(deviceType);
|
|
||||||
model.setMaterialType(materialType);
|
|
||||||
model.setVersionTag(genVersionTag());
|
|
||||||
model.setModelPath(modelPath);
|
|
||||||
model.setFeatureMapSnapshot(isBlank(featureMapSnapshot) ? "{}" : featureMapSnapshot);
|
|
||||||
model.setMetrics(metrics);
|
|
||||||
model.setTrainedAt(LocalDateTime.now());
|
|
||||||
model.setIsCurrent(activate ? 1 : 0);
|
|
||||||
model.setCreatedAt(LocalDateTime.now());
|
|
||||||
model.setUpdatedAt(LocalDateTime.now());
|
|
||||||
model.setModifier(currentUsername());
|
|
||||||
if (activate) {
|
|
||||||
QueryWrapper<AlgorithmModel> qw = new QueryWrapper<>();
|
|
||||||
qw.eq("algorithm_type", algorithmType).eq("device_type", deviceType);
|
|
||||||
if (!isBlank(materialType)) {
|
|
||||||
if ("Mixed".equals(materialType)) {
|
|
||||||
qw.in("material_type", List.of("Mixed", "MIX"));
|
|
||||||
} else {
|
|
||||||
qw.eq("material_type", materialType);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qw.and(wrapper -> wrapper.isNull("material_type").or().eq("material_type", ""));
|
|
||||||
}
|
|
||||||
AlgorithmModel upd = new AlgorithmModel();
|
|
||||||
upd.setIsCurrent(0);
|
|
||||||
algorithmModelService.update(upd, qw);
|
|
||||||
}
|
|
||||||
algorithmModelService.save(model);
|
|
||||||
return Map.of("code", 0, "msg", "训练成功", "data", model);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeMaterialType(String raw) {
|
private String normalizeMaterialType(String raw) {
|
||||||
if (raw == null) return null;
|
if (raw == null) return null;
|
||||||
String s = raw.trim();
|
String s = raw.trim();
|
||||||
@ -365,6 +309,14 @@ public class AlgorithmModelController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户名。
|
||||||
|
* <p>
|
||||||
|
* 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return 当前用户名或 "anonymous"
|
||||||
|
*/
|
||||||
private String currentUsername() {
|
private String currentUsername() {
|
||||||
try {
|
try {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
@ -385,55 +337,4 @@ public class AlgorithmModelController {
|
|||||||
return "anonymous";
|
return "anonymous";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Algorithm getAlgorithmByType(String algorithmType) {
|
|
||||||
QueryWrapper<Algorithm> qw = new QueryWrapper<>();
|
|
||||||
qw.eq("algorithm_type", algorithmType);
|
|
||||||
return algorithmService.getOne(qw);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> httpPostJson(String url, Object payload) {
|
|
||||||
try {
|
|
||||||
String json = objectMapper.writeValueAsString(payload);
|
|
||||||
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
|
|
||||||
HttpRequest req = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(url))
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.timeout(Duration.ofSeconds(30))
|
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8))
|
|
||||||
.build();
|
|
||||||
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
|
||||||
if (res.statusCode() >= 200 && res.statusCode() < 300) {
|
|
||||||
return objectMapper.readValue(res.body(), new TypeReference<Map<String, Object>>() {});
|
|
||||||
}
|
|
||||||
return Map.of("code", 1, "msg", "HTTP " + res.statusCode());
|
|
||||||
} catch (Exception e) {
|
|
||||||
return Map.of("code", 1, "msg", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String genVersionTag() {
|
|
||||||
return "v" + LocalDateTime.now().toString().replace(":", "-");
|
|
||||||
}
|
|
||||||
private String str(Object v) { return v == null ? "" : String.valueOf(v); }
|
|
||||||
private boolean isBlank(String s) { return s == null || s.trim().isEmpty(); }
|
|
||||||
private boolean bool(Object v) {
|
|
||||||
if (v instanceof Boolean) return (Boolean) v;
|
|
||||||
String s = str(v).toLowerCase();
|
|
||||||
return "true".equals(s) || "1".equals(s) || "yes".equals(s);
|
|
||||||
}
|
|
||||||
private String toJson(Object v) {
|
|
||||||
try {
|
|
||||||
if (v == null) return null;
|
|
||||||
if (v instanceof String) return (String) v;
|
|
||||||
return objectMapper.writeValueAsString(v);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Map<String, Object> castMap(Object v) {
|
|
||||||
if (v instanceof Map) return (Map<String, Object>) v;
|
|
||||||
return new HashMap<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,12 +24,32 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 临界数据管理接口。
|
||||||
|
* <p>
|
||||||
|
* 提供临界数据的新增、修改、删除、按设备类型查询,以及导入/导出等管理能力。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 字段约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>extraFeatures:JSON 字段;前端传空字符串时需归一化为 null,避免 JSON 列写入失败。</li>
|
||||||
|
* <li>modifier:最后修改人用户名,取自当前登录上下文。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/critical-data")
|
@RequestMapping("/critical-data")
|
||||||
public class CriticalDataController {
|
public class CriticalDataController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 临界数据服务(MyBatis-Plus Service)。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private CriticalDataService criticalDataService;
|
private CriticalDataService criticalDataService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于记录 modifier 等审计字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
|
|
||||||
@ -51,6 +71,14 @@ public class CriticalDataController {
|
|||||||
return criticalDataService.save(data);
|
return criticalDataService.save(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户名。
|
||||||
|
* <p>
|
||||||
|
* 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return 当前用户名或 "anonymous"
|
||||||
|
*/
|
||||||
private String currentUsername() {
|
private String currentUsername() {
|
||||||
try {
|
try {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
@ -185,6 +213,12 @@ public class CriticalDataController {
|
|||||||
.body(bytes);
|
.body(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出临界数据模板(V2)。
|
||||||
|
*
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @return 模板文件字节流
|
||||||
|
*/
|
||||||
@PreAuthorize("hasAuthority('criticalData:import')")
|
@PreAuthorize("hasAuthority('criticalData:import')")
|
||||||
@GetMapping("/v2/template")
|
@GetMapping("/v2/template")
|
||||||
public ResponseEntity<byte[]> templateCriticalDataV2(@RequestParam String deviceType) {
|
public ResponseEntity<byte[]> templateCriticalDataV2(@RequestParam String deviceType) {
|
||||||
|
|||||||
@ -12,19 +12,40 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备元数据接口。
|
||||||
|
* <p>
|
||||||
|
* 提供设备尺寸 schema(size-schema)等元数据,供前端动态渲染表单、导入导出与校验使用。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/devices/v2")
|
@RequestMapping("/devices/v2")
|
||||||
@Tag(name = "设备元数据接口", description = "提供设备尺寸 size-schema 元数据,供前端动态渲染与校验使用")
|
@Tag(name = "设备元数据接口", description = "提供设备尺寸 size-schema 元数据,供前端动态渲染与校验使用")
|
||||||
public class DeviceMetaController {
|
public class DeviceMetaController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备尺寸 schema 注册表。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private DeviceSizeSchemaRegistry registry;
|
private DeviceSizeSchemaRegistry registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定设备类型的尺寸 schema。
|
||||||
|
*
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @return 尺寸字段元数据
|
||||||
|
*/
|
||||||
@GetMapping("/size-schema")
|
@GetMapping("/size-schema")
|
||||||
@Operation(summary = "获取指定设备类型的尺寸 schema", description = "返回 deviceType 对应的字段元数据(key/label/unit/required/order/min/max),用于前端动态渲染与与导入导出/推理口径对齐")
|
@Operation(summary = "获取指定设备类型的尺寸 schema", description = "返回 deviceType 对应的字段元数据(key/label/unit/required/order/min/max),用于前端动态渲染与与导入导出/推理口径对齐")
|
||||||
public DeviceSizeSchema schema(@RequestParam String deviceType) {
|
public DeviceSizeSchema schema(@RequestParam String deviceType) {
|
||||||
return registry.getSchema(deviceType);
|
return registry.getSchema(deviceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全部设备类型的尺寸 schema。
|
||||||
|
*
|
||||||
|
* @return deviceType -> schema 的映射
|
||||||
|
*/
|
||||||
@GetMapping("/size-schema/all")
|
@GetMapping("/size-schema/all")
|
||||||
@Operation(summary = "获取全部设备类型的尺寸 schema", description = "返回所有 deviceType 的 schema Map,适合前端一次性缓存")
|
@Operation(summary = "获取全部设备类型的尺寸 schema", description = "返回所有 deviceType 的 schema Map,适合前端一次性缓存")
|
||||||
public Map<String, DeviceSizeSchema> all() {
|
public Map<String, DeviceSizeSchema> all() {
|
||||||
|
|||||||
@ -33,18 +33,50 @@ import org.apache.poi.ss.usermodel.*;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景事件管理接口。
|
||||||
|
* <p>
|
||||||
|
* 提供事件的新增、修改、批量保存、查询与导入等能力,并通过 Scenario -> Project 的关系校验项目读写权限。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 事件与权限:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>事件属于情景(scenarioId),通过情景关联项目(projectId)进行权限控制。</li>
|
||||||
|
* <li>写操作需要项目写权限;读操作需要项目读权限。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/events")
|
@RequestMapping("/events")
|
||||||
public class EventController {
|
public class EventController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件服务(MyBatis-Plus Service)。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private EventService eventService;
|
private EventService eventService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 解析与转换。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于记录 modifier 等审计字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景服务,用于根据 scenarioId 获取 projectId 并执行权限校验。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ScenarioService scenarioService;
|
private ScenarioService scenarioService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目权限辅助类,用于对 projectId 的读写操作进行鉴权校验。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ProjectAccessHelper projectAccessHelper;
|
private ProjectAccessHelper projectAccessHelper;
|
||||||
|
|
||||||
@ -436,6 +468,9 @@ public class EventController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private double getCellValueAsDouble(Cell cell) {
|
private double getCellValueAsDouble(Cell cell) {
|
||||||
|
if (cell == null) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
if (cell.getCellType() == CellType.NUMERIC) {
|
if (cell.getCellType() == CellType.NUMERIC) {
|
||||||
return cell.getNumericCellValue();
|
return cell.getNumericCellValue();
|
||||||
} else if (cell.getCellType() == CellType.STRING) {
|
} else if (cell.getCellType() == CellType.STRING) {
|
||||||
@ -482,6 +517,14 @@ public class EventController {
|
|||||||
projectAccessHelper.assertCanWriteProject(sc.getProjectId());
|
projectAccessHelper.assertCanWriteProject(sc.getProjectId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户名。
|
||||||
|
* <p>
|
||||||
|
* 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return 当前用户名或 "anonymous"
|
||||||
|
*/
|
||||||
private String currentUsername() {
|
private String currentUsername() {
|
||||||
try {
|
try {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|||||||
@ -4,9 +4,20 @@ 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.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 健康检查接口。
|
||||||
|
* <p>
|
||||||
|
* 用于探活与连通性测试,不依赖业务数据与权限。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
public class HealthController {
|
public class HealthController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ping 接口,返回固定字符串 "ok"。
|
||||||
|
*
|
||||||
|
* @return ok
|
||||||
|
*/
|
||||||
@GetMapping("/ping")
|
@GetMapping("/ping")
|
||||||
public ResponseEntity<String> ping() {
|
public ResponseEntity<String> ping() {
|
||||||
return ResponseEntity.ok("ok");
|
return ResponseEntity.ok("ok");
|
||||||
|
|||||||
@ -23,14 +23,38 @@ import jakarta.annotation.Resource;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物料管理接口(模板库与项目物料)。
|
||||||
|
* <p>
|
||||||
|
* 提供物料的基础增删改查,以及 Excel/CSV 导入导出与模板下载能力。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 权限与数据域:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>模板库数据:project_id = -1。</li>
|
||||||
|
* <li>项目数据:project_id != -1 时,通过 ProjectAccessHelper 校验读写权限。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/materials")
|
@RequestMapping("/materials")
|
||||||
public class MaterialController {
|
public class MaterialController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物料服务(MyBatis-Plus Service)。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private MaterialService materialService;
|
private MaterialService materialService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于记录 modifier 等审计字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目权限辅助类,用于对 projectId 的读写操作进行鉴权校验。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ProjectAccessHelper projectAccessHelper;
|
private ProjectAccessHelper projectAccessHelper;
|
||||||
|
|
||||||
@ -162,6 +186,11 @@ public class MaterialController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/v2/template")
|
@GetMapping("/v2/template")
|
||||||
|
/**
|
||||||
|
* 下载物料导入模板(V2)。
|
||||||
|
*
|
||||||
|
* @return xlsx 模板字节流
|
||||||
|
*/
|
||||||
public ResponseEntity<byte[]> templateMaterialsV2() {
|
public ResponseEntity<byte[]> templateMaterialsV2() {
|
||||||
byte[] bytes = materialService.templateMaterialsV2();
|
byte[] bytes = materialService.templateMaterialsV2();
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
@ -171,6 +200,15 @@ public class MaterialController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
/**
|
||||||
|
* 根据主键查询物料。
|
||||||
|
* <p>
|
||||||
|
* projectId != -1 时会校验项目读权限。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param id 物料主键
|
||||||
|
* @return 物料对象;不存在时返回 null
|
||||||
|
*/
|
||||||
public Material getById(@PathVariable String id) {
|
public Material getById(@PathVariable String id) {
|
||||||
Material m = materialService.getById(id);
|
Material m = materialService.getById(id);
|
||||||
if (m != null && m.getProjectId() != null && !m.getProjectId().isBlank() && !"-1".equals(m.getProjectId())) {
|
if (m != null && m.getProjectId() != null && !m.getProjectId().isBlank() && !"-1".equals(m.getProjectId())) {
|
||||||
@ -211,6 +249,14 @@ public class MaterialController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/by-project")
|
@GetMapping("/by-project")
|
||||||
|
/**
|
||||||
|
* 按项目分页查询物料列表。
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param pageNum 页码(默认 1)
|
||||||
|
* @param pageSize 每页条数(默认 20)
|
||||||
|
* @return 物料分页列表
|
||||||
|
*/
|
||||||
public Page<Material> pageByProject(@RequestParam String projectId,
|
public Page<Material> pageByProject(@RequestParam String projectId,
|
||||||
@RequestParam(defaultValue = "1") long pageNum,
|
@RequestParam(defaultValue = "1") long pageNum,
|
||||||
@RequestParam(defaultValue = "20") long pageSize) {
|
@RequestParam(defaultValue = "20") long pageSize) {
|
||||||
@ -232,6 +278,14 @@ public class MaterialController {
|
|||||||
return materialService.page(page, qw);
|
return materialService.page(page, qw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户名。
|
||||||
|
* <p>
|
||||||
|
* 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return 当前用户名或 "anonymous"
|
||||||
|
*/
|
||||||
private String currentUsername() {
|
private String currentUsername() {
|
||||||
try {
|
try {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|||||||
@ -16,21 +16,48 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型训练任务接口。
|
||||||
|
* <p>
|
||||||
|
* 提供训练任务的容量预检、任务提交、任务列表与详情查询、训练状态回调接入,以及模型发布与任务删除等能力。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 回调链路:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Python 侧回调 {@code /train/internal/callback},后端更新任务状态后通过 WebSocket 广播到 {@code /topic/train-status/all}。</li>
|
||||||
|
* <li>前端列表页订阅广播主题以更新任务状态;详情页可通过轮询 {@code /train/status/{taskId}} 获取最新状态。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/train")
|
@RequestMapping("/train")
|
||||||
public class ModelTrainController {
|
public class ModelTrainController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型训练任务服务。
|
||||||
|
*/
|
||||||
@Autowired
|
@Autowired
|
||||||
private ModelTrainService modelTrainService;
|
private ModelTrainService modelTrainService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket 推送服务,用于将训练状态广播给前端。
|
||||||
|
*/
|
||||||
@Autowired
|
@Autowired
|
||||||
private TrainWebSocketService trainWebSocketService;
|
private TrainWebSocketService trainWebSocketService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 序列化与请求参数解析。
|
||||||
|
*/
|
||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
@PreAuthorize("hasAuthority('modelTrain:add')")
|
@PreAuthorize("hasAuthority('modelTrain:add')")
|
||||||
@GetMapping("/capacity/check")
|
@GetMapping("/capacity/check")
|
||||||
|
/**
|
||||||
|
* 提交训练任务前的容量预检。
|
||||||
|
*
|
||||||
|
* @return 容量检查结果(包含 canSubmit、counts、limits 等)
|
||||||
|
*/
|
||||||
public ResponseResult capacityCheck() {
|
public ResponseResult capacityCheck() {
|
||||||
return ResponseResult.successData(modelTrainService.checkCapacity());
|
return ResponseResult.successData(modelTrainService.checkCapacity());
|
||||||
}
|
}
|
||||||
@ -39,6 +66,15 @@ public class ModelTrainController {
|
|||||||
* 接收 Python 端的训练状态回调
|
* 接收 Python 端的训练状态回调
|
||||||
*/
|
*/
|
||||||
@PostMapping("/internal/callback")
|
@PostMapping("/internal/callback")
|
||||||
|
/**
|
||||||
|
* Python 训练状态回调入口。
|
||||||
|
* <p>
|
||||||
|
* 回调体需包含 taskId 与 status 等字段;后端更新任务状态并通过 WebSocket 推送给前端。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param callbackData 回调数据
|
||||||
|
* @return 标准响应
|
||||||
|
*/
|
||||||
public ResponseResult handleTrainCallback(@RequestBody Map<String, Object> callbackData) {
|
public ResponseResult handleTrainCallback(@RequestBody Map<String, Object> callbackData) {
|
||||||
System.out.println("====== 收到 Python 端训练回调 ======");
|
System.out.println("====== 收到 Python 端训练回调 ======");
|
||||||
System.out.println("回调数据: " + callbackData);
|
System.out.println("回调数据: " + callbackData);
|
||||||
@ -62,6 +98,12 @@ public class ModelTrainController {
|
|||||||
*/
|
*/
|
||||||
@Log(value = "上传训练数据集", module = "模型训练")
|
@Log(value = "上传训练数据集", module = "模型训练")
|
||||||
@PostMapping("/upload")
|
@PostMapping("/upload")
|
||||||
|
/**
|
||||||
|
* 上传训练数据集并执行基础预检(解析列名、返回告警等)。
|
||||||
|
*
|
||||||
|
* @param file 数据集文件
|
||||||
|
* @return 上传与预检结果
|
||||||
|
*/
|
||||||
public ResponseResult upload(@RequestParam("file") MultipartFile file) {
|
public ResponseResult upload(@RequestParam("file") MultipartFile file) {
|
||||||
return ResponseResult.successData(modelTrainService.uploadAndInspectDataset(file));
|
return ResponseResult.successData(modelTrainService.uploadAndInspectDataset(file));
|
||||||
}
|
}
|
||||||
@ -72,6 +114,16 @@ public class ModelTrainController {
|
|||||||
@Log(value = "提交训练任务", module = "模型训练")
|
@Log(value = "提交训练任务", module = "模型训练")
|
||||||
@PreAuthorize("hasAuthority('modelTrain:add')")
|
@PreAuthorize("hasAuthority('modelTrain:add')")
|
||||||
@PostMapping("/submit")
|
@PostMapping("/submit")
|
||||||
|
/**
|
||||||
|
* 提交训练任务。
|
||||||
|
* <p>
|
||||||
|
* 采用 multipart/form-data 传参:task 为 JSON 字符串,file 为可选数据集文件。若上传了文件,优先以文件路径作为 datasetPath。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param taskJson 任务 JSON(字符串)
|
||||||
|
* @param file 数据集文件(可选)
|
||||||
|
* @return 提交结果,data 为 taskId
|
||||||
|
*/
|
||||||
public ResponseResult submit(@RequestPart("task") String taskJson,
|
public ResponseResult submit(@RequestPart("task") String taskJson,
|
||||||
@RequestPart(value = "file", required = false) MultipartFile file) {
|
@RequestPart(value = "file", required = false) MultipartFile file) {
|
||||||
try {
|
try {
|
||||||
@ -105,6 +157,18 @@ public class ModelTrainController {
|
|||||||
* 查询任务列表 (支持条件查询)
|
* 查询任务列表 (支持条件查询)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
|
/**
|
||||||
|
* 查询训练任务列表(分页,支持条件过滤)。
|
||||||
|
*
|
||||||
|
* @param current 当前页(默认 1)
|
||||||
|
* @param size 每页条数(默认 10)
|
||||||
|
* @param algorithmType 算法类型(兼容 algorithmType/algoType 两种参数)
|
||||||
|
* @param algoType 算法类型(兼容字段)
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @param status 状态过滤
|
||||||
|
* @param name 名称关键字(模糊匹配)
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
public ResponseResult list(@RequestParam(defaultValue = "1") Integer current,
|
public ResponseResult list(@RequestParam(defaultValue = "1") Integer current,
|
||||||
@RequestParam(defaultValue = "10") Integer size,
|
@RequestParam(defaultValue = "10") Integer size,
|
||||||
@RequestParam(value = "algorithmType", required = false) String algorithmType,
|
@RequestParam(value = "algorithmType", required = false) String algorithmType,
|
||||||
@ -140,6 +204,12 @@ public class ModelTrainController {
|
|||||||
* 查询任务详情/状态
|
* 查询任务详情/状态
|
||||||
*/
|
*/
|
||||||
@GetMapping("/status/{taskId}")
|
@GetMapping("/status/{taskId}")
|
||||||
|
/**
|
||||||
|
* 查询任务详情/状态。
|
||||||
|
*
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @return 任务对象
|
||||||
|
*/
|
||||||
public ResponseResult status(@PathVariable String taskId) {
|
public ResponseResult status(@PathVariable String taskId) {
|
||||||
ModelTrainTask task = modelTrainService.syncTaskStatus(taskId);
|
ModelTrainTask task = modelTrainService.syncTaskStatus(taskId);
|
||||||
return ResponseResult.successData(task);
|
return ResponseResult.successData(task);
|
||||||
@ -151,6 +221,15 @@ public class ModelTrainController {
|
|||||||
@Log(value = "发布训练模型", module = "模型训练")
|
@Log(value = "发布训练模型", module = "模型训练")
|
||||||
@PreAuthorize("hasAuthority('modelTrain:publish')")
|
@PreAuthorize("hasAuthority('modelTrain:publish')")
|
||||||
@PostMapping("/publish")
|
@PostMapping("/publish")
|
||||||
|
/**
|
||||||
|
* 发布模型。
|
||||||
|
* <p>
|
||||||
|
* 仅允许对成功完成的训练任务进行发布;versionTag 需满足版本号格式约束。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param body 包含 taskId 与 versionTag
|
||||||
|
* @return 发布结果
|
||||||
|
*/
|
||||||
public ResponseResult publish(@RequestBody Map<String, String> body) {
|
public ResponseResult publish(@RequestBody Map<String, String> body) {
|
||||||
String taskId = body.get("taskId");
|
String taskId = body.get("taskId");
|
||||||
String versionTag = body.get("versionTag");
|
String versionTag = body.get("versionTag");
|
||||||
@ -165,6 +244,12 @@ public class ModelTrainController {
|
|||||||
@PreAuthorize("hasAuthority('modelTrain:del')")
|
@PreAuthorize("hasAuthority('modelTrain:del')")
|
||||||
//删除训练任务
|
//删除训练任务
|
||||||
@DeleteMapping("/{taskId}")
|
@DeleteMapping("/{taskId}")
|
||||||
|
/**
|
||||||
|
* 删除训练任务。
|
||||||
|
*
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @return 删除结果
|
||||||
|
*/
|
||||||
public ResponseResult delete(@PathVariable String taskId) {
|
public ResponseResult delete(@PathVariable String taskId) {
|
||||||
boolean success = modelTrainService.removeById(taskId);
|
boolean success = modelTrainService.removeById(taskId);
|
||||||
return success ? ResponseResult.success() : ResponseResult.error("删除失败");
|
return success ? ResponseResult.success() : ResponseResult.error("删除失败");
|
||||||
|
|||||||
@ -24,17 +24,45 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目管理接口。
|
||||||
|
* <p>
|
||||||
|
* 提供项目的增删改查、权限可见性管理、工程数据导入导出、拓扑解析与仿真初始化相关能力。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 权限与可见性:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>visibility:PRIVATE / READONLY / PUBLIC(由 ProjectAccessHelper 统一归一化与鉴权)。</li>
|
||||||
|
* <li>非管理员用户查询项目列表时会按可见性与所有者过滤。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/projects")
|
@RequestMapping("/projects")
|
||||||
@Tag(name = "项目接口", description = "项目增删改查、拓扑解析与模拟初始化")
|
@Tag(name = "项目接口", description = "项目增删改查、拓扑解析与模拟初始化")
|
||||||
public class ProjectController {
|
public class ProjectController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目服务(MyBatis-Plus Service)。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ProjectService projectService;
|
private ProjectService projectService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 序列化与拓扑字段解析。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
|
private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于获取用户名等信息。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目权限辅助类,用于对项目读写、可见性修改等操作进行鉴权校验。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ProjectAccessHelper projectAccessHelper;
|
private ProjectAccessHelper projectAccessHelper;
|
||||||
|
|
||||||
@ -98,6 +126,14 @@ public class ProjectController {
|
|||||||
return projectService.updateById(p);
|
return projectService.updateById(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录账号。
|
||||||
|
* <p>
|
||||||
|
* 用于 creator/modifier 等审计字段;未登录或取不到用户信息时返回 "anonymous"。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return 当前账号或 "anonymous"
|
||||||
|
*/
|
||||||
private String currentAccount() {
|
private String currentAccount() {
|
||||||
try {
|
try {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|||||||
@ -18,14 +18,37 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景管理接口。
|
||||||
|
* <p>
|
||||||
|
* 提供情景的新增、修改、删除、按项目查询等能力,并在写操作时进行项目写权限校验。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 状态约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>status="0":默认初始状态(待运行/未运行)。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/scenarios")
|
@RequestMapping("/scenarios")
|
||||||
public class ScenarioController {
|
public class ScenarioController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景服务(MyBatis-Plus Service)。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ScenarioService scenarioService;
|
private ScenarioService scenarioService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于记录 modifier 等审计字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目权限辅助类,用于对 projectId 的读写操作进行鉴权校验。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ProjectAccessHelper projectAccessHelper;
|
private ProjectAccessHelper projectAccessHelper;
|
||||||
|
|
||||||
@ -206,6 +229,14 @@ public class ScenarioController {
|
|||||||
return scenarioService.page(page, qw);
|
return scenarioService.page(page, qw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户名。
|
||||||
|
* <p>
|
||||||
|
* 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return 当前用户名或 "anonymous"
|
||||||
|
*/
|
||||||
private String currentUsername() {
|
private String currentUsername() {
|
||||||
try {
|
try {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|||||||
@ -28,16 +28,37 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景结果查询与导出接口。
|
||||||
|
* <p>
|
||||||
|
* 提供情景仿真结果的分页查询与按条件导出 Excel 能力,并通过 Scenario -> Project 的关系校验项目读权限。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/scenario-results")
|
@RequestMapping("/scenario-results")
|
||||||
public class ScenarioResultController {
|
public class ScenarioResultController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景结果服务(MyBatis-Plus Service)。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ScenarioResultService scenarioResultService;
|
private ScenarioResultService scenarioResultService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景服务,用于获取 scenarioId 对应的 projectId 并执行权限校验。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ScenarioService scenarioService;
|
private ScenarioService scenarioService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目权限辅助类,用于对 projectId 的读操作进行鉴权校验。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ProjectAccessHelper projectAccessHelper;
|
private ProjectAccessHelper projectAccessHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备服务,用于补全结果中的设备名称/类型等展示字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private DeviceService deviceService;
|
private DeviceService deviceService;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* REST API Controller 层。
|
||||||
|
* <p>
|
||||||
|
* Controller 负责对外协议处理与请求路由,重点关注:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>入参来源:Path/Query/Body 的解析与校验。</li>
|
||||||
|
* <li>权限校验:在安全框架基础上增加必要的业务级访问控制(例如项目权限)。</li>
|
||||||
|
* <li>业务委托:不堆叠复杂业务逻辑,主要调用 Service 层完成业务处理。</li>
|
||||||
|
* <li>返回统一:接口返回结构与错误码语义一致,便于前端与测试定位。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 常见接口类型:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>基础 CRUD:项目、场景、设备、事件、材料、关键数据等。</li>
|
||||||
|
* <li>解析与校验:拓扑解析、属性解析、数据校验与问题汇总。</li>
|
||||||
|
* <li>推演与训练:推演提交、训练提交、状态查询、结果查询等。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 编码约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>避免直接操作 Mapper;统一通过 Service 层访问数据。</li>
|
||||||
|
* <li>避免在 Controller 中拼装复杂对象;复杂拼装放到 build/facade/model 相关包。</li>
|
||||||
|
* <li>异常处理依赖统一异常机制,Controller 内部尽量不捕获吞异常。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.controller;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 领域实体(Domain)层。
|
||||||
|
* <p>
|
||||||
|
* 该包主要承载与数据库表结构强相关的数据对象(Entity/PO),用于持久化与查询映射。
|
||||||
|
* 与 DTO/Model 不同,Domain 更强调:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>字段与表结构一致,便于 Mapper 层直接映射。</li>
|
||||||
|
* <li>以数据承载为主,尽量避免包含跨聚合业务逻辑。</li>
|
||||||
|
* <li>序列化字段(如 JSON)需要在接口层做输入规范化,避免落库类型不匹配。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Domain 用于持久化,不作为接口出参的最终结构(避免字段泄露与耦合)。</li>
|
||||||
|
* <li>字段校验(非空、范围、格式)在 Controller/Service 层完成。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.domain;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* DTO(Data Transfer Object)层。
|
||||||
|
* <p>
|
||||||
|
* DTO 用于跨层传递与接口出入参承载,典型场景包括:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>拓扑解析结果、推演计划、节点/边结构等结构化返回。</li>
|
||||||
|
* <li>事件属性解析结果、时间线点位、分段信息等解析型数据。</li>
|
||||||
|
* <li>面向前端的聚合视图对象,避免直接暴露 Domain 字段。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>字段命名与前端协议保持一致,减少序列化配置复杂度。</li>
|
||||||
|
* <li>DTO 内部尽量不包含持久化细节(主键策略、表字段等)。</li>
|
||||||
|
* <li>集合字段尽量返回空集合而非 null,减少前端判空负担。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.dto;
|
||||||
@ -14,15 +14,43 @@ import org.springframework.stereotype.Component;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仿真数据门面
|
* 仿真数据门面。
|
||||||
* 负责与各个业务Service交互,获取仿真所需的原始数据 (Project, Events 等)
|
* <p>
|
||||||
|
* 负责与各业务 Service 交互,获取仿真所需的原始数据(项目、设备、事件等),并组装为统一的数据包对象供仿真流程使用。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 当前实现的职责边界:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>只做数据拉取与聚合,不做仿真计算与业务写入。</li>
|
||||||
|
* <li>数据查询维度:projectId 关联项目与设备;scenarioId 关联事件。</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class SimDataFacade {
|
public class SimDataFacade {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目服务,用于获取项目基础信息与拓扑数据等。
|
||||||
|
*/
|
||||||
@Autowired private ProjectService projectService;
|
@Autowired private ProjectService projectService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件服务,用于加载情景下的事件列表。
|
||||||
|
*/
|
||||||
@Autowired private EventService eventService;
|
@Autowired private EventService eventService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备服务,用于加载项目下设备列表(静态属性解析与补全)。
|
||||||
|
*/
|
||||||
@Autowired private DeviceService deviceService;
|
@Autowired private DeviceService deviceService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载仿真所需的基础数据并组装为数据包。
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param scenarioId 情景ID
|
||||||
|
* @return 仿真数据包(包含 Project、Device 列表与 Event 列表)
|
||||||
|
*/
|
||||||
public SimDataPackage loadSimulationData(String projectId, String scenarioId) {
|
public SimDataPackage loadSimulationData(String projectId, String scenarioId) {
|
||||||
// 1. 获取项目与拓扑
|
// 1. 获取项目与拓扑
|
||||||
Project project = projectService.getById(projectId);
|
Project project = projectService.getById(projectId);
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Facade(门面)层。
|
||||||
|
* <p>
|
||||||
|
* 该包用于封装对外部依赖或跨领域聚合的调用细节,对上层(Controller/Service)提供更稳定的调用接口:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>聚合多个数据源/服务的结果,降低上层编排复杂度。</li>
|
||||||
|
* <li>对外部系统返回做统一解析、容错与错误语义转换。</li>
|
||||||
|
* <li>对外部调用的超时、重试、限流策略提供集中入口(如有需要)。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Facade 不应绕过 Service 的业务约束,避免形成“第二套业务入口”。</li>
|
||||||
|
* <li>对外部调用涉及敏感信息时必须避免日志泄露。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.facade;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Mapper(数据访问)层。
|
||||||
|
* <p>
|
||||||
|
* 该包用于定义数据库访问接口,负责将 Domain 实体与数据库表进行映射,典型职责:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>基础 CRUD:按主键/条件增删改查。</li>
|
||||||
|
* <li>复杂查询:按业务维度做聚合/统计/分页查询(如有)。</li>
|
||||||
|
* <li>尽量不在 Mapper 层加入业务判断,保持其“数据访问”属性。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>事务边界由 Service 层控制,Mapper 不负责事务语义。</li>
|
||||||
|
* <li>外部输入参数需在上层完成校验,Mapper 层不承担入参清洗。</li>
|
||||||
|
* <li>对于可能返回 null 的聚合结果(如 max/sum),上层应做判空兜底。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.mapper;
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 元信息(Meta)定义与注册。
|
||||||
|
* <p>
|
||||||
|
* 用于描述与设备、属性、尺寸等相关的“元数据”结构,包括:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>字段定义:字段名、类型、单位、取值约束等。</li>
|
||||||
|
* <li>Schema/Registry:对元数据进行注册、查询与按类型匹配。</li>
|
||||||
|
* <li>与业务实体解耦:元信息侧重“描述”,业务数据侧重“实例”。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>元信息应具备向后兼容策略,避免升级导致历史数据不可解析。</li>
|
||||||
|
* <li>注册表初始化应可控,避免在静态初始化中引入外部依赖。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.meta;
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 业务过程模型(Model)。
|
||||||
|
* <p>
|
||||||
|
* 该包用于承载推演/仿真过程中的上下文、请求与响应结构、结果转换等“过程型对象”,典型用途:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>推演请求/响应:面向推演服务的请求体与结果结构。</li>
|
||||||
|
* <li>推演上下文:在一次推演流程内贯穿使用的临时数据与中间结果。</li>
|
||||||
|
* <li>结果转换:将底层数据结构转换为前端可展示的结构(如轨迹、影响链路)。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 与 Domain/DTO 的区别:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Domain:更偏持久化实体;Model:更偏过程与计算语义。</li>
|
||||||
|
* <li>DTO:更偏接口传输;Model:更偏内部流程编排与输出组织。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Model 应可序列化(必要时),并避免引入不必要的框架依赖。</li>
|
||||||
|
* <li>对外输出字段应稳定,避免随内部实现频繁变化。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.model;
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* business-css 后端主模块。
|
||||||
|
* <p>
|
||||||
|
* 本模块围绕“仿真/场景/项目/设备/事件/材料/关键数据/训练任务”等核心对象提供后端服务能力,
|
||||||
|
* 主要以 REST API 形式对外提供增删改查、解析、仿真推演、模型训练提交与状态查询等能力。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 设计目标:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>职责清晰:Controller 仅做入参校验与协议转换,业务规则集中在 Service 层。</li>
|
||||||
|
* <li>最小副作用:对外接口尽量保持幂等,避免隐藏状态修改。</li>
|
||||||
|
* <li>可追溯:关键动作与异常路径保持可定位性,便于测试与运维排障。</li>
|
||||||
|
* <li>可演进:核心对象与流程以可扩展方式建模,避免跨层强耦合。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 目录结构约定(对应子 package):
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>build:仿真数据构建与组装流程(面向推演输入的结构化转换)。</li>
|
||||||
|
* <li>controller:对外 HTTP API 层,负责请求路由、鉴权入口与参数校验。</li>
|
||||||
|
* <li>service:领域服务接口层,定义业务能力边界与业务语义。</li>
|
||||||
|
* <li>service.impl:领域服务实现层,封装业务规则、事务语义与外部依赖调用。</li>
|
||||||
|
* <li>domain:领域实体(与表结构/持久化对象强相关)。</li>
|
||||||
|
* <li>dto:跨层传输对象(接口返回、解析结果、拓扑结构等)。</li>
|
||||||
|
* <li>mapper:数据访问层(MyBatis/MyBatis-Plus Mapper)。</li>
|
||||||
|
* <li>model:业务过程模型(推演上下文、推演结果、请求/响应结构)。</li>
|
||||||
|
* <li>meta:设备/属性等元信息定义与注册表。</li>
|
||||||
|
* <li>facade:面向外部依赖的聚合门面(减少 Controller/ServiceImpl 直接依赖外部细节)。</li>
|
||||||
|
* <li>security:与项目访问控制相关的辅助能力(在现有安全框架内做业务级校验)。</li>
|
||||||
|
* <li>config:Spring Boot 配置类(MyBatis、OpenAPI、WebSocket、RestTemplate 等)。</li>
|
||||||
|
* <li>utils:业务内工具类(解析、转换、拼装等与通用框架工具区分)。</li>
|
||||||
|
* <li>common.exception:业务异常定义(区分参数错误、状态错误、推演错误等)。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 编码约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>接口返回统一:成功/失败结构统一,错误信息可读且便于定位。</li>
|
||||||
|
* <li>空值与边界:对外输入必须显式校验,对外输出避免返回 null 集合。</li>
|
||||||
|
* <li>外部返回解析:对外系统返回字段(例如 code/msg/data)需做健壮解析与容错。</li>
|
||||||
|
* <li>序列化:DTO/Model 中字段命名与前端约定一致,避免隐式字段映射。</li>
|
||||||
|
* <li>日志:仅记录业务必要信息,不记录密钥、口令、token 等敏感数据。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 运行期约束:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>认证:统一使用平台侧鉴权机制(JWT + 在线状态校验)。</li>
|
||||||
|
* <li>权限:以“项目”为业务隔离边界,读写操作需校验项目可访问性。</li>
|
||||||
|
* <li>并发:训练/推演等重任务需受容量控制与超时回收机制约束。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 业务安全辅助能力。
|
||||||
|
* <p>
|
||||||
|
* 在平台安全框架(认证、鉴权、Token 校验等)之上,该包提供与业务对象相关的访问控制辅助能力,
|
||||||
|
* 常见场景为“按项目隔离”的访问检查。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>对外接口:封装“用户是否可访问某项目/资源”的判断。</li>
|
||||||
|
* <li>最小暴露:不在此处扩散安全框架细节,避免上层出现大量重复判断。</li>
|
||||||
|
* <li>失败语义:权限不足时给出明确的错误信息与统一的返回结构。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>安全校验为前置约束,避免业务执行到中途再拒绝导致数据不一致。</li>
|
||||||
|
* <li>不记录敏感信息(token、密钥、口令等)。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.security;
|
||||||
@ -5,6 +5,13 @@ import com.yfd.business.css.domain.AlgorithmModel;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 算法模型版本服务接口。
|
||||||
|
* <p>
|
||||||
|
* 提供模型版本查询、当前激活版本解析,以及批量删除前置校验等能力。
|
||||||
|
* 默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface AlgorithmModelService extends IService<AlgorithmModel> {
|
public interface AlgorithmModelService extends IService<AlgorithmModel> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +43,12 @@ public interface AlgorithmModelService extends IService<AlgorithmModel> {
|
|||||||
*/
|
*/
|
||||||
AlgorithmModel getCurrentModel(String algorithmType, String deviceType, String materialType);
|
AlgorithmModel getCurrentModel(String algorithmType, String deviceType, String materialType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除模型版本(带业务校验)。
|
||||||
|
*
|
||||||
|
* @param ids 模型主键列表
|
||||||
|
* @return 是否删除成功
|
||||||
|
*/
|
||||||
boolean deleteBatchWithCheck(List<String> ids);
|
boolean deleteBatchWithCheck(List<String> ids);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,5 +3,11 @@ package com.yfd.business.css.service;
|
|||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.yfd.business.css.domain.Algorithm;
|
import com.yfd.business.css.domain.Algorithm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 算法字典服务接口。
|
||||||
|
* <p>
|
||||||
|
* 定义算法字典(Algorithm)的基础数据访问与管理能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface AlgorithmService extends IService<Algorithm> {
|
public interface AlgorithmService extends IService<Algorithm> {
|
||||||
}
|
}
|
||||||
@ -6,20 +6,59 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 临界数据服务接口。
|
||||||
|
* <p>
|
||||||
|
* 提供临界数据的导入导出、校验、清理等业务能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface CriticalDataService extends IService<CriticalData> {
|
public interface CriticalDataService extends IService<CriticalData> {
|
||||||
/**
|
/**
|
||||||
* 导入临界数据
|
* 导入临界数据
|
||||||
*/
|
*/
|
||||||
boolean importCriticalData(MultipartFile file, String deviceType);
|
boolean importCriticalData(MultipartFile file, String deviceType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入临界数据(V2)。
|
||||||
|
*
|
||||||
|
* @param file Excel/CSV 文件
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @return 是否导入成功
|
||||||
|
*/
|
||||||
boolean importCriticalDataV2(MultipartFile file, String deviceType);
|
boolean importCriticalDataV2(MultipartFile file, String deviceType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验临界数据导入文件(V2)。
|
||||||
|
*
|
||||||
|
* @param file Excel/CSV 文件
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @return 校验结果(包含错误、可导入行等)
|
||||||
|
*/
|
||||||
Map<String, Object> validateCriticalDataV2(MultipartFile file, String deviceType);
|
Map<String, Object> validateCriticalDataV2(MultipartFile file, String deviceType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出临界数据(V2)。
|
||||||
|
*
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @param ids 指定导出记录ID列表(可选)
|
||||||
|
* @return xlsx 文件字节流
|
||||||
|
*/
|
||||||
byte[] exportCriticalDataV2(String deviceType, List<String> ids);
|
byte[] exportCriticalDataV2(String deviceType, List<String> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载临界数据导入模板(V2)。
|
||||||
|
*
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @return xlsx 模板字节流
|
||||||
|
*/
|
||||||
byte[] templateCriticalDataV2(String deviceType);
|
byte[] templateCriticalDataV2(String deviceType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按设备类型清空临界数据。
|
||||||
|
*
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @return 删除条数
|
||||||
|
*/
|
||||||
int deleteByDeviceType(String deviceType);
|
int deleteByDeviceType(String deviceType);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,20 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备推理服务。
|
||||||
|
* <p>
|
||||||
|
* 将仿真计算得到的设备时序数据(按 deviceType/materialType 分组)转换为推理请求,
|
||||||
|
* 调用 Python 推理服务,并将推理结果写入情景结果表。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 推理分组策略(由业务约定驱动):
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>按设备类型分组 → 按算法类型分组(全局/设备级覆盖)→ 按材料类型分组 → 按模型ID分组(可选)。</li>
|
||||||
|
* <li>若未指定模型ID,则使用当前激活版本的模型。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class DeviceInferService {
|
public class DeviceInferService {
|
||||||
@ -39,16 +53,34 @@ public class DeviceInferService {
|
|||||||
@Value("${file-space.model-path}")
|
@Value("${file-space.model-path}")
|
||||||
private String modelRootPath;
|
private String modelRootPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景服务,用于读取情景配置(全局算法类型、设备级算法配置等)。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ScenarioService scenarioService;
|
private ScenarioService scenarioService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型版本服务,用于解析当前激活模型或按 ID 定向加载模型版本。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private AlgorithmModelService algorithmModelService;
|
private AlgorithmModelService algorithmModelService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景结果服务,用于写入推理结果与失败信息。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ScenarioResultService scenarioResultService;
|
private ScenarioResultService scenarioResultService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对项目某情景下的设备数据进行推理并落库。
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param scenarioId 情景ID
|
||||||
|
* @param groupedDevices deviceType -> 设备时间步数据列表
|
||||||
|
*/
|
||||||
public void processDeviceInference(String projectId, String scenarioId,
|
public void processDeviceInference(String projectId, String scenarioId,
|
||||||
Map<String, List<DeviceStepInfo>> groupedDevices) {
|
Map<String, List<DeviceStepInfo>> groupedDevices) {
|
||||||
// 增加标志位,记录是否至少成功执行了一次推理
|
// 增加标志位,记录是否至少成功执行了一次推理
|
||||||
|
|||||||
@ -5,21 +5,69 @@ import com.yfd.business.css.domain.Device;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
/**
|
||||||
|
* 设备服务接口。
|
||||||
|
* <p>
|
||||||
|
* 提供设备模板库/项目设备的业务操作能力,包括新增更新、以及 Excel 导入导出等。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface DeviceService extends IService<Device> {
|
public interface DeviceService extends IService<Device> {
|
||||||
/**
|
/**
|
||||||
* 导入设备
|
* 导入设备
|
||||||
*/
|
*/
|
||||||
boolean importDevices(MultipartFile file, String deviceType);
|
boolean importDevices(MultipartFile file, String deviceType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入设备(V2)。
|
||||||
|
*
|
||||||
|
* @param file Excel/CSV 文件
|
||||||
|
* @param projectId 项目ID(为空时一般表示模板库)
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @return 是否导入成功
|
||||||
|
*/
|
||||||
boolean importDevicesV2(MultipartFile file, String projectId, String deviceType);
|
boolean importDevicesV2(MultipartFile file, String projectId, String deviceType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验设备导入文件(V2)。
|
||||||
|
*
|
||||||
|
* @param file Excel/CSV 文件
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @return 校验结果(包含错误、可导入行等)
|
||||||
|
*/
|
||||||
Map<String, Object> validateDevicesV2(MultipartFile file, String projectId, String deviceType);
|
Map<String, Object> validateDevicesV2(MultipartFile file, String projectId, String deviceType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出设备(V2)。
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @param ids 指定导出设备ID列表(可选)
|
||||||
|
* @return xlsx 文件字节流
|
||||||
|
*/
|
||||||
byte[] exportDevicesV2(String projectId, String deviceType, List<String> ids);
|
byte[] exportDevicesV2(String projectId, String deviceType, List<String> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载设备导入模板(V2)。
|
||||||
|
*
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @return xlsx 模板字节流
|
||||||
|
*/
|
||||||
byte[] templateDevicesV2(String deviceType);
|
byte[] templateDevicesV2(String deviceType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增设备(包含业务侧字段补全与校验)。
|
||||||
|
*
|
||||||
|
* @param device 设备对象
|
||||||
|
* @return 是否新增成功
|
||||||
|
*/
|
||||||
boolean createDevice(Device device) ;
|
boolean createDevice(Device device) ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存或更新设备(按业务主键/唯一键语义处理)。
|
||||||
|
*
|
||||||
|
* @param device 设备对象
|
||||||
|
* @return 是否保存/更新成功
|
||||||
|
*/
|
||||||
boolean saveOrUpdateByBusiness(Device device);
|
boolean saveOrUpdateByBusiness(Device device);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,5 +3,11 @@ package com.yfd.business.css.service;
|
|||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.yfd.business.css.domain.Event;
|
import com.yfd.business.css.domain.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件服务接口。
|
||||||
|
* <p>
|
||||||
|
* 定义事件(Event)的基础数据访问与管理能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface EventService extends IService<Event> {
|
public interface EventService extends IService<Event> {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,12 @@ import com.yfd.business.css.domain.Material;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物料服务接口。
|
||||||
|
* <p>
|
||||||
|
* 提供物料(模板库/项目物料)的业务操作能力,包括导入导出、保存与业务维度的保存/更新等。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface MaterialService extends IService<Material> {
|
public interface MaterialService extends IService<Material> {
|
||||||
/**
|
/**
|
||||||
* 导入物料
|
* 导入物料
|
||||||
@ -17,9 +23,28 @@ public interface MaterialService extends IService<Material> {
|
|||||||
*/
|
*/
|
||||||
boolean saveMaterial(Material material);
|
boolean saveMaterial(Material material);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存或更新物料(按业务主键/唯一键语义处理)。
|
||||||
|
*
|
||||||
|
* @param material 物料对象
|
||||||
|
* @return 是否保存/更新成功
|
||||||
|
*/
|
||||||
boolean saveOrUpdateByBusiness(Material material);
|
boolean saveOrUpdateByBusiness(Material material);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出物料(V2)。
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID(为空时一般表示模板库)
|
||||||
|
* @param ids 指定导出记录ID列表(可选)
|
||||||
|
* @param nameLike 名称模糊筛选(可选)
|
||||||
|
* @return xlsx 文件字节流
|
||||||
|
*/
|
||||||
byte[] exportMaterialsV2(String projectId, List<String> ids, String nameLike);
|
byte[] exportMaterialsV2(String projectId, List<String> ids, String nameLike);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载物料导入模板(V2)。
|
||||||
|
*
|
||||||
|
* @return xlsx 模板字节流
|
||||||
|
*/
|
||||||
byte[] templateMaterialsV2();
|
byte[] templateMaterialsV2();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,12 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型训练任务服务接口。
|
||||||
|
* <p>
|
||||||
|
* 提供数据集上传与解析、训练任务提交、状态同步与回调更新、以及模型发布等能力。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface ModelTrainService extends IService<ModelTrainTask> {
|
public interface ModelTrainService extends IService<ModelTrainTask> {
|
||||||
/**
|
/**
|
||||||
* 上传数据集
|
* 上传数据集
|
||||||
@ -15,8 +21,20 @@ public interface ModelTrainService extends IService<ModelTrainTask> {
|
|||||||
*/
|
*/
|
||||||
String uploadDataset(MultipartFile file);
|
String uploadDataset(MultipartFile file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析数据集列名列表。
|
||||||
|
*
|
||||||
|
* @param datasetPath 数据集文件路径
|
||||||
|
* @return 列名列表
|
||||||
|
*/
|
||||||
List<String> parseDatasetColumns(String datasetPath);
|
List<String> parseDatasetColumns(String datasetPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传数据集并立即进行字段解析与基础检查。
|
||||||
|
*
|
||||||
|
* @param file 上传文件
|
||||||
|
* @return 预检结果(包含 path/columns/warnings 等)
|
||||||
|
*/
|
||||||
Map<String, Object> uploadAndInspectDataset(MultipartFile file);
|
Map<String, Object> uploadAndInspectDataset(MultipartFile file);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,5 +66,10 @@ public interface ModelTrainService extends IService<ModelTrainTask> {
|
|||||||
*/
|
*/
|
||||||
boolean publishModel(String taskId, String versionTag);
|
boolean publishModel(String taskId, String versionTag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询训练任务提交容量(Training/Pending 上限与当前计数)。
|
||||||
|
*
|
||||||
|
* @return 容量检查结果(包含 canSubmit、counts、limits 等)
|
||||||
|
*/
|
||||||
Map<String, Object> checkCapacity();
|
Map<String, Object> checkCapacity();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,12 @@ import com.yfd.business.css.model.SimInfluenceNode;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目服务接口。
|
||||||
|
* <p>
|
||||||
|
* 提供项目的基础 CRUD、工程导入导出、拓扑解析、仿真初始化与运行等业务能力。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface ProjectService extends IService<Project> {
|
public interface ProjectService extends IService<Project> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -3,5 +3,11 @@ package com.yfd.business.css.service;
|
|||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.yfd.business.css.domain.ScenarioResult;
|
import com.yfd.business.css.domain.ScenarioResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景结果服务接口。
|
||||||
|
* <p>
|
||||||
|
* 定义情景仿真结果(ScenarioResult)的基础数据访问与管理能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface ScenarioResultService extends IService<ScenarioResult> {
|
public interface ScenarioResultService extends IService<ScenarioResult> {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,18 @@ package com.yfd.business.css.service;
|
|||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.yfd.business.css.domain.Scenario;
|
import com.yfd.business.css.domain.Scenario;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景服务接口。
|
||||||
|
* <p>
|
||||||
|
* 定义情景(Scenario)的基础数据访问与管理能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface ScenarioService extends IService<Scenario> {
|
public interface ScenarioService extends IService<Scenario> {
|
||||||
//根据场景id,获取算法类型
|
/**
|
||||||
|
* 根据情景 ID 获取该情景配置的算法类型。
|
||||||
|
*
|
||||||
|
* @param scenarioId 情景ID
|
||||||
|
* @return 算法类型
|
||||||
|
*/
|
||||||
String getAlgorithmType(String scenarioId);
|
String getAlgorithmType(String scenarioId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,18 @@ package com.yfd.business.css.service;
|
|||||||
import com.yfd.business.css.model.SimulationRequest;
|
import com.yfd.business.css.model.SimulationRequest;
|
||||||
import com.yfd.business.css.model.SimulationResult;
|
import com.yfd.business.css.model.SimulationResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仿真服务接口。
|
||||||
|
* <p>
|
||||||
|
* 定义仿真执行的统一入口,具体实现可根据不同仿真引擎/算法进行扩展。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
public interface SimulationService {
|
public interface SimulationService {
|
||||||
|
/**
|
||||||
|
* 执行一次仿真并返回结果。
|
||||||
|
*
|
||||||
|
* @param request 仿真请求参数
|
||||||
|
* @return 仿真结果
|
||||||
|
*/
|
||||||
SimulationResult runSimulation(SimulationRequest request);
|
SimulationResult runSimulation(SimulationRequest request);
|
||||||
}
|
}
|
||||||
@ -6,6 +6,16 @@ import org.springframework.stereotype.Service;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 训练状态 WebSocket 推送服务。
|
||||||
|
* <p>
|
||||||
|
* 将训练任务状态变化通过 STOMP 广播给前端,支持:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>单任务主题:/topic/train-status/{taskId}(详情页可用)</li>
|
||||||
|
* <li>全局主题:/topic/train-status/all(列表页可用)</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class TrainWebSocketService {
|
public class TrainWebSocketService {
|
||||||
|
|||||||
@ -12,6 +12,21 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 算法模型版本服务实现。
|
||||||
|
* <p>
|
||||||
|
* 提供当前激活模型的查询(按 algorithmType/deviceType/materialType 维度),以及模型版本的批量删除校验能力。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 业务约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>当前版本:is_current=1。</li>
|
||||||
|
* <li>材料类型兼容:Mixed 与 MIX 视为同一类。</li>
|
||||||
|
* <li>material_type 为空时,优先匹配空/NULL,再作为回退版本使用。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class AlgorithmModelServiceImpl extends ServiceImpl<AlgorithmModelMapper, AlgorithmModel> implements AlgorithmModelService {
|
public class AlgorithmModelServiceImpl extends ServiceImpl<AlgorithmModelMapper, AlgorithmModel> implements AlgorithmModelService {
|
||||||
|
|||||||
@ -7,6 +7,12 @@ import com.yfd.business.css.service.AlgorithmService;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 算法字典服务实现。
|
||||||
|
* <p>
|
||||||
|
* 在保存/更新算法字典时,对部分 JSON 字段进行归一化处理,避免空字符串导致解析失败或前端渲染异常。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class AlgorithmServiceImpl extends ServiceImpl<AlgorithmMapper, Algorithm> implements AlgorithmService {
|
public class AlgorithmServiceImpl extends ServiceImpl<AlgorithmMapper, Algorithm> implements AlgorithmService {
|
||||||
@ -22,6 +28,14 @@ public class AlgorithmServiceImpl extends ServiceImpl<AlgorithmMapper, Algorithm
|
|||||||
return super.updateById(entity);
|
return super.updateById(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归一化算法字典中的 JSON 字段为空值。
|
||||||
|
* <p>
|
||||||
|
* 将空/空白字符串替换为默认 JSON({} 或 []),保证字段在下游解析时始终为合法 JSON。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param a 算法对象
|
||||||
|
*/
|
||||||
private void normalizeJsonFields(Algorithm a) {
|
private void normalizeJsonFields(Algorithm a) {
|
||||||
if (a == null) return;
|
if (a == null) return;
|
||||||
a.setInputParams(blankToDefaultJson(a.getInputParams(), "{}"));
|
a.setInputParams(blankToDefaultJson(a.getInputParams(), "{}"));
|
||||||
|
|||||||
@ -37,13 +37,36 @@ import java.util.Collections;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 临界数据服务实现。
|
||||||
|
* <p>
|
||||||
|
* 提供临界数据的 Excel 导入、校验、导出与模板生成能力,并在导入过程中做字段解析与数据规整。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 设计要点:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>导入支持 xls/xlsx;V2 提供“先校验后导入”的交互方式。</li>
|
||||||
|
* <li>对数值/公式单元格使用 DataFormatter + FormulaEvaluator 进行兼容读取。</li>
|
||||||
|
* <li>解析失败信息以结构化 errors 列表返回给前端,便于定位问题行。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CriticalDataServiceImpl
|
public class CriticalDataServiceImpl
|
||||||
extends ServiceImpl<CriticalDataMapper, CriticalData>
|
extends ServiceImpl<CriticalDataMapper, CriticalData>
|
||||||
implements CriticalDataService {
|
implements CriticalDataService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 解析器,用于处理扩展字段与自定义属性的解析。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于写入 modifier 等审计字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -37,15 +37,40 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备服务实现。
|
||||||
|
* <p>
|
||||||
|
* 提供设备的导入导出(含 V2 校验与模板)、以及设备新增/更新等能力,并对设备尺寸等字段按 schema 做解析与规整。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 设计要点:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>导入支持 xls/xlsx;V2 提供“先校验后导入”的交互方式。</li>
|
||||||
|
* <li>deviceSizeSchemaRegistry 用于按 deviceType 获取尺寸字段元数据,以统一导入/导出/校验口径。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class DeviceServiceImpl
|
public class DeviceServiceImpl
|
||||||
extends ServiceImpl<DeviceMapper, Device>
|
extends ServiceImpl<DeviceMapper, Device>
|
||||||
implements DeviceService {
|
implements DeviceService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 解析器,用于处理扩展字段与自定义属性的解析。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于写入 modifier 等审计字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备尺寸 schema 注册表,用于按设备类型解析/校验尺寸字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private DeviceSizeSchemaRegistry deviceSizeSchemaRegistry;
|
private DeviceSizeSchemaRegistry deviceSizeSchemaRegistry;
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -7,6 +7,12 @@ import com.yfd.business.css.service.EventService;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件服务实现。
|
||||||
|
* <p>
|
||||||
|
* 事件数据主要由上层控制器按情景维度进行读写与权限校验;此处提供基于 MyBatis-Plus 的基础 CRUD 能力。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class EventServiceImpl
|
public class EventServiceImpl
|
||||||
|
|||||||
@ -35,13 +35,34 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 物料服务实现。
|
||||||
|
* <p>
|
||||||
|
* 提供物料的 Excel 导入导出、模板生成,以及物料新增/更新的业务语义处理(如 materialId 自动生成)。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 设计要点:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>导入支持 xls/xlsx,通过表头映射进行字段解析。</li>
|
||||||
|
* <li>custom_attrs 字段以 JSON 形式保存,需要做合法性校验与空值规整。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class MaterialServiceImpl
|
public class MaterialServiceImpl
|
||||||
extends ServiceImpl<MaterialMapper, Material>
|
extends ServiceImpl<MaterialMapper, Material>
|
||||||
implements MaterialService {
|
implements MaterialService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 解析器,用于处理自定义属性等 JSON 字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户信息服务,用于写入 modifier 等审计字段。
|
||||||
|
*/
|
||||||
@Resource
|
@Resource
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import com.yfd.business.css.service.ModelTrainService;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@ -23,6 +24,8 @@ import org.springframework.scheduling.annotation.Async;
|
|||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
import org.springframework.web.client.HttpStatusCodeException;
|
import org.springframework.web.client.HttpStatusCodeException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@ -53,6 +56,31 @@ import java.util.UUID;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型训练任务服务实现。
|
||||||
|
* <p>
|
||||||
|
* 该类负责训练数据集上传与解析、训练任务创建与提交、与外部 Python 训练服务交互、
|
||||||
|
* 以及训练任务状态流转与超时回收等能力。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 关键流程:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>数据集上传:接收 MultipartFile,将文件落盘并返回可追溯路径。</li>
|
||||||
|
* <li>数据集检查:解析表头/字段并返回给前端,用于配置训练参数。</li>
|
||||||
|
* <li>任务提交:创建训练任务,初始化状态并触发异步训练调用。</li>
|
||||||
|
* <li>容量控制:限制 Training 并发上限与 Pending 排队上限。</li>
|
||||||
|
* <li>超时回收:定时扫描超时的 Training 任务并置为失败,防止容量被长期占用。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约束与注意事项:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>路径通过配置注入,避免环境差异导致不可写目录。</li>
|
||||||
|
* <li>外部返回字段需要做健壮解析,避免 null/空字符串/非数字导致异常。</li>
|
||||||
|
* <li>回调更新需要具备幂等语义,避免重复回调造成状态覆盖。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, ModelTrainTask> implements ModelTrainService {
|
public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, ModelTrainTask> implements ModelTrainService {
|
||||||
@ -75,6 +103,10 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private ModelTrainServiceImpl self;
|
||||||
|
|
||||||
private static final Pattern VERSION_TAG_PATTERN = Pattern.compile("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$");
|
private static final Pattern VERSION_TAG_PATTERN = Pattern.compile("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$");
|
||||||
private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
|
private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
|
||||||
private static final Pattern DERIVED_EXPR_ALLOWED_PATTERN = Pattern.compile("^[A-Za-z0-9_+\\-*/()\\s]+$");
|
private static final Pattern DERIVED_EXPR_ALLOWED_PATTERN = Pattern.compile("^[A-Za-z0-9_+\\-*/()\\s]+$");
|
||||||
@ -82,6 +114,20 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
|||||||
private static final int MAX_PENDING = 5;
|
private static final int MAX_PENDING = 5;
|
||||||
private static final int TRAINING_TIMEOUT_MINUTES = 120;
|
private static final int TRAINING_TIMEOUT_MINUTES = 120;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传训练数据集文件并返回保存后的绝对路径。
|
||||||
|
* <p>
|
||||||
|
* 文件将按日期目录分组保存,文件名使用 UUID,避免并发上传冲突。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>入参校验:上传文件不能为空。</li>
|
||||||
|
* <li>路径解析:支持相对路径配置,运行时转换为绝对路径。</li>
|
||||||
|
* <li>目录创建:目录不存在时创建目录,并处理并发创建的情况。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param file 上传文件
|
||||||
|
* @return 保存后的绝对路径
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String uploadDataset(MultipartFile file) {
|
public String uploadDataset(MultipartFile file) {
|
||||||
if (file.isEmpty()) {
|
if (file.isEmpty()) {
|
||||||
@ -129,6 +175,16 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析数据集列名列表。
|
||||||
|
* <p>
|
||||||
|
* 该方法用于前端在配置训练参数时选择输入特征列/输出列等字段。
|
||||||
|
* 当前支持 CSV 与 Excel(xls/xlsx)两类格式,并对不支持的格式返回明确错误语义。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param datasetPath 数据集文件路径(绝对路径)
|
||||||
|
* @return 列名列表
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> parseDatasetColumns(String datasetPath) {
|
public List<String> parseDatasetColumns(String datasetPath) {
|
||||||
if (datasetPath == null || datasetPath.isBlank()) {
|
if (datasetPath == null || datasetPath.isBlank()) {
|
||||||
@ -148,6 +204,20 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
|||||||
throw new BizException("不支持的数据集格式: " + p.getFileName());
|
throw new BizException("不支持的数据集格式: " + p.getFileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传数据集并立即执行字段解析与基础检查。
|
||||||
|
* <p>
|
||||||
|
* 适用于“一步上传并预检”的页面交互,返回结构包含:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>path:保存后的数据集路径</li>
|
||||||
|
* <li>columns:解析出的列名列表</li>
|
||||||
|
* <li>warnings:字段与内容的告警信息(用于提示潜在配置错误)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param file 上传文件
|
||||||
|
* @return 预检结果
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> uploadAndInspectDataset(MultipartFile file) {
|
public Map<String, Object> uploadAndInspectDataset(MultipartFile file) {
|
||||||
String path = uploadDataset(file);
|
String path = uploadDataset(file);
|
||||||
@ -183,12 +253,25 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
|
|||||||
|
|
||||||
this.save(task);
|
this.save(task);
|
||||||
|
|
||||||
// 2. 异步调用 Python 训练
|
// 2. 调用 Python 训练
|
||||||
asyncCallTrain(task);
|
runAfterCommit(() -> self.asyncCallTrain(task));
|
||||||
|
|
||||||
return task.getTaskId();
|
return task.getTaskId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void runAfterCommit(Runnable runnable) {
|
||||||
|
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||||
|
@Override
|
||||||
|
public void afterCommit() {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> checkCapacity() {
|
public Map<String, Object> checkCapacity() {
|
||||||
long training = countActiveTraining();
|
long training = countActiveTraining();
|
||||||
|
|||||||
@ -51,6 +51,30 @@ import java.time.format.DateTimeFormatter;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目域核心服务实现。
|
||||||
|
* <p>
|
||||||
|
* 该类聚合了项目相关的高频业务能力,覆盖“项目基础信息”“拓扑解析与校验”“设备执行顺序解析”
|
||||||
|
* “画布视图数据组织”“推演初始化与运行”“工程数据 Excel 导入导出”等场景。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 主要能力边界:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>数据读取:通过 Project/Device/Material/Scenario/Event 等服务与 Mapper 获取所需数据。</li>
|
||||||
|
* <li>结构解析:解析项目 topology JSON,生成节点/边与线性执行计划,并输出问题列表。</li>
|
||||||
|
* <li>推演编排:组织推演输入上下文,计算设备顺序与材料注入信息,输出推演可用的数据结构。</li>
|
||||||
|
* <li>工程数据:支持工程 Excel 的导入/导出,用于项目初始化、备份与批量维护。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 运行与质量约束:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>健壮性:对 JSON 字段缺失、空字符串、非法引用等情况给出明确问题列表或异常语义。</li>
|
||||||
|
* <li>一致性:涉及级联删除/批量导入时,需要保证项目内相关实体的一致性与可追溯性。</li>
|
||||||
|
* <li>性能:导入导出与解析逻辑避免一次性构造超大字符串,必要处做长度截断与分段处理。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class ProjectServiceImpl
|
public class ProjectServiceImpl
|
||||||
@ -78,6 +102,19 @@ public class ProjectServiceImpl
|
|||||||
this.deviceDataParser = deviceDataParser;
|
this.deviceDataParser = deviceDataParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出全部项目数据到 Excel(xlsx)。
|
||||||
|
* <p>
|
||||||
|
* 主要用于后台管理侧的项目清单导出,字段包含项目编号、名称、描述、拓扑文本与时间字段等。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>排序:按创建时间倒序输出。</li>
|
||||||
|
* <li>长度限制:拓扑字段受 Excel 单元格长度限制,超长文本会被截断并追加省略号。</li>
|
||||||
|
* <li>异常语义:导出失败抛出运行时异常,交由上层统一异常处理机制返回。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return Excel 二进制内容
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public byte[] exportAllProjectsExcel() {
|
public byte[] exportAllProjectsExcel() {
|
||||||
log.info("exportAllProjectsExcel start");
|
log.info("exportAllProjectsExcel start");
|
||||||
@ -113,16 +150,26 @@ public class ProjectServiceImpl
|
|||||||
if (s == null) return "";
|
if (s == null) return "";
|
||||||
int max = 32767;
|
int max = 32767;
|
||||||
if (s.length() <= max) return s;
|
if (s.length() <= max) return s;
|
||||||
if (max <= 3) return s.substring(0, max);
|
|
||||||
return s.substring(0, max - 3) + "...";
|
return s.substring(0, max - 3) + "...";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
/**
|
/**
|
||||||
* 解析指定项目的拓扑结构,生成节点、边与线性计算计划
|
* 解析指定项目的拓扑结构,生成节点、边与线性计算计划。
|
||||||
|
* <p>
|
||||||
|
* topology 期望为 JSON 字符串,内部包含设备列表与连接关系等结构。
|
||||||
|
* 解析结果将包含:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>nodes:拓扑节点列表。</li>
|
||||||
|
* <li>edges:拓扑边列表。</li>
|
||||||
|
* <li>plans:线性执行计划(用于后续计算顺序/推演编排)。</li>
|
||||||
|
* <li>issues:解析过程中发现的问题列表(例如字段缺失、引用不存在等)。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
* @param projectId 项目ID
|
* @param projectId 项目ID
|
||||||
* @return TopologyParseResult
|
* @return TopologyParseResult
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public TopologyParseResult parseTopology(String projectId) {
|
public TopologyParseResult parseTopology(String projectId) {
|
||||||
log.info("parseTopology start projectId={}", projectId);
|
log.info("parseTopology start projectId={}", projectId);
|
||||||
try {
|
try {
|
||||||
@ -386,6 +433,20 @@ public class ProjectServiceImpl
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 topology 中的设备出现顺序解析设备列表。
|
||||||
|
* <p>
|
||||||
|
* 与直接按数据库字段排序不同,该方法以 topology JSON 中 devices 数组的顺序为准,
|
||||||
|
* 并将其映射为对应的设备实体列表。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>当项目不存在或 topology 为空时返回空列表。</li>
|
||||||
|
* <li>当某些 deviceId 在数据库中不存在时,会自动跳过缺失设备。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 设备列表(保持 topology 顺序)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<Device> parseDeviceOrder(String projectId) {
|
public List<Device> parseDeviceOrder(String projectId) {
|
||||||
log.info("parseDeviceOrder start projectId={}", projectId);
|
log.info("parseDeviceOrder start projectId={}", projectId);
|
||||||
@ -419,12 +480,21 @@ public class ProjectServiceImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
/**
|
/**
|
||||||
* 提取画布视图所需数据(设备、管线、边界、显示配置)
|
* 提取画布视图所需数据(设备、管线、边界、显示配置)。
|
||||||
|
* <p>
|
||||||
|
* 用于前端建模/编辑页面的初始化展示,返回结构为 Map,按前端协议组织多个子对象:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>基础信息:项目信息、拓扑基础字段。</li>
|
||||||
|
* <li>实体集合:设备、材料、事件等(按需要提供)。</li>
|
||||||
|
* <li>展示配置:画布边界、布局与扩展字段(按拓扑内容解析)。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
* @param projectId 项目ID
|
* @param projectId 项目ID
|
||||||
* @return Map 视图对象
|
* @return 画布视图对象
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public Map<String, Object> parseCanvasView(String projectId) {
|
public Map<String, Object> parseCanvasView(String projectId) {
|
||||||
log.info("parseCanvasView start projectId={}", projectId);
|
log.info("parseCanvasView start projectId={}", projectId);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -7,6 +7,12 @@ import com.yfd.business.css.service.ScenarioResultService;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景结果服务实现。
|
||||||
|
* <p>
|
||||||
|
* 结果数据由仿真/推理流程写入,本类提供基于 MyBatis-Plus 的基础 CRUD 能力与统一的数据访问入口。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class ScenarioResultServiceImpl
|
public class ScenarioResultServiceImpl
|
||||||
|
|||||||
@ -7,6 +7,12 @@ import com.yfd.business.css.service.ScenarioService;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 情景服务实现。
|
||||||
|
* <p>
|
||||||
|
* 提供情景相关的基础 CRUD 能力,以及按情景 ID 获取算法类型等轻量查询能力。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class ScenarioServiceImpl
|
public class ScenarioServiceImpl
|
||||||
|
|||||||
@ -8,9 +8,21 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仿真服务实现(占位实现)。
|
||||||
|
* <p>
|
||||||
|
* 当前实现用于打通接口链路与日志观测,尚未接入真实的仿真引擎计算逻辑。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class SimulationServiceImpl implements SimulationService {
|
public class SimulationServiceImpl implements SimulationService {
|
||||||
|
/**
|
||||||
|
* 执行一次仿真并返回结果。
|
||||||
|
*
|
||||||
|
* @param request 仿真请求参数
|
||||||
|
* @return 仿真结果
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SimulationResult runSimulation(SimulationRequest request) {
|
public SimulationResult runSimulation(SimulationRequest request) {
|
||||||
log.info("SimulationServiceImpl runSimulation start request={}", request);
|
log.info("SimulationServiceImpl runSimulation start request={}", request);
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Service 实现层。
|
||||||
|
* <p>
|
||||||
|
* 该包包含领域服务的具体实现,承载主要业务规则与流程编排,典型职责:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>业务规则落地:状态机、容量控制、参数校验、对象关联关系处理等。</li>
|
||||||
|
* <li>事务边界:在必要处声明事务,确保写操作一致性。</li>
|
||||||
|
* <li>外部调用:调用推演/训练等外部服务时,负责请求组装、返回解析与容错。</li>
|
||||||
|
* <li>数据访问:通过 Mapper 层完成持久化读写,不直接拼接 SQL。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 质量与安全约束:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>健壮性:对空值、空集合、外部返回字段做防御性处理,避免 NPE。</li>
|
||||||
|
* <li>一致性:写入前校验引用存在性,避免产生孤儿数据与不一致关联。</li>
|
||||||
|
* <li>幂等性:对外部回调/重复提交等场景具备幂等保护(如按任务ID去重)。</li>
|
||||||
|
* <li>敏感信息:日志不记录口令、token、密钥等敏感字段。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 性能约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>批量操作:大数据量导出/查询优先使用分页与批处理,避免一次性加载过多数据。</li>
|
||||||
|
* <li>长耗时任务:推演/训练等应走任务化机制并提供状态回调与超时回收。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.service.impl;
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Service(领域服务)接口层。
|
||||||
|
* <p>
|
||||||
|
* 该包定义后端核心业务能力的接口边界,是 Controller 与数据访问层之间的“业务语义层”。
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>接口表达业务语义:方法名与入参/出参体现领域含义,而非表字段操作。</li>
|
||||||
|
* <li>事务语义在实现层落地:接口侧强调“做什么”,实现侧强调“如何做”。</li>
|
||||||
|
* <li>幂等与一致性:对外接口尽量保持幂等与可预期错误语义。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 典型服务域:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>项目/场景:拓扑解析、推演输入准备、结果查询等。</li>
|
||||||
|
* <li>设备/事件/材料:基础数据维护、解析与结构化转换。</li>
|
||||||
|
* <li>训练任务:提交、容量控制、状态流转与回调处理。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>接口不要依赖 Web 层对象(HttpServletRequest 等)。</li>
|
||||||
|
* <li>接口返回值尽量明确,避免 Object/Map 泛化导致契约不清。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.service;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 业务工具类。
|
||||||
|
* <p>
|
||||||
|
* 该包用于放置与 business-css 业务强相关的工具能力,例如:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>数据解析:对设备数据、属性字段、拓扑结构等进行解析与归一化。</li>
|
||||||
|
* <li>转换与拼装:将 Domain/DTO/Model 在不同层之间做结构化转换。</li>
|
||||||
|
* <li>校验辅助:对复杂字段格式、引用关系、一致性约束做统一校验。</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 约定:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>工具类不应持有全局可变状态,避免并发与复用风险。</li>
|
||||||
|
* <li>与框架通用工具(framework 模块)区分,避免重复实现通用能力。</li>
|
||||||
|
* <li>异常提示应可定位输入问题,便于测试与排障。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
package com.yfd.business.css.utils;
|
||||||
@ -73,20 +73,6 @@ public class LoginController {
|
|||||||
user.getPassword());
|
user.getPassword());
|
||||||
|
|
||||||
|
|
||||||
// 是否需要验证码不需要改成false
|
|
||||||
boolean hascode = false;//true;
|
|
||||||
if (hascode) {
|
|
||||||
// 查询验证码
|
|
||||||
String code = webConfig.loginuserCache().get(user.getUuid());
|
|
||||||
// 清除验证码
|
|
||||||
webConfig.loginuserCache().remove(user.getUuid());
|
|
||||||
if (StrUtil.isBlank(code)) {
|
|
||||||
return ResponseResult.error("验证码不存在或已过期");
|
|
||||||
}
|
|
||||||
if (StrUtil.isBlank(user.getCode()) || !user.getCode().equalsIgnoreCase(code)) {
|
|
||||||
return ResponseResult.error("验证码错误");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//如果认证通过了,使用userId生成token token存入ResponseResult返回
|
//如果认证通过了,使用userId生成token token存入ResponseResult返回
|
||||||
UsernamePasswordAuthenticationToken authenticationToken =
|
UsernamePasswordAuthenticationToken authenticationToken =
|
||||||
new UsernamePasswordAuthenticationToken(user.getUsername(),
|
new UsernamePasswordAuthenticationToken(user.getUsername(),
|
||||||
|
|||||||
@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019-2020 Zheng Jie
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.yfd.platform.utils;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.SecretKeyFactory;
|
|
||||||
import javax.crypto.spec.DESKeySpec;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加密
|
|
||||||
* @author
|
|
||||||
* @date 2018-11-23
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class EncryptUtils {
|
|
||||||
|
|
||||||
private static final String STR_PARAM = "Passw0rd";
|
|
||||||
|
|
||||||
private static Cipher cipher;
|
|
||||||
|
|
||||||
private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));
|
|
||||||
|
|
||||||
private static DESKeySpec getDesKeySpec(String source) throws Exception {
|
|
||||||
if (source == null || source.length() == 0){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
|
|
||||||
String strKey = "Passw0rd";
|
|
||||||
return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对称加密
|
|
||||||
*/
|
|
||||||
public static String desEncrypt(String source) throws Exception {
|
|
||||||
DESKeySpec desKeySpec = getDesKeySpec(source);
|
|
||||||
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
|
|
||||||
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
|
|
||||||
return byte2hex(
|
|
||||||
cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对称解密
|
|
||||||
*/
|
|
||||||
public static String desDecrypt(String source) throws Exception {
|
|
||||||
byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8));
|
|
||||||
DESKeySpec desKeySpec = getDesKeySpec(source);
|
|
||||||
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
|
|
||||||
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, IV);
|
|
||||||
byte[] retByte = cipher.doFinal(src);
|
|
||||||
return new String(retByte);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String byte2hex(byte[] inStr) {
|
|
||||||
String stmp;
|
|
||||||
StringBuilder out = new StringBuilder(inStr.length * 2);
|
|
||||||
for (byte b : inStr) {
|
|
||||||
stmp = Integer.toHexString(b & 0xFF);
|
|
||||||
if (stmp.length() == 1) {
|
|
||||||
// 如果是0至F的单位字符串,则添加0
|
|
||||||
out.append("0").append(stmp);
|
|
||||||
} else {
|
|
||||||
out.append(stmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] hex2byte(byte[] b) {
|
|
||||||
int size = 2;
|
|
||||||
if ((b.length % size) != 0){
|
|
||||||
throw new IllegalArgumentException("长度不是偶数");
|
|
||||||
}
|
|
||||||
byte[] b2 = new byte[b.length / 2];
|
|
||||||
for (int n = 0; n < b.length; n += size) {
|
|
||||||
String item = new String(b, n, 2);
|
|
||||||
b2[n / 2] = (byte) Integer.parseInt(item, 16);
|
|
||||||
}
|
|
||||||
return b2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -38,7 +38,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -285,61 +284,6 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断两个文件是否相同
|
|
||||||
*/
|
|
||||||
public static boolean check(File file1, File file2) {
|
|
||||||
String img1Md5 = getMd5(file1);
|
|
||||||
String img2Md5 = getMd5(file2);
|
|
||||||
return img1Md5.equals(img2Md5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断两个文件是否相同
|
|
||||||
*/
|
|
||||||
public static boolean check(String file1Md5, String file2Md5) {
|
|
||||||
return file1Md5.equals(file2Md5);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] getByte(File file) {
|
|
||||||
// 得到文件长度
|
|
||||||
byte[] b = new byte[(int) file.length()];
|
|
||||||
try {
|
|
||||||
InputStream in = new FileInputStream(file);
|
|
||||||
try {
|
|
||||||
System.out.println(in.read(b));
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getMd5(byte[] bytes) {
|
|
||||||
// 16进制字符
|
|
||||||
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
|
||||||
try {
|
|
||||||
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
|
|
||||||
mdTemp.update(bytes);
|
|
||||||
byte[] md = mdTemp.digest();
|
|
||||||
int j = md.length;
|
|
||||||
char[] str = new char[j * 2];
|
|
||||||
int k = 0;
|
|
||||||
// 移位 输出字符串
|
|
||||||
for (byte byte0 : md) {
|
|
||||||
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
|
|
||||||
str[k++] = hexDigits[byte0 & 0xf];
|
|
||||||
}
|
|
||||||
return new String(str);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载文件
|
* 下载文件
|
||||||
*
|
*
|
||||||
@ -391,8 +335,4 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
|
|||||||
fis.close();
|
fis.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getMd5(File file) {
|
|
||||||
return getMd5(getByte(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,6 @@ import org.apache.commons.codec.binary.Base64;
|
|||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
|
||||||
import java.security.interfaces.RSAPublicKey;
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
|
||||||
@ -16,57 +14,6 @@ import java.security.spec.X509EncodedKeySpec;
|
|||||||
**/
|
**/
|
||||||
public class RsaUtils {
|
public class RsaUtils {
|
||||||
|
|
||||||
private static final String SRC = "123456";
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
|
||||||
System.out.println("\n");
|
|
||||||
RsaKeyPair keyPair = generateKeyPair();
|
|
||||||
System.out.println("公钥:" + keyPair.getPublicKey());
|
|
||||||
System.out.println("私钥:" + keyPair.getPrivateKey());
|
|
||||||
System.out.println("\n");
|
|
||||||
test1(keyPair);
|
|
||||||
System.out.println("\n");
|
|
||||||
test2(keyPair);
|
|
||||||
System.out.println("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 公钥加密私钥解密
|
|
||||||
*/
|
|
||||||
private static void test1(RsaKeyPair keyPair) throws Exception {
|
|
||||||
System.out.println("***************** 公钥加密私钥解密开始 *****************");
|
|
||||||
String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
|
|
||||||
String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
|
|
||||||
System.out.println("加密前:" + RsaUtils.SRC);
|
|
||||||
System.out.println("加密后:" + text1);
|
|
||||||
System.out.println("解密后:" + text2);
|
|
||||||
if (RsaUtils.SRC.equals(text2)) {
|
|
||||||
System.out.println("解密字符串和原始字符串一致,解密成功");
|
|
||||||
} else {
|
|
||||||
System.out.println("解密字符串和原始字符串不一致,解密失败");
|
|
||||||
}
|
|
||||||
System.out.println("***************** 公钥加密私钥解密结束 *****************");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 私钥加密公钥解密
|
|
||||||
* @throws Exception /
|
|
||||||
*/
|
|
||||||
private static void test2(RsaKeyPair keyPair) throws Exception {
|
|
||||||
System.out.println("***************** 私钥加密公钥解密开始 *****************");
|
|
||||||
String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);
|
|
||||||
String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
|
|
||||||
System.out.println("加密前:" + RsaUtils.SRC);
|
|
||||||
System.out.println("加密后:" + text1);
|
|
||||||
System.out.println("解密后:" + text2);
|
|
||||||
if (RsaUtils.SRC.equals(text2)) {
|
|
||||||
System.out.println("解密字符串和原始字符串一致,解密成功");
|
|
||||||
} else {
|
|
||||||
System.out.println("解密字符串和原始字符串不一致,解密失败");
|
|
||||||
}
|
|
||||||
System.out.println("***************** 私钥加密公钥解密结束 *****************");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公钥解密
|
* 公钥解密
|
||||||
*
|
*
|
||||||
@ -137,45 +84,4 @@ public class RsaUtils {
|
|||||||
byte[] result = cipher.doFinal(text.getBytes());
|
byte[] result = cipher.doFinal(text.getBytes());
|
||||||
return Base64.encodeBase64String(result);
|
return Base64.encodeBase64String(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建RSA密钥对
|
|
||||||
*
|
|
||||||
* @return /
|
|
||||||
* @throws NoSuchAlgorithmException /
|
|
||||||
*/
|
|
||||||
public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
|
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
|
||||||
keyPairGenerator.initialize(1024);
|
|
||||||
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
|
||||||
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
|
|
||||||
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
|
|
||||||
String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
|
|
||||||
String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
|
|
||||||
return new RsaKeyPair(publicKeyString, privateKeyString);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RSA密钥对对象
|
|
||||||
*/
|
|
||||||
public static class RsaKeyPair {
|
|
||||||
|
|
||||||
private final String publicKey;
|
|
||||||
private final String privateKey;
|
|
||||||
|
|
||||||
public RsaKeyPair(String publicKey, String privateKey) {
|
|
||||||
this.publicKey = publicKey;
|
|
||||||
this.privateKey = privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPublicKey() {
|
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrivateKey() {
|
|
||||||
return privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user