当前位置: 首页 > news >正文

【Spring Cloud】新闻头条微服务项目:文章内容安全审核(新增DFA+OCR过滤敏感词需求)

8420b26844034fab91b6df661ae68671.png

个人简介: 

> 📦个人主页:赵四司机
> 🏆学习方向:JAVA后端开发 
> ⏰往期文章:SpringBoot项目整合微信支付
> 🔔博主推荐网站:牛客网 刷题|面试|找工作神器
> 📣种一棵树最好的时间是十年前,其次是现在!
> 💖喜欢的话麻烦点点关注喔,你们的支持是我的最大动力。

前言:

最近在做一个基于SpringCloud+Springboot+Docker的新闻头条微服务项目,用的是黑马的教程,现在项目开发进入了尾声,我打算通过写文章的形式进行梳理一遍,并且会将梳理过程中发现的Bug进行修复,有需要改进的地方我也会继续做出改进。这一系列的文章我将会放入微服务项目专栏中,这个项目适合刚接触微服务的人作为练手项目,假如你对这个项目感兴趣你可以订阅我的专栏进行查看,需要资料可以私信我,当然要是能给我点个小小的关注就更好了,你们的支持是我最大的动力。

 如果你想要一个可以系统学习的网站,那么我推荐的是牛客网,个人感觉用着还是不错的,页面很整洁,而且内容也很全面,语法练习,算法题练习,面试知识汇总等等都有,论坛也很活跃,传送门链接:牛客刷题神器

目录

一:需求新增

1.需求分析

2.技术选型

(1)文本

(2)图片

3.DFA

(1)原理分析

(2)代码实现

4.OCR

(1)原理分析

(2)代码实现

二:完整审核流程

1.审核类

2.功能调用


一:需求新增

1.需求分析

        前面我们的想法是调用腾讯云的内容安全对文章的文本信息及图片进行安全检测,通过则将文章发布。但是这时候有个缺点,就是审核不能过滤一些敏感词,比如针孔摄像头、高额贷款、论文代写等,我们这时候就需要自己维护一套敏感词系统用来对文章进行检测。对于这套敏感词系统,我们可以在管理端进行删除增添的操作。

        除了要对文本进行敏感词过滤之外,由于图片中可能还会存在敏感词,所以我们也需要对图片中的文字进行检测,使用到的技术是OCR。

2.技术选型

(1)文本

方案说明
数据库模糊查询效率太低
String.indexOf("")查找数据库量大的话也是比较慢
全文检索分词再匹配
DFA算法确定有穷自动机(一种数据结构)

在文字过滤系统中,为了能够应付较高的并发,需要尽量减少计算。采用DFA算法基本没有什么计算,基本是状态转移。所以选用最后一种方案。 

(2)图片

方案说明
百度OCR收费
Tesseract-OCRGoogle维护的开源OCR引擎,支持Java,Python等语言调用
Tess4J封装了Tesseract-OCR ,支持Java调用

Tess4J封装了Tesseract-OCR,Java调用起来方便,因此选用此方案。 

3.DFA

(1)原理分析

DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。

其特征为:有一个有限状态集合和一些从一个状态通向另一个状态的边,每条边上标记有一个符号,其中一个状态是初态,某些状态是终态。但不同于不确定的有限自动机,DFA中不会有从同一状态出发的两条边标志有相同的符号。

简单点说就是,它是是通过event和当前的state得到下一个state,即event+state=nextstate。理解为系统中有多个节点,通过传递进入的event,来确定走哪个路由至另一个节点,而节点是有限的。

存储:一次性的把所有的敏感词存储到了多个map中,就是下图表示这种结构

敏感词:冰毒、大麻、大坏蛋

        其处理过程为先检索文章中每一个字符,然后查看该字符是否包含在敏感词库中,假如包含在敏感词库中则查看该字符在敏感词库中isEnd是否为0,假如不为0则继续检索下一个字符,然后继续查看该字符是否存在于敏感词库中,假如存在则查看isEnd是否为1,假如为1则说明是敏感词。举个例子,假如有这样一句话:“我不卖冰毒”,这时候就会先检索“我”字,发现不在敏感词库,然后继续检索下一个,直至到“冰”,这时候“冰”在敏感词库中,但是isEnd为0,继续检索,“毒”在敏感词库且isEnd为1,这时候检测到敏感词“冰毒”。

(2)代码实现

①创建敏感词表:

实体类:

package com.my.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.ASSIGN_ID)
    private Integer id;

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

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

}

②创建对应Mapper接口

③导入工具类

package com.my.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);
    } */
}

4.OCR

(1)原理分析

OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。

(2)代码实现

①在tbug-headlines-common中导入如下依赖

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

②封装成工具类

package com.my.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;
    }

}

③在spring.factories配置中添加该类,完整如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.my.common.exception.ExceptionCatch,\
  com.my.common.swagger.SwaggerConfiguration,\
  com.my.common.swagger.Swagger2Configuration,\
  com.my.common.tencentcloud.TextDetection,\
  com.my.common.tencentcloud.ImageDetection,\
  com.my.common.tess4j.Tess4jClient,\

 ④在tbug-headlines-wemedia中添加如下配置

tess4j:
  data-path: D:\headlinesPro\tessdata
  language: chi_sim

注意文件路径要填自己的路径

二:完整审核流程

1.审核类

package com.my.wemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.my.apis.article.IArticleClient;
import com.my.common.tencentcloud.ImageDetection;
import com.my.common.tencentcloud.TextDetection;
import com.my.common.tess4j.Tess4jClient;
import com.my.file.service.FileStorageService;
import com.my.model.article.dtos.ArticleDto;
import com.my.model.common.dtos.ResponseResult;
import com.my.model.wemedia.pojos.WmChannel;
import com.my.model.wemedia.pojos.WmNews;
import com.my.model.wemedia.pojos.WmSensitive;
import com.my.model.wemedia.pojos.WmUser;
import com.my.utils.common.SensitiveWordUtil;
import com.my.wemedia.mapper.WmSensitiveMapper;
import com.my.wemedia.service.WmAutoScanService;
import com.my.wemedia.service.WmChannelService;
import com.my.wemedia.service.WmNewsService;
import com.my.wemedia.service.WmUserService;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
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.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional
public class WmAutoScanServiceImpl implements WmAutoScanService {
    @Autowired
    private WmNewsService wmNewsService;
    @Autowired
    private TextDetection textDetection;
    @Autowired
    private ImageDetection imageDetection;

    /**
     * 自动审核文章文本及图片
     * @param id
     */
    @Override
    @Async //表明这是一个异步方法
    public void AutoScanTextAndImage(Integer id) throws Exception {
        log.info("开始进行文章审核...");
        // Thread.sleep(300);    //休眠300毫秒,以保证能够获取到数据库中的数据
        WmNews wmNews = wmNewsService.getById(id);
        if(wmNews == null) {
            throw new RuntimeException("WmAutoScanServiceImpl-文章信息不存在");
        }

        if(wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())) {
            //1.提取文章文本及图片
            Map<String,Object> map = getTextAndImages(wmNews);

            //2.检测文本
            //2.1提取文本
            String content = ((StringBuilder) map.get("content")).toString();
            //2.2自管理敏感词过滤
            boolean SHandleResult = handleSensitiveScan(content,wmNews);
            if(!SHandleResult) return;
            //2.3调用腾讯云进行文本检测
            Boolean THandleResult = handleTextScan(content, wmNews);
            if(!THandleResult) return;

            //3.检测图片
            //3.1提取图片
            List<String> imageUrl = (List<String>) map.get("images");
            //3.2调用腾讯云对图片进行检测
            Boolean IHandleResult = handleImageScan(imageUrl, wmNews);
            if(!IHandleResult) return;

            //4,审核成功
            //4.1保存文章
            log.info("检测到文章无违规内容");
            ResponseResult responseResult = saveAppArticle(wmNews);
            if(!responseResult.getCode().equals(200)) {
                throw new RuntimeException("WmAutoScanServiceImpl-文章审核,保存文章失败");
            }

            //4.2回填article_id
            wmNews.setArticleId((Long) responseResult.getData());
            wmNews.setStatus(WmNews.Status.PUBLISHED.getCode());
            wmNews.setReason("审核成功");
            wmNewsService.updateById(wmNews);
        }
    }

    /**
     * 提取文章文本及图片信息
     * @param wmNews
     * @return
     */
    private Map<String,Object> getTextAndImages(WmNews wmNews) {
        //存储纯文本
        StringBuilder text = new StringBuilder();

        //存储图片链接
        List<String> imageUrl = new ArrayList<>();

        //1.从文章内容中提取文本及图片
        if(StringUtils.isNotBlank(wmNews.getContent())) {
            List<Map> maps = JSON.parseArray(wmNews.getContent(),Map.class);
            for (Map map : maps) {
                if(map.get("type").equals("text")) {
                    //文本
                    text.append(map.get("value"));
                }
                if(map.get("type").equals("image")) {
                    //图片
                    imageUrl.add((String) map.get("value"));
                }
            }
        }
        //2.从封面中提取图片
        if(StringUtils.isNotBlank(wmNews.getImages())) {
            for (String imageurl : wmNews.getImages().split(",")) {
                imageUrl.add(imageurl);
            }
        }
        //3.结果封装
        Map<String,Object> map = new HashMap<>();
        map.put("content",text);
        map.put("images",imageUrl);

        return map;
    }

    @Autowired
    private WmSensitiveMapper wmSensitiveMapper;
    /**
     * 自管理敏感词过滤
     * @param content
     * @param wmNews
     * @return
     */
    private boolean handleSensitiveScan(String content,WmNews wmNews) {
        boolean flag = true;
        //1.获取所有的敏感词
        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(Wrappers.<WmSensitive>lambdaQuery().select(WmSensitive::getSensitives));
        List<String> sensitiveList = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());

        //2.初始化敏感词库
        SensitiveWordUtil.initMap(sensitiveList);

        //3.检测文章是否包含敏感词
        Map<String, Integer> map = SensitiveWordUtil.matchWords(content);
        if(map.size() > 0) {
            log.info("当前文章存在敏感词:{}",map.keySet());
            flag = false;
            wmNews.setStatus(WmNews.Status.FAIL.getCode());
            wmNews.setReason("当前文章存在敏感词"+map.keySet());
            wmNewsService.updateById(wmNews);
        }
        return flag;
    }

    /**
     * 文本检测
     * @param content
     * @param wmNews
     * @return
     */
    private Boolean handleTextScan(String content,WmNews wmNews) throws TencentCloudSDKException {
        log.info("开始检测文章文本内容...");
        boolean flag = true;
        //1.文本为空
        if(StringUtils.isBlank(content) && StringUtils.isBlank(wmNews.getTitle())) {
            return true;
        }
        //2.调用腾讯云对文本进行检测
        JSONObject resultJson = textDetection.greenTextDetection(content + wmNews.getTitle());
        //审核不通过
        if(resultJson.get("Suggestion").equals("Block")) {
            log.warn("文章存在违规文本,审核不成功");
            flag = false;
            wmNews.setStatus(WmNews.Status.FAIL.getCode());
            wmNews.setReason("文章存在违规文本,审核不成功");
            wmNewsService.updateById(wmNews);
        }
        //自动审核不确定
        if(resultJson.get("Suggestion").equals("Review")) {
            log.info("文章存在不确定文本");
            flag = false;
            wmNews.setStatus(WmNews.Status.ADMIN_AUTH.getCode());
            wmNews.setReason("文章存在不确定文本");
            wmNewsService.updateById(wmNews);
        }
        return flag;
    }

    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private Tess4jClient tess4jClient;
    /**
     * 图片检测
     * @param list
     * @param wmNews
     * @return
     * @throws TencentCloudSDKException
     */
    private Boolean handleImageScan(List<String> list, WmNews wmNews) throws TencentCloudSDKException {
        log.info("开始检测文章图片内容...");
        boolean flag = true;
        //图片信息为空
        if(list.size() == 0) {
            return true;
        }

        //1.图片去重
        List<String> imageList = list.stream().distinct().collect(Collectors.toList());
        //2.调用腾讯云接口逐一检测
        for (String image : imageList) {
            try {
                //敏感词检测
                //1.下载minio图片文件
                byte[] bytes = fileStorageService.downLoadFile(image);

                //2.从byte[]转换为bufferedImage
                ByteArrayInputStream in = new ByteArrayInputStream(bytes);
                BufferedImage imageFile = ImageIO.read(in);

                //3.识别图片文字
                if(imageFile != null) {
                    String result = tess4jClient.doOCR(imageFile);

                    //4.审核图片是否包含敏感词
                    boolean isSensitive = handleSensitiveScan(result,wmNews);
                    if(!isSensitive) {
                        return false;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            //腾讯云图片安全检测
            JSONObject resultJson = imageDetection.greenImageDetection(image);
            //审核不通过
            if(resultJson.get("Suggestion").equals("Block")) {
                log.warn("文章存在违规图片,审核不成功");
                flag = false;
                wmNews.setStatus(WmNews.Status.FAIL.getCode());
                wmNews.setReason("文章存在违规图片,审核不成功");
                wmNewsService.updateById(wmNews);
            }
            //自动审核不确定
            if(resultJson.get("Suggestion").equals("Review")) {
                log.info("文章存在不确定图片");
                flag = false;
                wmNews.setStatus(WmNews.Status.ADMIN_AUTH.getCode());
                wmNews.setReason("文章存在不确定图片");
                wmNewsService.updateById(wmNews);
            }
        }
        return flag;
    }

    @Autowired
    private WmChannelService wmChannelService;
    @Autowired
    private WmUserService wmUserService;
    @Resource
    private IArticleClient iArticleClient;
    /**
     * 保存App端文章数据
     * @param wmNews
     * @return
     */
    @Override
    public ResponseResult saveAppArticle(WmNews wmNews) {
        ArticleDto articleDto = new ArticleDto();

        //1.属性拷贝
        BeanUtils.copyProperties(wmNews,articleDto);

        //2.设置文章布局
        articleDto.setLayout(wmNews.getType());

        //3.设置频道信息
        WmChannel channel = wmChannelService.getById(wmNews.getChannelId());
        if(channel != null) {
            articleDto.setChannelName(channel.getName());
        }

        //4.设置作者信息
        articleDto.setAuthorId(wmNews.getUserId().longValue());
        WmUser wmUser = wmUserService.getById(wmNews.getUserId());
        if(wmUser != null) {
            articleDto.setAuthorName(wmUser.getName());
        }

        //5.设置文章id
        if(wmNews.getArticleId() != null) {
            articleDto.setId(wmNews.getArticleId());
        }

        //6.设置创建时间
        articleDto.setCreatedTime(new Date());

        //7.保存App端文章
        log.info("异步调用保存文章至App端");

        return iArticleClient.saveArticle(articleDto);
    }
}

2.功能调用

在文章提交实现类中添加对自动审核方法的调用(见第五步)

/**
 * 提交文章
 * @param dto
 * @return
 */
@Override
public ResponseResult submitNews(WmNewsDto dto) {
    //1.参数校验
    if(dto == null || dto.getContent().length() == 0) {
        return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    }

    //2.保存或修改文章
    //2.1属性拷贝
    WmNews wmNews = new WmNews();
    BeanUtils.copyProperties(dto,wmNews);

    //2.2设置封面图片
    if(dto.getImages() != null && dto.getImages().size() != 0) {
        String images = StringUtils.join(dto.getImages(), ",");
        wmNews.setImages(images);
    }

    //2.3封面类型为自动
    if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)) {
        wmNews.setType(null);
    }
    saveOrUpdateWmNews(wmNews);

    //3.判断是否为草稿
    if(dto.getStatus().equals(WmNews.Status.NORMAL.getCode())) {
        //直接保存结束
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    //4.不是草稿
    //4.1保存文章图片素材与文章关系
    //4.1.1提取图片素材列表
    List<String> imagesList = getImagesList(dto);

    //4.1.2保存
    saveRelatedImages(imagesList,wmNews.getId(),WemediaConstants.WM_CONTENT_REFERENCE);

    //4.2保存封面图片和文章关系
    saveRelatedCover(dto,imagesList,wmNews);

    //5.审核文章(异步调用)
    wmAutoScanService.AutoScanTextAndImage(wmNews.getId());

    return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}

下篇预告:实现文章定时发布

 友情链接: 牛客网  刷题|面试|找工作神器

相关文章:

  • 猿创征文|给妈妈做个相册——在服务器上搭建Lychee相册的保姆级教程
  • 利用云服务器搭配宝塔面板解禁网易云
  • Proximal Policy Optimization Algorithms
  • ARM KEIL流程_job
  • [ Linux 长征路第二篇] 基本指令head,tail,date,cal,find,grep,zip,tar,bc,unname
  • SpringBoot——快速整合EasyExcel实现Excel的上传下载
  • Vue 国际化之 vue-i18n 的使用
  • 7、Java——for循环打印九九乘法口诀表
  • 目标检测 YOLO 系列模型
  • Java开发五年跳槽涨薪从12K到35K,靠“狂刷”九遍面试题
  • DM数据库安装,docker镜像
  • 项目中的traceID
  • Webview+Viewpager左右滑动冲突
  • 【 C++ 】多态
  • jupyter 基本用法
  • 3.7、@ResponseBody 和 @RestController
  • Angular Elements 及其运作原理
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • JavaScript 基础知识 - 入门篇(一)
  • k个最大的数及变种小结
  • Material Design
  • Promise面试题,控制异步流程
  • React Native移动开发实战-3-实现页面间的数据传递
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • 百度贴吧爬虫node+vue baidu_tieba_crawler
  • 从setTimeout-setInterval看JS线程
  • 分享几个不错的工具
  • 如何使用 JavaScript 解析 URL
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (4)Elastix图像配准:3D图像
  • (AngularJS)Angular 控制器之间通信初探
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (笔试题)合法字符串
  • (多级缓存)缓存同步
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (力扣)循环队列的实现与详解(C语言)
  • (七)理解angular中的module和injector,即依赖注入
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • .htaccess配置重写url引擎
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .NET Micro Framework初体验
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .NET 的程序集加载上下文
  • .NET 服务 ServiceController
  • .net打印*三角形
  • .net利用SQLBulkCopy进行数据库之间的大批量数据传递
  • .NET下ASPX编程的几个小问题
  • /etc/motd and /etc/issue