软 件 项 目 技 术 报 告
编写人:学号:日期:
一、 项目背景
1. 项目实施主要目的
在我国的各级人民法院,为了记录庭审现场各类人员(包括:司法人员、嫌疑人、律师等)的谈话内容,有一批工作人员专门负责此项工作,这批工作人员被称之为“速录员”。速录员要求具有很快的文字录入速度和较高的录入准确率,以保证庭审记录的完整性和准确性。因此,如何有效地评测速录员录入文字的速度以及准确率是各级人民法院在招聘和考核速录员时需要面对的难题。此项目是为评测速录员素质编写的测试程序。
2. 编写意义
快速判断出使用人员的打字速度与准确率,保证法院录取速录员时能高素质的人员。
3. 相关背景知识
Visual Studio 2013下使用C++语言独立开发。
二、 项目研究内容
1. 音频播放问题
系统的主要功能为测试考试人员在听到对话时能否快速记录下来的能力,因此系统需要播放音频以供考试人员进行测试。
通过使用MCI(Media Control Interface,媒体控制接口)中的相关函数,成功为系统添加了音频。
系统提供了不止一个题目供人员考试,因此在考试人员选择相应的考试编号时,需要播放相应的音频。
通过使用MFC中组合框的相应功能,switch (m_combo.GetCurSel()),获取组合框中的信息,播放相应音频。
在考试人员选择提交后,或者当音频播放完毕后,需要结束音频播放。用到了自定义函数OnStop()。
2. 身份证校验
考试进行之前需要考试人员填写考生号和身份证号码已确认身份信息,如何确认输入的身份证号码是否合法是项目研究遇到的第二个问题。
考虑到中国人民共和国居民身份证的位数为18位,而第十八位的数字或字母是由前17位数字所决定的,在阅读相应资料和上网查找对应代码后,了解到了具体的实现方法。
1、将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2。
2、将这17位数字和系数相乘的结果相加。 3、用加出来和除以11,看余数是多少?
4、余数只可能有0-1-2-3-4-5-6-7-8-9-10这11个数字。其分别对应的最后一位身份证的号码为1-0-X -9-8-7-6-5-4-3-2。
5、通过上面得知如果余数是3,就会在身份证的第18位数字上出现的是9。如
果对应的数字是10,身份证的最后一位号码就是罗马数字x。
例如:某男性的身份证号码为【53010219200508011x】,我们看看这个身份证是不是合法的身份证。
首先我们得出前17位的乘积和
【(5*7)+(3*9)+(0*10)+(1*5)+(0*8)+(2*4)+(1*2)+(9*1)+(2*6)+(0*3)+(0*7)+(5*9)+(0*10)+(8*5)+(0*8)+(1*4)+(1*2)】是189,然后用189除以11得出的结果是189/11=17----2,也就是说其余数是2。最后通过对应规则就可以知道余数2对应的检验码是X。所以,可以判定这是一个正确的身份证号码。
为了更加确保输入身份证件号的合法性,除了对最后一位进行校验以外,还应该对日期年份进行校验。
首先获取到用户输入的身份证号,第七位到第十位为用户的出生年份。根据用户输入的出生年份首先判断是否为闰年,若为闰年且第十一位和第十二位的月份为二月,那么十三到十四位的日期必须为小于等于28的数字。且除一三五七八十腊月,其他月份的日期没有31号。
以上保证了用户输入身份证的合法性。
3. 倒计时功能
评测人员在测试时需要系统有倒计时功能限制考试人员的答题时间,如何为系统添加倒计时功能,并且在
考试人员提交时或者计时为零时停止计时,是编写项目遇到的第三个问题。
与其他人讨论获知,MFC中有相应的计时函数OnTimer(UINT_PTR nIDEvent),此函数在执行
SetTimer(.., …., ..);时被调用,第一个参数为时间器的编号,第二个参数为每多少毫秒调用OnTimer(),若实现倒计时功能,则设置为1000毫秒,并且在OnTimer()函数中设置总时间减少1,在资源视图中设置一个专为倒计时准备的编辑框,让倒计时的信息在编辑框中显示,这样实现了倒计时的功能。
在运行测试时又遇到了另一个问题,在SetTimer()函数每运行一次时,其相应的编辑框会刷新一次已更新时
间,但是其他的编辑框也会更新,这样用户在答题编辑框打字时也会被刷新掉。如何只让倒计时所在的编辑框刷新而又不影响其他控件呢?
自行研究后,使用了以下代码:
CEdit *pedit = (CEdit*)GetDlgItem(IDC_time);//这样获取Edit编辑框的指针
pedit->SetWindowText(m_time);
这样就实现了只让一个编辑框刷新而不影响其他编辑框的输入。
4. 配置文件
计算用户成绩时需要有相应的权重和参数,这些参数和权重需要放在配置文件中读取,如何读取配置文件的内容是遇到的下一个问题。 根据上网查阅资料可知,
GetPrivateProfileInt(_T(\节名
_T(\项名 0, //没找到此项时的缺省返回值
_T(\配置文件的准确路径
可以获取到相应的值,使用时应该注意文件的准确路径。
5. 成绩评定
成绩评定中最重要的是通过编辑距离(Edit Distance)算法,计算录入文字与标准答
案之间的相似度,即:准确率。
用m*n的矩阵存储距离值。算法大概过程: str1或str2的长度为0返回另一个字符串的长度。
初始化(n+1)*(m+1)的矩阵d,并让第一行和列的值从0开始增长。
扫描两字符串(n*m级的),如果:str1[i] == str2[j],用temp记录它,为0。否则temp记为1。然后在矩阵d[i][j]赋于d[i-1][j]+1 、d[i][j-1]+1、d[i-1][j-1]+temp三者的最小值。
扫描完后,返回矩阵的最后一个值即d[n][m]
最后返回的是它们的距离。怎么根据这个距离求出相似度呢?因为它们的最大距离就是两字符串长度的最大值。对字符串不是很敏感。现我把相似度计算公式定为1-它们的距离/字符串长度最大值
public static float similarity(String str1, String str2) { //计算两个字符串的长度。 int len1 = str1.length(); int len2 = str2.length(); //建立数组,比字符长度大一个空间 int[][] dif = new int[len1 + 1][len2 + 1]; //赋初值, 步骤B。
for (int a = 0; a <= len1; a++) { dif[a][0] = a; }
for (int a = 0; a <= len2; a++) { dif[0][a] = a; } //计算两个字符是否一样,计算左上的值 int temp; for (int i = 1; i <= len1; i++) { for (int j = 1; j <= len2; j++)
{ if (str1.charAt(i - 1) == str2.charAt(j - 1)) { temp = 0; } else { temp = 1; } //取三个值中最小的
dif[i][j] = min(dif[i - 1][j - 1] + temp, dif[i][j - 1] + 1, dif[i - 1][j] + 1); } } return 1 - (float) dif[len1][len2] / Math.max(str1.length(), str2.length()); } //得到最小值
public static int min(int... is) { int min = Integer.MAX_VALUE; for (int i : is) { if (min > i) { min = i; } } return min; }
上网查到以上资料后解决了计算准确率的方法。
6. 打印文件
在计算完成绩后,需要将相应信息打印到PDF文件中,如何将已知信息打印成.pdf文件是项目完成的最大问题,经过讨论和查阅相应资料,了解到打印PDF文件需要利用PDFLib工具。
在此之前,需要向项目中添加运行PDFLib的几个文件,
并且在项目cpp文件中写上相应的代码:
7. 文件加密
为了保证标准答案文件的安全性,需要将其加密处理,防止评测人员找到标准答案文件,拷贝其中内容。
因为输入的答案文件有中文,因此将程序换成多字节字符集,用来改变中文的编码,实现加密文件的功能。
答案文件中有一个表示符,当标识符为0时,说明此文件未被加密,为1时,说明文件已经加密,程序运行时,自动将为加密的文件进行加密,以下是加密方法。
首先定义出一个key,为编码移位的位数。 Result = str; // 初始化结果字符串 行操作
}
{ }
str = Result; // 保存结果 Result.Empty(); // 清除结果
for (ii = 0; ii < str.GetLength(); ii++) // 对加密结果进行转换 {
jj = (BYTE)str.GetAt(ii); // 提取字符 // 将字符转换为两个字母保存 str1 = \设置str长度为2
str1.SetAt(0, 65 + jj / 26);//这里将65改大点的数例如256,str1.SetAt(1, 65 + jj % 26); Result += str1;
Result.SetAt(ii, str.GetAt(ii) ^ (Key >> 8)); // 将密钥移位后Key = ((BYTE)Result.GetAt(ii) + Key)*C1 + C2; // 产生下
for (ii = 0; ii 与字符异或 一个密钥 密文就会变乱码,效果更好,相应的,解密处要改为相同的数 加密完成后将内容写入原文件,讲标识符置为1 相应的,在考试人员提交后,需要将答案文件进行解密,然后和提交的答案进行比较,解密文件的方法和加密一样。 CString str1; j += (BYTE)str.GetAt(2 * i + 1) - 65; str1 = \设置str长度为1 str1.SetAt(0, j); Result1 += str1; // 追加字符,还原字符串 Result1.Empty(); // 清除结果 for (i = 0; i < str.GetLength() / 2; i++) // 将字符串两个字母一组进行处理 { j = ((BYTE)str.GetAt(2 * i) - 65) * 26;//相应的,解密处要改为相同的数 int i, j=0;