From 46bb55d1c9638a4beb959fa3fb5c781948c842ec Mon Sep 17 00:00:00 2001 From: Fox_block Date: Sun, 18 Jan 2026 19:16:45 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4SecRandom?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD=E5=8F=8A=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除不再使用的SecRandom学生导入功能,包括前端组件、API接口和后端实现 将最近记录表格改为可折叠面板以优化界面布局 清理不再使用的状态变量和导入相关代码 --- src/main/repos/StudentRepository.ts | 208 +----------------- src/preload/index.ts | 2 - src/renderer/src/components/ScoreManager.tsx | 25 ++- .../src/components/StudentManager.tsx | 38 ---- 4 files changed, 18 insertions(+), 255 deletions(-) diff --git a/src/main/repos/StudentRepository.ts b/src/main/repos/StudentRepository.ts index cec0755..0cffea1 100644 --- a/src/main/repos/StudentRepository.ts +++ b/src/main/repos/StudentRepository.ts @@ -1,7 +1,6 @@ import { Service } from '../../shared/kernel' import { MainContext } from '../context' import { ScoreEventEntity, StudentEntity } from '../db/entities' -import net from 'net' export interface student { id: number @@ -49,65 +48,12 @@ export class StudentRepository extends Service { return { success: true } }) - this.mainCtx.handle('db:student:importFromSecRandom', async (event, input: any) => { + this.mainCtx.handle('db:student:importFromSecRandom', async (event) => { if (!this.mainCtx.permissions.requirePermission(event, 'admin')) return { success: false, message: 'Permission denied' } - - const ipcName = - typeof input?.ipcName === 'string' && input.ipcName.trim() - ? input.ipcName.trim() - : 'SecRandom.secrandom' - const timeoutMs = - typeof input?.timeoutMs === 'number' && - Number.isFinite(input.timeoutMs) && - input.timeoutMs > 0 - ? Math.floor(input.timeoutMs) - : 5000 - - let resp: any - try { - resp = await sendSecRandomIpcJson( - { - type: 'url', - payload: { url: 'SecRandom://secscore/importRoster' } - }, - { ipcName, timeoutMs } - ) - } catch (e: any) { - const socketPath = getSecRandomIpcPath(ipcName) - const detail = e instanceof Error ? e.message : String(e) - return { - success: false, - message: `SecRandom IPC 连接失败:${detail}(${socketPath})`, - data: { error: detail, socketPath, ipcName } - } - } - if (!resp?.success) { - return { - success: false, - message: resp?.message ? String(resp.message) : 'SecRandom IPC 返回失败', - data: { raw: resp } - } - } - - const roster = parseSecRandomRoster(resp) - if (!roster.items.length) { - return { - success: false, - message: roster.message || '未在 SecRandom 返回中解析到名单', - data: { raw: resp } - } - } - - const result = await this.importRosterMerge(roster.items) return { - success: true, - data: { - className: roster.className, - sourceMessage: roster.message, - ...result, - raw: resp - } + success: false, + message: 'SecRandom IPC 导入已禁用(后续再做)' } }) @@ -210,151 +156,3 @@ export class StudentRepository extends Service { }) } } - -function getSecRandomIpcPath(ipcName: string) { - if (process.platform === 'win32') return `\\\\.\\pipe\\${ipcName}` - return `/tmp/${ipcName}.sock` -} - -async function connectSocket(path: string, timeoutMs: number) { - return await new Promise((resolve, reject) => { - const socket = net.connect(path) - const onError = (err: any) => { - cleanup() - reject(err) - } - const onConnect = () => { - cleanup() - resolve(socket) - } - const timer = setTimeout(() => { - cleanup() - try { - socket.destroy(new Error('connect timeout')) - } catch { - void 0 - } - reject(new Error('connect timeout')) - }, timeoutMs) - - const cleanup = () => { - clearTimeout(timer) - socket.removeListener('error', onError) - socket.removeListener('connect', onConnect) - } - - socket.once('error', onError) - socket.once('connect', onConnect) - }) -} - -async function recvJsonLine(socket: net.Socket, timeoutMs: number) { - return await new Promise((resolve, reject) => { - let acc = '' - let settled = false - const maxChars = 2_000_000 - - const timer = setTimeout(() => { - cleanup() - reject(new Error('read timeout')) - }, timeoutMs) - - const cleanup = () => { - if (settled) return - settled = true - clearTimeout(timer) - socket.removeListener('data', onData) - socket.removeListener('error', onError) - socket.removeListener('end', onEnd) - socket.removeListener('close', onClose) - } - - const tryParse = (s: string) => { - const trimmed = s.trim() - if (!trimmed) return null - try { - return JSON.parse(trimmed) - } catch { - return null - } - } - - const finishIfParsed = (raw: string) => { - const parsed = tryParse(raw) - if (parsed == null) return false - cleanup() - resolve(parsed) - return true - } - - const onData = (chunk: Buffer) => { - acc += chunk.toString('utf8') - if (acc.length > maxChars) { - cleanup() - reject(new Error('response too large')) - return - } - const newlineIdx = acc.indexOf('\n') - if (newlineIdx >= 0) { - const line = acc.slice(0, newlineIdx) - if (finishIfParsed(line)) return - } - finishIfParsed(acc) - } - - const onError = (err: any) => { - cleanup() - reject(err) - } - - const onEnd = () => { - if (finishIfParsed(acc)) return - cleanup() - reject(new Error('connection closed without response')) - } - - const onClose = () => { - if (settled) return - if (finishIfParsed(acc)) return - cleanup() - reject(new Error('connection closed without response')) - } - - socket.on('data', onData) - socket.once('error', onError) - socket.once('end', onEnd) - socket.once('close', onClose) - }) -} - -async function sendSecRandomIpcJson(message: any, opts: { ipcName: string; timeoutMs: number }) { - const socketPath = getSecRandomIpcPath(opts.ipcName) - const socket = await connectSocket(socketPath, opts.timeoutMs) - try { - socket.write(JSON.stringify(message) + '\n') - return await recvJsonLine(socket, opts.timeoutMs) - } finally { - socket.end() - } -} - -function parseSecRandomRoster(resp: any) { - const className = String(resp?.result?.class_name ?? '').trim() - const message = String(resp?.result?.message ?? '').trim() - const data = resp?.result?.data - - const items: Array<{ name: string; secrandomId: number | null }> = [] - if (Array.isArray(data)) { - for (const row of data) { - if (!row || typeof row !== 'object') continue - if (row.exist !== true) continue - const name = String((row as any).name ?? '').trim() - if (!name) continue - const idRaw = (row as any).id - const secrandomId = Number.isFinite(idRaw) ? Number(idRaw) : null - items.push({ name, secrandomId }) - } - } - - return { className, message, items } -} diff --git a/src/preload/index.ts b/src/preload/index.ts index 30ffdfc..f3b1b6e 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -18,8 +18,6 @@ const api = { createStudent: (data: any) => ipcRenderer.invoke('db:student:create', data), updateStudent: (id: number, data: any) => ipcRenderer.invoke('db:student:update', id, data), deleteStudent: (id: number) => ipcRenderer.invoke('db:student:delete', id), - importStudentsFromSecRandom: (params?: { ipcName?: string; timeoutMs?: number }) => - ipcRenderer.invoke('db:student:importFromSecRandom', params), importStudentsFromXlsx: (params: { names: string[] }) => ipcRenderer.invoke('db:student:importFromXlsx', params), diff --git a/src/renderer/src/components/ScoreManager.tsx b/src/renderer/src/components/ScoreManager.tsx index 7dfd17d..fe3c44a 100644 --- a/src/renderer/src/components/ScoreManager.tsx +++ b/src/renderer/src/components/ScoreManager.tsx @@ -8,6 +8,7 @@ import { Button, MessagePlugin, Card, + Collapse, Table, PrimaryTableCol, Tag, @@ -318,16 +319,20 @@ export const ScoreManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { - - + + + +
+ + ) diff --git a/src/renderer/src/components/StudentManager.tsx b/src/renderer/src/components/StudentManager.tsx index 2561a4b..46d8033 100644 --- a/src/renderer/src/components/StudentManager.tsx +++ b/src/renderer/src/components/StudentManager.tsx @@ -14,7 +14,6 @@ export const StudentManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { const [loading, setLoading] = useState(false) const [visible, setVisible] = useState(false) const [importVisible, setImportVisible] = useState(false) - const [importLoading, setImportLoading] = useState(false) const [xlsxVisible, setXlsxVisible] = useState(false) const [xlsxLoading, setXlsxLoading] = useState(false) const [xlsxFileName, setXlsxFileName] = useState('') @@ -112,40 +111,6 @@ export const StudentManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { } } - const handleImportFromSecRandom = async () => { - if (!(window as any).api) return - if (!canEdit) { - MessagePlugin.error('当前为只读权限') - return - } - setImportLoading(true) - try { - let res: any - try { - res = await (window as any).api.importStudentsFromSecRandom({}) - } catch (e: any) { - MessagePlugin.error(e instanceof Error ? e.message : '导入失败') - return - } - if (!res?.success) { - MessagePlugin.error(res?.message || '导入失败') - return - } - const inserted = Number(res?.data?.inserted ?? 0) - const skipped = Number(res?.data?.skipped ?? 0) - const className = String(res?.data?.className ?? '').trim() - const sourceMessage = String(res?.data?.sourceMessage ?? '').trim() - const prefix = className ? `导入完成(${className}):` : '导入完成:' - const suffix = sourceMessage ? `(${sourceMessage})` : '' - MessagePlugin.success(`${prefix}新增 ${inserted},跳过 ${skipped}${suffix}`) - setImportVisible(false) - fetchStudents() - emitDataUpdated('students') - } finally { - setImportLoading(false) - } - } - const excelColName = (idx: number) => { let n = idx + 1 let s = '' @@ -375,9 +340,6 @@ export const StudentManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => { destroyOnClose > -