本期
这次 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 里面的内容。
有以下两个解决方案:
- 手动删除修改 known_hsots 里面的内容;
- 修改配置文件“~/.ssh/config”,加上这两行,重启服务器。
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
优缺点:
- 需要每次手动删除文件内容,一些自动化脚本的无法运行(在 SSH 登陆时失败),但是安全性高;
- 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>
效果如下图(参考了网上大家的做法, 比官网还靠谱)
3: 样式一样的新老项目共用一个域名.
背景:
一个辗转了三个团队, 维护了快三年的老 vue 项目, 代码简直乱到令人发指的程度, 比如请求报错了 /api/home/list/ip 这样的地址报 401, 那么我去查找这个地址在哪里发出的请求, 全局也没搜索到这个地址, 一个小时后我才知道 这个地址被分成了类似这种 -> , const a = ‘api’, const b = ‘home’, const c = ‘list’, 请求的时候 axios.get(/${a}/${b}/${c}/ip
), 不仅如此, 这个请求还被放在的 vuex 里面辗转反侧三个配置页面然后被各种重新命名之后不知所踪, 关键是连个文档也没有, 这个项目在我来到公司之前就已经是一个人人不愿碰的存在了.
我刚加入就接到了改造这个项目的任务
经过讨论这个项目是 ” 救不活了 ”, 但是我们可以另起一个一模一样的项目, 然后所有的新需求都放在新项目里面, 每次需求都把老项目的页面迁移过来一个, 这样一年半载之后就可以彻底使用新项目代替老项目
需要解决的问题:
- 两个项目域名相同
- 切换时尽量做到用户无感
- 共用一套登录系统
- 平稳迁移, 最终淘汰老项目
解决方案:
- 老项目地址 xxx/home, 新项目地址 xxx/new, 这个需要后端同学配置 nginx 代理, 监测到 xxx 后面的地址进行分配, 这样两个工程的静态资源也要做好代理的区分, 我们需要配置好 publicPath.
- 俩个工程的一级二级导航栏做到完全一样, 甚至悬停出现的弹框也要做到完全一致(视觉骗子), 还好这个项目导航方面并不是很复杂.
- 添加一个专门的 router 拦截函数, 老项目: 监测到 new 字段就跳转到新项目, 新项目检测到 home 路径就跳到老项目, 这样写对于 404 有 bug 最后我说解决的办法.
- 后台代码也跟我们一期一起重写, 这就要兼容新老后端代码.
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
可否做一个小插件把这两种文件类型与数据相互的转换,我分别看了下这两个文件的格式如下
文件类型各不相同,还有一个问题就是这两个工具的源也有差异,可能会出现某个插件某个版本 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 种数据类型,每种类型前面要有一个‘彩色的点’来快速表明他是什么类型如下图:
要解决的问题
- 这种修饰性的结构一般第一想法都是用 after 和 before 做。
- 圆内的文字需要动态传入
- 颜色能动态传入就完美了
- 因为这个数据可能上百条,所以不用真实 dom 很有必要。
- 不可以用 js 来修改,因为不想把简单问题弄得复杂,导致代码工程化被破坏。
- 模块化,最好其他地方加上一个 class 名就有了这个效果。
解决问题
- 这个要做成模块,中间的文字就要可配,我想到了 attr()函数。
- 颜色实在没想到动态的传入方式,只能退而求其次用 scss 老哥的 @each 助阵一下了,还好最多只有 6 种配色。
- 一个 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
本次的分享就是这样,我深刻的感受到‘好的解决方法’很多,最缺少的是好的问题,欢迎交流, 祝每天进步