Skip to content

SwissRo1l/Database-Project-main

Repository files navigation

Game Market Project (游戏资产交易市场)

这是一个基于 Spring Boot 和 Vue 3 的全栈游戏资产交易平台。项目模拟了一个真实的游戏内市场,支持玩家之间的物品买卖、价格走势分析(K线图)、资产管理以及钱包充值等功能。

Vue 3 Spring Boot PostgreSQL

✨ 最新更新 (Latest Updates)

  • 🎨 沉浸式 UI: 全新设计的动态粒子背景(Particle Network),支持鼠标互动;新增波浪式加载动画,提升视觉流畅度。
  • 🔍 高级筛选: 市场页面新增“全部商品”视图,支持按分类(步枪、狙击枪、手枪、刀具)筛选,并提供多种排序方式(价格升/降序、最新发布)。
  • 🖼️ 个性化头像: 用户中心支持本地上传自定义头像,打造专属个人资料。
  • 📦 库存管理: 优化了背包界面,新增分页功能,轻松管理海量库存物品。

🚀 主要功能 (Features)

🛒 市场系统 (Marketplace)

  • 实时行情: 首页展示热门推荐商品和 24小时涨幅榜。
  • 全局搜索: 支持通过关键词快速搜索特定装备(如 "AWP", "Dragon Lore")。
  • 分类浏览: 完整的分类索引和排序工具,帮助用户快速发现心仪商品。
  • 视觉体验: 全量覆盖 80+ 种武器皮肤组合的高清预览图。

📊 交易中心 (Trading Center)

  • 专业图表: 集成 ECharts 绘制实时 K线图,支持查看历史价格走势。
  • 深度数据: 实时展示买卖盘口(Order Book),辅助交易决策。
  • 快速撮合: 支持限价买入/卖出,系统自动匹配最优价格进行撮合。

🔥 核心交易功能是怎么实现的(实现细节)

本项目的“交易”可以理解为:

  • Maker(挂单方):提交限价单,进入订单簿等待成交。
  • Taker(吃单方):点击盘口中的某个挂单,按该挂单的价格立即成交(也会被实现为一张“对手方向的新订单”,再交给撮合引擎处理)。

整体链路:HTTP API → 订单/资金/库存预占 → 内存撮合(OrderBook)→ 交易结算(DB 原子更新)→ 通知 + SSE 刷新


0) 核心数据结构(通俗解释:用这些结构把交易跑起来)

为了把“挂单/吃单/成交/撤单”这件事做得既快又不乱,这套交易核心主要用了两类数据结构:内存数据结构(用于快速撮合) + 数据库结构(用于最终一致与可恢复)

A. 内存撮合:每个商品一张“订单簿”

  • MatchingEngineService.booksByAssetIdConcurrentHashMap<Integer, OrderBook>

    • 直观理解:一个大字典(Map),key 是 assetId,value 是这件商品的“订单簿”。
    • 作用:不同商品互不影响,撮合可以并行。
  • OrderBook(每个 assetId 一个)内部:两本“按规则自动排队”的账本

    • bids(买盘):ConcurrentSkipListMap<OrderKey, BookOrder>(按价格 DESC、时间 ASC 排序)
    • asks(卖盘):ConcurrentSkipListMap<OrderKey, BookOrder>(按价格 ASC、时间 ASC 排序)
    • 直观理解:
      • 买盘像“竞价队列”:谁出价更高谁排前面,同价谁更早下单谁排前面。
      • 卖盘像“降价队列”:谁要价更低谁排前面,同价同样按时间先后。
  • bidIndex / askIndexConcurrentHashMap<Integer, OrderKey>

    • 直观理解:为了“撤单”能 O(1) 找到队列里的位置,不用每次都整本翻。
  • OrderBook.lockReentrantLock

    • 直观理解:同一件商品的撮合是“临界区”,一次只允许一个线程在这本订单簿上做“匹配 + 插入/删除”,避免并发把数量扣乱。

B. 事件(Event):撮合与结算解耦的“交接单”

  • TradeExecution:一笔成交的“最小描述”
    • 关键字段:buyOrderId / sellOrderId / price / quantity(外加 maker/taker)
  • TradeExecutedEvent:一批成交的集合(以及可能需要取消的订单)
    • 直观理解:撮合引擎只负责“算出来应该成交哪些”,把结果打包成事件交给结算模块去“改钱/改库存/写成交记录”。

C. 数据库侧的“预占”字段:防止重复使用资金/库存

  • walletbalance + reserved
    • 直观理解:下买单时先把钱从“可用”挪到“冻结”,避免同一份余额被重复下多笔单。
  • player_assetquantity + reserved_quantity
    • 直观理解:下卖单时先把库存从“可卖”挪到“锁定”,避免同一件库存被重复挂卖。
  • market_order.reserved_funds
    • 直观理解:买单在撮合过程中会用掉一部分冻结金额,这个字段记录“这张买单还剩多少冻结可以释放”(例如价格改善导致少花钱)。

以上数据结构合在一起,就能做到:撮合靠内存结构快,结算靠事务落库稳,撤单靠索引结构准,重启靠数据库恢复可用订单簿


1) 交易相关 API(前后端对齐)

前端交易页在 src/views/Trade.vue,主要调用:

  • Maker 下单:POST /api/trade/orders(前端:src/api/trade.js#createOrder
  • 查询我的挂单:GET /api/trade/pending?userId=...(前端:src/api/trade.js#fetchPendingOrders
  • 撤单:
    • POST /api/trade/cancel/lock(先“锁撤单”,从内存订单簿移除,避免继续被撮合)
    • POST /api/trade/cancel(最终撤单,释放预占资金/库存并落库)
    • POST /api/trade/cancel/unlock(撤单解锁:重新加入订单簿)
  • Taker 吃单成交:POST /api/market/trade(前端:src/api/market.js#executeTrade
  • K 线/成交记录:GET /api/market/history?itemId=... 等(前端:src/api/market.js#fetchTradeHistory

对应后端控制器:

  • backend/src/main/java/com/gamemarket/controller/TradeController.java
  • backend/src/main/java/com/gamemarket/controller/MarketController.java

2) 下单时做了什么:预占(资金/库存)+ 进入撮合

入口:backend/src/main/java/com/gamemarket/service/OrderService.java#createOrder

BUY 限价单(买单)

  1. 计算总价:total = price * amount
  2. 调用 WalletService.reserveFunds(playerId, total) 把可用余额转入 reserved(预占)
  3. 落库 MarketOrderstatus=CREATED -> OPEN,并记录 reservedFunds=price*amount
  4. 提交到撮合引擎:MatchingEngineService.submit(order)

SELL 限价单(卖单)

  1. 校验玩家库存 PlayerAsset 是否足够:available = quantity - reservedQuantity
  2. 增加 reservedQuantity(预占库存,避免重复出售)
  3. 落库 MarketOrderstatus=CREATED -> OPEN
  4. 提交到撮合引擎:MatchingEngineService.submit(order)

此外会触发一次“全端刷新”事件:RealtimeEventService.broadcastRefresh()


2.5) 通俗版实现流程(不看代码也能懂)

你可以把交易系统想象成“菜市场撮合 + 收银台结算”,分两步走:

第一步:先排队(下单/挂单)

  1. 你下单时,系统先把你的资源“锁住”:
  • 买单:先冻结余额(wallet.reserved 增加)
  • 卖单:先锁定库存(player_asset.reserved_quantity 增加)
  1. 然后把订单写进数据库(保证可追溯、可恢复)。
  2. 再把订单放进这件商品对应的“订单簿队列”(内存 OrderBook),等待撮合。

第二步:撮合成交后再结算(改钱/改库存/写成交记录)

  1. 撮合引擎只做一件事:在订单簿里按“价格优先、时间优先”找能成交的对手单,算出一批 TradeExecution
  2. 结算模块收到这批成交后,在一个数据库事务里同时完成:
  • 买家扣冻结并扣余额、卖家加余额(钱包变更)
  • 卖家扣库存、买家加库存(库存变更)
  • 写入 trade_history(成交流水)
  • 更新订单 filled_quantity 与状态(OPEN/MATCHING/PARTIALLY_FILLED/FILLED)
  1. 事务提交后,广播一次 SSE refresh,前端收到就重新拉盘口/余额/成交列表。

这样做的直觉好处:

  • :撮合在内存队列里完成,不用每次都扫数据库。
  • :真正“改钱/改库存”只发生在结算事务里,要么全成功,要么全回滚。
  • 不乱:预占字段把资源锁住,避免一份钱/一份库存被重复使用。

3) 撮合引擎怎么工作:每个资产一个内存订单簿 + 价格/时间优先

撮合入口:backend/src/main/java/com/gamemarket/matching/MatchingEngineService.java

  • 维度:booksByAssetId每个 assetId 一个 OrderBook
  • 并发:OrderBook 内部用 每资产一把锁ReentrantLock)保证“撮合 + 入簿”原子性。
  • 排序规则(价格优先,时间优先):
    • Bid(买盘):价格 DESC,时间 ASC
    • Ask(卖盘):价格 ASC,时间 ASC
  • 自成交保护:如果 maker 和 taker 是同一玩家,会跳过该挂单继续找下一条(不会直接取消 taker)。
  • 定价规则:maker-taker 定价,成交价使用 maker 订单价:tradePrice = maker.price

核心逻辑在:backend/src/main/java/com/gamemarket/matching/OrderBook.java#matchAndAdd

撮合的输出不会直接改数据库,而是生成 TradeExecutedEvent,并通过 publishAfterCommit 在事务提交后再发布事件,避免“订单没写入 DB,但撮合/结算先跑了”的竞态。

系统重启后的正确性:撮合引擎在启动时会从数据库恢复活跃订单到内存订单簿:

  • MatchingEngineService#recoverActiveOrders 会加载 OPEN/MATCHING/PARTIALLY_FILLED 的订单并 loadRestingOrder

4) 成交结算怎么落库:异步事件 + 单事务结算(钱包/库存/成交记录/订单状态)

结算处理器:backend/src/main/java/com/gamemarket/matching/TradeExecutionHandler.java

  • 触发方式:监听 TradeExecutedEvent@EventListener
  • 执行模型:@Async 异步执行,但方法本身是 @Transactional,确保这批 execution 的结算在一个事务中完成
  • 失败策略:任何异常都会 setRollbackOnly(),保证钱包/库存/成交记录不会“只写一半”

结算做的事情(按执行顺序):

  1. 处理被撮合引擎判定取消的订单(本实现预留了 cancelledOrderIds 的处理路径)
  2. 处理 executions(逐笔)
    • 钱包转账:
      • 买家:WalletService.commitReserved(buyer, total)(reserved↓ + balance↓)
      • 卖家:WalletService.addFunds(seller, total)(balance↑)
    • 买单的 MarketOrder.reservedFunds 也会随成交递减,用于支持“价格改善”后把剩余预占释放
    • 库存转移:
      • 卖家:reservedQuantity↓quantity↓
      • 买家:若无该物品则创建 PlayerAsset,并 quantity↑
    • 写入成交记录:TradeHistory(asset、price、quantity、tradeTime、buyOrderId、sellOrderId)
    • 生成通知:写入 Notification(best-effort,不影响主交易事务)
  3. 更新订单状态与 filledQuantity
    • OPEN → MATCHING(第一次成交时)
    • filledQuantity 累加后:
      • 未满:PARTIALLY_FILLED
      • 满额:FILLED,并释放 BUY 订单剩余 reservedFunds(价格改善/未用完预占)
  4. 结算事务完成后,会广播 refreshRealtimeEventService.broadcastRefresh()

这部分是交易一致性的核心:把“资金/库存/成交记录/订单状态”集中在同一个事务里更新。


5) Taker 吃单是怎么实现的:把“点击成交”翻译成一张对手订单

入口:backend/src/main/java/com/gamemarket/service/OrderService.java#executeTrade

前端点击盘口“买入/卖出”时,会把 maker 的 orderId、自己的 userId、成交数量 quantity 提交给:

  • POST /api/market/tradeMarketController#executeTradeOrderService#executeTrade

后端会:

  1. 对 maker 订单加行级锁(findByIdForUpdate),校验状态仍可成交(OPEN/MATCHING/PARTIALLY_FILLED)
  2. 校验不能与自己成交、数量合法
  3. 根据 maker 订单方向推导 taker 方向:maker=SELL → taker=BUY;maker=BUY → taker=SELL
  4. 先对 taker 做资源预占(资金 or 库存),然后创建一张 taker MarketOrder(OPEN)
  5. 将 taker 订单提交给撮合引擎:matchingEngineService.submit(taker)

这样做的好处是:不需要写两套撮合逻辑,吃单/挂单都走同一套 OrderBook 撮合与 TradeExecutionHandler 结算。


6) 撤单为什么要“锁”:先从内存订单簿移除,再落库释放资源

撤单入口:backend/src/main/java/com/gamemarket/service/OrderService.java#beginCancel / cancelOrder / abortCancel

  • beginCancel
    • 先对订单加行级锁
    • 先从内存订单簿移除matchingEngineService.removeFromBook(order))避免继续被撮合
    • 状态置为 CANCEL_PENDING
  • cancelOrder
    • 释放剩余预占:BUY 释放 Wallet.reserved;SELL 释放 PlayerAsset.reservedQuantity
    • 状态置为 CANCELLED,并写一条 Notification
  • abortCancel
    • 把订单从 CANCEL_PENDING 拉回 OPEN
    • 重新 submit 回订单簿

7) 实时刷新怎么做:SSE 广播“refresh”,前端统一重新拉数据

后端使用 Server-Sent Events(SSE):

  • 订阅接口:GET /api/realtime/streambackend/src/main/java/com/gamemarket/controller/RealtimeController.java
  • 广播实现:backend/src/main/java/com/gamemarket/realtime/RealtimeEventService.java
    • broadcastRefresh() 会发出名为 refresh 的 SSE 事件

前端订阅在:src/utils/realtime.js,收到 refresh 后会派发 window 事件(realtime:refresh),各页面按需刷新:盘口、余额、成交历史等。


8) 关键数据表/字段(与实现相关)

  • market_orderMarketOrder):
    • order_type(BUY/SELL)
    • pricequantityfilled_quantity
    • status(CREATED/OPEN/MATCHING/PARTIALLY_FILLED/FILLED/CANCEL_PENDING/CANCELLED)
    • reserved_funds(买单预占资金的“剩余可释放值”)
  • walletbalance + reserved(预占资金不允许被重复下单使用)
  • player_assetquantity + reserved_quantity(预占库存不允许被重复挂卖)
  • trade_history:成交明细(用于 K 线/成交列表/涨跌幅等统计)

9) 代码导航(快速定位)

  • 下单/撤单/吃单入口:
    • backend/src/main/java/com/gamemarket/service/OrderService.java
    • backend/src/main/java/com/gamemarket/controller/TradeController.java
    • backend/src/main/java/com/gamemarket/controller/MarketController.java
  • 撮合:
    • backend/src/main/java/com/gamemarket/matching/MatchingEngineService.java
    • backend/src/main/java/com/gamemarket/matching/OrderBook.java
  • 结算:
    • backend/src/main/java/com/gamemarket/matching/TradeExecutionHandler.java
  • 实时推送:
    • backend/src/main/java/com/gamemarket/realtime/RealtimeEventService.java
    • src/utils/realtime.js

👤 用户中心 (User Profile)

  • 资产背包: 可视化管理持有的游戏饰品,支持分页浏览、查看稀有度、参考价和持仓成本。
  • 钱包系统: 支持模拟充值、提现,并提供详细的资金流水记录。
  • 订单管理: 实时查看当前挂单状态,支持随时撤单。
  • 个人设置: 支持修改昵称、上传头像等个性化设置。

🛠 技术栈 (Tech Stack)

  • Frontend:
    • Vue 3 (Composition API)
    • Vite (Build Tool)
    • Vue Router (Routing)
    • Pinia (State Management)
    • ECharts (Data Visualization)
    • CSS3 Animations & Canvas
  • Backend:
    • Java 21
    • Spring Boot 3.3.5
    • Spring Data JPA / Hibernate
    • Lombok
  • Database:
    • PostgreSQL 16
  • DevOps:
    • Docker & Docker Compose
    • Maven
    • NPM

🏁 快速开始 (Quick Start)

1. 环境准备

确保本地已安装:

  • Node.js 18+
  • Java 21 (JDK)
  • Maven 3.8+
  • Docker (可选,用于运行数据库)

2. 启动数据库

项目根目录下运行:

docker start market-postgres
# 或者使用 docker-compose up -d

3. 启动后端

cd backend
mvn spring-boot:run

后端服务将运行在 http://localhost:8080

4. 启动前端

npm install
npm run dev

前端服务将运行在 http://localhost:5173

📂 项目结构

├── backend/            # Spring Boot 后端源码
│   ├── src/main/java   # Java 业务逻辑
│   └── uploads/        # 用户上传文件存储目录
├── database/           # SQL 初始化脚本
├── src/                # Vue 3 前端源码
│   ├── api/            # Axios 请求封装
│   ├── assets/         # 静态资源 & 全局样式
│   ├── components/     # 公共组件 (ParticleBackground, NavBar等)
│   ├── router/         # 路由配置
│   ├── store/          # Pinia 状态管理
│   └── views/          # 页面视图 (Market, Inventory, Profile等)
└── vite.config.js      # Vite 配置 (含代理设置)

📝 开发日志

  • 2025-11-29:
    • 实现了基于 Canvas 的动态粒子背景。
    • 修复了库存页面的分页显示问题。
    • 完善了头像上传功能的本地存储与静态资源映射。
    • 优化了市场筛选器的交互逻辑与加载状态。

Project maintained by SwissRo1l

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors