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