JavaWeb_LeadNews_Day4-阿里云内容安全, 雪花算法, app文章保存, 自媒体文章审核
- 阿里云内容安全
- 分布式主键策略-雪花算法
- app文章保存
- 具体实现
- 总结
- 自媒体文章审核
- 提取内容和图片
- 审核内容和图片
- 保存app端文章
- Feign远程调用降级
- 发布文章异步调用
- 自管理敏感词
- 图片文字识别
- 入门
- 集成至文字审核
- 静态文章生成, 异步
- 来源
阿里云内容安全
- 依赖
<!-- 内容安全 --> <dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId> </dependency> <dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-green</artifactId> </dependency>
- 实现
@SpringBootTest(classes = WemediaApplication.class) @RunWith(SpringRunner.class) public class AliyunTest {@Autowiredprivate GreenTextScan greenTextScan;@Autowiredprivate FileStorageService fileStorageService;@Autowiredprivate GreenImageScan greenImageScan;/*** 测试文本内容审核*/@Testpublic void testScanText() throws Exception {Map map = greenTextScan.greeTextScan("我是一个好人, 冰毒");System.out.println(map);}/*** 测试图片审核*/@Testpublic void testScanImage() throws Exception {byte[] bytes = fileStorageService.downLoadFile("http://192.168.174.133:9000/leadnews/2023/07/22/7086dfbae7d1478f9ffc7784351e8708.png");List<byte[]> list = new ArrayList<>();list.add(bytes);Map map = greenImageScan.imageScan(list);System.out.println(map);} }
- 总结
- 工具类太复杂, 云盾内容安全也没开通(要企业认证), 看看测试好了
- 测试图片审核会报错, 可能是没开通
分布式主键策略-雪花算法
- 背景
随着业务的增长,文章表可能要占用很大的物理存储空间,为了解决该问题, 后期使用数据库分片技术。将一个数据库进行拆分,通过数据库中间件连接。 如果数据库中该表选用ID自增策略,则可能产生重复的ID, 此时应该使用分布式ID生成策略来生成ID。
- 技术选型
方案 优势 劣势 redis (INCR)生成一个全局连续递增的数字类型主键 增加了一个外部组件的依赖, Redis不可以, 则整个数据库将无法再插入 UUID 全局唯一, Mysql也有UUID实现 36个字符组成, 占用空间大 snowflake算法 全局唯一, 数字类型, 存储成本低 机器规模大于1024台无法支持 - 雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。 其核心思想是:使用41bit作为毫秒数, 10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID), 12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID), 最后还有一个符号位,永远是0
app文章保存
具体实现
- feign远程调用依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 文章端远程接口
// 定义接口 @FeignClient("leadnews-article") public interface IArticleClient {@PostMapping("/api/v1/article/save")ResponseResult saveArticle(@RequestBody ArticleDto dto); }// 实现接口 @RestController public class ArticleClient implements IArticleClient {@Autowiredprivate ApArticleService apArticleService;@PostMapping("/api/v1/article/save")@Overridepublic ResponseResult saveArticle(@RequestBody ArticleDto dto) {return apArticleService.saveArticle(dto);} }
- app文章保存
// Dto public class ArticleDto extends ApArticle {/*** 文章内容*/private String content; }// Service public ResponseResult saveArticle(ArticleDto dto) {// 1. 检查参数if(dto == null){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}ApArticle apArticle = new ApArticle();BeanUtils.copyProperties(dto, apArticle);// 2. 保存或修改文章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);// 修改文章内容LambdaQueryWrapper<ApArticleContent> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ApArticleContent::getArticleId, dto.getId());ApArticleContent apArticleContent = apArticleContentMapper.selectOne(queryWrapper);apArticleContentMapper.updateById(apArticleContent);}// 3. 返回文章idreturn ResponseResult.okResult(apArticle.getId()); }
总结
dto
中没有配置表的数据, 所以保存时设置配置的默认值, 修改时不用修改文章配置表- 返回
文章id
是需要在wm_news
表中设置article_id
, 新闻表中有个文章id
字段
自媒体文章审核
提取内容和图片
- 实现思路
- 通过id查询文章
- 查询文章状态, 需要是
提交, 待审核
状态 - 获取文章内容和图片
- 具体实现
@Service @Slf4j @Transactional public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {@Autowiredprivate WmNewsMapper wmNewsMapper;/*** 自媒体文章审核* @param id*/@Overridepublic void autoScanWmNews(Integer id) throws Exception {// 查询文章WmNews wmNews = wmNewsMapper.selectById(id);if(wmNews == null){throw new RuntimeException("WmNewsAutoScanServiceImpl-文章不存在");}if(wmNews.getStatus() == WmNews.Status.SUBMIT.getCode()) {// 获取图片和文本内容Map<String, Object> textAndImages = handleTextAndImages(wmNews);}} /*** 获取图片和文本内容* @param wmNews* @return*/private Map<String, Object> handleTextAndImages(WmNews wmNews) {// 图片路径List<String> imgList = new ArrayList<>();// 文本内容StringBuilder builder = new StringBuilder();// 从自媒体文章的内容中提取文章和图片if(StringUtils.isNotBlank(wmNews.getContent())) {List<Map> maps = JSONArray.parseArray(wmNews.getContent(), Map.class);for (Map map : maps) {if (map.get("type").equals("image")) {imgList.add((String) map.get("value"));} else if (map.get("type").equals("text")) {builder.append(map.get("value"));}}}// 提取文章的封面图片if(StringUtils.isNotBlank(wmNews.getImages())) {String[] split = wmNews.getImages().split(",");imgList.addAll(Arrays.asList(split));}Map<String, Object> resultMap = new HashMap<>();resultMap.put("content", builder.toString());resultMap.put("images", imgList);return resultMap;} }
- 总结
Arrays.asList
将String数组
转化为List<String>
List
的addAll
方法可以将List<>
直接全加进来
审核内容和图片
- 具体实现
@Service @Slf4j @Transactional public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {@Autowiredprivate WmNewsMapper wmNewsMapper;@Autowiredprivate FileStorageService fileStorageService;@Autowiredprivate GreenTextScan greenTextScan;@Autowiredprivate GreenImageScan greenImageScan;/*** 自媒体文章审核* @param id*/@Overridepublic void autoScanWmNews(Integer id) throws Exception {...if(wmNews.getStatus() == WmNews.Status.SUBMIT.getCode()) {...// 2. 文章文本审核boolean isTextScan = handleTextScan((String) textAndImages.get("content"), wmNews);if(!isTextScan) return;// 3. 文章图片审核boolean isImageScan = handleImageScan((List<String>) textAndImages.get("images"), wmNews);if(!isImageScan) return;}}/*** 审核图片* @param images* @param wmNews* @return*/private boolean handleImageScan(List<String> images, WmNews wmNews) {boolean flag = true;if(images == null || images.size() == 0){return flag;}List<byte[]> list = new ArrayList<>();// 图片去重images = images.stream().distinct().collect(Collectors.toList());for (String image : images) {byte[] bytes = fileStorageService.downLoadFile(image);list.add(bytes);}try {Map map = greenImageScan.imageScan(list);if(map != null){// 违规内容if(map.get("suggestion").equals("block")){flag = false;updateWmNews(wmNews, 2, "当前文章中存在违规内容");}// 不确定内容, 需要人工审核if(map.get("suggestion").equals("review")){flag = false;updateWmNews(wmNews, 3, "当前文章中存在不确定内容");}}} catch (Exception e) {flag = false;throw new RuntimeException(e);}return flag;}/*** 审核纯文本内容* @param content* @param wmNews* @return*/private boolean handleTextScan(String content, WmNews wmNews) {boolean flag = true;content += wmNews.getTitle();if(StringUtils.isEmpty(content)){return flag;}try {Map map = greenTextScan.greeTextScan(content);if(map != null){// 违规内容if(map.get("suggestion").equals("block")){flag = false;updateWmNews(wmNews, 2, "当前文章中存在违规内容");}// 不确定内容, 需要人工审核if(map.get("suggestion").equals("review")){flag = false;updateWmNews(wmNews, 3, "当前文章中存在不确定内容");}}} catch (Exception e) {flag = false;e.printStackTrace();}return flag;}/*** 修改文章内容* @param wmNews* @param status* @param reason*/private void updateWmNews(WmNews wmNews, int status, String reason) {wmNews.setStatus((short) status);wmNews.setReason(reason);wmNewsMapper.updateById(wmNews);} }
- 总结
- 之前误以为
stream流中可以通过return+collect(Collectors.toList)修改原数据内容
, 而是stream().map((item)->{...})
中的item若是对象, 则可以通过修改对象中的属性来修改原数据内容, 而直接通过给对象赋值来修改对象则不行, 因为这相当于让item指向新的地址, 这没有什么意义, 跟原本的数据无关. - idea快捷键:
ctrl+alt+m
, 抽取方法.
- 之前误以为
保存app端文章
- 具体实现
@Service
@Slf4j
@Transactional
public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {.../*** 自媒体文章审核* @param id*/@Overridepublic void autoScanWmNews(Integer id) throws Exception {...if(wmNews.getStatus() == WmNews.Status.SUBMIT.getCode()) {...// 4. 审核通过, 保存app端相关文章ResponseResult responseResult = saveAppArticle(wmNews);if(!responseResult.getCode().equals(200)){throw new RuntimeException("WmNewsAutoScanServiceImpl-文字审核, 保存app端相关文章数据失败");}// 回填article_idwmNews.setArticleId((Long) responseResult.getData());updateWmNews(wmNews, 9, "审核成功");}}@Autowiredprivate IArticleClient articleClient;@Autowiredprivate WmChannelMapper wmChannelMapper;@Autowiredprivate WmUserMapper wmUserMapper;/*** 保存app端相关的文章数据* @param wmNews*/private ResponseResult saveAppArticle(WmNews wmNews) {ArticleDto dto = new ArticleDto();BeanUtils.copyProperties(wmNews, dto);// 文章的布局dto.setLayout(wmNews.getType());// 频道WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());if(wmChannel != null){dto.setChannelName(wmChannel.getName());}// 作者dto.setAuthorId(Long.valueOf(wmNews.getUserId()));WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());if(wmNews != null){dto.setAuthorName(wmUser.getName());}// 设置文字idif(wmNews.getArticleId() != null){dto.setId(wmNews.getArticleId());}dto.setCreatedTime(new Date());ResponseResult responseResult = articleClient.saveArticle(dto);return responseResult;}
}
- 总结
- 保存完, 要将返回的文章id, 保存为news表中的article_id
Feign远程调用降级
- 介绍
- 服务降级是服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃
- 服务降级虽然会导致请求失败,但是不会导致阻塞
- 编写降级实现类
@Component public class IArticleClientFallback implements IArticleClient {@Overridepublic ResponseResult saveArticle(ArticleDto dto) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"获取数据失败");} }
- 自媒体微服务扫描降级类
@Configuration @ComponentScan("com.heima.apis.article.fallback") public class InitConfig { }
- feign接口指定降级类
@FeignClient(value = "leadnews-article",fallback = IArticleClientFallback.class) public interface IArticleClient {... }
- 配置开启服务降级, 并指定响应时间
feign: # 开启feign对hystrix熔断降级的支持 hystrix:enabled: true # 修改调用超时时间 client:config:default:connectTimeout: 2000readTimeout: 2000
- 在文章微服务延时
public ResponseResult saveArticle(ArticleDto dto) {// 测试feign调用降级try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();} }
- 总结: 麻
发布文章异步调用
- 在自动审核上加异步注解
@Async public void autoScanWmNews(Integer id){... }
- 文章发布成功后, 调用自动审核
public ResponseResult submitNews(WmNewsDto dto) {...wmNewsAutoScanService.autoScanWmNews(wmNews.getId());return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS); }
- 自媒体启动类添加注解
@EnableAsync public class WemediaApplication {... }
自管理敏感词
- 方案介绍
方案 说明 数据库模糊查询 效率太低 String.indexOf(“”)查找 数据库量大的话也是比较慢 全文检索 分词再匹配 DFA算法 确定有穷自动机(一种数据结构) - 具体实现
private boolean handleSensitiveScan(String content, WmNews wmNews) {boolean flag = true;// 查询敏感词List<WmSensitive> wmSensitiveList = wmSensitiveMapper.selectList(Wrappers.<WmSensitive>lambdaQuery().select(WmSensitive::getSensitives));List<String> sensitiveList = wmSensitiveList.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());// 初始化敏感词库SensitiveWordUtil.initMap(sensitiveList);// 查看文章中是否包含敏感词Map<String, Integer> wordMap = SensitiveWordUtil.matchWords(content);if(wordMap.size() > 0){updateWmNews(wmNews, 2, "当前文章中存在违规内容:" + wordMap);flag = false;}return flag; }
- 总结
- 使用DFA算法实现敏感词检测, 感觉DFA算法和字典树差不多
图片文字识别
入门
- 什么是OCR
OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符, 通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程
- 方案
方案 说明 百度OCR 收费 Tesseract-OCR Google维护的开源OCR引擎, 支持Java, Python等语言调用 Test4j 封装了Tesseract-OCR, 支持Java调用 - tess4j依赖
<dependencies><dependency><groupId>net.sourceforge.tess4j</groupId><artifactId>tess4j</artifactId><version>4.1.1</version></dependency> </dependencies>
- 案例
public static void main(String[] args) throws TesseractException {// 创建实例ITesseract tesseract = new Tesseract();// 设置字体库路径tesseract.setDatapath("D:\\workspace\\tessdata");// 设置语言 简体中文tesseract.setLanguage("chi_sim");File file = new File("D:\\csdn图库\\微服务.png");// 识别图片String res = tesseract.doOCR(file).replaceAll("\\r|\\n", "-");System.out.println("识别的结果为:"+res); }
集成至文字审核
- 导入依赖, spring工厂加入工具类, 在调用工具类的服务中配置参数
- 工具类
@Getter @Setter @Component @ConfigurationProperties(prefix = "tess4j") public class Tess4jClient {private String dataPath;private String language;public String doOCR(BufferedImage image) throws TesseractException {//创建Tesseract对象ITesseract tesseract = new Tesseract();//设置字体库路径tesseract.setDatapath(dataPath);//中文识别tesseract.setLanguage(language);//执行ocr识别String result = tesseract.doOCR(image);//替换回车和tal键 使结果为一行result = result.replaceAll("\\r|\\n", "-").replaceAll(" ", "");return result;} }
- 具体实现
// 从byte[]转换为BufferedImage ByteArrayInputStream in = new ByteArrayInputStream(bytes); BufferedImage imageFile = ImageIO.read(in); // 识别图片文字 String result = tess4jClient.doOCR(imageFile); System.out.println("识别内容: " + result); // 审核是否包含敏感词 boolean isSensitive = handleSensitiveScan(result, wmNews); if(!isSensitive){return isSensitive; }
静态文章生成, 异步
@Service
@Slf4j
@Transactional
public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {@Autowiredprivate Configuration configuration;@Autowiredprivate FileStorageService fileStorageService;@Autowiredprivate ApArticleService apArticleService;/*** 生成静态文件上传到minIO中* @param article* @param content*/@Override@Asyncpublic void buildArticleToMinIO(ApArticle article, String content) {if(StringUtils.isNotBlank(content)) {// 2. 文章内容通过freemarker生成html文件Template template = null;StringWriter out = null;try {template = configuration.getTemplate("article.ftl");// 数据模型Map<String, Object> data = new HashMap<>();data.put("content", JSONArray.parseArray(content));out = new StringWriter();// 合成template.process(data, out);} catch (Exception e) {throw new RuntimeException(e);}// 3. 把html上传到minio中InputStream in = new ByteArrayInputStream(out.toString().getBytes());String path = fileStorageService.uploadHtmlFile("", article.getId()+".html", in);// 4. 修改ap_article表, 保存static_url字段apArticleService.update(Wrappers.<ApArticle>lambdaUpdate().eq(ApArticle::getId, article.getId()).set(ApArticle::getStaticUrl, path));}}
}
来源
黑马程序员. 黑马头条