https://gf.bilibili.com/item/detail/1104039029
为了帮助小白入门 Java,博主录制了本项目配套的《项目手把手启动教程》,希望能给同学们带来帮助。
一、摘要
1.1 项目介绍
基于Vue+SpringBoot+MySQL的海南旅游推荐系统,基于协同推荐算法,包括用户网页和管理后台,包含景点类型模块、旅游景点模块、行程推荐模块、美食推荐模块、景点排名模块,还包含系统自带的用户管理、部门管理、角色管理、菜单管理、日志管理、数据字典管理、文件管理、图表展示等基础模块,海南旅游推荐系统基于角色的访问控制,给景点管理员、游客使用,可将权限精确到按钮级别,您可以自定义角色并分配权限,系统适合设计精确的权限约束需求。
1.2 项目录屏
二、功能模块
2.1 用户端
- 景点推荐:根据用户个性化偏好给用户推荐感兴趣的景点
【景点信息包含:景点名称、景点类型、评分、收藏量、门票价格、门票预订(提供购买链接,用户可以通过点击链接到其他平台购买门票)、开放时间、景区地址(所在市区、详细地址)、景点介绍】 - 景点筛选:用户可通过设置自己想要的景点类型、景点门票价格范围、景区地址(海口市、三亚市、儋州市、三沙市等)来筛选满足自身需求的景点
筛选:【注:若用户只设置了一个筛选条件则只需满足一个筛选条件就推荐给用户,若设置两个以上,则需都满足才给用户推荐】 - 旅游攻略:用户可以通过搜索景点名称来获取景点周边美食以及行程路线的相关信息
(1)交通指南:起点、终点、交通方式、行程路线
(2)周边美食:美食图片、名称、类型、简介、人均消费 - 景点数据:景点数据可视化
(1)好评度排名:管理员可以看到好评度高的前十个景点【排名、景点名称、好评度】
(2)景点收藏量:管理员可以看到收藏量排名前十的景点【排名、景点名称、收藏量】 - 个人中心:
(1)个人信息:账号、姓名、联系方式、身份证号(用户可以更新个人信息、退出登录)
(2)景点收藏:用户可以查看、取消收藏过的景点
2.2 管理员端
- 个人中心:管理员个人信息
- 景点信息管理:
(1)查询:可通过搜索景点名称、地址、景点类型来获取需要的景点数据(搜索到需要的景点数据后可进行查看、修改、删除景点信息操作)
(2)添加:可以添加新的景点信息 - 用户信息管理:
(1)查询:可通过搜索用户账号来查询需要的用户(查询到需要的用户后可对用户信息进行查看、修改、删除操作)
(2)添加:可添加新用户信息 - 行程信息管理:
(1)查询:可通搜索景点地址来获取景点行程路线信息(查询到需要的行程信息后可对其进行查看、修改、删除操作)
(2)添加:可添加信息 - 美食信息管理:
(1)查询:可通搜索景点地址来获取景点周边美食信息(查询到需要的信息后可对其进行查看、修改、删除操作)
(2)添加:可添加新的美食信息 - 景点数据:景点数据可视化(同用户端的景点数据可视化)
(1)好评度排名:管理员可以看到好评度高的前十个景点【排名、景点名称、好评度】
(2)景点收藏量:管理员可以看到收藏量排名前十的景点【排名、景点名称、收藏量】
三、系统展示
四、核心代码
4.1 随机景点推荐
@RequestMapping(value = "/getRecommendList2OnWeb", method = RequestMethod.GET) @ApiOperation(value = "查询推荐的景点") public Result<List<ScenicSpot>> getRecommendList2(){ List<ScenicSpot> spotList = iScenicSpotService.list(); int[] arr = new int[spotList.size()]; for(int i = 1; i < spotList.size(); i ++) { arr[i - 1] = i; } int[] ints = selectM(arr, 10); List<ScenicSpot> ans = new ArrayList<>(); for (int i : ints) { ans.add(spotList.get(i)); } return new ResultUtil<List<ScenicSpot>>().setData(ans); } public static int[] selectM(int[] arr,int m){ int len=arr.length; if(m>arr.length) { throw new RuntimeException("xxxxx"); } int[] res=new int[m]; for(int i=0;i<m;i++){ int randomIndex=len-1-new Random().nextInt(len-i); res[i]=arr[randomIndex]; int tmp=arr[randomIndex]; arr[randomIndex]=arr[i]; arr[i]=tmp; } return res; }
4.2 景点评价
@RequestMapping(value = "/addEvaluate", method = RequestMethod.GET) @ApiOperation(value = "新增评价") public Result<Evaluate> addEvaluate(@RequestParam String id, @RequestParam BigDecimal level, @RequestParam String message){ ScenicSpot ss = iScenicSpotService.getById(id); if(ss == null) { return ResultUtil.error("景点不存在"); } User currUser = securityUtil.getCurrUser(); QueryWrapper<Evaluate> qw = new QueryWrapper<>(); qw.eq("spot_id",ss.getId()); qw.eq("user_id",currUser.getId()); qw.last("limit 1"); Evaluate evaluate = iEvaluateService.getOne(qw); if(evaluate == null) { evaluate = new Evaluate(); evaluate.setSpotId(ss.getId()); evaluate.setSpotName(ss.getTitle()); evaluate.setUserId(currUser.getId()); evaluate.setUserName(currUser.getNickname()); } evaluate.setLevel(level); evaluate.setMessage(message); evaluate.setTime(DateUtil.now()); iEvaluateService.saveOrUpdate(evaluate); return ResultUtil.success(); }
4.3 协同推荐算法
@Scheduled(cron = "0 0/1 * * * ?") @ApiOperation(value = "景点数据更新") public void job(){ List<ScenicSpot> spotList = iScenicSpotService.list(); for (ScenicSpot vo : spotList) { Long evaluateSum = 0L; QueryWrapper<Evaluate> evalQw = new QueryWrapper<>(); evalQw.eq("spot_id",vo.getId()); List<Evaluate> evaluateList = iEvaluateService.list(evalQw); for (Evaluate evaluate : evaluateList) { evaluateSum += evaluate.getLevel().longValue(); } // 收藏 10分 QueryWrapper<Collection> coQw = new QueryWrapper<>(); coQw.eq("spot_id",vo.getId()); evaluateSum += iCollectionService.count(coQw); // 浏览 1分 String viewStr = redisTemplate.get("SPOT_VIEW:" + vo.getId()); if(!ZwzNullUtils.isNull(viewStr)) { try { long viewNumber = Long.parseLong(viewStr); evaluateSum += viewNumber; } catch (Exception e) {} } vo.setValue(evaluateSum); } Collections.sort(spotList, new Comparator<ScenicSpot>() { @Override public int compare(ScenicSpot o1, ScenicSpot o2) { return (int)(o2.getValue() - o1.getValue()); } }); if(spotList.size() > 10) { spotList = spotList.subList(0,10); } for (ScenicSpot vo1 : spotList) { // 评分 BigDecimal evaluateSum = BigDecimal.ZERO; QueryWrapper<Evaluate> evalQw = new QueryWrapper<>(); evalQw.eq("spot_id",vo1.getId()); List<Evaluate> evaluateList = iEvaluateService.list(evalQw); for (Evaluate evaluate : evaluateList) { evaluateSum = evaluateSum.add(evaluate.getLevel()); } if(evaluateList.size() > 0) { vo1.setStar(evaluateSum.divide(BigDecimal.valueOf(evaluateList.size()),2, RoundingMode.DOWN)); } else { vo1.setStar(BigDecimal.valueOf(-1)); } // 收藏 QueryWrapper<Collection> coQw = new QueryWrapper<>(); coQw.eq("spot_id",vo1.getId()); vo1.setCollection(iCollectionService.count(coQw)); } redisTemplate.set("SPOT_JOB_DATA", JSON.toJSONString(spotList)); System.out.println("缓存完毕!"); }
4.4 网站登录
@RequestMapping(value = "/loginOnWeb", method = RequestMethod.GET) @ApiOperation(value = "网站前台登陆") public Result<String> loginOnWeb(@RequestParam String userName, @RequestParam String password){ QueryWrapper<User> qw = new QueryWrapper<>(); qw.eq("username",userName); List<User> userList = iUserService.list(qw); if(userList.size() < 1) { return ResultUtil.error("用户不存在"); } User user = userList.get(0); if(!new BCryptPasswordEncoder().matches(password, user.getPassword())){ return ResultUtil.error("密码不正确"); } String accessToken = securityUtil.getToken(user.getUsername(), true); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(new SecurityUserDetails(user), null, null); SecurityContextHolder.getContext().setAuthentication(authentication); return new ResultUtil<String>().setData(accessToken); }
4.5 查询景点美食
@RequestMapping(value = "/getByPage", method = RequestMethod.GET) @ApiOperation(value = "查询美食") public Result<IPage<DeliciousFood>> getByPage(@ModelAttribute DeliciousFood deliciousFood ,@ModelAttribute PageVo page){ QueryWrapper<DeliciousFood> qw = new QueryWrapper<>(); if(!ZwzNullUtils.isNull(deliciousFood.getTitle())) { qw.like("title",deliciousFood.getTitle()); } if(!ZwzNullUtils.isNull(deliciousFood.getContent())) { qw.like("content",deliciousFood.getContent()); } if(!ZwzNullUtils.isNull(deliciousFood.getSpotId())) { qw.eq("spot_id",deliciousFood.getSpotId()); } IPage<DeliciousFood> data = iDeliciousFoodService.page(PageUtil.initMpPage(page),qw); return new ResultUtil<IPage<DeliciousFood>>().setData(data); }
五、免责说明
- 本项目仅供个人学习使用,商用授权请联系博主,否则后果自负。
- 博主拥有本软件构建后的应用系统全部内容所有权及独立的知识产权,拥有最终解释权。
- 如有问题,欢迎在仓库 Issue 留言,看到后会第一时间回复,相关意见会酌情考虑,但没有一定被采纳的承诺或保证。
下载本系统代码或使用本系统的用户,必须同意以下内容,否则请勿下载!
- 出于自愿而使用/开发本软件,了解使用本软件的风险,且同意自己承担使用本软件的风险。
- 利用本软件构建的网站的任何信息内容以及导致的任何版权纠纷和法律争议及后果和博主无关,博主对此不承担任何责任。
- 在任何情况下,对于因使用或无法使用本软件而导致的任何难以合理预估的损失(包括但不仅限于商业利润损失、业务中断与业务信息丢失),博主概不承担任何责任。
- 必须了解使用本软件的风险,博主不承诺提供一对一的技术支持、使用担保,也不承担任何因本软件而产生的难以预料的问题的相关责任。