需求分析

1.排行榜页的显示

2.排行榜的定期更新

3.主题推荐

4.旅游攻略导航

第一部分(排行榜页的显示)

image

在前端就可以看到需要的攻略一共有三个,海外,国内和热门,需要用一个type字段来区分,然后根据浏览数去排序就好了,具体的排序依据还要看到时候的业务需要

依旧是f12起手去看接口

image

非常的明显的一个rank字样代表排行榜,对应的是StrategyRanks中的rank方法,传了一个type参数,发送请求的是get方法

接下来确定返回值类型,这个需要去前端看

  <div class="nav-title">
            <h3>海外攻略推荐排行</h3>
            <b class="gn-arrow"></b>
          </div>
          <div class="nav-panel rank-panel">
            <ol>
              <li v-for="(item, index) in rankList1" :key="item.id">
                <em class="rank-num">{{ index + 1 }}</em>
                <strong>
                  <a :href="`/destination/details/${item.destId}`" target="_blank">{{ item.destName }}</a>
                </strong>
                <a :href="`/strategy/article/${item.strategyId}`" target="_blank">{{ item.strategyTitle }}</a>
              </li>
            </ol>
          </div>
        </div>

const rankList1 = ref([])
const rankList2 = ref([])
const rankList3 = ref([])
const themeList = ref([])

onMounted(() => {
  nextTick(() => {
    getBannerStrategy()
    getStrategtRank(1)
    getStrategtRank(2)
    getStrategtRank(3)
    getStrategyTheme()
  })

从这段代码中可以看出前端需要的是一个可以遍历的list,需要的数据是item.后面的字段名,rank表中的数据完全符合,直接返回List<>泛型里面装一个rank的实体类,然后参数type是用直接传的方式,我们直接在参数列表去接,这样controller中的代码就可以确定了,能copy的就不要手搓,不然敲错了是真的难受

 @GetMapping("/rank")
    public R<List<StrategyRank>> rank(Long type) {
        return R.ok(strategyRankService.queryRank(type));
    }

下面去业务层,前端传来了一个用来确定状态(海外,国内,热门)的type,直接使用条件构造器首先根据type查询然后,然后根据statisnum字段进行降序排序,这里需要注意的是我们的需求二存在更新排行耪一说,所以我们需要加入对最新时间的筛选,代码如下

 //切记insql中的字段是数据库的字段而不是实体类中的数据,不要敲错了
  return lambdaQuery().inSql(StrategyRank::getStatisTime, "select max(statis_time) from ta_strategy_rank ")
                .eq(StrategyRank::getType, type)
                .orderByDesc(StrategyRank::getStatisnum)
                .last("limit 10")
                .list();

这样就可以成功查询排行榜内容并进行显示了,效果如下

image

第二部分(排行榜的定期更新)

本节的定期更新涉及定时任务

image

在若依的后端管理系统可以看到有一个已经写好的定时任务的模块,可以在里面添加定时任务

image

我们只需要在该页面完成各项的添加,便能在定时任务的界面随时控制定时任务的开关,最关键的便是添加方法,这是后端要去做的

在项目中定时任务一定是有一个专属的服务去完成的,我们本部分想要做的是排行榜的定时更新,也就是将排行榜的更新作为定时任务,我们需要去完成这个定时更新

在完成顺序上个人建议是一定要最后写业务逻辑,先要让这个方法调用能通,如果一下子全写完报错的话会无从下手的

第一步,远程接口中定义对应的远程调用接口,更新不需要返回数据,也没有什么参数,唯一需要的只有请求来源,放在请求头中,用于区分你的请求是从哪里来的,接口代码如下

 @PostMapping("/strategies/feign/statisRank")
    R<?> statisRank(@RequestHeader(SecurityConstants.FROM_SOURCE) String source);

每写一个这种远程调用的接口都需要一个降级类,就是当你这个接口调用失败之后,会作何处理的类,不写的话,降级类(fallback)会报错的,这个写起来也简单,只要去降级类实现对应的方法即可

  @Override
            public R<?> statisRank(String source) {
                return R.fail(Lists.newArrayList(), "查询攻略排行失败:" + throwable.getMessage());
            }

这样远程调用的接口部分就写好了

第二步,完成排行榜服务的接口和业务(测试版)

根据上面的远程调用接口直接复制,加一个private权限修饰符,完成方法体,调用service去完成对应的更新方法即可controller接口如下

  @PostMapping("/feign/statisRank")
    public R<?> statisRank(){
        strategyService.queryRank();
        return R.ok();
    };

然后去实现queryRank()方法,这里简单写一个打印的操作,一会儿测试一下是否能通

第三步,在定时任务服务中创建我们对应的任务类,然后添加注解@Component(对应的任务名,首字母小写),最后写方法(权限修饰符是public,不然别人读不到了),这里要用我们远程调用接口写的方法,所以需要添加依赖,然后把接口注入去调用对应的方法

@Component("strategyTask")
public class StrategyTask {

     @Autowired
     private RemoteStrategyService remoteStrategyService;

    public void statisRank(){
       remoteStrategyService.statisRank("inner");
    }
}

接下来去后端管理系统中做一个测试,写法如图所示,然后在管理界面把启动按钮打开

image
image

在后端的控制台中看到结果成功显示,说明我们的定时任务已经通了,可以成功调用更新方法,接下来就是最后一步,完成排行榜更新业务

image

再次看这个排行榜,本身的数据是从rank表取出来的,但是更新的数据是从攻略表取出来的,因为排行榜本身的数据就是从攻略表排序取出来的,接下来我们来厘清一下思路,首先确定rank表需要的数据,然后从攻略表按条件查询,最后从查到的每条数据中把rank表需要的数据填满即可。接下来看看代码

 Date now = new Date();
        //查国外
        List<Strategy> abroadList = lambdaQuery().eq(Strategy::getIsabroad, 1)
                .orderByDesc(Strategy::getViewnum)
                .last("limit 10")
                .list();
        List<StrategyRank> abroadRank = new ArrayList<>();
        addRank(abroadList, now,abroadRank,1L);
        //查国内
        List<Strategy> domesticList = lambdaQuery().eq(Strategy::getIsabroad, 0)
                .orderByDesc(Strategy::getViewnum)
                .last("limit 10")
                .list();
        List<StrategyRank> domesticRank = new ArrayList<>();
        addRank(domesticList, now,domesticRank,2L);
        //查热门
        List<Strategy> hotList = lambdaQuery()
                .orderByDesc(Strategy::getViewnum)
                .last("limit 10")
                .list();
        List<StrategyRank> hotRank = new ArrayList<>();
        addRank(hotList, now,hotRank,3L);

    }

    @Override
    public List<ThemeVo> queryTheme() {
        QueryWrapper<Strategy> queryWrapper = new QueryWrapper<>();
        queryWrapper.groupBy("theme_name");
        queryWrapper.select(" theme_name themeName, GROUP_CONCAT(DISTINCT dest_name) names,GROUP_CONCAT(DISTINCT dest_id) ids");
        List<Map<String, Object>> mapList = baseMapper.selectMaps(queryWrapper);
        List<ThemeVo> themeVoList = new ArrayList<>();
        for(Map<String, Object> map : mapList){
            String  themeName = (String) map.get("themeName");
            String ids = (String) map.get("ids");
            String names = (String) map.get("names");
            String[] namearr = names.split(",");
            String[] idarr = ids.split(",");
            List<Destination> dests = new ArrayList<>();
            for(int i = 0;i<namearr.length;i++){
                Long id= Long.parseLong(idarr[i]);
                String name = namearr[i];
                Destination destination = new Destination();
                destination.setId(id);
                destination.setName(name);
                dests.add(destination);
            }
            ThemeVo themeVo = new ThemeVo(themeName, dests);
            themeVoList.add(themeVo);
        }
        return themeVoList;
    }

这里因为进行补全数据的时候代码操作是一摸一样的,所以把代码提取出来共用了

第三部分(主题推荐)