列车购票系统设计方案


简介

  本项目的题目为设计一个类似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