智能图库模块-图库分析(后端)
今天进行了图库分析模块后端部分的学习开发,对于图库分析,从字面上来看就是要分析当前的图库或者某个空间的资源使用情况,比如目前图库空间用了多少的资源、有哪些分类、哪些标签,图片的大小等,对于管理员来说,对所有方面进行分析是有利于管理员来把控系统的资源分配的。
用户对自己的空间进行分析和管理员来所有的空间进行分析本质上是一样的,区别在于图片的选择范围,转化为sql语句的话在where前面的select都是一样的,所以可以通过引入一些字段来进行区分:
▼java复制代码private void fillAnalyzeQueryWrapper(SpaceAnalyzeRequest spaceAnalyzeRequest, QueryWrapper<Picture> queryWrapper){ boolean queryAll = spaceAnalyzeRequest.isQueryAll(); if (queryAll)return; boolean queryPublic = spaceAnalyzeRequest.isQueryPublic(); if (queryPublic){ queryWrapper.isNull("spaceId"); return; } Long spaceId = spaceAnalyzeRequest.getSpaceId(); if (spaceId != null){ queryWrapper.eq("spaceId",spaceId); return; } throw new BusinessException(ErrorCode.PARAMS_ERROR,"未指定查询范围"); }
spaceAnalyzeRequest是一个基础的空间分析类,后面的多种分析请求类都会继承这个类,这个类的三个成员变量queryAll、queryPublic和spaceId用于进行区分选择的范围,queryAll的优先级最高,sapceId仅在前面两个字段都为false的时候才生效,在后面写方法需要拼接查询条件时直接调用该方法即可。
分析空间也是需要权限的,而且后面的请求都需要校验,因此可以抽出来写成一个方法:
▼java复制代码private void checkSpaceAnalyzeAuth(SpaceAnalyzeRequest spaceAnalyzeRequest, User loginUser){ boolean queryPublic = spaceAnalyzeRequest.isQueryPublic(); boolean queryAll = spaceAnalyzeRequest.isQueryAll(); //仅管理员可以全空间分析或者公共图库分析 if (queryAll || queryPublic){ ThrowUtils.throwIf(!userService.isAdmin(loginUser), ErrorCode.NO_AUTH_ERROR); }else{ //分析特定空间,仅本人或者管理员可以 Long spaceId = spaceAnalyzeRequest.getSpaceId(); ThrowUtils.throwIf(spaceId == null, ErrorCode.PARAMS_ERROR); Space space = spaceService.getById(spaceId); ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR,"空间不存在"); spaceService.checkSpaceAuth(loginUser,space); } }
对于上面的分析请求,流程都是校验参数、校验权限(checkSpaceAnalyzeAuth)、补充查询条件(fillAnalyzeQueryWrapper,有的请求需要额外补充别的条件)、进行查询和封装返回结果,以分析空间使用数据为例:
▼java复制代码public SpaceUsageAnalyzeResponse getSpaceUsageAnalyze(SpaceUsageAnalyzeRequest spaceUsageAnalyzeRequest, User loginUser) { //校验参数 ThrowUtils.throwIf(spaceUsageAnalyzeRequest == null,ErrorCode.PARAMS_ERROR); //全空间或者公共图库 需要从Picture表中查询 if (spaceUsageAnalyzeRequest.isQueryAll() || spaceUsageAnalyzeRequest.isQueryPublic()){ //权限校验 ,仅管理员可以访问 checkSpaceAnalyzeAuth(spaceUsageAnalyzeRequest,loginUser); //统计图库的使用空间 QueryWrapper<Picture> queryWrapper = new QueryWrapper<>(); queryWrapper.select("picSize"); //补充查询范围 fillAnalyzeQueryWrapper(spaceUsageAnalyzeRequest,queryWrapper); //调用list()返回的是List<Picture>,存的是一个对象,当数据量大的时候会占用大量空间,需要转换为List<Long> //getBaseMapper().selectObjs 返回的其实是Long List<Object> pictureObjList = pictureService.getBaseMapper().selectObjs(queryWrapper); long usedSize = pictureObjList.stream().mapToLong(obj -> (Long) obj).sum(); long usedCount = pictureObjList.size(); //封装返回结果 SpaceUsageAnalyzeResponse spaceUsageAnalyzeResponse = new SpaceUsageAnalyzeResponse(); spaceUsageAnalyzeResponse.setUsedSize(usedSize); spaceUsageAnalyzeResponse.setUsedCount(usedCount); //公共图库无数量和容量限制 spaceUsageAnalyzeResponse.setMaxSize(null); spaceUsageAnalyzeResponse.setSizeUsageRatio(null); spaceUsageAnalyzeResponse.setMaxCount(null); spaceUsageAnalyzeResponse.setCountUsageRatio(null); return spaceUsageAnalyzeResponse; }else { //特定空间直接从Space表中查询 Long spaceId = spaceUsageAnalyzeRequest.getSpaceId(); ThrowUtils.throwIf(spaceId == null || spaceId <= 0, ErrorCode.PARAMS_ERROR); //获取空间信息 Space space = spaceService.getById(spaceId); ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR,"空间不存在"); //权限校验 仅管理员可以访问 checkSpaceAnalyzeAuth(spaceUsageAnalyzeRequest,loginUser); //封装返回结果 SpaceUsageAnalyzeResponse spaceUsageAnalyzeResponse = new SpaceUsageAnalyzeResponse(); spaceUsageAnalyzeResponse.setUsedSize(space.getTotalSize()); spaceUsageAnalyzeResponse.setUsedCount(space.getTotalCount()); spaceUsageAnalyzeResponse.setMaxSize(space.getMaxSize()); spaceUsageAnalyzeResponse.setMaxCount(space.getMaxCount()); //计算比例 double sizeUsageRatio = NumberUtil.round(space.getTotalSize() * 100.0 / space.getMaxSize() , 2).doubleValue(); double countUsageRatio = NumberUtil.round(space.getTotalCount() * 100.0 / space.getMaxCount(), 2).doubleValue(); spaceUsageAnalyzeResponse.setSizeUsageRatio(sizeUsageRatio); spaceUsageAnalyzeResponse.setCountUsageRatio(countUsageRatio); return spaceUsageAnalyzeResponse; } }
上面的代码中,由于只需要获取图片的大小来计算使用情况,所以只select了图片大小这一列,并且在调用mapper方式时不用.list(queryWrapper)而是调用getBaseMapper().selectObjs(queryWrapper),这样查出来的列表就不会封装成Picture对象,在这里它其实就是Long,如果数据量很大并且封装的是Picture对象的话,就会占用大量存储和影响性能,通过上面的写法便能提高性能且节约了存储空间,是一个优化的细节,再通过pictureObjList.stream().mapToLong(obj -> (Long) obj).sum()来将Object转为Long之后进行计算得到图片大小之和,这是Java8的stream流特性,可以帮助我们提高开发的效率,这行代码不用stream的话便是要写一个for循环来求和,有了stream直接一行代码就可以搞定了,所以掌握stream的使用是有必要的,后面的请求在处理封装结果时也会用到stream流。
另外几个请求的的实现流程也是差不多的,只是不同请求在sql语句的拼接、mapper的调用和封装的细节上有区别,在不清楚怎么写wrapper的时候,可以在MySQL控制台中写sql语句来验证想法是否正确,然后自己或者通过ai写出相应的wrapper。mapper的调用上在获取空间图片分类分析中,select了不止一列:
queryWrapper.select("category","count(*) as count","sum(picSize) as size").groupBy("category");
在查询时就调用了getBaseMapper().selectMaps(queryWrapper)来得到一个Map对象而不是Object队列,然后对这个Map使用stream进行处理:
▼java复制代码List<SpaceCategoryAnalyzeResponse> list = pictureService.getBaseMapper().selectMaps(queryWrapper).stream() .map(result -> { String category = (String) result.get("category"); Long count = (Long) result.get("count"); Long size = ((BigDecimal) result.get("size")).longValue(); return new SpaceCategoryAnalyzeResponse(category, count, size); }) .collect(Collectors.toList());
在空间图片标签分析中,我们存在数据库的tags是以Json形式的字符串存进去的,为了得到每个标签的使用情况,我们在查出标签队列后,需要对这个队列进行扁平化处理,即把原本的["a","b"],["a","c"] 转化为 ["a","b","a","c"],而stream流中也确实有对应的方法来实现这个扁平化:
▼java复制代码//解析标签并统计 //扁平化 把多个数组的值合并成一个数组 Map<String, Long> tagCountMap = tagJsonList.stream() //["a","b"],["a","c"] => ["a","b","a","c"] .flatMap(tagJson -> JSONUtil.toList(tagJson, String.class).stream()) .collect(Collectors.groupingBy(tag -> tag, Collectors.counting()));
之后将这个map转为响应对象并按次数进行降序排序返回:
▼java复制代码return tagCountMap.entrySet().stream() .sorted((e1,e2) -> Long.compare(e2.getValue(),e1.getValue()))//降序 .map(entry -> new SpaceTagAnalyzeResponse(entry.getKey(),entry.getValue())) .collect(Collectors.toList());
后面的空间图片大小分析、用户上传行为分析和空间使用排行分析也跟前面这些请求一样的思路,这里就不再多作说明了。
