feat: 新增重置控制端的功能;前端可以显示提交记录 fix: 修复资源数据库sha256计算问题;修复资源数据库无法上传的问题

This commit is contained in:
SikongJueluo 2025-08-20 10:20:30 +08:00
parent c8444d1d4e
commit ec84eeeaa4
No known key found for this signature in database
9 changed files with 4143 additions and 56 deletions

4034
nohup.out Normal file

File diff suppressed because it is too large Load Diff

View File

@ -17,4 +17,13 @@ public class String
return new string(charArray); return new string(charArray);
} }
public static string BytesToString(byte[] bytes, string separator = "")
{
return BitConverter.ToString(bytes).Replace("-", separator.ToString());
}
public static string BytesToBase64(byte[] bytes)
{
return Convert.ToBase64String(bytes);
}
} }

View File

@ -37,10 +37,6 @@ public class ResourceController : ControllerBase
if (string.IsNullOrWhiteSpace(request.ResourceType) || file == null) if (string.IsNullOrWhiteSpace(request.ResourceType) || file == null)
return BadRequest("资源类型、资源用途和文件不能为空"); return BadRequest("资源类型、资源用途和文件不能为空");
// 验证资源用途
if (request.ResourcePurpose != ResourcePurpose.Template && request.ResourcePurpose != ResourcePurpose.User)
return BadRequest($"无效的资源用途: {request.ResourcePurpose}");
// 模板资源需要管理员权限 // 模板资源需要管理员权限
if (request.ResourcePurpose == ResourcePurpose.Template && !User.IsInRole("Admin")) if (request.ResourcePurpose == ResourcePurpose.Template && !User.IsInRole("Admin"))
return Forbid("只有管理员可以添加模板资源"); return Forbid("只有管理员可以添加模板资源");

View File

@ -135,7 +135,8 @@ public class ResourceManager
// 验证资源用途 // 验证资源用途
if (resourcePurpose != ResourcePurpose.Template && if (resourcePurpose != ResourcePurpose.Template &&
resourcePurpose != ResourcePurpose.User) resourcePurpose != ResourcePurpose.User &&
resourcePurpose != ResourcePurpose.Homework)
{ {
logger.Error($"无效的资源用途: {resourcePurpose}"); logger.Error($"无效的资源用途: {resourcePurpose}");
return new(new Exception($"无效的资源用途: {resourcePurpose}")); return new(new Exception($"无效的资源用途: {resourcePurpose}"));
@ -149,7 +150,8 @@ public class ResourceManager
} }
// 计算数据的SHA256 // 计算数据的SHA256
var sha256 = SHA256.HashData(data).ToString(); var sha256Bytes = SHA256.HashData(data);
var sha256 = Common.String.BytesToBase64(sha256Bytes);
if (string.IsNullOrEmpty(sha256)) if (string.IsNullOrEmpty(sha256))
{ {
logger.Error($"SHA256计算失败"); logger.Error($"SHA256计算失败");

View File

@ -429,7 +429,7 @@ public class Jtag
if (!MsgBus.IsRunning) if (!MsgBus.IsRunning)
return new(new Exception("Message Bus not Working!")); return new(new Exception("Message Bus not Working!"));
var retPack = await MsgBus.UDPServer.WaitForDataAsync(address, 0, port); var retPack = await MsgBus.UDPServer.WaitForDataAsync(this.ep, 0, this.timeout);
if (!retPack.IsSuccessful || !retPack.Value.IsSuccessful) if (!retPack.IsSuccessful || !retPack.Value.IsSuccessful)
return new(new Exception("Send address package failed")); return new(new Exception("Send address package failed"));

View File

@ -183,6 +183,21 @@ public sealed class UDPClientPool
return await Task.Run(() => { return SendDataPack(endPoint, pkg); }); return await Task.Run(() => { return SendDataPack(endPoint, pkg); });
} }
/// <summary>
/// 发送重置信号
/// </summary>
/// <param name="endPoint">IP端点IP地址与端口</param>
/// <returns>是否成功</returns>
public async static ValueTask<bool> SendResetSignal(IPEndPoint endPoint)
{
return await Task.Run(() =>
{
return SendAddrPack(
endPoint,
new WebProtocol.SendAddrPackage(BurstType.FixedBurst, 0, true, 0, 0xF0F0F0F0));
});
}
/// <summary> /// <summary>
/// 读取设备地址数据 /// 读取设备地址数据
/// </summary> /// </summary>
@ -219,8 +234,7 @@ public sealed class UDPClientPool
if (!MsgBus.IsRunning) if (!MsgBus.IsRunning)
return new(new Exception("Message Bus not Working!")); return new(new Exception("Message Bus not Working!"));
var retPack = await MsgBus.UDPServer.WaitForDataAsync( var retPack = await MsgBus.UDPServer.WaitForDataAsync(endPoint, taskID, timeout);
endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
if (!retPack.IsSuccessful) return new(retPack.Error); if (!retPack.IsSuccessful) return new(retPack.Error);
else if (!retPack.Value.IsSuccessful) else if (!retPack.Value.IsSuccessful)
return new(new Exception("Send address package failed")); return new(new Exception("Send address package failed"));
@ -389,8 +403,7 @@ public sealed class UDPClientPool
if (!ret) return new(new Exception($"Send address package failed at segment {i}!")); if (!ret) return new(new Exception($"Send address package failed at segment {i}!"));
// Wait for data response // Wait for data response
var retPack = await MsgBus.UDPServer.WaitForDataAsync( var retPack = await MsgBus.UDPServer.WaitForDataAsync(endPoint, taskID, timeout);
endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
if (!retPack.IsSuccessful) return new(retPack.Error); if (!retPack.IsSuccessful) return new(retPack.Error);
if (!retPack.Value.IsSuccessful) if (!retPack.Value.IsSuccessful)
@ -606,8 +619,7 @@ public sealed class UDPClientPool
return new(new Exception("Message bus not working!")); return new(new Exception("Message bus not working!"));
// Wait for Write Ack // Wait for Write Ack
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync( var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(endPoint, taskID, timeout);
endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error); if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
_progressTracker.AdvanceProgress(progressId, 10); _progressTracker.AdvanceProgress(progressId, 10);
@ -671,7 +683,7 @@ public sealed class UDPClientPool
if (!ret) return new(new Exception("Send data package failed!")); if (!ret) return new(new Exception("Send data package failed!"));
// Wait for Write Ack // Wait for Write Ack
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(endPoint.Address.ToString(), taskID, endPoint.Port, timeout); var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(endPoint, taskID, timeout);
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error); if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
if (!udpWriteAck.Value.IsSuccessful) if (!udpWriteAck.Value.IsSuccessful)

View File

@ -194,14 +194,15 @@ public class UDPServer
var startTime = DateTime.Now; var startTime = DateTime.Now;
var isTimeout = false; var isTimeout = false;
while (!isTimeout)
{
var elapsed = DateTime.Now - startTime;
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
if (isTimeout) break;
try try
{
while (!isTimeout)
{ {
var elapsed = DateTime.Now - startTime;
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
if (isTimeout) break;
using (await udpDataLock.AcquireWriteLockAsync(TimeSpan.FromMilliseconds(timeout))) using (await udpDataLock.AcquireWriteLockAsync(TimeSpan.FromMilliseconds(timeout)))
{ {
if (udpData.TryGetValue(key, out var sortedList) && sortedList.Count > 0) if (udpData.TryGetValue(key, out var sortedList) && sortedList.Count > 0)
@ -214,23 +215,16 @@ public class UDPServer
} }
} }
} }
catch
{ if (data is null)
logger.Trace("Get nothing even after time out"); throw new TimeoutException("Get nothing even after time out");
return new(null); else return new(data.DeepClone());
}
} }
catch
if (data is null)
{ {
logger.Trace("Get nothing even after time out"); logger.Trace("Get nothing even after time out");
return new(null); return new(null);
} }
else
{
return new(data.DeepClone());
}
} }
/// <summary> /// <summary>
@ -367,17 +361,22 @@ public class UDPServer
/// <summary> /// <summary>
/// 异步等待写响应 /// 异步等待写响应
/// </summary> /// </summary>
/// <param name="address">IP地址</param> /// <param name="endPoint">IP地址及端口</param>
/// <param name="taskID">[TODO:parameter]</param> /// <param name="taskID">[TODO:parameter]</param>
/// <param name="port">UDP 端口</param>
/// <param name="timeout">超时时间范围</param> /// <param name="timeout">超时时间范围</param>
/// <returns>接收响应包</returns> /// <returns>接收响应包</returns>
public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync
(string address, int taskID, int port = -1, int timeout = 1000) (IPEndPoint endPoint, int taskID, int timeout = 1000)
{ {
var address = endPoint.Address.ToString();
var port = endPoint.Port;
var data = await FindDataAsync(address, taskID, timeout); var data = await FindDataAsync(address, taskID, timeout);
if (!data.HasValue) if (!data.HasValue)
{
await UDPClientPool.SendResetSignal(endPoint);
return new(new Exception("Get None even after time out!")); return new(new Exception("Get None even after time out!"));
}
var recvData = data.Value; var recvData = data.Value;
if (recvData.Address != address || (port > 0 && recvData.Port != port)) if (recvData.Address != address || (port > 0 && recvData.Port != port))
@ -393,17 +392,22 @@ public class UDPServer
/// <summary> /// <summary>
/// 异步等待数据 /// 异步等待数据
/// </summary> /// </summary>
/// <param name="address">IP地址</param> /// <param name="endPoint">IP地址</param>
/// <param name="taskID">[TODO:parameter]</param> /// <param name="taskID">任务ID</param>
/// <param name="port">UDP 端口</param>
/// <param name="timeout">超时时间范围</param> /// <param name="timeout">超时时间范围</param>
/// <returns>接收数据包</returns> /// <returns>接收数据包</returns>
public async ValueTask<Result<RecvDataPackage>> WaitForDataAsync public async ValueTask<Result<RecvDataPackage>> WaitForDataAsync
(string address, int taskID, int port = -1, int timeout = 1000) (IPEndPoint endPoint, int taskID, int timeout = 1000)
{ {
var address = endPoint.Address.ToString();
var port = endPoint.Port;
var data = await FindDataAsync(address, taskID, timeout); var data = await FindDataAsync(address, taskID, timeout);
if (!data.HasValue) if (!data.HasValue)
{
await UDPClientPool.SendResetSignal(endPoint);
return new(new Exception("Get None even after time out!")); return new(new Exception("Get None even after time out!"));
}
var recvData = data.Value; var recvData = data.Value;
if (recvData.Address != address || (port >= 0 && recvData.Port != port)) if (recvData.Address != address || (port >= 0 && recvData.Port != port))

View File

@ -1,9 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRequiredInjection } from "@/utils/Common";
import { templateRef } from "@vueuse/core"; import { templateRef } from "@vueuse/core";
import { File, UploadIcon, XIcon } from "lucide-vue-next"; import { File, UploadIcon, XIcon } from "lucide-vue-next";
import { isNull } from "mathjs"; import { isNull } from "mathjs";
import { useSlots } from "vue"; import { useSlots } from "vue";
import { useAlertStore } from "./Alert";
const alert = useRequiredInjection(useAlertStore);
const slots = useSlots(); const slots = useSlots();
interface Props { interface Props {
@ -17,6 +20,10 @@ const props = withDefaults(defineProps<Props>(), {
closeAfterUpload: false, closeAfterUpload: false,
}); });
const emits = defineEmits<{
finishedUpload: [];
}>();
const inputFiles = defineModel<File[] | null>("inputFiles", { default: null }); const inputFiles = defineModel<File[] | null>("inputFiles", { default: null });
const isShowModal = defineModel<boolean>("isShowModal", { default: false }); const isShowModal = defineModel<boolean>("isShowModal", { default: false });
@ -42,6 +49,8 @@ function handleUpload() {
if (!inputFiles.value) return; if (!inputFiles.value) return;
props.callback(inputFiles.value); props.callback(inputFiles.value);
if (props.closeAfterUpload) close(); if (props.closeAfterUpload) close();
alert.info("上传成功");
emits("finishedUpload");
} }
function show() { function show() {

View File

@ -161,17 +161,17 @@
> >
暂无提交记录 暂无提交记录
</div> </div>
<div v-else class="overflow-y-auto"> <div v-else class="overflow-y-auto fit-content max-h-50">
<ul class="steps steps-vertical"> <ul class="steps steps-vertical">
<li <li
class="step" class="step"
:class="{ 'step-primary': _idx === 1 }" :class="{
v-for="(commit, _idx) in commitsList.slice(0, 3)" 'step-primary': _idx === commitsList.length - 1,
}"
v-for="(commit, _idx) in commitsList"
> >
{{ commit.id }} --- {{ commit.uploadTime.toTimeString() }}
{{ commit.uploadTime.toDateString() }}
</li> </li>
<li class="step" v-if="commitsList.length > 3">......</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -214,7 +214,9 @@
ref="uploadModal" ref="uploadModal"
class="fixed z-auto" class="fixed z-auto"
:auto-upload="true" :auto-upload="true"
:close-after-upload="true"
:callback="submitExam" :callback="submitExam"
@finished-upload="handleSubmitFinished"
/> />
</div> </div>
</template> </template>
@ -234,11 +236,12 @@ import { useRouter } from "vue-router";
import { formatDate } from "@/utils/Common"; import { formatDate } from "@/utils/Common";
import { computed } from "vue"; import { computed } from "vue";
import { watch } from "vue"; import { watch } from "vue";
import { isNull, isUndefined } from "lodash"; import { delay, isNull, isUndefined } from "lodash";
import { Download, GitGraph, Smile, Upload, XIcon } from "lucide-vue-next"; import { Download, GitGraph, Smile, Upload, XIcon } from "lucide-vue-next";
import UploadModal from "@/components/UploadModal.vue"; import UploadModal from "@/components/UploadModal.vue";
import { templateRef } from "@vueuse/core"; import { templateRef } from "@vueuse/core";
import { toFileParameter } from "@/utils/Common"; import { toFileParameter } from "@/utils/Common";
import { onMounted } from "vue";
const alertStore = useRequiredInjection(useAlertStore); const alertStore = useRequiredInjection(useAlertStore);
const router = useRouter(); const router = useRouter();
@ -259,11 +262,23 @@ async function updateCommits() {
const list = await client.getCommitsByExamId(props.selectedExam.id); const list = await client.getCommitsByExamId(props.selectedExam.id);
commitsList.value = list; commitsList.value = list;
} }
watch(() => props.selectedExam, updateCommits); watch(
() => show.value,
() => {
if (show.value) {
updateCommits();
}
},
);
onMounted(() => {
if (show.value) {
updateCommits();
}
});
// Download resources // Download resources
const downloadingResources = ref(false); const downloadingResources = ref(false);
const downloadResources = async () => { async function downloadResources() {
if (!props.selectedExam || downloadingResources.value) return; if (!props.selectedExam || downloadingResources.value) return;
downloadingResources.value = true; downloadingResources.value = true;
@ -311,10 +326,10 @@ const downloadResources = async () => {
} finally { } finally {
downloadingResources.value = false; downloadingResources.value = false;
} }
}; }
// //
const startExam = () => { function startExam() {
if (props.selectedExam) { if (props.selectedExam) {
// ID // ID
console.log("开始实验:", props.selectedExam.id); console.log("开始实验:", props.selectedExam.id);
@ -323,9 +338,9 @@ const startExam = () => {
query: { examId: props.selectedExam.id }, query: { examId: props.selectedExam.id },
}); });
} }
}; }
const submitExam = (files: File[]) => { function submitExam(files: File[]) {
try { try {
const client = AuthManager.createClient(ResourceClient); const client = AuthManager.createClient(ResourceClient);
@ -341,11 +356,17 @@ const submitExam = (files: File[]) => {
alertStore.error(err.message || "上传资料失败"); alertStore.error(err.message || "上传资料失败");
console.error("上传资料失败:", err); console.error("上传资料失败:", err);
} }
}; }
const closeExamDetail = () => { async function handleSubmitFinished() {
delay(async () => {
await updateCommits();
}, 1000);
}
function closeExamDetail() {
show.value = false; show.value = false;
}; }
</script> </script>
<style lang="postcss" scoped></style> <style lang="postcss" scoped></style>