列車購票系統設計方案


簡介

  本項目的題目為設計一個類似12306的網絡列車購票系統后端。通過上一篇文章的分析,我們已經的出了系統的數據原型和需求。下面我將通過給出分解視圖、依賴視圖、執行視圖、實現視圖、部署視圖和數據庫實現來描述項目的完整設計方案。

分解視圖

​  項目采用微服務架構,所以先對模塊進行水平拆分,然后進行一些垂直才分得到分解視圖。

分解視圖
  • PRC為公共的遠程調用接口包,其中還包含了每一個模塊的遠程調用接口
  • TicketServer為購票服務,負責接受前端的請求然后進行處理
  • ReTicketServer為退票服務
  • StaticSearchServer是靜態搜索功能,負責處理用戶需要獲得的不會實時變化或者變化比較慢的數據
  • UserServer是用戶相關的服務
  • CandidateServer是候補服務,通過輪詢訪問票池是否還有余票,或者是退票
  • DynamicSearchServer動態數據的查詢,負責處理經常變化的數據的查詢,例如余票的查詢
  • PayServer負責支付和退款服務
  • TicketPool票池,用來存儲和計算余票情況

依賴視圖

  模塊直接使用gPRC作為遠程調用協議,從而對模塊進行解耦。

依賴視圖

執行視圖&API數據

  對幾個復雜的執行過程做時序圖。

支付

支付時序
支付流程

流程一:

接受來自另一個服務的請求數據如下:

{
    "username": ,// 發出業務的用戶名
    "money": ,// 付款金額
    "affair_number": //業務號
}

然后支付服務通過http請求獲得支付寶的orderInfo,然后創建訂單,存儲在redis中。然后再返回訂單號和orderInfo,主要是為了讓另一個服務器給用戶傳回支付信息。

返回數據如下:

{
    "code": ,// 返回代碼
    "msg": ,// 返回信息
    "data": {//返回數據
		"affair_number", //業務編號
    	        "orderInfo": , //訂單信息 
      }
}

流程二:

用戶支付完成后,通知支付服務支付完成,通知數據如下:

{
    "username": ,// 發出業務的用戶名
    "token": ,// 驗證信息
    "affair_number": ,//業務號
    "orderInfo": //訂單信息
}

接收到通知后,支付服務器將通過支付寶查詢訂單狀態,然后如果查到已支付完成,將會向相應的服務發出通知然后回傳最終數據給用戶。

退票

退票時序 退票流程

用戶選中需要退的票,然后點擊退票后前端將向服務器發送退票請求,請求數據如下:

{
    "username": ,//用戶名稱
    "token": ,// 驗證信息
    "ticket_outside_id": // 票號
}

退票服務,查詢到這張票對應的訂單,然后計算要退款的金額。調用支付服務進行退款,發送數據如下:

{
    "order_id": ,// 訂單號
    "money": // 退款金額
}

購票

購票時序 購票流程

​ 用戶通過前端點擊購票,前端向服務器發送購票請求,請求數據如下:

{
    "username":, // 用戶名
    "token":,//驗證信息
    "date":, // 發車日期
    "train_number":,//車次
    "start_station":,//上車站
    "end_station":,//下車站
    "passengers":[ // 乘客數據,數組
    	{
    		"passenger_seq":, // 乘客序號,已經存儲在用戶信息中
    		"seat_class":, //座位等級,如一等座,二等座 
    		"seat_tpye": // 座位類型A,B,C等
		}
	]
}

​  服務器接收到請求后將檢查用戶已有的訂單中是否存在沖突例如時間重疊,如果沒有沖突,購票服務將通過票池獲取用戶所需的票,通過票信息生成訂單返回給用戶,用戶根據訂單查看自己是否要購買,因為用戶所選擇的座位編號可能是要變化的。返回的訂單數據如下:

{
    "code":,// 返回代碼
    "msg": ,// 返回信息
    "data":{
    	"order_outer_id":,//訂單外部id
    	"train_number":,//車次號
    	"start_station":,// 上車站
    	"end_station":,//下車站
    	"arrival_time":,//到達下車站時間
    	"duration":, // 中間用時
    	"start_date":,//發車日期
    	"total_money":,//總金額
    	"tickets":[// 乘客的票信息
			{
    			"passenger_name":,//乘車人姓名
    			"passenger_id":,//乘車人身份證號
    			"carriage_number":,//車廂號
    			"seat":,//座位號
    			"money"://票價
			}	
    	]
	}
}

  用戶選擇取消訂單,票將退回票池,如果用戶支付訂單,系統將進入支付服務。用戶支付完成后,返回給用戶與訂單類似的票數據。

實現視圖

12306A/       12306后端A小組

|------rpc       grpc相關的接口和協議文件

|   |------pay     pay服務器的rpc代碼, 同理如果是user服務應該在該文件夾下建立user文件夾

|     |------proto  .proto文件存放

|     |------client  grpc客戶端, grpc服務再server中自己實現

|------server      每個微服務項目

|   |------candidate  候補服務器

|      |------controller  控制層,數據的接受的校驗

|      |------service  服務層,業務邏輯

|      |------model  模型層,與數據庫連接

|      |------redis  緩存連接

|      |------setting  配置服務

|      |------config  配置文件存放

|   |------pay     支付服務器

|   |------reticket   退票服務器

|   |------search   搜索

|     |------dynamic  動態搜索

|     |------static  靜態搜索

|   |------ticket    購票服務器

|   |------user     用戶服務器

|------ticketPool     線程池服務,主要是對內提供服務

部署視圖

部署圖

數據庫

部署圖

​ user表用來存儲用戶的信息,該表是很多信息的基礎,例如候補、訂單和購票都要根據該表的用戶信息進行。user表數據如下表:

字段 類型 內容
id unsigned int 主鍵
created_at timestamp 創建時間
modified_at timestamp 最近一次修改時間
deleted_at timestamp 刪除時間用於軟刪除
created_by varchar(100) 創建人
modified_by varchar(100) 最近一次修改人
deleted_by varchar(100) 刪除人
username varchar(100) 用戶名
password varchar(500) 密碼
state varchar(100) 狀態
token varchar(500) 驗證信息
tokenExpire timestamp 驗證信息過期時間
passenger_01_name varchar(100) 常用聯系人1名稱
passenger_01_id varchar(100) 常用聯系人1身份證號
passenger_02_name varchar(100) 常用聯系人2名稱
passenger_02_id varchar(100) 常用聯系人2身份證號
passenger_03_name varchar(100) 常用聯系人3名稱
passenger_03_id varchar(100) 常用聯系人3身份證號
passenger_04_name varchar(100) 常用聯系人4名稱
passenger_04_id varchar(100) 常用聯系人4身份證號
passenger_05_name varchar(100) 常用聯系人5名稱
passenger_05_id varchar(100) 常用聯系人5身份證號

  后面的每個表中都會含有id,created_at,modifided_at,deleted_at,created_by,modified_by,deleted_by字段表中不在進行描述。

  order表,用來記錄用戶的下單情況,只要用戶創建了訂單不管訂單有沒有支付,或者已完成或者過期都會在該表中進行記錄,表字段和內容如下:

字段 類型 內容
user_id unsigned int 用戶id 外鍵
alipay_order_info varchar(500) 支付寶訂單號
money varchar(100) 支付金額
affair_id varchar(100) 對外訂單號
expire_duration int 過期時間
state int 狀態,0未支付,1已支付,2已過期
order_outer_id varhar(100) 訂單外部id

  station表,用來存儲站點信息,例如:name:北京北,city:北京,spell:bjb,state:0。字段信息如下表:

字段 類型 內容
name varchar(50) 站名
city varchar(50) 所在城市
spell varchar(20) 拼音縮寫
state int 狀態

  train表,用來存儲車次信息。字段內容如下表:

字段 類型 內容
number varchar(50) 車次號
start_station varchar(100) 車次起點站
end_station varchar(100) 車次終點站
train_type unsigned int 列車類型 外鍵
state int 狀態

  stop_info表用來記錄列車停靠站信息,作為station和train的關聯表。

字段 類型 內容
train_id unsigned int 車次id 外鍵
station_id unsigned int 車站id 外鍵
train_number varchar(50) 車次編號
station_name varchar(50) 車站名
arrived_time varchar(50) 到達時間
stay_duration unsigned int 停留時間
stay_num unsigned int 停留序號

  candidate表用於存儲候補信息,當用戶選擇候補操作時就會在該表中生成一個候補信息。

字段 類型 內容
date timestamp 候補時間
train_id unsigned int 候補車次id 外鍵
user_id unsigned int 候補的用戶id 外鍵
passenger_number int 候補人數
passenger_id_tag varchar(20) 候補乘車人的身份證標識,用逗號分隔
state int 狀態,0正在候補,1候補成功,2為候補失敗

​  ticket表用來存儲用戶已經購買的票。

字段 類型 內容
user_id unsigned int 用戶id 外鍵
start_station varchar(100) 等待車站名稱
end_station varchar(100) 下車站名稱
start_time timestamp 發車時間
train_id unsigned int 車次id 外鍵
ticket_outer_id varchar(100) 票外部id
train_num varchar(50) 車次號
passager_name varchar(100) 乘客名
passager_id varchar(100) 乘客身份證號
state int 狀態,0為未發車,1為已經發車,2為以退票

  train_run_info表,記錄的時列車運行時的情況,例如列車當前已經到那個車站了,方用戶查看自己所乘列車的情況,字段如下表。

字段 類型 內容
train_id unsigned int 車次id 外鍵
now_station varchar(100) 當前停靠車站名
state int 狀態,1為在行駛,2為在停靠站

  train_type表用來存儲列車類型。雖然有很車次但是,大部分車其實都是一樣的,有着相同的車箱數,相同的速度,外觀等。我們所以用一個train_type來存儲他們之間的共性,這樣可以減少數據冗余。字段如下表。

字段 類型 內容
type_number varchar(50) 列車類型編號
carriage_list int 車廂id 逗號分隔
carriage_num int 車廂數
max_speed int 最高速度
wifi_state int 0為沒有wifi,1有wifi
fool_carriages varchar(100) 餐車車廂號,用逗號分隔
max_passager int 定員數
length int 列車長度

  carriage_type存儲的是車廂類型。在一輛列車或者不同列車中往往會有相同的車廂,而且存儲車廂信息的最主要目的是記錄車廂有什么類型的作為、每種作為可以坐多少人、每種座位從什么編號開始賣起。比如車廂中有二等座A、B、C、E、F五種,如果一個乘客購買了一張A的票那就出票0A,當前A的記錄加一,下一個乘客購買A座位就出票1A,以此類推。

字段 類型 內容
soft_berth_number int 軟卧數量
hard_berth_number int 硬卧數量
senior_soft_benth_number int 高級軟卧數量
hard_seat_number int 硬座數量
second_seat_number int 二等座數量
first_seat_number int 一等座數量
business_seat_number int 商務座數量
business_seat varchar(200) 商務座的全部編號
first_seat varchar(50) 一等座開始編號,如A、B、E****、F
second_seat varchar(50) 二等座開始編號
hard_seat varchar(50) 硬座開始編號
hard_berth varchar(50) 硬卧開始編號
soft_berth varchar(50) 軟卧開始編號
senior_soft_berth varchar(50) 高級軟卧開始編號

技術選型說明

開發方法:

  在需求分析階段,使用面向對象的方式對系統進行分析,得到項目的數據庫原型。在實現階段采用面向接口的開發方式,使用web框架后小組成員實現網絡接口,並且在不同的模塊直接也采用面向接口編程方式,這樣能夠很好的保持軟件的可維護性和可擴展性。

保證軟件的健壯性

  首先,使用gin網絡框架自帶的validator對所有傳入的數據進行驗證,拒絕非法的數據。
其次,使用JWT技術對前端請求進行驗證。前端進行登錄后,服務器會發送一個帶有時效的token字符串,然后在一些請求中都需要帶有這段token,否則服務器將拒絕請求。若token過期了服務器會要求重新登錄。

保證時延要求

​  這里的時延並不包括網絡時延,主要是請求到達服務器后再返回數據的時間。使用zipkin工具記錄請求進入到服務器后到返回請求數據的時間差。
使用redis保證快速響應請求。redis是一個K-V內存數據庫,由於將數據存儲在內存當中所以速度比普通的數據庫快很多,也因為在內存中所以redis主要還是當緩存使用。
  將余票信息存在在內存中,這樣可以快速計算余票。

開發主要語言:Golang

開發環境:Windows10,MacOS

部署環境:Docker+Ubuntu

開發工具:Goland,VSCode

測試方案:wrk性能測試,配合前端進行黑盒測試


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM