keep going with us.
post @ 2018-09-11

这是第 [ 1 ] 篇阅读笔记


记于 2018.9.11

几点思考:

  • 工作/生活的成长路径对之后的影响是很有作用的。

    一个从小在条条框框中成长,或者习惯于在体制内工作的人,与一个不墨守成规,乐于创业的人是有很大不同的,甚至不在一个世界。

    成长过程中的路径依赖会形成一个安全的舒适区,是一种保护也是一种诅咒。

  • 第一桶金的路径会奠定你的底层思维

    赚快钱会很容易摧毁一个人。

    稳扎稳打才能真正赚钱。

  • 多点布局才能帮助自己更好的征服世界。

    说白了就是突破自己的舒适圈

  • 沉迷游戏、追剧、综艺会摧毁你的思考能力

    这些活动会让你处在一个低投入、高回报的状态,很短的时间就可以对大脑形成强烈的愉悦感,久而久之,自然会形成一个直接的 【联结】,从而慢慢失去定下心来专注思考的能力。

最后一句话勉励一下:

生活对你的为难,不过都是为当年自己的懒惰买的单。

Read More

初创团队,目前业务方向聚焦在 物联网 方向,天天和搞 监控,NBiot,ARM,单片机等等的高手搞事情。未来会结合大数据机器学习做更多有趣的创新。 如果你有兴趣就加入我们吧。

我们缺:

  • 老前端一枚:

    1. 熟练 Nodejs
    2. php/Python/Golang 至少熟悉一种后端语言
    3. React/Vue2 至少一种
  • 老后端(偏大数据方向)一枚:

    1. 熟练 Python/Golang 至少一种
    2. 熟悉 Hadoop/Spark/Yarn ,了解其基本原理
    3. 用 Spark 搞过事 :D
  • 集成工程师一枚:

    1. 精通上位机/下位机开发
    2. 基于 Windows 平台下的 DLL 扩展开发
    3. 熟练使用 Asp.NET

PS:

  • 你最好

    1. 自己运营着自己的blog/github
    2. 代码洁癖
    3. 熟练使用 Docker
    4. 了解 微服务 和 机器学习
    5. 对新鲜事物保持高度好奇心
    6. 有一些执着的兴趣爱好,如:漫画,搜集玩偶,吉他,健身,游泳等等
  • 小福利

    1. 水果零食不断
    2. 定期团建
    3. 免费学游泳
    4. 万古大神带你组排 Dota 天梯,一起去上海( TI9 ) 支持 CN DOTA

划重点:

  • 仅限扬州地区,不提供食宿
  • 老大很挑,有意者请充分准备好,欢迎自认为有潜力的年轻人来挑战
  • 所有岗位均是 8k 起
  • 试用期最长 3个月,期间无保险,80%
  • 联系: 手机和微信都是 1377 0683 580
Read More

通过ffmpeg将 RTSP 的流媒体转换成 RTMP 格式

本文主要介绍在浏览器上播放监控摄像头的方法。一些主流的监控摄像头都提供了 rtsp 流媒体协议,这种协议只能通过特定的播放器才能正常播放,想要在浏览器中播放,必须要通过转码;具体细节这里不一一列举,可以搜索rtsprtmp 的原理。

如果有问题需要交流,可以联系我,手机号也是微信号: 13770683580 . 或者在下方留言。

主要实现思路

  • nginx部署一个rtmp流媒体服务端
  • ffmpeg转换rtsp流到服务端
  • 浏览器通过flash播放

Linux上的实现

  • 编译 nginx 并添加 rtmp 模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    git clone https://github.com/arut/nginx-rtmp-module.git  
    # centos redhat
    # yum -y install openssl openssl-devel
    # ubuntu debain
    apt-get install openssl libssl-dev
    wget http://nginx.org/download/nginx-1.14.0.tar.gz
    tar -zxvf nginx-1.14.0.tar.gz
    cd nginx-1.14.0
    ./configure --prefix=/usr/local/nginx --add-module=../nginx-rtmp-module --with-http_ssl_module
    make && make install

    编译成功之后,修改nginx.conf的配置信息。

    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
    # insert into the root element
    rtmp {
    server {
    listen 1935;

    application live {
    live on;
    }
    application hls {
    live on;
    hls on;
    hls_path data/misc/hls;
    hls_fragment 1s;
    hls_playlist_length 3s;
    }
    }
    }
    # insert after the http server element
    location /stat {
    rtmp_stat all;
    rtmp_stat_stylesheet stat.xsl;
    }

    location /stat.xsl {
    root nginx-rtmp-module/;
    }

    location /control {
    rtmp_control all;
    }
    location /hls {
    types {
    application/vnd.apple.mpegurl m3u8;
    video/mp2t ts;
    }
    root data/misc;
    add_header Cache-Control no-cache;
    }
  • 编译安装 ffmpeg

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    sudo apt-get install yasm nasm \
    build-essential automake autoconf \
    libtool pkg-config libcurl4-openssl-dev \
    intltool libxml2-dev libgtk2.0-dev \
    libnotify-dev libglib2.0-dev libevent-dev \
    checkinstall
    wget https://www.ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2
    tar jxvf ffmpeg-snapshot.tar.bz2
    cd ffmpeg
    ./configure --prefix=/usr
    time make -j 8
    cat RELEASE
    sudo checkinstall
    sudo dpkg --install ffmpeg_*.deb
  • 调试一下视频流

    • 需要下载一个 vlc 的播放器
    • 打开流
  • 执行转码命令
    ffmpeg -i "rtsp://admin:admin123@192.168.1.205:554/h264/ch1/main/av_stream" -f flv -r 25 -s 1960*1280 -an "rtmp://localhost:1935/live/test"

  • 使用 video.js 在浏览器中播放

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>Video.js | HTML5 Video Player</title>
    <link href="https://unpkg.com/video.js@5.20.1/dist/video-js.min.css" rel="stylesheet">
    </head>
    <body>
    <video id="example_video_1" class="video-js vjs-default-skin" controls preload="auto" width="640" height="360" poster="" data-setup="{}">
    <source src="rtmp://192.168.100.196:1935/live/test" type="rtmp/flv">
    <p class="vjs-no-js">Allow Browser Enable Flash Plugin</p>
    </video>
    <script src="https://unpkg.com/video.js@5.20.1/dist/video.js"></script>
    </body>
    </html>

    实际使用 video.js 的过程中会出现一些问题,其主要原因是较新的版本不支持 flash 播放,需要使用 5.x 的版本来进行播放。

Read More

本文是一个笔记帖,是关于 [实现简单神经网络] 的视频课程中使用到的源码整理。视频地址: https://www.imooc.com/learn/813

本文中提到一些基本概念与线性代数的关联

文中提到了一些机器学习的相关知识,老师介绍的比较浅显易懂,这里记录一些自己的理解。

要深入理解这些概念需要有一定的线性代数的基础。

  • 神经元
    一个神经元就是整个神经网络中的一个节点,它允许接受一组信息,并将这些信息做一个简单的处理,并将处理结果反馈给下一个神经元。

    这里接受的一组信息可以理解为一个n维的列向量,处理信息的过程就是将这个向量乘以神经元自身的一个列向量的过程,处理的结果会进行一个分类,将其处理为1或者-1 。

  • 感知器
    感知器是一个具有自我学习能力的神经元,可以通过一组分类好的数据对其进行训练,使其具有处理信息,将其分类的功能。

    这里分类好的数据可以理解为一个m*(n+1)维的增广矩阵,m表示数据集的量,n表示神经元单次处理的输入信息的维度,即输入的列向量的维度。
    而这个矩阵的一般解就是这个神经元的模型,即这个神经元的列向量。

  • 简单分类算法
    该算法就是一个简单的数学模型;
    假设: 输入的向量信息为 (10, 8, 7),我们需要神经元经过分类后得到 1
    那我们可以将这次处理用数据公式表示出来:
    fn(10 * w1 + 8 * w2 + 7 * w3) = 1

    而这里的 (w1, w2, w3) 正是我们需要知道的神经元的向量。

    将这个转换成通常的公式:
    fn(W0 + X1 * W1 + X2 * W2 + ... + Xn * Wn) = Y

    这个公式表示了一次一般的神经元的向量运算。

    神经元的训练过程就是将已知的若干个(X1, X2, .... Xn, Y)带入到该公式中进行运算,以求得 (W0, W1, W2 ... Wn) 。而这个向量就是神经元的模型,可以用于预测之后的输入信息。

Read More
post @ 2018-07-09

Selenium For Python

本文中主要分享一些关于 Windows 环境下使用 Selenium 来操作 IE11 的一些细节和问题。

1. 如何使用 IE

  • 1.1 下载对应版本的 webdriver.exe
    这里指的对应的版本指的是 浏览器的软件版本 64/86。如果版本不对,会导致 sendKeys 函数执行效率很慢。
  • 1.2 将 webdriver.exe 放在 windows 的 PATH
  • 1.3 设置 IE11 的安全策略,全部保持一致,全部信任或者不信任
  • 1.4 一些遇到的问题
    • 1.4.1 一些没有证书的 https 站点,会提示 继续前往 ,可以通过模拟点击A标签来绕过错误提示
    • 1.4.2 第一次操作的时候,IE11 的安全策略会弹出添加到信任站点,否则弹框会导致一些未知问题

2. 如何重复使用已开启的浏览器回话?

这是本文的重点之一

  • 2.1 关于 chrome 的会话保持

    实现思路:
    • i. 打开一个 selenium 会生成一个 sessionid , 只要将该id保存下来,下次继续使用这个id就可以重新使用 。
    • ii. 通过 driver.session_id 可以获取到当前回话的 sessionid
    • iii. 将这个 sessionid 赋值给新的 driver 实例,或许可以重连到上一次的会话。
    编码目标:
    • i. 继承 Remote 类,并复写 start_session 函数,阻止生成新的 session_id

      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
      # -*- coding: utf-8 -*-
      from selenium.webdriver import Remote
      from selenium.webdriver.chrome import options
      from selenium.common.exceptions import InvalidArgumentException

      class ReuseChrome(Remote):

      def __init__(self, command_executor, session_id):
      self.r_session_id = session_id
      Remote.__init__(self, command_executor=command_executor, desired_capabilities={})

      def start_session(self, capabilities, browser_profile=None):
      """
      重写start_session方法
      """
      if not isinstance(capabilities, dict):
      raise InvalidArgumentException("Capabilities must be a dictionary")
      if browser_profile:
      if "moz:firefoxOptions" in capabilities:
      capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encoded
      else:
      capabilities.update({'firefox_profile': browser_profile.encoded})

      self.capabilities = options.Options().to_capabilities()
      self.session_id = self.r_session_id
      self.w3c = False
  • 2.2 关于 ie 的会话保持
    实现了 Chrome 的 reuse, ie 的实现思路也是类似,只是 IEWebDriver 有些参数和 Remote 的有些不同;需要翻阅一些 selenium 的源码。思路都一样,就不重复说了,直接贴代码。

    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
    # -*- coding: utf-8 -*-
    from selenium.webdriver.ie.webdriver import WebDriver as IEWebDriver
    from selenium.common.exceptions import InvalidArgumentException
    from selenium.webdriver.ie import options

    class ReuseIe(IEWebDriver):

    def __init__(self, executable_path, port, session_id):
    self.r_session_id = session_id
    IEWebDriver.__init__(self, port=port, desired_capabilities={})

    def start_session(self, capabilities, browser_profile=None):
    """
    重写start_session方法
    """
    if not isinstance(capabilities, dict):
    raise InvalidArgumentException("Capabilities must be a dictionary")
    if browser_profile:
    if "moz:firefoxOptions" in capabilities:
    capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encoded
    else:
    capabilities.update({'firefox_profile': browser_profile.encoded})

    self.capabilities = options.Options().to_capabilities()
    self.session_id = self.r_session_id
    self.w3c = True

3. 如何等待一些异步执行的js执行完立即执行?

这是本文的另一个重点

实际使用的过程中,会遇到异步执行的js代码,执行下一步操作需要等待这个js执行完。
在不了解 selenium 的一些特性之前,会想到 `time.sleep(?)` ,这样确实可以让操作等待;且等待的时间是固定的,无法与dom或者js真正的交互;为了安全只能尽量设置得大一些,这样也白白浪费了时间。
selenium 为我们提供了一些非常友好的 api 来解决这个问题。
1
WebDriverWait(_driver, seconds).until(EC.element_to_be_clickable((By.ID, id)))
意思就是:等待若干秒直到某个元素可被点击,则立即返回该元素,超时则抛出相应的异常。 通过这个api可以确保异步或者网络延迟导致dom结构变化的时间在一个范围内,脚本可以按照设计的流程执行下去,并且不消耗多余的等待时间。 使用前,需要导入它们
1
2
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
更多的相关api可以安装了 selenium 之后在 site-package 中找到它的源代码。

4. 如何确保dom被完全渲染出来了?

判断dom有没有正常的显示可以使用:

1
_driver.find_element_by_id(id).is_displayed()

当然这是立即判断,没有等待。
想要等待某个元素显示好了再执行,则需要

1
WebDriverWait(_driver, seconds).until(EC.visibility_of_element_located((By.ID, id)))

同样也是用到了 expected_conditions 库中的api。

5. 如何执行单元测试

想要测试 selenium 的脚本,需要先编写一个 html 页面,提供一些dom元素:buttoninputalert 等等,然后通过脚本去控制它们;再使用 python 的 unittest 框架的断言库去测试它们。

6. Bug: 使用之前的浏览器回话,操作 iframe 时会无法在父子框架之间进行正常的切换

如果在一个 frame 中,脚本异常退出,重连之后,无法再次获取到该frame,这个问题暂时没有解决。

Read More

yf-fpm-server 是一个面向小微团队产品开发的前后端分离框架。

让你们少走一些弯路
前后端分离框架可以解决的问题
  • 支持异构项目的集成【App与管理平台数据交互】
  • 前后端分工更明确
  • 提高前端开发效率【mock】
  • 减少后端运维成本

对于一个小微型的产品研发团队来说,这些问题通常交给一个全栈工程师去解决的;而随着产品的不断迭代,这些问题会不断产生新的问题,通常意义上的填坑过程。


一个健康的产品研发过程应该在一开始就考虑前后端分离的事情。

控制成本
node.js
  • 使用javascript语法,对web开发者来说已经驾轻就熟
  • 运行在服务端,依赖V8引擎和C++的天然优势,它可以承受少量并发
  • 近2年来在开源社区很活跃,被大部分公司深度应用,并开源了一些产品

团队引入nodejs是一个如虎添翼的过程,加以合理利用,它可以解决95%的事情,也就意味着它能快速的将生产力转化成输出。

即使在小的团队也要尽可能的快速迭代

多版本同时在线
清晰的架构
  • 框架研发之初,团队面临的最大的问题就是应对快速迭代
  • 框架已被用于多个项目的生产环境,最长的已运行2年多
  • 通过2年多线上运营过程,不断的升级和优化
  • 代码开源,核心代码简洁,通过插件无侵入的扩展功能和模块

与引入资本的团队不同是,初创阶段,团队选择框架更看中其上手难度和维护成本,一个合适的框架可以减少很多工作。

采用taobao和jd的开放平台的设计方案,定义统一的入口,通过参数定位业务接口,实现灵活的业务开发。

统一入口
提高业务实现效率
  • 提供一个/api的路由来处理业务请求
  • 非restful范式,不用维护冗长的路由
  • 统一的全局异常处理
  • 丰富的客户端SDK
Read More

框架设计之初为了提供一个相对标准的api入口,节约项目开发的前后端沟通成本,前端可以通过已实现好的sdk库可以很方便的与后端进行数据交互

yf-fpm-server 框架一直在做的就是:

  • 如果你是个人,小规模,实力不强,但可以使用nodejs,想做一个规模不是很大的项目,这个框架就很适合你(们)。它是个能将项目从初期到中后期过度的很好的框架。当然,我们是实践了若干的商业项目之后才会如此力荐的。
  • 充分结合nodejs的特性和koa框架的简洁,不断的精简和优化代码

    • 最终的依赖:
      1
      2
      3
      4
      5
      6
      "koa": "^2.0.0",
      "koa-bodyparser": "^3.2.0",
      "koa-router": "^7.1.0",
      "koa2-cors": "^2.0.3",
      "lodash": "^4.16.1",
      "pubsub-js": "^1.5.5"
  • 一些变动的api

    • extendModule(name, module, version) 该函数多用于开发插件的过程中很快的注入业务函数
    • getPlugins() 获取已安装的插件信息
    • isPluginInstalled(name) 检测是否已安装了某个插件
    • _counter 该字段用于记录api被调用的次数
    • _prject_info 该字段包含了项目的package.json信息
    • _start_time 该字段记录了项目启动的时间戳 通过 _.now() 生成

    WARNING:这里有一些重要的变化,对于所有的业务函数增加了2个参数

    1
    2
    3
    4
    5
    6
    /*
    * args: 前端传来的业务参数
    * ctx: koa 的上下文
    * before: 前置钩子函数执行的结果,通过是个 Array
    */
    (args, ctx, before) => { //.. }

    再此之前是没有后面2个参数的

    1
    (args) => { //.. }

    需要注意的是,一些通过插件调用了系统的fpm.execute()函数,通常没有ctx参数,所以需要做个谨慎的非空判断

  • 利用插件的机制将非必要的功能拆分到插件库中,项目开发过程中结合业务添加插件到系统中即可,已实现的插件有:


    除此之外,还有一些插件正在开发过程中,比如用于后台查看的 fpm-plugin-admin,方便用户直观的查看系统运行的状态。

Read More

本次更新主要是移除了一些内核中可插件化的代码,并新增了2个插件 fpm-plugin-qiniu-upload,fpm-plugin-socketio 。2.2.3版本将是一个lts版本。

依赖变更

  • remove qiniu

最新的依赖

1
2
3
4
5
6
7
8
9
"koa": "^2.0.0",
"koa-bodyparser": "^3.2.0",
"koa-multer": "^1.0.1",
"koa-router": "^7.1.0",
"koa2-cors": "^2.0.3",
"lodash": "^4.16.1",
"md5": "^2.1.0",
"moment": "^2.13.0",
"pubsub-js": "^1.5.5"

移除的特性

  • remove upload 的路由
    1
    this.app.use(upload.routes()).use(upload.allowedMethods())

插件

  • 上传文件到七牛的插件 fpm-plugin-qiniu-upload

  • 集成socketio服务插件 fpm-plugin-socketio

  • 已实现的可用插件列表

    1
    2
    3
    4
    5
    6
    "fpm-plugin-baidu": "^0.0.1",
    "fpm-plugin-emailer": "^1.0.3",
    "fpm-plugin-mysql": "^1.0.1",
    "fpm-plugin-qiniu-upload": "^1.0.0",
    "fpm-plugin-schedule": "^1.0.0",
    "fpm-plugin-socketio": "^0.0.2",
Read More

本次版本更新主要针对文件上传下载进行了初步的实现。

增加的依赖

新的特性

  • 添加新的 /upload , /download/:hash 路由
  • 限制文件类型
    • zip
    • json
    • txt,log等文本类型
  • 限制文件大小
    • 2 x 1024 x 1024 (B)

完整代码

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
import _ from 'lodash'
import Router from 'koa-router'
import multer from 'koa-multer'
import fs from 'fs'
import E from '../error.js'

const router = Router()

//将上传的信息保存在内存中
var datas = {}

// 上传的文件类型限制
const ALLOW_MIMETYPES = ['application/octet-stream', 'application/zip', 'application/x-zip-compressed']

const fileFilter = (req, file, cb) =>{
if(_.indexOf(ALLOW_MIMETYPES, file.mimetype)> -1){
cb(null, 1)
}else{
cb(E.Upload.TYPE_NOT_ALLOWD)
}
}

const upload = multer({ dest: 'uploads/' , fileFilter: fileFilter, limits: { fileSize: 2 * 1024 * 1024 }})

// 上传表单以file为文件的字段
const defaultHandler = upload.single('file')

// 捕获异常
const handler = async (ctx, next) => {
try{
await defaultHandler(ctx, next)
}catch(e){
ctx.error = e
}
if(ctx.error){
ctx.fail(ctx.error)
}else{
let data = ctx.req.file
datas[data.filename] = data
ctx.success({data: {hash: data.filename, url: '/download/' + data.filename}})
}
}

// 上传文件的路由
router.post('/upload', handler)

// 下载文件的路由
router.get('/download/:id', async (ctx, next) => {
let data = datas[ctx.params.id]
ctx.type = data.mimetype
ctx.attachment(data.originalname)
ctx.body = await fs.createReadStream(data.path)
await next()
})

export default router
Read More
  • 1.合并analyse.js 和 auth.js代码
  • 2.增强权限匹配的模式
  • 3.添加系统异常捕捉绑定函数
  • 4.去除2个多余的函数
  • 5.新增2个钩子埋入点

1.合并analyse.js 和 auth.js代码

将2个中间件的代码合并到auth.js中,在auth中间件中验证key和root权限

2.增强权限匹配的模式

使用正则表达式来匹配接口权限

1
2
3
4
5
6
7
8
let method = postData.method
// 使用正则表达式来匹配信息
let root = '^' + approot + '$'
// 涵盖权限
if(new RegExp(root).test(method)){
await next()
return
}

如:

  • order.* 可授权order模块下的所有接口权限
  • app.* 可授权app模块下的所有接口权限
  • (order|app).* 可授权 order 和 app 两个模块下所有的接口
  • * 可授权所有权限

3.添加系统异常捕捉绑定函数

在 fpm 的核心api中添加 bindErrorHandler(handler) 绑定koa的系统错误回调。

1
2
3
bindErrorHandler(handler){
this.errorHandler = handler
}

4.去除2个多余的函数

删除了 use(middleware) , addRouter(routers, methods) 2个多余的函数。

5.新增2个钩子埋入点

添加了 FPM_MIDDLEWARE , FPM_ROUTER 2个钩子埋入点,可在插件开发中利用,可为fpm绑定更多的路由和中间件。

1
2
this.runAction('FPM_MIDDLEWARE', this, this.app)
this.runAction('FPM_ROUTER', this, this.app)

除了传入 fpm , 还会将koa的引用一起传入到插件的 bind() 函数中。

Read More
⬆︎TOP