Admin: name submission archives as group-studentNo-name-attempt

This commit is contained in:
2026-09-27 20:46:17 +03:00
parent a91cd34e30
commit d35d345059
3 changed files with 23 additions and 3 deletions

View File

@@ -746,16 +746,36 @@ class AdminController(
@GetMapping("/submissions/{id}/download") @GetMapping("/submissions/{id}/download")
fun downloadSubmissionAsAdmin( fun downloadSubmissionAsAdmin(
@PathVariable id: String, @PathVariable id: String,
@RequestParam(required = false) group: String?,
@RequestParam(required = false) no: String?,
@RequestParam(required = false) name: String?,
@RequestParam(required = false) attempt: String?,
session: HttpSession, session: HttpSession,
): ResponseEntity<ByteArray> { ): ResponseEntity<ByteArray> {
val file = api.downloadSubmissionAsAdmin(token(session), id) val file = api.downloadSubmissionAsAdmin(token(session), id)
val encoded = java.net.URLEncoder.encode(file.filename, java.nio.charset.StandardCharsets.UTF_8) val filename = submissionFilename(group, no, name, attempt) ?: file.filename
val encoded = java.net.URLEncoder.encode(filename, java.nio.charset.StandardCharsets.UTF_8)
return ResponseEntity.ok() return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encoded") .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encoded")
.contentType(MediaType.APPLICATION_OCTET_STREAM) .contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(file.bytes) .body(file.bytes)
} }
/** Человекочитаемое имя архива сдачи: группа-номер студенческого-ФИО-попыткаN.zip */
private fun submissionFilename(group: String?, no: String?, name: String?, attempt: String?): String? {
val parts = listOfNotNull(
group?.takeIf { it.isNotBlank() },
no?.takeIf { it.isNotBlank() },
name?.takeIf { it.isNotBlank() },
attempt?.takeIf { it.isNotBlank() }?.let { "попытка$it" },
)
if (parts.isEmpty()) return null
val base = parts.joinToString("-")
.replace(Regex("[\\\\/:*?\"<>|\\r\\n]"), "_")
.trim()
return if (base.isBlank()) null else "$base.zip"
}
@PostMapping("/labs/{labId}/assign") @PostMapping("/labs/{labId}/assign")
fun assignVariant( fun assignVariant(
@PathVariable labId: String, @PathVariable labId: String,

View File

@@ -89,7 +89,7 @@
<span th:if="${cell.defenseStatus == 'started'}" class="cell-chip chip-warn" title="Защита: студент пишет работу">🛡⏳</span> <span th:if="${cell.defenseStatus == 'started'}" class="cell-chip chip-warn" title="Защита: студент пишет работу">🛡⏳</span>
<span th:if="${cell.defenseStatus == 'ready'}" class="cell-chip chip-ok" title="Защита: работа сохранена, можно принимать">🛡✓</span> <span th:if="${cell.defenseStatus == 'ready'}" class="cell-chip chip-ok" title="Защита: работа сохранена, можно принимать">🛡✓</span>
<a th:if="${cell.submissionId}" class="cell-chip chip-link" <a th:if="${cell.submissionId}" class="cell-chip chip-link"
th:href="'/admin/submissions/' + ${cell.submissionId} + '/download'" th:href="@{/admin/submissions/{id}/download(id=${cell.submissionId}, group=${group.name}, no=${row.studentNo}, name=${row.fullName}, attempt=${cell.submissionSeq})}"
title="Скачать последнее решение студента">↓<span class="font-mono" th:text="${cell.submissionSeq}">1</span></a> title="Скачать последнее решение студента">↓<span class="font-mono" th:text="${cell.submissionSeq}">1</span></a>
<span th:unless="${cell.submissionId}" class="cell-chip chip-dim" title="Решений пока не сдавалось">↓—</span> <span th:unless="${cell.submissionId}" class="cell-chip chip-dim" title="Решений пока не сдавалось">↓—</span>
<button class="cell-chip" type="button" <button class="cell-chip" type="button"

View File

@@ -127,7 +127,7 @@
</td> </td>
<td th:attr="data-filter-value=${s.submissionId != null ? 'сдано' : 'нет'}"> <td th:attr="data-filter-value=${s.submissionId != null ? 'сдано' : 'нет'}">
<a th:if="${s.submissionId}" class="cell-link" <a th:if="${s.submissionId}" class="cell-link"
th:href="'/admin/submissions/' + ${s.submissionId} + '/download'"> th:href="@{/admin/submissions/{id}/download(id=${s.submissionId}, group=${g.groupName}, no=${s.studentNo}, name=${s.fullName}, attempt=${s.submissionSeq})}">
попытка <span th:text="${s.submissionSeq}">1</span> ↓</a> попытка <span th:text="${s.submissionSeq}">1</span> ↓</a>
<div th:if="${!#lists.isEmpty(s.submissionFiles)}" class="text-muted text-sm font-mono"> <div th:if="${!#lists.isEmpty(s.submissionFiles)}" class="text-muted text-sm font-mono">
<span th:text="${#strings.listJoin(s.submissionFiles, ', ')}">main.c</span> <span th:text="${#strings.listJoin(s.submissionFiles, ', ')}">main.c</span>