2024/9/6黑马头条跟学笔记(四)

news2024/11/23 2:49:00

D4内容介绍

image-20240906101020691

阿里三方安全审核

分布式主键

异步调用

feign

熔断降级

1.自媒体文章自动审核

image-20240906100928119

1.1审核流程

image-20240906101332056

image-20240906101633320

查文章——调接口文本审核——minio下载图片图片审核——审核通过保存文章——发布

草稿1,失败2,人工3,发布9

1.2接口获取

注册阿里云,开通内容安全

image-20240906101850390

image-20240906102014686

获取akey和skey

1.3文本内容审核接口

文本垃圾内容检测:https://help.aliyun.com/document_detail/70439.html?spm=a2c4g.11186623.6.659.35ac3db3l0wV5k

图片垃圾内容检测:https://help.aliyun.com/document_detail/70292.html?spm=a2c4g.11186623.6.616.5d7d1e7f9vDRz4

图片垃圾内容Java SDK: https://help.aliyun.com/document_detail/53424.html?spm=a2c4g.11186623.6.715.c8f69b12ey35j4

image-20240906103116395

image-20240906103205077

参数1,scene场景

参数2,task (图片地址)

设置两种鉴别场景 收费两次

image-20240906103554040

提供了相应的java sdk

1.4项目集成

image-20240906103648693

1.4.1依赖导入

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.1.1</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-green</artifactId>
    <version>3.6.6</version>
</dependency>
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.9</version>
</dependency>
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>2.8.3</version>
</dependency>

1.4.2实体类拷贝

拷贝阿里云审核工具类到common模块的common包下

image-20240906104028267

image-20240906104234811

配置文件里读取并设置

image-20240906104407538

传入content 返回map,信息包含是否通过,人工审核等其他建议

1.4.3配置中心里wemedia添加配置

不会自己百度查如何申请阿里云的accesskey

aliyun:
 accessKeyId: 自填
 secret: 自填
#aliyun.scenes=porn,terrorism,ad,qrcode,live,logo
 scenes: terrorism

image-20240906104853138

1.4.4测试

现在common模块注入那俩util工具类,wemedia微服务test测试类才能使用到

image-20240906105200914

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.common.exception.ExceptionCatch,\
  com.heima.common.swagger.SwaggerConfiguration,\
  com.heima.common.swagger.Swagger2Configuration,\
  com.heima.common.aliyun.GreenImageScan,\
  com.heima.common.aliyun.GreenTextScan
 

测试类

package com.heima.wemedia;

import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.file.service.FileStorageService;
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.Arrays;
import java.util.Map;

@SpringBootTest(classes = WemediaApplication.class)
@RunWith(SpringRunner.class)
public class AliyunTest {

    @Autowired
    private GreenTextScan greenTextScan;

    @Autowired
    private GreenImageScan greenImageScan;

    @Autowired
    private FileStorageService fileStorageService;

    @Test
    public void testScanText() throws Exception {
        Map map = greenTextScan.greeTextScan("我是一个好人,冰毒");
        System.out.println(map);
    }

    @Test
    public void testScanImage() throws Exception {
        byte[] bytes = fileStorageService.downLoadFile("http://192.168.200.130:9000/leadnews/2021/04/26/ef3cbe458db249f7bd6fb4339e593e55.jpg");
        Map map = greenImageScan.imageScan(Arrays.asList(bytes));
        System.out.println(map);
    }
}

image-20240906111008764

image-20240906111747344

image-20240906111820835

不过听说增强版相较于1.0不需企业认证?但是由于教程没有且不想耗费时间钻研这段直接跳过

image-20240906113645217

image-20240906113705163

当然图片审核也跳过了

1.5分布式主键策略——雪花算法

image-20240906115201523

审核通过时进行文章保存

1.5.1表结构-article库

image-20240906115238739

1对1表关系

表数据满到快溢出来了,进行分表,不过自增id会重复

1.5.2分布式ID技术选型

image-20240906115535822

  • 第一位为0不用,为1负数,所以不用
  • 第二部分为时间戳
  • 前五位机房id 25=32,后五位为每个机房有多少个工作id 也32台,
    32个机房每个机房32台工作id,一共1024台机器
  • 序列号12位,4096个id不重复

image-20240906120453515

ID_WORKER为雪花算法

指定机房id和机器id

在nacos注册中心的article微服务里替换原先的mp配置

image-20240906120542812

image-20240906120701396

mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.heima.model.article.pojos
  global-config:
    datacenter-id: 1
    workerId: 1

2.app端文章保存接口

image-20240906131919977

为什么自媒体库也有文章id?。当修改审核通过后根据文章id修改前台的文章 aparticle

image-20240906132546609

怎么没有修改文章配置?因为默认前端会设置好配置,保存时初始化添加即可

image-20240906132453909

feign接口

image-20240906132819500

微服务间的调用使用远程客户端,操作成功且审核成功后返回文章id后续修改通过该id再次远程app端修改文章

如果没审核通过则没有aid

image-20240906133007681

实现步骤

image-20240906133204548

2.1在feign-api微服务定义接口

导入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
定义文章端的接口

接收wm文章的实体dto

image-20240906134346885

package com.heima.model.article.dtos;

import com.heima.model.article.pojos.ApArticle;
import lombok.Data;

@Data
public class ArticleDto  extends ApArticle {
    /**
     * 文章内容
     */
    private String content;
}

在service下的article服务实现接口

image-20240906200114903

package com.heima.article.feign;

import com.heima.apis.article.IArticleClient;
import com.heima.article.service.ApArticleService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@RestController
public class ArticleClient implements IArticleClient {

    @Autowired
    private ApArticleService apArticleService;

    @Override
    @PostMapping("/api/v1/article/save")
    public ResponseResult saveArticle(@RequestBody ArticleDto dto) {
        return apArticleService.saveArticle(dto);
    }

}

mapper
package com.heima.article.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.pojos.ApArticleConfig;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ApArticleConfigMapper extends BaseMapper<ApArticleConfig> {
}

修改ApArticleConfig

添加如下构造函数,当保存文章时初始化实体类,id对应其他四个默认值,不下架不删除

   public ApArticleConfig(Long articleId){
        this.articleId = articleId;
        this.isComment = true;
        this.isForward = true;
        this.isDelete = false;
        this.isDown = false;
    }

service添加保存方法

思路

  1. 没id,保存, 文章 文章配置 文章内容
  2. 有id,传来的文章id对应章内容表里的文章id 进行修改。先根据articleId查出来content实体,然后setContent在insert回去
package com.heima.article.service.impl;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

@Service
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired
    ApArticleMapper apArticleMapper;

    /**
     * 加载文章列表
     *
     * @param dto
     * @param type 1为加载更多,2为加载最新
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, Short loadtype) {

        // 参数校验

        // 判断大小是否正确
        Integer size = dto.getSize();
        if (size == null || size == 0) {
            size = Math.min(size, 50);
        }
        // 类型参数检验,既不为1,加载更多也不为2加载最新,那么就默认1加载更多
        if (!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE) && !loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)) {
            loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;
        }
        // 文章频道校验,如果不指定频道,那就是首页,直接加载最新10条
        if (StringUtils.isEmpty(dto.getTag())) {
            dto.setTag(ArticleConstants.DEFAULT_TAG);
        }
        // 时间校验。如果没有最大和最小时间,那么说明时间范围为无限,此时降序展示10条最新数据,与前面的Tag频道搭配
        if (dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
        if (dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());

        // 2.查询数据
        List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, loadtype);
        return ResponseResult.okResult(apArticles);
    }

    @Autowired
    private ApArticleConfigMapper articleConfigMapper;
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private ApArticleMapper articleMapper;

    /**
     * 保存app端相关文章
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult saveArticle(ArticleDto dto) {
        // 0. 校验参数
        if (dto == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        // 保存文章,先拷贝
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto, apArticle);
        // 1. 没由id的情况下
        if (dto.getId() == null) {
            // 保存文章
            save(apArticle);
            // 初始化文章配置实体,保存文章实体
            ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
            articleConfigMapper.insert(apArticleConfig);
            // 保存文章内容到文章内容表
            ApArticleContent apArticleContent = new ApArticleContent();
            // id+content id相当于开后门,后续修改update可以根据他找到家~
            apArticleContent.setArticleId(apArticle.getId());
            apArticleContent.setContent(dto.getContent());
            apArticleContentMapper.insert(apArticleContent);
        }
        // 2.存在id,那就是修改了
        else {
            // 直接修改文章,可能是封面?还是标题?
            articleMapper.updateById(apArticle);
            // 修改分出去的另一张文章内容表 ,根据文章id找,使用lambda
            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());
    }
}

启动postman发保存请求测试

http://localhost:51802/api/v1/article/save

{
    "title":"黑马头条项目背景22222222222222",
    "authoId":1102,
    "layout":1,
    "labels":"黑马头条",
    "publishTime":"2028-03-14T11:35:49.000Z",
    "images": "http://192.168.200.130:9000/leadnews/2021/04/26/5ddbdb5c68094ce393b08a47860da275.jpg",
    "content":"22222222222222222黑马头条项目背景,黑马头条项目背景,黑马头条项目背景,黑马头条项目背景,黑马头条项目背景"
}

image-20240906215853985

image-20240906215934366

好家伙2028年

好家伙讲义authoId,少个r

image-20240906220151253

修改测试
{
	
    "title":"黑马头条项目背景66666",
    "authoId":1102,
    "layout":1,
    "labels":"黑马头条",
    "publishTime":"2028-03-14T11:35:49.000Z",
    "images": "http://192.168.200.130:9000/leadnews/2021/04/26/5ddbdb5c68094ce393b08a47860da275.jpg",
    "content":"22222222222222222黑马头条项目背景,黑马头条项目背景,黑马头条项目背景,黑马头条项目背景,黑马头条项目背景"
}

先捋一下前面的服务

image-20240906221533057

model,common,utl模块静态引用

feign-api内部服务互相访问

gateway外部请求重定向具体服务(具体的某个网关添加拦截器id加工处理操作对象标识)

service多个业务服务

basic自定义starter(minio)后续文章保存微服务引入并调用生成静态url路径并保存内容到数据库

test测试模块包含对freemarker的使用,和minio的使用

  • 当有很多标识时使用枚举或常量

  • 共同条件放最后,独有条件前判断,过五关斩六将

  • 大文本大空间分表,减少数据库压力

  • minio存静态url,减少查询大表数据

  • 提交文章时根据类型进行 设置type并且设置从内容抽取封面图,文章and素材绑定写表,删素材必须先删文章

  • 自媒体保存文章,调用前台文章的保存,或修改,且配置表不变,只在新增时变化

  • 存素材,拦截器存id,接口拿id,以id存对应素材,和收藏关系

  • feign要调用谁,谁实现接口,到时候审核完毕则调用该保存接口

  • 微服务实现流程

    springboot

    • 配置bootstrap yml,填入远程注册中心端口

    • 写服务名(后续拿着这个去注册中心匹配),
      服务发现注册:discovery配置云端nacos的服务地址+端口

      配置注册:config,配置云端nacos IP+PORT ,指定文件后缀yml

    nacos中心

    • datasource,mp配置

    • 路由则填写每个服务的路由名

      • id 唯一标识

      • lb://leadnews-user 标识转发到后端服务的地址
        image-20240906230737469

      • predicates翻译过来是谓词?转发路由所需满足的条件?
        -Path=/user/** 必须以/user打头的请求路径

      • stripPrefix,翻译为条带前缀 等于1意为去掉路径的第一标识

        /user/** => /** 这样就能成功定位到后端的某个接口

3.自媒体文章审核

3.1表结构

image-20240907000351766

3.2实现

3.2.1思路

image-20240907000419931

自媒体审核在自媒体微服务下

3.2.2service

传自媒体文章id

package com.heima.wemedia.service;

public interface WmNewsAutoScanService {

    /**
     * 自媒体文章审核
     * @param id  自媒体文章id
     */
    public void autoScanWmNews(Integer id);
}

3.2.3实现

image-20240907092559621

content的结构是由多个对象组成,用map的key-value结构收集

package com.heima.wemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
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.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
@Transactional
public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {

    @Autowired
    private WmNewsMapper wmNewsMapper;

    /**
     * 自媒体文章审核
     *
     * @param id 自媒体文章id
     */
    @Override
    public void autoScanWmNews(Integer id) {
        // 1.对查询自媒体内容
        WmNews wmNews = wmNewsMapper.selectById(id);
        if (wmNews == null) {
            throw new RuntimeException("WmNewsAutoScanServiceImpl-文章不存在");
        }
        // 判断该文章是否是已提交状态
        if (!wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())) {
            // 对其内容抽取取出所有的Text和Image,这里我们封装一个方法
            Map<String, Object> stringObjectMap = extractTextAndImage(wmNews);
            // 2.文本审核方法.传文本和文章,后续根据审核结果修改文章数据库的状态为人工审核还是审核成功的状态
            if (scanText(stringObjectMap.get("text"), wmNews)) return;
            // 3.图片审核
            if (scanImages(wmNews, (List<String>) stringObjectMap.get("images"))) return;
            // 4.审核通过,修改状态为已发布
            ResponseResult result = saveApArticle(wmNews);
            if (result.getCode() != 200) {
                throw new RuntimeException("WmNewsAutoScanServiceImpl-文章审核,保存app端相关文章数据失败");
            }
            //回填article_id,下次进行修改会使用到
            wmNews.setArticleId((Long) result.getData());
            updateWmNews(wmNews, (short) 9, "审核成功");
        }

    }

    @Autowired
    private WmChannelMapper wmChannelMapper;
    @Autowired
    private WmUserMapper wmUserMapper;
    @Autowired
    private IArticleClient articleClient;

    private ResponseResult saveApArticle(WmNews wmNews) {
        wmNews.setStatus(WmNews.Status.PUBLISHED.getCode());
        ArticleDto articleDto = new ArticleDto();
        BeanUtils.copyProperties(wmNews, articleDto);
        // 文章布局,自媒体里叫做type,而ap端叫layout,也就是封面那玩意了其实
        articleDto.setLayout(wmNews.getType());
        // 拷贝频道名 因为分表了
        WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());
        if (wmChannel != null) {
            articleDto.setChannelName(wmChannel.getName());
        }
        // 作者名
        Integer userId = wmNews.getUserId();
        WmUser wmUser = wmUserMapper.selectById(userId);
        articleDto.setAuthorId(Long.valueOf(userId));
        if (wmUser != null) {
            articleDto.setAuthorName(wmUser.getName());
        }
        // 文章id设置同步自媒体和app端id
        if (wmNews.getArticleId() != null) {
            articleDto.setId(wmNews.getArticleId());
        }
        // 创建时间
        articleDto.setCreatedTime(new Date());
        //远程调用传递数据
        ResponseResult responseResult = articleClient.saveArticle(articleDto);
        return responseResult;

    }

    @Autowired
    private GreenImageScan greenImageScan;
    @Autowired
    private FileStorageService fileStorageService;

    private boolean scanImages(WmNews wmNews, List<String> images) {
        boolean flag = true;
        // 判断有无图片,无图片无需审核
        if (images.size() == 0 || images == null) {
            return flag;
        }

        // 依次下载图片到list里,用bytes数组存储每一张图片
        // 由于封面可能来自于内容,所以要去重
        images = images.stream().distinct().collect(Collectors.toList());
        List<byte[]> bytes = new ArrayList<>();
        for (String image : images) {
            byte[] imageBytes = fileStorageService.downLoadFile(image);
            bytes.add(imageBytes);
        }
        // 将该集合传给接口审核
        try {
            Map map = greenImageScan.imageScan(bytes);
            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;
    }

    @Autowired
    private GreenTextScan greenTextScan;

    private boolean scanText(Object text, WmNews wmNews) {
        boolean flag = true;

        if (StringUtils.isBlank(wmNews.getTitle()) || StringUtils.isBlank(text.toString())) {
            return flag;
        }

        try {
            Map map = greenTextScan.greeTextScan(wmNews.getTitle() + "-" + text.toString());
            if (map != null) {
                if (map.get("suggestion").equals("block")) {
                    flag = false;
                    updateWmNews(wmNews, (short) 2, "当前文章存在违规内容");
                } else if (map.get("suggestion").equals("review")) {
                    flag = false;
                    updateWmNews(wmNews, (short) 3, "当前文章存在不确定内容,需要人工审核");
                }
            }
        } catch (Exception e) {
            flag = false;
            e.printStackTrace();
        }

        return flag;
    }

    private void updateWmNews(WmNews wmNews, short status, String reason) {
        wmNews.setStatus((short) status);
        wmNews.setReason(reason);
        wmNewsMapper.updateById(wmNews);
    }


    private Map<String, Object> extractTextAndImage(WmNews vmNews) {
        // 抽取出来content然后加工
        String content = vmNews.getContent();
        // 初始化String容器和List<String>容器来存储Text和Images
        StringBuilder stringBuilder = new StringBuilder();
        List<String> images = new ArrayList<>();
        // 对content进行拆分,拆分出Text和Images
        // 1,从内容提取出图片和文本
        if (StringUtils.isNotBlank(content)) {
            // 由于存放的数据是json字符串,这里我们把它转为对象,且属性为多个Map,KV结构
            List<Map> maps = JSON.parseArray(content, Map.class);
            // 遍历map,如果是文本就放到字符串构造器,图片放list里
            for (Map map : maps)
                if (map.get("type").equals("text")) {
                    stringBuilder.append(map.get("value"));
                } else if (map.get("type").equals("image")) {
                    images.add(map.get("value").toString());
                }
        }
        // 2.提取文章封面图
        String covers = vmNews.getImages();
        if (StringUtils.isNotBlank(covers)) {
            // 转为字符串数组
            String[] split = covers.split(",");
            // 转为List并且addAll该list的所有内容到图片集合
            images.addAll(Arrays.asList(split));
        }
        // 3.将文本和图片存入map里后续审核
        Map<String, Object> stringObjectMap = new HashMap<>();
        stringObjectMap.put("content", stringBuilder.toString());
        stringObjectMap.put("images", images);
        return stringObjectMap;
    }
}

添加feign注解,扫描feign apis包否则iarticleClient注入,远程调用失效

image-20240907105128652
@EnableFeignClients(basePackages = "com.heima.apis")

image-20240907105406797

不报红了了

3.2.4单元测试

image-20240907112752598

选中类名ctrl + shift + T创建

指定上下文和运行类(运行测试类之前先启动aparticle服务,因为feign要调他

id填写之前创建的数据
image-20240907113443216

package com.heima.wemedia.service;

import com.heima.wemedia.WemediaApplication;
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 static org.junit.Assert.*;


@SpringBootTest(classes = WemediaApplication.class)
@RunWith(SpringRunner.class)
public class WmNewsAutoScanServiceTest {

    @Autowired
    private WmNewsAutoScanService wmNewsAutoScanService;

    @Test
    public void autoScanWmNews() {

        wmNewsAutoScanService.autoScanWmNews(6238);
    }
}

如果出现timeout的在feign模块下新建配置文件application.yml 超时时间拉满,因为你电脑太fw了加载半天

#hystrix的超时时间
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 3000000
#ribbon的超时时间
ribbon:
  ReadTimeout: 30000000
  ConnectTimeout: 300000000

image-20240907124520272

wmNews测试通过,状态为已发布,审核成功

image-20240907124631786

用户浏览端数据库增加成功

image-20240907124729124

内容配置表新增成功…

3.2.5fegin接口调用方式

image-20240907140013186

wemediea服务(发请求的人)引入apis依赖,调用article客户端发请求

引导类增加注释,开启feign指定客户端端包

4.feign调用服务降级

4.1场景

image-20240907135059777

服务自我保护,保护服务不崩溃

会导致请求失败,但是不会阻塞请求也就是说不会卡住,会闪退的意思

当服务接收不了太多请求时降级,

4.2步骤

image-20240907135253130

①实现接口,设置响应

②远程接口注解里增加属性fallback,指向降级类

③客户端配置文件开启熔断降级支持

降级类

image-20240907142026030

package com.heima.apis.article.fallback;

import com.heima.apis.article.IArticleClient;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import org.springframework.stereotype.Component;

@Component
public class IArticleClientFallback implements IArticleClient {
    @Override
    public ResponseResult saveArticle(ArticleDto dto) {
        return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"获取数据失败");
    }
}

远程接口指向降级代码类

image-20240907142211952

@FeignClient(value = "leadnews-article",fallback = IArticleClientFallback.class)

由于该降级类存在在feign模块,不在wemedia下,不能被其加载component注解失效,因此要在wemedia服务下创建初始配置类扫描apis/fallback包,

因为该降级处理类作用在客户端wemedia上也就是发请求的那一方,所以客户端要将该类加载到自身的容器里

扫描类代码

image-20240907142928422

package com.heima.wemedia.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.heima.apis.article.fallback")
public class InitConfig {
}

配置文件开启服务降级

一般不在编码的配置文件里设置 而在nacos配置中心

为什么基本配置都写在nacos呢?

  • 统一管理
  • 动态更新,不用重新编译打包部署重启,直接改配置中心
  • 版本历史回滚
  • 权限控制,不同角色配置内容不一样
  • 健康检查,配置文件坏了及时通知管理员
  • 格式灵活,yml xml。。。等等 还有许多由于不光说不练假把式就不列举了,反正用到再来

这里我们前面在feign模块下也有设置了。此时我们可以把原先的删除重新在这指定超时时间即可

image-20240907143648977

feign:
  # 开启feign对hystrix熔断降级的支持
  hystrix:
    enabled: true
  # 修改调用超时时间
  client:
    config:
      default:
        connectTimeout: 2000
        readTimeout: 2000

注意这里缩进不要复制错了,刚才报了1次错浪费了我几分钟,泪目

4.3测试

在文章保存实现类设置定时器,3000超过时间则失败,尝尝咸淡

image-20240907144151844

   try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

审核测试,由于之前创建了多条,这回还另一篇待审核的试试,你也可以修改刚才审核成功的9为1然后试试看

image-20240907144333151

运行查看

image-20240907150003670

image-20240907150029590

成功到达降级类

image-20240907150057996

记得删掉 “优化加钱代码”

image-20240907150151559

5.发布文章后异步审核

哪种好?异步的,发出审核调用后直接下一条代码接着干,同步还得搁那傻傻的等待,阻塞请求,体验感极差

5.1步骤

image-20240907150445417

①方法上加注解

②成功发布后调用审核方法

③引导类加注解开启异步调用

image-20240907151209093

6.综合测试

服务启动列表

image-20240907151303102

image-20240907151417426

测试发布&&自动审核

image-20240907151529996

image-20240907155345729

自动审核成功

如果你的内容很多,那么数据库保存 素材~文章 关系表会很慢,因此下文拿不到

image-20240907160228462

就是说前面保存的vmNews的动作还没执行完,id也没生成好,由于数据库操作是异步的

image-20240907162331833

image-20240907162444514
这俩其中之一涉及数据库的操作 没执行完此时

这一步还没搞完因此select不到,

image-20240907162555179

image-20240907162903912

我们可以给他加一个定时器1秒让他执行完前面数据库操作再来select,不过一旦涉及到了定时器,业务就开始变得臃肿了感觉

刷新再次添加大内容的文章,等待三秒刷新 审核成功,

image-20240907163621202

太捞了b样的,我一张素材库还只能添加一次。因为他是素材库和imgurl一一对应的,也就是前面比较url.size=dbUrl.size也就是说我一张图片给多次引用就产生了 dbUrl=1 urlSize=5次引用,

image-20240907164347907

不过可以给前端content内的url去重,测试保存成功

image-20240907164149717

多图保存202497

还有个槽点,这个图片添加的按钮太tm小了吧,而且一次一张,猴年马月

如果你有内容安全的接口如果输入敏感词会得到以下效果

image-20240907164745558

image-20240907164947886

敏感ak47,csdn不会检测到吧 (笑)

如果审核过程中炸了不影响发布,不过审核状态一直为待审核

image-20240907165300675

image-20240907165316142

上页面了,不过一直待审核

7.自管理的敏感词

image-20240907171422664

image-20240907170607109

意思是以什么开头,且以什么为end,说明该词为敏感词 通俗点举例,end为1,说明这个词完蛋了,end结束了

例如

  • 冰,不是end为1
  • 冰,不是end为1,
    • 下一个字符不存在,只有单个冰,则结束
    • 下一个字符毒存在,get索引,查下一个如果下一个字符存在,则获取是否为最后一个,isEnd,如果是,则该tree成立,判断为敏感词
    • 下一个字符块存在,get索引,不在该树里,且跳出结束 ,但是冰X毒怎么算?

直至检测到最后一个值不在该树里则通过

7.1dfv算法工具类

检测到则提示出现次数

7.2文章审核集成自管理敏感词

image-20240907185202638

实体

package com.heima.model.wemedia.pojos;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 敏感词信息表
 * </p>
 *
 * @author itheima
 */
@Data
@TableName("wm_sensitive")
public class WmSensitive implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 敏感词
     */
    @TableField("sensitives")
    private String sensitives;

    /**
     * 创建时间
     */
    @TableField("created_time")
    private Date createdTime;

}

mapper

package com.heima.wemedia.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmSensitive;
import org.apache.ibatis.annotations.Mapper;


@Mapper
public interface WmSensitiveMapper extends BaseMapper<WmSensitive> {
}

业务实现

  1. 查所有敏感词
  2. 初始化
  3. 调用方法,判断敏感次数

方法

    @Autowired
    private WmSensitiveMapper wmSensitiveMapper;

    private boolean handleSensitiveScan(String content, WmNews wmNews) {
        boolean flag = true;
        // 查询所有的敏感词.仅需一个字段,用map加lambda
        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(Wrappers.<WmSensitive>lambdaQuery().select(WmSensitive::getSensitives));
        // 转为字符串类型的集合,后续进行工具类调用
        List<String> sensitiveList = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
        // 初始化敏感词库
        SensitiveWordUtil.initMap(sensitiveList);
        // 开始检测返回值为敏感的词名
        Map<String, Integer> stringIntegerMap = SensitiveWordUtil.matchWords(content);
        if (stringIntegerMap.size() > 0) {
            updateWmNews(wmNews, (short) 2, "包含敏感词" + stringIntegerMap);
            flag = false;
        }
        return flag;
    }

调用地方

image-20240907192845636

if (!handleSensitiveScan((String) stringObjectMap.get("content"), wmNews))return;

测试

image-20240907193034924

image-20240907193548270

8.图片文字识别

image-20240907193856404

8.1需求

识别图片文字。过滤敏感词并返回错误信息

8.2技术点

image-20240907194017164

OCR,optical character recognition 光学字符识别,电子设备通过字符的亮暗确定形状,字符识别将形状翻译成文字的 过程

8.3Tesseract-OCR

image-20240907194150394

  • 支持UTF8编码,100多种语言识别
  • 多种输出格式,文本,html,pdf
  • 图片要清晰好分辨

8.4步骤

image-20240907194640295

image-20240907194816316

依赖

 <dependencies>
        <dependency>
            <groupId>net.sourceforge.tess4j</groupId>
            <artifactId>tess4j</artifactId>
            <version>4.1.1</version>
        </dependency>
    </dependencies>

测试类

package com.heima;

import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;

import java.io.File;

public class Main {
    public static void main(String[] args) throws TesseractException {
        // 创建实例
        Tesseract tesseract = new Tesseract();

        //设置库路径
        tesseract.setDatapath("E:\\javaOCR\\tessdata");

        //设置语言 -->简体中文
        tesseract.setLanguage("chi_sim");

        File file = new File("E:\\javaOCR\\testImg\\1.png");

        String result = tesseract.doOCR(file);
        // System.out.println("识别的结果为"+result);

        // 替换换行和回车,同时替换掉的用-连接
        System.out.println("识别的结果为"+result.replaceAll("\\r|\\n", "-"));
    }
}

测试结果

image-20240907195932903

image-20240907200219769

哎哟我去,一丝不差

8.5集成

image-20240907200406117

common模块创建工具类,封装tess4j方法

image-20240907212824866

package com.heima.common.tess4j;

import lombok.Getter;
import lombok.Setter;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
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 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;
    }

}

注册到工厂

image-20240907213307420

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.common.exception.ExceptionCatch,\
  com.heima.common.swagger.SwaggerConfiguration,\
  com.heima.common.swagger.Swagger2Configuration,\
  com.heima.common.aliyun.GreenTextScan,\
  com.heima.common.aliyun.GreenImageScan,\
  com.heima.common.tess4j.Tess4jClient

配置文件

在wemedia下yml配置补充

image-20240907213133958

tess4j:
  data-path: E:\javaOCR\tessdata
  language: chi_sim

使用

在图片审核方法中,下载图片后的步骤加入,注入使用即可(先转字节数组输入流,在通过ImageIO读取到bufferedImg)

新的检测类

private boolean scanImages(WmNews wmNews, List<String> images) {
        boolean flag = true;
        // 判断有无图片,无图片无需审核
        if (images.size() == 0 || images == null) {
            return flag;
        }
        images = images.stream().distinct().collect(Collectors.toList());
        List<byte[]> bytes = new ArrayList<>();
        try {
            // 依次下载图片到list里,用bytes数组存储每一张图片
            // 由于封面可能来自于内容,所以要去重
            for (String image : images) {
                byte[] imageBytes = fileStorageService.downLoadFile(image);
                ByteArrayInputStream stream = new ByteArrayInputStream(imageBytes);
                BufferedImage bufferedImage = ImageIO.read(stream);
                String ocrContent = tess4jClient.doOCR(bufferedImage);
                boolean scanResult = handleSensitiveScan(ocrContent, wmNews);
                if (!scanResult) {
                    return scanResult;
                }
                bytes.add(imageBytes);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


        // // 将该集合传给接口审核
        // try {
        //     Map map = greenImageScan.imageScan(bytes);
        //     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 true;
    }

发图片测试

image-20240907215331386

image-20240907215347939

识别成功

image-20240907215411632

返回

image-20240907215431223

数据库

image-20240907215503322

前台

9.文章详情静态文件生成

image-20240907215821490

代码思路

根据内容上传文件返回url的接口和实现

在保存文章业务后面增加上传静态页面

方法增加异步注解

引导类开启异步注解 和

实现类事务注解(操作了数据库)

由原先直接指定id查内容到现在 现成apdto 含有content调用

接口

package com.heima.article.service;

import com.heima.model.article.pojos.ApArticle;

public interface ArticleFreemarkerService {

    /**
     * 生成静态文件上传到minIO中
     * @param apArticle
     * @param content
     */
    public void buildArticleToMinIO(ApArticle apArticle,String content);
}

实现

package com.heima.article.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ArticleFreemarkerService;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleContent;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {
    // 注入template模板类
    @Autowired
    private Configuration configuration;
    // 文件上传类
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private ApArticleMapper apArticleMapper;

    public void buildArticleToMinIO(ApArticle apArticle,String content)  {
        // 1.获取文章内容
        if (StringUtils.isNotBlank(content)) {
            // 2.文章内容通过freemarker生成html文件
            StringWriter out = new StringWriter();
            Template template = null;
            try {
                template = configuration.getTemplate("article.ftl");
                Map<String, Object> params = new HashMap<>();
                params.put("content", JSONArray.parseArray(content));
                template.process(params, out);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

            InputStream is = new ByteArrayInputStream(out.toString().getBytes());

            // 3.把html文件上传到minio中
            String path = fileStorageService.uploadHtmlFile("",  apArticle.getId()+ ".html", is);

            // 4.修改ap_article表,保存static_url字段
            ApArticle article = new ApArticle();
            article.setId(apArticle.getId());
            article.setStaticUrl(path);
            apArticleMapper.updateById(article);


        }
    }
}

在article服务的saveArticle方法调用

image-20240907222644827

测试

重启文章服务,生成静态文件出加断点调试

image-20240907223015640

image-20240907223041605

image-20240907223211536

访问成功

10.今日作业

任务1

image-20240907223317672

微服务之间崩了没互相之间通知一下,使用seata 完成微服务一致性

任务2

image-20240907223422270

定时发布的文章审核时间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2114447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云原生之WEB应用服务器Tomcat(持续更新中)

WEB应用服务器Tomcat 1.Tomcat功能介绍1.1 安装Tomcat1.2 生成启动文件 2.结合反向代理实现Tomcat部署2.1 利用nginx反向代理实现 3.Memcached&#xff08;解决sion丢失问题&#xff09;3.1 简介3.2 安装与启动 4.session 共享服务器 1.Tomcat功能介绍 Tomcat 服务器是一个免费…

Cmake之2.6版本重要特性及用法实例(十一)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

西班牙语语法之西语前置词学习柯桥学外语到银泰对面

前置词用法大盘点 ▼ 1.a 表示行进的方向&#xff1a; Voy a la playa. 我要去海滩。 表示具体的位置&#xff1a; al norte del palacio 在宫殿北侧。 表示具体的时间&#xff1a; Me levanto a las 6. 我6点起床。 表示命令&#xff1a; ¡A trabajar! 工作&#…

linux服务器之top命令详解

top&#xff1a;系统资源管理器 top命令类似于windows的任务管理器&#xff0c;可以查看内存、cpu、进程等信息(动态查看系统资源信息)在linux系统中常用top命令查看资源性能分析工具 一、参数释义&#xff1a; 第一行 系统时间和平均负载 top&#xff1a;名称22:12:46&#…

[数据结构] 哈希结构的哈希冲突解决哈希冲突

标题&#xff1a;[C] 哈希结构的哈希冲突 && 解决哈希冲突 水墨不写bug 目录 一、引言 1.哈希 2.哈希冲突 3.哈希函数 二、解决哈希冲突 1.闭散列 I&#xff0c;线性探测 II&#xff0c;二次探测 2.开散列 正文开始&#xff1a; 一、引言 哈希表是一种非常实用而…

移动UI:分类列表页、筛选页的设计揭秘。

移动UI的列表页设计需要考虑用户体验和界面美观性&#xff0c;以下是一些建议的设计要点&#xff1a; 1. 列表项的展示&#xff1a; 列表页应该清晰地展示各个列表项&#xff0c;包括标题、副标题、缩略图等内容&#xff0c;以便用户快速浏览和识别。可以使用卡片式布局或者简…

计算机毕业设计选题推荐-班级管理系统-教务管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

多云架构下大模型训练的存储稳定性探索

一、多云架构与大模型训练的融合 &#xff08;一&#xff09;多云架构的优势与挑战 多云架构为大模型训练带来了诸多优势。首先&#xff0c;资源灵活性显著提高&#xff0c;不同的云平台可以提供不同类型的计算资源和存储服务&#xff0c;满足大模型训练在不同阶段的需求。例…

机器学习之监督学习(四)决策树和随机森林

机器学习之监督学习&#xff08;四&#xff09;决策树和随机森林 0. 文章传送1. 决策树 Decision Tree案例引入构建过程 0. 文章传送 机器学习之监督学习&#xff08;一&#xff09;线性回归、多项式回归、算法优化[巨详细笔记] 机器学习之监督学习&#xff08;二&#xff09;…

YOLOv8改进实战 | 注意力篇 | 引入ICCV2023顶会LSKNet:大选择性卷积注意力模块LSKA,助力小目标检测

YOLOv8专栏导航:点击此处跳转 前言 YOLOv8 是由 YOLOv5 的发布者 Ultralytics 发布的最新版本的 YOLO。它可用于对象检测、分割、分类任务以及大型数据集的学习,并且可以在包括 CPU 和 GPU 在内的各种硬件上执行。 YOLOv8 是一种尖端的、最先进的 (SOTA) 模型,它建立在以前…

Leetcode JAVA刷刷站(113)路径总和 ||

一、题目概述 二、思路方向 为了找出从根节点到叶子节点路径总和等于给定目标和的所有路径&#xff0c;我们可以使用深度优先搜索&#xff08;DFS&#xff09;的方法。在遍历过程中&#xff0c;我们维护一个当前路径的和以及一个列表来存储当前路径的节点值。当我们到达一个叶…

HarmonyOS云端开发(二)

文章目录 登录认证服务以及云数据库使用一创建登录注册页面二、开启认证服务二、创建数据表1.导出数据表2.使用表 进行增删改查3.查看云数据库 登录认证服务以及云数据库使用 云端开发不需要存储token,由提供的API统一验证&#xff0c;假设未登录则跳转登录页&#xff0c;已登…

uniapp,vite整合windicss

官方文档&#xff1a;https://weapp-tw.icebreaker.top/docs/quick-start/frameworks/hbuilderx 安装&#xff1a; npm i -D tailwindcss postcss autoprefixer # 初始化 tailwind.config.js 文件 npx tailwindcss initnpm i -D weapp-tailwindcss# 假如 tailwindcss 在 weap…

nginx 新建一个 PC web 站点

注意&#xff1a;进行实例之前必须完成nginx的源码编译。&#xff08;阅读往期文章完成步骤&#xff09; 1.编辑nginx的配置文件&#xff0c;修改内容 [rootlocalhost ~]# vim /usr/local/nginx/conf/nginx.conf 2.创建新目录/usr/local/nginx/conf.d/&#xff0c;编辑新文件…

JavaScript (输出,语句,语法)

目录 JavaScript 输出 使用window.alert() 写入警示框 使用document.write() 写入HTMl输入 使用 innerHTML 写入html元素 使用console.log&#xff08;&#xff09;写入 浏览器控制台 JavaScript 语句 实例 语句组成 分号 关键字 JavaScript 语法 JavaScript 标识符 …

【Hadoop|MapReduce篇】MapReduce概述

1. MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在一个Hadoop集群上。 2. Map…

项目——负载均衡OJ

项目要实现的一个整体的功能&#xff1a; 编写一个在线OJ网络服务器,只实现类似 leetcode 的题目列表在线编程功能 项目宏观结构: Oj服务器在收到提交的代码时&#xff0c;把代码负载均衡的选择发送给其他几个编译与运行服务器去编译运行代码&#xff0c;判断代码的编译运行结…

springboot+vue+mybatis计算机毕业设计气象数据分析与可视化系统+PPT+论文+讲解+售后

随着互联网技术不断地发展&#xff0c;网络与大数据成为了人们生活的一部分&#xff0c;而气象数据分析与可视化系统 作为网上应用的一个全新的体现&#xff0c;由于其特有的便捷性&#xff0c;已经被人们所接受。目前主流的气象数据分析与可视化系统 服务不仅不明确并且管理…

滑动窗口系列(同向双指针)/9.7

新的解题思路 一、三数之和的多种可能 给定一个整数数组 arr &#xff0c;以及一个整数 target 作为目标值&#xff0c;返回满足 i < j < k 且 arr[i] arr[j] arr[k] target 的元组 i, j, k 的数量。 由于结果会非常大&#xff0c;请返回 109 7 的模。 输入&…

AMEYA360:村田量产用于汽车市场的高可靠性0603M铜电极负温度系数NTC热敏电阻

株式会社村田制作所开发了0603M尺寸(0.60.30.3mm)铜电极负温度系数(NTC)热敏电阻&#xff0c;型号分别是“NCU03XH103F6SRL”和“NCU03XH103F60RL”&#xff0c;该新品扩充了NCU系列的产品尺寸阵容&#xff0c;满足了汽车市场应用中电路板的高密度化和小型化、以及对电子部件的…