文章目录
1.项目需求
中国移动,中国联通,中国电信是国内三大通信运营商,每个运营商都提供了不同的品牌套餐来应对不同的用户群,比如北京移动主要有全球通,神州行,动感地带等三大品牌套餐,每种套餐内容和费用不同,嗖嗖移动是一个假定的通信运营商,提供了三种套餐,有话痨套餐,网虫套餐,超人套餐,各种套餐的服务内容及费用如下表:
品牌套餐 | 话痨套餐 | 网虫套餐 | 超人套餐 |
---|---|---|---|
通话时长(分钟) | 1000 | 100 | 600 |
上网流量 | 10 | 40 | 20 |
短信条数(条) | 100 | 100 | 50 |
费用(元/月) | 58 | 68 | 78 |
如实际使用中超出套餐内包含的通话时长,短信条数和上网流量,则按一下规则计费:
- 超出的通话: 0.2元/分
- 超出的短信:0.1元/条
- 超出的上网流量:0.1元/MB
本任务实现的"嗖嗖移动业务大厅"提供了嗖嗖移动用户的常用功能,包括新用户注册,本月账单查询,套餐余量查询,打印消费详情,套餐变更,办理退网,话费充值,查看消费记录,查看话费说明等功能.另外,还可以模拟用户通话,上网,发送短信的场景进行相应的扣费并记录消费信息.各功能介绍如下表:
菜单级别 | 功能 | 描述 |
---|---|---|
主菜单 | 用户登录 | 输入正确的手机号码和密码进入二级菜单列表 |
主菜单 | 用户注册 | 录入信息并开卡,用户输入的信息包括:选择卡号,选择套餐类型,输入用户名和密码,预存话费金额(预存话费金额必须满足以支付所选套餐的一个月的费用) |
主菜单 | 话费充值 | 输入正确的用户名和密码之后,可为该卡号充值 |
主菜单 | 资费说明 | 提供各品牌套餐所包含的通话时长,上网流量,短信条数,月费用等 |
主菜单 | 退出系统 | 提出本系统 |
二级菜单 | 使用嗖嗖 | 登录成功后,随机进入一个场景,消费套餐余量或者话费余额,并记录消费信息.当话费余额不足时,抛出异常提醒用户充值 |
二级菜单 | 本月账单查询 | 可查询该卡号的套餐费用,实际消费金额,账户余额 |
二级菜单 | 套餐余量查询 | 可查询该卡号的套餐余量 |
二级菜单 | 消费详情 | 可以根据月份输出消费详情,根据客户需求可选择打印当前卡号用户的消费详单, 使用输出流把用户信息输出到文件 |
二级菜单 | 套餐变更 | 可变更为其他套餐类型,变更后话费余额需减去变更后的套餐费用,余额不足时需要给出信息提示,套餐变更后重新统计卡中实际消费数据以及当月消费金额 |
二级菜单 | 办理退网 | 登录成功后可以从已注册的号码在数据库中的全部数据,并退出系统 |
2.项目使用的技术
- 面向对象的思想
- 封装,继承,多态,接口的使用
- 异常处理的合理使用
- 集合框架的使用
- I/O 操作实现对文件的写
- MySQL数据
- JDBC操作数据库
3.项目需求分析
3.1 实体类接口
-
Card(电话号码类)
- cardNumber 卡号
- status 状态
-
MoboleCard(嗖嗖移动卡类)
- cardNumber 卡号
- username 用户名
- password 密码
- serPackage 所属套餐
- money 账户余额
- status 状态
-
monthlyConsumptionRecords(月消费记录类)
- cardNumber 卡号
- consumAmount 当月消费金额
- realTalkTime 当月实际通话时长
- realSMSCount 当月实际发送短信条数
- realFlow 当月实际上网流量
- consumeDate 消费日期
-
套餐类 SerPackage
- talkTime 通话时长
- smsCount 短信条数
- price 套餐月资费
- flow 上网流量
- type 套餐类型
-
套餐类型类 SerPackageType
- name 套餐名称
-
ConsumInfo(消费信息类)
- cardNumber 卡号
- type 消费类型
- consumData 消费数据
- consumeDate 消费日期
-
Scene(使用场景类)
- type 场景类型
- data 场景消费数据
- description description
-
RechargeRecord(充值记录类)
- amount 充值金额
- rechargeDate 充值日期
- cardNumber 卡号
4.程序流程图
5.功能模块分析
5.1 用户登录
主要思路:首先用户输入手机号码和密码,将手机号码传给服务层,如何再传到Dao层,Dao层传出用户信息到表示层,表示层判断号码是否被冻结,是否欠费,如果冻结就不准登录同时提示充钱激活,如果欠费就提醒欠费。
代码演示:
private void login() {
System.out.print("请输入手机号码:");
card_number = scanner.next();
System.out.print("请输入密码:");
password = scanner.next();
//传号码,得数据,mobole存储用户数据
moboleCard = moboleCardService.freeze(card_number);
if (moboleCard != null) {
//判断是否冻结,status:0.正常 1.冻结
if (moboleCard.getStatus() == 0) {
String reply = moboleCardService.login(card_number, password);
System.out.println(reply);
if ("登录成功".equals(reply)) {
if (moboleCard.getMoney() < 0) {
System.err.println("您已欠费,请及时充值,如果欠费达到50元我们将对你的手机号进行冻结!");
}
secondMenu();
}
} else {
System.out.println("对不起,您的手机号欠费" + -moboleCard.getMoney() + "已经被冻结,无法登录!\n请及时联系管理员或将欠费还清以解除冻结!");
System.out.print("是否充值(1.不充值 其他键即为充值):");
if (scanner.nextInt() != 1) {
rechargeSoso(card_number);
}
}
} else {
System.out.println("请输入正确的手机号!");
}
firstMenu();
}
5.2 用户注册
主要思路:首先输入注册信息,在选择电话号码时我弄了个上一页和下一页,让用户有更多的选择空间。选择完套餐就是输入充值金额,如果金额不足以支付套餐金额,就重新输入金额,然后就存入数据库,并记录扣费信息
主代码演示:
private void enroll() {
MonthlyConsumptionRecord monthly = new MonthlyConsumptionRecord();
List<Card> cards = cardService.chooseCard();
//获取现在的时间对于的天,后面用
LocalDate now = LocalDate.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("d");
String nowDay = dtf.format(now);
System.out.println("********************************************************");
if (!cards.isEmpty()) {
int page = 1;
int num = 0;
//选择手机号码,选择后存入全局变量card_number
chooseCard(cards, page, num);
System.out.print("请输入你的姓名:");
String username = scanner.next();
System.out.print("请输入你的密码:");
password = scanner.next();
//展示套餐
showPackage();
int ser_package;
//如果输入的数据不合法就重新输入
do {
System.out.print("请选择你想要的套餐:");
ser_package = scanner.nextInt();
} while (ser_package < 0 || ser_package > 4);
outer:
do {
System.out.print("请输入你的充值金额:");
Double money = scanner.nextDouble();
for (SerPackage serPackage : serPackages) {
//判断输入的金额是否满足套餐所需金额
if (serPackage.getId().equals(ser_package)) {
if (serPackage.getPrice() > money) {
System.out.println("你充值的金额不足!");
} else {
//每月一号收取月租,防止在一号注册时扣两次月租
if (!nowDay.equals("1")) {
money -= serPackage.getPrice();
}
String reply = moboleCardService.enrollSoso(new MoboleCard(card_number, 0, username, password, ser_package, money));
System.out.println(reply);
//将消费数据传入消费数据表
monthly.setCard_number(card_number);
monthly.setConsum_amount(money);
monthly.setReal_talk_time(0);
monthly.setReal_SMS_count(0);
monthly.setReal_flow(0);
monthlyConsumptionRecordService.update(monthly);
if ("注册失败".equals(reply)) {
updateCard(0);
}
break outer;
}
}
}
} while (true);
} else {
System.out.println("没有多余的手机号码可以注册!");
}
firstMenu();
}
展示手机号码代码:
private void chooseCard(List<Card> cards, int page, int num) {
System.out.println("\n***************************************");
System.out.println("请从下面的手机号码中挑选:");
boolean bo = true;
do {
System.out.print((num % 10 + 1) + "." + cards.get(num).getCard_number() + "\t");
num++;
//每一行五个,换行
if (num % 5 == 0 || num == cards.size()) {
System.out.println();
}
//一页输出十个
if (num % 10 == 0 || num == cards.size()) {
//计算页数
int maxPage;
if (cards.size() % 10 == 0) {
maxPage = cards.size() / 10;
} else {
maxPage = cards.size() / 10 + 1;
}
System.out.println();
System.out.println("当前为第" + page + "页");
System.out.println("1.上一页\t2.下一页\t3.选择手机号码");
System.out.print("请选择:(选择其他键返回第一页)");
switch (scanner.nextInt()) {
case 1:
if (page != 1) {
page--;
num = (page - 1) * 10;
} else {
num = 0;
System.out.println("这是第一页,没有上一页了!");
}
chooseCard(cards, page, num);
break;
case 2:
if (page != maxPage) {
page++;
} else {
//如果是最后一页,将num加的值退回,重新输出这一页
if (num % 10 == 0) {
num -= 10;
} else {
num -= num % 10;
}
System.out.println("这是最后一页了!");
}
chooseCard(cards, page, num);
break;
case 3:
int maxIndex = 10;
int choose;
if (page == maxPage) {
maxIndex = cards.size() % 10;
}
do {
System.out.print("请输入你想要的手机号码的编号:");
choose = scanner.nextInt();
if (choose > maxIndex) {
System.out.println("请重新输入!");
}
} while (choose > maxIndex);
//精准定位选中的手机号码
card_number = cards.get(choose + (page - 1) * 10 - 1).getCard_number();
updateCard(1);
break;
default:
page = 1;
num = 0;
chooseCard(cards, page, num);
}
bo = false;
}
} while (bo);
}
}
5.3 话费充值
主要思路:根据电话号码,输入要充值的金额数,然后修改数据库的数据,如果手机号码被冻结,如果账户余额大于0就解冻。
代码演示:
private void rechargeSoso(String card_number) {
RechargeRecord record = new RechargeRecord();
System.out.print("请选择你的充值金额:");
Double amount = scanner.nextDouble();
//充值,修改数据库中的数据
moboleCardService.rechargeSoso(card_number, amount);
//读取数据库中的数据,获取用户数据
moboleCard = moboleCardService.freeze(card_number);
if (moboleCard != null) {
//如果手机号码是冻结状态,且余额大于0
if (moboleCard.getStatus() == 1 && (moboleCard.getMoney() + amount) > 0) {
moboleCard.setStatus(0);
//如果修改数据成功
if (moboleCardService.update(moboleCard) == 1) {
System.out.println("手机号码:" + card_number + "已成功解冻!");
}
}
}
//添加充值数据
record.setCard_number(card_number);
record.setAmount(amount);
if (rechargeRecordService.insert(record) == 1) {
System.out.println("充值成功!");
}
System.out.println();
}
5.4 套餐说明
主要代码:
private void showPackage() {
System.out.println("************************套餐说明*****************************");
serPackages = serPackageService.getPackageType();
//循环输出读到的数据
for (SerPackage serPackage : serPackages) {
System.out.println("套餐" + serPackage.getId() + "---" + serPackage);
}
}
5.5 使用嗖嗖
主要思路:用Random获取一个随机数,随机进行一个场景,并输出。然后根据这个场景的类别分别进行数据的修改。算消费金额时有三种情况,第一种是消费的属性的量没有超过套餐给的量,就不计算金额,仅计算消费的属性的量;第二种是在消费之前,套餐还有余额,消费后套餐余额不足还超出消费,就要计算超出量的金额;第三种是套餐本来就没有余额,直接计算消费的金额。为区分这三种情况,我是先计算使用后的总消费量,如果总消费量没超出套餐提供的量,就是第一种情况,超出套餐提供的量,再判断使用前套餐剩余量是否大于0,大于0就是第二种情况,小于零就是第三种情况,然后再分别计算消费金额。最后再判断一下卡是否欠费,是否被冻结(欠费超50,自动冻结)。
场景展示:
主要代码(以通话为例,流量和短信与之类似):
System.out.println("***********欢迎使用嗦嗦*************");
List<Scene> scenes = sceneService.queryAll();
Random random = new Random();
int choose = random.nextInt(scenes.size());
//获取随机场景并输出
String type = scenes.get(choose).getType();
System.out.println(scenes.get(choose).getDescription());
double money = 0;
if (type.equals("通话")) {
int before = monthlyConsumptionRecord.getReal_talk_time();
//计算原套餐还剩下多少
int balance = serPackages.get(mcp.getSer_package() - 1).getTalk_time() - before;
//将总通话时间计算出来传入对象
monthlyConsumptionRecord.setReal_talk_time(before + scenes.get(choose).getData());
//如果总使用通话时间超过套餐所提供的通话时间,扣钱
if (monthlyConsumptionRecord.getReal_talk_time() > serPackages.get(mcp.getSer_package() - 1).getTalk_time()) {
//计算当月消费金额
if (balance > 0) {
money = (monthlyConsumptionRecord.getReal_talk_time() - serPackages.get(mcp.getSer_package() - 1).getTalk_time()) * 0.2;
} else {
money = scenes.get(choose).getData() * 0.2;
}
monthlyConsumptionRecord.setConsum_amount(monthlyConsumptionRecord.getConsum_amount() + money);
//扣钱
moboleCard.setMoney(moboleCard.getMoney() - money);
}
}
5.6 本月账单查询
主要代码:
private void selectBill() {
System.out.println("*********本月账单**********");
System.out.println("您的卡号:" + card_number + ",当月账单:");
//mcp为全局对象,通过多表查询存储本月账单信息
System.out.println("套餐资费:" + mcp.getPrice());
System.out.println("合计:" + (mcp.getPrice() + mcp.getConsum_amount()));
System.out.println("账号余额:" + mcp.getMoney());
secondMenu();
}
5.7 套餐余量查询
主要代码:
private void remainder() {
int callTime;
int sms;
int flowRate;
List<MonthlyConsumptionRecord> monthlyConsumptionRecords = monthlyConsumptionRecordService.queryById(card_number);
//通过电话号码查询的list数据里只存储了一条数据,运用索引0获取
MonthlyConsumptionRecord monthlyConsumptionRecord = monthlyConsumptionRecords.get(0);
System.out.println("*********余量查询**********");
System.out.println("您的卡号" + card_number + ",套餐内剩余:");
//计算通话时长剩余量
if ((callTime = (serPackages.get(mcp.getSer_package() - 1).getTalk_time() - monthlyConsumptionRecord.getReal_talk_time())) < 0) {
callTime = 0;
}
System.out.println("通话时长:" + callTime + "分钟");
if ((sms = (serPackages.get(mcp.getSer_package() - 1).getSms_count() - monthlyConsumptionRecord.getReal_SMS_count())) < 0) {
sms = 0;
}
System.out.println("短信条数:" + sms + "条");
if ((flowRate = (serPackages.get(mcp.getSer_package() - 1).getFlow() - monthlyConsumptionRecord.getReal_flow())) < 0) {
flowRate = 0;
}
System.out.println("上网流量:" + flowRate / 1024 + "GB");
secondMenu();
}
5.8 消费详情
private void consumeList() {
BufferedWriter bw = null;
StringBuffer stringBuffer = new StringBuffer();
System.out.println("*********消费详情查询**********");
System.out.print("请输入本年要查询的月份(1-12):");
int month = scanner.nextInt();
//获取对应月份的消费数据
List<ConsumInfo> consumInfos = consuminfoService.selectData(card_number, month);
if (consumInfos != null && !consumInfos.isEmpty()) {
System.out.println("序号\t类型\t数据\t日期");
int i = 1;
//循环打印数据
for (ConsumInfo consumInfo : consumInfos) {
System.out.println(i + "\t" + consumInfo);
stringBuffer.append(i).append("\t").append(consumInfo).append("\n");
i++;
}
System.out.println("请问需要打印消费清单吗?(1.需要 2.不需要)");
if (scanner.nextInt() == 1) {
try {
//根据手机号打印到对应的文件中
bw = new BufferedWriter(new FileWriter("D:\\飞思---\\项目\\嗦嗦移动大厅\\客户消费记录\\" + card_number + ".txt"));
bw.write(String.valueOf(stringBuffer));
bw.newLine();
bw.flush();
System.out.println("打印完成");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (bw != null) {
bw.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
} else {
System.out.println("[友情提示]:对不起,不存在本卡号" + month + "月消费记录");
}
secondMenu();
}
5.9 套餐变更
主要思路:根据用户的选择,如果用户本来就是这个套餐,就不可改变套餐。如果套餐可以改变,就判断用户余额能否满足套餐所需月租,不足就让用户选择是否充值,不充值就退出,充值就修改数据库数据。
主要代码:
private void packageAlter() {
boolean bo = true;
System.out.println("*********套餐变更**********");
showPackage();
moboleCards = moboleCardService.queryById(card_number);
moboleCard = moboleCards.get(0);
System.out.print("请选择(序号):");
int packageNum = scanner.nextInt();
if (moboleCard.getSer_package() == packageNum) {
System.out.println("[友情提示]:您已经是该套餐的用户,无需更换!");
} else {
while (moboleCard.getMoney() < serPackages.get(packageNum - 1).getPrice()) {
System.out.println("[友情提示]:对不起,您的余额不足以支付新套餐本月资费,请充值后办理变更套餐业务!");
bo = false;
System.out.print("是否充值(1.不充值 其他键即为充值):");
if (scanner.nextInt() != 1) {
rechargeSoso(card_number);
bo = true;
}
}
if (bo) {
SerPackage serPackage = serPackages.get(packageNum - 1);
moboleCard.setMoney(moboleCard.getMoney() - serPackage.getPrice());
moboleCard.setSer_package(packageNum);
moboleCard.setCard_number(card_number);
if (moboleCardService.update(moboleCard) == 1) {
System.out.println("[友情提示]:更换套餐成功!" + serPackage);
}
}
}
secondMenu();
}
5.10 办理退网
主要代码:
private void exit() {
System.out.println("\n你确认要退网吗:");
System.out.println("请选择(1.确认 其他键为取消):");
if (scanner.nextInt() == 1) {
cardService.updateCard(card_number, 0);
List<ConsumInfo> consuminfos = consuminfoService.selectDates(card_number);
List<MonthlyConsumptionRecord> monthlyConsumptionRecords = monthlyConsumptionRecordService.queryById(card_number);
List<RechargeRecord> rechargeRecords = rechargeRecordService.queryById(card_number);
int consuminfo = consuminfoService.deleteConsuminfo(card_number);
int moboleCard = moboleCardService.deleteMoboleCard(card_number);
int monthlyConsumption = monthlyConsumptionRecordService.deleteMCR(card_number);
int rechargeRecord = rechargeRecordService.deleteRechargeRecord(card_number);
if (consuminfo == consuminfos.size() && moboleCard == 1 && monthlyConsumptionRecords.size() == monthlyConsumption && rechargeRecords.size() == rechargeRecord) {
System.out.println("注销成功");
firstMenu();
} else {
System.out.println("注销失败");
}
}
secondMenu();
}
5.11 自动收取月租和每月自动生成消费数据
主要思路:用两个循环读取数据,一个是读取用户数据表中的手机号,一个是消费数据表的数据,如果当月本电话号码存在消费数据就不添加,不存在就添加上默认数据。然后通过LocalDate获取当前时间然后获取当前的日,如果是一号,就对每个手机号的进行扣月租的处理。
主要代码:
@Override
public void atuoAddData() {
//用来添加数据
MonthlyConsumptionRecord monthly = new MonthlyConsumptionRecord();
//用来转换日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
LocalDate now = LocalDate.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM");
String nowMonth = dtf.format(now);
Mobole_CardDaoImpl moboleCardDao = new Mobole_CardDaoImpl();
List<Mobole_Card> moboleCards = moboleCardDao.queryAll();
MonthlyConsumptionRecordsDaoImpl monthlyConsumptionRecordsDao = new MonthlyConsumptionRecordsDaoImpl();
List<MonthlyConsumptionRecord> monthlyConsumptionRecords = monthlyConsumptionRecordsDao.queryAll();
//遍历用户表
for (Mobole_Card mobole_Card : moboleCards) {
boolean bo = true;
String card_number = mobole_Card.getCard_number();
//遍历消费数据表
for (MonthlyConsumptionRecord monthlyConsumptionRecord : monthlyConsumptionRecords) {
//判断用户表中的电话号码是否在消费数据表中有当月的记录
if (mobole_Card.getCard_number().equals(monthlyConsumptionRecord.getCard_number()) && sdf.format(monthlyConsumptionRecord.getConsume_date()).equals(nowMonth)) {
bo = false;
}
}
if (bo) {
//传默认值
monthly.setCard_number(card_number);
monthly.setReal_talk_time(0);
monthly.setReal_SMS_count(0);
monthly.setReal_flow(0);
monthlyConsumptionRecordsDao.insert(monthly);
//收月租
monthly.setConsum_amount(monthlyRent(mobole_Card.getCard_number()));
monthlyConsumptionRecordsDao.update(monthly);
}
}
}
收月租的代码:
@Override
public Double monthlyRent(String card_number) {
MoboleCard moboleCard=new MoboleCard();
MoboleCardDaoImpl moboleCardDao = new MoboleCardDaoImpl();
List<MoboleCard_Package> moboleCardPackages = moboleCardDao.getMessage(card_number);
//判断读的数据是否为空
if (moboleCardPackages != null && !moboleCardPackages.isEmpty()) {
//存储多表查出来的数据
MoboleCard_Package moboleCardPackage = moboleCardPackages.get(0);
//获取现在的时间对应的天
LocalDate now = LocalDate.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("d");
String nowDay = dtf.format(now);
//如果是一号
if (nowDay.equals("1")) {
//将数据传入对象,防止修改表内不该改的数据
moboleCard.setCard_number(card_number);
moboleCard.setUsername(moboleCardPackage.getUsername());
moboleCard.setPassword(moboleCardPackage.getPassword());
moboleCard.setSer_package(moboleCardPackage.getSer_package());
//扣钱
moboleCard.setMoney(moboleCardPackage.getMoney()-moboleCardPackage.getPrice());
if(moboleCard.getMoney()<-50){
moboleCard.setStatus(1);
}else {
moboleCard.setStatus(0);
}
//修改数据
if(moboleCardDao.update(moboleCard)!=1){
throw new RuntimeException("月租没收成");
}
return moboleCardPackage.getPrice();
}
}
return 0.0;
}
}
6.总结
这个小项目,灵活的运用了面向对象的思想,三层架构的思想和JDBC对数据库的操作。
不过还有很多不足的地方,首先对数据库的处理方面,没有进行回滚操作,一个操作存在多个sql语句,如果前几个sql语句正常执行,后面的出了异常,数据就会出问题。然后就是使用嗖嗖模块太死板了,没有真正的场景操作,只是根据自己存在数据库的场景,进行计算收费等。