编辑
2025-11-20
黑马头条
00
请注意,本文编写于 38 天前,最后修改于 9 天前,其中某些信息可能已经过时。

目录

1.自媒体环境详情
2.图片上传功能
3.发布文章功能
4.自媒体文章自动审核
5.分布式Id
5.保存文章思路
6.feign降级策略
7.自管理敏感词过滤
8.图片识别文字并且审核
9.延迟发布

1.自媒体环境详情

image.png

2.图片上传功能

image.png

实现在网关中将userId存入请求头中,在网关中添加

java
// 获取用户信息 Object userId = claimsBody.get("id"); // 存储到header中 ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> httpHeaders.add("userId",userId+"")).build(); // 重置请求 exchange.mutate().request(serverHttpRequest);

在wmedia中添加自定义拦截器并将userId存入ThreadLocal中

java
package com.heima.wemedia.interceptor; import com.heima.model.wemedia.pojos.WmUser; import com.heima.utils.thread.WmThreadLocalUtil; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Enumeration; public class WmTokenInterceptor implements HandlerInterceptor { /** * 得到header中的用户信息并且存入到当前线程中 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userId = request.getHeader("userId"); if (userId != null){ WmUser wmUser = new WmUser(); wmUser.setId(Integer.valueOf(userId)); WmThreadLocalUtil.setWmUser(wmUser); } return true; } /** * 清理线程中的数据 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { WmThreadLocalUtil.clear(); } }

编写图片上传的业务代码

java
package com.heima.wemedia.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.heima.file.service.FileStorageService; import com.heima.model.common.dtos.ResponseResult; import com.heima.model.common.enums.AppHttpCodeEnum; import com.heima.model.wemedia.pojos.WmMaterial; import com.heima.utils.thread.WmThreadLocalUtil; import com.heima.wemedia.mapper.WmMaterialMapper; import com.heima.wemedia.service.WmMaterialService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.Date; import java.util.UUID; @Slf4j @Service @Transactional public class WmMaterialServiceImpl extends ServiceImpl<WmMaterialMapper, WmMaterial> implements WmMaterialService { @Autowired private FileStorageService fileStorageService; /** * 图片上传实现类 * @param multipartFile * @return */ @Override public ResponseResult uploadPicture(MultipartFile multipartFile) { // 1.检查参数 if (multipartFile == null || multipartFile.getSize() == 0) { return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID); } // 2.上传图片到MinIO String fileId = null; String fileName = UUID.randomUUID().toString().replace("-",""); String originalFilename = multipartFile.getOriginalFilename(); String postfix = originalFilename.substring(originalFilename.lastIndexOf(".")); try { fileId = fileStorageService.uploadImgFile("",fileName+postfix,multipartFile.getInputStream()); log.info("上传图片到MinIo中{}",fileId); } catch (IOException e) { e.printStackTrace(); log.error("WmMaterialServiceImpl上传文件失败"); } // 3.将图片存储到数据库中 WmMaterial wmMaterial = new WmMaterial(); wmMaterial.setUserId(WmThreadLocalUtil.getWmUser().getId()); wmMaterial.setUrl(fileId); wmMaterial.setIsCollection((short)0); wmMaterial.setType((short)0); wmMaterial.setCreatedTime(new Date()); save(wmMaterial); // 4.返回结果 return ResponseResult.okResult(wmMaterial); } }

3.发布文章功能

image.png

实现流程

image.png

参数详情

image.png

返回参数说明

image.png

发布文章业务层代码

java
package com.heima.wemedia.service.impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.heima.common.constants.WemediaConstants; import com.heima.common.exception.CustomException; import com.heima.model.common.dtos.PageResponseResult; import com.heima.model.common.dtos.ResponseResult; import com.heima.model.common.enums.AppHttpCodeEnum; import com.heima.model.wemedia.dtos.WmNewsDto; import com.heima.model.wemedia.dtos.WmNewsPageReqDto; import com.heima.model.wemedia.pojos.WmMaterial; import com.heima.model.wemedia.pojos.WmNews; import com.heima.model.wemedia.pojos.WmNewsMaterial; import com.heima.model.wemedia.pojos.WmUser; import com.heima.utils.thread.WmThreadLocalUtil; import com.heima.wemedia.mapper.WmMaterialMapper; import com.heima.wemedia.mapper.WmNewsMapper; import com.heima.wemedia.mapper.WmNewsMaterialMapper; import com.heima.wemedia.service.WmNewsService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.servlet.View; import java.util.*; import java.util.stream.Collectors; @Service @Slf4j @Transactional public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService { private final View error; public WmNewsServiceImpl(View error) { this.error = error; } @Autowired private WmNewsMaterialMapper wmNewsMaterialMapper; @Autowired private WmMaterialMapper wmMaterialMapper; /** * 媒体端文章查询接口 * @param dto * @return */ @Override public ResponseResult findAll(WmNewsPageReqDto dto) { // 参数检查 dto.checkParam(); // 分页条件查询 IPage page = new Page<>(dto.getPage(),dto.getSize()); LambdaQueryWrapper<WmNews> lambdaQueryWrapper = new LambdaQueryWrapper<>(); // 状态精确查询 if (dto.getStatus() !=null){ lambdaQueryWrapper.eq(WmNews::getStatus,dto.getStatus()); } // 频道精确查询 if (dto.getChannelId() !=null){ lambdaQueryWrapper.eq(WmNews::getChannelId,dto.getChannelId()); } // 根据时间查询 if (dto.getBeginPubDate() !=null&& dto.getEndPubDate()!=null){ lambdaQueryWrapper.between(WmNews::getPublishTime,dto.getBeginPubDate(),dto.getEndPubDate()); } // 关键字模糊查询 if (StringUtils.isNotBlank(dto.getKeyword())){ lambdaQueryWrapper.like(WmNews::getTitle,dto.getKeyword()); } WmUser user = WmThreadLocalUtil.getWmUser(); // 当前登录人检索 lambdaQueryWrapper.eq(WmNews::getUserId,user.getId()); // 按照发布时间倒序排序 lambdaQueryWrapper.orderByDesc(WmNews::getPublishTime); page = page(page, lambdaQueryWrapper); ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)page.getTotal()); responseResult.setData(page.getRecords()); return responseResult; } /** * 文章保存修改或存为草稿实现方法 * @param dto * @return */ @Override public ResponseResult submitNews(WmNewsDto dto) { // 条件判断 if (dto == null || dto.getContent() == null){ return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID); } // 保存或修改文章 WmNews wmNews = new WmNews(); // 属性名称和类型相同能够拷贝 拷贝属性到文章类中 BeanUtils.copyProperties(dto,wmNews); if (dto.getImages() !=null){ // 将images列表转换成字符串s String imageStr = org.apache.commons.lang.StringUtils.join(dto.getImages(),","); wmNews.setImages(imageStr); } // 封面类型为-1 if (dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){ wmNews.setType(null); } saveOrUpdateWmNews(wmNews); // 判断是否为草稿,如果为草稿结束当前方法 if (dto.getStatus().equals(WmNews.Status.NORMAL.getCode())){ return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS); } // 提取图片url信息 List<String> materials = extractUrlInfo(dto.getContent()); // 提取封面url信息 // 保存文章内容图片与素材的关系 saveRelativeInfoForContent(materials,wmNews.getId()); // 保存文章封面图片与素材的关系 如果设置为自动则自动匹配封面 saveRelativeInfoForCover(dto,wmNews,materials); return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS); } /** * 保存文章封面图片与素材的关系 如果设置为自动则自动匹配封面 * 1.如果内容图片大于1小于3则设置单图 type 1 * 2.如果内容图片大于等于3则设置3图 type 3 * 3.如果内容图片没有则不设置图片 type 0 * 保存封面与文章的关系 * @param dto * @param wmNews * @param materials */ private void saveRelativeInfoForCover(WmNewsDto dto, WmNews wmNews, List<String> materials) { List<String> images = dto.getImages(); if (dto.getType() == WemediaConstants.WM_NEWS_TYPE_AUTO){ // 多图 if (materials.size() >=3){ wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE); images = materials.stream().limit(3).collect(Collectors.toList()); // 单图 } else if (materials.size()>=1) { wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE); images = materials.stream().limit(1).collect(Collectors.toList()); // 无图 } else{ wmNews.setType(WemediaConstants.WM_NEWS_NONE_IMAGE); } // 修改文章 if (images != null && images.size() > 0){ wmNews.setImages(org.apache.commons.lang.StringUtils.join(images,",")); } updateById(wmNews); if (images != null && images.size() > 0){ saveRelativeInfo(images,wmNews.getId(),WemediaConstants.WM_COVER_REFERENCE); } } } /** * 提取文章内容分中的图片信息 * @param content * @return */ private List<String> extractUrlInfo(String content) { List<Map> maps = JSON.parseArray(content,Map.class); List<String> materials = new ArrayList<>(); for (Map map : maps) { if (map.get("type") .equals( "image")){ String imageUrl= (String) map.get("value"); materials.add(imageUrl); } } return materials; } /** * 保存图片与内容的关系 * @param materials */ private void saveRelativeInfoForContent(List<String> materials,Integer newsId) { saveRelativeInfo(materials,newsId,WemediaConstants.WM_CONTENT_REFERENCE); } /** * 保存文章与图片的关系到数据库中 * @param materials * @param newsId * @param wmContentReference */ private void saveRelativeInfo(List<String> materials, Integer newsId, Short wmContentReference) { // 根据图片的url查询图片id List<WmMaterial> wmMaterials= wmMaterialMapper.selectList(Wrappers.<WmMaterial>lambdaQuery().in(WmMaterial::getUrl, materials)); // 判断素材是否有效 if (wmMaterials == null ||wmMaterials.size()==0){ throw new CustomException(AppHttpCodeEnum.MATERIALS_REFERENCE_FAIL); } // 判断数据库查询的数据和url的数据是否 if (wmMaterials.size() != materials.size()){ throw new CustomException(AppHttpCodeEnum.MATERIALS_REFERENCE_FAIL); } List<Integer> idList = wmMaterials.stream().map(WmMaterial::getId).collect(Collectors.toList()); wmNewsMaterialMapper.saveRelations(idList,newsId,wmContentReference); } private void saveOrUpdateWmNews(WmNews wmNews) { wmNews.setUserId(WmThreadLocalUtil.getWmUser().getId()); wmNews.setCreatedTime(new Date()); wmNews.setSubmitedTime(new Date()); wmNews.setEnable((short) 1); // 默认上架 if (wmNews.getId() == null){ // 保存 save(wmNews); }else { // 修改 删除文章图片已关联的关系 wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId,wmNews.getId())); updateById(wmNews); } } }

4.自媒体文章自动审核

image.png

业务流程

image.png

5.分布式Id

image.png

技术选型

image.png

雪花算法

image.png

5.保存文章思路

image.png

接口示例

image.png

业务代码

java
/** * 保存app端相关文章实现类 * @param dto * @return */ @Override public ResponseResult saveArticle(ArticleDto dto) { // 1.检查参数 if (dto == null){ return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID); } ApArticle apArticle = new ApArticle(); BeanUtils.copyProperties(dto, apArticle); // 2.判断是否存在id if (dto.getId() == null){ // 2.1 不存在id 保存文章 文章配置 文章内容 // 保存文章 save(apArticle); // 保存配置 ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId()); apArticleConfigMapper.insert(apArticleConfig); ApArticleContent apArticleContent = new ApArticleContent(); apArticleContent.setArticleId(apArticle.getId()); apArticleContent.setContent(dto.getContent()); apArticleContentMapper.insert(apArticleContent); }else{ // 2.2存在id修改文章 文章内容 // 修改文章 updateById(apArticle); // 修改文章内容 ApArticleContent apArticleContent= apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId())); apArticleContent.setContent(dto.getContent()); apArticleContentMapper.updateById(apArticleContent); } // 3.结果返回 文章id return ResponseResult.okResult(apArticle.getId()); }

编写wemedia端业务逻辑 使用feign远程调用app端的保存article的方法

java
package com.heima.wemedia.service.impl; import com.alibaba.fastjson.JSONArray; import com.heima.apis.article.IArticleClient; import com.heima.common.aliyun.GreenImageScan; import com.heima.common.aliyun.GreenTextScan; import com.heima.file.service.FileStorageService; import com.heima.model.article.dtos.ArticleDto; import com.heima.model.common.dtos.ResponseResult; import com.heima.model.wemedia.pojos.WmChannel; import com.heima.model.wemedia.pojos.WmNews; import com.heima.model.wemedia.pojos.WmUser; import com.heima.wemedia.mapper.WmChannelMapper; import com.heima.wemedia.mapper.WmNewsMapper; import com.heima.wemedia.mapper.WmUserMapper; import com.heima.wemedia.service.WmNewsAutoScanService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; @Service @Transactional @Slf4j public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService { @Autowired private WmNewsMapper wmNewsMapper; @Autowired private GreenTextScan greenTextScan; @Autowired private GreenImageScan greenImageScan; @Autowired private FileStorageService fileStorageService; @Autowired private IArticleClient articleClient; @Autowired private WmChannelMapper wmChannelMapper; @Autowired private WmUserMapper wmUserMapper; @Override public void autoScanWmNews(Integer id) { // 1.查询文章 自媒体文章 WmNews wmNews = wmNewsMapper.selectById(id); if (wmNews == null) { throw new RuntimeException("文章不存在"); } if (wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())){ // 从content中提取纯文本和图片 Map<String,Object> textAndImages = hendleTextAndImages(wmNews); // 2.审核文本内容 aliyun接口 boolean isTextScan = handleTextScan((String) textAndImages.get("content"),wmNews); if (!isTextScan)return; // 3.审核图片 boolean isImageScan = handleImageScan((List<String>) textAndImages.get("images"),wmNews); if (!isImageScan)return; // 4.保存app端的相关文章数据 ResponseResult responseResult = saveAppArticle(wmNews); if (!responseResult.getCode().equals(200)) { throw new RuntimeException("文章审核保存文章app端失败"); } // 回填数据 wmNews.setArticleId((Long)responseResult.getData()); updateWmNews(wmNews,(short)9,"审核成功"); } } /** * 保存app端的文章相关数据 * @param wmNews */ private ResponseResult saveAppArticle(WmNews wmNews) { ArticleDto articleDto = new ArticleDto(); // 属性拷贝 BeanUtils.copyProperties(wmNews,articleDto); // 文章的布局 articleDto.setLayout(wmNews.getType()); // 文章频道 WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId()); if (wmChannel!=null){ articleDto.setChannelName(wmChannel.getName()); } // 文章作者 articleDto.setAuthorId(Long.valueOf(wmNews.getUserId())); WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId()); if (wmUser!=null){ articleDto.setAuthorName(wmUser.getName()); } if (wmNews.getArticleId() != null){ // 如果媒体文章的app文章id不为空则表示之前审核成功过则将app文章id传入dto articleDto.setId(wmNews.getArticleId()); } articleDto.setCreatedTime(new Date()); ResponseResult responseResult = articleClient.saveArticle(articleDto); return responseResult; } /** * 审核图片内容 * @param images * @param wmNews * @return */ private boolean handleImageScan(List<String> images, WmNews wmNews) { boolean flag = true; // 图片为空判断 if (images == null&&images.size() == 0){ return flag; } // 图片去重 images = images.stream().distinct().collect(Collectors.toList()); List<byte[]> imageList = new ArrayList<>(); // 下载图片 minio for (String image : images) { byte[] bytes= fileStorageService.downLoadFile(image); imageList.add(bytes); } // 审核图片 try { Map map = greenImageScan.myImageScan(imageList); if (map!=null){ if (map.get("suggestion").equals("block")){ flag = false; // 审核失败 updateWmNews(wmNews,(short)2,"当前文章图片中存在违规内容"); } if (map.get("suggestion").equals("review")){ flag = false; // 不确定信息需要人工审核 updateWmNews(wmNews,(short)3,"当前文章图片存在不确定内容"); } } } catch (Exception e) { flag = false; e.printStackTrace(); } return flag; } /** * 审核纯文本内容 * @param content * @param wmNews * @return */ private boolean handleTextScan(String content, WmNews wmNews) { boolean flag = true; if ((wmNews.getTitle()+"-"+content).length() == 1){ return flag; } try { Map map = greenTextScan.myGreeTextScan(wmNews.getTitle()+"-"+content); if (map!=null){ if (map.get("suggestion").equals("block")){ flag = false; // 审核失败 updateWmNews(wmNews,(short)2,"当前文章中存在违规内容"); } if (map.get("suggestion").equals("review")){ flag = false; // 不确定信息需要人工审核 updateWmNews(wmNews,(short)3,"当前文章存在不确定内容"); } } } catch (Exception e) { flag = false; e.printStackTrace(); } return flag; } /** * 修改文章内容 * @param wmNews */ private void updateWmNews(WmNews wmNews,short status,String reason) { wmNews.setStatus(status); wmNews.setReason(reason); wmNewsMapper.updateById(wmNews); } /** * 提取文章的文本内容 * 提取文章的封面 * @param wmNews * @return */ private Map<String, Object> hendleTextAndImages(WmNews wmNews) { Map<String, Object> textAndImages = new HashMap<>(); // 1.从文章内容中提取文章文本和图片 // 存储纯文本内容 StringBuilder stringBuilder = new StringBuilder(); // 存储图片内容 List<String> images = new ArrayList<>(); if (StringUtils.isNotBlank(wmNews.getContent())){ List<Map> list = JSONArray.parseArray(wmNews.getContent(),Map.class); for (Map map : list) { if (map.get("type").equals("text")){ stringBuilder.append(map.get("value")); } if (map.get("type").equals("image")){ images.add((String) map.get("value")); } } } // 提取文章封面 if (StringUtils.isNotBlank(wmNews.getImages())){ String[] imageArray = wmNews.getImages().split(","); images.addAll(Arrays.asList(imageArray)); } textAndImages.put("content",stringBuilder.toString()); textAndImages.put("images",images); return textAndImages; } }

6.feign降级策略

image.png

7.自管理敏感词过滤

image.png

DFA算法

image.png

java
package com.heima.utils.common; import java.util.*; public class SensitiveWordUtil { public static Map<String, Object> dictionaryMap = new HashMap<>(); /** * 生成关键词字典库 * @param words * @return */ public static void initMap(Collection<String> words) { if (words == null) { System.out.println("敏感词列表不能为空"); return ; } // map初始长度words.size(),整个字典库的入口字数(小于words.size(),因为不同的词可能会有相同的首字) Map<String, Object> map = new HashMap<>(words.size()); // 遍历过程中当前层次的数据 Map<String, Object> curMap = null; Iterator<String> iterator = words.iterator(); while (iterator.hasNext()) { String word = iterator.next(); curMap = map; int len = word.length(); for (int i =0; i < len; i++) { // 遍历每个词的字 String key = String.valueOf(word.charAt(i)); // 当前字在当前层是否存在, 不存在则新建, 当前层数据指向下一个节点, 继续判断是否存在数据 Map<String, Object> wordMap = (Map<String, Object>) curMap.get(key); if (wordMap == null) { // 每个节点存在两个数据: 下一个节点和isEnd(是否结束标志) wordMap = new HashMap<>(2); wordMap.put("isEnd", "0"); curMap.put(key, wordMap); } curMap = wordMap; // 如果当前字是词的最后一个字,则将isEnd标志置1 if (i == len -1) { curMap.put("isEnd", "1"); } } } dictionaryMap = map; } /** * 搜索文本中某个文字是否匹配关键词 * @param text * @param beginIndex * @return */ private static int checkWord(String text, int beginIndex) { if (dictionaryMap == null) { throw new RuntimeException("字典不能为空"); } boolean isEnd = false; int wordLength = 0; Map<String, Object> curMap = dictionaryMap; int len = text.length(); // 从文本的第beginIndex开始匹配 for (int i = beginIndex; i < len; i++) { String key = String.valueOf(text.charAt(i)); // 获取当前key的下一个节点 curMap = (Map<String, Object>) curMap.get(key); if (curMap == null) { break; } else { wordLength ++; if ("1".equals(curMap.get("isEnd"))) { isEnd = true; } } } if (!isEnd) { wordLength = 0; } return wordLength; } /** * 获取匹配的关键词和命中次数 * @param text * @return */ public static Map<String, Integer> matchWords(String text) { Map<String, Integer> wordMap = new HashMap<>(); int len = text.length(); for (int i = 0; i < len; i++) { int wordLength = checkWord(text, i); if (wordLength > 0) { String word = text.substring(i, i + wordLength); // 添加关键词匹配次数 if (wordMap.containsKey(word)) { wordMap.put(word, wordMap.get(word) + 1); } else { wordMap.put(word, 1); } i += wordLength - 1; } } return wordMap; } public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("法轮"); list.add("法轮功"); list.add("冰毒"); initMap(list); String content="我是一个好人,并不会卖冰毒,也不操练法轮功,我真的不卖冰毒"; Map<String, Integer> map = matchWords(content); System.out.println(map); } }

8.图片识别文字并且审核

image.png

配置

image.png

java
package com.heima.common.tess4j; import lombok.Getter; import lombok.Setter; import net.sourceforge.tess4j.ITesseract; import net.sourceforge.tess4j.Tesseract; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.awt.image.BufferedImage; @Getter @Setter @Component @ConfigurationProperties(prefix = "tess4j") public class Tess4jClient { private String dataPath; private String language; public String doOCR(BufferedImage image) throws Exception { // 创建tesseract对象 ITesseract instance = new Tesseract(); // 设置字体库路径 instance.setDatapath(dataPath); // 设置语言 instance.setLanguage(language); String result = instance.doOCR(image); result = result.replaceAll("\\r\\n", "-").replaceAll(" ", ""); return result; } }

9.延迟发布

image.png

image.png

image.png

redis实现

image.png

实现思路

image.png

悲观锁和乐观锁

image.png

mybatis-plus集成乐观锁

image.png

导入相关文件测试redis

java
package com.heima.schedule.test; import com.heima.common.redis.CacheService; import com.heima.schedule.ScheduleApplication; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Set; @SpringBootTest(classes = ScheduleApplication.class) @RunWith(SpringRunner.class) public class RedisTest { @Autowired private CacheService cacheService; @Test public void testList(){ // 在list左边添加元素 cacheService.lLeftPush("list_001","hello world,redis"); // 获取list右边的元素并删除 String list = cacheService.lRightPop("list_001"); System.out.println(list); } @Test public void testZset(){ // 添加数据到Zset中 分值 // cacheService.zAdd("zset_key_001","hello_zset_001",1000); // cacheService.zAdd("zset_key_001","hello_zset_002",4000); // cacheService.zAdd("zset_key_001","hello_zset_003",3000); // cacheService.zAdd("zset_key_001","hello_zset_004",7000); Set<String> zsetKey001 = cacheService.zRangeByScore("zset_key_001", 0, 5000); System.out.println(zsetKey001); } }

业务实现

image.png

java
代码实现 package com.heima.schedule.service.impl; import com.alibaba.fastjson.JSON; import com.heima.common.constants.ScheduleConstants; import com.heima.common.redis.CacheService; import com.heima.model.schedule.dtos.Task; import com.heima.model.schedule.pojos.Taskinfo; import com.heima.model.schedule.pojos.TaskinfoLogs; import com.heima.schedule.mapper.TaskinfoLogsMapper; import com.heima.schedule.mapper.TaskinfoMapper; import com.heima.schedule.service.TaskService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Calendar; import java.util.Date; @Service @Transactional @Slf4j public class TaskServiceImpl implements TaskService { @Autowired private TaskinfoMapper taskinfoMapper; @Autowired private CacheService cacheService; @Autowired private TaskinfoLogsMapper taskinfoLogsMapper; @Override public Long addTask(Task task) { // 1.添加任务到数据库中 Boolean success = addTaskToDb(task); // 添加任务到redis if (success){ TaskToCache(task); } return task.getTaskId(); } /** * 把任务添加到redis中 * @param task */ private void TaskToCache(Task task) { String key = ScheduleConstants.TOPIC+task.getTaskType()+"_"+task.getPriority(); // 获取5分钟之后的时间 毫秒值 Calendar calendar = Calendar.getInstance(); // 增加5分钟 calendar.add(Calendar.MINUTE,5); // 将时间转换为毫秒值 Long nextScheduleTime = calendar.getTimeInMillis(); if (task.getExecuteTime() <=System.currentTimeMillis()){ // 2.1如果任务执行时间小于等于当前时间 cacheService.lLeftPush(key, JSON.toJSONString(task)); } else if (task.getExecuteTime() <= nextScheduleTime) { // 2.2如果任务执行时间大于当前时间 && 小于预设时间 存入zset cacheService.zAdd(ScheduleConstants.FUTURE+key,JSON.toJSONString(task),task.getExecuteTime()); } } /** * 添加任务到数据库中 * @param task * @return */ private Boolean addTaskToDb(Task task) { Boolean flag = false; try { // 保存任务表 Taskinfo taskinfo = new Taskinfo(); BeanUtils.copyProperties(task,taskinfo); taskinfo.setExecuteTime(new Date(task.getExecuteTime())); taskinfoMapper.insert(taskinfo); // 设置taskID task.setTaskId(taskinfo.getTaskId()); // 保存任务日志数据 TaskinfoLogs taskinfoLogs = new TaskinfoLogs(); BeanUtils.copyProperties(taskinfo,taskinfoLogs); // 乐观锁版本 初始为1 taskinfoLogs.setVersion(1); // 初始化状态 taskinfoLogs.setStatus(ScheduleConstants.SCHEDULED); taskinfoLogsMapper.insert(taskinfoLogs); flag = true; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("task数据添加失败"+e); } return flag; } }

取消任务

image.png

java
@Override public Long addTask(Task task) { // 1.添加任务到数据库中 Boolean success = addTaskToDb(task); // 添加任务到redis if (success){ TaskToCache(task); } return task.getTaskId(); } /** * 把任务添加到redis中 * @param task */ private void TaskToCache(Task task) { String key = task.getTaskType()+"_"+task.getPriority(); // 获取5分钟之后的时间 毫秒值 Calendar calendar = Calendar.getInstance(); // 增加5分钟 calendar.add(Calendar.MINUTE,5); // 将时间转换为毫秒值 Long nextScheduleTime = calendar.getTimeInMillis(); if (task.getExecuteTime() <=System.currentTimeMillis()){ // 2.1如果任务执行时间小于等于当前时间 cacheService.lLeftPush(ScheduleConstants.TOPIC+key, JSON.toJSONString(task)); } else if (task.getExecuteTime() <= nextScheduleTime) { // 2.2如果任务执行时间大于当前时间 && 小于预设时间 存入zset cacheService.zAdd(ScheduleConstants.FUTURE+key,JSON.toJSONString(task),task.getExecuteTime()); } } @Override public boolean cancelTask(Long taskId) { boolean flag = false; // 更新任务日志 删除任务 Task task = updateDb(taskId,ScheduleConstants.CANCELLED); // 删除redis的数据 if (task != null){ removeTaskFromCache(task); flag = true; } return flag; } /** * * 删除redis中的数据 * @param task */ private void removeTaskFromCache(Task task) { String key = task.getTaskType()+"_"+task.getPriority(); if (task.getExecuteTime() <= System.currentTimeMillis()){ cacheService.lRemove(ScheduleConstants.TOPIC+key,0,JSON.toJSONString(task)); }else{ cacheService.zRemove(ScheduleConstants.FUTURE+key,JSON.toJSONString(task)); } } /** * 更新任务日志 删除任务 * @param taskId * @param status * @return */ private Task updateDb(Long taskId, Integer status) { Task task = new Task(); try { // 删除任务信息 taskinfoMapper.deleteById(taskId); // 更新任务日志 TaskinfoLogs taskinfoLogs = taskinfoLogsMapper.selectById(taskId); taskinfoLogs.setStatus(status); taskinfoLogsMapper.updateById(taskinfoLogs); BeanUtils.copyProperties(taskinfoLogs,task); task.setExecuteTime(taskinfoLogs.getExecuteTime().getTime()); }catch(Exception e){ log.error("删除任务失败"+e); } return task; }

消费任务

image.png

java
/** * 按照类型和优先级拉取任务 * @param type * @param priority * @return */ @Override public Task pull(int type, int priority) { Task task = null; try { String key = type+"_"+priority; // 从redis中拉取任务 String task_Json = cacheService.lRightPop(ScheduleConstants.TOPIC+key); if (StringUtils.isNotBlank(task_Json)){ task = JSON.parseObject(task_Json,Task.class); // 修改数据库信息 updateDb(task.getTaskId(),ScheduleConstants.EXECUTED); } } catch (Exception e) { e.printStackTrace(); log.error("拉取任务异常"+e); } return task; }

未来数据定时刷新

image.png

image.png

redis通道

image.png

代码示例

java
@Scheduled(cron = "0 */1 * * * ?") // 每分钟执行 public void refresh(){ log.info("未来数据定时刷新--定时任务"); // 获取所有未来数据的集合 Set<String> futureKeys = cacheService.scan(ScheduleConstants.FUTURE+"*"); // 按照key和分值查询符合条件的数据 for (String futureKey : futureKeys) { // 获取当前数据的key String topicKey = ScheduleConstants.TOPIC+futureKey.split(ScheduleConstants.FUTURE)[1]; // 按照key和分值查找符合条件的key Set<String> tasks = cacheService.zRangeByScore(futureKey, 0, System.currentTimeMillis()); // 同步数据 if (!tasks.isEmpty()){ cacheService.refreshWithPipeline(futureKey,topicKey,tasks); log.info("成功将"+futureKey+"刷新到了"+topicKey); } } }

@EnableScheduling // 开启调度任务 启动类添加

分布式锁解决方法抢用的问题

image.png

image.png

代码实现

java
/** * 加锁 * * @param name * @param expire * @return */ public String tryLock(String name, long expire) { name = name + "_lock"; String token = UUID.randomUUID().toString(); RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory(); RedisConnection conn = factory.getConnection(); try { //参考redis命令: //set key value [EX seconds] [PX milliseconds] [NX|XX] Boolean result = conn.set( name.getBytes(), token.getBytes(), Expiration.from(expire, TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT //NX ); if (result != null && result) return token; } finally { RedisConnectionUtils.releaseConnection(conn, factory,false); } return null; }

数据库同步到redis

image.png

代码实现

java
/** * 定时同步到redis中 */ @PostConstruct // 启动服务自动调用 @Scheduled(cron = "0 */5 * * * ?") // 每5分钟执行一次 public void reloadData(){ // 清理缓存中的数值 clearCache(); // 查询符合条件的数据 小于未来5分中的数据 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE,5); List<Taskinfo> taskinfos = taskinfoMapper.selectList(Wrappers.<Taskinfo>lambdaQuery().lt(Taskinfo::getExecuteTime, calendar.getTime())); // 将数据同步到redis中 if (taskinfos!=null&&taskinfos.size()>0){ for (Taskinfo taskinfo : taskinfos) { Task task = new Task(); BeanUtils.copyProperties(taskinfo,task); task.setExecuteTime(taskinfo.getExecuteTime().getTime()); TaskToCache(task); } } log.info("数据库中的数据同步到了redis"); } public void clearCache(){ // 查询所有的key Set<String> topicKeys = cacheService.scan(ScheduleConstants.TOPIC+"*"); Set<String> futureKeys = cacheService.scan(ScheduleConstants.FUTURE+"*"); // 清理缓存中的数据 cacheService.delete(topicKeys); cacheService.delete(futureKeys); }

本文作者:钱小杰

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!