软件系统方案设计
本人的工程实践项目是设计一个类似12306的网上售票系统,本文将分析该项目的同时对软件架构进行初步设计。
项目简介
题目基本要求
- 参考12306站点进行售票系统建模设计,尽可能接近覆盖真实线上系统,实现的功能有但不限于:
- 用户信息注册
- 查询余票: 根据时间,车次,站点区间,座次(一等座,二等座,硬卧,硬座…)查询余票
- 售票: 支持一次购买同一车次的多张车票(多人),支持订单30分钟内锁定,超时释放。支付接口可以mock。
- 退票: 支持一个用户账户下的批量退票
- 改签: 同一用户一张车票只能改签一次
- 所有读写接口延迟要求 <= 500ms
- 单机支持到500qps的并发请求
系统架构
项目整体采用微服务架构,根据功能进行拆分,大致分为以下几个服务模块:
用户服务:用户注册、登录、个人信息维护等功能
查询服务:用于接收并处理用户查询站点、余票等请求
购票服务:用于接收并处理购票请求,考虑到购票请求并发度较高,所以单独作为一个模块
退改服务:用于接收并处理退票、改签相关请求
订单服务:用于接收并处理订单创建、订单查询等相关请求
支付服务:用于处理用户支付、退款等相关请求
票池:为了提高系统性能,设计专门的数据结构管理车票,购票、退票等请求与票池进行交互。
对于每个微服务而言,其内部架构使用了MVC架构。
- Model是指数据模型,是对客观事物的抽象。
- View是指视图,也就是呈现给用户的一个界面,是model的具体表现形式,也是收集用户输入的地方。
- Contorller指控制器,主要负责与model和view打交道。
API接口
注册接口
需要发送的数据格式如下:
{
"name": "",
"password":""
}
返回注册成功与否的结果
{
"code": 10000,
"msg": "success",
"data": null
}
登录接口
登录可以通过绑定用户名+密码,手机号码+密码的形式
而后端系统会根据name字段的正则表达式匹配是否为手机号码,自动识别用户名或者密码登录
{
"name":"",
"password":""
}
返回的结果如下所示
{
"code":10000,
"msg":"success",
"data":{
"username":"",
"token":"",
"phone":"",
"idnum":"",
"isvalid":""
}
}
余票查询接口
余票查询需要输入日期、出发地和目的地三个条件,可选的有G、D等类型的火车,请求数据如下所示:(注:其中type为一个数组,标识动车类型)
{
"date":"",
"departure":"",
"destination":"",
"type":[]
}
返回结果如下所示:
{
"code":10000,
"msg":"success",
"data":[{
"train_no":"",
"departure":"",
"destination":"",
"start_time":"",
"arrive_time":"",
"first_class_seats":"",
"second_class_seats":"",
"soft_berth_seats":"",
"hard_berth_seats":"",
"hard_seats":"",
"business_seats":""
}
]
}
可能有不同类型的车次,为一个列表,示例只给出了一趟车的数据
时刻表接口
请求数据只需要车次号和时间
{
"train_no":"",
"date":""
}
返回数据为时刻表:
{
"code":10000,
"msg":"success",
"data":{
"train_no":"",
"date":"",
"timetable":
[{
"station_name":"",//车站名
"start_time":"",//发时
"arrive_time":"",//到时
"stay_time":"",//停留时间
"on_time":""//是否正晚点
}
]
}
下单接口
{
"token":"",
"train_no":"",
"date":"",
"departure":"",
"destination":"",
"start_time":"",
"arrive_time":"",
"seat_type":""
}
返回数据如下所示:
{
"code":10000,
"msg":"success",
"data":[{
"order_id":"",//订单号
"train_no":"",//车次号
"date":"",//发车日期
"departure":"",//出发地
"destination":"",//目的地
"start_time":"",//发车时间
"arrive_time":"",//到达时间
"payed":"",//是否已支付
"create_time":"",//下单时间
"ticket":{
"seat_type":"",//座位类型
"seat_no":"",//座位号
"price":"",//车票价格
"passenger_name":"",//乘客姓名
"passenger_idnum":"",//乘客身份证号
"ticket_type":""//车票类型,学生票、成人票等
}
]
}
视图模型
分解视图
分解是构建软件架构模型的关键步骤,分解视图也是描述软件架构模型的关键视图,一般分解视图呈现为较为明晰的分解结构(breakdown structure)特点。分解视图用软件模块勾划出系统结构,往往会通过不同抽象层级的软件模块形成层次化的结构。
依赖视图
依赖视图展现了软件模块之间的依赖关系。
执行视图
执行视图展示了系统运行时的时序结构特点,比如流程图、时序图等。执行视图中的每一个执行实体,一般称为组件(Component),都是不同于其他组件的执行实体。执行实体可以最终分解到软件的基本元素和软件的基本结构,因而与软件代码具有比较直接的映射关系。在设计与实现过程中,我们一般将执行视图转换为伪代码之后,再进一步转换为实现代码。
项目中我负责的模块为购票和改签,以这两个模块为例。
购票
改签
实现视图
实现视图是描述软件架构与源文件之间的映射关系。实现视图有助于码农在海量源代码文件中找到具体的某个软件单元的实现。实现视图与软件架构的静态结构之间映射关系越是对应的一致性高,越有利于软件的维护。
由于本项目我们小组只负责后端,前端有另外的小组完成,所以此处只包含了后端的源代码目录文件结构。
项目使用微服务架构,每个服务的RPC目录结构和Server目录结构类似。
部署视图
部署视图是将执行实体和计算机资源建立映射关系。这里的执行实体的粒度要与所部署的计算机资源相匹配,比如以进程作为执行实体那么对应的计算机资源就是主机,这时应该描述进程对应主机所组成的网络拓扑结构,这样可以清晰地呈现进程间的网络通信和部署环境的网络结构特点。
数据库设计
用户表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 用户id |
username | varchar | 用户名 |
password | varchar | 密码 |
name | varchar | 姓名 |
sex | varchar | 性别 |
birthday | datetime | 出生日期 |
telephone | varchar | 手机号 |
varchar | 邮箱 | |
address | varchar | 地址 |
identification_type | varchar | 证件类型 |
identification_number | varchar | 证件号 |
乘车人表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 乘车人id |
name | varchar | 乘车人姓名 |
identification_type | varchar | 证件类型 |
identification_number | varchar | 证件号码 |
passenger_type | varchar | 乘客类型(成人、学生等) |
用户_乘车人表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 用户_乘车人表id |
passengerid | int | 乘车人id |
userid | int | 用户id |
列车表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 列车id |
traintypeid | int | 列车类型id |
trainNumber | int | 列车编号 |
start_stationid | int | 始发站id |
dest_stationid | int | 终点站id |
starttime | datetime | 发车时间 |
arrivetime | datetime | 到达时间 |
列车类型表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 列车类型表id |
trainType | string | 列车类型(动车、火车等) |
车厢表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 车厢id |
carriageType | varchar | 车厢类型 |
seatnumber | int | 座位数量 |
列车_车厢表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 列车_车厢表id |
trainid | int | 列车id |
carriageid | int | 车厢id |
carriagenumber | int | 车厢编号,用于表示该车厢是列车的几号车厢 |
车站表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 车站id |
stationname | varchar | 车站名称 |
列车_车站表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 列车_车站表id |
stationid | int | 车站id |
trainid | int | 列车id |
arrivetime | datetime | 列车到达时间 |
starttime | datetime | 列车发车时间 |
车票表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 车票id |
trainid | int | 该车票对应的列车id |
start_stationid | int | 车票出发站id |
dest_stationid | int | 车票目的站id |
starttime | datetime | 开车时间 |
carriageid | int | 车厢id |
seat | int | 座位号 |
passengerid | int | 乘车人id |
ticketstatus | varchar | 车票状态 |
订单表
字段名 | 字段类型 | 字段描述 |
---|---|---|
id | int | 订单id |
userid | int | 创建该订单的用户id |
ticketid | int | 订单购买的车票id |
amountmoney | float | 订单金额 |
createtime | datetime | 创建时间 |
paytime | datetime | 支付时间 |
orderstatus | varchar | 订单状态(未支付、已支付..) |
运行环境及技术选型
主要技术:
- Gin:Go语言轻量级Web框架
- gRPC:基于Protobuf的跨语言RPC框架,用于不同微服务之间的交互
- Redis:用于存储热点数据,提高系统响应速度
开发、运行环境:
- 开发语言:Golang
- 开发系统:Windows、Mac
- 开发工具:GoLand、VScode
- 数据库:Redis、Mysql
- 部署环境:Ubuntu+Docker