活动介绍

java使用stream对Map<ConcurrencyEntity, String>的key的CreateTime降序

时间: 2024-11-30 12:13:49 浏览: 36
在Java中,你可以使用`Stream` API 对 `Map<ConcurrencyEntity, String>` 的 key 进行排序,特别是当你想按照 `CreateTime` 这个字段降序排列时。首先,你需要确保 `ConcurrencyEntity` 类有一个可以获取 `CreateTime` 的getter方法,例如 `getCreateTime()`。 下面是一个示例步骤: ```java import java.util.stream.Collectors; // 假设ConcurrencyEntity类有一个ctime属性 Map<ConcurrencyEntity, String> map = ...; // 你的map // 使用 Stream API 创建一个新的sorted Map,键按CreateTime降序 Map<ConcurrencyEntity, String> sortedMap = map.entrySet() .stream() // 转换为entry stream .sorted(Map.Entry.<ConcurrencyEntity, String>comparingByKey(ConcurrencyEntity::getCreateTime) // 按照CreateTime比较 .reversed()) // 降序排序 .collect(Collectors.toMap( entry -> entry.getKey(), // 投影key到新map entry -> entry.getValue(), // 投影value到新map (oldValue, newValue) -> oldValue)); // 链接旧值和新值,默认情况下如果有冲突,使用第一个元素(这里假设不会有冲突)
阅读全文

相关推荐

解释@Service @RequiredArgsConstructor public class FormDataService extends ServiceImpl<FormDataMapper, FormData> { // private final CacheUtils cacheUtils; public void saveFormResult(FormData entity, Long userId) { // String formKey = entity.getFormKey(); // entity.setSerialNumber(cacheUtils.incr(StrUtil.format(FORM_RESULT_NUMBER, formKey), CommonConstants.ConstantNumber.ONE)); entity.setUserId(userId); entity.setSubmitAddress(AddressUtils.getRealAddressByIP(entity.getSubmitRequestIp())); this.save(entity); } public FormDataTableVO listFormDataTable(QueryFormResultRequest request) { List<FormData> dataEntityList; Long total; // 查询全部 if (0 == request.getCurrent()) { dataEntityList = this.list(Wrappers.<FormData>lambdaQuery().eq(FormData::getFormKey, request.getFormKey())); total = Long.valueOf(dataEntityList.size()); } else { Page<FormData> page = this.page(request.toMybatisPage(), Wrappers.<FormData>lambdaQuery().eq(FormData::getFormKey, request.getFormKey())); dataEntityList = page.getRecords(); total = page.getTotal(); } List<Map> list = dataEntityList.stream().map(item -> { Map<String, Object> originalData = item.getOriginalData(); item.setOriginalData(null); // originalData.put(BaseEntity.Fields.createTime, LocalDateTimeUtil.formatNormal(item.getCreateTime())); // originalData.put(BaseEntity.Fields.updateTime, LocalDateTimeUtil.formatNormal(item.getUpdateTime())); originalData.put(FormData.Fields.createTime, LocalDateTimeUtil.formatNormal(item.getCreateTime())); originalData.put(FormData.Fields.updateTime, LocalDateTimeUtil.formatNormal(item.getUpdateTime())); Map<String, Object> formDataMap = BeanUtil.beanToMap(item); formDataMap.putAll(originalData); return formDataMap; }).collect(Collectors.toList()); return new FormDataTableVO(list, total); } public Boolean deleteByIds(List<String> dataIdList, String formKey) { baseMapper.deleteBatchIds(dataIdList); return null; } public Boolean updateFormResult(FormData formData) { FormData data = this.getById(formData.getId()); data.setOriginalData(formData.getOriginalData()); return this.updateById(data); } }

{ String userId = Optional.ofNullable(param.getUserId()).orElse(LoginUserUtil.getLoginUserId()); Integer queryType = Optional.ofNullable(param.getQueryType()).orElse(1); Integer createTimeSort = Optional.ofNullable(param.getCreateTimeSort()).orElse(0); List<TraineeUserNoteDTO> allNoteList = new ArrayList<>(); if (queryType == 1){ //查询我发布的笔记 allNoteList = traineeUserNoteMapper.getMyNotePage(createTimeSort, userId); } else if (queryType == 2) { //查询我点赞的笔记 List<ZshLikeDTO> zshLikeDTOS = traineeLikeMapper.selectList(new LambdaQueryWrapper<ZshLikeDTO>() .eq(ZshLikeDTO::getUserId, userId) .eq(ZshLikeDTO::getObjectType, 4)); if (CollectionUtils.isEmpty(zshLikeDTOS)){ return new Page<>(param.getCurrent(), param.getSize()); } List<String> notes = zshLikeDTOS.stream().map(ZshLikeDTO::getObjectId).collect(Collectors.toList()); List<ZshUserNoteDTO> zshNoteList = traineeUserNoteMapper.selectBatchIds(notes); allNoteList = zshNoteList.stream().map(zshUserNoteDTO -> { TraineeUserNoteDTO traineeUserNoteDTO = new TraineeUserNoteDTO(); BeanUtils.copyProperties(zshUserNoteDTO, traineeUserNoteDTO); return traineeUserNoteDTO; }).collect(Collectors.toList()); } //获取笔记点赞数,需获取全部笔记及点赞数才可排序 List<String> noteIdList = allNoteList.stream() .map(TraineeUserNoteDTO::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); if (noteIdList.isEmpty()) { return new Page<>(param.getCurrent(), param.getSize(), 0); } Map<String, Integer> mapLikeCount = traineeLikeMapper.selectLikeCount(noteIdList, 4) .stream() .collect(Collectors.toMap(IdCountModel::getId, IdCountModel::getCount)); List<MyNoteVO> allNoteLikeList = allNoteList.stream().map(dto -> { MyNoteVO myNoteVO = new MyNoteVO(); BeanUtils.copyProperties(dto, myNoteVO); //设置笔记点赞数 myNoteVO.setLikeCount(mapLikeCount.getOrDefault(myNoteVO.getId(), 0)); return myNoteVO; }).collect(Collectors.toList()); //根据点赞数排序 if (param.getLikeCountSort() != null){ if (param.getLikeCountSort() == 1

public void startProcessByDefId(String procDefId, Map<String, Object> variables) { try { ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionId(procDefId).singleResult(); startProcess(processDefinition, variables); } catch (Exception e) { e.printStackTrace(); throw new ServiceException("流程启动错误"); } } private void startProcess(ProcessDefinition procDef, Map<String, Object> variables) { if (ObjectUtil.isNotNull(procDef) && procDef.isSuspended()) { throw new ServiceException("流程已被挂起,请先激活流程"); } // 设置流程发起人Id到流程中 String userIdStr = TaskUtils.getUserId(); identityService.setAuthenticatedUserId(userIdStr); variables.put("initiator",userIdStr); // 设置流程状态为进行中 variables.put(ProcessConstants.PROCESS_STATUS_KEY, ProcessStatus.RUNNING.getStatus()); // 发起流程实例 ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDef.getId(), variables); // 第一个用户任务为发起人,则自动完成任务 //wfTaskService.startFirstTask(processInstance, variables); }还是出现在了代办里面@Override public TableDataInfo<WfTaskVo> selectPageTodoProcessList(ProcessQuery processQuery, PageQuery pageQuery) { Page<WfTaskVo> page = new Page<>(); TaskQuery taskQuery = taskService.createTaskQuery() .active() //.includeProcessVariables() .or() .taskAssignee(TaskUtils.getUserId()) // 分配给当前用户的任务 .taskCandidateUser(TaskUtils.getUserId()) // 当前用户是候选人的任务 .taskCandidateGroupIn(TaskUtils.getCandidateGroup()) .endOr() .orderByTaskCreateTime().desc(); // 构建搜索条件 ProcessUtils.buildProcessSearch(taskQuery, processQuery); page.setTotal(taskQuery.count()); int offset = pageQuery.getPageSize() * (pageQuery.getPageNum() - 1); List<Task> taskList = taskQuery.listPage(offset, pageQuery.getPageSize()); List<WfTaskVo> flowList = new ArrayList<>(); // 预收集所有流程实例ID(用于批量查询变量) Set<String> processInstanceIds = taskList.stream() .map(Task::getProcessInstanceId) .collect(Collectors.toSet()); // 批量查询流程变量(减少数据库查询次数) Map<String, Map<String, Object>> processVariablesMap = new HashMap<>(); if (!processInstanceIds.isEmpty()) { historyService.createHistoricVariableInstanceQuery() .processInstanceIdIn(processInstanceIds.toArray(new String[0])) .list() .forEach(instance -> { processVariablesMap .computeIfAbsent(instance.getProcessInstanceId(), k -> new HashMap<>()) .put(instance.getName(), instance.getValue()); }); } Map<String, ProcessDefinition> processDefinitionMap = repositoryService.createProcessDefinitionQuery() .processDefinitionIdIn(taskList.stream() .map(Task::getProcessDefinitionId).distinct().toArray(String[]::new)) .list() .stream() .collect(Collectors.toMap(ProcessDefinition::getId, pd -> pd)); for (Task task : taskList) { WfTaskVo flowTask = new WfTaskVo(); // 当前流程信息 flowTask.setTaskId(task.getId()); flowTask.setTaskDefKey(task.getTaskDefinitionKey()); flowTask.setCreateTime(task.getCreateTime()); flowTask.setProcDefId(task.getProcessDefinitionId()); flowTask.setTaskName(task.getName()); ProcessDefinition pd = processDefinitionMap.get(task.getProcessDefinitionId()); flowTask.setDeployId(pd.getDeploymentId()); flowTask.setProcDefName(pd.getName()); flowTask.setProcDefVersion(pd.getVersion()); flowTask.setProcInsId(task.getProcessInstanceId()); // 流程发起人信息 HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() .processInstanceId(task.getProcessInstanceId()) .singleResult(); Long userId = Long.parseLong(historicProcessInstance.getStartUserId()); String nickName = userService.selectNickNameById(userId); flowTask.setStartUserId(userId); flowTask.setStartUserName(nickName); // 流程变量 flowTask.setProcVars(processVariablesMap.getOrDefault( task.getProcessInstanceId(), Collections.emptyMap() )); flowList.add(flowTask); } page.setRecords(flowList); return TableDataInfo.build(page); }

**谷粒随享** ## 第7章 专辑/声音详情 **学习目标:** - 专辑详情业务需求 - 专辑服务 1.专辑信息 2.分类信息 3.统计信息 4,主播信息 - 搜索服务:汇总专辑详情数据 - 专辑包含**声音列表(付费标识动态展示)** - MongoDB文档型数据库应用 - 基于**MongoDB**存储用户对于声音**播放进度** - 基于Redis实现排行榜(将不同分类下包含各个维度热门专辑排行) # 1、专辑详情 ![详情-专辑详情和声音列表](assets/详情-专辑详情和声音列表.gif) 专辑详情页面渲染需要以下四项数据: - **albumInfo**:当前专辑信息 - **albumStatVo**:专辑统计信息 - **baseCategoryView**:专辑分类信息 - **announcer**:专辑主播信息 因此接下来,我们需要在**专辑微服务**、**用户微服务**中补充RestFul接口实现 并且 提供远程调用Feign API接口给**搜索微服务**来调用获取。 在专辑**搜索微服务**中编写控制器**汇总专辑详情**所需**数据**: 以下是详情需要获取到的数据集 1. 通过专辑Id 获取专辑数据{已存在} 2. 通过专辑Id 获取专辑统计信息**{不存在}** 3. 通过三级分类Id 获取到分类数据{已存在} 4. 通过用户Id 获取到主播信息{存在} ## 1.1 服务提供方提供接口 ### 1.1.1 根据专辑Id 获取专辑数据(已完成) ### 1.1.2 根据三级分类Id获取到分类信息(已完成) ### 1.1.3 根据用户Id 获取主播信息(已完成) ### 1.1.4 根据专辑Id 获取统计信息 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/67 **AlbumInfoApiController** 控制器 java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ @Operation(summary = "根据专辑ID查询专辑统计信息") @GetMapping("/albumInfo/getAlbumStatVo/{albumId}") public Result<AlbumStatVo> getAlbumStatVo(@PathVariable Long albumId) { AlbumStatVo albumStatVo = albumInfoService.getAlbumStatVo(albumId); return Result.ok(albumStatVo); } **AlbumInfoService**接口 java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ AlbumStatVo getAlbumStatVo(Long albumId); **AlbumInfoServiceImpl**实现类 java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ @Override public AlbumStatVo getAlbumStatVo(Long albumId) { return albumInfoMapper.getAlbumStatVo(albumId); } **albumInfoMapper.java** java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ AlbumStatVo getAlbumStatVo(@Param("albumId") Long albumId); **albumInfoMapper.xml** sql <select id="getAlbumStatVo" resultType="com.atguigu.tingshu.vo.album.AlbumStatVo"> select stat.album_id, max(if(stat.stat_type='0401', stat.stat_num, 0)) playStatNum, max(if(stat.stat_type='0402', stat.stat_num, 0)) subscribeStatNum, max(if(stat.stat_type='0403', stat.stat_num, 0)) buyStatNum, max(if(stat.stat_type='0404', stat.stat_num, 0)) commentStatNum from album_stat stat where stat.album_id = #{albumId} and stat.is_deleted = 0 group by stat.album_id </select> service-album-client模块**AlbumFeignClient** 接口中添加 java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ @GetMapping("/albumInfo/getAlbumStatVo/{albumId}") public Result<AlbumStatVo> getAlbumStatVo(@PathVariable Long albumId); **AlbumDegradeFeignClient**熔断类: java @Override public Result<AlbumStatVo> getAlbumStatVo(Long albumId) { log.error("[专辑模块]提供远程调用方法getAlbumStatVo服务降级"); return null; } ## 1.2 服务调用方汇总数据 回显时,后台需要提供将数据封装到map集合中; java result.put("albumInfo", albumInfo); 获取专辑信息 result.put("albumStatVo", albumStatVo); 获取专辑统计信息 result.put("baseCategoryView", baseCategoryView); 获取分类信息 result.put("announcer", userInfoVo); 获取主播信息 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/69 在service-search 微服务**itemApiController** 控制器中添加 java package com.atguigu.tingshu.search.api; import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.search.service.ItemService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @Tag(name = "专辑详情管理") @RestController @RequestMapping("api/search") @SuppressWarnings({"all"}) public class itemApiController { @Autowired private ItemService itemService; /** * 根据专辑ID查询专辑详情相关数据 * * @param albumId * @return */ @Operation(summary = "根据专辑ID查询专辑详情相关数据") @GetMapping("/albumInfo/{albumId}") public Result<Map<String, Object>> getItemInfo(@PathVariable Long albumId) { Map<String, Object> mapResult = itemService.getItemInfo(albumId); return Result.ok(mapResult); } } 接口与实现 java package com.atguigu.tingshu.search.service; import java.util.Map; public interface ItemService { /** * 根据专辑ID查询专辑详情相关数据 * * @param albumId * @return */ Map<String, Object> getItemInfo(Long albumId); } java package com.atguigu.tingshu.search.service.impl; import cn.hutool.core.lang.Assert; import com.atguigu.tingshu.album.AlbumFeignClient; import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.model.album.BaseCategoryView; import com.atguigu.tingshu.search.service.ItemService; import com.atguigu.tingshu.user.client.UserFeignClient; import com.atguigu.tingshu.vo.album.AlbumStatVo; import com.atguigu.tingshu.vo.user.UserInfoVo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadPoolExecutor; @Slf4j @Service @SuppressWarnings({"all"}) public class ItemServiceImpl implements ItemService { @Autowired private AlbumFeignClient albumFeignClient; @Autowired private UserFeignClient userFeignClient; @Autowired private ThreadPoolExecutor threadPoolExecutor; /** * 根据专辑ID查询专辑详情相关数据 * 1.albumInfo:当前专辑信息 * 2.albumStatVo:专辑统计信息 * 3.baseCategoryView:专辑分类信息 * 4.announcer:专辑主播信息 * * @param albumId * @return */ @Override public Map<String, Object> getItemInfo(Long albumId) { //1.创建响应结果Map对象 HashMap在多线程环境下并发读写线程不安全:导致key覆盖;导致死循环 //采用线程安全:ConcurrentHashMap Map<String, Object> mapResult = new ConcurrentHashMap<>(); //2.远程调用专辑服务获取专辑基本信息-封装albumInfo属性 CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> { AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData(); Assert.notNull(albumInfo, "专辑:{}不存在", albumId); mapResult.put("albumInfo", albumInfo); return albumInfo; }, threadPoolExecutor); //3.远程调用专辑服务获取专辑统计信息-封装albumStatVo属性 CompletableFuture<Void> albumStatCompletableFuture = CompletableFuture.runAsync(() -> { AlbumStatVo albumStatVo = albumFeignClient.getAlbumStatVo(albumId).getData(); Assert.notNull(albumStatVo, "专辑统计信息:{}不存在", albumId); mapResult.put("albumStatVo", albumStatVo); }, threadPoolExecutor); //4.远程调用专辑服务获取专辑分类信息-封装baseCategoryView属性 CompletableFuture<Void> baseCategoryViewCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> { BaseCategoryView categoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData(); Assert.notNull(categoryView, "分类:{}不存在", albumInfo.getCategory3Id()); mapResult.put("baseCategoryView", categoryView); }, threadPoolExecutor); //5.远程调用用户服务获取主播信息-封装announcer属性 CompletableFuture<Void> announcerCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> { UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData(); Assert.notNull(userInfoVo, "用户:{}不存在", albumInfo.getUserId()); mapResult.put("announcer", userInfoVo); }, threadPoolExecutor); //6.组合异步任务,阻塞等待所有异步任务执行完毕 CompletableFuture.allOf( albumInfoCompletableFuture, albumStatCompletableFuture, baseCategoryViewCompletableFuture, announcerCompletableFuture ).join(); return mapResult; } } ## 1.3 获取专辑声音列表 查询时,评论数关键字属性:commentStatNum ![](assets/image-20231005140148572.png) 需求:根据专辑ID分页查询声音列表,返回当前页10条记录,对每条声音付费标识处理。**关键点:哪个声音需要展示付费标识。** **默认每个声音付费标识为:false** 判断专辑付费类型:0101-免费、**0102-vip免费、0103-付费** - 用户未登录 - 专辑类型不是免费,将除了免费可以试听声音外,将本页中其余声音付费标识设置:true - 用户登录(获取是否为VIP) - 不是VIP,或者VIP过期(除了免费以外声音全部设置为付费) - 是VIP,专辑类型为付费 需要进行处理 - 统一处理需要付费情况 - 获取用户购买情况(专辑购买,或者声音购买)得到每个声音购买状态 - 判断根据用户购买情况设置声音付费标识 ### 1.3.1 获取用户声音列表付费情况 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/87 user_paid_album 这张表记录了用户购买过的专辑 user_paid_track 这张表记录了用户购买过的声音 如果购买过,则在map 中存储数据 key=trackId value = 1 未购买value则返回0 例如: - 某专辑第一页,除了试听的声音(前五)从6-10个声音需要在用户微服务中判断5个声音是否购买过 - 用户翻到第二页,从11-20个声音同样需要判断用户购买情况 **UserInfoApiController** 控制器: java /** * 该接口提供给给专辑服务,展示声音列表动态判断付费标识 * 判断当前用户某一页中声音列表购买情况 * * @param userId 用户ID * @param albumId 专辑ID * @param needChackTrackIdList 待检查购买情况声音列表 * @return data:{声音ID:购买结果} 结果:1(已购)0(未购买) */ @Operation(summary = "判断当前用户某一页中声音列表购买情况") @PostMapping("/userInfo/userIsPaidTrack/{userId}/{albumId}") public Result<Map<Long, Integer>> userIsPaidTrack( @PathVariable Long userId, @PathVariable Long albumId, @RequestBody List<Long> needChackTrackIdList) { Map<Long, Integer> mapResult = userInfoService.userIsPaidTrack(userId, albumId, needChackTrackIdList); return Result.ok(mapResult); } **UserInfoService接口**: java /** * 判断当前用户某一页中声音列表购买情况 * * @param userId 用户ID * @param albumId 专辑ID * @param needChackTrackIdList 待检查购买情况声音列表 * @return data:{声音ID:购买结果} 结果:1(已购)0(未购买) */ Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needChackTrackIdList); **UserInfoServiceImpl实现类**: java @Autowired private UserPaidAlbumMapper userPaidAlbumMapper; @Autowired private UserPaidTrackMapper userPaidTrackMapper; /** * 判断当前用户某一页中声音列表购买情况 * * @param userId 用户ID * @param albumId 专辑ID * @param needChackTrackIdList 待检查购买情况声音列表 * @return data:{声音ID:购买结果} 结果:1(已购)0(未购买) */ @Override public Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needChackTrackIdList) { //1.根据用户ID+专辑ID查询已购专辑表 LambdaQueryWrapper<UserPaidAlbum> userPaidAlbumLambdaQueryWrapper = new LambdaQueryWrapper<>(); userPaidAlbumLambdaQueryWrapper.eq(UserPaidAlbum::getAlbumId, albumId); userPaidAlbumLambdaQueryWrapper.eq(UserPaidAlbum::getUserId, userId); Long count = userPaidAlbumMapper.selectCount(userPaidAlbumLambdaQueryWrapper); if (count > 0) { //1.1 存在专辑购买记录-用户购买过该专辑,将待检查声音列表购买情况设置为:1 Map<Long, Integer> mapResult = new HashMap<>(); for (Long trackId : needChackTrackIdList) { mapResult.put(trackId, 1); } return mapResult; } //2. 不存在专辑购买记录-根据用户ID+声音列表查询已购声音表 LambdaQueryWrapper<UserPaidTrack> userPaidTrackLambdaQueryWrapper = new LambdaQueryWrapper<>(); userPaidTrackLambdaQueryWrapper.eq(UserPaidTrack::getUserId, userId); userPaidTrackLambdaQueryWrapper.in(UserPaidTrack::getTrackId, needChackTrackIdList); //获取本页中已购买声音列表 List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(userPaidTrackLambdaQueryWrapper); //2.1 不存在声音购买记录-将待检查声音列表购买情况设置为:0 if (CollectionUtil.isEmpty(userPaidTrackList)) { Map<Long, Integer> mapResult = new HashMap<>(); for (Long trackId : needChackTrackIdList) { mapResult.put(trackId, 0); } return mapResult; } //2.2 存在声音购买记录-循环判断待检查声音ID找出哪些是已购,哪些是未购买 List<Long> userPaidTrackIdList = userPaidTrackList.stream().map(UserPaidTrack::getTrackId).collect(Collectors.toList()); Map<Long, Integer> mapResult = new HashMap<>(); for (Long needCheckTrackId : needChackTrackIdList) { //如果待检查声音ID包含在已购声音Id集合中(已购买) if (userPaidTrackIdList.contains(needCheckTrackId)) { mapResult.put(needCheckTrackId, 1); } else { //反之则未购买声音 mapResult.put(needCheckTrackId, 0); } } return mapResult; } service-user-client模块中**UserFeignClient** 远程调用接口中添加: java /** * 该接口提供给给专辑服务,展示声音列表动态判断付费标识 * 判断当前用户某一页中声音列表购买情况 * * @param userId 用户ID * @param albumId 专辑ID * @param needChackTrackIdList 待检查购买情况声音列表 * @return data:{声音ID:购买结果} 结果:1(已购)0(未购买) */ @PostMapping("/userInfo/userIsPaidTrack/{userId}/{albumId}") public Result<Map<Long, Integer>> userIsPaidTrack( @PathVariable Long userId, @PathVariable Long albumId, @RequestBody List<Long> needChackTrackIdList); **UserDegradeFeignClient熔断类**: java @Override public Result<Map<Long, Integer>> userIsPaidTrack(Long userId, Long albumId, List<Long> needChackTrackIdList) { log.error("[用户服务]提供远程调用方法userIsPaidTrack执行服务降级"); return null; } ### 1.3.2 查询专辑声音列表 在service-album 微服务中添加控制器. 获取专辑声音列表时,我们将数据都统一封装到**AlbumTrackListVo**实体类中 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/89 **TrackInfoApiController控制器** java /** * 用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识 * * @param albumId * @param page * @param limit * @return */ @GuiGuLogin(required = false) @Operation(summary = "用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识") @GetMapping("/trackInfo/findAlbumTrackPage/{albumId}/{page}/{limit}") public Result> getAlbumTrackPage(@PathVariable Long albumId, @PathVariable Integer page, @PathVariable Integer limit) { //1.获取用户ID Long userId = AuthContextHolder.getUserId(); //2.封装分页对象 Page<AlbumTrackListVo> pageInfo = new Page<>(page, limit); //3.调用业务层封装分页对象 pageInfo = trackInfoService.getAlbumTrackPage(pageInfo, albumId, userId); return Result.ok(pageInfo); } **TrackInfoService接口:** java /** * 用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识 * * @param pageInfo MP分页对象 * @param albumId 专辑ID * @param userId 用户ID * @return */ Page<AlbumTrackListVo> getAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long albumId, Long userId); **TrackInfoServiceImpl实现类:** - 根据专辑Id 获取到专辑列表, - 用户为空的时候,然后找出哪些是需要付费的声音并显示付费 isShowPaidMark=true 付费类型: 0101-免费 0102-vip付费 0103-付费 - ​ 用户不为空的时候 - 判断用户的类型 - vip 免费类型 - 如果不是vip 需要付费 - 如果是vip 但是已经过期了 也需要付费 - 需要付费 - 统一处理需要付费业务 ​ 获取到声音Id列表集合 与 用户购买声音Id集合进行比较 将用户购买的声音存储到map中,key=trackId value = 1或0; 1:表示购买过,0:表示没有购买过 如果声音列表不包含,则将显示为付费,否则判断用户是否购买过声音,没有购买过设置为付费 java @Autowired private UserFeignClient userFeignClient; /** * 分页获取专辑下声音列表,动态根据用户情况展示声音付费标识 * * @param userId 用户ID * @param albumId 专辑ID * @param pageInfo 分页对象 * @return */ @Override public Page<AlbumTrackListVo> getAlbumTrackPage(Long userId, Long albumId, Page<AlbumTrackListVo> pageInfo) { //1.根据专辑ID分页获取该专辑下包含声音列表(包含声音统计信息)-默认声音付费标识为false pageInfo = albumInfoMapper.getAlbumTrackPage(pageInfo, albumId); //2.TODO 动态判断当前页中每个声音付费标识 关键点:找出付费情况 //2.根据专辑ID查询专辑信息 AlbumInfo albumInfo = albumInfoMapper.selectById(albumId); Assert.notNull(albumInfo, "专辑:{}不存在", albumId); String payType = albumInfo.getPayType(); //3.处理用户未登录情况 if (userId == null) { //3.1 判断专辑付费类型:VIP免费(0102)或 付费(0103) 除了免费试听外声音都应该设置付费标识 if (SystemConstant.ALBUM_PAY_TYPE_VIPFREE.equals(payType) || SystemConstant.ALBUM_PAY_TYPE_REQUIRE.equals(payType)) { //3.2 获取本页中声音列表,过滤将声音序号大于免费试听集数声音付费标识设置为true pageInfo.getRecords() .stream() .filter(albumTrackVo -> albumTrackVo.getOrderNum() > albumInfo.getTracksForFree()) //过滤获取除免费试听以外声音 .collect(Collectors.toList()) .stream().forEach(albumTrackListVo -> { albumTrackListVo.setIsShowPaidMark(true); }); } } else { //4.处理用户已登录情况 //4.1 远程调用用户服务获取用户信息得到用户身份 UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(userId).getData(); Assert.notNull(userInfoVo, "用户{}不存在", userId); Integer isVip = userInfoVo.getIsVip(); //4.2 默认设置需要进一步确定购买情况标识:默认false Boolean isNeedCheckPayStatus = false; //4.2.1 如果专辑付费类型 VIP免费 if (SystemConstant.ALBUM_PAY_TYPE_VIPFREE.equals(payType)) { //当前用户为普通用户或VIP会员过期 if (isVip.intValue() == 0) { isNeedCheckPayStatus = true; } if (isVip.intValue() == 1 && new Date().after(userInfoVo.getVipExpireTime())) { isNeedCheckPayStatus = true; } } //4.2.2 如果专辑付费类型 付费 if (SystemConstant.ALBUM_PAY_TYPE_REQUIRE.equals(payType)) { //当前用户为普通用户或VIP会员过期 isNeedCheckPayStatus = true; } if (isNeedCheckPayStatus) { //4.3 进一步确定用户是否购买专辑或声音-远程调用用户服务获取本页中专辑或者声音购买情况 //本页中需要检查购买情况声音列表,过滤掉当前页免费试听声音 List<AlbumTrackListVo> needCheckTrackList = pageInfo.getRecords().stream() .filter(albumTrackListVo -> albumTrackListVo.getOrderNum() > albumInfo.getTracksForFree()) .collect(Collectors.toList()); //本页中需要检查购买情况声音ID列表,过滤掉当前页免费试听声音 List<Long> needCheckTrackIdList = needCheckTrackList.stream() .map(albumTrackListVo -> albumTrackListVo.getTrackId()) .collect(Collectors.toList()); Map<Long, Integer> userPayStatusTrackMap = userFeignClient.userIsPaidTrack(userId, albumId, needCheckTrackIdList).getData(); //4.4 循环当前页中声音列表-跟返回用户购买情况声音集合逐一判断 needCheckTrackList.stream().forEach(needCheckTrack -> { Integer payStatus = userPayStatusTrackMap.get(needCheckTrack.getTrackId()); if (payStatus.intValue() == 0) { //4.5 某个声音用户未购买,将设置付费标识 isShowPaidMark:true needCheckTrack.setIsShowPaidMark(true); } }); } } return pageInfo; } **TrackInfoMapper接口**:条件必须是当前已经开放并且是审核通过状态的数据,并且还需要获取到声音的播放量以及评论数量 java /** * 查询指定专辑下包含声音列表 * * @param pageInfo * @param albumId * @return */ Page<AlbumTrackListVo> getAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, @Param("albumId") Long albumId); **TrackInfoMapper.xml** 映射文件 动态SQL sql #分页查询指定专辑下包含声音列表(包含统计信息) select * from track_info where album_id = 307; select * from track_info where album_id = 307 and id = 16289; select * from track_stat where track_id = 16289; select ti.id trackId, ti.track_title trackTitle, ti.media_duration mediaDuration, ti.order_num orderNum, ti.create_time createTime, max(if(ts.stat_type='0701', ts.stat_num, 0)) playStatNum, max(if(ts.stat_type='0702', ts.stat_num, 0)) collectStatNum, max(if(ts.stat_type='0703', ts.stat_num, 0)) praiseStatNum, max(if(ts.stat_type='0704', ts.stat_num, 0)) commentStatNum from track_info ti left join track_stat ts on ts.track_id = ti.id where ti.album_id = 307 and ti.is_deleted = 0 group by ti.id order by ti.order_num sql <select id="getAlbumTrackPage" resultType="com.atguigu.tingshu.vo.album.AlbumTrackListVo"> select ti.id trackId, ti.track_title, ti.media_duration, ti.order_num, ti.create_time, max(if(stat.stat_type='0701', stat.stat_num, 0)) playStatNum, max(if(stat.stat_type='0702', stat.stat_num, 0)) collectStatNum, max(if(stat.stat_type='0703', stat.stat_num, 0)) praiseStatNum, max(if(stat.stat_type='0704', stat.stat_num, 0)) commentStatNum from track_info ti left join track_stat stat on stat.track_id = ti.id where ti.album_id = #{albumId} and ti.is_deleted = 0 group by ti.id order by ti.order_num </select> 测试: - 手动增加用户购买专辑记录:**user_paid_album** - 手动增加用户购买声音记录:**user_paid_track** - 手动修改VIP会员:**user_info** 情况一:未登录情况,专辑付费类型:VIP免费 付费 查看声音列表->试听声音免费+其余都需要展示付费标识 情况二:登录情况 - 普通用户 - 免费 全部免费 - VIP付费 试听声音免费+用户购买过专辑/声音,未购买展示付费标识 - 付费:试听声音免费+用户购买过专辑/声音,未购买展示付费标识 - VIP用户 - 免费 全部免费 - VIP付费 全部免费 - 付费:试听声音免费+用户购买过专辑/声音,未购买展示付费标识 时间处理: java @Data @Schema(description = "UserInfoVo") public class UserInfoVo implements Serializable { @Schema(description = "用户id") private Long id; @Schema(description = "微信openId") private String wxOpenId; @Schema(description = "nickname") private String nickname; @Schema(description = "主播用户头像图片") private String avatarUrl; @Schema(description = "用户是否为VIP会员 0:普通用户 1:VIP会员") private Integer isVip; @Schema(description = "当前VIP到期时间,即失效时间") @DateTimeFormat( pattern = "yyyy-MM-dd" ) @JsonFormat( shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "GMT+8" ) @JsonDeserialize private Date vipExpireTime; } # 2、MongoDB文档型数据库 详情见:**第6章 MongoDB入门.md** **播放进度**对应的实体类: java @Data @Schema(description = "UserListenProcess") @Document public class UserListenProcess { @Schema(description = "id") @Id private String id; @Schema(description = "用户id") private Long userId; @Schema(description = "专辑id") private Long albumId; @Schema(description = "声音id,声音id为0时,浏览的是专辑") private Long trackId; @Schema(description = "相对于音频开始位置的播放跳出位置,单位为秒。比如当前音频总时长60s,本次播放到音频第25s处就退出或者切到下一首,那么break_second就是25") private BigDecimal breakSecond; @Schema(description = "是否显示") private Integer isShow; @Schema(description = "创建时间") private Date createTime; @Schema(description = "更新时间") private Date updateTime; } # 3、声音详情 ![详情-获取播放记录进度](assets/详情-获取播放记录进度.gif) ## 3.1 获取声音播放进度 在播放声音的时候,会有触发一个获取播放进度的控制器!因为页面每隔10s会自动触发一次保存功能,会将数据写入MongoDB中。所以我们直接从MongoDB中获取到上一次声音的播放时间即可! ![](assets/tingshu013.png) > YAPI接口:http://192.168.200.6:3000/project/11/interface/api/71 在 service-user 微服务的 **UserListenProcessApiController** 控制器中添加 java /** * 获取当前用户收听声音播放进 * * @param trackId * @return */ @GuiGuLogin(required = false) @Operation(summary = "获取当前用户收听声音播放进度") @GetMapping("/userListenProcess/getTrackBreakSecond/{trackId}") public Result<BigDecimal> getTrackBreakSecond(@PathVariable Long trackId) { Long userId = AuthContextHolder.getUserId(); if (userId != null) { BigDecimal breakSecond = userListenProcessService.getTrackBreakSecond(userId, trackId); return Result.ok(breakSecond); } return Result.ok(); } **UserListenProcessService接口**: java /** * 获取当前用户收听声音播放进度 * @param userId 用户ID * @param trackId 声音ID * @return */ BigDecimal getTrackBreakSecond(Long userId, Long trackId); **UserListenProcessServiceImpl**实现类: java package com.atguigu.tingshu.user.service.impl; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.IdUtil; import com.alibaba.fastjson.JSON; import com.atguigu.tingshu.common.constant.KafkaConstant; import com.atguigu.tingshu.common.constant.RedisConstant; import com.atguigu.tingshu.common.constant.SystemConstant; import com.atguigu.tingshu.common.service.KafkaService; import com.atguigu.tingshu.common.util.MongoUtil; import com.atguigu.tingshu.model.user.UserListenProcess; import com.atguigu.tingshu.user.service.UserListenProcessService; import com.atguigu.tingshu.vo.album.TrackStatMqVo; import com.atguigu.tingshu.vo.user.UserListenProcessVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Date; import java.util.concurrent.TimeUnit; @Service @SuppressWarnings({"all"}) public class UserListenProcessServiceImpl implements UserListenProcessService { @Autowired private MongoTemplate mongoTemplate; /** * 获取当前用户收听声音播放进度 * * @param userId 用户ID * @param trackId 声音ID * @return */ @Override public BigDecimal getTrackBreakSecond(Long userId, Long trackId) { //1.构建查询条件 Query query = new Query(); query.addCriteria(Criteria.where("userId").is(userId).and("trackId").is(trackId)); //2.执行查询播放进度 UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId)); if (userListenProcess != null) { return userListenProcess.getBreakSecond(); } return new BigDecimal("0.00"); } } ![image-20240307200116030](assets/image-20240307200116030.png) ## 3.2 更新播放进度 页面每隔10秒左右更新播放进度. 1. 更新播放进度页面会传递 专辑Id ,秒数,声音Id 。后台会将这个三个属性封装到UserListenProcessVo 对象中。然后利用MongoDB进行存储到UserListenProcess实体类中! 2. 为了提高用户快速访问,将用户信息存储到缓存中。先判断当前用户Id 与 声音Id 是否存在,不存在的话才将数据存储到缓存,并且要发送消息给kafka。 3. kafka 监听消息并消费,更新专辑与声音的统计数据。 ### 3.2.1 更新MongoDB ![](assets/tingshu015.png) > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/73 在 **UserListenProcessApiController** 控制器中添加 java /** * 更新当前用户收听声音播放进度 * @param userListenProcessVo * @return */ @GuiGuLogin(required = false) @Operation(summary = "更新当前用户收听声音播放进度") @PostMapping("/userListenProcess/updateListenProcess") public Result updateListenProcess(@RequestBody UserListenProcessVo userListenProcessVo){ Long userId = AuthContextHolder.getUserId(); if (userId != null) { userListenProcessService.updateListenProcess(userId, userListenProcessVo); } return Result.ok(); } **UserListenProcessService**接口: java /** * 更新当前用户收听声音播放进度 * @param userId 用户ID * @param userListenProcessVo 播放进度信息 * @return */ void updateListenProcess(Long userId, UserListenProcessVo userListenProcessVo); **UserListenProcessServiceImpl**实现类: java @Autowired private RedisTemplate redisTemplate; @Autowired private KafkaService kafkaService; /** * 更新当前用户收听声音播放进度 * * @param userId 用户ID * @param userListenProcessVo 播放进度信息 * @return */ @Override public void updateListenProcess(Long userId, UserListenProcessVo userListenProcessVo) { //1.根据用户ID+声音ID获取播放进度 Query query = new Query(); //1.1 设置查询条件 query.addCriteria(Criteria.where("userId").is(userId).and("trackId").is(userListenProcessVo.getTrackId())); //1.2 设置查询第一条记录(避免小程序暂停后恢复播放将积压更新进度请求并发发起,导致新增多条播放进度) query.limit(1); UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId)); if (userListenProcess == null) { //2.如果播放进度不存在-新增播放进度 userListenProcess = new UserListenProcess(); userListenProcess.setUserId(userId); userListenProcess.setAlbumId(userListenProcessVo.getAlbumId()); userListenProcess.setTrackId(userListenProcessVo.getTrackId()); userListenProcess.setBreakSecond(userListenProcessVo.getBreakSecond()); userListenProcess.setIsShow(1); userListenProcess.setCreateTime(new Date()); userListenProcess.setUpdateTime(new Date()); } else { //3.如果播放进度存在-更新进度 userListenProcess.setBreakSecond(userListenProcessVo.getBreakSecond()); userListenProcess.setUpdateTime(new Date()); } mongoTemplate.save(userListenProcess, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId)); //4.采用Redis提供set k v nx ex 确保在规定时间内(24小时/当日内)播放进度统计更新1次 String key = RedisConstant.USER_TRACK_REPEAT_STAT_PREFIX + userId + ":" + userListenProcessVo.getTrackId(); long ttl = DateUtil.endOfDay(new Date()).getTime() - System.currentTimeMillis(); Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, userListenProcess.getTrackId(), ttl, TimeUnit.MILLISECONDS); if (flag) { //5.如果是首次更新播放进度,发送消息到Kafka话题 //5.1 构建更新声音播放进度MQVO对象 TrackStatMqVo mqVo = new TrackStatMqVo(); //生成业务唯一标识,消费者端(专辑服务、搜索服务)用来做幂等性处理,确保一个消息只能只被处理一次 mqVo.setBusinessNo(IdUtil.fastSimpleUUID()); mqVo.setAlbumId(userListenProcessVo.getAlbumId()); mqVo.setTrackId(userListenProcessVo.getTrackId()); mqVo.setStatType(SystemConstant.TRACK_STAT_PLAY); mqVo.setCount(1); //5.2 发送消息到更新声音统计话题中 kafkaService.sendMessage(KafkaConstant.QUEUE_TRACK_STAT_UPDATE, JSON.toJSONString(mqVo)); } } ### 3.2.2 更新MySQL统计信息 在service-album 微服务中添加监听消息: java package com.atguigu.tingshu.album.receiver; import com.alibaba.fastjson.JSON; import com.atguigu.tingshu.album.service.AlbumInfoService; import com.atguigu.tingshu.common.constant.KafkaConstant; import com.atguigu.tingshu.vo.album.TrackStatMqVo; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; /** * @author: atguigu * @create: 2023-12-19 15:31 */ @Slf4j @Component public class AlbumReceiver { @Autowired private AlbumInfoService albumInfoService; /** * 监听到更新声音统计信息 * 1.考虑消息幂等性 2.是否需要事务管理 * * @param record */ @KafkaListener(topics = KafkaConstant.QUEUE_TRACK_STAT_UPDATE) public void updateTrackStat(ConsumerRecord<String, String> record) { String value = record.value(); if (StringUtils.isNotBlank(value)) { log.info("[专辑服务],监听到更新声音统计消息:{}", value); TrackStatMqVo mqVo = JSON.parseObject(value, TrackStatMqVo.class); albumInfoService.updateTrackStat(mqVo); } } } 在**TrackInfoService** 中添加接口 java /** * MQ监听更新声音统计信息 * @param mqVo */ void updateTrackStat(TrackStatMqVo mqVo); 在**TrackInfoServiceImpl** 中添加实现 java @Autowired private AlbumStatMapper albumStatMapper; @Autowired private RedisTemplate redisTemplate; /** * MQ监听更新声音统计信息(包含:播放、收藏、点赞、评论) * * @param mqVo */ @Override @Transactional(rollbackFor = Exception.class) public void updateTrackStat(TrackStatMqVo mqVo) { //1.做幂等性处理,统一个消息只处理一次 采用set k(业务消息唯一标识) v NX EX String key = "mq:" + mqVo.getBusinessNo(); try { Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, mqVo.getBusinessNo(), 1, TimeUnit.HOURS); if (flag) { //2.更新声音统计信息 trackStatMapper.updateStat(mqVo.getTrackId(), mqVo.getStatType(), mqVo.getCount()); //3.更新专辑统计信息(播放量、评论量只要声音+1,对应专辑也得+1) if (SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())) { albumStatMapper.updateStat(mqVo.getAlbumId(), SystemConstant.ALBUM_STAT_PLAY, mqVo.getCount()); } if (SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())) { albumStatMapper.updateStat(mqVo.getAlbumId(), SystemConstant.ALBUM_STAT_COMMENT, mqVo.getCount()); } } } catch (Exception e) { //如果更新数据库发送异常,事务会进行回滚,下次再次投递消息允许继续处理统一个消息 redisTemplate.delete(key); throw new RuntimeException(e); } } **TrackStatMapper**.java 添加方法 java package com.atguigu.tingshu.album.mapper; import com.atguigu.tingshu.model.album.TrackStat; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.*; @Mapper public interface TrackStatMapper extends BaseMapper<TrackStat> { /** * 更新声音统计信息 * @param trackId 声音ID * @param statType 统计类型 * @param count 数量 */ @Update("update track_stat set stat_num = stat_num + #{count} where track_id = #{trackId} and stat_type = #{statType}") void updateStat(@Param("trackId") Long trackId, @Param("statType") String statType, @Param("count") Integer count); } **AlbumStatMapper**.java 接口添加 java package com.atguigu.tingshu.album.mapper; import com.atguigu.tingshu.model.album.AlbumStat; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Update; @Mapper public interface AlbumStatMapper extends BaseMapper<AlbumStat> { @Update("update album_stat set stat_num = stat_num + #{count} where album_id = #{albumId} and stat_type = #{statType}") void updateStat(@Param("albumId") Long albumId, @Param("statType") String statType, @Param("count") Integer count); } ## 3.3 专辑上次播放专辑声音 ![image-20231012111356796](assets/image-20231012111356796.png) 我们需要根据用户Id 来获取播放记录 ,需要获取到专辑Id 与声音Id 封装到map中然后返回数据即可! > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/83 控制器 **UserListenProcessApiController** java /** * 获取当前用户上次播放专辑声音记录 * * @return */ @GuiGuLogin @GetMapping("/userListenProcess/getLatelyTrack") public Result<Map<String, Long>> getLatelyTrack() { Long userId = AuthContextHolder.getUserId(); return Result.ok(userListenProcessService.getLatelyTrack(userId)); } **UserListenProcessService接口:** java /** * 获取用户最近一次播放记录 * @param userId * @return */ Map<String, Long> getLatelyTrack(Long userId); **UserListenProcessServiceImpl实现类**: java /** * 获取用户最近一次播放记录 * * @param userId * @return */ @Override public Map<String, Long> getLatelyTrack(Long userId) { //根据用户ID查询播放进度集合,按照更新时间倒序,获取第一条记录 //1.构建查询条件对象 Query query = new Query(); //1.1 封装用户ID查询条件 query.addCriteria(Criteria.where("userId").is(userId)); //1.2 按照更新时间排序 query.with(Sort.by(Sort.Direction.DESC, "updateTime")); //1.3 只获取第一条记录 query.limit(1); //2.执行查询 UserListenProcess listenProcess = mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId)); if (listenProcess != null) { //封装响应结果 Map<String, Long> mapResult = new HashMap<>(); mapResult.put("albumId", listenProcess.getAlbumId()); mapResult.put("trackId", listenProcess.getTrackId()); return mapResult; } return null; } ## 3.4 获取声音统计信息 ![](assets/tingshu014.png) > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/75 统计声音需要更新的数据如下,我们将数据封装到一个实体类中便于操作 java @Data @Schema(description = "用户声音统计信息") public class TrackStatVo { @Schema(description = "播放量") private Integer playStatNum; @Schema(description = "订阅量") private Integer collectStatNum; @Schema(description = "点赞量") private Integer praiseStatNum; @Schema(description = "评论数") private Integer commentStatNum; //该属性需要修改 } 在**TrackInfoApiController** 控制器中添加 java /** * 根据声音ID,获取声音统计信息 * @param trackId * @return */ @Operation(summary = "根据声音ID,获取声音统计信息") @GetMapping("/trackInfo/getTrackStatVo/{trackId}") public Result<TrackStatVo> getTrackStatVo(@PathVariable Long trackId){ return Result.ok(trackInfoService.getTrackStatVo(trackId)); } **TrackInfoService接口**: java /** * 根据声音ID,查询声音统计信息 * @param trackId * @return */ TrackStatVo getTrackStatVo(Long trackId); **TrackInfoServiceImpl实现类**: java /** * 根据声音ID,查询声音统计信息 * @param trackId * @return */ @Override public TrackStatVo getTrackStatVo(Long trackId) { return trackInfoMapper.getTrackStatVo(trackId); } **TrackInfoMapper**.java java /** * 获取声音统计信息 * @param trackId * @return */ @Select("select\n" + " track_id,\n" + " max(if(stat_type='0701', stat_num, 0)) playStatNum,\n" + " max(if(stat_type='0702', stat_num, 0)) collectStatNum,\n" + " max(if(stat_type='0703', stat_num, 0)) praiseStatNum,\n" + " max(if(stat_type='0704', stat_num, 0)) commentStatNum\n" + " from track_stat where track_id = #{trackId} and is_deleted=0\n" + "group by track_id") TrackStatVo getTrackStatVo(@Param("trackId") Long trackId); **SQL** sql # 根据声音ID查询指定声音统计信息 playStatNum collectStatNum praiseStatNum commentStatNum select track_id, max(if(stat_type='0701', stat_num, 0)) playStatNum, max(if(stat_type='0702', stat_num, 0)) collectStatNum, max(if(stat_type='0703', stat_num, 0)) praiseStatNum, max(if(stat_type='0704', stat_num, 0)) commentStatNum from track_stat where track_id = 49162 and is_deleted=0 group by track_id # 4、更新Redis排行榜 手动调用一次更新,查看排行榜。后续会整合xxl-job 分布式定时任务调度框架做定时调用。 ![详情-排行榜](assets/详情-排行榜.gif) > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/77 service-album 微服务中**BaseCategoryApiController**控制器中添加 java /** * 查询所有一级分类列表 * @return */ @Operation(summary = "查询所有一级分类列表") @GetMapping("/category/findAllCategory1") public Result> getAllCategory1() { return Result.ok(baseCategoryService.list()); } **AlbumFeignClient** java /** * 查询所有一级分类列表 * @return */ @GetMapping("/category/findAllCategory1") public Result> getAllCategory1(); **AlbumDegradeFeignClient熔断类**: java @Override public Result> getAllCategory1() { log.error("[专辑模块Feign调用]getAllCategory1异常"); return null; } > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/79 在**SearchApiController** 中添加控制器 java /** * 为定时更新首页排行榜提供调用接口 * @return */ @Operation(summary = "为定时更新首页排行榜提供调用接口") @GetMapping("/albumInfo/updateLatelyAlbumRanking") public Result updateLatelyAlbumRanking(){ searchService.updateLatelyAlbumRanking(); return Result.ok(); } **SearchService**接口: java /** * 获取不同分类下不同排序方式榜单专辑列表 */ void updateLatelyAlbumRanking(); **SearchServiceImpl实现类:** java @Autowired private RedisTemplate redisTemplate; /** * 获取不同分类下不同排序方式榜单专辑列表 */ @Override public void updateLatelyAlbumRanking() { try { //1.远程调用专辑服务获取所有1级分类列表 List<BaseCategory1> category1List = albumFeignClient.getdAllCategory1().getData(); Assert.notNull(category1List, "一级分类为空"); //2.循环遍历1级分类列表,获取该分类下5种不同排序方式榜单专辑 for (BaseCategory1 baseCategory1 : category1List) { Long category1Id = baseCategory1.getId(); //3.在处理当前1级分类中,再次循环5种不同排序方式得到具体榜单数据 //3.1 声明排序方式数组 String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"}; for (String rankingDimension : rankingDimensionArray) { //3.2 调用ES检索接口获取榜单数据 SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search( s -> s.index(INDEX_NAME) .query(q -> q.term(t -> t.field("category1Id").value(category1Id))) .sort(sort -> sort.field(f -> f.field(rankingDimension).order(SortOrder.Desc))) .size(10) , AlbumInfoIndex.class ); //3.3 获取当前分类下某个排序方式榜单专辑列表 List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits(); if (CollectionUtil.isNotEmpty(hits)) { List<AlbumInfoIndex> list = hits.stream().map(hit -> hit.source()).collect(Collectors.toList()); //4.将榜单专辑列表存入Redis-Hash中 //4.1 声明Redis排行榜Hash接口 Key 形式:前缀+1级分类ID field:排序方式 val:榜单列表 String key = RedisConstant.RANKING_KEY_PREFIX + category1Id; //4.2 将当前分类榜单数据放入Redis中 redisTemplate.opsForHash().put(key, rankingDimension, list); } } } } catch (Exception e) { log.error("[搜索服务]更新排行榜异常:{}", e); throw new RuntimeException(e); } } # 5、获取排行榜 ![image-20231012114420751](assets/image-20231012114420751.png) 点击排行榜的时候,能看到获取排行榜的地址 排行榜:key=ranking:category1Id field = hotScore 或 playStatNum 或 subscribeStatNum 或 buyStatNum 或albumCommentStatNum value=List<AlbumInfoIndexVo> ![](assets/tingshu016.png) > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/81 **SearchApiController** 控制器中添加 java /** * 获取指定1级分类下不同排序方式榜单列表-从Redis中获取 * @param category1Id * @param dimension * @return */ @Operation(summary = "获取指定1级分类下不同排序方式榜单列表") @GetMapping("/albumInfo/findRankingList/{category1Id}/{dimension}") public Result> getRankingList(@PathVariable Long category1Id, @PathVariable String dimension){ List<AlbumInfoIndex> list = searchService.getRankingList(category1Id, dimension); return Result.ok(list); } **SearchService**接口: java /** * 获取指定1级分类下不同排序方式榜单列表-从Redis中获取 * @param category1Id * @param dimension * @return */ List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension); **SearchServiceImpl实现类**: java /** * 获取指定1级分类下不同排序方式榜单列表-从Redis中获取 * * @param category1Id * @param dimension * @return */ @Override public List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension) { //1.构建分类排行榜Hash结构Key String key = RedisConstant.RANKING_KEY_PREFIX + category1Id; //2.获取Redis中hash结构中value Boolean flag = redisTemplate.opsForHash().hasKey(key, dimension); if (flag) { List<AlbumInfoIndex> list = (List) redisTemplate.opsForHash().get(key, dimension); return list; } return null; } 如何向别人介绍这个模块的设计思路】

用这个方法改造public void fillExcelData(int index, Row row) { int startNo = index * sheetSize; int endNo = Math.min(startNo + sheetSize, list.size()); int rowNo = (1 + rownum) - startNo; for (int i = startNo; i < endNo; i++) { rowNo = i > 1 ? rowNo + 1 : rowNo + i; row = sheet.createRow(rowNo); // 得到导出对象. T vo = (T) list.get(i); Collection<?> subList = null; if (isSubListValue(vo)) { subList = getListCellValue(vo); subMergedLastRowNum = subMergedLastRowNum + subList.size(); } int column = 0; for (Object[] os : fields) { Field field = (Field) os[0]; Excel excel = (Excel) os[1]; if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList)) { boolean subFirst = false; for (Object obj : subList) { if (subFirst) { rowNo++; row = sheet.createRow(rowNo); } List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class); int subIndex = 0; for (Field subField : subFields) { if (subField.isAnnotationPresent(Excel.class)) { subField.setAccessible(true); Excel attr = subField.getAnnotation(Excel.class); this.addCell(attr, row, (T) obj, subField, column + subIndex); } subIndex++; } subFirst = true; } this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size(); } else { this.addCell(excel, row, vo, field, column++); } } }}需要导出拿到list中的对象数据是AlarmData(id=77, hiveKey=1671649, errField=, general=, specialtyId=13, specialtyName=配电专业, sysName=能源互联网营销服务系统, ruleId=226, ruleName=档案信息-配电-变压器挂接到多条线路, monitorType=准确性, areaName=兰州, areaCode=27010000, countyName=城关供电服务中心, countyCode=00000000000006240108, powerstationName=中央广场营业班, powerstationCode=624010805, manageOwnerName=null, manageOwnerisc=null, manageOwnerTel=, businessOwnerName=, businessOwnerisc=, businessOwnerTel=, productionOwnerName=温金梅,常达, productionOwnerisc=90004880,90001923, productionOwnerTel=2025-03-17 16:08:17, ownerName=null, ownerisc=null, ownerTel=null, finishTime=, verifyPassTime=, verifyStatus=2, isRectify=null, isVerify=1, orderId=875, whiteListId=, applyStatus=, issuanceOwnerisc=90001923, issuanceOwnerName=常达, issuanceOwnerTel=null, isToOwner=0, createTime=2024-09-29 10:15:04, verifyTime=, updateTime=2024-09-29 10:15:04, displayList=[KeyValue(key=管理单位, value=624010805), KeyValue(key=设备标识, value=1671649), KeyValue(key=设备名称, value= 白银路箱变1#箱变 配电变压器), KeyValue(key=线路数量, value=2)], checkInfo=null, isHaveOwner=null)

[ { "id": 1, "groupName": "test-wu", "description": "sadsaffdsa", "dingId": "401021613327413104", "userName": "武福根", "email": "[email protected]", "createTime": "2023-05-31 03:34:51", "createName": "admin", "updateTime": "2023-05-31 03:34:51", "updateName": "admin" }, { "id": 2, "groupName": "test-wu", "description": "sadsaffdsa", "dingId": "322610312920535304", "userName": "俞永波", "email": "[email protected]", "createTime": "2023-05-31 03:34:51", "createName": "admin", "updateTime": "2023-05-31 03:34:51", "updateName": "admin" }, { "id": 3, "groupName": "ron-test", "description": "测试2", "dingId": "306208510726220319", "userName": "杨志亮", "email": "[email protected]", "createTime": "2023-05-31 03:44:12", "createName": "admin", "updateTime": "2023-05-31 03:44:12", "updateName": "admin" }, { "id": 4, "groupName": "ron-test", "description": "测试2", "dingId": "314447695938075811", "userName": "雍睿涵", "email": "[email protected]", "createTime": "2023-05-31 03:44:12", "createName": "admin", "updateTime": "2023-05-31 03:44:12", "updateName": "admin" }, { "id": 5, "groupName": "ron-test", "description": "测试2", "dingId": "066357011133738415", "userName": "蔡秋丹", "email": "[email protected]", "createTime": "2023-05-31 03:44:12", "createName": "admin", "updateTime": "2023-05-31 03:44:12", "updateName": "admin" }, { "id": 6, "groupName": "ron-test", "description": "测试2", "dingId": "322610312920535304", "userName": "俞永波", "email": "[email protected]", "createTime": "2023-05-31 03:34:51", "createName": "admin", "updateTime": "2023-05-31 03:34:51", "updateName": "admin" } ] stream分组,将

{ "msg": "操作成功", "code": 200, "data": [ { "searchValue": null, "createBy": "创建者", "createByName": , "createTime": "2025-03-10 10:36:20",创建时间 "updateBy": "更新者", "updateTime": "2025-03-10 10:38:18",更新时间 "remark": "备注", "params": {}, "routeId": 90980, "routeCode": "工艺路线编号", "workingHours": null, "routeName": "工艺路线名称", "routeDesc": "工艺路线说明", "enableFlag": "Y",是否启用 "attr1": null, "attr2": null, "attr3": 0, "attr4": 0, "defaultProcess": 0, "routeProcessList": [//组成工序 { "searchValue": null, "createBy": "", "createByName": null, "createTime": "2025-03-10 10:36:57", "updateBy": "", "updateTime": "2025-03-14 10:40:13", "remark": "", "params": {}, "recordId": 260083,//工艺路线ID "routeCode": "R1173", "routeName": "钣金工艺路线", "routeDesc": "111", "enableFlag": "Y", "routeRemark": "111", "routeId": 90980, "processId": 1533,//工序ID "processCode": "工序编码", "processName": "工序名称", "orderNum": 1,//序号 "orderNumber": null, "nextProcessId": 1534, "nextProcessName": "", "nextProcessCode": "", "processSection": null, "nextProcessAutoStart": null, "linkType": "SS", "defaultPreTime": 0, "defaultSufTime": 0, "colorCode": "#00AEF3", "keyFlag": "N", "attr1": null, "attr2": null, "attr3": 0, "attr4": 0, "weldingTime": null, "workingHours": null, "isEntrust": "N", "isSingle": "N", "inProcessQuantity": null, "canTransferOutQty": null, "transferOutQty": null, "waitReceiveQty": null, "waitFinishQty": null, "receivedQty": null, "repairQty": null, "workOrderId": null, "inQty": 0, "routeIds": null } ] }]}设计一个返回参数实体类

{ "_embedded": { "files": [ { "storeId": "683e49641fc00330b96aa2e5", "name": "容量测试.pdf", "originalFileName": "容量测试.pdf", "contentType": "application/octet-stream", "category": "容量", "size": 2446968, "createTime": "2025-06-03T01:01:24.966", "creator": { "sub": "901ab2d0-6e6e-4e72-a37a-07efc90fd11e", "name": "adminuser", "email": "[email protected]" }, "currentVersion": "0.0.1-init", "description": "容量文档", "tags": [ "tag1,tag2,tag3" ], "_links": { "self": { "href": "http://10.254.222.112:29020/api/v1/files/de9568a0-8c89-411c-a677-c0723d5d0eec" }, "file": { "href": "http://10.254.222.112:29020/api/v1/files/de9568a0-8c89-411c-a677-c0723d5d0eec" }, "docRepo": { "href": "http://10.254.222.112:29020/api/v1/files/de9568a0-8c89-411c-a677-c0723d5d0eec/docRepo" }, "paragraphs": { "href": "http://10.254.222.112:29020/api/v1/files/de9568a0-8c89-411c-a677-c0723d5d0eec/paragraphs" } } }, { "storeId": "683e49a41fc00330b96aa2f0", "name": "an.md", "originalFileName": "an.md", "contentType": "text/plain", "category": "干扰", "size": 95442, "createTime": "2025-06-03T01:02:28.295", "creator": { "sub": "901ab2d0-6e6e-4e72-a37a-07efc90fd11e", "name": "adminuser", "email": "[email protected]" }, "currentVersion": "0.0.1-init", "description": "干扰文档", "tags": [ "tag4,tag5,tag6" ], "_links": { "self": { "href": "http://10.254.222.112:29020/api/v1/files/90c9bced-03a2-4dbd-8b4e-0f2f3233fb71" }, "file": { "href": "http://10.254.222.112:29020/api/v1/files/90c9bced-03a2-4dbd-8b4e-0f2f3233fb71" }, "docRepo": { "href": "http://10.254.222.112:29020/api/v1/files/90c9bced-03a2-4dbd-8b4e-0f2f3233fb71/docRepo" }, "paragraphs": { "href": "http://10.254.222.112:29020/api/v1/files/90c9bced-03a2-4dbd-8b4e-0f2f3233fb71/paragraphs" } } }, { "storeId": "683e49c01fc00330b96aa2f2", "name": "an.json", "originalFileName": "an.json", "contentType": "text/plain", "category": "覆盖", "size": 27264, "createTime": "2025-06-03T01:02:56.793", "creator": { "sub": "901ab2d0-6e6e-4e72-a37a-07efc90fd11e", "name": "adminuser", "email": "[email protected]" }, "currentVersion": "0.0.1-init", "description": "覆盖文档", "tags": [ "tag7,tag8,tag9" ], "_links": { "self": { "href": "http://10.254.222.112:29020/api/v1/files/440f056a-d195-4fd4-91dc-785121b8c45f" }, "file": { "href": "http://10.254.222.112:29020/api/v1/files/440f056a-d195-4fd4-91dc-785121b8c45f" }, "docRepo": { "href": "http://10.254.222.112:29020/api/v1/files/440f056a-d195-4fd4-91dc-785121b8c45f/docRepo" }, "paragraphs": { "href": "http://10.254.222.112:29020/api/v1/files/440f056a-d195-4fd4-91dc-785121b8c45f/paragraphs" } } } ] }, "_links": { "self": { "href": "http://10.254.222.112:29020/api/v1/files/search/findAllByDocRepo_Id?id=9e8d7c6b-5a4f-3e2d-1c0b-9a8f7e6d5c4b" } } } 返回的数据是这样的 我 怎么封装特定 API 方法

大家在看

recommend-type

基于 ADS9110的隔离式数据采集 (DAQ) 系统方案(待编辑)-电路方案

描述 该“可实现最大 SNR 和采样率的 18 位 2Msps 隔离式数据采集参考设计”演示了如何应对隔离式数据采集系统设计中的典型性能限制挑战: 通过将数字隔离器引入的传播延迟降至最低,使采样率达到最大 通过有效地减轻数字隔离器引入的 ADC 采样时钟抖动,使高频交流信号链性能 (SNR) 达到最大 特性 18 位、2Msps、1 通道、差分输入、隔离式数据采集 (DAQ) 系统 利用 ADS9110 的 multiSPI:trade_mark: 数字接口实现 2MSPS 采样率,同时保持低 SPI 数据速率 源同步 SPI 数据传输模式,可将隔离器传播延迟降至最低并提高采样率 可降低隔离器引入的抖动的技术,能够将 SNR 提高 12dB(100kHz Fin,2MSPS) 经测试的设计包含理论和计算、组件选择、PCB 设计和测量结果 原理图 附件文档: 方案相关器件: ISO1541:低功耗、双向 I2C 隔离器 ISO7840:高性能 5.7kVRMS 增强型四通道数字隔离器 ISO7842:高性能 5.7kVRMS 增强型四通道数字隔离器
recommend-type

自动化图书管理系统 v7.0

自动化图书馆管理系统包含了目前图书馆管理业务的每个环节,能同时管理图书和期刊,能打印条码、书标,并制作借书证,最大藏书量在300万册以上。系统采用CNMARC标准及中图法第四版分类,具有Web检索与发布功能,条码扫描,支持一卡通,支持触摸屏。系统包括系统管理、读者管理、编目、流通、统计、查询等功能。能够在一个界面下实现图书、音像、期刊的管理,设置假期、设置暂离锁(提高安全性)、暂停某些读者的借阅权、导入导出读者、交换MARC数据、升级辅助编目库等。安装本系统前请先安装SQL 2000SQL 下载地址 http://pan.baidu.com/s/145vkr安装过程如有问题可咨询: TEL 13851381727  QQ 306404635
recommend-type

真正的VB6.0免安装,可以装U盘启动了

这个,,资源都来自CSDN大神们,在这里声明下。
recommend-type

详细说明 VC++的MFC开发串口调试助手源代码,包括数据发送,接收,显示制式等29782183com

详细说明 VC++的MFC开发串口调试助手源代码,包括数据发送,接收,显示制式等29782183com
recommend-type

文档编码批量转换UTF16toUTF8.rar

将UTF16编码格式的文件转换编码到UTF8 使用格式:U16toU8.exe [output] 如果没有output,则覆盖源文件,否则输出到output中 方便命令行使用,批量转换文件编码

最新推荐

recommend-type

C#类库封装:简化SDK调用实现多功能集成,构建地磅无人值守系统

内容概要:本文介绍了利用C#类库封装多个硬件设备的SDK接口,实现一系列复杂功能的一键式调用。具体功能包括身份证信息读取、人证识别、车牌识别(支持臻识和海康摄像头)、LED显示屏文字输出、称重数据读取、二维码扫描以及语音播报。所有功能均被封装为简单的API,极大降低了开发者的工作量和技术门槛。文中详细展示了各个功能的具体实现方式及其应用场景,如身份证读取、人证核验、车牌识别等,并最终将这些功能整合到一起,形成了一套完整的地磅称重无人值守系统解决方案。 适合人群:具有一定C#编程经验的技术人员,尤其是需要快速集成多种硬件设备SDK的应用开发者。 使用场景及目标:适用于需要高效集成多种硬件设备SDK的项目,特别是那些涉及身份验证、车辆管理、物流仓储等领域的企业级应用。通过使用这些封装好的API,可以大大缩短开发周期,降低维护成本,提高系统的稳定性和易用性。 其他说明:虽然封装后的API极大地简化了开发流程,但对于一些特殊的业务需求,仍然可能需要深入研究底层SDK。此外,在实际部署过程中,还需考虑网络环境、硬件兼容性等因素的影响。
recommend-type

基于STM32F1的BLDC无刷直流电机与PMSM永磁同步电机源码解析:传感器与无传感器驱动详解

基于STM32F1的BLDC无刷直流电机和PMSM永磁同步电机的驱动实现方法,涵盖了有传感器和无传感两种驱动方式。对于BLDC电机,有传感器部分采用霍尔传感器进行六步换相,无传感部分则利用反电动势过零点检测实现换相。对于PMSM电机,有传感器部分包括霍尔传感器和编码器的方式,无传感部分则采用了滑模观测器进行矢量控制(FOC)。文中不仅提供了详细的代码片段,还分享了许多调试经验和技巧。 适合人群:具有一定嵌入式系统和电机控制基础知识的研发人员和技术爱好者。 使用场景及目标:适用于需要深入了解和实现BLDC和PMSM电机驱动的开发者,帮助他们掌握不同传感器条件下的电机控制技术和优化方法。 其他说明:文章强调了实际调试过程中可能遇到的问题及其解决方案,如霍尔传感器的中断触发换相、反电动势过零点检测的采样时机、滑模观测器的参数调整以及编码器的ABZ解码等。
recommend-type

基于Java的跨平台图像处理软件ImageJ:多功能图像编辑与分析工具

内容概要:本文介绍了基于Java的图像处理软件ImageJ,详细阐述了它的跨平台特性、多线程处理能力及其丰富的图像处理功能。ImageJ由美国国立卫生研究院开发,能够在多种操作系统上运行,包括Windows、Mac OS、Linux等。它支持多种图像格式,如TIFF、PNG、GIF、JPEG、BMP、DICOM、FITS等,并提供图像栈功能,允许多个图像在同一窗口中进行并行处理。此外,ImageJ还提供了诸如缩放、旋转、扭曲、平滑处理等基本操作,以及区域和像素统计、间距、角度计算等高级功能。这些特性使ImageJ成为科研、医学、生物等多个领域的理想选择。 适合人群:需要进行图像处理的专业人士,如科研人员、医生、生物学家,以及对图像处理感兴趣的普通用户。 使用场景及目标:适用于需要高效处理大量图像数据的场合,特别是在科研、医学、生物学等领域。用户可以通过ImageJ进行图像的编辑、分析、处理和保存,提高工作效率。 其他说明:ImageJ不仅功能强大,而且操作简单,用户无需安装额外的运行环境即可直接使用。其基于Java的开发方式确保了不同操作系统之间的兼容性和一致性。
recommend-type

Teleport Pro教程:轻松复制网站内容

标题中提到的“复制别人网站的软件”指向的是一种能够下载整个网站或者网站的特定部分,然后在本地或者另一个服务器上重建该网站的技术或工具。这类软件通常被称作网站克隆工具或者网站镜像工具。 描述中提到了一个具体的教程网址,并提到了“天天给力信誉店”,这可能意味着有相关的教程或资源可以在这个网店中获取。但是这里并没有提供实际的教程内容,仅给出了网店的链接。需要注意的是,根据互联网法律法规,复制他人网站内容并用于自己的商业目的可能构成侵权,因此在此类工具的使用中需要谨慎,并确保遵守相关法律法规。 标签“复制 别人 网站 软件”明确指出了这个工具的主要功能,即复制他人网站的软件。 文件名称列表中列出了“Teleport Pro”,这是一款具体的网站下载工具。Teleport Pro是由Tennyson Maxwell公司开发的网站镜像工具,允许用户下载一个网站的本地副本,包括HTML页面、图片和其他资源文件。用户可以通过指定开始的URL,并设置各种选项来决定下载网站的哪些部分。该工具能够帮助开发者、设计师或内容分析人员在没有互联网连接的情况下对网站进行离线浏览和分析。 从知识点的角度来看,Teleport Pro作为一个网站克隆工具,具备以下功能和知识点: 1. 网站下载:Teleport Pro可以下载整个网站或特定网页。用户可以设定下载的深度,例如仅下载首页及其链接的页面,或者下载所有可访问的页面。 2. 断点续传:如果在下载过程中发生中断,Teleport Pro可以从中断的地方继续下载,无需重新开始。 3. 过滤器设置:用户可以根据特定的规则过滤下载内容,如排除某些文件类型或域名。 4. 网站结构分析:Teleport Pro可以分析网站的链接结构,并允许用户查看网站的结构图。 5. 自定义下载:用户可以自定义下载任务,例如仅下载图片、视频或其他特定类型的文件。 6. 多任务处理:Teleport Pro支持多线程下载,用户可以同时启动多个下载任务来提高效率。 7. 编辑和管理下载内容:Teleport Pro具备编辑网站镜像的能力,并可以查看、修改下载的文件。 8. 离线浏览:下载的网站可以在离线状态下浏览,这对于需要测试网站在不同环境下的表现的情况十分有用。 9. 备份功能:Teleport Pro可以用来备份网站,确保重要数据的安全。 在实际使用此类工具时,需要注意以下几点: - 著作权法:复制网站内容可能侵犯原作者的版权,因此在使用此类工具时,必须确保有合法权利去下载和使用目标网站的内容。 - 服务条款:许多网站的服务条款明确禁止未经授权的网站克隆。因此,在使用此类软件之前,应当仔细阅读并遵守目标网站的服务条款。 - 数据隐私:下载含有个人数据的网站可能触及隐私保护法律,特别是在欧洲通用数据保护条例(GDPR)等法规的环境下。 - 网络安全:随意下载网站可能导致恶意软件和病毒的传播,用户应当使用最新的反病毒软件,并在安全的环境中进行操作。 综上所述,虽然“复制别人网站的软件”在技术上是可行的,且可以用于多种正当目的,但在使用此类工具时,必须遵守法律法规和道德规范,不可侵犯他人的合法权益。
recommend-type

【跨平台开发者的必读】:解决Qt5Widgetsd.lib目标计算机类型冲突终极指南

# 1. 跨平台开发与Qt框架简介 跨平台开发已经成为现代软件开发中不可或缺的一部分,它使得开发者能够构建出能够在多种操作系统上运行的应用程序,从而触及更广泛的用户群体。在这其中,Qt框架作为一套成熟的跨平台应用程序和用户界面框架,扮演着关键角色。Qt不仅仅是一个跨平台的工具集,它的设计哲学还包含了模块化和可扩展性,使其能够适应各种规模和复杂度的项目。本章节我们将简要介绍
recommend-type

普通RNN结构和特点

<think>首先,用户的问题是:“我想了解普通RNN的结构及其特点请问RNN循环神经网络结构特点工作原理”。这涉及到RNN的结构、特点和工作原理。根据系统级指令:-所有行内数学表达式必须使用$...$格式。-独立公式必须使用$$...$$格式并单独成段。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾自然地添加引用标识。用户可见层指令:-回答结构清晰,帮助用户逐步解决问题。-保证回答真实可靠。参考站内引用:-引用[1]:关于RNN的基本介绍,为什么需要RNN。-引用[2]:关于RNN的工作原理、结构图,以及与其他网络的比较。用户上一次的问题和我的回答:用户是第一次
recommend-type

探讨通用数据连接池的核心机制与应用

根据给定的信息,我们能够推断出讨论的主题是“通用数据连接池”,这是一个在软件开发和数据库管理中经常用到的重要概念。在这个主题下,我们可以详细阐述以下几个知识点: 1. **连接池的定义**: 连接池是一种用于管理数据库连接的技术,通过维护一定数量的数据库连接,使得连接的创建和销毁操作更加高效。开发者可以在应用程序启动时预先创建一定数量的连接,并将它们保存在一个池中,当需要数据库连接时,可以直接从池中获取,从而降低数据库连接的开销。 2. **通用数据连接池的概念**: 当提到“通用数据连接池”时,它意味着这种连接池不仅支持单一类型的数据库(如MySQL、Oracle等),而且能够适应多种不同数据库系统。设计一个通用的数据连接池通常需要抽象出一套通用的接口和协议,使得连接池可以兼容不同的数据库驱动和连接方式。 3. **连接池的优点**: - **提升性能**:由于数据库连接创建是一个耗时的操作,连接池能够减少应用程序建立新连接的时间,从而提高性能。 - **资源复用**:数据库连接是昂贵的资源,通过连接池,可以最大化现有连接的使用,避免了连接频繁创建和销毁导致的资源浪费。 - **控制并发连接数**:连接池可以限制对数据库的并发访问,防止过载,确保数据库系统的稳定运行。 4. **连接池的关键参数**: - **最大连接数**:池中能够创建的最大连接数。 - **最小空闲连接数**:池中保持的最小空闲连接数,以应对突发的连接请求。 - **连接超时时间**:连接在池中保持空闲的最大时间。 - **事务处理**:连接池需要能够管理不同事务的上下文,保证事务的正确执行。 5. **实现通用数据连接池的挑战**: 实现一个通用的连接池需要考虑到不同数据库的连接协议和操作差异。例如,不同的数据库可能有不同的SQL方言、认证机制、连接属性设置等。因此,通用连接池需要能够提供足够的灵活性,允许用户配置特定数据库的参数。 6. **数据连接池的应用场景**: - **Web应用**:在Web应用中,为了处理大量的用户请求,数据库连接池可以保证数据库连接的快速复用。 - **批处理应用**:在需要大量读写数据库的批处理作业中,连接池有助于提高整体作业的效率。 - **微服务架构**:在微服务架构中,每个服务可能都需要与数据库进行交互,通用连接池能够帮助简化服务的数据库连接管理。 7. **常见的通用数据连接池技术**: - **Apache DBCP**:Apache的一个Java数据库连接池库。 - **C3P0**:一个提供数据库连接池和控制工具的开源Java框架。 - **HikariCP**:目前性能最好的开源Java数据库连接池之一。 - **BoneCP**:一个高性能的开源Java数据库连接池。 - **Druid**:阿里巴巴开源的一个数据库连接池,提供了对性能监控的高级特性。 8. **连接池的管理与监控**: 为了保证连接池的稳定运行,开发者需要对连接池的状态进行监控,并对其进行适当的管理。监控指标可能包括当前活动的连接数、空闲的连接数、等待获取连接的请求队列长度等。一些连接池提供了监控工具或与监控系统集成的能力。 9. **连接池的配置和优化**: 连接池的性能与连接池的配置密切相关。需要根据实际的应用负载和数据库性能来调整连接池的参数。例如,在高并发的场景下,可能需要增加连接池中连接的数量。另外,适当的线程池策略也可以帮助连接池更好地服务于多线程环境。 10. **连接池的应用案例**: 一个典型的案例是电商平台在大型促销活动期间,用户访问量激增,此时通用数据连接池能够保证数据库操作的快速响应,减少因数据库连接问题导致的系统瓶颈。 总结来说,通用数据连接池是现代软件架构中的重要组件,它通过提供高效的数据库连接管理,增强了软件系统的性能和稳定性。了解和掌握连接池的原理及实践,对于任何涉及数据库交互的应用开发都至关重要。在实现和应用连接池时,需要关注其设计的通用性、配置的合理性以及管理的有效性,确保在不同的应用场景下都能发挥出最大的效能。
recommend-type

【LabVIEW网络通讯终极指南】:7个技巧提升UDP性能和安全性

# 摘要 本文系统介绍了LabVIEW在网络通讯中的应用,尤其是针对UDP协议的研究与优化。首先,阐述了UDP的原理、特点及其在LabVIEW中的基础应用。随后,本文深入探讨了通过调整数据包大小、实现并发通信及优化缓冲区管理等技巧来优化UDP性能的LabVIEW方法。接着,文章聚焦于提升UDP通信安全性,介绍了加密技术和认证授权机制在LabVIEW中的实现,以及防御网络攻击的策略。最后,通过具体案例展示了LabVIEW在实时数据采集和远程控制系统中的高级应用,并展望了LabVIEW与UDP通讯技术的未来发展趋势及新兴技术的影响。 # 关键字 LabVIEW;UDP网络通讯;性能优化;安全性;
recommend-type

简要介绍cnn卷积神经网络

<think>首先,用户的问题是:“我想了解卷积神经网络的基本概念和原理 请问CNN卷积神经网络简介及其工作原理”。所以,我需要基于提供的引用材料,构建一个清晰的回答。 引用材料总结: - [^1]: CNN的核心思想是局部感受野、权值共享和时间或空间亚采样,提供位移、尺度、形变不变性。三大特色:局部感知、权重共享和多卷积核。 - [^2]: CNN是一种前馈神经网络,由卷积层和池化层组成,特别在图像处理方面出色。与传统多层神经网络相比,CNN加入了卷积层和池化层,使特征学习更有效。 - [^3]: CNN与全连接神经网络的区别:至少有一个卷积层提取特征;神经元局部连接和权值共享,减少参数数
recommend-type

基于ASP的深度学习网站导航系统功能详解

从给定文件中我们可以提取以下IT知识点: ### 标题知识点 #### "ASP系统篇" - **ASP技术介绍**:ASP(Active Server Pages)是一种服务器端的脚本环境,用于创建动态交互式网页。ASP允许开发者将HTML网页与服务器端脚本结合,使用VBScript或JavaScript等语言编写代码,以实现网页内容的动态生成。 - **ASP技术特点**:ASP适用于小型到中型的项目开发,它可以与数据库紧密集成,如Microsoft的Access和SQL Server。ASP支持多种组件和COM(Component Object Model)对象,使得开发者能够实现复杂的业务逻辑。 #### "深度学习网址导航系统" - **深度学习概念**:深度学习是机器学习的一个分支,通过构建深层的神经网络来模拟人类大脑的工作方式,以实现对数据的高级抽象和学习。 - **系统功能与深度学习的关系**:该标题可能意味着系统在进行网站分类、搜索优化、内容审核等方面采用了深度学习技术,以提供更智能、自动化的服务。然而,根据描述内容,实际上系统并没有直接使用深度学习技术,而是提供了一个传统的网址导航服务,可能是命名上的噱头。 ### 描述知识点 #### "全后台化管理,操作简单" - **后台管理系统的功能**:后台管理系统允许网站管理员通过Web界面执行管理任务,如内容更新、用户管理等。它通常要求界面友好,操作简便,以适应不同技术水平的用户。 #### "栏目无限分类,自由添加,排序,设定是否前台显示" - **动态网站结构设计**:这意味着网站结构具有高度的灵活性,支持创建无限层级的分类,允许管理员自由地添加、排序和设置分类的显示属性。这种设计通常需要数据库支持动态生成内容。 #### "各大搜索和站内搜索随意切换" - **搜索引擎集成**:网站可能集成了外部搜索引擎(如Google、Bing)和内部搜索引擎功能,让用户能够方便地从不同来源获取信息。 #### "网站在线提交、审阅、编辑、删除" - **内容管理系统的功能**:该系统提供了一个内容管理平台,允许用户在线提交内容,由管理员进行审阅、编辑和删除操作。 #### "站点相关信息后台动态配置" - **动态配置机制**:网站允许管理员通过后台系统动态调整各种配置信息,如网站设置、参数调整等,从而实现快速的网站维护和更新。 #### "自助网站收录,后台审阅" - **网站收录和审核机制**:该系统提供了一套自助收录流程,允许其他网站提交申请,由管理员进行后台审核,决定是否收录。 #### "网站广告在线发布" - **广告管理功能**:网站允许管理员在线发布和管理网站广告位,以实现商业变现。 #### "自动生成静态页 ver2.4.5" - **动态与静态内容**:系统支持动态内容的生成,同时也提供了静态页面的生成机制,这可能有助于提高网站加载速度和搜索引擎优化。 #### "重写后台网址分类管理" - **系统优化与重构**:提到了后台网址分类管理功能的重写,这可能意味着系统进行了一次重要的更新,以修复前一个版本的错误,并提高性能。 ### 标签知识点 #### "ASP web 源代码 源码" - **ASP程序开发**:标签表明这是一个ASP语言编写的网站源代码,可能是一个开源项目,供开发者下载、研究或部署到自己的服务器上。 ### 压缩包子文件名称列表知识点 #### "深度学习(asp)网址导航程序" - **文件内容和类型**:文件列表中提到的“深度学习(asp)网址导航程序”表明这是一个ASP语言编写的网址导航系统程序,可能包含了系统安装和配置需要的所有源文件。 通过以上分析,我们可以得出这个ASP系统是一个传统的网址导航系统,以后台管理为核心功能,并没有实际运用到深度学习技术。系统的主要功能包括对网站内容、分类、搜索引擎、广告位、以及其他网站相关信息的管理。它可能还提供了一个平台,供用户提交网址,供管理员审核并收录到导航中。源代码可能以ASP语言编写,并在文件中包含了所有必要的程序文件。