bat脚本实现判题系统OJ

前言

hello大家好,本学期的javaWeb实习做了个OJ网站,ui参考PTA,详细介绍参考博主后面的博客,本篇文章主要介绍后台bat脚本语言实现的判题系统部分…

版本

v1.0
注:为啥要有版本呢???嘻嘻。。。
目前系统只实现了了C++判题,java其他语言的懒得弄了,然后系统还有一些bug未修复,总之就是未完善,欢迎各位大佬指导指导…

模块说明

  • 截图
    bat脚本实现判题系统OJ_第1张图片

  • 说明

文件名 描述 备注
judgeMain.bat 判题脚本入口程序需传入相应参数
compile.bat 用于编译的bat脚本
runJudge.bat 运行程序并判题的脚本
timeOverExit.bat 程序超时的自动终止脚本
getUseMemory.bat 获取程序内存消耗的脚本
updateDatabase.bat 更新数据库判题状态的脚本

注:judgeMain.bat runJudge.bat timeOverExit.bat getUserMemory.bat配备相应的vbs启动文件以实现后台隐藏运行,详细代码请看下文GitHub链接

核心模块代码

Talk is cheap. Show me the code. ------Linus Torvalds

judgeMain.bat

rem @echo off
rem 注意文件名不要和系统命令一样如main
rem 注意注释使用'rem'
rem 判题入口文件
rem 需编译的文件
rem 提交的代码ID
set submitProblemId=%1
rem 需编译的文件
set compileFilePath=%2
rem 可执行文件
set exeFilePath=%3
rem 运行时的进程名
set processName=%4
rem 编译错误输出文件
set compileErrFilePath=%5
rem 判题输入数据文件
set inputFile=%6
rem 用户运行的输出
set outputFile=%7
rem 临时输出文件
set tempOutputFile=%8
rem 判题文件
set judgeFile=%9

rem 超过十个参数则右移
rem 判题结果数据文件
shift /1
set resultFile=%9
rem 时间限制
shift /2
set /a timeLimit=%9
rem 内存限制
shift /3
set /a memoryLimit=%9
rem 判题状态
rem -1 等待 0 通过 1编译错误 2答案错误 3运行超时 4内存超限
set /a resultStatus=1
rem 超时强制关闭时间 8s
set /a exitTime=8
rem 运行时间
set /a executeTime=0
rem 使用内存
set /a executeMemory=0
:init
rem 判断参数错误,所有参数不能为空
set errArg=if "%submitProblemId%" equ "" goto errEnd
if "%compileFilePath%" equ "" goto errEnd
if "%exeFilePath%" equ "" goto errEnd
if "%processName%" equ "" goto errEnd
if "%compileErrFilePath%" equ "" goto errEnd
if "%inputFile%" equ "" goto errEnd
if "%outputFile%" equ "" goto errEnd
if "%tempOutputFile%" equ "" goto errEnd
if "%judgeFile%" equ "" goto errEnd
if "%resultFile%" equ "" goto errEnd
if "%timeLimit%" equ "" goto errEnd
if "%memoryLimit%" equ "" goto errEnd
rem 初始化
cd .>%resultFile%
:compile
rem 编译结果
for /f %%i in ('compile.bat %compileFilePath% %exeFilePath% %compileErrFilePath%') do (
    set /a resultStatus=%%i
)

if "%resultStatus%" EQU "2" (
    rem  编译成功
) else (
    echo %date% %time:~0,8%>>debug.txt
    echo 编译失败%compileFilePath%>>debug.txt
    goto database
)
:timeOverExit
rem 超时则强制退出
wscript timeOverExit.vbs %processName% %resultFile% %exitTime%
:getUseMemory
rem 获取内存消耗 程序
wscript getUseMemory.vbs  %processName% %resultFile%

:runJudge
wscript runJudge.vbs %exeFilePath% %inputFile% %outputFile% %judgeFile% %resultFile% %tempOutputFile% %timeLimit%


:loopReadResult
rem 循环读取结果
rem 注意变量延迟问题
for /f "tokens=1,2 delims=:" %%i in (%resultFile%) do (
    if "%%i" equ "executeTime" (
        set /a executeTime=%%j
    )
    if "%%i" equ "executeMemory" (
        set /a executeMemory=%%j
        
    )
    if "%%i" equ "nowStatus" (
        set /a resultStatus=%%j
        goto endLoop
    )

)
rem 每次等待1s
choice /t 1 /d y /n >nul
goto loopReadResult
:endLoop

if %executeMemory% GEQ %memoryLimit% (
    set /a resultStatus=4
)
:database
rem 此处为数据库更新的操作
rem 数据定义
rem 注意字符串加双引号 ""
rem 注意不要重复操作>>txt
call updateDatabase.bat %submitProblemId% %resultStatus% %compileErrFilePath% %executeTime% %executeMemory% >>dataBaseErr.txt 2>>&1

echo %date% %time:~0,8%>>log.txt
echo 题目评测完成,ID:%submitProblemId%>>log.txt
echo 题目ID:%submitProblemId%,评测结果:%resultStatus%,用时:%executeTime%,消耗内存:%executeMemory% >>log.txt

goto end

:errEnd
echo %date% %time:~0,8%>>debug.txt
echo 错误参数%errArg%>>debug.txt
echo 参数错误
:end
echo 评测程序结束
echo 执行究极自身终止程序
rem taskkill /im cmd.exe /f
exit

compile.bat

@echo off
rem 需编译的文件
set compileFilePath=%1
rem 可执行文件
set exeFilePath=%2
rem 编译错误输出文件
set compileErrFilePath=%3
rem 编译指令
set compileCMD=g++ %compileFilePath% -o %exeFilePath%
rem 编译结果 0 成功 1失败
set /a result=1
:compile
rem 注意括号前后要有空格
if exist %compileFilePath% (
    rem 编译并输出错误,先清空错误输出
    cd .>%compileErrFilePath%
    %compileCMD%>%compileErrFilePath% 2>&1
    goto checkCompile
)

rem 编译文件不存在则编译失败
goto end


:checkCompile
for %%a in ("%compileErrFilePath%") do ( 
    if "%%~za" equ "0" ( 
        rem 编译无误
        set /a result=2
    ) else ( 
        set /a result=1
    ) 
)

:end
echo %result%

exit 

runJudge.bat

@echo off
setlocal enabledelayedexpansion
rem 可执行文件
set exeFilePath=%1
rem 判题输入数据文件
set inputFile=%2
rem 用户运行的输出
set outputFile=%3
rem 判题文件
set judgeFile=%4
rem 判题结果数据文件
set resultFile=%5
rem 临时输出文件
set tempOutputFile=%6
rem 时间限制(平均)
set /a limitTime=%7


:runExe
rem 清空output文件
cd . >%outputFile%
rem 必须添加delims不然会以空格作为分隔
rem 每个测试用例以$符号结尾
set num = 0
rem 计算平均耗时
set /a avaTime=0
rem 获得耗时单位ms
rem 秒 0~9
set /a sTime1=%time:~7,1%
rem 100ms 0~9
set /a sTime2=%time:~9,1%
rem 10ms 0~9
set /a sTime3=%time:~10,1%
rem 开始时间单位ms
set /a sTime=%sTime1%*1000+%sTime2%*100+%sTime3%*10
rem 清空temp
cd .>%tempOutputFile%
for /f  "delims=" %%i in (%inputFile%) do (
    if not '%%i' equ '$' (
        
        echo %%i>>%tempOutputFile%
    ) else (
        call %exeFilePath% <%tempOutputFile% >>%outputFile%
        set /a num+=1
        rem 清空temp
        cd .>%tempOutputFile%
    )

)
:getTime
rem 获得耗时单位ms
rem 秒 0~9
set /a eTime1=%time:~7,1%
rem 100ms 0~9
set /a eTime2=%time:~9,1%
rem 10ms 0~9
set /a eTime3=%time:~10,1%
rem 结束时间单位ms
set /a eTime=%eTime1%*1000+%eTime2%*100+%eTime3%*10
rem 获得耗时
rem 注意时间循环
set costTime=0
if %eTime% GEQ %sTime% (
    set /a costTime=%eTime%-%sTime%
) else (
    set /a costTime=%eTime%-%sTime%+10000
)
rem 计算平均耗时
set /a avaTime=%costTime%/%num%

:chekIsErrExit
rem 检查是否被异常终止
rem 检查是否超时
rem 阻碍程序运行不输出
for /f "tokens=1,2 delims=:" %%a in (%resultFile%) do (
    if "%%a" equ "nowStatus" (
        rem 程序已超时被timeOverExit终止
        if "%%b" equ "3" (
            set /a resultStatus=3
            set /a avaTime=%limitTime%
            echo %date% %time:~0,8%>>debug.txt
            echo 进程超时终止 from runJudge.bat>>debug.txt
            goto end
        )
    )

)
:checkTimeLimit
rem 检查时间限制
if %avaTime% GEQ %limitTime% (
    rem 超时
    set /a resultStatus=3
    goto end
)

rem 比较测试结果和正确结果
:compareJudge
fc %outputFile% %judgeFile% >nul
if %errorlevel% equ 0 (
  rem 评测通过
  set /a resultStatus=0
) else (
  rem 答案错误
  set /a resultStatus=2
)
:end
echo %date% %time:~0,8%>>log.txt
echo 题目评测结果errorlevel:%errorlevel%,ID:%submitProblemId%>>log.txt
echo executeTime:%avaTime%>>%resultFile%
rem 状态一定在最后输出
echo nowStatus:%resultStatus%>>%resultFile%
exit

timeOverExit.bat

@echo off
rem 程序超时判断 防止程序超时
rem 获取传入参数
set processName=%1
set resultFile=%2
set /a exitTime=%3
rem 设置等待8s关闭评测程序
rem 每个测试用例大概要1s 最多5个用例 选取易错用例
rem ping 127.0.0.1 -n 5 >nul
rem choice指令可以定时
rem 判断参数
if "%processName%" equ "" goto errEnd
if "%resultFile%" equ "" goto errEnd
if "%exitTime%" equ "" goto errEnd
choice /t %exitTime% /d y /n >nul
set /a isFind=0
:loop
for /f "tokens=1 delims= " %%i in ('tasklist ^|findstr "%processName%"') do (
    if "%%i" neq "" (
        taskkill /im %processName% /f
        echo %date% %time:~0,8%>>debug.txt
        echo 程序运行超时%exitTime%s>>debug.txt
        echo %%i进程已终止>>debug.txt
        echo executeTime:%exitTime%000>>%resultFile%
        echo nowStatus:3>>%resultFile%
        rem 循环检测保证程序已被终止
        set /a isFind+=1
        goto loop
    ) else (      
        echo %date% %time:~0,8%>>debug.txt
        echo 超时终止程序结束>>debug.txt
        echo %processName%进程未找到>>debug.txt
        goto end
    )

)



goto end
:errEnd
echo %date% %time:~0,8%>>debug.txt
echo 空参数 from timeOverExit>>debug.txt
:end
rem echo 程序结束 终止进程次数%isFind%
exit

getMemory.bat

rem @echo off
rem 获取进程运行消耗内存
rem 入参为文件名
set processName=%1
set resultFile=%2
:init
rem 判断参数
if "%processName%" equ "" goto errEnd
if "%resultFile%" equ "" goto errEnd


for /l %%a in (1,1,10) do (
    for /f "tokens=5 delims= " %%i in ('tasklist ^|findstr "%processName%"') do (
        echo %date% %time:~0,8%>>out.txt
        echo %%a>>out.txt
        for /f "tokens=1,2 delims=," %%j in ("%%i") do (
            if "%%j%%k" neq "" (
                echo executeMemory:%%j%%k>>%resultFile%
                goto end
            )
        )                       
    ) 
)
:err
echo %date% %time:~0,8%>>debug.txt
echo 获取%processName%内存失败>>debug.txt
goto end
:errEnd
echo %date% %time:~0,8%>>debug.txt
echo 空参数 from getUserMemory>>debug.txt

:end
exit

updateDatabase.bat


@echo off
rem 更新数据库的函数
:readInfo
rem 读取编译错误信息
setlocal enabledelayedexpansion
set judgeMessage=未知错误
if %2 equ 0 set judgeMessage=评测通过
if %2 equ 1 set judgeMessage=%3
if %2 equ 2 set judgeMessage=答案错误
if %2 equ 3 set judgeMessage=运行超时
if %2 equ 4 set judgeMessage=内存超限

:updateSQL
rem 数据库名
set database=onlinejudge
rem 表名
set table=submit_code_table
set id=%1
rem 登录指令
set "loginSQL=mysql --user=root --password=YOURPASSWORD"
set "querySQL=update %table% set judge_status=%2,judge_message='%judgeMessage%',execute_time=%4,execute_memory=%5 where id=%id%"
set "unknownErrSQL=update %table% set judge_status=2,judge_message='unknown error' where id=%id%"
if "%id%" equ "" (
  echo %date% %time:~0,8%>>debug.txt
  echo ID不能为空>>debug.txt
  echo ID不能为空
  goto end
)
echo %date% %time:~0,8%>>sqlLog.txt
(
   echo use %database%;
  @echo %querySQL%;
)>>sqlLog.txt
(echo use %database%;%querySQL%;)|%loginSQL%

if %errorlevel% equ 0 (
    echo %date% %time:~0,8%>>log.txt
    echo 更新数据库题目状态成功,ID:%id%>>log.txt
) else (
    echo %date% %time:~0,8%>>log.txt
    echo 数据库更新出错: %errorlevel%,ID:%id%>>log.txt
    echo 请查看错误日志databaseErr.txt>>log.txt
    goto errUpdate
)
goto end
rem 判题程序错误则先更新题目状态为4
:errUpdate
(echo use %database%;%unknownErrSQL%;)|%loginSQL%
:end

后言

刚开始准备做判题是想用python的,临时起意用了Bat,对于一个刚入门脚本编写的新手这也算练练手吧,bug心情好就修复更新下吧,欢迎各位留言交流…趁没有人我是菜鸡(*^▽^*)
附上完整源码GitHub:TSOJ源码

你可能感兴趣的:(批处理脚本,shell,oj系统)