中软杯国内航班查询与机票预定系统2

251 阅读7分钟

日期:2022年6月15日

正文


完成基本功能要求——对给定日期、航班机票的预订及预订后的机票改期退票功能;应当具备基本的座位库存控制能力,能正确处理多人并发预订机票时的库存扣减事务。

第二大块的基本功能就相较于第一块的简单查询来说相对复杂了一些

简单分析一下需求:

1.机票预订,需要用户预定的该机票类型数量减少,同时将该机票的基本信息添加到用户的订单中

2.退票:机票数量增加,用户订单删除(delete)

3.改票:机票数量不变,用户订单更新(update)

4.座位库存:最简单的方案就是保持与机票数量一致

映入眼帘的就是数据库设计

机票表的字段设计,根据基本的要求来说以下字段是必不可少的

在此基础上我又添加了机票价格,奖励里程,备注三个字段,下图是完整设计:

java接口设计

明显的我们可以看出来这块功能是至少两个服务才能才能实现的,这就用到的服务与服务之间的Feign远程调用

所以order订单接口和ticket机票接口分别放到不同的两个服务中去

具体实现步骤:

由于之前写过,相同的步骤我就不在这里一一追述的,具体可以结合我之前的博客中软杯国内航班查询与机票预定系统1,这里我就重点讲述不同的地方。

1.首先是对ticket机票接口的完善

1.1修改查询所有的findAll接口

这个就是可以实现通过前端传过来的flight_id(航班编号)来查询到所有的该航班的机票信息

    /**
     * 查询所有数据
     * @param ticket 查询实体
     * @return 所有数据
     */
    @GetMapping("findAll")
    public R selectAll(Ticket ticket) {

        List<Ticket> list = ticketService.select(ticket);

        return success(list);

    }

1.2添加机票数量递减的接口

通过前端传过来的ticket_id来对对应机票数量上的减少,同时也是座位数的减少

reduce是我自定义的接口方法

    //每调用一次机票数就随之递减
    @GetMapping("reduce/{id}")
    public R reduce(@PathVariable Integer id) {

        return success(this.ticketService.reduce(id));
    }

1.3编写reduce接口以及该实现类

接口:

/**
 * 机票信息(Ticket)表服务接口
 *
 * @author makejava
 * @since 2022-06-13 08:57:42
 */
public interface TicketService extends IService<Ticket> {


    List<Ticket> select(Ticket ticket);

    
    Object reduce(Integer id);
}

实现类:

这里重要的逻辑代码中mybatis-plus中的条件构造器是重点,之前没有学过setSql自定义sql语句,在对原字段内容上的修改困惑了我很长的时间

wrapper.eq("id", id).setSql("ticket_amount=ticket_amount-1");

package com.domestic.flightservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.domestic.flightservice.dao.TicketDao;
import com.domestic.flightservice.entity.Plane;
import com.domestic.flightservice.entity.Ticket;
import com.domestic.flightservice.service.TicketService;
import com.domestic.servicebase.exceptionhandler.FlightException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * 机票信息(Ticket)表服务实现类
 *
 * @author makejava
 * @since 2022-06-13 08:57:42
 */
@Service("ticketService")
public class TicketServiceImpl extends ServiceImpl<TicketDao, Ticket> implements TicketService {

    @Override
    public List<Ticket> select(Ticket ticket) {

        Integer flightId = ticket.getFlightId();
        String cabinType = ticket.getCabinType();


        QueryWrapper<Ticket> wrapper = new QueryWrapper<>();
        wrapper.eq("flight_id", flightId);

        if (StringUtils.isEmpty(flightId)){
            throw new FlightException(-1,"请给我传所需要查询的航班好,我才能查询该航班的机票信息!");
        }

        if (StringUtils.isEmpty(cabinType)) {   //如果前端没有给cabinType传值,默认显示经济舱

            //前端传航班号+仓位类型   默认先显示经济舱
            wrapper.eq("cabin_type", "经济舱");
        }else {
            wrapper.eq("cabin_type",cabinType);
        }
        List<Ticket> list = baseMapper.selectList(wrapper);
        return list;
    }

    @Override
    public Object reduce(Integer id) {

        Ticket ticket = new Ticket();

        Ticket ticket1 = baseMapper.selectById(id);
        Integer ticketAmount = ticket1.getTicketAmount();

        UpdateWrapper<Ticket> wrapper = new UpdateWrapper<>();
        if (ticketAmount>0) {
            wrapper.eq("id", id).setSql("ticket_amount=ticket_amount-1");
        }else
        {

            throw new FlightException(-1,"机票已售光!");
        }

        int update= baseMapper.update(ticket,wrapper);
        return update;

    }
}

2.接下来是order订单相关接口编写

2.1首先是最基础的功能——查询此账号上的订单信息

通过前端传过来的user_id来查询数据库,这个时候我已经能得心应手的写出自定义方法接口以及实现类了,代码这东西还是得带着脑子的敲才管用。

    /**
     * 查询指定用户的所有订单
     *
     * @return 所有数据
     */
    @PostMapping("selectByUserId")
    public R selectAll(@RequestBody Serializable uid) {

      List<Oder> list = oderService.findall(uid);
        return success(list);
    }

2.2然后是买票的新增订单(insert),退票的删除订单(delete)以及改签的修改订单(update)

/**
     * 新增数据
     *
     * @param oder 实体对象
     * @return 新增结果
     */
    @PostMapping("insert")
    public R insert(@RequestBody Oder oder) {

        Integer ticketId = oder.getTicketId();
        R r = planeClient.selectOne(ticketId);


        int insert = oderService.insert(oder);

        return success(insert);
//        return success(this.oderService.save(oder));
    }
    
    /**
     * 改票
     * @param oder 实体对象
     * @return 修改结果
     */
    @PutMapping("update")
    public R update(@RequestBody Oder oder) {
        return success(this.oderService.updateById(oder));
    }

    /**
     * 退票
     *
     * @param idList 主键结合
     * @return 删除结果
     */

    @DeleteMapping("delete")
    public R delete(@RequestParam("idList") List<Long> idList) {
        return success(this.oderService.removeByIds(idList));
    }`

2.3创建对应接口以及实现类

这里的逻辑很简单就不再多余赘述,就是写基本的判断

注意:这里的“oder.setInsuranceYw(0);”优先级是大于前端传值的,是全局set,这里之所以有这么多注释就是这个原因

package com.domestic.oderservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.domestic.oderservice.dao.OderDao;
import com.domestic.oderservice.entity.Oder;
import com.domestic.oderservice.service.OderService;
import com.domestic.servicebase.exceptionhandler.FlightException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.util.List;

/**
 * 订单信息(Oder)表服务实现类
 *
 * @author makejava
 * @since 2022-05-24 19:46:50
 */
@Service("oderService")
public class OderServiceImpl extends ServiceImpl<OderDao, Oder> implements OderService {


    @Override
    public List<Oder> findall(Serializable uid) {
        QueryWrapper<Oder> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id",uid);

        return baseMapper.selectList(wrapper);
    }

    @Override
    public int insert(Oder oder) {

        String phoneNumber = oder.getPhoneNumber();
        String contacts = oder.getContacts();
//        oder.setInsuranceGl(0);    //隔离津贴 0是未勾选,1是勾选,默认设置未勾选
//        oder.setInsuranceTg(0);    //退改保险
//        oder.setInsuranceYw(0);    //延误保险


        if (StringUtils.isEmpty(phoneNumber)){
            throw new FlightException(-1,"请输入联系人电话!");
        }
        if (StringUtils.isEmpty(contacts)){
            throw new FlightException(-1,"请输入联系人姓名!");
        }


/*        if (StringUtils.isEmpty(oder.getInsuranceGl())){
            oder.setInsuranceGl(0);    //隔离津贴 0是未勾选,1是勾选,默认设置未勾选
        }else
        {
            oder.setInsuranceGl(1);
        }
        if (StringUtils.isEmpty(oder.getInsuranceTg())){
            oder.setInsuranceTg(0);    //0是未勾选,1是勾选,默认设置未勾选
        }else {
            oder.setInsuranceTg(1);
        }
        if (StringUtils.isEmpty(oder.getInsuranceYw())){
            oder.setInsuranceYw(0);    //0是未勾选,1是勾选,默认设置未勾选
        }else {
            oder.setInsuranceYw(1);
        }*/
        if (!StringUtils.isEmpty(oder.getInsuranceGl())) {
            oder.setInsuranceGl(1);
        }
        if (!StringUtils.isEmpty(oder.getInsuranceTg())){
            oder.setInsuranceTg(1);
        }
        if (!StringUtils.isEmpty(oder.getInsuranceYw())){
            oder.setInsuranceYw(1);
        }

/*        if (!StringUtils.isEmpty(oder.getGetSeat())){
            oder.setGetSeat(oder.getGetSeat()+1);
        }
       if (StringUtils.isEmpty(oder.getFirstBarding())){
            oder.setFirstBarding(0);
        }
        if (StringUtils.isEmpty(oder.getEatWaiting())){
            oder.setEatWaiting(0);
        }*/
        return (baseMapper.insert(oder));
    }

}

3.今天的重点,也是我最想讲的内容——Feign远程调用

如何实现前端调用购买机票的接口之后,机票就减少一张呢,同时还生成一个订单,换句话说怎么让前端同学同时调用两个不相干的接口呢?这就用到了Feign,Feign调用API就像调用本地方法一样方便。

3.1要使用Feign远程调用前提是需要你配置的服务都注册到了注册中心上

如下图,pom.xml文件中的依赖,application.properties中注册中心的配置,服务启动类的注解,三者缺一不可

3.2然后开始在调用端order订单服务中Feign的接口

重点:1. 在请求注解后面加的地址一定是远程调用接口的完整地址

2.在@PathVariable注解后面必须添加与之对应的字符,比如@GetMapping("/systemservice/plane/{id}")与@PathVariable("id") Serializable id)

package com.domestic.oderservice.client;

import com.baomidou.mybatisplus.extension.api.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import sun.security.krb5.internal.Ticket;

import java.io.Serializable;

@Component
@FeignClient(name = "service-flight"/*,fallback = PlaneFileDegradeFeighClient.class*/)
//@FeignClient("service-flight")
public interface PlaneClient {

    //在查询订单的时候同时查询该订单的飞机信息  远程调用
    @GetMapping("/systemservice/plane/{id}")
    public R selectOne(@PathVariable("id") Serializable id);


    //远程调用机票信息,填充ticket_idcabin_type,cabinname
    @GetMapping("/systemservice/ticket/{id}")
    public R selectOne(@PathVariable("id") Integer id);

    //远程调用机票递减的接口
    @GetMapping("reduce/{id}")
    public R reduce(@PathVariable("id") Integer id);
}

3.3在调用端的controller中注入Feign远程调用的接口,修改调用端的controller,根据业务逻辑实现远程调用

    //注入Feign远程调用的接口PlaneClient
    @Resource
    private PlaneClient planeClient;

4.最后测试Feign远程调用的实现结果

同时启动order服务与Flight服务

order:

Flight:

问题

看到现在可能有人会发现我的order服务的order是拼错的这是因为

总结


其实前端也可以为同一个按键绑定多个接口,但是这样就多了一步重新传输值,接受值的过程,Feign远程调用节约了此过程,今天的重点就是Feign远程调用的使用,难点其实是最初的数据库设计以及java接口实现的选择,由于我现在学的知识有限,在解决方案以及设计上并不能直接就是最优的方案,我的数据库就会不断地随着我的方案进行修改,然后我的代码也会随之修改,前端也要修改,这就导致进度会发生滞后。