源本科技 | 码上会

Java 简易银行系统

2025/12/27
121
0

学习目标

  • 掌握 三层架构设计view/controller(视图或控制器层) + mapper(数据访问层) + service(业务逻辑层)

  • 实现 纯内存存储(使用数组模拟数据库),理解无持久化场景下的数据管理

  • 理解 事务回滚的模拟机制 在转账中的作用(通过状态回退实现)

  • 能够设计 菜单驱动的控制台应用程序

  • 实践 封装与模块化编程,提升代码可维护性


总体目标

构建一个命令行界面(CLI)的简易银行系统,实现账户创建、登录、余额查询和转账功能


核心功能需求

1. 账户创建

  • 输入

    • 用户名(非空、唯一)

    • 密码(非空,明文存储)

  • 处理逻辑

    • 系统自动分配账号(从 1001 开始递增)

    • 初始余额固定为 1000 元

    • 若用户名已存在,拒绝创建

    • 系统最多支持 5 个账户(数组容量限制)

  • 输出

    • 成功:提示“账户创建成功!账号:XXXX”

    • 失败:提示“用户名已存在” 或 “系统已满”


2. 用户登录

  • 输入

    • 用户名

    • 密码

  • 验证规则

    • 用户名必须存在

    • 密码必须完全匹配(区分大小写)

  • 输出

    • 成功:进入已登录菜单

    • 失败:提示“用户名或密码错误!”


3. 查询余额

  • 触发条件:用户登录后选择“查询余额”

  • 显示格式

-------------------------------------------------
账号        客户姓名      余额
1001        张三          1000
-------------------------------------------------
  • 要求:实时显示当前账户信息


4. 转账功能

  • 输入

    • 收款人账号(整数)

    • 转账金额(正整数)

  • 校验逻辑(顺序执行,模拟事务)

    1. 转账金额 > 0

    2. 转出账户余额 ≥ 转账金额

    3. 收款账号存在(在系统中)

  • 执行逻辑

    • 若所有校验通过,则 同时更新 转出方与收款方余额

    • 否则,不执行任何修改(即“回滚”效果)

  • 输出

    • 成功:“转账成功!”

    • 失败:对应错误提示(如“余额不足”、“收款账号不存在”等)

注意:由于是单线程程序,无需锁机制;通过“先校验、再一次性修改”即可保证原子性


5. 操作流程

启动程序
   │
   ├─→ [1] 创建账户 → 输入用户名/密码 → 成功/失败提示
   │
   ├─→ [2] 登录 → 输入凭据 → 
   │        │
   │        └─→ 成功 → 进入子菜单:
   │                 ├─ [1] 转账
   │                 ├─ [2] 查余额
   │                 └─ [3] 退出登录
   │
   └─→ [3] 退出系统 → 程序终止

开发流程

项目结构

MiniBankingApp/
├── src/
│   └── banking/
│       ├── entity/          // 实体类
│       │   └── Account.java
│       ├── mapper/          // 数据访问层(内存模拟)
│       │   └── AccountMapper.java
│       ├── service/         // 业务逻辑层
│       │   └── BankService.java
│       ├── views/           // 视图层(UI 控制台交互)
│       │   └── BankView.java
│       └── Main.java        // 程序入口
└──

包命名规范清晰体现分层职责,符合现代 Java 工程实践

1. 实体层

Entity

package banking.entity;

public class Account {
  private int accountNo;
  private String username;
  private String password; // 实际应加密,此处简化
  private int balance;

  public Account(int accountNo, String username, String password) {
    this.accountNo = accountNo;
    this.username = username;
    this.password = password;
    this.balance = 1000; // 初始余额
  }

  public int getAccountNo() {
    return accountNo;
  }

  public void setAccountNo(int accountNo) {
    this.accountNo = accountNo;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public int getBalance() {
    return balance;
  }

  public void setBalance(int balance) {
    this.balance = balance;
  }
}

2. 数据访问层

Mapper

package banking.mapper;

import banking.entity.Account;

public class AccountMapper {

  // 使用固定长度为 5 的数组存储账户
  private static final Account[] ACCOUNTS = new Account[5];
  private static int nextAccountNo = 1001; // 起始账号

  // 获取下一个可用账号
  public static int getNextAccountNo() {
    return nextAccountNo++;
  }

  // 检查用户名是否已存在
  private static boolean usernameExists(String username) {
    for (Account acc : ACCOUNTS) {
      if (acc != null && acc
              .getUsername()
              .equals(username)) {
        return true;
      }
    }
    return false;
  }

  // 查找第一个空位(用于插入新账户)
  private static int findEmptySlot() {
    for (int i = 0; i < ACCOUNTS.length; i++) {
      if (ACCOUNTS[i] == null) {
        return i;
      }
    }
    return -1; // 数组已满
  }

  // 保存新账户(模拟“插入”)
  public static boolean save(Account account) {
    if (usernameExists(account.getUsername())) {
      return false; // 用户名重复
    }

    int slot = findEmptySlot();
    if (slot == -1) {
      System.out.println("系统已达到最大账户数量(5个),无法创建新账户!");
      return false;
    }

    ACCOUNTS[slot] = account;
    return true;
  }

  // 按用户名查找账户
  public static Account findByUsername(String username) {
    for (Account acc : ACCOUNTS) {
      if (acc != null && acc
              .getUsername()
              .equals(username)) {
        return acc;
      }
    }
    return null;
  }

  // 按账号查找账户
  public static Account findByAccountNo(int accountNo) {
    for (Account acc : ACCOUNTS) {
      if (acc != null && acc.getAccountNo() == accountNo) {
        return acc;
      }
    }
    return null;
  }

  // (可选)获取当前账户总数,用于调试
  public static int getAccountCount() {
    int count = 0;
    for (Account acc : ACCOUNTS) {
      if (acc != null) count++;
    }
    return count;
  }
}

3. 业务逻辑层

Service

package banking.service;

import banking.entity.Account;
import banking.mapper.AccountMapper;

public class BankService {

  public static void createAccount(String username, String password) {
    if (username == null || username
            .trim()
            .isEmpty() ||
            password == null || password
            .trim()
            .isEmpty()) {
      System.out.println("用户名和密码不能为空!");
      return;
    }

    Account newAccount = new Account(AccountMapper.getNextAccountNo(), username, password);
    if (AccountMapper.save(newAccount)) {
      System.out.println("账户创建成功!账号:" + newAccount.getAccountNo());
    } else {
      System.out.println("用户名已存在,请更换用户名。");
    }
  }

  public static Account login(String username, String password) {
    Account account = AccountMapper.findByUsername(username);
    if (account != null && account
            .getPassword()
            .equals(password)) {
      return account;
    }
    return null;
  }

  public static void showBalance(Account account) {
    System.out.println("\n-------------------------------------------------");
    System.out.printf("%s %15s %12s\n", "账号", "客户姓名", "余额");
    System.out.printf("%s %16s %15s\n",
            account.getAccountNo(),
            account.getUsername(),
            account.getBalance());
    System.out.println("-------------------------------------------------");
  }

  // 模拟事务:先检查,再执行,失败则不修改(因无并发,直接顺序操作即可)
  public static void transfer(Account sender, int receiverAccountNo, int amount) {
    if (amount <= 0) {
      System.out.println("转账金额必须大于 0!");
      return;
    }
    if (sender.getBalance() < amount) {
      System.out.println("余额不足!");
      return;
    }

    Account receiver = AccountMapper.findByAccountNo(receiverAccountNo);
    if (receiver == null) {
      System.out.println("收款账号不存在!");
      return;
    }

    // 执行转账(原子操作模拟)
    sender.setBalance(sender.getBalance() - amount);
    receiver.setBalance(receiver.getBalance() + amount);

    System.out.println("转账成功!");
  }
}

4. 视图层

package banking.views;

import banking.entity.Account;
import banking.service.BankService;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class BankView {
  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));

  public static void launch() throws Exception {
    while (true) {
      System.out.println("\n===============================");
      System.out.println("      欢迎使用 InBank 银行系统");
      System.out.println("===============================");
      System.out.println("1) 创建账户");
      System.out.println("2) 登录账户");
      System.out.println("3) 退出系统");
      System.out.print("请选择操作编号:");

      int choice = Integer.parseInt(READER.readLine());
      switch (choice) {
        case 1 -> handleCreateAccount();
        case 2 -> handleLogin();
        case 3 -> {
          System.out.println("感谢使用 InBank 银行系统!再见。");
          System.exit(0);
        }
        default -> System.out.println("无效选项!请输入 1、2 或 3。");
      }
    }
  }

  private static void handleCreateAccount() throws Exception {
    System.out.print("请输入唯一用户名:");
    String username = READER.readLine();
    System.out.print("请输入密码:");
    String password = READER.readLine();
    BankService.createAccount(username, password);
  }

  private static void handleLogin() throws Exception {
    System.out.print("请输入用户名:");
    String username = READER.readLine();
    System.out.print("请输入密码:");
    String password = READER.readLine();

    Account account = BankService.login(username, password);
    if (account == null) {
      System.out.println("登录失败:用户名或密码错误!");
      return;
    }

    loggedInMenu(account);
  }

  private static void loggedInMenu(Account account) throws Exception {
    while (true) {
      System.out.println("\n您好," + account.getUsername() + "!请选择操作:");
      System.out.println("1) 转账");
      System.out.println("2) 查询余额");
      System.out.println("3) 退出登录");
      System.out.print("请输入选项编号:");

      int choice = Integer.parseInt(READER.readLine());
      switch (choice) {
        case 1 -> {
          System.out.print("请输入收款人账号:");
          int receiverAc = Integer.parseInt(READER.readLine());
          System.out.print("请输入转账金额:");
          int amount = Integer.parseInt(READER.readLine());
          BankService.transfer(account, receiverAc, amount);
        }
        case 2 -> BankService.showBalance(account);
        case 3 -> {
          System.out.println("已退出登录。");
          return;
        }
        default -> System.out.println("无效选项!");
      }
    }
  }
}

5. 主程序入口

package banking;

import banking.views.BankView;

public class Main {
  public static void main(String[] args) throws Exception {
    BankView.launch();
  }
}

重点总结

  • 三层架构清晰分离关注点entity 定义数据结构,mapper 管理数据存取,service 封装业务规则。

  • 内存模拟数据库 是理解 CRUD 和事务逻辑的有效教学手段。

  • 控制台交互 虽简单,但完整体现了用户流程闭环。

  • 事务原子性 在无数据库时可通过“预检查 + 顺序执行”保证(高并发需额外机制)。