中台研发-发票系统-服务端开发

147 阅读4分钟

需求分析

        在编写发票系统的服务端中,主要需要开发的模块是需要清晰的给展现给用户看到的内容就是前面客户端开发的内容,这个模块涉及到主要的模块就是查询了,而针对查询面对最多的问题可能就是查询的时候多去查询数据库了

  下面就以我在项目中实际遇到的案例给演示如何去有效避免多次查找数据库带来的接口效率低下问题(这些前提是我们在使用mybatis-plus的快捷开发的前提下)。

这些接口主要是针对后台的接口,所以对于效率可能没有那么高的要求,按照个人的实际开发需求去实现,这里我只做一个简单的dem

后台实现

1. 查询数据的详细信息(两表联查)

这里先上我们的一个常规操作,就是当遇到一个查询涉及到两个表(不含多个)的时候我们可能就会先去构造一次,然后构造完成之后会再次去调用数据库,取另外一个表中的数据

   QueryWrapper<Invoce> wrapper = new QueryWrapper<>();
   wrapper.lambda().eq(Invoce::getOrderId, orderId);
   Invoce invoce = baseMapper.selectOne(wrapper);
InvoceVO vo new InvoceVO().setInvoce(invoce).setMessage(messageService.getById(invoce.getInvoiceId()));
}

那么我就需要对这段代码进行改正,使用一次构造就可以获取到这些所需的所有数据,如下代码:

LambdaQueryWrapper<User> userWrapper = new LambdaQueryWrapper<>();
userWrapper.eq(User::getUsername, "testUser"); 
List<Map<String, Object>> list = userMapper.selectMaps(userWrapper .nested(qw -> qw.inSql("id", "select user_id from `order` where ...")));

最后我们也可以简短一点直接使用mybatis的xml去写:

<select id="selectUserOrder" resultType="map"> 
   SELECT u.*, o.* 
   FROM user u INNER JOIN order o ON u.id = o.user_id 
   WHERE u.username = 'testUser' 
</select>

然后调用里面的方法直接访问到这个内容

2. 多表联查条件查询带分页(管理端最常用的接口)

同样的套路按照上面的操作去操作,但是如果我们条件过多使用mybatis-plus去操作数据库,就可能导致代码的扩展性变差,可读性也变差,所以这里我直接使用了xml的方式去写(以及处理分页该如何去处理)
首先我们去传入条件以及分页的参数

ORDER BY apply_time DESC
<if test="pageNo != null and pageSize != null">
   LIMIT #{pageNo}, #{pageSize}
</if>

然后直接获取到查到的list,再看看我们要给前端传递的实体类

List<listVO> listvo;
private Long pageNo;
private Long pageSize;
private Integer total;
private Long pages;

里面有我们需要的其他的参数,然后我们根据返回的list去用java代码拼接这些参数

BListVO.setListvo(listvo);
BListVO.setPageSize(DTO.getPageSize());
BListVO.setPageNo(DTO.getPageNo());
BListVO.setTotal(listvo.size());
BListVO.setPages(listvo.size() / listvo.getPageSize() + 1);

3. 展示数据统计

         在我们写后台的一些网站,往往也需要一些能直观的展现给业务人员,以便于分析的图表,那么这个时候就需要我们能巧妙的利用每一个数据的关系,从而减少请求数据库的次数

例如我们有个需求是统计当月一个商品的成交量,一个当月商品的赔付金额,那么种情况下因为我们都查询的是当月的一个数据,所以我们就只需要去获取一次当月数据,然后对这个进行一次遍历在获取到我们需要的数据即可,减少一次请求数据库的操作
具体操作如下:

      QueryWrapper<in> wrapper = new QueryWrapper<>();
        // 获取当前日期
        LocalDate currentDate = LocalDate.now();

//      获取本月的起始日期和结束日期
        LocalDate firstDayOfThisMonth = currentDate.with(TemporalAdjusters.firstDayOfMonth());
        LocalDate lastDayOfThisMonth = currentDate.with(TemporalAdjusters.lastDayOfMonth());
        List<in> successList = list(wrapper.lambda()
                .eq(in::Id, sellerId)
                .in(in::Status, INVOICE_FAIL.getStatus(), INVOICE_SUCCESS.getStatus(), INVOICE_PAY.getStatus())
                .ge(Invoce::getTime, Date.from(firstDayOfThisMonth.atStartOfDay(ZoneId.systemDefault()).toInstant()))
                .lt(Invoce::getTime, Date.from(lastDayOfThisMonth.atStartOfDay(ZoneId.systemDefault()).toInstant())));

//        计算本月成功的数量以及本月已赔付的金额
        BigDecimal monthAmount = BigDecimal.ZERO;
        AtomicInteger successLong = new AtomicInteger();
        successList.forEach(invoice -> {
            if (invoice.getInvoiceStatus().equals(INVOICE_SUCCESS.getStatus())) {
                successLong.addAndGet(1);
            } else if (invoice.getInvoiceStatus().equals(INVOICE_PAY.getStatus())) {
                monthAmount.add(invoice.getInvoiceAmount());
            }
        });

技术分析与提升

在项目中如何去处理延时数据

在项目中我们遇到需求是当这个订单客户发起退货,但是商户没有处理,如果到了指定的时间没有去处理,那么就将执行自动退款程序。

基于这种情况,我们考虑了使用延时队列来执行,过程是这样的,如果有用户发起了退款,就将这条消息加入到延时队列中,然后到了时间再由消费者去消费这条数据,判断如果用户撤回了操作或者商家已经处理了这条数据,那么就自动丢弃这条数据。 所以就可实现的队列进行讨论

  1. JDK提供的延时队列
  2. 引入中间件提供的队列
  3. 定时任务去执行批次任务