当前位置: 首页 > news >正文

模拟手机办卡项目(移动大厅)--结合面向对象、JDBC、MYSQL、dao层模式,使用JAVA控制台实现

目录

1. 项目需求

2. 项目使用的技术

3.项目需求分析

3.1 实体类和接口

 4.项目结构

5.业务实现

5.1 登录

5.1.1 实现步骤

 5.1.2 原生代码问题

​编辑

 5.1.3 解决方法

1.说明:

2. ResultSetHandler结果集处理

5.1.4 代码

5.1.5 实现后的效果图

 登录成功​编辑

登录失败 

 5.2 注册

5.2.1 实现步骤

 5.2.2 代码

5.2.3 数据库设计

5.2.4 实现后的效果图

 5.3 本月账单查询

5.3.1 实现步骤

5.2.2 代码 

5.2.3 实现后的效果图

 5.4 使用嗖嗖

5.4.1 功能分析

5.4.2 实现步骤

5.4.3 代码

5.4.4 实现后的效果图 

消费成功:

​编辑 消费失败:


1. 项目需求

       中国移动,中国联通,中国电信是国内3大通信运营商,每个运营商都提供了不同的品牌套餐来应对不同的用户群,比如北京移动主要有全球通,神州行,动感地带等3大品牌套餐,每种套餐的内容和费用不同,嗖嗖移动是一个假定的通信运营商,提供了话痨套餐,网虫套餐,超人套餐,各种套餐所包含的服务内容及费用如下表:

品牌套餐

话痨套餐网虫套餐超人套餐
通话时长(分钟)6000300
上网流量02010
短信条数(条)100050
费用(元/月)586878

如实际使用中超出套餐内包含的通话时长,短信条数和上网流量,则按一下规则计费:

  • 超出的通话: 0.2元/分

  • 超出的短信:0.1元/条

  • 超出的上网流量:0.1元/MB

        本任务实现的"嗖嗖移动业务大厅"提供了嗖嗖移动用户的常用功能,包括新用户注册,本月账单查询,套餐余量查询,打印消费详情,套餐变更,办理退网,话费充值,查看消费记录,查看话费说明等功能.另外,还可以模拟用户通话,上网,发送短信的场景进行相应的扣费并记录消费信息.各功能介绍如下表:    

菜单级别功能描述
主菜单用户登录输入正确的手机号码和密码进入二级菜单列表
主菜单用户注册录入信息并开卡,用户输入的信息包括:选择卡号,选择套餐类型,输入用户名和密码,预存话费金额(预存话费金额必须满足以支付所选套餐的一个月的费用)
主菜单使用嗖嗖输入正确的手机号码和密码之后,随机进入本号码所属套餐可以支持的一个场景,消费套餐余量或者话费余额,并记录消费信息.当话费余额不足时,抛出异常提醒用户充值
主菜单话费充值输入正确的用户名和密码之后,可为该卡号充值
主菜单资费说明提供各品牌套餐所包含的通话时长,上网流量,短信条数,月费用等
主菜单退出系统退出本系统
二级菜单本月账单查询可查询该卡号的套餐费用,实际消费金额,账户余额
二级菜单套餐余量查询可查询该卡号的套餐余量
二级菜单打印消费详情输入正确的卡号和密码后,可打印当前卡号用户的消费详单, 使用输出流把用户信息输出到文件
二级菜单套餐变更可变更为其他套餐类型,变更后话费余额需减去变更后的套餐费用,余额不足时需要给出信息提示,套餐变更后重新统计卡中实际消费数据以及当月消费金额
二级菜单办理退网输入正确的卡号和密码后,可以从已注册的号码列表中删除本号码,并退出系统

2. 项目使用的技术

  • 面向对象的思想

  • 封装,继承,多态,接口的使用

  • 异常处理的合理使用

  • 集合框架的使用

  • I/O 操作实现对文件的写

  • MySQL数据

  • JDBC操作数据库

3.项目需求分析

3.1 实体类和接口

  1. Card(电话号码类)

  2. MoboleCard(嗖嗖移动卡类)

  3. monthlyConsumptionRecords(月消费记录类)

  4. 套餐类 SerPackage

  5. 套餐类型类 SerPackageType

  6. ConsumInfo(消费信息类)

  7. Scene(使用场景类)

  8. RechargeRecord(充值记录类)

Card(电话号码类)

private String cardNumber;
private Integer status;

 MoboleCard(嗖嗖移动卡类)

private String cardNumber;
private String username;
private String password;
private Integer serPackage;
private BigDecimal money;
private Integer status;

 monthlyConsumptionRecords(月消费记录类)

private String cardNumber;
private BigDecimal comsumAmount;
private Integer realTalkTime;
private Integer realSmsCount;
private Integer realFlow;
private Date consumeDate;

SerPackage(套餐类 )

private Integer talkTime;
private Integer smsCount;
private BigDecimal price;
private Integer flow;
private Integer type;

 SerPackageType(套餐类型类)

private String name;

 ConsumInfo(消费信息类)

private String cardNumber;
private String type;
private Integer consumData;
private Date consumeDate;

 Scene(使用场景类)

private String type;
private Integer data;
private String description;

 RechargeRecord(充值记录类)

private Integer id;
private BigDecimal amount;
private Date rechargeDate;
private String CardNum;

 4.项目结构

分层模式:

|–entity 实体类所在包

|–dao 数据访问层接口包

|–impl: 数据访问层接口的实现类所在包     每一个方法只操作一条sql

|–service: 业务接口层

|–impl: 业务接口实现类所在包

|–ui : 界面菜单显示

|–util: 通用工具类所在包

5.业务实现

5.1 登录

从前(ui/网页)往后写

ui –> service–> mappe

上层调用下层, 不能下层调用上层

5.1.1 实现步骤

  1. 用户在控制台输入手机卡号和密码,根据手机卡号和密码查询tb_mobile_card表,判断是否存在该用户
  2. 如果用户卡号和密码输入正确,通过用户输入的电话号码查询tb_card表,判断卡号是否被冻结
  3. 编写异常类,通过异常判断该用户是卡号或密码输入错误还是卡号被冻结

 5.1.2 原生代码问题

 5.1.3 解决方法

使用第三方的jar: apache提供的: commons-dbutils

提供一个核心类: QueryRunner

1.说明:

DBUtils是java编程中数据库操作的实用工具,小巧简单实用。第一个操作数据库的框架(jar) DBUtils封装了许多JDBC的代码,简化了JDBC操作,可以少写很多代码 DBUtils三个核心功能的介绍: DBUtils类: 它就是一个工具类,定义了关闭资源和事务处理的方法

QueryRunner类: 提供了对sql语句操作的方法,query():查询,update():修改

ResultSetHandler接口: 用于定义select操作后,如何封装结果集

QueryRunner核心类

  • QueryRunner(DataSource ds):提供数据源(连接池)DataSource,DBUtils底层自动维护和连接Connection数据库 DataSource : 数据源(连接池)

  • QueryRunner(): 创建一个QueryRunner对象

  • query(String sql, ResultSetHandler<T> rsh, Object... params):执行查询语句select

  • update(String sql, Object... params):执行更新语句insert,update,delete,参数就是一个数组,参数个数取决于sql语句中?的个数

2. ResultSetHandler结果集处理
Handler类型说明
BeanHandler将结果集中第一条记录封装到一个指定的javaBean中。
BeanListHandler将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
ScalarHandler它是用于单个数据。例如select count(*) from 表操作。
ArrayHandler将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值
ArrayListHandler将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。
ColumnListHandler将结果集中指定的列的字段值,封装到一个List集合中
KeyedHandler将结果集中每一条记录封装到Map<String,Object>,在将这个map集合做为另一个Map的value,另一个Map集合的key是指定的字段的值。
MapHandler将结果集中第一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值
MapListHandler将结果集中每一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值,在将这些Map封装到List集合中。

5.1.4 代码

 UI层方法:

private void loginUI() {
    System.out.println("\n---------------------用户登录--------------------");
    System.out.print("请输入您的手机卡号:");
    String cardNum = scanner.next();
    System.out.print("请输入密码:");
    String password = scanner.next();
    //调用业务层的登录方法
    MobileCardService mobileCardService=new MobileCardServiceImpl();
    try{
        cardNumber=mobileCardService.login(cardNum,password);
    }catch (CardNumOrPasswordException e){
        System.out.println("提示!!! "+e.getMessage());
        showFirstMenu();
    }catch (CardFreezenException e){
        System.out.println("提示!!! "+e.getMessage());
        showFirstMenu();
    }
    showSecondMenu();
}

MobileCardDaoImpl电话卡数据访问实现方法:

public MobileCard findByCardNum(String cardNum) {
    QueryRunner queryRunner = new QueryRunner();
    Connection connection = null;
    try {
        connection = getConnection();
        String sql="select id,card_number cardNumber,username,password,ser_package serPackage,money,status from tb_mobile_card where card_number = ?";
        //注意:要求字段名与属性对应  如果属性名与数据库列名不一致 取别名
        MobileCard mobileCard = queryRunner.query(connection, sql, new BeanHandler<>(MobileCard.class), cardNum);//(Connection对象,sql语句,结果的映射.?的值)
        return mobileCard;
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        close(connection);
    }
}

用户手机卡业务层实现:

MobileCardService:

/**
* 登录
* @param cardNum 卡号
* @param password  卡密码
* @throws CardNumOrPasswordException  卡号或密码错误异常
* @throws CardFreezenException  卡冻结异常
*/
String login(String cardNum, String password) throws CardNumOrPasswordException, CardFreezenException;

MobileCardServiceImple:

@Override
public String login(String cardNum, String password) throws CardNumOrPasswordException, CardFreezenException {
    MobileCardDao mobileCardDao=new MobileCardDaoImpl();
    MobileCard mobileCard=mobileCardDao.findByCardNum(cardNum);
    if(mobileCard==null){
        throw new CardNumOrPasswordException("卡号或者密码错误");
    }else{
        //比较密码
        if(!mobileCard.getPassword().equals(password)){
            throw new CardNumOrPasswordException("卡号或者密码错误");
        }
        //判断状态是否冻结
        else {
            if(mobileCard.getStatus()==CARD_FREEZEN){
                throw new CardFreezenException("该卡已冻结,请联系客服");
            }
        }
    }
    return mobileCard.getCardNumber();
}

电话卡业务层实现:

CardService:

public interface CardService extends IService<Card>{

    Card queryByNumber(String cardNumber);
}

CardServiceImpl:

public Card queryByNumber(String cardNumber) {
    CardDao cardDao = new CardDaoImpl();
    return cardDao.queryByNumber(cardNumber);
}

电话卡数据访问层实现:

CardDao:

public interface CardDao extends BaseDao<Card>{

    Card queryByNumber(String cardNumber);
}

CardDaoImpl:

public Card queryByNumber(String cardNumber) {
    QueryRunner queryRunner = new QueryRunner();
    Connection connection = null;
    try {
        connection = getConnection();
        String sql="select id,cardNumber,status from tb_card where cardNumber = ?";
        //注意:要求字段名与属性对应  如果属性名与数据库列名不一致 取别名
        Card card = queryRunner.query(connection, sql, new BeanHandler<>(Card.class), cardNumber);//(Connection对象,sql语句,结果的映射.?的值)
        return card;
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        close(connection);
    }
}

5.1.5 实现后的效果图

 登录成功
登录失败 

 5.2 注册

5.2.1 实现步骤

  1. 查询tb_card: 查询所有可用号码

  2. 查询所有套餐类型 tb_serpackage_type,并展示

  3. 用户选择对应的套餐类型, 根据套餐类型查询套餐详情 tb_serpackage

  4. 用户输入充值金额, 判断充值金额 >= 套餐月租, 办卡成功, 往tb_moboel_card 插入一条记录, 往充值表tb_recharge_record 插入一条记录, 修改tb_card的记录的状态  

 5.2.2 代码

UI层实现:

private void registerUI() {
    System.out.println("\n---------------------用户注册--------------------");
    System.out.println("\n----------可选择的卡号----------");
    CardService cardService = new CardServiceImpl();
    List<Card> cardList = cardService.queryAll();
    for(int i=0;i<cardList.size();i++){
        Card card=cardList.get(i);
        System.out.print("\t"+(i+1)+". "+card.getCardNumber());
        if((i+1) % 3 == 0){
            System.out.println();
        }
    }
    System.out.print("\n请选择您要注册的手机卡号:");
    int choice = scanner.nextInt();
    Card card = cardList.get(choice-1);
    //列表显示所有的套餐类型
    System.out.println("\n----------可选择的套餐----------");
    SerPackageTypeService serPackageTypeService = new SerPackageTypeServiceImpl();
    List<SerPackageType> serPackageList = serPackageTypeService.queryAll();
    for(int i=0;i<serPackageList.size();i++){
        SerPackageType serPackageType=serPackageList.get(i);
        System.out.println((i+1)+". "+serPackageType.getName());
    }
    System.out.print("\n请选择您要注册的套餐类型:");
    int choice1 = scanner.nextInt();
    SerPackageType chooseserPackageType = serPackageList.get(choice1-1);
    //查询用户选择的套餐类型的套餐详情
    SerPackageService serPackageService = new SerPackageServiceImpl();
    SerPackage serPackage = serPackageService.queryByType(chooseserPackageType.getId());
    //基本信息输入
    System.out.print("\n请输入您的姓名:");
    String name=scanner.next();
    System.out.print("\n请输入您的密码:");
    String password=scanner.next();
    System.out.print("\n请输入预存话费金额:");
    BigDecimal money=scanner.nextBigDecimal();
    //判断输入的金额是否满足套餐
    //月租大于预存金额
    double balance=0;
    while((balance=serPackage.getPrice().subtract(money).doubleValue()) > 0){
        System.out.println("提示!!! 您的预存话费不足以支付本月套餐话费,请重新输入!");
        System.out.println("\n请输入您的预存话费:");
        money=scanner.nextBigDecimal();
    }
    //往用户手机卡中插入一条数据zh-CN
    MobileCardService mobileCardService=new MobileCardServiceImpl();
    MobileCard mobileCard=new MobileCard();
    mobileCard.setCardNumber(card.getCardNumber());
    mobileCard.setUsername(name);
    mobileCard.setPassword(password);
    mobileCard.setMoney(money.subtract(serPackage.getPrice()));
    mobileCard.setSerPackage(serPackage.getId());
    mobileCard.setStatus(CARD_NORMAL);
    mobileCardService.add(mobileCard);
    //往充值记录表中插入一条数据
    RechargeRecord rechargeRecord=new RechargeRecord();
    rechargeRecord.setAmount(money);
    rechargeRecord.setCardNum(card.getCardNumber());
    rechargeRecord.setRechargeDate(new Date());
    RechargeRecordService rechargeRecordService=new RechargeRecordServiceImpl();
    rechargeRecordService.add(rechargeRecord);
    //修改手机卡表的手机号码状态为已激活
    card.setStatus(CARD_FREEZEN);
    cardService.update(card);
    System.out.println("【友情提示】恭喜您,注册成功! 您的手机卡号为:"+card.getCardNumber()+",用户名:"+name+",当前卡余额:"+mobileCard.getMoney());
    System.out.println(chooseserPackageType.getName()+"详细信息如下:");
    System.out.println("套餐名称:"+chooseserPackageType.getName()+"\n套餐价格:"+serPackage.getPrice()+"元\n通话时长:"+serPackage.getTalkTime()+"分钟\n短信条数:"+serPackage.getSmsCount()+"条\n流量:"+serPackage.getFlow()+"MB");
    loginUI();
}

套餐类型业务层实现:

SerPackageTypeServiceImpl:

public List<SerPackageType> queryAll() {
    SerPackageTypeDao serPackageTypeDao = new SerPackageTypeDaoImpl();
    return serPackageTypeDao.queryAll();
}

套餐类型数据访问层实现:

SerPackageTypeDaoImpl:

public List<SerPackageType> queryAll() {
    String sql="select * from tb_serpackage_type";
    Connection con = null;
    try {
        con=getConnection();
        QueryRunner queryRunner=new QueryRunner();
        return queryRunner.query(con,sql,new BeanListHandler<SerPackageType>(SerPackageType.class));
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        close(con);
    }
}

用户卡业务层实现:

MobileCardServiceImpl:

public void add(MobileCard mobileCard) {
    MobileCardDao mobileCardDao=new MobileCardDaoImpl();
    mobileCardDao.add(mobileCard);
}

用户卡数据访问层实现:

MobileCardDaoImpl:

public void add(MobileCard mobileCard) {
    Connection connection = null;
    try {
        connection = getConnection();
        String sql="insert into tb_mobile_card(card_number,username,password,ser_package,money,status) values(?,?,?,?,?,?)";
        QueryRunner queryRunner=new QueryRunner();
        queryRunner.update(connection,sql,mobileCard.getCardNumber(),mobileCard.getUsername(),
                                          mobileCard.getPassword(),mobileCard.getSerPackage(),
                                          mobileCard.getMoney(),mobileCard.getStatus());
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        close(connection);
    }
}

消费记录业务层实现:

ConsumInfoServiceImpl:

public void add(ConsumInfo consumInfo) {
    ConsumInfoDao consumInfoDao=new ConsumInfoDaoImpl();
    consumInfoDao.add(consumInfo);
}

消费记录数据访问层实现:

ConsumInfoDaoImpl:

public void add(ConsumInfo consumInfo) {
    Connection connection = null;
    try {
        connection = getConnection();
        String sql="insert into tb_consuminfo(card_number,type,consum_data,consume_date) values(?,?,?,?)";
        QueryRunner queryRunner=new QueryRunner();
        queryRunner.update(connection,sql,consumInfo.getCardNumber(),consumInfo.getType(),
                consumInfo.getConsumData(), DateUtil.formatDate(consumInfo.getConsumeDate(),"yy-MM-dd hh:mm:ss"));
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        close(connection);
    }
}

电话卡类业务层实现:

CardServiceImpl:

public void update(Card card) {
    CardDao cardDao = new CardDaoImpl();
    cardDao.update(card);
}

电话卡类数据访问层实现:

CardDaoImpl:

public void update(Card card) {
    Connection connection = null;
    try {
        connection = getConnection();
        String sql="update tb_card set status=? where id=?";
        QueryRunner queryRunner=new QueryRunner();
        queryRunner.update(connection,sql,card.getStatus(),card.getId());
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        close(connection);
    }
}

5.2.3 数据库设计

5.2.4 实现后的效果图

 5.3 本月账单查询

5.3.1 实现步骤

  1. 根据登录的手机号码查询卡的余额 tb_mobile_card
  2. 根据手机号码绑定的套餐id查询套餐月租
  3. 根据手机号,本月日期:2024-11-01 查询本月消费金额
  4. 如果没有记录,就往表里面插入一条数据
  5. 有就直接查询 tb_monthly_consumption_records

5.2.2 代码 

UI层实现:

private void QueryMonthBill() {
    System.out.println("\n---------------------本月账单查询--------------------");
    //查询手机号码的余额
    MobileCardService mobileCardService = new MobileCardServiceImpl();
    MobileCard mobileCard = mobileCardService.querryByNumber(cardNumber);
    //根据手机号码绑定的套餐id查询套餐月租
    SerPackageService serPackageService = new SerPackageServiceImpl();
    SerPackage serPackage = serPackageService.queryByType(mobileCard.getSerPackage());
    //根据手机号,本月日期:2024-11-01  查询本月消费金额
    MonthlyConsumptionRecordsService mCRService = new MonthlyConsumptionRecordsServiceImpl();
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.DAY_OF_MONTH,1);
    Date date = cal.getTime();
    MonthlyConsumptionRecords consumptionRecords =mCRService.queryByNumberAndDate(cardNumber,date);
    if(Objects.isNull(consumptionRecords)){
        consumptionRecords=new MonthlyConsumptionRecords();
        consumptionRecords.setCardNumber(cardNumber);
        consumptionRecords.setComsumAmount(new BigDecimal(0));
        consumptionRecords.setConsumeDate(date);
        consumptionRecords.setRealFlow(0);
        consumptionRecords.setRealTalkTime(0);
        consumptionRecords.setRealSmsCount(0);
        mCRService.add(consumptionRecords);
    }
    System.out.println("【友情提示】 您的本月账单如下:");
    System.out.println("\n您的卡号:"+cardNumber);
    System.out.println("套餐资费:"+serPackage.getPrice()+"元");
    System.out.println("合计:"+serPackage.getPrice().doubleValue()+consumptionRecords.getComsumAmount().doubleValue()+"元");
    System.out.println("账户余额:"+mobileCard.getMoney().doubleValue()+"元");
    showSecondMenu();
}

月消费记录业务层实现:

MonthlyConsumptionRecordsServiceImpl:

public MonthlyConsumptionRecords queryByNumberAndDate(String cardNumber, Date date) {
    MonthlyConsumptionRecordsDao mcrDao = new MonthlyConsumptionRecordsDaoImpl();
    return mcrDao.queryByNumberAndDate(cardNumber,date);
}

月消费记录数据访问层实现:

MonthlyConsumptionRecordsDaompl:

public MonthlyConsumptionRecords queryByNumberAndDate(String cardNumber, Date date) {
    QueryRunner queryRunner = new QueryRunner();
    Connection connection = null;
    try {
        connection = getConnection();
        String sql="select id,card_number cardNumber,consum_amount comsumAmount,real_talk_time realTalkTime,real_SMS_count realSmsCount,real_flow realFlow,consume_date consumeDate from tb_monthly_consumption_records " +
                "where card_number = ? and consume_date = ?";
        //注意:要求字段名与属性对应  如果属性名与数据库列名不一致 取别名
        MonthlyConsumptionRecords mcr = queryRunner.query(connection, sql, new BeanHandler<>(MonthlyConsumptionRecords.class), cardNumber,formatDate(date,"yy-MM-dd"));
        return mcr;
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        close(connection);
    }
}

5.2.3 实现后的效果图

 5.4 使用嗖嗖

5.4.1 功能分析

(1)模拟嗖嗖用户使用卡的过程,选择该功能后,输入当前卡号,通过验证后,可随机进入如下表的6个场景,要求所进入的场景的服务类型是该卡所属套餐支持的(如网虫套餐只能进入服务类型为"上网"的场景)

序号服务类型描述
0通话问候客户,谁知其如此难缠,通话90分钟
1通话询问妈妈身体状况,本地通话30分钟
2短信参与环境保护实施方案问卷调查,发送短信5条
3短信同时朋友本人已换手机号码,发送短信50条
4上网和女朋友微信视频聊天,使用流量1GB
5上网晚上手机在线追剧,一不留神睡着了,使用流量2GB
(2) 模拟消费,进入场景之后,将按场景的描述要求消费套餐余量, 如果套餐余量不足,则需要按套餐外的费用规则扣费,成功消费后,添加一条消费记录

5.4.2 实现步骤

  1. 输入手机号码
  2. 根据手机号码查询用户手机卡,判断是否存在,是否禁用
  3. 查询所有场景,随机一个场景
  4. 根据场景类型(通话,短信,上网),判断是否额外支付,余额是否满足消费
  5. 如果余额不足本次消费,计算最大消费数据
  6. 如果产生费用,修改用户卡中余额,没有产生费用,不需要修改
  7. 修改月账单记录表,真实数据,额外费用
  8. 往消费记录表插入一条数据
  9. 输出结果
  10. 该查询的表:套餐表 用户卡表 月消费记录表 场景表 消费记录表

5.4.3 代码

部分数据库表查询和增删改的方法前面写过,所以要增加的数据分层实现方法比较轻松实现,难得是UI层和计算

UI层实现:

private void useSoSoUI() {
    System.out.println("\n---------------------使用嗖嗖--------------------");
    System.out.print("请输入手机卡号:");
    cardNumber=scanner.next();
    //根据手机号码查询用户手机卡,判断是否存在,是否禁用
    MobileCardService mobileCardService = new MobileCardServiceImpl();
    MobileCard mobileCard = mobileCardService.querryByNumber(cardNumber);
    if(Objects.isNull(mobileCard)){
        System.out.println("【友情提示】 该卡不存在,无法使用");
        return;
    }else if(mobileCard.getStatus().intValue() == CARD_FREEZEN){
        System.out.println("【友情提示】 该卡已被冻结,无法使用");
        return;
    }
    //查询卡的套餐
    SerPackageService serPackageService = new SerPackageServiceImpl();
    SerPackage serPackage = serPackageService.queryByType(mobileCard.getSerPackage());
    //查询所有场景
    SceneService sceneService=new SceneServiceImpl();
    List<Scene> scenes = sceneService.queryAll();
    //随机一个场景
    Random random = new Random();
    int index = random.nextInt(scenes.size());
    Scene scene = scenes.get(index);
    String sceneType=scene.getType();
    //查询余额消费记录
    MonthlyConsumptionRecordsService mcrService = new MonthlyConsumptionRecordsServiceImpl();
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.DAY_OF_MONTH,1);
    Date date = cal.getTime();
    MonthlyConsumptionRecords consumptionRecords =mcrService.queryByNumberAndDate(cardNumber,date);
    if(Objects.isNull(consumptionRecords)){
        consumptionRecords=new MonthlyConsumptionRecords();
        consumptionRecords.setCardNumber(cardNumber);
        consumptionRecords.setComsumAmount(new BigDecimal(0));
        consumptionRecords.setConsumeDate(date);
        consumptionRecords.setRealFlow(0);
        consumptionRecords.setRealTalkTime(0);
        consumptionRecords.setRealSmsCount(0);
        mcrService.add(consumptionRecords);
    }
    ConsumInfo consumInfo=new ConsumInfo();
    ConsumInfoService csrService = new ConsumInfoServiceImpl();
    int margin=0;
    BigDecimal amount = new BigDecimal(0); //需要支付的费用
    int extra = 0; //额外通话时长
    int real = scene.getData();
    switch (sceneType){
        case "通话":
             //判断余量是否满足本次消费
            //余量= 套餐免费通话时长 - 本月已消费时长
            margin=serPackage.getTalkTime()-consumptionRecords.getRealTalkTime(); //余量
            margin = margin < 0 ? 0 : margin;
            if(margin < scene.getData()){  //余量  不足
                extra = scene.getData() - margin;  //额外需要的通话时长
                amount = new BigDecimal(extra).multiply(new BigDecimal(EXTRA_TALK));  //额外需要的话费
                //余额是否满足需要支付的话费
                if(mobileCard.getMoney().compareTo(amount) < 0){  //不满足
                    //计算最大消费数据
                    extra=mobileCard.getMoney().divide(new BigDecimal(EXTRA_TALK),2,BigDecimal.ROUND_HALF_UP).intValue();
                    amount = new BigDecimal(extra).multiply(new BigDecimal(EXTRA_TALK));
                    real=margin+extra;  //余额不足  真实通话时长
                    System.out.println("【友情提示】本次已通话"+real+"分钟,您的余额不足,请充值后使用");
                }
            }
            //如果产生费用,修改用户卡中余额,没有产生费用,不需要修改
            if(amount.compareTo(new BigDecimal(0)) > 0){  //产生额外费用需要支付,修改用户卡中余额
                mobileCard.setMoney(mobileCard.getMoney().subtract(amount));
                mobileCardService.update(mobileCard);
            }
            //修改月账单记录表,真实数据,额外费用
            consumptionRecords.setRealTalkTime(real+consumptionRecords.getRealTalkTime());
            consumptionRecords.setComsumAmount(consumptionRecords.getComsumAmount().add(amount));
            mcrService.update(consumptionRecords);
            //往消费记录表插入一条数据
            consumInfo.setConsumeDate(new Date());
            consumInfo.setCardNumber(cardNumber);
            consumInfo.setType(sceneType);
            consumInfo.setConsumData(real);
            csrService.add(consumInfo);
            System.out.println(scene.getDescription());
            break;
        case "短信":
            //判断余量是否满足本次消费
            //余量= 套餐免费通话时长 - 本月已消费时长
            margin=serPackage.getSmsCount()-consumptionRecords.getRealSmsCount(); //余量
            margin = margin < 0 ? 0 : margin;
            if(margin < scene.getData()){  //余量  不足
                extra = scene.getData() - margin;  //额外需要的通话时长
                amount = new BigDecimal(extra).multiply(new BigDecimal(EXTRA_SMS));  //额外需要的话费
                //余额是否满足需要支付的话费
                if(mobileCard.getMoney().compareTo(amount) < 0){  //不满足
                    //计算最大消费数据
                    extra=mobileCard.getMoney().divide(new BigDecimal(EXTRA_SMS),2,BigDecimal.ROUND_HALF_UP).intValue();
                    amount = new BigDecimal(extra).multiply(new BigDecimal(EXTRA_SMS));
                    real=margin+extra;  //余额不足  真实通话时长
                    System.out.println("【友情提示】本次已发送"+real+"条,您的余额不足,请充值后使用");
                }
            }
            //如果产生费用,修改用户卡中余额,没有产生费用,不需要修改
            if(amount.compareTo(new BigDecimal(0)) > 0){  //产生额外费用需要支付,修改用户卡中余额
                mobileCard.setMoney(mobileCard.getMoney().subtract(amount));
                mobileCardService.update(mobileCard);
            }
            //修改月账单记录表,真实数据,额外费用
            consumptionRecords.setRealSmsCount(real+consumptionRecords.getRealSmsCount());
            consumptionRecords.setComsumAmount(consumptionRecords.getComsumAmount().add(amount));
            mcrService.update(consumptionRecords);
            //往消费记录表插入一条数据
            consumInfo.setConsumeDate(new Date());
            consumInfo.setCardNumber(cardNumber);
            consumInfo.setType(sceneType);
            consumInfo.setConsumData(real);
            csrService.add(consumInfo);
            System.out.println(scene.getDescription());
            break;
        case "上网":
            //判断余量是否满足本次消费
            //余量= 套餐免费通话时长 - 本月已消费时长
            margin=serPackage.getFlow()-consumptionRecords.getRealFlow(); //余量
            margin = margin < 0 ? 0 : margin;
            if(margin < scene.getData()){  //余量  不足
                extra = scene.getData() - margin;  //额外需要的通话时长
                amount = new BigDecimal(extra).multiply(new BigDecimal(EXTRA_FLOW));  //额外需要的话费
                //余额是否满足需要支付的话费
                if(mobileCard.getMoney().compareTo(amount) < 0){  //不满足
                    //计算最大消费数据
                    extra=mobileCard.getMoney().divide(new BigDecimal(EXTRA_FLOW),2,BigDecimal.ROUND_HALF_UP).intValue();
                    amount = new BigDecimal(extra).multiply(new BigDecimal(EXTRA_FLOW));
                    real=margin+extra;  //余额不足  真实通话时长
                    System.out.println("【友情提示】本次已使用"+real+"MB的流量,您的余额不足,请充值后使用");
                }
            }
            //如果产生费用,修改用户卡中余额,没有产生费用,不需要修改
            if(amount.compareTo(new BigDecimal(0)) > 0){  //产生额外费用需要支付,修改用户卡中余额
                mobileCard.setMoney(mobileCard.getMoney().subtract(amount));
                mobileCardService.update(mobileCard);
            }
            //修改月账单记录表,真实数据,额外费用
            consumptionRecords.setRealFlow(real+consumptionRecords.getRealFlow());
            consumptionRecords.setComsumAmount(consumptionRecords.getComsumAmount().add(amount));
            mcrService.update(consumptionRecords);
            //往消费记录表插入一条数据
            consumInfo.setConsumeDate(new Date());
            consumInfo.setCardNumber(cardNumber);
            consumInfo.setType(sceneType);
            consumInfo.setConsumData(real);
            csrService.add(consumInfo);
            System.out.println(scene.getDescription());
            break;
    }
    showFirstMenu();
}

消费记录业务层实现:

ConsumInfoServiceImpl:

public void add(ConsumInfo consumInfo) {
    ConsumInfoDao consumInfoDao=new ConsumInfoDaoImpl();
    consumInfoDao.add(consumInfo);
}

消费记录数据分层实现:

ConsumInfoDaoImpl:

public void add(ConsumInfo consumInfo) {
    Connection connection = null;
    try {
        connection = getConnection();
        String sql="insert into tb_consuminfo(card_number,type,consum_data,consume_date) values(?,?,?,?)";
        QueryRunner queryRunner=new QueryRunner();
        queryRunner.update(connection,sql,consumInfo.getCardNumber(),consumInfo.getType(),
                consumInfo.getConsumData(), DateUtil.formatDate(consumInfo.getConsumeDate(),"yy-MM-dd hh:mm:ss"));
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        close(connection);
    }
}

月账单记录业务层实现:

public void update(MonthlyConsumptionRecords monthlyConsumptionRecords) {
    MonthlyConsumptionRecordsDao mcrDao = new MonthlyConsumptionRecordsDaoImpl();
    mcrDao.update(monthlyConsumptionRecords);
}

月账单记录数据访问层实现:

public void update(MonthlyConsumptionRecords mcrRecords) {
     Connection connection = null;
    try {
        connection = getConnection();
        String sql="update tb_monthly_consumption_records set consum_amount = ?,real_talk_time = ?,real_SMS_count = ?,real_flow = ? where id = ?";
        QueryRunner queryRunner=new QueryRunner();
        queryRunner.update(connection,sql,mcrRecords.getComsumAmount(),mcrRecords.getRealTalkTime(),
                mcrRecords.getRealSmsCount(),mcrRecords.getRealFlow(),mcrRecords.getId());
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }finally {
        close(connection);
    }
}

5.4.4 实现后的效果图 

消费成功:
 消费失败:

将套餐中的固定数额消费完后,再使用嗖嗖就会显示余额不足

相关文章:

模拟手机办卡项目(移动大厅)--结合面向对象、JDBC、MYSQL、dao层模式,使用JAVA控制台实现

目录 1. 项目需求 2. 项目使用的技术 3.项目需求分析 3.1 实体类和接口 4.项目结构 5.业务实现 5.1 登录 5.1.1 实现步骤 5.1.2 原生代码问题 ​编辑 5.1.3 解决方法 1.说明&#xff1a; 2. ResultSetHandler结果集处理 5.1.4 代码 5.1.5 实现后的效果图 登录成功​…...

机器学习—大语言模型:推动AI新时代的引擎

云边有个稻草人-CSDN博客 目录 引言 一、大语言模型的基本原理 1. 什么是大语言模型&#xff1f; 2. Transformer 架构 3. 模型训练 二、大语言模型的应用场景 1. 文本生成 2. 问答系统 3. 编码助手 4. 多语言翻译 三、大语言模型的最新进展 1. GPT-4 2. 开源模型 …...

C++:探索哈希表秘密之哈希桶实现哈希

文章目录 前言一、链地址法概念二、哈希表扩容三、哈希桶插入逻辑四、析构函数五、删除逻辑六、查找七、链地址法代码实现总结 前言 前面我们用开放定址法代码实现了哈希表&#xff1a; C&#xff1a;揭秘哈希&#xff1a;提升查找效率的终极技巧_1 对于开放定址法来说&#…...

具身智能高校实训解决方案——从AI大模型+机器人到通用具身智能

一、 行业背景 在具身智能的发展历程中&#xff0c;AI 大模型的出现成为了关键的推动力量。这些大模型具有海量的参数和强大的语言理解、知识表示能力&#xff0c;能够为机器人的行为决策提供更丰富的信息和更智能的指导。然而&#xff0c;单纯的大模型在面对复杂多变的现实…...

【消息序列】详解(8):探秘物联网中设备广播服务

目录 一、概述 1.1. 定义与特点 1.2. 工作原理 1.3. 应用场景 1.4. 技术优势 二、截断寻呼&#xff08;Truncated Page&#xff09;流程 2.1. 截断寻呼的流程 2.2. 示例代码 2.3. 注意事项 三、无连接外围广播过程 3.1. 设备 A 启动无连接外围设备广播 3.2. 示例代…...

【RL Base】强化学习核心算法:深度Q网络(DQN)算法

&#x1f4e2;本篇文章是博主强化学习&#xff08;RL&#xff09;领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对相关等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅…...

深入浅出 Python 网络爬虫:从零开始构建你的数据采集工具

在大数据时代&#xff0c;网络爬虫作为一种数据采集技术&#xff0c;已经成为开发者和数据分析师不可或缺的工具。Python 凭借其强大的生态和简单易用的语言特点&#xff0c;在爬虫领域大放异彩。本文将带你从零开始&#xff0c;逐步构建一个 Python 网络爬虫&#xff0c;解决实…...

美国发布《联邦风险和授权管理计划 (FedRAMP) 路线图 (2024-2025)》

文章目录 前言一、战略目标实施背景2010年12月&#xff0c;《改革联邦信息技术管理的25点实施计划》2011年2月&#xff0c;《联邦云计算战略》2011年12月&#xff0c;《关于“云计算环境中的信息系统安全授权”的首席信息官备忘录》2022年12月&#xff0c;《FedRAMP 授权法案》…...

Python语法基础(三)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 我们这篇文章来说一下函数的返回值和匿名函数 函数的返回值 我们先来看下面的这一段函数的定义代码 # 1、返回值的意义 def func1():print(111111111------start)num166print…...

云计算之elastaicsearch logstach kibana面试题

1.ELK是什么? ELK 其实并不是一款软件,而是一整套解决方案,是三个软件产品的首字母缩写 Elasticsearch:负责日志检索和储存 Logstash:负责日志的收集和分析、处理 Kibana:负责日志的可视化 这三款软件都是开源软件,通常是配合使用,而且又先后归于 Elastic.co 公司名下,…...

【已解决】git push需要输入用户名和密码问题

解决方法&#xff1a; 1&#xff09;查看使用的clone方式&#xff1a; git remote -v 2&#xff09;若为HTTPS&#xff0c;删除原clone方式: git remote rm origin 3&#xff09;添加新的clone方式&#xff1a; git remote add origin gitgithub.com:zludon/git_test.git …...

python的字符串处理

需求&#xff1a; 编写一个程序&#xff0c;输入一段英文句子&#xff0c;统计每个单词的长度&#xff0c;并将单词按照长度从短到长排序。 程序逻辑框图 1、用户输入一句英文句子。 2、对输入的句子进行预处理&#xff08;去空格并分割为单词列表&#xff09;。 3、统计每个单…...

【线程】Java多线程代码案例(2)

【线程】Java多线程代码案例&#xff08;2&#xff09; 一、定时器的实现1.1Java标准库定时器1.2 定时器的实现 二、线程池的实现2.1 线程池2.2 Java标准库中的线程池2.3 线程池的实现 一、定时器的实现 1.1Java标准库定时器 import java.util.Timer; import java.util.Timer…...

虚拟机之间复制文件

在防火墙关闭的前提下&#xff0c;您可以通过几种不同的方法将文件从一个虚拟机复制到另一个虚拟机。这里&#xff0c;我们假设您想要从 IP 地址为 192.168.4.5 的虚拟机上的 /tmp 文件夹复制文件到当前虚拟机&#xff08;192.168.4.6&#xff09;的 /tmp 文件夹下。以下是几种…...

如何为 XFS 文件系统的 /dev/centos/root 增加 800G 空间

如何为 XFS 文件系统的 /dev/centos/root 增加 800G 空间 一、前言二、准备工作三、扩展逻辑卷1. 检查现有 LVM 配置2. 扩展物理卷3. 扩展卷组4. 扩展逻辑卷四、调整文件系统大小1. 检查文件系统状态2. 扩展文件系统五、处理可能出现的问题1. 文件系统无法扩展2. 磁盘空间不足3…...

Java算法OJ(11)双指针练习

目录 1.前言 2.正文 2.1存在重复数字 2.1.1题目 2.1.2解法一代码 解析&#xff1a; 2.1.3解法二代码 解析&#xff1a; 2.2存在重复数字plus 2.2.1题目 2.2.2代码 2.2.3解析 3.小结 1.前言 哈喽大家好吖&#xff0c;今天来给大家分享双指针算法的相关练习&…...

44.扫雷第二部分、放置随机的雷,扫雷,炸死或成功 C语言

按照教程打完了。好几个bug都是自己打出来的。比如统计周围8个格子时&#xff0c;有一个各自加号填成了减号。我还以为平移了&#xff0c;一会显示是0一会显示是2。结果单纯的打错了。debug的时候断点放在scanf后面会顺畅一些。中间多放一些变量名方便监视。以及mine要多显示&a…...

大语言模型LLM的微调代码详解

代码的摘要说明 一、整体功能概述 这段 Python 代码主要实现了基于 Hugging Face Transformers 库对预训练语言模型&#xff08;具体为 TAIDE-LX-7B-Chat 模型&#xff09;进行微调&#xff08;Fine-tuning&#xff09;的功能&#xff0c;使其能更好地应用于生成唐诗相关内容的…...

钉钉与企业微信机器人:助力网站定时任务高效实现

钉钉、企业微信机器人在网站定时任务中的应用&#xff0c;主要体现在自动化通知、提醒以及数据处理等方面。 以下是一些具体的应用场景&#xff1a; 1. 自动化通知 项目进度提醒&#xff1a;在蒙特网站所负责的软件开发或网站建设项目中&#xff0c;可以利用机器人设置定时任…...

自然语言处理工具-广告配音工具用于语音合成助手/自媒体配音/广告配音/文本朗读-已经解锁了 全功能的 apk包

Android -「安卓端」 广告配音工具用于语音合成助手/自媒体配音/广告配音/文本朗读。 广告配音工具&#xff1a;让您的文字“说话”&#xff0c;在这个快速发展的数字时代&#xff0c;广告配音工具为各种语音合成需求提供了一站式解决方案。无论是自媒体配音、商业广告配音、…...

深入解析注意力机制

引言随着深度学习的快速发展&#xff0c;注意力机制&#xff08;Attention Mechanism&#xff09;逐渐成为许多领域的关键技术&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;和计算机视觉&#xff08;CV&#xff09;中。其核心思想是赋予模型“关注重点”的能力…...

Unity图形学之雾Fog

1.设置雾化&#xff1a; 2.雾化变化曲线&#xff1a;FogMode &#xff08;1&#xff09;线性&#xff1a; &#xff08;2&#xff09;一次指数&#xff1a; &#xff08;3&#xff09;二次指数&#xff1a; Shader "Custom/FogTest" {Properties{_Color ("Color…...

【大数据学习 | Spark-Core】详解Spark的Shuffle阶段

1. shuffle前言 对spark任务划分阶段&#xff0c;遇到宽依赖会断开&#xff0c;所以在stage 与 stage 之间会产生shuffle&#xff0c;大多数Spark作业的性能主要就是消耗在了shuffle环节&#xff0c;因为该环节包含了大量的磁盘IO、序列化、网络数据传输等操作。 负责shuffle…...

如何启动 Docker 服务:全面指南

如何启动 Docker 服务:全面指南 一、Linux 系统(以 Ubuntu 为例)二、Windows 系统(以 Docker Desktop 为例)三、macOS 系统(以 Docker Desktop for Mac 为例)四、故障排查五、总结Docker,作为一种轻量级的虚拟化技术,已经成为开发者和运维人员不可或缺的工具。它允许用…...

使用client-go在命令空间test里面对pod进行操作

目录 一、获取使用restApi调用的token信息 二、client-go操作pod示例 1、获取到客户端 2、创建pod 3、获取test命令空间的所有pod 4、获取某个具体pod的详细信息 5、更新pod 6、删除pod 三、总结 官方参考地址&#xff1a;https://kubernetes.io/docs/reference/kuber…...

Linux中网络文件系统nfs使用

一、nfs服务 NFS&#xff08;Network File System&#xff09; 是一种用于在网络中共享文件的协议&#xff0c;允许不同操作系统&#xff08;如 Linux、Unix、MacOS 等&#xff09;之间进行文件共享。 NFS 的工作原理基于客户端-服务器模型&#xff0c;服务器提供共享文件系统…...

气膜建筑:打造全天候安全作业空间,提升工程建设效率—轻空间

在现代建筑工程中&#xff0c;施工环境的管理和作业效率是决定项目进度和质量的关键因素。然而&#xff0c;施工过程中常常会受到天气变化的影响&#xff0c;诸如大风、雨雪、沙尘等恶劣天气常常延误工期&#xff0c;增加施工难度。为了解决这一问题&#xff0c;气膜建筑以其独…...

【HarmonyOS学习日志(10)】一次开发,多端部署之功能级一多开发,工程级一多开发

功能级一多开发 SysCap机制介绍 HarmonyOS使用SysCap机制&#xff08;即SystemCapability&#xff09;&#xff0c;可以帮助开发者仅关注设备的系统能力&#xff0c;而不用考虑成百上千种具体的设备类型。 在过去&#xff0c;开发不同设备上的应用就用不同设备的SDK进行开发&…...

dmdba用户资源限制ulimit -a 部分配置未生效

dmdba用户资源限制ulimit -a 部分配置未生效 1 环境介绍2 数据库实例日志报错2.1 mpp01 实例日志报错2.2 mpp02 实例日志报错 3 mpp02 服务器资源限制情况4 关闭SELinux 问题解决4.1 临时关闭 SELinux4.2 永久关闭 SELinux 5 达梦数据库学习使用列表 1 环境介绍 Cpu x86 Os Ce…...

【Code First】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列 &#x1f…...

网站建设算行政工作吗/企业如何网络推广

输入输出参数: 给存储过程传参数,叫做输入参数,用户告诉存储过程需要 利用这个参数干些什么. 输出参数: 从存储过程得到那些数据. 创建一个可选参数的存储过程: create proc pa1 name varchar(50)NULL as if(name is not null)select * from a where name like name%; elsesele…...

旅游网站专业化建设的要点/产品网络营销策划

基于窗体的身份验证是一项 ASP.NET 身份验证服务&#xff0c;它使应用程序能够提供自己的登录用户界面并进行自己的凭据验证。ASP.NET 对用户进行身份验证&#xff0c;将未经身份验证的用户重定向到登录页&#xff0c;并执行所有必要的 Cookie 管理。这种身份验证是许多网站使用…...

安陆网站建设推广/百度网盘资源搜索

本章首要介绍一下搜索引擎蜘蛛都有哪些类型。了解类型之前要先知道什么是搜索引擎蜘蛛。百度百科上有相关解说&#xff0c;我们用一个形象的表述重新说一下。以百度为例&#xff0c;大家要为什么能在百度上搜到各类网站网页的内容&#xff0c;是由于百度派出去的小弟—百度蜘蛛…...

如何查到网站是谁做的/无锡seo公司

输入描写叙述: 输入包括多组数据&#xff0c;每组数据包括两行。 第一行为正整数n&#xff08;3≤n≤50&#xff09;。紧接着第二行包括n个由数值和运算符组成的列表。 “-*/”分别为加减乘除四则运算。当中除法为整除。即“5/31”。 输出描写叙述: 相应每一组数据&#xff0c;…...

做化工的网站/南京网站制作设计

微信上进行的网页宣传、游戏传播、APP下载各类活动很多&#xff0c;但是各位朋友肯定经常会遇到一些特殊需求&#xff0c;网页需要在手机默认浏览器打开而不是微信内置浏览器。这个问题怎么解决呢? 斗在微信营销的浪潮中 解决方案&#xff1a;微信中打开链接&#xff0c;自动打…...

判断网站模板版本/常用的搜索引擎

在寒冷的冬天&#xff0c;很多车主在开车的时候会出现一个问题&#xff0c;那就是凉车启动困难这个问题&#xff0c;具体就是打不着火&#xff0c;必须得打几下才能启动&#xff0c;或者在打火的时候&#xff0c;发动机运转了几个行程之后才能打着火&#xff0c;而且打着火以后…...