# simulated_bank_all **Repository Path**: wangrenxuan/simulated_bank_all ## Basic Information - **Project Name**: simulated_bank_all - **Description**: 基于 Spring Boot 4.0.8 + Vue 3 的模拟银行网银系统(登录、首页和账单查询、AI 风控转账) - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-29 - **Last Updated**: 2026-10-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # simulated_bank 模拟银行网银系统,实现基础网银业务。 功能:用户登录、首页展示、账单查询、AI风控转账(功能尚未完全开发完成)。 技术栈: 后端:Spring Boot、MySQL、Redis、RabbitMQ 前端:Vue3 + Vite ## 1. 开发环境 | 组件 | 版本 | 说明 | |---|---|---| | Docker Desktop | - | 容器编排,`docker compose up -d` 一键启动 | | MySQL | 8.0 | 容器内 3306,本机映射 **3307**(避免与本机已装 MySQL 端口冲突) | | Redis | 7-alpine | 默认 6379,无密码 | | RabbitMQ | 3.13-management | AMQP 5672,管理后台 15672(guest/guest) | | Spring Boot | **4.0.8** | Java 17,Spring 7.0.9 | | MyBatis | 4.0.1 | 非 MyBatis-Plus | | JJWT | 0.12.6 | HS256 签名 | | BCryptPasswordEncoder | - | 用户登录密码 + 账户支付密码双重加密 | 镜像来源:[https://github.com/Rodert/DockerHub](https://github.com/Rodert/DockerHub) (国内镜像加速仓库) ## 2. 初始化 启动中间件 docker compose up -d 确认三个容器 healthy docker compose ps 连接测试(IDE 里直接跑 ConnectTest.java,全程 try-with-resources,确保连接自动释放) ### 2.1 建表(schemaRead.sql) 三张表,模拟真实银行数据模型: - **customer** — 客户表:登录手机号、BCrypt 密码、风险等级、客户层冻结状态。 - **account** — 账户表:核心资金表,银行卡号、余额、BCrypt 支付密码、单卡状态。设计为客户-账户 1:N,因为真实银行一个人通常同时持有活期、定期、贷款、信用卡等多类账户。 - **transaction** — 交易流水表:历史流水禁止 UPDATE/DELETE,错账只能新增 trans_type=5 冲正流水。一笔转账生成两条流水(转出+转入),通过 trans_ref 关联。按月 RANGE 分区(p202609 / p202610 / p_future),查某月流水时命中分区裁剪,只扫对应分区。 索引:`account.customer_id`(查名下账户)、`transaction(account_id, trans_time)`(按账户+时间查流水)、`transaction.trans_ref`(转账关联)。 ### 2.2 数据初始化 由 `DataInitializer`(CommandLineRunner)在应用启动时执行: - **Java 层而非 SQL**,因为 BCrypt 密码哈希值需要 Java 生成(SQL 层无法调用 Spring Security 的 encoder) - 带 `@Transactional`,客户/账户/流水一次性插入,任一步失败全部回滚 - customer 表有数据则跳过(幂等) - 默认账号:`13800000001/13800000002/13800000003`,登录密码和支付密码均为 `123456`(BCrypt 加密后存入) ## 3. 功能链路 ### 3.1 鉴权层(JWT + Redis 双重校验) 所有请求先过 `JwtAuthFilter`: 1. `/auth/**` 放行 2. 从 Header 取 `Authorization: Bearer xxx` 3. 解析 JWT 取出 customerId 4. **Redis 二次校验**:查 `bank:customer:{customerId}` key 是否存在。JWT 即使没过期(2h),Redis key 被手动删除或 TTL 到期 → 拒绝。**这样可以强制下线用户**,纯 JWT 做不到。 5. 注入 SecurityContext,principal 即为 customerId,Controller 用 `@AuthenticationPrincipal Long customerId` 直接拿 ### 3.2 登录 `/auth/login` 链路:手机号phone 查 customer 表是否存在 → BCrypt 校验密码 → 检查cutomer表里的 客户层冻结状态 account_status → Redis Hash 写入登录态(customerId / realName / phone / riskLevel,TTL 2h)→ JwtUtil.generate(customerId) 生成JWT token → **只返回 token**(前端 localStorage 存)。 ### 3.3 首页 `/home/dashboard` 链路: 1. JwtAuthFilter 放行 → Controller 拿到 customerId 2. **Redis Hash 取 realName**(脱敏返回),取不到抛 10003 3. **DB 查 account WHERE customer_id = ?**(走 idx_customer_id 索引),**余额必须查数据库**(不缓存,直接 SELECT 拿;转账/消费时 WHERE account_id FOR UPDATE 加锁) 4. 卡号脱敏(前4 + ********** + 后4),账户列表返回前端同时 **缓存 accountNos 和 accountIds 到同一个 Redis Hash**(TTL 跟随登录态 key) 5. 本月收/支:transaction 表 account_id IN (...) + trans_time ≥月初 <下月初,CASE WHEN amount>0/ 0 聚合(走分区裁剪) 同一登录状态内刷新页面: accountNos/accountIds 已在 Redis → 第 4 步缓存命中不再回写;realName 也在 Redis → 不触发 10003。如果用户手动删了 Redis key,下次请求进 AuthFilter 就被踢去登录页。 ### 3.4 账单 `/home/bill` 链路: 1. 从 Redis `bank:customer:{customerId}` 的 accountIds 字段确认该 accountId 属于当前用户(防越权) 2. DB 查 transaction WHERE account_id = ? + trans_time 按月范围(走分区裁剪) 3. trans_type / trans_category 用 Java 枚举 `TransTypeEnum` / `TransCategoryEnum` 转中文 4. 无结果抛 20001;有结果返回列表,含 transRef / orderId / remark / transTime ## 4. 异常处理 自定义 `BizException` + `ErrorCode` 枚举 + `GlobalExceptionHandler`统一捕获。 ### 错误码一览 | code | 含义 | 前端行为 | |---|---|---| | 10001 | 手机号或密码错误 | 页面弹窗 | | 10002 | 账户冻结 | 页面弹窗 | | 10003 | 登录态过期 | 清除 token → 跳转 /loginpage | | 10004 | 无账户 | 页面弹窗 | | 20001 | 该月无账单 | 页面弹窗 | | 20002 | 账户不存在 | 页面弹窗 | | 20003 | 账户不属于当前用户 | 页面弹窗 | | 50000 | 系统内部错误 | 页面弹窗 | 前端 request.js 拦截器:`AUTH_CODES = [10003, 40101, 40102]` 统一跳登录页,其余业务码 ElMessage 弹窗。