做项目前首先要弄清需求厘清思路,而不是上来就写代码,其实项目无非是获取需求、设计模块、代码实现需求、调试改进的过程,那就让我们从获取需求开始吧!
获取需求其实就是要实现什么?我们来看项目说明和功能描述:
【项目说明】
- 据了解,目前在校大学生80%以上有做兼职的需求,兼职打工已经不仅仅是经济困难的学生赚取生活费用的途径。调查显示,全球经济危机对就业产生冲击,用人单位对人员的社会实践能力要求提高,大学期间必要的社会实践既可以提高能力,又为适应社会,减轻家庭经济负担起到了积极的作用;社会中虚假兼职机构、欺骗性中介机构充斥,真伪难辨,学生受骗事件频发,极大的损害了学生的经济利益,甚至对学生的人生安全造成威胁。从校园踏入社会,兼职只是一段小小的插曲,通过兼职丰富阅历、增长社会经验固然是好事,可是毫无戒备的步入社会,不仅会事与愿违,还可能造成不必要的损所以。所以,我们需求设计一个校园兼职平台来方便在校学生找兼职。
- 本平台根据角色定位不同有不同的模块,首先系统有一个登录注册模块,属于兼职人员的模块有个人信息管理和兼职申请;属于管理员的模块有基本信息维护、兼职信息投放和系统管理。
- 平台登录注册:用户可以通过账号密码登录平台,如果用户是第一次使用该平台,则可以通过注册方式完成信息录入。平台根据用户输入的账号密码,判断该用户的角色是兼职人员还是管理员,展示不同的操作界面。如果用户忘记密码,可以通过自己的账号和注册时录入的真实姓名找回密码。
- 个人信息管理:能够对用户的基本信息进行管理,包括个人信息的修改,如学生转到别的专业,此时要修改学生的基本信息;修改密码,为了账户的安全性,可以修改用户的密码。查询信用等级,比如有一个用户一开始可以完成兼职等级最高的工作,但是在这次工作中,未能按时完成工作,于是管理员降低了该用户的信用等级,该用户就不能接高级的兼职工作了。查询余额,用户每次按时完成兼职能得到一笔费用,有逾期会扣除一部分佣金,最后得到总的余额。
- 兼职申请模块:用户能够查询所有的职位信息,因为信用等级的限制,用户也可以只查询自己能够申请的职位信息。查询到合适的职位用户可以直接申请,申请后到达工作截至时间前完成工作可以提前结束工作得到佣金,如果逾期后完成,管理员会调整用户的信用等级。用户可以查询自己已经完成过的兼职信息和自己正在完成的兼职信息。
- 基本信息维护模块:管理员能够根据用户完成兼职的情况动态的调整用户的兼职等级状态。管理员可以调整兼职的等级,供用户选择。管理员能够根据兼职的等级不同设置逾期费用扣除比例,比如最高级的兼职逾期一天扣10元,扣除兼职费用后继续扣除用户余额,直到用户余额为0,自动结束工作,结算费用。
- 兼职信息投放:管理员可以把所有公司的兼职招聘信息发布到网上,根据公司的需求设置岗位所需人员,工资以及兼职的时间段,比如发传单,这份兼职需要周一到周五的上午9点至11点工作,那么在这个时间段已经有了其他工作的用户就不能申请该工作。
- 系统管理模块:管理员能够删除管理员账户,也能删除平台用户以及一些完成过的兼职信息。管理员能够添加平台用户信息到系统,本平台是针对学生的软件,所以能够注册的只能是学生,所以平台需要提前将学生信息录入系统,用户才能注册账号,如果未能及时录入信息照成无法注册,那么就需要联系管理员进行信息录入。管理员可以添加其他的管理员账户,为了安全起见,管理员和普通用户一样能够修改密码。管理员还能够查询所有的用户信息、管理员账户信息、已经投放的职位信息、已经完成的兼职信息和已经逾期的兼职信息。
【项目功能】
(1)登录注册功能:用户通过注册进入平台,注册信息包括用户编号(用户编号决定角色类型,学号注册为兼职人员,教师编号注册为管理员),注册后到登录界面输入账号和密码登录平台,注册信息包括:用户编号(学号/教师编号)、密码、姓名、出生日期、性别、电话、所在院系、注册日期等。后期如果忘记了密码,用户通过自己的账号和注册时的姓名找回密码。注意,为了安全起见,只有已经录入系统的用户可以注册账户,本平台提前录入了部分管理员和学生的信息到系统中供大家注册。(相关存储数据可通过I/O流直接存在文件,可也直接在文件里读的数据)
(2)个人信息管理:包括个人信息修改、密码修改、查询信用等级和查询余额功能。普通用户登录成功之后,可以修改个人注册时录入的信息,学号和注册时间不可更改,密码单独修改;也可以查询个人的信用等级,若信用等级不是最高,则查询个人信用等级时附加显示“按时完成X次后信用等级恢复至X级”(默认未逾期完成兼职工作3次恢复1级信用等级,等级梯度为1-5级,5级最高)。(相关存储数据可通过I/O流直接存在文件,可也直接在文件里读的数据)
(3)兼职申请功能:包括兼职信息查询、申请工作和结束工作。用户登录成功之后,可以查询所有的兼职信息,包括自己能申请的职位和不能申请的职位,也可以只查看自己能申请的职位(是否可申请与信用等级、岗位所需人员、兼职时间相关)。用户还可查看自己已完成的兼职信息和自己正在完成的兼职信息。满足条件的兼职,用户可以申请,开始工作时间为系统当前时间,相对的该兼职岗位对应的所需人员需要减少;完成工作后(逾期完成信用自动降低1级,0级为最低,0级后不可再申请兼职工作),需要计算所得薪酬(所得薪酬和是否逾期、逾期天数相关),若未逾期且信用等级未满,则需要计算恢复信用等级所需的次数,完成时间手动输入,不可小于兼职开始时间。(相关存储数据可通过I/O流直接存在文件,可也直接在文件里读的数据)
(4)基本信息维护功能:包括兼职等级设置、用户信用调整和逾期费用扣除比例调整。管理员端登录成功之后,可以调整兼职的等级(兼职等级梯度为一-五级,五级最高,一级最低),普通用户的信用等级>=兼职等级可申请职位,还可以增删兼职的类型,比如开发外包/销售/设计师等。管理员可以根据用户的表现动态地调整用户的信用等级,可越级增减,0级信用的普通用户管理员不可调整其信用等级。管理员还可以调整兼职的逾期费用,每天逾期扣除的费用相同,比如:原定一份兼职总薪资1000元,工作要求10天完成,逾期1天扣除100元,调整后一天扣除200元(扣除兼职费用为0后继续扣除用户余额,直到用户余额为<=0,自动结束工作,结算费用)。(相关存储数据可通过I/O流直接存在文件,可也直接在文件里读的数据)
(5)兼职信息投放:包括兼职人员设置、兼职的金额设置、兼职的时间录入以及兼职的其他信息录入。系统管理员登录成功之后可以把所有公司发布的兼职信息录入到平台,录入信息包括:营业执照编号、公司名字、岗位名称、兼职类型、兼职等级、工作内容、需求人数、工作时间等。系统管理员可以根据市场和公司要求,动态调整这些兼职工作的部分内容,比如需求人数、薪水等,工作时间不允许更改。(相关存储数据可通过I/O流直接存在文件,可也直接在文件里读的数据)
(6)系统管理功能:包括对普通用户、管理员和兼职信息的增删查改等。系统提前录入了一些管理员或者学生的信息在系统中,但是未全部录入,所以后续需要注册的用户需要已经录入的管理员登录平台进行信息录入,系统录入仅需要学号/教师编号和姓名即可,此信息供用户注册使用。管理员可以删除普通用户和别的管理员,以及没有用的兼职信息。(相关存储数据可通过I/O流直接存在文件,可也直接在文件里读的数据)
而且还有其他注意事项:
【项目注意】
- 注意命名(类名,包名,方法名,注释等),请严格遵循Java命名规则完成项目。
参考文献:https://blog.youkuaiyun.com/yang_best/article/details/42169549
- 每一个业务逻辑操作需要加上日志打印功能(用I/o流实现,比如什么时候进行了增删改等操作,需要有记录,或者发现异常了也需要将信息同步在日志文件里)
- 每个功能模块,如果有异常抛出,需要自定义异常来进行处理
看完了功能描述和前情提要我直接眼前一黑,啥啥啥?这都是啥!
整理要求和实现功能,可得到如下结构图:
【项目实现结构图】
脑图链接:
https://naotu.baidu.com/file/b58117cb7a0a11f777301eb1fe039766
编写步骤:
完成类的创建,将类创建后放在domain包里;
根据要求确定步骤和方法:
为了安全起见,只有已经录入系统的用户可以注册账户,本平台提前录入了部分管理员和学生的信息到系统中供大家注册。(相关存储数据可通过I/O流直接存在文件,可也直接在文件里读的数据)
在File包中建一个文本文件,提前录入信息
在这里 我设置t开头的用户为管理员用户,其他的s开头的是普通用户。
在使用代码实现序列化时,发现报错标红
Add exception to method signature 向方法签名添加异常
添加方法后正常 。
关于方法是否返回以及返回类型的取舍
一开始方法都是静态类型static void ,但是在判断用户状态时需要.isAlive,返回一个boolean类型的的结果,因此后面为了方便方法的编写可以都写blooean类型,默认返回true
在view模块编写方法,首先是mian方法,其中用输出语句完成主界面的编写
// 用循环语句再次回到主界面
while (true) {
//用输出语句完成主界面的编写
System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆");
System.out.println("\t\t欢迎来到校园兼职平台");
System.out.println("1 注册账号");
System.out.println("2 登录账号");
System.out.println("3 查看账号信息");
System.out.println("4 退出");
System.out.println("☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆");
System.out.println("请输入您的选择:");
// 用Scanner语句实现键盘录入
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
// 用switch语句完成操作选择
switch (line) {
case "1":
// System.out.println("注册账号");
loginUser(arrary);
break;
case "2":
System.out.println("登录账号");
break;
case "3":
// System.out.println("查看账号信息");
findUser(arrary);
break;
case "4":
System.out.println("您已成功退出,谢谢使用!");
System.exit(0); //JVM退出
}
}
}
//思路 //用键盘录入选择删除账号信息 //显示提示信息 //键盘录入要删除的学生学号 //遍历集合将对应用户对象从集合中删除给出删除成功提示 //调用方法
删除账号时因为没有添加限制条件,所以即便没有内容也会错误输出“删除成功”字样
sc.nextInt与sc.nextLine常见出错
sc.next()类同于sc.nextInt(),都是以空格或回车换行作为结束
sc.nextLine()以回车换行作为结束.
通过键值对将用户编号及用户名一一对应,可以读入并在控制台输出
从代码中输入的信息,包括日志时间和uid以及用户姓名:
自定义异常
建了exception的包,里面建一个名为MyException的自定义异常,继承自它的超类Exception,包含无参和有参构造
Expected 1 arguments but found o 预期有1个参数,但找到o
找到registerMenu方法,给定参数即可
关于年龄的判断及String与Int的类型转化
在输入时为了便于编写代码,年龄的类型是String,但是在输入时要加以限制,比如招收兼职工作的法定最低年龄是18岁,而教职工的最高法定年龄是70岁,所以要先在输入时用正则表达式来约束,我编写的约束为:
^(?:1[8-9]|(?:2[0-9])|(?:3[0-9])|(?:4[0-9])|(?:5[0-9])|(?:60)|(?:6[0-9])|(?:70))$
正则表达式具体的编写规则和常用代码可参考:
正则表达式 – 教程 | 菜鸟教程 (runoob.com)
但是在判断时犯了难,因为字符无法用if语句来判断,这时候就需要将String类型转化为int类型了:
int i = Integer.parseInt(age);
之后就可以添加判断了。
System.out.println("请输入您的年龄(18-70):");
String age = TSUtility.inputCheck(2, 2, "^(?:1[8-9]|(?:2[0-9])|(?:3[0-9])|(?:4[0-9])|(?:5[0-9])|(?:60)|(?:6[0-9])|(?:70))$","请选择与之对应的适龄工作哦~");
// 将String转化为Int
int i = Integer.parseInt(age);
if (i<18 || i >70){
System.out.println("您输入的年龄有误,请重新核对");
}
String转int的几种常用方法
String类型转int类型通常需要int的包装类Integer,该类有三个方法可以实现这种转换,分别为decode(String s)、parseInt(String s)、valueOf(String s)。
那么他们有哪些不同呢?下面看看API文档中的介绍:
decode(String s):
将 String
解码为 Integer
。接受通过以下语法给出的十进制、十六进制和八进制数字;
parseInt(String s)或parseInt(String s, int radix)
将字符串参数作为有符号的十进制整数进行解析。除了第一个字符可以是用来表示负值的 ASCII 减号 '-'
('\u002D'
) 外,字符串中的字符都必须是十进制数字。返回得到的整数值,就好像将该参数和基数 10 作为参数赋予parseInt(java.lang.String, int)
方法一样。
valueOf(String s)
返回保存指定的 String
的值的 Integer
对象。将该参数解释为表示一个有符号的十进制整数, 就好像将该参数赋予parseInt(java.lang.String)
方法一样。结果是一个表示字符串指定的整数值的Integer
对象。
可以看出decode(String s)的功能比较强大,因为它不仅可以转换10进制,还可以转换其他的进制。
键盘输入注册,输入了,但是没写入文件中
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
欢迎来到校园兼职平台
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
🐕
🐕
🐕-----<请先注册或登录>-----🐕
按回车键继续...
1.注册 2.登录 3.忘记密码 4.退出
请选择:1
请输入您的学号/教师编号作为账号uid(至多8个字符):
stu006
请输入您的用户名:
海山了
请输入您的密码(3-12位):
123
请输入您的性别(男/女):
男
请输入你的电话:
15770378817
请输入您的出生日期,格式‘2000-02-02’:
1999-01-23
请输入您的年龄(18-70):
23
请输入您所在院系,格式“数工学院”/“法学院”:
数工学院
信息录入完成.
NotSerializableException异常
抛出一个实例需要一个Serializable接口。 序列化运行时或实例的类可能会抛出此异常。 参数应该是类的名称。
报错原因:未接入Serializable接口
解决方法:
接入Serializable接口:
在每个实体类后面加 implements Serializable
给对象所属的类加一个serialVersionUID :
在实体类后面加 private static final long serialVersionUID = 42L(可自定义);
数据写入成功!
Exception in thread "main" java.lang.NullPointerException
线程“main”中的异常java.lang.NullPointerException
错误: main 方法不是类 Project.campusparttimejob3.view.IndexView 中的static, 请将 main 方法定义为:
public static void main(String[] args)
Exception in thread "main" java.lang.NullPointerException
空指针异常出现的原因:
注册信息未写入ArrayList集合arr中,得从中调取出来对比
解决方法:
将uid和password写入currentUser当前用户,调用对比
//实现登录功能
public static boolean login(String uid, String password) throws IOException, ClassNotFoundException {
// 反序列化(将字节序列转化为java对象,便于人们观看)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(UserService.INF));
ArrayList<User> list = (ArrayList<User>) ois.readObject();
//遍历后比较设置的账号和密码
for (User user : list) {
if (uid.equals(user.getUid())&&password.equals(user.getPassword())) {
// .isAlive()查看当前线程是否活动
if (!user.isAlive()) {
System.out.println("您的账户已被禁用,详情请联系管理员");
return false;
}
// 将uid和password写入currentUser当前用户
CurrentUser.currentUser.setUid(uid);
CurrentUser.currentUser.setPassword(password);
return true;
}
}
ois.close();
return false;
}
ParseException 表示解析时意外出现错误
java.text.ParseException: Unparseable date: "1661216803509"
试图通过输入的出生年月日使用时间戳获取年龄,但是出现了解析错误,Unparseable data 不可解析数据
DateFormat是只读的,无法更改
出错原因:
之前为了方便录入设置的年龄是String类型,需要手动输入,但是后来使用Date中的时间戳获取年龄构造时强转了类型→String.valueof
解决方法:
因为最后时间和开始时间数据类型都为Long,所以年龄的数据类型也设为Long
Date end = new Date();//结束时间(当前时间)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("当前时间为:"+sdf.format(end));
try {
Date star = sdf.parse(birthday);//开始时间 //转换成日期
Long starTime=star.getTime();//时间转换成毫秒值
Long endTime=end.getTime();
Long num=endTime-starTime;//时间戳相差的毫秒数
Long day = num/24/60/60/1000;//除以一天的毫秒数
Long age = num/24/60/60/1000/365;//除以一年的毫秒数
System.out.println("出生至今的天数为:"+day);
System.out.println("您的年龄为:"+age);
temp.setAge(age);
} catch (ParseException e) {
e.printStackTrace();
}
将对象静态化去掉就能正常读出
写入成功
在登录时明明已经成功输入密码和UID却还是进入循环,输出提示语句,并且只有五次机会,
// 登录界面
// • 实现注册方法
//• 如果没有账户则需要注册
//• 如果有账号则直接进行登录
//• 实现登录功能
//• 判断用户输入的值是否正确
//• 如果正确则进入软件菜单
public void loginMenu() throws IOException, ClassNotFoundException, ParseException, InterruptedException {
Map<String, Integer> count = new HashMap<String, Integer>();
Scanner sc = new Scanner(System.in);
Wangxx:
for (; ; ) {
System.out.println("====================");
System.out.println("==\t 登录界面\t ==");
System.out.println("====================");
System.out.println("请输入uid:");
String uid = sc.next();
if (count.containsKey(uid) && count.get(uid) >= 5) {
System.out.println("您的账户已经被停用,请联系管理员");
continue;
}
System.out.println("请输入密码:");
String password = sc.next();
if (ls.login(uid, password)) {
System.out.println("登录成功!欢迎您!" + uid);
//IndexView.softwareMainMenu();
} else {
ObjectInputStream ios = new ObjectInputStream(new FileInputStream(UserService.INF));
ArrayList<User> list = (ArrayList<User>) ios.readObject();
for (User user : list) {
if (user.getUid().equals(uid)) {
if (count.containsKey(uid)) {
count.put(uid, count.get(uid) + 1);
System.out.println("登陆次数还剩" + (5 - count.get(uid)) + "次");
} else {
count.put(uid, 1);
System.out.println("登陆次数还剩" + (5 - count.get(uid)) + "次");
}
if (count.get(uid) == 5) {
user.setAlive(false);
System.out.println("登录次数还剩0次,该账户已停用!");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(UserService.INF));
oos.writeObject(list);
oos.flush();
oos.close();
oos.close();
}
continue Wangxx;
}
}
System.out.println("账户未被注册");
}
}
}
//实现登录功能
public boolean login(String uid, String password) throws IOException, ClassNotFoundException {
// 反序列化(将字节序列转化为java对象,便于人们观看)
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(UserService.INF));
ArrayList<User> list = IOUtils.myDeserializable(INF);
//遍历后比较设置的账号和密码
while (true){
for (User user : list) {
if (uid.equals(user.getUid()) && password.equals(user.getPassword())) {
// .isAlive()查看当前线程是否活动
if (!user.isAlive()) {
System.out.println("您的账户已被禁用,详情请联系管理员");
return false;
}
// 将uid和password写入currentUser当前用户
CurrentUser.currentUser.setUid(uid);
CurrentUser.currentUser.setPassword(password);
System.out.println("您已成功登录!");
return false;
}
}
System.out.println("请重新确认您的账号和密码!");
return false;
}
}
原来是登录方法返回值返回了一个false,我以为是标志位,但其实是之前方法类型为boolen,只会返回true/false,返回false其实不是终止而是进入else,执行登录次数减去的操作
解决方法:
成功登录返回true即可
//实现登录功能
public boolean login(String uid, String password) throws IOException, ClassNotFoundException {
// 反序列化(将字节序列转化为java对象,便于人们观看)
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(UserService.INF));
ArrayList<User> list = IOUtils.myDeserializable(INF);
//遍历后比较设置的账号和密码
while (true){
for (User user : list) {
if (uid.equals(user.getUid()) && password.equals(user.getPassword())) {
// .isAlive()查看当前线程是否活动
if (!user.isAlive()) {
System.out.println("您的账户已被禁用,详情请联系管理员");
System.exit(0);
}
//// 将uid和password写入currentUser当前用户
// CurrentUser.currentUser.setUid(uid);
// CurrentUser.currentUser.setPassword(password);
System.out.println("您已成功登录!");
return true;
}
}
System.out.println("请重新确认您的账号和密码!");
return false;
}
这样就不会在方法里一直调用了。