Blog - wingsico

1
2
3
4
5
6
7
8
9
const pick = (field = "", object = {}) => object[field]
const pluck = (field, array) => array.map(object => pick(field, object))
const deepPluck = (field, array) => array.map(object => deepPick(field, object))
const deepPick = (fields, object = {}) => {
const [first, ...remaining] = fields.split(".")
return (remaining.length) ?
deepPick(remaining.join("."), object[first]) :
object[first]
}

前言

之前那一觉睡的有点久啊嘿嘿,废话少说,直接开始进入正题吧,实现Linux的定时服务。

准备工作

自己的电脑是Mac OS,命令跟linux差不多吧,但是定时任务也不应该在自己的电脑上跑吧,因为自己的电脑总会关的,服务也会停止的。所以我选择了我的博客所在的服务器,这个是linux centos系统。这个是我在腾讯云花一元/月领的(现在这个活动已经结束啦),ok,自动签到js脚本和服务器都有了,可以开始我们的任务了。

start!

首先,服务器上还没有我们的脚本,我们需要把我们的脚本上传到服务器里,怎么上传呢? 在mac os里(may linux里也有),进入我们脚本所在目录:

➜ ~ cd ~/my-github/nodejs/util
➜ ~ scp checkin.js root@wingsico.org:/home/checkin.js
password:
ok

这样就上传到我们服务器的/home目录下,并将文件命名为checkin.js 可以通过ssh进入服务器查看一下,很好,/home目录下已经有我们的脚本文件checkin.js了,先尝试运行一波:

[root@VM_48_13_centos ~/home]# node checkin.js
[root@VM_48_13_centos ~]# cd /var/spool/mail/
[root@VM_48_13_centos mail]# nano root
// 翻到最后一行
OK {“msg”:”\u83b7\u5f97\u4e86 96 MB\u6d41\u91cf.”,”ret”:1}

签到成功了,ok,那么我们就开始写定时服务。

定时服务

在linux中我们使用crontab这个命令来管理定时任务,我们可以使用crontab –help(其实是靠输入错误来弹出相关提示…)来查看相关命令:

usage: crontab [-u user] file
crontab [-u user] [ -e | -l | -r ]
(default operation is replace, per 1003.2)
-e (edit user’s crontab)
-l (list user’s crontab)
-r (delete user’s crontab)
-i (prompt before deleting user’s crontab)
-s (selinux context)

为了更好的运行脚本,我们写一个shell脚本来运行js脚本:

#!/bin/sh
/root/.nvm/versions/node/v8.7.0/bin/node /home/checkin.js

发现了吗?我们这里没有直接用node来运行js脚本,因为shell脚本运行的环境不同,不能直接通过node来运行js脚本,我们使用which node来找到node的运行的源文件,保存在统一目录下,命名为checkin.sh,接下来我们在定时任务里就可以直接运行这个脚本就好了,现在来编辑定时任务了,输入crontab -e,添加

10 09 * * * sudo bash /home/checkin.sh &

前面的参数的含义就是 每天的9点10分,这个命令就是在每天9点10分运行一次checkin.sh脚本,记得加sudo,不然会在日志中提示权限不够。好了,之后输入:

[root@VM_48_13_centos ~]# /sbin/service crond start

开启定时服务~之后如果修改crontab的话使用

[root@VM_48_13_centos ~]# /sbin/service crond restart

即可~

起因

曾在大一的时候就听田大佬写了一个每日自动签到的脚本,觉得很牛逼,不过现在看起来确实和当初田大佬说的那样简单>,<,话不多说,直接开始吧。

准备工作

首先这个签到是我学校的一个工作室的内部网站的签到。一开始认为只需要登录了就算签到了(大一不了解的时候),现在知道了,其浏览器上的流程大概是: 打开登录页面 ——> 输入账号密码 ——> 点击登录 ——> 登录成功&自动完成签到; 其内部实现为: 输入网址 ——> 发送get请求 ——> 拿到返回的html并展示 ——> 输入账号密码 ——> 将账号密码放在请求体内通过post请求发送到对应的登录api ——> 拿到对应api所返回的数据(这里我们只需要token) ——> 对签到的api发送一个post请求,将token放入请求头中发送 ——> 签到成功. 那么如何实现以上操作呢?

实现

首先,打开us.ncuos.com(我们内部的网站), f12打开chrome debugger tools,点击network,钩上presever log,然后输入账号密码,点击登录,成功之后右边就会出现一堆请求,我们找到Name为login的包,找到这几项 这里标清楚了请求的url,method和请求体的类型,根据这三个我们可以很简单的写一个模拟登录来拿到对应的token:

var request = require('request') // 引入request包

var contents = {
  username: '613*****',
  password: '******',
  remember_me: false
} // 请求体,目前为一个对象

var options = {
  url: 'http://us.ncuos.com/api/user/login', // 请求的url
  method: 'POST', // 请求的方式 post
  headers: { // 设置请求头
    'Content-Type': 'application/json; charset=utf-8'
  },
  body: JSON.stringify(contents) // 对象JSON化
}

// 发送一个请求,
request(options, (error, response, body) => {
  if (!error && response.statusCode == 200) {
    var token = response.headers.authorization // 响应头里存储着token
    console.log(token)
  }
})

ok,是不是很简单呢,这样我们就拿到了token,就可以为所欲为了啊嘎嘎,比如爬个帖子内容啥的!哦不,回归正题,签到签到,我们再次回到浏览器,我们登录之后还请求了一些别的数据,比如Name为checkin的就是我们要找的签到的api, 由于这个接口是没有请求体的,所以我们不需要类似上面的contents了,只需要在登录之后再发送一个post请求,设置好请求头就可以了,由于代码比较短,就直接放在登录成功后的if语句内:

request(options, (error, response, body) => {
  if (!error && response.statusCode == 200) {
    var token = response.headers.authorization // 响应头里存储着token
    var params = {
      "url": 'http://us.ncuos.com/api/checkin', // 请求url
      "method": 'POST', // 请求方式
      "headers": { // 请求头
        "Content-Type": "Application/json", // 请求体格式
        "Authorization": token // auth token授权
      }
    }
    request(params, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        console.log(body) // 打印出响应体
      } else {
        console.log(error)
      }
    })
  }
})

写好了之后,保存为checkin.js, 执行node checkin.js,发现打印出来的是null,什么情况,为了查看原因,我们将签到请求做出一些修改:

request(params, (error, response, body) => {
  console.log(response.statusCode, response.statusMessage)
  if (!error && response.statusCode == 200) {
    console.log(body) // 打印出响应体
  } else {
    console.log(error)
  }
})

发现打印了 400,forbidden,null,再打印了一下整个response,发现在error里有更加详细的一个报错:

body: ‘{“error”: “InvalidData”, “message”: “invalid json content”, “status”: 400}’ } ‘{“error”: “InvalidData”, “message”: “invalid json content”, “status”: 400}’

很容易看出,error为非法json格式内容。wtf?啥情况,我这个根本不需要请求体啊,为什么还来个这个错误呢,于是走上了漫漫排查之路。

debugger

我首先先尝试了一下console.log(params),发现headers里的Authorization少了引号: 难道是这个的原因吗?然后我尝试了各种方法还是不能将引号加上,不过JSON.stringify(params) 之后是有引号的,所以觉得不是这个问题。苦思冥想无果之后,求助了一下学长,把情况说清楚之后,学长给出了一个尝试建议: 把headers里面的’Content-Type’去掉试试,去掉之后,我靠真的就成功了。之后探其原因是设置请求头中多余地规定了请求体的格式,但是你并没有也不需要发送请求体(对于这个api),但设置了这个请求头之后就强制的要求了你的格式,所以导致上面的报错信息,非法的content。为了验证这个想法,我把Content-type重新加上,写了一个json格式的请求体,里面数据随便填,果然也成功了!至此,我们的这个签到脚本就完成了。最终代码如下:

var http = require('http')
var request = require('request')

var contents = {
  username: 'username',
  password: 'password',
  remember_me: false
}

var options = {
  url: 'http://us.ncuos.com/api/user/login',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json; charset=utf-8'
  },
  body: JSON.stringify(contents)
}


request(options, (error, response, body) => {
  if (!error && response.statusCode == 200) {
    var token = response.headers.authorization
    var params = {
      "url": 'http://us.ncuos.com/api/checkin',
      "method": 'POST',
      "headers": {
        "Authorization": token
      }
    }
    request(params, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        console.log(body)
      } else {
        console.log(error)
      }
    })
  }
});

但是这样每天我们还是需要去自己执行一下这个脚本,还是不如自己手动去登录一下签到呢,所以等下我们就要实现的是 Linux服务器定时任务,可以每日定时执行这个脚本,就可以每日自动签到了,这个留到下一篇博客讲吧~先去睡觉~

要实现顺序表和链表,首先我们需要知道的是,何为顺序表何为链表。

顺序表

这个很简单,在JS中,数组就是顺序表的实例,它具有顺序表的一切性质:

  1. 存放在连续的内存当中,物理位置相邻,换句话说,其数据元素都是用一组地址连续的存储单元以此存储在线性表当中;
  2. 只要确定了存储线性表的起始位置,顺序表中任一数据元素都可以随机存取;
  3. 具有线性表的性质(关于线性表,这里不作介绍,可以在这里了解线性表

所以,在JS,要初始化一个空的顺序表是十分简单的:

// 方式一
var ary1 = [] // []
// 方式二
var ary2 = new Array() // []
// 神经病方式
var ary3 = [...''] // []
var ary4 = [...new Set()] // []
var ary5 = [...new Map()] // []
var ary6 = [...new String()] // []
var ary7 = ''.split('') // []
var ary8 = Array.prototype.slice.call('') // []
// 终极神经病方式
var obj = {}
var ary9 = [...obj[Symbol.iterator]] // []

既然是自己实现顺序表,自然不能使用js数组中自带的方法啦,一般来说,顺序表有以下几个基本方法:

  1. createList()
  2. initList(L)
  3. getLength(L)
  4. insertList(L, i, e)
  5. deleteList(L, i , e)
  6. getListElem(L, i)
  7. getElemIndex(L, e)

So, 接下来一个个实现吧~


 //createList
function createList():any[] {
  var array = new Array() // []
  return array
}

// initList(L)
function initList(list: any[]): number[] {
  if (list instanceof Array) {
    var length = 15 // 由于js本身不能自己输入值,所以采用自己赋值来初始化一个顺序表
    for (let i = 0; i < length; i++) { list[i] = i + 1 } return list 
  }
} 

// getLength(L) 
function getLength(list: number[]): number{ 
  let len: number = 0; 
  while(L[len] !== undefined) { // 循环遍历每一个数据元素, 一个不为空则计数器加一 
    len++ 
  }
  return len 
} 

// insertList(L, i, e) 
function insertList(list: number[], index:number, item: number): number[] {
  let i: number
  let len = getLength(list)
  for (i = len - 1; i >= index - 1; i--){
     list[i+1] = list[i] // 位置index之后的元素(包括index)全部往后移一个单元
  }
  list[index - 1] = item
  return list
}

// deleteList(L, i)
function deleteList(list: number[], index: number): number {
  if (index > getLength(list) + 1 || index < 1) {
    return -1
  }
  let i: number = index - 1
  let len: number = getLength(list)
  let e = list[i]
  for (; i < len; i++){
    list[i] = list[i + 1] // index之后的每一个元素往前移一个单元
  } 
  list.length-- // 长度减1
  return e 
} 

// getListElem 
function getListElem(list: number[], index: number):number {
  if (index > getLength(list) + 1 || index < 1) {
    return -1
  }
  return list[index - 1]
}

// getElemIndex(L, e)
function getIndex(list: number[], value: number): number {
  let i: number = 0
  for (; i < getLength(list); i++) {
    if (list[i] === value) {
      return i
    }
  }
}

最后再用一个test函数测试:

function test():void {
  let sqList = createList()
  console.log('创建空顺序表:' + sqList)
  initList(sqList)
  console.log('初始化一个数字类型顺序表:' + sqList)
  let elem = getListElem(sqList, 5)
  console.log('获取第五个元素:' + elem)
  let index = getListIndex(sqList, 6)
  console.log('获取值为6的游标:' + index)
  insertList(sqList, 4, 999)
  console.log('在第四个位置插入999:' + sqList)
  let e = deleteList(sqList, 4)
  console.log('删除第四个位置的元素:' + sqList, '被删除的元素:' + e)
  console.log('表长为:' + getLength(sqList))
  console.log('所有元素为:')
  consAll(sqList) // 输出所有元素
}
test()

一个简单的顺序表就完成了~,接下来介绍链表。

链表

先介绍以下何为链表 链表也是线性表中的一元,它也是一个连续的表,不过相比于顺序表,它不要求物理位置上相邻,但要求的是逻辑位置上相邻,因此他没有顺序表所具有的弱点——删除、插入等需要移动大量的数据元素,但是也失去了顺序表随机存储的优点。我们先来看一下链表的结构: 链表 从上图可以清晰的看出,对于一个数据元素,我们这里把它叫做结点好了,一个结点(Node)中包含两个域,一个是存储自身信心的数据域,另一个是指向下一个结点的指针域,由于js里没有指针,那么我暂且称之为链域,指向的是该结点的下一个结点。从上图我们还发现了一个head结点,我们把它称之为 头指针,他的数据域可以不存储任何数据,也可以存储像表长等信息,其链域中存储的是指向链表的第一个结点的链。可能现在会对头指针的存在产生一个疑惑,那么我举个例子就明白了,试想一下,如果没有头指针,我们如何在第一个结点前再插入一个结点呢?这个操作很简单,就是把你新创建的结点的链域指向第一个结点,但是如果你不是插入到链表的最前面呢?你要先创建一个结点,然后让想插入的那个位置的前一个结点的链域指向你新创建的结点,再让你的链域指向你想插入的位置的那个结点。发现了吗?插入第一个和插入中间其他位置的操作是不相同的,为了简化和统一像这样的操作,于是引入头指针。链表的最后一个结点之后没有结点的存在,因此让他的链指向NULL 在传统语言如c/c++中,因为有指针的存在,可以很明确的创建一个链表,而js中是没有指针的(this应该不算吧),那我们要如何去实现一个链表的结点呢?一开始想到用对象Object实现,但是对象复制有一些麻烦,于是转而选择了类function,看以下代码:

function LNode() :void{
  this.data = null;
  this.next = null;
}

用公用属性data当作结点的数据域,用公有属性next当作链域,当新建一个结点的时候只需要 new LNode()即可,比较方便,当然,也可以使用es6的class,代码如下:

class LNode {
  constructor() {
    this.data = null;
    this.next = null;
  }
}

由于typescirpt用的有一些生疏,以及class用的不太熟练,先用es5实现那些方法。 代码如下:

function LNode() :void{
  this.data = null;
  this.next = null;
}

function createLink(head: any, n: number): void{
  let i: number;
  head.data = n
  for (i = n; i > 0; i--) {
    let newNode = new LNode()
    newNode.data = Math.floor(Math.random() * 10 + 1)
    newNode.next = head.next
    head.next = newNode
  }
}

function getElem(LinkList: any, i: number): number {
  if (!LinkList.next) {
    return
  }
  let p: any = LinkList.next
  let j: number = 1
  while (p && j < i) { p = p.next ++j } if (!p || j > i) {
    return
  }
  return p.data
}

function getIndexs(LinkList: any, value: number): number[] {
  if (!LinkList.next) {
    return
  }

  let p: any = LinkList.next
  let indexs: number[] = new Array()
  let len = 0
  let i: number = 1
  while (p) {
    if (p.data === value) {
      indexs[len] = i
      len++
    }
    p = p.next
    i++
  }

  return indexs
}

function insertElem(LinkList: any, pos: number, e: number): void {
  if (!LinkList.next) {
    return
  }

  let p: any = LinkList
  let i: number = 0

  while (p && i < pos - 1) { p = p.next i++ } if (!p || i > pos - 1) {
    return
  }

  let s = new LNode()
  s.data = e
  s.next = p.next
  p.next = s
  LinkList.data++
}

function consAll(LinkList: any): void {
  let list: String = ''
  let p: any = LinkList.next
  while (p) {
    list += p.data + ', '
    p = p.next
  }
  console.log(list)
}

function deleteElem(LinkList: any, i: number): number {
  if (!LinkList.next) {
    return
  }

  let p: any = LinkList.next
  let j: number = 1
  let e: number
  let q

  while (p && j < i - 1) { p = p.next ++j } if (!p || j > i - 1) {
    return 
  }

  q = p.next
  p.next = q.next
  // 由于js的垃圾回收机制,不需要手动释放内存
  LinkList.data--
  return e
}

function getLenth(LinkList: any): number{
  if (!LinkList.next) {
    return 0
  }
  let p = LinkList.next
  let j = 0
  while (p) {
    p = p.next
    j++
  }
  return j
  // 由于我把表长存在头结点的data中,所以可以直接 return LinkList.data
}

function test(): void {
  var L = new LNode()
  createLink(L, 10)
  consAll(L)
  console.log(getIndexs(L, 5))
  console.log(getElem(L, 4))
  insertElem(L, 3, 999)
  consAll(L)
  deleteElem(L, 7)
  consAll(L)
  console.log(getLenth(L))
}

test()

结语

用js实现数据结构比C/C++更容易,为了更加靠近,我在这里使用了一部分typescript,最近还学习了kmp算法,快排,栈,图,队列,广度优先算法,狄克斯特拉算法等等,将在今后的博客一一列出。

参考资料

《数据结构》 严蔚敏 (PS: 这本书。。不好)

前言

大二了,学校开了一门数据结构的课,在学习过程中,发现自己对好多C/C++的知识有点忘却了,然后想到自己这一年基本上都在学习Javascript,所以何不用JS来学数据结构呢?


路要一步步走,饭要一口口吃,我需要一步步从基本的开始学习,加油!以后该主题的博文均放置在以下板块内。

文章链接

顺序表、链表的JS实现: [http://wingsico.org/2017/11/10/squence_link/ ‎](http://wingsico.org/2017/11/10/squence_link/ ‎)