• Vue整合Markdown组件+SpringBoot文件上传+代码差异对比


    前言

    一眨眼礼拜五了,说啥再水一篇博文,之后的话,小爷就可以去玩几把游戏了,嘿嘿~。

    那么今天带来的主要是使用这个Vue整合到Markdown一个组件实现这个前端博客的编辑,然后是咱们的后端,实现这个图片上传,之后的话是咱们的这个文本差异对比的一个组件的使用吧(其实之所以要说这个是因为接下来要用的到的这两个组件间有差异冲突,先前也是搞了好久才发现这个问题的)

    那么本文要做的呢就是三个事情嘛。

    1. 整合Markdown组件(这里是Vue2)
    2. 实现Markdown的图片上传(这个其实和先前的OSS上传用户头像是类似的)
    3. Markdown的显示
    4. 文本差异对比插件

    由于咱们需要使用到OSS服务,所以的话,还没有的同学可以查看这篇博文:懒人系列–文件上传之OSS使用案例
    这个应该也是我写很详细的一篇博文了吧。

    实现效果

    废话不多说,咱们先来看看咱们这个实现的效果吧。

    博文编辑(问答)

    在这里的话,我设计的玩意有两个东西,一个是博文,一个是问答,他们都是基于Markdown进行编辑的。
    在这里插入图片描述
    咱们这里只是测试哈(值得一提的是,咱们的话其实也是有这个文本的一些过滤的,敏感词汇之类的,当然实现的算法目前是很简单就是一个对比过滤,木有上机器学习,一个木有数据训练,一个是基于java的比较少咱们还得是用python,最后是资源消耗有点大)

    之后是回答页面:

    在这里插入图片描述

    回答的展示页面(Markdown展示)

    这个就简单了
    在这里插入图片描述
    这个图不好看,可以看这个:
    在这里插入图片描述

    (问题的回答这个暂时我是不想做限制的,因为确实有些问题的回答就是很复杂的)

    至于图片上传的演示,这个都是哈。

    代码差异对比

    这个主要是这个玩意:

    在这里插入图片描述
    (这个博文部分的功能还没写,其实和问答部分的一样,我在等这个社区功能做好)
    (这个是大家都可以看到的,但是只有作者才能够去进行操作,所以我们博文的展示完全是靠这个来看的,以内容为驱动(当然这个平台上线可能也没人,反正我做着玩))

    在这里插入图片描述
    那么为什么要说这个呢,原因很简单,这个和我们要用的Markdown组件之间存在一定的兼容问题。这个是啥情况我们待会再说。

    ok,这个就是咱们的效果,那么咱们一个一个来:

    Markdown组件

    这个组件的话有很多,那么在我们这里的话我是选择了 mavon-editor

    这个使用的话其实非常简单,官方文档说的也还详细,主要我觉得最爽的是那个自己集成了代码高亮。

    下载安装

    npm install mavon-editor --save
    
    • 1

    为了方便,我这里选择全局注册使用。那么在main.js里面进行注册

    //全局注册
    import mavonEditor from 'mavon-editor'
    import 'mavon-editor/dist/css/index.css'	//解决编辑器的功能显示问题
    Vue.use(mavonEditor)
    
    • 1
    • 2
    • 3
    • 4

    使用,使用的话很简单,在一个页面使用就好了

    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    但是这个的话,其实还是最基础的使用,这里是没有集成到图片上传的。

    功能实现

    那么图片上传的话,咱们其实是有两个部分的,一个是前端,还有一个是后端。

    我们先来说说前端吧。

    前端

            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    首先的话还是引入,但是这里的话绑定了两个函数
    这两个一个就是上传图片的,还有一个就是拿到咱们Markdown文本的。

    图片上传

    那么我们先来看到这个。

    这个的话,咱们是有这个OSS的,所以上传也是到OSS里面的(其实主要是为了使用那个图片扫描,毕竟不是所有的图片都是可以看的,总有用户上传不友好的图片,文字))

        toOss(pos,$file){
          //组装数据
          let formData = new FormData();
          Object.keys(this.dataObj).forEach(key => {
            formData.append(key, this.dataObj[key]);
          });
          formData.append('file',$file)
          //此时发送请求去给到OSS
          this.axios({
            url: this.dataObj.host,
            method: 'post',
            data: formData
          }).then((res) => {
            let imgpath = this.dataObj.host + '/' + this.dataObj.key;
            //把这个给到我们的编辑器
            this.$refs.md.$img2Url(pos,imgpath)
          })
        },
        imgAdd(pos, $file){
          /**
           * 上传图片到OSS服务里面
           * */
          let filename = $file.name
          let _self = this;
          // 获取认证码
          this.axios
            .get('/third-part/oss/quizWriteAnsImgPolicy')
            .then(response => {
              response = response.data;
              _self.dataObj.policy = response.data.policy;
              _self.dataObj.signature = response.data.signature;
              _self.dataObj.ossaccessKeyId = response.data.accessid;
              _self.dataObj.key = response.data.dir +getUUID()+"_"+filename;
              _self.dataObj.dir = response.data.dir;
              _self.dataObj.host = response.data.host;
              //推送到OSS
              this.toOss(pos,$file);
            }).catch(function (error) {
            alert(error)
            console.log("出错了...",err)
          })
        },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    获取Markdown

    之后的话是获取我们这个文本。

        change(value, render){
          //value为编辑器中的实际内容,即markdown的内容,render为被解析成的html的内容
          this.form.html = render;
        },
    
    • 1
    • 2
    • 3
    • 4

    我这里的话,绑定了这个数据:

    this.form.html
    
    • 1

    通过这个就可以上传了:

      submit(){
          //点击提交后既可以获取html内容,又可以获得markdown的内容,之后存入到服务端就可以了
          this.fullscreenLoading = true;
    
          // console.log(this.form.value);
          // console.log(this.form.html);
          //将Markdown文档提交到服务器
          let flag = true;
          if(!this.form.value){
            flag = false;
          }else {
            if(this.form.value.length<10){
              flag = false;
              alert("回答不能低于10个字符")
            }
          }
          if(flag){
            //此时对用户回答进行提交
            this.axios({
              url: "/quiz/quiz/base/baseUpAns",
              method: 'post',
              headers: {
                "userid": this.userid,
                "loginType": "PcType",
                "loginToken": this.loginToken,
              },
              data:{
                "userid": this.userid,
                "quizid": this.Messages.quizid,
                "quizTitle": this.Messages.quizTitle,
                "context": this.form.value
              }
            }).then((res)=>{
              res = res.data;
              if(res.code===0){
                alert(res.msg)
              }else {
                this.$message.error(res.msg);
              }
              this.fullscreenLoading = false;
              this.editFlag = false;
            });
          }else {
            alert("您的回答为空!")
          }
    
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    我的这部分代码是这样的(这里也没有去封装,天知道还要不要改接口,写好再封)

    那么这个是我们前端比较重要的事情,那么接下来是我们的后端。

    后端

    后端的话,其实有两个接口,第一个是授权OSS的,还有一个是保存咱们的这个回答的,也就是Markdown的。

    那么我们这边的话,授权的话,这个已经说烂了,在那个OSS整合里面。

    那么我们这边主要说说这个,敏感词过滤吧。

    毕竟咱们这边上传其实就老三样。

    限制

    首先是咱们的这个限制,就是防止恶意刷提交,由于是咱们的这个文本,里面的内容是有可能修改的,修改了多少其实也不太好评判,所以的话,不太好保证这个接口幂等,只能限制,这个限制的话咱们就是直接使用这个redis做的。

     if(redisUtils.hasKey(RedisTransKey.getBaseUpQuizKey(entity.getUserid()))){
                return R.error(BizCodeEnum.HAS_UPANS.getCode(), BizCodeEnum.HAS_UPANS.getMsg());
            }
    ......
    你的业务
    
    成功之后
    
    设置标志
     
    redisUtils.set(RedisTransKey.setBaseUpAnsKey(entity.getUserid())
            ,1,5, TimeUnit.MINUTES
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    反正就这样吧。然后这个redisUtils这个咱们先前也给出过是这样的:

    public class RedisUtils {
    
        @Autowired
        private RedisTemplate<String,Object> redisTemplate;
    
        /**
         *  指定缓存失效时间
         * @param key 键
         * @param time 时间(秒)
         * @return
         */
        public boolean expire(String key, long time) {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
                return true;
            } else {
                throw new RuntimeException("超时时间小于0");
            }
        }
    
    
        /**
         * 根据key 获取过期时间
         * @param key 键 不能为null
         * @return 时间(秒) 返回0代表为永久有效
         */
        public long getExpire(String key) {
            return redisTemplate.getExpire(key, TimeUnit.SECONDS);
        }
    
        /**
         * 根据key 获取过期时间
         * @param key 键 不能为null
         * @param tiemtype 时间类型
         * @return 时间(秒) 返回0代表为永久有效
         */
        public long getExpire(String key,TimeUnit tiemtype) {
    
            return redisTemplate.getExpire(key, tiemtype);
        }
    
        /**
         *  判断key是否存在
         * @param key 键
         * @return true 存在 false不存在
         */
        public boolean hasKey(String key) {
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        }
    
        /**
         *  删除缓存
         * @param key 可以传一个值 或多个
         */
        @SuppressWarnings("unchecked")
        public void del(String... key) {
            if (key != null && key.length > 0) {
                if (key.length == 1) {
                    redisTemplate.delete(key[0]);
                } else {
                    redisTemplate.delete(CollectionUtils.arrayToList(key));
                }
            }
        }
    
    // ============================String=============================
        /**
         * 普通缓存获取
         * @param key 键
         * @return 值
         */
        public Object get(String key) {
            return key == null ? null : redisTemplate.opsForValue().get(key);
        }
    
        /**
         * 普通缓存放入
         * @param key 键
         * @param value  值
         * @return true成功 false失败
         */
        public boolean set(String key, Object value) {
            redisTemplate.opsForValue().set(key, value);
            return true;
        }
    
    
        /**
         *  普通缓存放入并设置时间
         * @param key 键
         * @param value 值
         * @param time time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
         * @return true成功 false 失败
         */
        public boolean set(String key, Object value, long time) {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                this.set(key, value);
            }
            return true;
        }
    
        /**
         *  普通缓存放入并设置时间
         * @param key 键
         * @param value 值
         * @param time time 时间类型自定义设定
         * @return true成功 false 失败
         */
    
        public boolean set(String key, Object value, long time,TimeUnit tiemtype) {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, tiemtype);
            } else {
                this.set(key, value);
            }
            return true;
        }
    
    
    
    
        /**
         *  递增
         * @param key 键
         * @param delta 要增加几(大于0)
         * @return
         */
        public long incr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递增因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, delta);
        }
    
        /**
         *  递减
         * @param key
         * @param delta 要减少几(大于0)
         * @return
         */
        public long decr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递减因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, -delta);
        }
    
    // ================================Map=================================
    
        /**
         *  HashGet
         * @param key 键
         * @param item  项 不能为null
         * @return
         */
        public Object hget(String key, String item) {
            return redisTemplate.opsForHash().get(key, item);
        }
    
    
        /**
         *  获取hashKey对应的所有键值
         * @param key 键
         * @return 对应的多个键值
         */
        public Map<Object, Object> hmget(String key) {
            return redisTemplate.opsForHash().entries(key);
        }
    
        /**
         * HashSet
         * @param key 键
         * @param map 对应多个键值
         * @return true 成功 false 失败
         */
        public boolean hmset(String key, Map<String, Object> map) {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        }
        /**
         * HashSet 并设置时间
         * @param key 键
         * @param map 对应多个键值
         * @param time 时间(秒)
         * @return true成功 false失败
         */
        public boolean hmset(String key, Map<String, Object> map, long time) {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        }
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value) {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        }
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value, long time) {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        }
        /**
         * 删除hash表中的值
         * @param key 键 不能为null
         * @param item 项 可以使多个 不能为null
         */
        public void hdel(String key, Object... item) {
            redisTemplate.opsForHash().delete(key, item);
        }
        /**
         * 判断hash表中是否有该项的值
         * @param key 键 不能为null
         * @param item 项 不能为null
         * @return true 存在 false不存在
         */
        public boolean hHasKey(String key, String item) {
            return redisTemplate.opsForHash().hasKey(key, item);
        }
        /**
         * hash递增 如果不存在,就会创建一个 并把新增后的值返回
         * @param key 键
         * @param item 项
         * @param by 要增加几(大于0)
         * @return
         */
        public double hincr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, by);
        }
        /**
         * hash递减
         * @param key 键
         * @param item 项
         * @param by 要减少记(小于0)
         * @return
         */
        public double hdecr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, -by);
        }
        // ============================set=============================
        /**
         * 根据key获取Set中的所有值
         * @param key 键
         * @return
         */
        public Set<Object> sGet(String key) {
            return redisTemplate.opsForSet().members(key);
        }
        /**
         * 根据value从一个set中查询,是否存在
         * @param key 键
         * @param value 值
         * @return true 存在 false不存在
         */
        public boolean sHasKey(String key, Object value) {
            return redisTemplate.opsForSet().isMember(key, value);
        }
        /**
         * 将数据放入set缓存
         * @param key 键
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSet(String key, Object... values) {
            return redisTemplate.opsForSet().add(key, values);
        }
        /**
         * 将set数据放入缓存
         * @param key 键
         * @param time 时间(秒)
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSetAndTime(String key, long time, Object... values) {
            final Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        }
        /**
         * 获取set缓存的长度
         * @param key 键
         * @return
         */
        public long sGetSetSize(String key) {
            return redisTemplate.opsForSet().size(key);
        }
        /**
         * 移除值为value的
         * @param key 键
         * @param values 值 可以是多个
         * @return 移除的个数
         */
        public long setRemove(String key, Object... values) {
            final Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        }
        // ===============================list=================================
        /**
         * 获取list缓存的内容
         * @param key 键
         * @param start 开始
         * @param end 结束  0 到 -1代表所有值
         * @return
         */
        public List<Object> lGet(String key, long start, long end) {
            return redisTemplate.opsForList().range(key, start, end);
        }
        /**
         * 获取list缓存的长度
         * @param key 键
         * @return
         */
        public long lGetListSize(String key) {
            return redisTemplate.opsForList().size(key);
        }
        /**
         * 通过索引 获取list中的值
         * @param key 键
         * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
         * @return
         */
        public Object lGetIndex(String key, long index) {
            return redisTemplate.opsForList().index(key, index);
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, Object value) {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @param time 时间(秒)
         * @return
         */
        public boolean lSet(String key, Object value, long time) {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSetList(String key, List<Object> value) {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSetList(String key, List<Object> value, long time) {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        }
        /**
         * 根据索引修改list中的某条数据
         * @param key 键
         * @param index 索引
         * @param value 值
         * @return
         */
        public boolean lUpdateIndex(String key, long index, Object value) {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404

    文本过滤

    这个小东西的结构很简单。
    我把这个小东西封装在了我们的common组件当中。主要是为了方便使用。

    项目结构

    在这里插入图片描述

    之后的话,我们还有黑白名单:

    这个就很简单了,那个黑名单,白名单是吧。

    实现

    这个实现的话,其实很简单,原理就不讲了,因为烂大街了,咱们直接用就好了。反正这个也不准,我们更加期望的是能够使用机器学习去做。

    那么这里的话依次给出代码:
    这个代码已经烂大街了,这里做一个整合。

    /**
     * 结束类型定义
     **/
    public enum EndType {
    
        /**
         * 有下一个,结束
         */
        HAS_NEXT, IS_END
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    
    /**
     * 敏感词标记
     */
    public class FlagIndex {
    
        /**
         * 标记结果
         */
        private boolean flag;
        /**
         * 是否黑名单词汇
         */
        private boolean isWhiteWord;
        /**
         * 标记索引
         */
        private List<Integer> index;
    
        public boolean isFlag() {
            return flag;
        }
    
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    
        public List<Integer> getIndex() {
            return index;
        }
    
        public void setIndex(List<Integer> index) {
            this.index = index;
        }
    
        public boolean isWhiteWord() {
            return isWhiteWord;
        }
    
        public void setWhiteWord(boolean whiteWord) {
            isWhiteWord = whiteWord;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    
    /**
    
     * 初始化敏感词库,将敏感词加入到HashMap中,构建DFA算法模型
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public class WordContext {
    
        /**
         * 敏感词字典
         */
        private final Map wordMap = new HashMap(2048);
    
        /**
         * 是否已初始化
         */
        private boolean init;
        /**
         * 黑名单列表
         */
        private final String blackList;
        /**
         * 白名单列表
         */
        private final String whiteList;
    
        public WordContext() {
            this.blackList = "/blacklist.txt";
            this.whiteList = "/whitelist.txt";
            initKeyWord();
        }
    
        public WordContext(String blackList, String whiteList) {
            this.blackList = blackList;
            this.whiteList = whiteList;
            initKeyWord();
        }
    
        /**
         * 获取初始化的敏感词列表
         *
         * @return 敏感词列表
         */
        public Map getWordMap() {
            return wordMap;
        }
    
        /**
         * 初始化
         */
        private synchronized void initKeyWord() {
            try {
                if (!init) {
                    // 将敏感词库加入到HashMap中
                    addWord(readWordFile(blackList), WordType.BLACK);
                    // 将非敏感词库也加入到HashMap中
                    addWord(readWordFile(whiteList), WordType.WHITE);
                }
                init = true;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 读取敏感词库,将敏感词放入HashSet中,构建一个DFA算法模型:
    * 中 = { isEnd = 0 国 = {
    * isEnd = 1 人 = {isEnd = 0 民 = {isEnd = 1} } 男 = { isEnd = 0 人 = { isEnd = 1 } * } } } 五 = { isEnd = 0 星 = { isEnd = 0 红 = { isEnd = 0 旗 = { isEnd = 1 } } } } */
    public void addWord(Iterable<String> wordList, WordType wordType) { Map nowMap; Map<String, String> newWorMap; // 迭代keyWordSet for (String key : wordList) { nowMap = wordMap; for (int i = 0; i < key.length(); i++) { // 转换成char型 char keyChar = key.charAt(i); // 获取 Object wordMap = nowMap.get(keyChar); // 如果存在该key,直接赋值 if (wordMap != null) { nowMap = (Map) wordMap; } else { // 不存在则构建一个map,同时将isEnd设置为0,因为他不是最后一个 newWorMap = new HashMap<>(4); // 不是最后一个 newWorMap.put("isEnd", String.valueOf(EndType.HAS_NEXT.ordinal())); nowMap.put(keyChar, newWorMap); nowMap = newWorMap; } if (i == key.length() - 1) { // 最后一个 nowMap.put("isEnd", String.valueOf(EndType.IS_END.ordinal())); nowMap.put("isWhiteWord", String.valueOf(wordType.ordinal())); } } } } /** * 在线删除敏感词 * * @param wordList 敏感词列表 * @param wordType 黑名单 BLACk,白名单WHITE */ public void removeWord(Iterable<String> wordList, WordType wordType) { Map nowMap; for (String key : wordList) { List<Map> cacheList = new ArrayList<>(); nowMap = wordMap; for (int i = 0; i < key.length(); i++) { char keyChar = key.charAt(i); Object map = nowMap.get(keyChar); if (map != null) { nowMap = (Map) map; cacheList.add(nowMap); } else { return; } if (i == key.length() - 1) { char[] keys = key.toCharArray(); boolean cleanable = false; char lastChar = 0; for (int j = cacheList.size() - 1; j >= 0; j--) { Map cacheMap = cacheList.get(j); if (j == cacheList.size() - 1) { if (String.valueOf(WordType.BLACK.ordinal()).equals(cacheMap.get("isWhiteWord"))) { if (wordType == WordType.WHITE) { return; } } if (String.valueOf(WordType.WHITE.ordinal()).equals(cacheMap.get("isWhiteWord"))) { if (wordType == WordType.BLACK) { return; } } cacheMap.remove("isWhiteWord"); cacheMap.remove("isEnd"); if (cacheMap.size() == 0) { cleanable = true; continue; } } if (cleanable) { Object isEnd = cacheMap.get("isEnd"); if (String.valueOf(EndType.IS_END.ordinal()).equals(isEnd)) { cleanable = false; } cacheMap.remove(lastChar); } lastChar = keys[j]; } if (cleanable) { wordMap.remove(lastChar); } } } } } /** * 读取敏感词库中的内容,将内容添加到set集合中 */ private Set<String> readWordFile(String file) throws Exception { Set<String> set; // 字符编码 String encoding = "UTF-8"; try (InputStreamReader read = new InputStreamReader( this.getClass().getResourceAsStream(file), encoding)) { set = new HashSet<>(); BufferedReader bufferedReader = new BufferedReader(read); String txt; // 读取文件,将文件内容放入到set中 while ((txt = bufferedReader.readLine()) != null) { set.add(txt); } } // 关闭文件流 return set; } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    public class WordFilter {
    
        /**
         * 敏感词表
         */
        private final Map wordMap;
    
        /**
         * 构造函数
         */
        public WordFilter(WordContext context) {
            this.wordMap = context.getWordMap();
        }
    
        /**
         * 替换敏感词
         *
         * @param text 输入文本
         */
        public String replace(final String text) {
            return replace(text, 0, '*');
        }
    
        /**
         * 替换敏感词
         *
         * @param text   输入文本
         * @param symbol 替换符号
         */
        public String replace(final String text, final char symbol) {
            return replace(text, 0, symbol);
        }
    
        /**
         * 替换敏感词
         *
         * @param text   输入文本
         * @param skip   文本距离
         * @param symbol 替换符号
         */
        public String replace(final String text, final int skip, final char symbol) {
            char[] charset = text.toCharArray();
            for (int i = 0; i < charset.length; i++) {
                FlagIndex fi = getFlagIndex(charset, i, skip);
                if (fi.isFlag()) {
                    if (!fi.isWhiteWord()) {
                        for (int j : fi.getIndex()) {
                            charset[j] = symbol;
                        }
                    } else {
                        i += fi.getIndex().size() - 1;
                    }
                }
            }
            return new String(charset);
        }
    
        /**
         * 是否包含敏感词
         *
         * @param text 输入文本
         */
        public boolean include(final String text) {
            return include(text, 0);
        }
    
        /**
         * 是否包含敏感词
         *
         * @param text 输入文本
         * @param skip 文本距离
         */
        public boolean include(final String text, final int skip) {
            boolean include = false;
            char[] charset = text.toCharArray();
            for (int i = 0; i < charset.length; i++) {
                FlagIndex fi = getFlagIndex(charset, i, skip);
                if(fi.isFlag()) {
                    if (fi.isWhiteWord()) {
                        i += fi.getIndex().size() - 1;
                    } else {
                        include = true;
                        break;
                    }
                }
            }
            return include;
        }
    
        /**
         * 获取敏感词数量
         *
         * @param text 输入文本
         */
        public int wordCount(final String text) {
            return wordCount(text, 0);
        }
    
        /**
         * 获取敏感词数量
         *
         * @param text 输入文本
         * @param skip 文本距离
         */
        public int wordCount(final String text, final int skip) {
            int count = 0;
            char[] charset = text.toCharArray();
            for (int i = 0; i < charset.length; i++) {
                FlagIndex fi = getFlagIndex(charset, i, skip);
                if (fi.isFlag()) {
                    if(fi.isWhiteWord()) {
                        i += fi.getIndex().size() - 1;
                    } else {
                        count++;
                    }
                }
            }
            return count;
        }
    
        /**
         * 获取敏感词列表
         *
         * @param text 输入文本
         */
        public List<String> wordList(final String text) {
            return wordList(text, 0);
        }
    
        /**
         * 获取敏感词列表
         *
         * @param text 输入文本
         * @param skip 文本距离
         */
        public List<String> wordList(final String text, final int skip) {
            List<String> wordList = new ArrayList<>();
            char[] charset = text.toCharArray();
            for (int i = 0; i < charset.length; i++) {
                FlagIndex fi = getFlagIndex(charset, i, skip);
                if (fi.isFlag()) {
                    if(fi.isWhiteWord()) {
                        i += fi.getIndex().size() - 1;
                    } else {
                        StringBuilder builder = new StringBuilder();
                        for (int j : fi.getIndex()) {
                            char word = text.charAt(j);
                            builder.append(word);
                        }
                        wordList.add(builder.toString());
                    }
                }
            }
            return wordList;
        }
    
        /**
         * 获取标记索引
         *
         * @param charset 输入文本
         * @param begin   检测起始
         * @param skip    文本距离
         */
        private FlagIndex getFlagIndex(final char[] charset, final int begin, final int skip) {
            FlagIndex fi = new FlagIndex();
    
            Map current = wordMap;
            boolean flag = false;
            int count = 0;
            List<Integer> index = new ArrayList<>();
            for (int i = begin; i < charset.length; i++) {
                char word = charset[i];
                Map mapTree = (Map) current.get(word);
                if (count > skip || (i == begin && Objects.isNull(mapTree))) {
                    break;
                }
                if (Objects.nonNull(mapTree)) {
                    current = mapTree;
                    count = 0;
                    index.add(i);
                } else {
                    count++;
                    if (flag && count > skip) {
                        break;
                    }
                }
                if ("1".equals(current.get("isEnd"))) {
                    flag = true;
                }
                if ("1".equals(current.get("isWhiteWord"))) {
                    fi.setWhiteWord(true);
                    break;
                }
            }
    
            fi.setFlag(flag);
            fi.setIndex(index);
    
            return fi;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    /**
     * 词汇类型
    
     **/
    public enum WordType {
    
        /**
         * 黑名单/白名单
         */
        BLACK, WHITE
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    使用

    之后的话,我们就可以愉快使用了,那么使用的话,我们这里是整合了SpringBoot的,所以的话,我们直接使用配置类即可。

    @Configuration
    public class WordFilterConfig {
    
        @Bean
        public WordContext wordContext(){
            return new WordContext();
        }
    
        @Bean
        public WordFilter wordFilter(WordContext wordContext){
            return new WordFilter(wordContext);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    然后就可以使用了,直接注入即可。
    在这里插入图片描述

    那么到这里咱们的这个Markdown的组件编辑就好了

    Markdown展示

    之后就是展示,我们这里的话为了方便后面修改存储的都是Markdown的文本,所以的话,也是非常方便的。那么的话得益于这个组件,我们不需要那么麻烦,直接这样用就好了。

    <!--    这个是我们的回答的具体内容-->
    
            <div style="width: 96%;margin: 0 auto">
              <br><br>
              <mavon-editor
                class="md"
                :boxShadow="false"
                :value="Messages.context"
                :subfield="false"
                defaultOpen="preview"
                :toolbarsFlag="true"
    
              />
            </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如果不想要这个工具栏的话,还可以:toolbarsFlag=“false”
    但是的话,咱们是要的主要是有目录,而且方便观看。

    之后是代码的风格,我们现在这个:
    在这里插入图片描述
    这个是GitHub的一个风格,我们可以通过这个codeStyle等去设置。

    更加完整的咱们去官网可以看到:
    https://github.com/hinesboy/mavonEditor

    我这里就不阐述了,前端的事情咋也不敢问。

    代码差异对比

    之后就是咱们的代码差异对比了。

    首先在组件上有很多的选择,一般很多博文,教程啥的用的都是这个: vue-code-diff

    但是问题就在这里,首先这个玩意的话,其实也是有代码高亮的,没错就是这个:highlight 来实现的。
    我们的那个编辑器组件的高亮也是这个来实现的。本来是没啥问题的,但是问题在于这个是9.x版本的,我们那个组件是10.x版本的,这个是我翻找依赖和官方文档和说明已经源码发现的。那么如果你使用的是这个那么这个玩意就会存在一定的冲突,后果就是你的代码显示区域很奇怪,全部压缩到了一行里面。

    所我们需要使用另一个代替品。刚好有这个玩意:
    v-code-diff 这个是在 vue-code-diff 基础上开发的。

    安装

    npm i v-code-diff
    
    • 1

    之后的话是这个:

    npm i @vue/composition-api
    
    • 1

    因为我的是vue2,vue3不用,现在好多都是vue3,下次我再换vue3吧,没办法一开始学的时候是vue2后来没管了,一直用的就是2.

    使用

    也是可以全局,可以局部使用的。
    全局就是在main.js这样

    import Vue from 'vue';
    import CodeDiff from 'v-code-diff'
    
    Vue.use(CodeDiff);
    
    • 1
    • 2
    • 3
    • 4

    然后直接使用

    <template>
      <code-diff
          :old-string="'12345'"
          :new-string="'3456'"
          file-name="test.txt"
          output-format="side-by-side"/>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    单独局部的话就是这样:

    <template>
      <code-diff
          :old-string="'12345'"
          :new-string="'3456'"
          file-name="test.txt"
          output-format="side-by-side"/>
    </template>
    <script>
    import {CodeDiff} from 'v-code-diff'
    
    export default {
      name: 'App',
      components: {
        CodeDiff
      }
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    总结

    临时加水一篇~

  • 相关阅读:
    【场景化解决方案】连接“云上管车”与道闸系统,企业用车流程更高效
    详解字符串比较函数:strcmp函数及其模拟实现
    Qt扫盲-QJsonValue理论总结
    Python+pytest接口自动化之参数关联
    平衡二叉树c语言版
    django配置前端文件放置的位置
    嵌入式笔试面试刷题(day15)
    《用户体验度量》读书笔记
    力扣刷题记录141.1-----34. 在排序数组中查找元素的第一个和最后一个位置
    Python 导入Excel三维坐标数据 生成三维曲面地形图(体) 5-3、线条平滑曲面且可通过面观察柱体变化(三)
  • 原文地址:https://blog.csdn.net/FUTEROX/article/details/127455288