17

本期

这次9个并不都是bug, 其中有几个小优化, 虽然一个月的时间遇到很多bug, 但并不是每个都有参考价值, 让我们看看这次我遇到的有趣问题吧.

1: git 报错WARNING: POSSIBLE DNS SPOOFING DETECTED!

bug现象:
一个平凡的清晨, 放下书包喝了口白开水, 习惯性的git pull了一下, 这个不速之客就出现在了命令行里面, 脑袋中竟然第一反应是希望这个bug有趣一点, 这样就可以写文章了, 这个bug是突然出现的本身肯定与我的操作无关, 当时问了下其他同事也遇到了这个问题, 但是我需要查一下如何解决它,以及它的危害与预防.

bug追查:
WARNING: POSSIBLE DNS SPOOFING DETECTED! 翻译成中文 警告:检测到可能的DNS欺骗!哦哦原来是"信任"方面的问题, 那我能想到的就是"身份验证方面", 有关身份我只做过全局的gitlab的账号邮箱设置, 权限设置, 还有就是ssh的设置, 那么怎么看都是ssh嫌疑最大, 最后还是去网上查到的解决方法.

bug解决方案:
删除 known_hosts 文件
一台主机上有多个Linux系统,会经常切换,那么这些系统使用同一ip,登录过一次后就会把ssh信息记录在本地的~/.ssh/known_hsots文件中,
切换该系统后再用ssh访问这台主机就会出现冲突警告,需要手动删除修改known_hsots里面的内容。
有以下两个解决方案:

  1. 手动删除修改known_hsots里面的内容;
  2. 修改配置文件“~/.ssh/config”,加上这两行,重启服务器。
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null

优缺点:

  1. 需要每次手动删除文件内容,一些自动化脚本的无法运行(在SSH登陆时失败),但是安全性高;
  2. SSH登陆时会忽略known_hsots的访问,但是安全性低;

2:地图geojson 绘制地图的具体操作(附上地图的代码)

需求:
项目内需要使用地图展示资产在全球的分布, 需要绘制'全球地图',有闪烁提示有悬停展示详情, 本功能只是一个小模块, 所以不许有明显的加载感, 后端会返回给我每个点对应的经纬度.

分析与准备:

本次并没有采用我们公司内部孵化的地图库, 因为它并不够轻量, 最后选用的是echarts, 我也是第一次用echarts画地图.
说实话官网的例子挺不好的, 很多地方也没有说明白反正我第一次去学习的体验挺差.
地图需要画出各个国家的区域线条, 那就需要一个描述文件而这个文件一般都叫做"geojson"文件, 而这个文件又分为pc(以中心点绘制)、以经纬度绘制两种比较常见, 而本次我们采用的是经纬度的定位, 那就需要用经纬度的geojson!! 这个非常重要, 具体的json这个网站里面挺全的, 但他都是pc绘制geojson文件库
下面是我简单做了下封装的组件
<template>
<div
  id="main"
  style="position: relative; width: 100%; height: 100%; padding: 0px; margin: 0px; border-width: 0px; cursor: default;"
/>
</template>

<script>
import echarts from 'echarts';
import geoJson from '../assets/geojson/countries.geo.json';

export default {
props: {
  mapData: Array, // 里面只有三个值, 经度, 纬度, 数量 用于打点
},
data() {
  return {
    sanData: [],
    myChart: null,
    tooltip: { // 提示框
      trigger: 'item',
      formatter: (arg) => {
        if (arg.value) {
          return `${arg.name}: ${arg.value[2]}`;
        }
        return '';
      },
    },
    geo: { // 地图的底色样式
      map: 'all',
      zoom: 1,
      top: 30,
      left: 30,
      right: 30,
      bottom: 30,
      show: true,
      roam: false,
      label: {
        normal: {
          show: false,
        },
        emphasis: {
          show: false,
        },
      },
      itemStyle: {
        normal: {
          areaColor: '#47D1FF', // 板块颜色
          borderColor: '#3B5077', // 边线
          shadowColor: 'rgba(0, 0, 0, 0.2)',
          shadowBlur: 10,
        },
        emphasis: {
          areaColor: '#2B91B7', // 悬停
        },
      },
    },
    effect: { // 闪点样式
      name: '数量',
      type: 'effectScatter',
      showEffectOn: 'render', // render emphasis
      coordinateSystem: 'geo',
      hoverAnimation: true,
      legendHoverLink: true,
      symbolSize: () => 20,
      rippleEffect: {
        brushType: 'stroke',
      },
      itemStyle: {
        normal: {
          color: '#ff8003',
        },
      },
      label: {
        normal: {
          formatter(arg) {
            return arg.value[2];
          },
          position: 'inside',
          show: true,
        },
        emphasis: {
          show: false,
        },
      },
    },
    map: { // 地图的绘制数据
      type: 'map',
      name: '工程数',
      mapType: 'world', // 自定义扩展图表类型
      geoIndex: 0,
      itemStyle: {
        normal: { label: { show: true } },
        emphasis: { label: { show: true } },
      },
    },
  };
},
methods: {
  initMap() {
    this.myChart = echarts.init(document.getElementById('main'));
    const {
      myChart, sanData, effect, tooltip, geo, map,
    } = this;
    echarts.registerMap('all', geoJson, {});
    effect.data = sanData;
    const option = {
      geo,
      tooltip,
      series: [effect, map],
    };
    myChart.setOption(option);
  },
  // 组装成地图需要的打点数据
  initDian() {
    this.mapData.forEach((item) => {
      this.sanData.push({
        name: item.country,
        value: [
          item.lng,
          item.lat,
          item.value,
        ],
      });
    });
  },
},
mounted() {
  this.initDian();
  this.initMap();
},
};
</script>
效果如下图(参考了网上大家的做法, 比官网还靠谱)

sj.png

3:样式一样的新老项目共用一个域名.

背景:
一个辗转了三个团队, 维护了快三年的老vue项目, 代码简直乱到令人发指的程度, 比如请求报错了 /api/home/list/ip这样的地址报401, 那么我去查找这个地址在哪里发出的请求, 全局也没搜索到这个地址, 一个小时后我才知道 这个地址被分成了类似这种-> , const a = 'api', const b = 'home', const c = 'list', 请求的时候 axios.get(/${a}/${b}/${c}/ip), 不仅如此, 这个请求还被放在的vuex里面辗转反侧三个配置页面然后被各种重新命名之后不知所踪, 关键是连个文档也没有, 这个项目在我来到公司之前就已经是一个人人不愿碰的存在了.

我刚加入就接到了改造这个项目的任务
经过讨论这个项目是"救不活了", 但是我们可以另起一个一模一样的项目, 然后所有的新需求都放在新项目里面, 每次需求都把老项目的页面迁移过来一个, 这样一年半载之后就可以彻底使用新项目代替老项目

需要解决的问题:

  1. 两个项目域名相同
  2. 切换时尽量做到用户无感
  3. 共用一套登录系统
  4. 平稳迁移, 最终淘汰老项目

解决方案:

  1. 老项目地址xxx/home, 新项目地址xxx/new, 这个需要后端同学配置nginx代理, 监测到xxx后面的地址进行分配, 这样两个工程的静态资源也要做好代理的区分, 我们需要配置好publicPath.
  2. 俩个工程的一级二级导航栏做到完全一样, 甚至悬停出现的弹框也要做到完全一致(视觉骗子), 还好这个项目导航方面并不是很复杂.
  3. 添加一个专门的router拦截函数, 老项目: 监测到new字段就跳转到新项目, 新项目检测到home路径就跳到老项目, 这样写对于404有bug最后我说解决的办法.
  4. 后台代码也跟我们一期一起重写, 这就要兼容新老后端代码.

404问题:
如果监测到new字段就跳转到新项目, 检测到home路径就跳到老项目, 这个会有bug, 比如我乱打一个xxx/jjj那么这个就会留在原地, 我们就需要在'新老'两个项目里面都写一个'404页面', 这一点想到了那么如果是这样 xxx/new/jjj这又是一个'404页面'但是他带new字段, 那么它跳到新项目又变成了bug, 我想到的解决方案是 new这个新项目只要检测到不是自己路由表里的地址, 就把这个连接指向老项目, 老乡维护一套新项目的路由地址list, 只有在list内才会进行跳转新项目的操作, 这样bug就解决了, 可能你会有更好的方法哦可以一起交流.

401与303 前端 测试 后端:

这里是个交流沟通的问题
问题是这样的, 老项目接口未登录报303, 新项目接口未登录报401, 这个现象看起来很好办, 只要检测到 303或是401统一跳登录页面, 但是问题来了, 这两个状态码的判断标准不一样, 出现了'循环跳转'的bug.

比如说: 你在老项目里面不报303那么登录页面就把你转到'来时的'路径, 但是这个路径如果指向新项目, 但是新型项目里面报了401, 导致新项目又跳回登录页面, 日复一年年复一年的循环跳转.

这种问题是沟通问题

只要出现循环的跳转, 测试就会来找到我们, 因为这看起来绝壁是前端问题啊, 你死循环管后端什么事, bug提给我不管是不是我的问题, 有时间的话我都会帮着排查与修复, 我把这个原因搞清楚后去跟后端同学沟通, 这个过程挺慢的, 毕竟谁也不希望bug是自己的..., 可是这个问题到第一次上线他们也没有去解决, 线上又报循环跳转测试仍让我紧急修复, 这个虽然能够理解但也挺无语, 后来用的技巧就是把问题在群里抛出来, 用简短的话把问题说清楚, 不能"看着像前端问题就前端改", 当然了我跟后台同学了解下303余与401的判断标准, 在前端侧也增加了一层登录判断, 虽然没啥大用但是也能帮后端同学解决95%的差异了, 可是剩下5%的问题我已经帮他们分析出解决方案, 可是也放在下个版本做了.

通过这些问题能够感受到, 前端不光是写代码, 也有统筹整个项目的责任在身上, 积极整合大家也是一种锻炼

4:element表单隐藏input

故事是这样的
作者接到了一个离职者的遗留工程的新增需求,需求很简单:在一个表单中根据某一项的选择情况来决定显示与隐藏一个input,比如'类型下拉框'选择‘个人’就隐藏‘订单号’反之则显示并且为'必填', 这个需求简直简单到爆。

bug描述
当先不选择'个人', 然后再选择'个人'选项时,‘订单号的input’消失了但是报错提示信息'订单号为必填'还是存在。

bug初步分析
element-ui本身是否有缺陷, 我用v-if控制输入框的隐藏它没有检测到。

bug初步解决
在我隐藏这个输入框后, 100毫秒的时候调用一次表单的验证方法,虽然你能够解决但这个方法给人的感觉就是临时方案。

bug深入分析
建一个vue工程把这个bug情况在纯净的新工程里面还原一次,结果并未复现, 这就说明这bug跟人家element没有关系,定位在代码本身有问题就好了,问题的元凶就在@change这个事件里面, 上一位同学是在行间这样写的。
@change="() => { updateDesc(); updateYtsUserType(); updateMonitoring() }"
而在updateMonitoring方法里面有一个对表单的校验,这个updateMonitoring方法还有5处地方在调用,所以并不建议修改这里, 导致这一bug的原因是@change事件与vue的数据更新机制未衔接好, vue更新一个值是要经过diff算法的, 更新数据采用三种方案,1:事件的订阅发布 2: promise 3:settimeout 逐级兼容, change事件恰好在这之前就调用了表单的验证,所以把这个检验放在下一个宏任务就不会出现这个bug了, 也不会出现错误信息一闪而逝的情况。

想起了聚焦与失焦的问题(某些移动端有bug)
之前做移动端项目有三个input, 我需要监控当前用户从某个input框里面填完再跳到某个input框的操作路径, 并做一些相应的处理,具体的业务场景我记不清了, 但是当时我发现,比如我再inputa里面输入内容后跳到inputb里面,有时候是先执行的inputb的聚焦 然后 才是inputa的失焦 并且这个时间不是固定的, 因为当时我搞了个0秒延时器并不是100%奏效, 这个点大家可以注意一下。
正常因该是 a聚焦-> a失焦-> b->聚焦 pc端还挺好

5:上传打包文件的小优化, 小而美提升工作体验

这种事情我也认为属于小bug, 毕竟程序员是追求高效的, 而且减少操作步骤可以减少出错的概率。

公司项目组暂时由我们上传到服务器,自动化正在搭建。

之前上传代码
1: 打包
yarn build
2:压缩
把打包好的dist文件压缩为zip格式
3:scp上传dist.zip文件
4:ssh连接服务器
5: 进入指定目录, unzip解压dist

现在传代码
1: 打包
yarn build
2:scp上传代码
scp -r dist/* root@1.1.1.1:/home/xxx/xxxt

反思
如果包并不是特别大的话或者网络很不好, 建议使用第二种, 不以优化小而不为,不以危害小而为之。

6: Cannot assign to read only property 'exports' of object '#<Object>'

这个又是接收了一个离职同学的老项目

bug描述
拉新项目-> 切分支-> yarn 一切正常,打开地址就报这个错.
中文意思就是这个项目里面混合使用了common的规范与es的规范, 需要我统一规范, 就比比如使用了 module.exports 也使用了 import

bug初步解决
使用插件让两者兼容就解决了
npm install babel-plugin-transform-es2015-modules-commonjs

babelrc配置
{ "plugins": ["transform-es2015-modules-commonjs"] }

思考
这个问题应该不会是个需要下载插件或者修改代码才能解决的问题, 毕竟开发了很久, 如果启动都有bug那怎么上线的, 所以问题应该就存在于流程上, 或者外部的环境上, 比如它使用了我本地的全局webpack的话那就有可能是我webpack版本问题, 但查了一下package.json文件并无这种情况,那问题就在操作流程上。

bug解决
原因很简单, 这个是个老工程使用npm管理版本, 而我习惯性的用了yarn, 导致yarn命令并不能读取package-lock.json文件,那么我下载的版本与之前同学使用的版本可能出现了不同, 删掉node_modules文件夹npm i, 解决问题

反思:npm与yarn的锁文件可否用插件兼容
npm: package-lock.json
yarn:yarn.lock
可否做一个小插件把这两种文件类型与数据相互的转换, 我分别看了下这两个文件的格式如下
npm
yarn

文件类型各不相同, 还有一个问题就是这两个工具的源也有差异, 可能会出现某个插件某个版本yarn可以安装, npm里面没有等等问题, 这就导致类似的兼容插件没有大的意义了。
总结
大家还是暂时老老实实使用过同一种包管理工具把。

7: 厉害的hover

hover思维的局限
刚入门的时候一个老师给我讲,标签的:hover只能修改这个元素本身与这个元素内部的元素, 所以当时写一个弹出菜单的话需要把这个'标题''弹出框'写在一个div里面, hover这个div的时候弹出框block, 但是其实hover可以选择兄弟元素, 可以选择远方的兄弟元素, 可以给兄弟元素增加条件
举例子

相隔的一个兄弟元素内部的span
 <div class="home">
    <div class="main">被hover的元素</div>
    <div class="content">相邻的元素</div>
    <div class="content">
      <span>远处的元素</span>
      <p>我不变色</p>
    </div>
  </div>
    .main:hover +.content +.content >span {
      color: red;
    }

8:after变色变文案

需求描述
比如我现在有6种数据类型, 每种类型前面要有一个‘彩色的点’来快速表明他是什么类型如下图:
g.png

要解决的问题

  1. 这种修饰性的结构一般第一想法都是用after和before做。
  2. 圆内的文字需要动态传入
  3. 颜色能动态传入就完美了
  4. 因为这个数据可能上百条,所以不用真实dom很有必要。
  5. 不可以用js来修改, 因为不想把简单问题弄得复杂,导致代码工程化被破坏。
  6. 模块化,最好其他地方加上一个class名就有了这个效果。

解决问题

  1. 这个要做成模块,中间的文字就要可配,我想到了attr()函数。
  2. 颜色实在没想到动态的传入方式, 只能退而求其次用scss老哥的@each助阵一下了, 还好最多只有6种配色。
  3. 一个class还不够, 需要data-*属性来配合。

上才艺,咳咳不是,是上代码

<div data-color="red"  data-title="1">动漫</div>
    <div data-color="green" data-title="2">小说</div>
    <div data-color="blue" data-title="3">狗狗</div>
*[data-color]::after {
  content: attr(data-title);
  color: white;
  align-items: center;
  display: inline-flex;
  justify-content: center;
  width: 20px;
  height: 20px;
  font-size: 12px;
  margin-left: 10px;
  border-radius: 50%;
}

@each $color in red, green, yellow, blue {
  *[data-color="#{$color}"]::after {
    background-color: $color;
  }
}

注意
attr只能在content中生效。
如果attr不是只能在content里生效就好了,我们就可以完美解决这一问题,这方面现在也在提案中, 但是暂时没有浏览器支持。

受限于css的本性, 这里并不完美, 颜色方面还是要传,如果大家有好的方法可以私信讨论一下。

9: 复制也很有意思

问题描述
我们前端工程师无时无刻都在与json打交道(或是一个大对象),比如我们生成了一个很长的json或者是ajax获取到了一段很长的json, 我们想要把这个json拿出来单独作为一个文件本地引入, 或者是在控制台看着累要拿到某些工具中进行解析,为了提升性能会出现很多..., 除此之外还会出现对象类型需要手动打开等情况, 说实话复制出来不是太方便

起因是一个晨会的知识分享

用鼠标复制还是处于网民阶段, 成需要肯定是利用方法

同学a:

使用window.copy方法把数据复制到剪切板里面就可以拿到了, 对于开发环节来说又不用在乎兼容性, 这个方法我试了一下也并没有长度限制挺好用的。

同学b:

使用js直接把json数据生成在一个文件里面岂不是更方便, 听到这个的时候就是感觉js不是不可以操纵用户的文件系统么。。。 有点打破我的固有认知, 但是试了一下他提供的方法, 还真不错,下面我就介绍一下这个方法与原理:上才艺

(function(console){

  console.save = function(data, filename){

  if(!data) {

  console.error('Console.save: No data')

  return;

  }

  if(!filename) filename = 'console.json'

  if(typeof data === 'object'){

  data = JSON.stringify(data, undefined, 4)

  }

  var blob = new Blob([data], {type: 'text/json'}),

  e = document.createEvent('MouseEvents'),

  a = document.createElement('a')

  a.download = filename

  a.href = window.URL.createObjectURL(blob)

  a.dataset.downloadurl = ['text/json', a.download, a.href].join(':')

  e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)

  a.dispatchEvent(e)

  }

})(console)

console.save({a:1})

// 这里是解释版, 每个语句的意思都详细的解释

(function(console){
  console.save = function(data, filename){
  if(!data) { // 1:没东西没有意义, 大部分可能是复制错了
      console.error('Console.save: No data')
     return;
  }
  // 2:名字肯定要有个
  if(!filename) filename = 'console.json'

  if(typeof data === 'object'){ // 3:转成字符串
    // 4: JSON.stringify
    // 第一个参数:就是数据了
    // 第二个参数:指定哪些key需要处理, undefined就是都处理
    // 第三个参数: 层级之间的空格缩进数
     data = JSON.stringify(data, undefined, 4)
  }
  // 这个构造函数厉害了: Blob
  // Blob类型的对象表示不可变的类似文件对象的原始数据。
  // Blob对象是二进制数据,但它是类似文件对象的二进制数据,因此可以像操作File对象一样操作Blob对象
  // Blob 表示的不一定是JavaScript原生格式的数据(这句最关键)。File 接口基于Blob
  // 第一个参数是数组, 就是个拼接,这个自己拼也行
  // 第二个参数是配置, 这里我们指定为json文件
  var blob = new Blob([data,data], {type: 'text/json'}),

  // 自定义一个事件
  e = document.createEvent('MouseEvents'),
  // a标签没什么好说的
  a = document.createElement('a')
  // 必须定义, 否则变成了跳转
  a.download = filename
  // URL就是定义本地路径的api, 我们做一个上传组件 或是裁剪组件的时候, 会用这种方式展示本地的图片
  a.href = window.URL.createObjectURL(blob)
  // dataset就是获取属性的意思
  // 格式->> text/json:文件名:blob数据
  a.dataset.downloadurl = ['text/json', a.download, a.href].join(':')
  // 我们的自定义事件配置
  e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
  // 触发我们的自定义事件
  a.dispatchEvent(e)

  }
})(console)

console.save({a:21},'来段静态文件')

虽然用处真的不大, 但是开阔了思路对未来的变成之路也是有好处的。

end

本次的分享就是这样,我深刻的感受到‘好的解决方法’很多, 最缺少的是好的问题,欢迎交流, 祝每天进步


lulu_up
5.7k 声望6.9k 粉丝

自信自律, 终身学习, 创业者