refactor: 移除SecRandom导入功能及相关代码

移除不再使用的SecRandom学生导入功能,包括前端组件、API接口和后端实现
将最近记录表格改为可折叠面板以优化界面布局
清理不再使用的状态变量和导入相关代码
This commit is contained in:
Fox_block
2026-01-18 19:16:45 +08:00
parent 8c48b35c08
commit 46bb55d1c9
4 changed files with 18 additions and 255 deletions
+3 -205
View File
@@ -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<net.Socket>((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<any>((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 }
}
-2
View File
@@ -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),
+15 -10
View File
@@ -8,6 +8,7 @@ import {
Button,
MessagePlugin,
Card,
Collapse,
Table,
PrimaryTableCol,
Tag,
@@ -318,16 +319,20 @@ export const ScoreManager: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
</Form>
</Card>
<Card title="最近记录" style={{ backgroundColor: 'var(--ss-card-bg)' }}>
<Table
data={events}
columns={columns}
rowKey="uuid"
loading={loading}
size="small"
pagination={{ pageSize: 5, total: events.length }}
style={{ color: 'var(--ss-text-main)' }}
/>
<Card style={{ backgroundColor: 'var(--ss-card-bg)' }}>
<Collapse defaultValue={[]} expandMutex>
<Collapse.Panel header="最近记录" value="recent">
<Table
data={events}
columns={columns}
rowKey="uuid"
loading={loading}
size="small"
pagination={{ pageSize: 5, total: events.length }}
style={{ color: 'var(--ss-text-main)' }}
/>
</Collapse.Panel>
</Collapse>
</Card>
</div>
)
@@ -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
>
<Space direction="vertical" style={{ width: '100%' }}>
<Button loading={importLoading} disabled={!canEdit} onClick={handleImportFromSecRandom}>
SecRandomIPC
</Button>
<Button
loading={xlsxLoading}
disabled={!canEdit}