软件工程导论-个人项目

软件工程导论-个人项目

题目要求

用户:

小学、初中和高中数学老师。

功能:

1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码; while循环

2、登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。每道题目的操作数在1-5个之间,操作数取值范围为1-100;

3、题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准,见5);

4、在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;

5、生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;

6、个人项目9月11日晚上10点以前提交至创新课程管理系统。提交方式:工程文件打包,压缩包名为“几班+姓名.rar”。迟交2天及以内者扣分,每天扣20%。迟交2天及以上者0分。

img

代码分析

1. 用户类-User

根据题目需求,用户类应该主要拥有三个属性:用户名、密码和用户所属的学校种类。设置好User类的构造函数和getter、setter便于后续对其进行新增和修改操作。

定义makePaper方法用于生成对应类型的试卷。

注意:在判断字符相同时使用equals()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class User {
private String username;
private String password;
private String type;

public User(String username, String password, String type) {
this.username = username;
this.password = password;
this.type = type;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

//开始制作试卷
public void makePaper(User user, int num) {
PaperMaker paperMaker = new PaperMaker();
if (this.type.equals("小学")) {
paperMaker.makePrimary(user, num);
} else if (this.type.equals("初中")) {
paperMaker.makeJunior(user, num);
} else if (this.type.equals("高中")) {
paperMaker.makeSenior(user, num);
}
}
}
2. 试卷生成类-PaperMaker

因为题目分为三个难度,所以创建三个链表分别存储对应难度的符号,在构造函数中进行数据的初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//小学专用符号
private LinkedList<String> primary = new LinkedList<>();
//初中专用符号
private LinkedList<String> junior = new LinkedList<>();
//高中专用符号
private LinkedList<String> senior = new LinkedList<>();

public PaperMaker() {
primary.add("+");
primary.add("-");
primary.add("×");
primary.add("÷");
junior.add("²");
junior.add("√");
senior.add("");
senior.add("sin");
senior.add("cos");
senior.add("tan");
}

makePrimary是生成小学题目的方法:传入对象user和所需生成的题目数量,即可进行生成。

实现操作数随机,设置cnt计数器,固定最大数量为5;设置breakOrNot参数,该参数在每次添加数据后随机更新决定是否退出,来实现随机个操作数的功能。

实现随机括号功能,设置bracket参数记录未闭合的括号数量,设置bracketOrNot参数每轮取随机数来决定是否加入左括号,设置rightBracket参数每轮取随机数来决定是否加入右括号,算式生成结束后,根据bracket的值在式子末尾补全右括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//生成小学难度题目 生成数量是number

public void makePrimary(User user, int number) {
//生成多少道题目就执行多少次循环
for (int i = 0; i < number; i++) {
Random random = new Random();
StringBuffer stringBuffer = new StringBuffer();
//判断括号配对
int bracket = 0;
//判断退出
int breakOrNot = 0;
//操作数最多个数
int cnt = 5;
//保证操作数不超过5个
while (cnt-- > 0) {
//判断是否本轮退出
breakOrNot = random.nextInt(2);
//随机加入 (
int bracketOrNot = random.nextInt(3);
if (bracketOrNot == 1 && breakOrNot != 1 && cnt != 0) {
bracket++;
stringBuffer.append("(");
}

//随机生成1-100之间的操作数
int ran = random.nextInt(100) + 1;
stringBuffer.append(ran);


//判断 加入 )
int rightBracket = random.nextInt(2);
if (bracketOrNot != 1 && bracket > 0 && rightBracket == 1) {
stringBuffer.append(")");
bracket--;
}
//随机结束算式
if (cnt != 4) {
if (breakOrNot == 1 || cnt == 0) {
break;
}
}
//随机生成符号
int symbolSelect = random.nextInt(4);
String symbol = primary.get(symbolSelect);
stringBuffer.append(symbol);
}
//补全 )
for (int j = 0; j < bracket; j++) {
stringBuffer.append(")");
}
//输出 =
stringBuffer.append("=");
//生成试卷
IOController ioController = new IOController();
//生成试卷文件
File newFile = ioController.makeFile(user);
//存储题目
String title = stringBuffer.toString();
//题目查重
if (ioController.check(newFile, title) == true) {
i--;
continue;
}
//写入试卷
ioController.writeIn(newFile, i + 1, title);
}
}

makeJunior是生成初中题目的方法,逻辑基本与上文类似,新增随机加入根号和平方的方法。由于要确保每个算式至少有一个根号或平方,故在while循环外定义squareOrNot = 1来确保每个式子都有平方。

1
2
3
4
5
6
7
8
9
10
11
12
13
int squareOrNot = 1;

//随机加根号
int evolutionOrNot = random.nextInt(2);
if (evolutionOrNot == 1) {
stringBuffer.append(junior.get(1));
}

//随机加平方
if (squareOrNot == 1) {
stringBuffer.append(junior.get(0));
}
squareOrNot = random.nextInt(2);

makeSenior是生成高中题目的方法,逻辑基本与上文类似,新增随即加入三角函数的方法,不再做详细解释。

1
2
3
4
5
int tFunction = random.nextInt(3) + 1;                

//随机生成三角函数
stringBuffer.append(senior.get(tFunction));
tFunction = random.nextInt(4);
3.文件输出类-IOController

文件输出主要分三部分:

  1. 找到或生成对应的文件夹和txt文件

首先确定文件夹的路径,若之前该文件夹不存在则新创建这个文件夹。再获取当前时间,生成txt 文件的题目,创建这个txt文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//生成专用文件夹 和 txt文件
public File makeFile(User user) {
String mkPath = "D:\\JavaProject\\AutomationPaper\\paper\\" + user.getUsername();
File file = new File(mkPath);
Date date = new Date();
//时间处理 用于txt文件的名称
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
String title = simpleDateFormat.format(date) + ".txt";
if (!file.exists()) {
//单例模式 如果没有对应文件夹则生成
file.mkdirs();
}
File paper = new File(mkPath, title);
try {
paper.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
return paper;
}
  1. 文件的写入

没什么说的,直接看代码吧。

写完后记得调用flush()函数刷新文件,否则可能出现写入失败的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//写入文件
public void writeIn(File file, Integer n, String title) {
try {
//传入文件 确定类型为续写
FileWriter fileWriter = new FileWriter(file, true);
//序号
String num = n.toString() + ".";
fileWriter.append(num + " " + title);
//空一行
fileWriter.append("\r\n");
fileWriter.append("\r\n");
//刷新文件,关闭writer
fileWriter.flush();
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}

}
  1. 算式查重

遍历该txt文件的上一级目录,来获取这个用户所获取的所有试题,再对每一套试题进行遍历对比,若重复则返回true,否则返回false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public boolean check(File file, String title) {
//定义变量 是否重复
boolean repetition = false;
//获取txt文件上一级目录的路径
String parentPath = file.getParent();
File parent = new File(parentPath);
//遍历上一级目录,获得其下的所有文件
File[] files = parent.listFiles();
//一个一个文件进行查重对比
for (int i = 0; i < files.length; i++) {
try {
FileReader fileReader = new FileReader(files[i]);
BufferedReader bufferedReader = new BufferedReader(fileReader);
while (true) {
//读取一行数据
String usedTitle = bufferedReader.readLine();
//如果是空行就跳过
if (usedTitle == null) {
break;
}
//以" "为分隔符," "后面的就是算式
String[] uT = usedTitle.split(" ");
if (uT.length > 1) {
if (uT[1].equals(title)) {
System.out.println("重复");
//如果重复改变变量的值
repetition = true;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return repetition;
}
4. 登录类-Login

登陆类需存储当前已有用户的信息,负责用户的登录判断、操作判断和改变用户的类型。

信息初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
HashMap<String, User> userMap = new HashMap<>();

public Login() {
userMap.put("张三1", new User("张三1", "123", "小学"));
userMap.put("张三2", new User("张三2", "123", "小学"));
userMap.put("张三3", new User("张三3", "123", "小学"));
userMap.put("李四1", new User("李四1", "123", "初中"));
userMap.put("李四2", new User("李四2", "123", "初中"));
userMap.put("李四3", new User("李四3", "123", "初中"));
userMap.put("王五1", new User("王五1", "123", "高中"));
userMap.put("王五2", new User("王五2", "123", "高中"));
userMap.put("王五3", new User("王五3", "123", "高中"));
}

用户登录与操作判断

根据表内信息,由用户名获取密码来与输入的密码进行比较来判断是登陆成功。登陆成功后根据输入的数据长度来判断用户想进行切换操作、生成题目操作还是退出操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//用户登录
public void userLogin(String username, String password) {
User user = userMap.get(username);
System.out.println("当前选择为" + user.getType() + "出题");
//判断密码是否正确
if (user.getPassword().equals(password)) {
while (true) {
System.out.println("准备生成" + user.getType()
+ "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):");
String inStr = new Scanner(System.in).next();
//当输入的是 切换为XX 时
if (inStr.length() > 4) {
String sub = inStr.substring(0, 3);
if (sub.equals("切换为")) {
setUserType(username, inStr.substring(3, 5));
}
} else {//输入的是数字时
int num = Integer.parseInt(inStr);
if (num == -1) {
break;
} else if (num >= 10 && num <= 30) {
user.makePaper(user, num);
} else {
System.out.println("请输入10-30之间的自然数 或 输入-1退出");
}
}
}
} else {
System.out.println("请输入正确的用户名、密码");
}
}

改变用户类型

没什么好说的,传入用户名和要变成的类型即可。

1
2
3
4
5
6
7
8
9
10
11
12
//用于改变用户的类型
public void setUserType(String username, String type) {
if (type.equals("小学")) {
userMap.get(username).setType(type);
} else if (type.equals("初中")) {
userMap.get(username).setType(type);
} else if (type.equals("高中")) {
userMap.get(username).setType(type);
} else {
System.out.println("请输入小学、初中和高中三个选项中的一个");
}
}
5. 主类-Main

通过死循环来实现退出登录后不会结束程序,而是再次让用户输入用户名密码。用户名和密码通过空格分隔,故对密码进行去除空格(trim)处理。

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Login login = new Login();
while (true) {
//识别用户名和密码
System.out.println("请输入用户名、密码");
login.userLogin(in.next(), in.nextLine().trim());
}
}
}

说实话程序逻辑总体还是有点乱的,后续想在进行优化调整。