15 changed files with 138 additions and 659 deletions
-
3.gitignore
-
7doc/a8k移液泵优化方案.md
-
355doc/代码框架.md
-
9doc/代码缩写.md
-
4src/main/java/a8k/a8kproj/A8kIdCardDataParseService.java
-
6src/main/java/a8k/controler/api/v1/app/data/A8kProjectInfoControler.java
-
144src/main/java/a8k/controler/filemgr/FileMgrController.java
-
6src/main/java/a8k/service/app/appdata/ProjInfoMgrService.java
-
7src/main/java/a8k/service/dao/ProjectBaseInfoDao.java
-
15src/main/java/a8k/service/test/MainflowCtrlTestService.java
-
13src/main/java/a8k/service/test/TestStateMgrService.java
-
75src/main/java/a8k/utils/ZCSVUtils.java
-
0src/main/resources/optdata/project_base_info.csv
-
81src/main/resources/templates/filemgr/index.html
@ -1,355 +0,0 @@ |
|||||
/* |
|
||||
* |
|
||||
* while(!isWaitingForStop()){ |
|
||||
* if(当前样本处理完成){ |
|
||||
* if(入料口有样本){ |
|
||||
* 扫描样本 && 判断耗材是否充足 |
|
||||
* 如果耗材不足, |
|
||||
* 提示用户(抛出提示事件), |
|
||||
* 暂停设备(退出线程(抛出pause异常)) |
|
||||
* 如果扫描样本有误 |
|
||||
* 提示用户 |
|
||||
* 暂停设备 |
|
||||
* } |
|
||||
* } |
|
||||
* else{ |
|
||||
* 处理样本 |
|
||||
* } |
|
||||
* } |
|
||||
* |
|
||||
* try{ |
|
||||
* |
|
||||
* }catch(PauseException e){ |
|
||||
* //pauseSelf |
|
||||
* //抛出事件 |
|
||||
* } |
|
||||
* |
|
||||
* if(event){ |
|
||||
* pauseSampleProcessThread(); |
|
||||
* } |
|
||||
* |
|
||||
*/ |
|
||||
|
|
||||
|
|
||||
经验: |
|
||||
动作与赋值分开 |
|
||||
当一组动作完全执行完之后,再赋值给状态,这样就可以使得动作在执行过程中任意时间点都可以被打断。 |
|
||||
|
|
||||
|
|
||||
while(true){ |
|
||||
TubeCfg cfg = getTubeCfg(i); |
|
||||
moveTubeHolderProcessPos |
|
||||
if(tubeType == xxx) |
|
||||
takeTube |
|
||||
shakeTube(times,degree) |
|
||||
takeHap |
|
||||
move |
|
||||
else if(){ |
|
||||
... |
|
||||
|
|
||||
} |
|
||||
pausePoint; |
|
||||
} |
|
||||
|
|
||||
问题: |
|
||||
有两种暂停状态: |
|
||||
一种是复位势暂停。 |
|
||||
一种是保持现状势暂停。(前提是循环中) |
|
||||
|
|
||||
pause/stop |
|
||||
|
|
||||
|
|
||||
动作划分为状态机 |
|
||||
scan{ |
|
||||
. |
|
||||
. |
|
||||
. |
|
||||
. |
|
||||
} |
|
||||
|
|
||||
|
|
||||
状态机->enter |
|
||||
状态机->enter |
|
||||
|
|
||||
|
|
||||
{ |
|
||||
IDLE |
|
||||
{ |
|
||||
if(state.eq(wokring)){ |
|
||||
state.changeTo(scanPrepare) |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
SCANPREPARE |
|
||||
{ |
|
||||
enterState |
|
||||
exitState |
|
||||
|
|
||||
if(ioTrigger){ |
|
||||
moveTubeHolderTo |
|
||||
waitForIoTrigger |
|
||||
changeStateTo(scanTubeHolderType) |
|
||||
if(error){ |
|
||||
changeStateToError |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
SCANHODLER |
|
||||
{ |
|
||||
move |
|
||||
scan |
|
||||
|
|
||||
|
|
||||
|
|
||||
} |
|
||||
SCAN |
|
||||
|
|
||||
PAUSE |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
|
|
||||
} |
|
||||
|
|
||||
|
|
||||
``` |
|
||||
用户PAUSE --> 暂停,恢复,全局状态的变更。 |
|
||||
遇到错误 --> 通知,记录,清空工作位。无需恢复。 |
|
||||
状态机 --> |
|
||||
修改全局状态。 |
|
||||
记录。 |
|
||||
执行动作。 |
|
||||
|
|
||||
|
|
||||
|
|
||||
前端接口: |
|
||||
pause; |
|
||||
continue; |
|
||||
|
|
||||
|
|
||||
全局都有哪些状态? |
|
||||
1. 类似于配置, |
|
||||
初始化 |
|
||||
运行过程中修改(前端提交) |
|
||||
|
|
||||
2. 运行过程控制,记录当前设备运行到哪一步,以便执行下一步。 |
|
||||
|
|
||||
|
|
||||
步骤与暂停之间的关系。 |
|
||||
步骤与恢复? |
|
||||
|
|
||||
|
|
||||
步骤与停止之间的关系。 |
|
||||
|
|
||||
步骤 |
|
||||
步骤 |
|
||||
步骤 |
|
||||
步骤 |
|
||||
步骤 |
|
||||
暂停 |
|
||||
步骤 |
|
||||
|
|
||||
---------------------------------------------- |
|
||||
每一步都有几种处理方法 |
|
||||
1. firstEnter |
|
||||
checkCondition是否满足 |
|
||||
2. finalExit |
|
||||
3. loop |
|
||||
4. pause |
|
||||
5. continue |
|
||||
|
|
||||
waitting_for_take_sample |
|
||||
|
|
||||
pause |
|
||||
stepPause; |
|
||||
stepContinue; |
|
||||
|
|
||||
stop |
|
||||
|
|
||||
|
|
||||
协作? |
|
||||
全局状态 |
|
||||
|
|
||||
|
|
||||
|
|
||||
{ |
|
||||
|
|
||||
take_tip |
|
||||
take_xxx |
|
||||
put_xxx |
|
||||
waitting_for |
|
||||
onPause{ |
|
||||
.... |
|
||||
} |
|
||||
onStop{ |
|
||||
... |
|
||||
} |
|
||||
|
|
||||
pauseCondtion{ |
|
||||
... |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
{ |
|
||||
pause... |
|
||||
} |
|
||||
|
|
||||
pause_point |
|
||||
|
|
||||
``` |
|
||||
|
|
||||
|
|
||||
``` |
|
||||
服务的提供者, |
|
||||
服务的使用者, |
|
||||
|
|
||||
相互配合呢? |
|
||||
|
|
||||
triggerHasTakeSample |
|
||||
doNext |
|
||||
|
|
||||
|
|
||||
---处理--- |
|
||||
| | |
|
||||
| --搬运--(等待) |
|
||||
| | |
|
||||
扫描与预处理(等待) 抛出事件。 |
|
||||
|
|
||||
|
|
||||
机械臂 |
|
||||
takeSampleEnd |
|
||||
|
|
||||
---------------------------- |
|
||||
暂停: |
|
||||
waitingForPause,不需要弹出,只有单次循环结束 |
|
||||
停止: |
|
||||
break? |
|
||||
异常: |
|
||||
exception? |
|
||||
|
|
||||
|
|
||||
第一假如需要允许暂停,则需要将步骤进行拆分,暂停点作为一步。 |
|
||||
|
|
||||
|
|
||||
机械动作的暂停,涉及到,暂停和暂停恢复,不能简单的在代码中添加一个pause就可以的。 |
|
||||
|
|
||||
|
|
||||
协作 |
|
||||
|
|
||||
机械臂异常--> 中断 --> 退出。 |
|
||||
扫描与预处理 --> break = WaittingForFlag(stop) |
|
||||
|
|
||||
|
|
||||
---------------------- |
|
||||
急停: |
|
||||
不恢复--->threadStop-->触发线程异常 |
|
||||
|
|
||||
|
|
||||
停止: |
|
||||
抛出异常 |
|
||||
程序打断 |
|
||||
|
|
||||
打断当前状态,调用状态本身的onStop(); |
|
||||
stop---> |
|
||||
|
|
||||
停止: |
|
||||
不想用异常 |
|
||||
stopPoint. |
|
||||
stopPoint. |
|
||||
stopPoint(if(isStopFlag) throwException) |
|
||||
|
|
||||
onStop{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
waittingForState(其他线程,用户,等触发的逻辑){} |
|
||||
|
|
||||
|
|
||||
取试管 |
|
||||
if(stopFlag){ |
|
||||
.... |
|
||||
.... |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if(stopFlag){ |
|
||||
.... |
|
||||
.... |
|
||||
.... |
|
||||
} |
|
||||
putTubeBak |
|
||||
... |
|
||||
... |
|
||||
... |
|
||||
... |
|
||||
... |
|
||||
.. |
|
||||
|
|
||||
stop() |
|
||||
|
|
||||
|
|
||||
公共区+相互等待 |
|
||||
|
|
||||
取样的时候,机械臂线程出现异常 |
|
||||
机械臂退出 |
|
||||
尝试一次复位 |
|
||||
|
|
||||
|
|
||||
预处理线程 |
|
||||
stop(); |
|
||||
直接执行,抛出异常。 |
|
||||
|
|
||||
|
|
||||
|
|
||||
stop |
|
||||
stop |
|
||||
stop |
|
||||
|
|
||||
isStoped |
|
||||
isStoped |
|
||||
isStoped |
|
||||
|
|
||||
前端只触发状态的改变,而不直接触发具体动作。这样可以保证让某些接口可以重复调用而不出错。 |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
stoped...... |
|
||||
stoped...... |
|
||||
stoped...... |
|
||||
|
|
||||
耗材不足 |
|
||||
---> 暂停补充耗材 |
|
||||
pause: |
|
||||
先抛出事件,再执行暂停动作 |
|
||||
|
|
||||
|
|
||||
|
|
||||
--------------------------------------------- |
|
||||
1. 耗材不足导致的暂停(任何阶段,例如取耗材失败) |
|
||||
2. 用户信息获取失败导致的暂停(扫描阶段) |
|
||||
|
|
||||
|
|
||||
设备继续运行下 |
|
||||
设备触发耗材不足事件 |
|
||||
在触发点等待waitingForStop |
|
||||
|
|
||||
|
|
||||
如果是计数不对,则设置错误标志位。 |
|
||||
如果耗材数量不足,则在处理当前试管前,触发stop |
|
||||
|
|
||||
// |
|
||||
状态: |
|
||||
1. 满足自己运行的状态。 |
|
||||
2. 满足其他服务运行的状态。 |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
``` |
|
||||
|
|
||||
|
|
||||
``` |
|
||||
``` |
|
@ -1,9 +0,0 @@ |
|||||
|
|
||||
``` |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
``` |
|
@ -1,144 +0,0 @@ |
|||||
//package a8k.controler.filemgr; |
|
||||
// |
|
||||
// |
|
||||
//import a8k.constant.FilePathConstant; |
|
||||
//import a8k.type.appret.ApiV1Ret; |
|
||||
//import io.swagger.v3.oas.annotations.tags.Tag; |
|
||||
//import lombok.extern.slf4j.Slf4j; |
|
||||
//import org.springframework.core.io.FileSystemResource; |
|
||||
//import org.springframework.core.io.Resource; |
|
||||
//import org.springframework.http.ContentDisposition; |
|
||||
//import org.springframework.http.HttpHeaders; |
|
||||
//import org.springframework.http.ResponseEntity; |
|
||||
//import org.springframework.stereotype.Controller; |
|
||||
//import org.springframework.web.bind.annotation.*; |
|
||||
//import org.springframework.web.multipart.MultipartFile; |
|
||||
// |
|
||||
//import java.io.File; |
|
||||
//import java.io.IOException; |
|
||||
// |
|
||||
//@Tag(name = "文件管理") |
|
||||
//@Slf4j |
|
||||
//@Controller |
|
||||
//@RequestMapping(value = "/files") |
|
||||
//public class FileMgrControler { |
|
||||
// |
|
||||
// @GetMapping("") |
|
||||
// public String index() { |
|
||||
// log.info("======================================================================================="); |
|
||||
// return "files/index.html"; |
|
||||
// } |
|
||||
// |
|
||||
// @GetMapping("/download/{path}") |
|
||||
// @ResponseBody |
|
||||
// public ResponseEntity<Resource> download(@PathVariable String path) { |
|
||||
// String realpath = String.format(FilePathConstant.FILE_DOWNLOAD_PATH + "/%s", path); |
|
||||
// String contentDisposition = ContentDisposition |
|
||||
// .builder("attachment") |
|
||||
// .filename(path) // Use the original filename |
|
||||
// .build().toString(); |
|
||||
// return ResponseEntity.ok() |
|
||||
// .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) |
|
||||
// .body(new FileSystemResource(realpath)); |
|
||||
// } |
|
||||
// |
|
||||
// |
|
||||
// @PostMapping("/upload") |
|
||||
// @ResponseBody |
|
||||
// public String handleFileUpload(@RequestParam("file") MultipartFile file) { |
|
||||
// String uploadDir = FilePathConstant.FILE_UPLOAD_PATH; |
|
||||
// File updatefilepath = new File(uploadDir); |
|
||||
// if (!updatefilepath.exists()) { |
|
||||
// updatefilepath.mkdirs(); |
|
||||
// } |
|
||||
// |
|
||||
// try { |
|
||||
// file.transferTo(new File(updatefilepath.getAbsolutePath() + file.getOriginalFilename())); |
|
||||
// return "success"; |
|
||||
// } catch (IOException e) { |
|
||||
// return "fail"; |
|
||||
// } |
|
||||
// } |
|
||||
// |
|
||||
//} |
|
||||
// src/main/java/a8k/controller/FileMgrController.java |
|
||||
package a8k.controler.filemgr; |
|
||||
|
|
||||
import a8k.service.app.appdata.FileMgrService; |
|
||||
import cn.hutool.core.net.URLDecoder; |
|
||||
import jakarta.annotation.Resource; |
|
||||
import jakarta.servlet.http.HttpServletRequest; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.springframework.core.io.FileSystemResource; |
|
||||
import org.springframework.http.ContentDisposition; |
|
||||
import org.springframework.http.HttpHeaders; |
|
||||
import org.springframework.http.ResponseEntity; |
|
||||
import org.springframework.stereotype.Controller; |
|
||||
import org.springframework.ui.Model; |
|
||||
import org.springframework.web.bind.annotation.*; |
|
||||
import org.springframework.web.multipart.MultipartFile; |
|
||||
|
|
||||
import java.io.File; |
|
||||
import java.nio.charset.StandardCharsets; |
|
||||
import java.nio.file.Path; |
|
||||
import java.nio.file.Paths; |
|
||||
import java.util.List; |
|
||||
|
|
||||
@Controller |
|
||||
@Slf4j |
|
||||
@RequestMapping("/filemgr") |
|
||||
public class FileMgrController { |
|
||||
|
|
||||
|
|
||||
@Resource |
|
||||
FileMgrService fileMgrService; |
|
||||
|
|
||||
record FileGroup(String name, List<FileInfo> files) { |
|
||||
} |
|
||||
|
|
||||
record FileInfo(String filePath, String name) { |
|
||||
} |
|
||||
|
|
||||
|
|
||||
FileGroup buidOptFileGroup() { |
|
||||
List<FileInfo> files = fileMgrService.getOptReportList().stream() |
|
||||
.map(fileName -> new FileInfo(fileMgrService.getOptReportFilePath(fileName), fileName)) |
|
||||
.toList(); |
|
||||
return new FileGroup("光学报告", files); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
@GetMapping("") |
|
||||
public String fileDownloadPage(Model model) { |
|
||||
// Mock data for file groups and files |
|
||||
List<FileGroup> fileGroups = List.of( |
|
||||
buidOptFileGroup() |
|
||||
); |
|
||||
model.addAttribute("fileGroups", fileGroups); |
|
||||
return "filemgr/index.html"; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
@GetMapping("/download/**") |
|
||||
@ResponseBody |
|
||||
public ResponseEntity<org.springframework.core.io.Resource> download(HttpServletRequest request) { |
|
||||
String path = request.getRequestURI().substring("/filemgr/download/".length()); |
|
||||
//转义地址中空格,TODO:支持处理中文文件名 |
|
||||
// path = path.replaceAll("%20", " "); |
|
||||
path = URLDecoder.decode(path, StandardCharsets.UTF_8); |
|
||||
|
|
||||
|
|
||||
String fileName = Paths.get(path).getFileName().toString(); |
|
||||
log.info("Download file:{} path:{}", fileName, path); |
|
||||
|
|
||||
File file = new File(path); |
|
||||
|
|
||||
String contentDisposition = ContentDisposition |
|
||||
.builder("attachment") |
|
||||
.filename(fileName, StandardCharsets.UTF_8) // Use the original filename |
|
||||
.build().toString(); |
|
||||
return ResponseEntity.ok() |
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) |
|
||||
.body(new FileSystemResource(file.getAbsolutePath())); |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,75 @@ |
|||||
|
package a8k.utils; |
||||
|
|
||||
|
|
||||
|
import a8k.service.dao.type.ProjectBaseInfo; |
||||
|
import cn.hutool.core.text.csv.CsvUtil; |
||||
|
import cn.hutool.core.text.csv.CsvWriter; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.nio.charset.Charset; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class ZCSVUtils { |
||||
|
|
||||
|
|
||||
|
public static <T> void writeCSV(String filePath, Class<T> type, List<T> objects) { |
||||
|
File file = new File(filePath); |
||||
|
try ( |
||||
|
CsvWriter csvWriter = CsvUtil.getWriter(file.getAbsoluteFile(), StandardCharsets.UTF_8); |
||||
|
) { |
||||
|
|
||||
|
List<String> headers = new ArrayList<>(); |
||||
|
var fields = type.getDeclaredFields(); |
||||
|
for (var field : fields) { |
||||
|
headers.add(field.getName()); |
||||
|
} |
||||
|
|
||||
|
String[] headersArray = new String[headers.size()]; |
||||
|
headers.toArray(headersArray); |
||||
|
|
||||
|
|
||||
|
csvWriter.write(headersArray); |
||||
|
|
||||
|
for (var object : objects) { |
||||
|
String[] row = new String[fields.length]; |
||||
|
for (int i = 0; i < fields.length; i++) { |
||||
|
fields[i].setAccessible(true); |
||||
|
row[i] = String.format("%s", fields[i].get(object)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
log.info("row: {}", row); |
||||
|
csvWriter.write(row); |
||||
|
} |
||||
|
csvWriter.flush(); |
||||
|
} catch (IllegalAccessException e) { |
||||
|
throw new RuntimeException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static <T> List<T> readCSV(String filePath, Class<T> clazz) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
List<ProjectBaseInfo> objects = new ArrayList<>(); |
||||
|
|
||||
|
for (int i = 0; i < 10; i++) { |
||||
|
ProjectBaseInfo projectBaseInfo = new ProjectBaseInfo(); |
||||
|
projectBaseInfo.projId = i; |
||||
|
projectBaseInfo.projName = "projName" + i; |
||||
|
projectBaseInfo.projShortName = "projShortName" + i; |
||||
|
projectBaseInfo.subProjNum = i; |
||||
|
projectBaseInfo.reactionTemperature = i; |
||||
|
projectBaseInfo.color = "color" + i; |
||||
|
objects.add(projectBaseInfo); |
||||
|
} |
||||
|
writeCSV("tmp/test.csv", ProjectBaseInfo.class, objects); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -1,81 +0,0 @@ |
|||||
<!-- src/main/resources/templates/filemgr/index.html --> |
|
||||
<!DOCTYPE html> |
|
||||
<html lang="en"> |
|
||||
<head> |
|
||||
<meta charset="UTF-8"> |
|
||||
<title>文件管理</title> |
|
||||
<link href="/background/js/bootstrap.min.css" rel="stylesheet"> |
|
||||
<style> |
|
||||
body { |
|
||||
padding: 20px; |
|
||||
} |
|
||||
h1 { |
|
||||
margin-bottom: 20px; |
|
||||
} |
|
||||
.file-group { |
|
||||
margin-bottom: 20px; |
|
||||
} |
|
||||
.file-group h2 { |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
.file-group .file { |
|
||||
margin-bottom: 5px; |
|
||||
} |
|
||||
.file-group .file button { |
|
||||
width: 100%; |
|
||||
} |
|
||||
.download-title { |
|
||||
font-size: 2em; |
|
||||
color: #007bff; |
|
||||
text-align: left; |
|
||||
margin-bottom: 30px; |
|
||||
} |
|
||||
</style> |
|
||||
</head> |
|
||||
<body> |
|
||||
<div class="container"> |
|
||||
<h1 class="download-title">文件下载</h1> |
|
||||
<div th:each="group : ${fileGroups}" class="file-group"> |
|
||||
<h2 th:text="${group.name}" class="text-primary"></h2> |
|
||||
<div th:each="file : ${group.files}" class="file"> |
|
||||
<a th:href="@{'/filemgr/download/' + ${file.filePath}}" class="btn btn-outline-primary download-link"> |
|
||||
<span th:text="${file.name}"></span> |
|
||||
</a> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<!-- Notification Modal --> |
|
||||
<div class="modal fade" id="notificationModal" tabindex="-1" role="dialog" aria-labelledby="notificationModalLabel" aria-hidden="true"> |
|
||||
<div class="modal-dialog" role="document"> |
|
||||
<div class="modal-content"> |
|
||||
<div class="modal-header"> |
|
||||
<h5 class="modal-title" id="notificationModalLabel">通知</h5> |
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> |
|
||||
<span aria-hidden="true">×</span> |
|
||||
</button> |
|
||||
</div> |
|
||||
<div class="modal-body"> |
|
||||
文件下载成功 |
|
||||
</div> |
|
||||
<div class="modal-footer"> |
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<script src="/background/js/jquery-3.5.1.min.js"></script> |
|
||||
<script src="/background/js/bootstrap.min.js"></script> |
|
||||
<script> |
|
||||
document.addEventListener('DOMContentLoaded', function() { |
|
||||
var downloadLinks = document.querySelectorAll('.download-link'); |
|
||||
downloadLinks.forEach(function(link) { |
|
||||
link.addEventListener('click', function() { |
|
||||
$('#notificationModal').modal('show'); |
|
||||
}); |
|
||||
}); |
|
||||
}); |
|
||||
</script> |
|
||||
</body> |
|
||||
</html> |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue