互联酒旅项目实践记录

330 阅读8分钟

依托微信小程序和App 客户端提供线上预定酒店和旅游产品的互联网产品。

  • 解决用户痛点1:提高了用户搜索酒店和预定酒店的效率
  • 解决用户痛点2:售后功能保障了用户的合法权益
  • 解决用户痛点3:基于数据分析提供给用户多需求场景的组合产品

产品文档

产品文档这一块,主要是体验目前已有的产品,对其功能和界面进行熟悉,梳理我们的项目需要实现的功能点和使用的技术点。

打开首页

搜索框

默认搜索附近的酒店,点击搜索框后进入搜索详情页面。

搜索详情页

64787564109

搜索框

在搜索框输入酒店或机票,会给出相关的推荐,点击下方推荐或者搜索按钮直达搜索结果。

64787605388

搜索历史

点击搜索按钮后,当前搜索框内的内容会被记录为一条搜索历史记录。

搜索推荐

附近热门搜索

推荐菜单栏

64787615883

包含有:

  • 猜你想搜:根据用户搜索历史做推荐

  • 附近酒店:按距离和相关规则推荐附近酒店

  • **(地名)酒店榜:应该是根据好评和下单量推荐当地的酒店

  • 低价机票:从当地出发目的地随机,选取价格最低的一组单程机票

  • 全网热搜:全网搜索量较高的关键词

头部菜单栏选项

火车票

64787627929

给出火车票搜索的表单,包含出发地,目的地,时间,火车分类,车票分类(是否为学生票)。

下方还有火车票相关的其他服务,包括抢票、优惠、出行权益、火车票账号】船票拼车、订酒店、低价打车、旅游等。

64791222083

机票

同上。但会复杂一些。

64791227910

酒店

打开小程序后默认的显示菜单。将订酒店分为4类:国内普通酒店、钟点房、民宿公寓、海外。可通过“我的附近”定位当前位置,通过关键字、价格、星级,综合进行酒店查询。

64791232685

国内

点击我的附近,可定位当前位置。或者自己输入城市。

点击日期,可选择入住时间起止。

下方是关键字/酒店/地址/价格星级,可对搜索结果进行模糊查询。

点击搜索,进入酒店列表展示页面。

顶端是城市、入住时间、上述模糊查询的关键字。

64792600949

接下来是过滤菜单。

  • 筛选:按特征过滤。

    64792628350

  • 附近:按位置过滤。

    64792626961

  • 价格星级:字面意思

    64792624157

  • 智能排序:对搜索结果进行排序

    64792618930

点击某一个酒店进入酒店详情页。

顶端展示酒店的照片,名称,评分,评价,地理位置。

接下来是入住时间选择和房型选择

64792634546

钟点房

钟点房界面类似。在下方还提供额外的分类查询服务。

64791248805

汽车票

汽车票菜单下包含有汽车票、船票、跨城拼车服务等。其中跨城出行点开后跳转到拼车服务小程序。

64791257131

打车

64791265898

默认是常规打车:输入当前位置(默认进入后会自动定位最佳上车点)以及目的地,点击用车查询搜索当前附近的车辆。

门票

64791283670

广告banner

底部菜单栏选项

只在首页配合父菜单栏选项显示。

首页

特价

调用同程特价的页面展示。

里程商城

展示可通过里程兑换商品的页面。

订单

所有酒店、机票、火车票的订单显示在这里。

64792715196

我的

个人中心页面。

64792718934

数据库设计

通过产品文档的梳理,我们对功能有一个大致的了解。随后,根据功能设计数据库。这一部分的主要依据是阿里的编程规范。目前最新的是黄山版。我们队做的是和房型相关的部分,因此这部分整理的接口如下:

hotel-4.0-room1[酒店房间]-202246205944

在设计过程中,主要学习到以下知识点:

  • 命名规范

    • 命名以下划线分割,都用小写单词,不用缩写,要见名知意
    • 表不要加多余前缀。比如酒店房间表,因为本项目中关于房间只可能是一种,因此不应该命名为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个字节。
      

      image-20220503191112739

    • 中间表设计

      • 为了解耦合,在设计的时候应该添加中间表。比如房间表和预定时间
      • 如果某一张表存储的信息不多,则可以不要中间表

接口设计

规范:

  • 后台接口以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使用技巧:

环境变量

使用环境变量可以统一定义测试环境地址,不需要一个个输入

新建环境:

image-20220503225631195

设置环境url前缀:

image-20220503225736939

全局参数

可以设定当前项目/环境/目录下所有接口的统一参数。比如token,每个接口都需要设置,因此最好统一管理。

全局参数:当前项目下有效

image-20220503230012139

目录参数:仅对当前目录有效

image-20220503230153067

image-20220503230219463

预执行脚本/后执行脚本

token通常是登录后才能获得。如果每次都要手动登录再把token复制到全局变量肯定是比较麻烦的。一种优雅的方式是使用后执行脚本。编辑一个登录接口,该接口设置后执行脚本,登录成功后自动设置一个token环境变量。

image-20220503230556085

apt.variables.set("access_token", response.json.access_token);这句代码表示设置一个全局环境变量"access_token",其值为response中的access_token