• puppeteer使用代理来爬取网络数据

    前言: 关于爬虫,在网络上的爬取数据总会让目标网站反感,但是目标网站又无从得知该用户是否为爬虫,于是就通过一些方式去做判断,最为常用的方式便是屏蔽可能是爬虫ip的访问,将其重定向到另外一个页面..这个问题对于爬虫方解决起来不难 ----- 代理. 代理: 要使已经被屏蔽设备网络ip能够重新访问目标网络,使用代理是最简单的方式,意思是,你访问目标网站时,不要直接访问目标网站,而是通过中间层去访问,你把请求发给中间层,让中间层把你的请求发送到目标网站,目标网站把返回的资源重新发送回给你,这样你与目标网站便是间接接触,目标网站无法得知你的真实ip. 代理方式:普通代理与隧道代理..两者方式最主要的区别是前者是主动获取代理ip,后者是自动获取访问的ip..前者在你使用代理的时候,当无法再访问目标网站的时候,每一次都需要重新获取代理ip,而后者只需要获取一次ip,后面切换ip的工作在ip服务器上自动切换. 使用: 我使用的是puppeteer来爬取网站上的数据,使用方式也比较简单: 在获取到代理ip(proxyServer)后:
    const browser = await puppeteer.launch({
            args: ['--no-sandbox', '--disable-setuid-sandbox', `--proxy-server=${proxyServer}`]
    });
    我使用的代理服务是芝麻http代理,使用起来也方便..有兴趣的可以去搜索一下... 总结: 加深了对网络的认知,也是自己基础知识的学习  
  • 我的2020年总结

    前言: 时间可真快,不知不觉又是一年过去了,又到了总结一年经历的时候了. 2020年从海康离职,就职于国铁吉讯,同事依然很nice,这家公司环境相对开发,工作压力相对于海康要轻松的多,没想过作为一个程序员开发能有正常的双休时间,感谢公司能给我这样的体验机会吧. 一:工作. 今年都干了些啥?
    1. 这一年多的时间,把服务器的知识熟悉了,因为上线了一个真实node服务项目,也很感激领导的信任,能给我这样的机会去实践node中间层服务,以前虽然有写过node服务,但是都是仅限于自己的项目,并没有真实的项目经验.通过这次的经验,也让我清楚的了解后端开发的链路,原来和自己开发博客系统并无多少差别.
    2. 上线了一版数据可视化大屏,也算是自己的新领域的突破吧,毕竟以前没有真正玩过这个领域,以前虽然有接触过,但都没有实际的项目.
    3. 记得刚进公司接手的第一个项目,项目极大,1000+的路由地址,然后我看到这1000+的路由全部分布在一个js里,当初接手这个项目的时候为了改一个路由要花好几分钟去找修改的地点,这让我比较奔溃,后面也经过我的改造,按模块去拆分了,也为这个项目添加了大量的公用组件.为什么会出现这种情况,也许和我们公司的管理有关系,2个外包,大部分的工作量得让外包来写,外包也许遵守的原则的尽快上线,他们考虑的利益是如果通过这个项目让我多学一些知识,后面外包走后,这一堆事就交给了我,我也不得不去看1000+的代码(不仅是路由1000+,实际业务也有好多的1000+行的代码),后面修改起来也是非常费劲的事,想去整改,但又无能为力.
    在国铁的工作,更让我觉得代码质量的重要性,代码真的不是给机器看的,有时候更的时候是给人看的,在海康的时候我也一直遵守这样的原则,后面在交接工作的时候也交接的非常顺利,交接工作的同事并没有太多的问题,我也能按时顺利的离职..为了能够达成想要的效果,在很久以前也去看了大佬的源码,ant-admin的那个后台代码,也学习了一点经验,后面自己的项目也会严格按照相关的流程去书写,增加代码的可读性. 2020年自我感觉最好的一个项目: 并非是公司的项目,而是自己的项目-----爬虫 是爬取菜谱网站的一个小项目,数据源有10万+条,这也是占据我业余大部分时间的小项目.项目代码大概600行,共导出接口10个..使用puppeteer获取我需要的页面数据源,通过模拟真实人员操作去获取页面的数据,当然该网站也不会轻易让我爬取数据,也给加上了一些限制,比如IP的访问限制,这个也难不倒我,可以通过代理置换访问的代理IP,这样便可以继续访问了,当然还有比较笨的方式,在本地跑的时候,就是重启路由器. 其它小项目---- autojs: 自动采集蚂蚁能量,淘宝双11自动签到等,这个还有挺有意思的,也创建了一个群,有兴趣的可以去找找我的相关博文.   二: 生活 2019年的时候一家人还分居两地,今年老婆小孩都来杭州了,一家人终于在一起住了,小家也终于有小家的样子了.虽然平时会吵架,但也不影响这个小家. 老婆在家全职带小孩,自己的压力自然要大一些,也正是老婆全职带小孩,自己也才有更多的精力忙自己的事. 小孩子确实事多,也给这个家加了很多欢乐,希望以后越来越好吧! 周末也会带小孩出去转转,去的最多的就是钱江世纪公园了吧.. 在杭州第一次为小孩过生日,买了他最喜欢的赛车和飞机作为礼物.   三:展望 记得去年提的目标有好几个未能达成的,前后思考了一下原因,没那精力去搞,最主要的目前的工作用不上相关的技术,在海康的时候未来也许能用的上... 希望今年的我再多多学习一些知识,多涉及一些未知领域,也不知道自己的能力上限有多少,2020年支付宝余额的目标还差个年终奖.    
  • 关于CSS module scoped相关的知识

    背景: 日常在使用react,vue做开发写CSS的时候,会不会遇到CSS污染呢?我和另外一个小伙伴写的css名称是一样的,结果我写的css被另外一个小伙伴给覆盖了,至少我就有遇到过,不过是几年前的事了,后面由于自己有一套命名规则,这类事故基本就没发生过了.css module也是为了防止css污染而产生的,它会给你的class名称加上hash字符串,避免命名污染.下面就用vue与react来实现相关的css隔离. 在vue里如何避免css污染?
    1. <style scoped>
        .self-repair{
          margin-top: 16px;
        }
      </style>
    加上scoped经过编译后: <div data-v-f9904c26="" class="self-repair"> vue会给所在元素上加上data-v  随机字符串,浏览器生成的样式为:
    .self-repair[data-v-f9904c26] {
        margin-top: 16px;
    也自动为这个class多加了一个属性判断,这样便能保证其唯一性.
    但是这个也同时也引起了一个问题,如果修改子组件里的css样式呢? 这个时候深度选择器就可以用上了,可以在要修改子组件的样式前加上 /deep/  或者 >>>,例如:
    .self-repair>>> .van-button__text{
        color: red;
      }
    .self-repair/deep/ .van-button__text{
        color: red;
      }
    经过编译后的在浏览器上是这样的代码:
    .self-repair[data-v-f9904c26] .van-button__text {
           color: red;
    这样就可以穿透到子组件修改其样式了.
    关于css module 这个在react与vue中都有使用到,原理都差不多,都是把css当成模块来引用
    vue中的写法:
    <style module>
      .selfRepair{
        margin-top: 16px;
    
      }
    </style>
    <div :class="$style.selfRepair">
      提交
    </div>
    这样便可以生效了,在style里加上module,这个webpack在编译的时候就知道如何去编译这段css了,通过编译后的代码如下:
    .index_selfRepair_2B4Cz {
        margin-top: 16px;
    也是带有hash值的,保证了唯一性.
    在react也基本同理,把css引入,进而像引用函数方法一样把这个样式放到元素上,均可以保证样式的唯一性.
    总结:
            自己在开发的过程中心面的scoped与module其实并不常用,因为有时候为了复用css,会把样式写到全局,如果用了module的方式就会很死板,必须要通过引入才能生效,那我日常开发是如何保证不重复呢?其实很简单,css也有自己的命名规范----BEM,这个也只是针对新项目来说,对于一些老项目,别人从来不按照类似的规范来写,只能越填越乱,有时间为了表示唯一性,就加上了自己名字拼音的首字母.
  • useCallback memo useMemo useEffect 用法详解

    背景: 今天花时间把前端的代码整理了一下,有很多地方值得记录以加深学习印象,最主要的还是react的新特性,项目中大量用到了useCallback,memo,useMemo,useEffect,下面就这些特性来说明,用自己的语言组织来说了. useEffect effect即副作用,即别的数据的变化会重新调用此副作用里的函数.
    useEffect(()=>{
       console.log('mount');
       return ()=>{
          console.log('did update done')
      }
    },[name])
    当name的值改变的时候,会重新mount,这个方法在项目中有很多处的应用,比如搜索表格数据,name即搜索条件,当name重新被赋值的时候即重新拉取表格数据,如果有多个搜索条件的时候也可以加到依赖中. 副作用函数里的return则是在页面被卸载时调用. useEffect相当于老版本class里的componentDidMount,componentDidUpdate,componentWillUnmount等生命周期的集合,这个hooks在项目中属于基本的应用,也是必须掌握的.   memo 这个hook是针对组件的,减少组件的不必要更新. 子组件
    const TextCell = memo(function(props:any) {
      console.log('我重新渲染了')
      return (
        <p onClick={props.click}>ffff</p>
      )
    })
    
    父组件
    const fatherComponent = () => {
    const [number,setNumber] = useState(0);
     return(
        <div>
          模块{number}
          <TextCell/>
    
          <Button onClick={()=>setNumber(number => number + 1)}>加加加</Button>
        </div>
      )
    }
    
    
    在这里如果没有用到memo 每次父组件重新setNumber,子组件都会重新渲染一次,加上了后只会在初始化的时候渲染,减少了子组件渲染的次数,这个在之前老的class方法,要减少子组件的渲染,可以使用pureComponent,或者在生命周期 componentWillReciveProps方法里返回false即可.   useCallback 这个hook主要是针对函数的,为什么会有这个hook呢? 比如: 子组件
    const TextCell = memo(function(props:any) {
      console.log('我重新渲染了')
      return (
        <p onClick={props.click}>ffff</p>
      )
    })
    
    父组件
    const fatherComponent = () => {
    const [number,setNumber] = useState(0);
    
    const handleClick = useCallback(()=>{
       console.log(33)
    },[])
     return(
        <div>
          模块{number}
          <TextCell click={handleClick}/>
    
          <Button onClick={()=>setNumber(number => number + 1)}>加加加</Button>
        </div>
      )
    }
    这里如果不使用useCallback,哪怕子组件用memo包裹了 也还是会更新子组件,因为子组件的绑定的函数click在父组件更新的时候也会更新引用地址,导致子组件的更新,但是这个其实是没必要的更新,绑定的函数并不需要子组件更新,useCallback就是阻止这类没必要的更新而存在的. 如果是用class的方法的话,需要在constructor里绑定函数,会比较麻烦,不易阅读. 这里需要注意的是 如果是有参数需要传递,则需要这样写 <TextCell click={useCallback(()=>handleClick('传递的参数'),[])}/>   useMemo useMemo会在页面初始化的时候执行一次,并把执行的结果缓存一份
    const fatherComponent = () => {
    function expensiveFn() {
      let result = 0;
      for (let i = 0; i < 10000; i++) {
        result += i;
      }
      console.log(result)
      return result;
    }
    
    const base = useMemo(expensiveFn, [number]);
    return (
       <div>{base}</div> 
    )
    }
    在这个地方如果没有使用useMemo而是 直接用 const base = expensiveFn(),则在页面每次刷新的时候都会重新调用这个方法,影响页面的性能,而如果使用上了useMemo,react会把expensiveFn的执行结果缓存一份,即使页面刷新了,也不会再执行expensiveFn函数.useCallback和useMemo的区别其实就是前者是缓存过程,而后者是缓存结果.这个在页面上有时候会应用到select里的选项数据源的上下级联动.这里的数据源一般不会改变. 最后总结: 这里就说这么多了,16版本的react在很多写法真的做了很多升级,不适应也是很正常的,老的方式在性能上其实也不会很差,如果追求性能极限以上的内容都是可以参考的,掌握以上的内容react新版本的功能便可以掌握70%了,剩余的一些用法其实并不常用,像自定义hook,useContext,useRecduer
  • 使用苹果电脑无法在服务器中使用rz 怎么办?

    背景: 作为前端,一些基本的服务器知识还是需要的,今天的需求是把本地的文件上传到指定的服务器,由于公司使用了跳板机,无法使用scp把文件上传上去,只能通过服务器的rz方式来上传文件. 碰到问题: 在服务器上使用rz的时候,服务器给出了下面的内容:B0100000023be50ive.**B0100000023be50   看不懂什么意思,但是那个上传文件的弹出框始终是用不了的,在这个地方停留了近2分钟,最后便退出了.本来以为是原生命令行工具的问题,后面下载了iterm2同样也不行.. 解决问题: github上搜索了一波,发现了这个仓库:https://github.com/aikuyun/iterm2-zmodem,确实帮我解决了问题,原来是没有安装相应的插件. 方案: 在电脑上安装lrzsz 1.安装支持rz和sz命令的lrzsz:brew install lrzsz 2.在本地/usr/local/bin/目录下保存iterm2-send-zmodem.sh 和iterm2-recv-zmodem.sh两个脚本 3.设置一下两个脚本的权限,一般 chmod 777 就行了
    chmod 777 /usr/local/bin/iterm2-*
    4.设置Iterm2的Tirgger特性,profiles->default->editProfiles->Advanced中的Tirgger 添加两条trigger,分别设置 Regular expression,Action,Parameters,Instant如下:
    1.第一条
            Regular expression: rz waiting to receive.\*\*B0100
            Action: Run Silent Coprocess
            Parameters: /usr/local/bin/iterm2-send-zmodem.sh
            Instant: checked
    2.第二条
            Regular expression: \*\*B00000000000000
            Action: Run Silent Coprocess
            Parameters: /usr/local/bin/iterm2-recv-zmodem.sh
            Instant: checked
    
  • 双11来了 收猫币麻烦? 使用autojs实现个自动脚本解放双手

    前言: 双11来了,我也是一个剁手党,当然要想办法能省点钱算一点,双11的活动是攒喵币,和往年一样,点击各种商家页面来获取,这个过程是一个重复的过程,往年我都是手动点完的,但是今年真不想再继续做这些重复的事了,于是就有了下面这个自动脚本.   技术调研: 在网上也搜索了好多的方式方法,最终还是使用了autojs这个脚本工具,正所谓物理外挂最为致命,这个也有点类似物理外佳了.   实现效果: 还有自动浏览店铺的功能,这里没办法上传效果图 实现代码: 这次双11,在淘宝与支付宝中都有活动,所以写了两个项目,不过代码逻辑大体都差不多,主要是点击到活动页面=>等待15秒 => 返回上一层页面... 然后循环 代码贴上:  
    var baseWidth = 1080,baseHeight = 2400;
    var height = device.height;
    var width = device.width;
    
    setScreenMetrics(baseWidth,baseHeight);
    
    threads.start(function(){
      events.observeKey();
      events.on("key_down", function(keyCode, events){
        if(keyCode == keys.volume_up){
          toastLog('停止脚本使用')
          exit();
        }
      });
    });
    var canCapture = requestScreenCapture();
    if(!canCapture){
      toast('请授权截图!')
      exit()
    }
    
    launchApp('手机淘宝')
    sleep(3000);
    
    click(820,860);
    
    sleep(6000);
    
    let catImg = images.read('/sdcard/catImg.jpg');
    if(catImg === null){
      catImg = images.load('https://pic1.zhimg.com/80/v2-8a9af44ba48b553fbbdafd96475ad47c_1440w.jpeg'); // 图片不能为png格式的 无法加载到
      catImg.saveTo('/sdcard/catImg.jpg');
    }
    
    // let screenCaptureImg2 = images.grayscale(screenCaptureImg)
    // images.save(screenCaptureImg2,'/sdcard/screenCaptureImg2.jpg')
    sleep(2000);
    
    // 如果有11月11日见弹出框 则关闭
    var eleven = text('好的,11月11日见').findOnce();
    if(eleven){
      click('好的,11月11日见');
      sleep(2000)
    }
    
    click(942,1973); // 点击  赚喵币
    
    
    sleep(1500)
    while(true){
      var goText = text('去浏览').findOnce();
      if(goText){
        click(goText.bounds().centerX(),goText.bounds().centerY());
        sleep(5000)
        swipe(baseWidth/3,baseHeight - 200,baseWidth/2,200,2000);
        sleep(13000);
        back();
        sleep(2000);
        click('领取奖励');
        sleep(1000)
      }else{
        var goSearch = text('去搜索').findOnce();
        if(goSearch){
          click(goSearch.bounds().centerX(),goSearch.bounds().centerY());
          sleep(5000)
          swipe(baseWidth/3,baseHeight - 200,baseWidth/2,200,2000);
          sleep(13000);
          back();
          sleep(2000);
          click('领取奖励');
          sleep(1000);
          
        }else{
          var array = ['逛一逛"潮酷新品"(0/2)','逛一逛"潮酷新品"(1/2)','逛一逛"潮流趋势"(0/2)','逛一逛"潮流趋势"(1/2)','逛一逛"时尚配饰"(0/2)','逛一逛"时尚配饰"(1/2)','逛一逛"家居百货"(0/2)','逛一逛"家居百货"(1/2)'];
          var goTo = null;
          for(var i = 0;i<array.length;i++){
            var a = text(array[i]).findOnce();
            if(a){
              goTo = a;
              break;
            }
          }
          if(goTo){
            click(goTo.bounds().centerX(),goTo.bounds().centerY());
            sleep(2000)
            swipe(baseWidth/3,baseHeight - 200,baseWidth/2,200,2000);
            sleep(15500);
            back();
            sleep(2000);
            click('领取奖励');
            sleep(1000);
          }else{
            break
          }
        }
      }
    }
    toastLog('结束!');
    click(974,650); // 点击右上角关闭
    exit();
    
    
    
    
    function readImg(name,url){
      let catImg = images.read('/sdcard/'+name);
      if(catImg === null){
        catImg = images.load(url); // 图片不能为png格式的 无法加载到
        catImg.saveTo('/sdcard/'+name);
      };
      return catImg
    }
    这段代码是用于淘宝收取喵币的,前后仅花了2小时不到,这一块内容还是比较简单的,主要之前写过类似的代码,有些经验了. 遇到的问题: 1.点击文字,由于不确定一些字体是图片还是纯文本,所以在调试的时候,都是凭感觉去试 2.活动的文本较多,有些地方情况无法用找图片的方式来定位要点击的位置,但是文字内容又不确定,所以只能把可能的文本内容情况给加上,比如这一块     var array = ['逛一逛"潮酷新品"(0/2)','逛一逛"潮酷新品"(1/2)','逛一逛"潮流趋势"(0/2)','逛一逛"潮流趋势"(1/2)','逛一逛"时尚配饰"(0/2)','逛一逛"时尚配饰"(1/2)','逛一逛"家居百货"(0/2)','逛一逛"家居百货"(1/2)']; 现在是把我可能遇到的情况都加上了,如果后面支付宝有增删改查的话,也需要跟着改一下逻辑... 3.手机兼容,由于自己只有一个手机,只能使用自己的手机来测试,虽然官网有说可以通过方法setScreenMetrics(baseWidth,baseHeight)来保持比例,但是自己没试过 也许还是不行呢. 4.还有各类的点击可能性,并没有完成覆盖,有些去完成的任务只能通过手点来获取喵币. 5.autojs是好早之前的框架,现在已经不维护了,有一些es6的语法是不支持的,比如const,let,尽量不用到新语法来写业务逻辑. 6.各种sleep 需要等待app内容加载完成,如果页面有不确定因素也是需要延长下一步的执行逻辑的.     总结: 小东西有大用处,在接下来的几天时间能够帮助我节省好多的时间,让自己感觉高大上一些,多探究一些小玩意,对自己以后的职业生涯也有帮助.. 另外有兴趣的可以加群一起玩转autojs... 上面过期了加我好友 我拉入群吧 一定要备注autojs  
  • taro实现微信小程序手写电子签名功能

    背景: 之所以写这个功能,是因为手写签名应该是canvas功能点最少的一个组件了吧,在微信小程序手动实现一波,有利于后面对小程序canvas的入门,先看效果. 效果: 未签名状态 随意签名状态

    实现:

    技术点1: 为了保证用户的最大化签名区域,采用了横屏的形式. 小程序并非微信小游戏,没有横屏的概念,那就只好自己手动横过来了, 签名区域与交互区域都横屏了,实现难点主要在css上,实现css如下:
    .canvas-box {
      width: calc(100vw - 120px);
      height: 100vh;
      margin-left: 120px;
      position: relative;
      .text_warp{
        .text-detail{
          width: 300px;
          top: 50%;
          left: 50%;
          transform: translate(-50%,-50%) rotate(90deg);
          position: absolute;
          font-size: 50px;
          color: #333;
          white-space: nowrap;
        }
      }
    }
    这里没做旋转,只是把高宽控制了一下. 技术点2: 左方的文字区域则做了相应的旋转:
    .layout-flex{
      display: flex;
      justify-content: space-between;
      position: absolute;
      top: 0;
      left: 100px;
      width: 100vh;
      transform: rotate(90deg);
      transform-origin:left top;
      height: 120px;
      .read{
        display: flex;
        line-height: 100px;
        font-size: 20px;
        .at-checkbox{
          &:before{
            display: none;
          }
          &:after{
            display: none;
          }
        }
        .at-checkbox__option-wrap{
          padding-right: 0;
        }
      }
      .btn-warp{
        display: flex;
        .at-button + .at-button{
          margin: 0 10px;
        }
      }
    }
    旋转的时候记得配置一下origin,旋转原点,默认是中间的.
    
    
    技术点3:js部分的代码
    import Taro, {Component, Config} from '@tarojs/taro';
    import {View, Button, Canvas, Text} from '@tarojs/components';
    import {APP_URLS} from '@/utils/api/api_urls'
    import {upload} from '@/utils/api/httpRequest'
    import './index.scss'
    import {
      AtButton,
      AtCheckbox
    } from 'taro-ui';
    
    let ctx = Taro.createCanvasContext('canvas', this);
    let startX = 0;
    let startY = 0;
    let canvasw = 0;
    let canvash = 0;
    
    
    export default class Signature extends Component {
    
      config = {
        navigationBarTitleText: '电子签名'
      }
      state = {
        isPaint: false,
        tempFilePath: '',
        checkedList: ['list1'],
        isShow: true, // 是否展示文字说明
        trustorIndex: 0,
      }
    
      initCanvas() {
        ctx = Taro.createCanvasContext('canvas', this);
        ctx.setStrokeStyle('#000000');
        ctx.setLineWidth(4);
        ctx.setLineCap('round');
        ctx.setLineJoin('round');
      }
    
      canvasStart = (e) => {
        if (startX !== 0) {
          this.setState({
            isShow: false
          })
        }
        startX = e.changedTouches[0].x
        startY = e.changedTouches[0].y
        ctx.beginPath()
      }
    
      canvasMove = (e) => {
        if (startX !== 0) {
          this.setState({
            isPaint: true,
            isShow: false
          })
        }
        let x = e.changedTouches[0].x
        let y = e.changedTouches[0].y
        ctx.moveTo(startX, startY)
        ctx.lineTo(x, y)
        ctx.stroke();
        ctx.draw(true)
        startX = x
        startY = y
      }
    
      canvasEnd(e) {
        console.log('结束')
      }
    
      clearDraw = () => {
        startX = 0;
        startY = 0;
        ctx.clearRect(0, 0, canvasw, canvash);
        ctx.draw(true);
        this.setState({
          isPaint: false,
          tempFilePath: ''
        })
      }
    
      createImg() {
        if (!this.state.isPaint) {
          Taro.showToast({
            title: '签名内容不能为空!',
            icon: 'none'
          });
          return false;
        }
        // 生成图片
        Taro.canvasToTempFilePath({
          canvasId: 'canvas',
          success: async res => {
             console.log(res)
          },
          fail(err) {
            console.log(err)
          }
        })
      }
    
      uploadImage(filePath) {
        return new Promise((resolve, reject) => {
          let data = {
            url: APP_URLS.UPLOAD_FILES,
            filePath,
            name: 'file',
            formDate: {}
          }
    
          upload(data)
            .then(res => {
              console.log(res);
              resolve(res);
            })
            .catch(e => {
              Taro.showToast({title: '材料上传失败,请稍后再试', icon: 'none'})
            })
        })
      }
    
      // 获取 canvas 的尺寸(宽高)
      getCanvasSize() {
        const query = Taro.createSelectorQuery()
        query.select('#canvas').boundingClientRect(function (res) {
          canvasw = res.width
          canvash = res.height
        })
        query.exec()
      }
    
      // 重新签名
      afreshDraw = () => {
        this.setState({
          canSign: true
        })
      }
    
      componentDidMount() {
        this.getCanvasSize()
        this.initCanvas()
      }
    
      componentWillUnmount() {
        ctx = null
      }
    
      handleChange = (checkedList) => {
        this.setState({
          checkedList
        })
      }
    
      handleHideWarp = () => {
        this.setState({
          isShow: false
        })
      }
    
      render() {
        return (
          <View className="signature">
            <View className="canvas-box">
              <Canvas
                id="canvas"
                canvasId="canvas"
                className="canvas"
                disableScroll={true}
                onTouchStart={this.canvasStart.bind(this)}
                onTouchMove={this.canvasMove.bind(this)}
                onTouchEnd={this.canvasEnd.bind(this)}
                onTouchCancel={this.canvasEnd.bind(this)}
                onClick={this.handleHideWarp}
                disable-scroll={true}
              >
              </Canvas>
              {
                this.state.isShow &&
                <View className='text_warp' onTouchStart={this.handleHideWarp} onClick={this.handleHideWarp}>
                  <Text
                    className='text-detail'>请在此区域签名</Text>
                </View>
              }
            </View>
    
            <View className="layout-flex buttons">
              <View className='read'>
                <AtCheckbox
                  options={[
                    {
                      value: 'list1',
                      label: '',
                    }
                  ]}
                  selectedList={this.state.checkedList}
                  onChange={this.handleChange.bind(this)}
                />
                <Text>我已阅读并同意《电子签名协议》</Text>
              </View>
              <View className='btn-warp'>
                <AtButton onClick={this.clearDraw}>重新签名</AtButton>
                <AtButton customStyle={{margin: '0 10px'}} type='primary'
                          onClick={this.createImg.bind(this)}>确认签名</AtButton>
              </View>
            </View>
    
          </View>
        );
      }
    }
    
    全部copy到微信小程序里即可正常使用.
    
    
    排坑:
    坑1:
    旋转的问题,有时候把握不好或者说空间想像能力不够丰富,导致旋转的时候花费了一些时间.
    坑2:
    上面的placeholder的问题.有想过直接在canvas里绘制出这几个文字,但是会引起其它的问题,比如要保证文字的垂直居中状态,这个时候就得计算文字的长度,
    然后绘制到canvas上,这显然有些麻烦. 所以换了另外一种思路,直接一个div覆盖在最上方,开始绘制的时候给隐藏了,这种方式可能不适合所有项目.
    坑3:
    微信小程序在开发的时候使用真机预览的电子签名绘制的时候会有所卡顿,也不知道大家是否都一样,但是一旦上传到体验版本又是比较流畅的,这个无从去排查问题.
    
    
    总结:
        多尝试项目,可以让自己更好的成长
    
    
     
  • 惰性函数

    需求

    我们现在需要写一个 foo 函数,这个函数返回首次调用时的 Date 对象,注意是首次。

    解决一:普通方法

    var t;
    function foo() {
        if (t) return t;
        t = new Date()
        return t;
    }

    问题有两个,一是污染了全局变量,二是每次调用 foo 的时候都需要进行一次判断。

    解决二:闭包

    我们很容易想到用闭包避免污染全局变量。

    var foo = (function() {
        var t;
        return function() {
            if (t) return t;
            t = new Date();
            return t;
        }
    })();

    然而还是没有解决调用时都必须进行一次判断的问题。

    解决三:函数对象

    函数也是一种对象,利用这个特性,我们也可以解决这个问题。

    function foo() {
        if (foo.t) return foo.t;
        foo.t = new Date();
        return foo.t;
    }

    依旧没有解决调用时都必须进行一次判断的问题。

    解决四:惰性函数

    不错,惰性函数就是解决每次都要进行判断的这个问题,解决原理很简单,重写函数。

    var foo = function() {
        var t = new Date();
        foo = function() {
            return t;
        };
        return foo();
    };

    更多应用

    DOM 事件添加中,为了兼容现代浏览器和 IE 浏览器,我们需要对浏览器环境进行一次判断:

    // 简化写法
    function addEvent (type, el, fn) {
        if (window.addEventListener) {
            el.addEventListener(type, fn, false);
        }
        else if(window.attachEvent){
            el.attachEvent('on' + type, fn);
        }
    }

    问题在于我们每当使用一次 addEvent 时都会进行一次判断。

    利用惰性函数,我们可以这样做:

    function addEvent (type, el, fn) {
        if (window.addEventListener) {
            addEvent = function (type, el, fn) {
                el.addEventListener(type, fn, false);
            }
        }
        else if(window.attachEvent){
            addEvent = function (type, el, fn) {
                el.attachEvent('on' + type, fn);
            }
        }
    }

    当然我们也可以使用闭包的形式:

    var addEvent = (function(){
        if (window.addEventListener) {
            return function (type, el, fn) {
                el.addEventListener(type, fn, false);
            }
        }
        else if(window.attachEvent){
            return function (type, el, fn) {
                el.attachEvent('on' + type, fn);
            }
        }
    })();

    当我们每次都需要进行条件判断,其实只需要判断一次,接下来的使用方式都不会发生改变的时候,想想是否可以考虑使用惰性函数。

  • 在react获取函数组件实例

    前言:

    在平时开发中难免会使用到子组件的方法,在之前老的class实现方式中,可以通过ref直接获取到子组件的实例,然后调用子组件的方法,但是在函数组件中,却没有实例,你通过ref是无法直接获取到子组件的方法的.

    解决方案:

    在子组件中实例化函数组件:

    const childComponent = React.forwardRef((props,ref) =>{
      // useImperativeHandle 在react中引入
      useImperativeHandle(ref,()=>{
        return {
          reset:()=>{
            form.resetFields()
          }
        }
      })
      return (
          <div>fff</div>
        )
     })

    在父组件中引用方式即与之前用class的方式一样了

  • 使用puppeteer 上线到服务器填坑记录

    背景:

    一直想着在网上尝试爬虫服务,在去年的时候,由于自己服务器知识不足,导致自己对服务器运行puppeteer一直出错,后面也不了了之,这段时间又重新捡起来了,为了能够多学一些,多谁知一些.

    排坑:

    1.服务器无法正常启动无头chrome

    await puppeteer.launch({headless:false});

    哪怕把headless置为true,也一样无法正常启动

    解决方案:

    const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});

    方案可以去github的issues里面搜索一下

    加上链接:https://github.com/puppeteer/puppeteer/issues/290

    2.ubuntu安装无头浏览器需要各种插件

    apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3
    libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 
    libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 
    libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 
    ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget

    要想正常使用puppeteer需要安装以上插件.可能在其它环境下也是需要安装相同插件.

    3.开发中遇到的问题

    由于自己想走全套流程,所以也有用到mysql,学习mysql 从不熟悉到慢慢会一点,成就感还是有的.下面就碰到了一个坑:

    数据库里已经存在的更新,没有存在的则新加,开始用了on duplicate key update 这个语法,

    You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"15414675859332.html","a","b","c","d" on duplicate key update href=values(href),' at line 1

    后面替换成 replace into

    为什么替换,因为上面那个错误自己一直无法排查出来原因,不管在服务器上的mysql里输入同样的语法,还是在电脑端执行相同的语法均可以正常执行,但是一到服务上就不对了.

    这个问题也是排查了很久,后面替换成 replace into后,才慢慢发现了问题:

    'replace into headList (imgId,src,alt,href,imgType) values '

    后面的values后面是没有值的,为什么没有值?

    最开始的时候以为是异步原因,以为是mysql删除了,后面经过排查是puppeteer上的坑,应该说是自己的经验不足,在服务器上拉取数据的时候,由于网络慢的原因,导致有时候数据没有出来 但是js代码已经执行完了,引起values为空的问题,所以在服务器上使用puppeteer也要充分考虑到网络的因素,具体代码如下:

    while(true){
            await page.waitFor(1000);
            await page.waitFor('.m_txul>li .img');
            const ele = await page.$$eval('.m_txul>li .img',((eles,imgType) =>{
              const array = [];
              let str = '';
              for(let i = 0;i<eles.length;i++){
                const href = eles[i].href;
                const imgId = href.match(/.*?([\d]*.html)/);
                array.push({
                  src:eles[i].children[0].src,
                  alt:eles[i].children[0].alt,
                  href:eles[i].href,
                  imgId,
                  id:i
                });
                if(i === eles.length - 1){
                  str+= `("${imgId[1]}","${eles[i].children[0].src}","${eles[i].children[0].alt}","${eles[i].href}","${imgType}")`
                }else{
                  str+= `("${imgId[1]}","${eles[i].children[0].src}","${eles[i].children[0].alt}","${eles[i].href}","${imgType}"),`     
                }
              }
              return str
            }),urlObj.imgType);
            await page.evaluate(() => {
              return new Promise(async (resolve, reject) => {
                const height = document.querySelector('html').scrollHeight;
                const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
                for (let i = 0; i < height; i+=100) {
                  await delay(200)
                  window.scroll(0, i);
                }
                 resolve();
              })
            });
            if(ele){
              const sql = `replace into headList (imgId,src,alt,href,imgType) values ${ele}`
              // const sql = `insert into headList (imgId,src,alt,href,imgType) values ${ele} on duplicate key update href=values(href),src=values(src),alt=values(alt),imgType=values(imgType)`
              console.log(`更新次数:${num++}`);
              await exec(sql)
            }
            const bool = await page.$eval('.pagination>ul a:nth-last-child(2)',ele => {
              return ele.innerText === '下一页'
            });
            if(!bool){
              break;
            }else{
              await page.click('.pagination>ul a:nth-last-child(2)');
            }
            // num--
          }

    总结:

    功能不多,但是对我来说是一个新东西,并且是一个非常值得学习的技能,虽然有一定的坑,但是还是很开心能够入门这个技能

分类目录

No Data

No Data

热门文章

No Data

No Data