依托微信小程序和App 客户端提供线上预定酒店和旅游产品的互联网产品。
- 解决用户痛点1:提高了用户搜索酒店和预定酒店的效率
- 解决用户痛点2:售后功能保障了用户的合法权益
- 解决用户痛点3:基于数据分析提供给用户多需求场景的组合产品
产品文档
产品文档这一块,主要是体验目前已有的产品,对其功能和界面进行熟悉,梳理我们的项目需要实现的功能点和使用的技术点。
打开首页
搜索框
默认搜索附近的酒店,点击搜索框后进入搜索详情页面。
搜索详情页
搜索框
在搜索框输入酒店或机票,会给出相关的推荐,点击下方推荐或者搜索按钮直达搜索结果。
搜索历史
点击搜索按钮后,当前搜索框内的内容会被记录为一条搜索历史记录。
搜索推荐
附近热门搜索
推荐菜单栏
包含有:
-
猜你想搜:根据用户搜索历史做推荐
-
附近酒店:按距离和相关规则推荐附近酒店
-
**(地名)酒店榜:应该是根据好评和下单量推荐当地的酒店
-
低价机票:从当地出发目的地随机,选取价格最低的一组单程机票
-
全网热搜:全网搜索量较高的关键词
头部菜单栏选项
火车票
给出火车票搜索的表单,包含出发地,目的地,时间,火车分类,车票分类(是否为学生票)。
下方还有火车票相关的其他服务,包括抢票、优惠、出行权益、火车票账号】船票拼车、订酒店、低价打车、旅游等。
机票
同上。但会复杂一些。
酒店
打开小程序后默认的显示菜单。将订酒店分为4类:国内普通酒店、钟点房、民宿公寓、海外。可通过“我的附近”定位当前位置,通过关键字、价格、星级,综合进行酒店查询。
国内
点击我的附近,可定位当前位置。或者自己输入城市。
点击日期,可选择入住时间起止。
下方是关键字/酒店/地址/价格星级,可对搜索结果进行模糊查询。
点击搜索,进入酒店列表展示页面。
顶端是城市、入住时间、上述模糊查询的关键字。
接下来是过滤菜单。
-
筛选:按特征过滤。
-
附近:按位置过滤。
-
价格星级:字面意思
-
智能排序:对搜索结果进行排序
点击某一个酒店进入酒店详情页。
顶端展示酒店的照片,名称,评分,评价,地理位置。
接下来是入住时间选择和房型选择
钟点房
钟点房界面类似。在下方还提供额外的分类查询服务。
汽车票
汽车票菜单下包含有汽车票、船票、跨城拼车服务等。其中跨城出行点开后跳转到拼车服务小程序。
打车
默认是常规打车:输入当前位置(默认进入后会自动定位最佳上车点)以及目的地,点击用车查询搜索当前附近的车辆。
门票
广告banner
底部菜单栏选项
只在首页配合父菜单栏选项显示。
首页
特价
调用同程特价的页面展示。
里程商城
展示可通过里程兑换商品的页面。
订单
所有酒店、机票、火车票的订单显示在这里。
我的
个人中心页面。
数据库设计
通过产品文档的梳理,我们对功能有一个大致的了解。随后,根据功能设计数据库。这一部分的主要依据是阿里的编程规范。目前最新的是黄山版。我们队做的是和房型相关的部分,因此这部分整理的接口如下:
在设计过程中,主要学习到以下知识点:
-
命名规范
- 命名以下划线分割,都用小写单词,不用缩写,要见名知意
- 表不要加多余前缀。比如酒店房间表,因为本项目中关于房间只可能是一种,因此不应该命名为hotel_room,而是直接命名room
-
约束规范
- 字段如果不为空,都应该给上默认值。比如数字是0,字符串是""等。空值在程序处理中会有很多潜在的问题。建议所有字段都这么处理。
- create_time和update_time是每个表都必须要有的,他们可以直接设置默认值(即当前时间)
- create_time:CURRENT_TIMESTAMP
- update_time:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- 数据都是逻辑删除,通过deleted字段说明是否删除
- 数据库字符集使用utf8mb4,可支持emoji表情的存储
-
其他
-
索引设计
-
索引字段长度限制:不超过191字符
UTF-8编码的字符可以是1-4个字节,但是在MySQL中最大只能存储3个字节。 在版本5.5开始引入innodb_large_prefix,其默认值为off,索引的前缀最大限制为767个字节;若值为on时(版本5.7.7开始作为默认值),最大限制为3072个字节。 -
中间表设计
- 为了解耦合,在设计的时候应该添加中间表。比如房间表和预定时间
- 如果某一张表存储的信息不多,则可以不要中间表
-
接口设计
规范:
- 后台接口以admin开头,用以区分和前台交互的接口
- RESTFUL风格不是必须的
- 路径应体现层级关系,比如获取酒店某个房型的房间列表,层级是 酒店->房型->房间,路径可以写成
/hotel/room-style/{id}/get-room-list - 多个单词拼接应使用
-(中划线)符号 - 响应体标准结构:code、message、data
我设计的接口文档:
console-docs.apipost.cn/preview/46d…
接口代码编写
规范:
- 标准的类注释和方法注释
- MVC层次规范:
- controller:参数校验,服务调用
- service:逻辑处理
- dao:数据库交互
MybatisPlus
-
大部分功能在IService接口中都已实现,自定义service接口只需要继承它即可
-
ServiceImpl中默认实现了部分service中没实现的抽象方法
-
因此自定义service标准结构如下
public interface IRoomService extends IService<Room> { /** * 查询房间信息(包含预定起始和结束日期) * @param roomStyleId 房型id * @return 包含预定信息的房间列表 */ List<RoomVO> getRoomVOList(Long roomStyleId); /** * 更新房间基本信息和预定信息 * @param room 房间基本信息 */ void update(Room room); }@Service public class RoomServiceImpl extends ServiceImpl<RoomDAO, Room> implements IRoomService { @Autowired private IRoomReservedDateService roomReservedDateService; /** * 根据房型id获取房间信息 * @param roomStyleId 房型id * @return */ @Override public List<RoomVO> getRoomVOList(Long roomStyleId){ ... } /** * 更新房间号 * @param room 房间基本信息 */ @Override public void update(Room room) { ... } } -
使用lambda表达式拼接查询条件,使用
方法引用表示数据库列名而不是直接使用字符串。@Override @Transactional(rollbackFor = Exception.class) public void update(HDRoom room) { boolean updated = lambdaUpdate() .eq(HDRoom::getId, room.getId()) .update(room); if(!updated){ Asserts.fail("更新房间基本信息错误"); } } -
尽量不要使用多表联查,因为可能不走索引。可替代的方案是调用多个服务,取得数据后在外部进行拼接。
@Autowired private IRoomReservedDateService roomReservedDateService; /** * 根据房型id获取房间信息 * @param roomStyleId 房型id * @return */ @Override public List<RoomVO> getRoomVOList(Long roomStyleId){ if(roomStyleId < 1){ Asserts.fail("房型id不合法,请传入大于等于1的值"); } List<RoomVO> result = new ArrayList<>(); //根据roomStyleId查找房间列表 List<Room> roomList = lambdaQuery().eq(Room::getRoomStyleId, roomStyleId).list(); if(CollUtil.isEmpty(roomList)){ Asserts.fail("没有找到styleId为" + roomStyleId + "的房间"); } //查询所有结束于当前日期之后的预定时间 List<RoomReservedDate> roomReservedDateList = roomReservedDateService.lambdaQuery() .select(RoomReservedDate::getRoomId, RoomReservedDate::getStartDate, RoomReservedDate::getEndDate) .ge(RoomReservedDate::getEndDate, new Date()) .list(); //对每个房间,找到其预定时间,生成RoomVO添加进result roomList.forEach(room -> { RoomVO roomVO = new RoomVO().copy(room); roomReservedDateList.stream().filter(rrd -> ObjectUtil.equal(rrd.getRoomId(), room.getId())) .forEach(rrd -> { roomVO.setStartDate(rrd.getStartDate()); roomVO.setEndDate(rrd.getEndDate()); result.add(roomVO); }); }); return result; }
如果非要多表联查,则一定要检查sql执行计划,确保走索引
- 尽量不要写xml文件。mapper自定义方法可以使用注解。
测试
使用apipost写接口文档并进行测试。
apipost使用技巧:
环境变量
使用环境变量可以统一定义测试环境地址,不需要一个个输入
新建环境:
设置环境url前缀:
全局参数
可以设定当前项目/环境/目录下所有接口的统一参数。比如token,每个接口都需要设置,因此最好统一管理。
全局参数:当前项目下有效
目录参数:仅对当前目录有效
预执行脚本/后执行脚本
token通常是登录后才能获得。如果每次都要手动登录再把token复制到全局变量肯定是比较麻烦的。一种优雅的方式是使用后执行脚本。编辑一个登录接口,该接口设置后执行脚本,登录成功后自动设置一个token环境变量。
apt.variables.set("access_token", response.json.access_token);这句代码表示设置一个全局环境变量"access_token",其值为response中的access_token