1 项目介绍:
中国移动,中国联通,中国电信是国内3大通信运营商,每个运营商都提供了不同的品牌套餐来应对不同的用户群,比如北京移动主要有全球通,神州行,动感地带等3大品牌套餐,每种套餐的内容和费用不同,嗖嗖移动是一个假定的通信运营商.本任务实现的"嗖嗖移动业务大厅"提供了嗖嗖移动用户的常用功能,包括新用户注册,本月账单查询,套餐余量查询,打印消费详情,套餐变更,办理退网,话费充值,查看消费记录,查看话费说明等功能.另外,还可以模拟用户通话,上网,发送短信的场景进行相应的扣费并记录消费信息
2 主要功能:
菜单级别 | 功能 | 描述 |
---|---|---|
主菜单 | 用户登录 | 输入正确的手机号码和密码进入二级菜单列表 |
主菜单 | 用户注册 | 录入信息并开卡,用户输入的信息包括:选择卡号,选择套餐类型,输入用户名和密码,预存话费金额(预存话费金额必须满足以支付所选套餐的一个月的费用) |
主菜单 | 使用嗖嗖 | 输入正确的手机号码和密码之后,随机进入本号码所属套餐可以支持的一个场景,消费套餐余量或者话费余额,并记录消费信息.当话费余额不足时,抛出异常提醒用户充值 |
主菜单 | 话费充值 | 输入正确的用户名和密码之后,可为该卡号充值 |
主菜单 | 资费说明 | 提供各品牌套餐所包含的通话时长,上网流量,短信条数,月费用等 |
主菜单 | 退出系统 | 提出本系统 |
二级菜单 | 本月账单查询 | 可查询该卡号的套餐费用,实际消费金额,账户余额 |
二级菜单 | 套餐余量查询 | 可查询该卡号的套餐余量 |
二级菜单 | 打印消费详情 | 输入正确的卡号和密码后,可打印当前卡号用户的消费详单, 使用输出流把用户信息输出到文件 |
二级菜单 | 套餐变更 | 可变更为其他套餐类型,变更后话费余额需减去变更后的套餐费用,余额不足时需要给出信息提示,套餐变更后重新统计卡中实际消费数据以及当月消费金额 |
二级菜单 | 办理退网 | 输入正确的卡号和密码后,可以从已注册的号码列表中删除本号码,并退出系统 |
3 使用技术:
-
面向对象的思想
-
封装,继承,多态,接口的使用
-
异常处理的合理使用
-
集合框架的使用
-
I/O 操作实现对文件的写
-
MySQL数据
-
JDBC操作数据库
4 项目结构:
本次项目采用的是三层架构,三层架构就是为了符合“高内聚,低耦合”思想,把各个功能模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三层架构,各层之间采用接口相互访问,并通过对象模型的实体类(Model)作为数据传递的载体,不同的对象模型的实体类一般对应于数据库的不同表,实体类的属性与数据库表的字段名一致。
4.1 数据访问层就是用JDBC操作对数据库进行一系列的增删改查操作
4.2 模块就是给数据库的各个表创建一个类对象
4.3 服务层就是数据访问层和表示层之间的桥梁,用于实现业务逻辑
4.4 表示层就是进行所有的功能展示,以及进行各种操作
4.5 uitl是一个自己封装好的工具类,用于JDBC操作,可以让代码更加简洁
package com.fs.util;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* JDBC工具类
*/
public class JDBCUtil {
private static String url;
private static String user;
private static String password;
private static String driver;
private static final String dbconfig = "jdbc.properties";
private static Properties prop = new Properties();
/**
* 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块
*/
static{
//读取资源文件,获取值。
try {
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(dbconfig);
prop.load(in);
//3. 获取数据,赋值
url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("password");
driver = prop.getProperty("driver");
//4. 注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取连接
* @return 连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
/**
* 释放资源
* @param stmt
* @param conn
*/
public static void close(ResultSet rs,Statement stmt, Connection conn){
if( rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 封装执行增删改的sql
* @param sql 执行的sql语句
* @param params 可变参数,
* 可以存储多个参数值,一个方法只能有一个可变参数,必须放方法参数的最后一个
* 可以用集合或者数组代替
* @return
*/
public static int executeUpdate(String sql,Object... params){
Connection connection = null;
PreparedStatement pstm = null;
int count = 0;
try {
connection = getConnection();
pstm = connection.prepareStatement(sql);
//设置?占位符的参数值 params可变参数当数组使用
if (params != null && params.length >0){
for (int i = 0; i < params.length; i++) {
pstm.setObject(i+1,params[i]);
}
}
//执行命令
count = pstm.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
close(null,pstm,connection);
}
return count;
}
/**
* 封装执行查询语句功能
* @param sql sql语句
* @param clazz 指定类型字节码
* @param params
* @param <T> 泛型
* @return
*/
public static <T> List<T> executeQuery(String sql,Class<T> clazz, Object...params){
List<T> list = new ArrayList<>();
Connection connection = null;
PreparedStatement pstm = null;
ResultSet resultSet = null;
try {
connection = getConnection();
pstm = connection.prepareStatement(sql);
//设置?占位符的参数值 params可变参数当数组使用
if (params != null && params.length >0){
for (int i = 0; i < params.length; i++) {
pstm.setObject(i+1,params[i]);
}
}
resultSet = pstm.executeQuery();
//循环获取查询行数据
while (resultSet.next()) {
//通过反射根据字节码class获取对象
T t = clazz.newInstance();
//通过反射获取此对象中的属性,给属性赋值
Field[] fields = clazz.getDeclaredFields();
//遍历实体的属性,封装到模型对象中
for (Field field : fields) {
//暴力反射
field.setAccessible(true);
//getObject(列下标/列的名称)
//根据属性名从结果集行中获取数据保存设置属性的值
//模型实体中的属性名必须和表中列名一致
//如果列和属性名不一致, select ename empName取别名
field.set(t,resultSet.getObject(field.getName()));
}
list.add(t);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}finally {
close(resultSet,pstm,connection);
}
return list;
}
}
5 部分代码解释:
5.1 这是数据访问层中编写的登录方法,录入账户名和密码,编写查询语句,调用工具类方法,返回查询结果
public List<Mobole_card> login(String username, int password) {
List<Mobole_card> count1 = null;
String sql1 = "select * from tb_mobole_card where card_number=? and password=?";
count1 = JDBCUtil.executeQuery(sql1, Mobole_card.class, username, password);
return count1;
}
5.2 这是服务层接收数据访问层返回的结果,对其进行判断,再返回值
public String login(String username, int password) {
List<Mobole_card> list = soso.login(username, password);
if (!list.isEmpty()) {
if (list.get(0).getStatus().equals(1))
return "改卡号已被禁用";
else
return "登录成功";
} else
return "登陆失败";
}
5.3 这是展示层的登录代码,先使用扫描器输入账号密码,调用服务层登录方法并接收,最后返回登录成功或者失败
private void doLogin() {
System.out.println("手机号码");
username = input.next();
System.out.println("请输入密码");
int password = input.nextInt();
String login = sosoService.login(username, password);
System.out.println(login);
if (login == "登录成功") {
showSecondMenu();
} else {
System.out.println("账号密码错误,登录失败,请重新登录");
showFiratMenu();
}
}
6 项目总结:
本次嗖嗖业务大厅的项目是我目前所有所学知识所写的一个综合项目,难度还是比较大的,不过收获也挺多,对JDBC操作有了更深的理解,出现的各种异常,基本上都可以在短时间内找到错误来源,之前对封装继承这些不熟悉的知识,也在这个项目中得到了提升.并且代码也不像以前一样杂乱,毫无章法,而是更加规律,代码也更简洁明了.