作者:西岚

写在前言

要知道在深圳上班是非常痛苦的事情,特别是我上班的科兴科技园这一块,去的人非常多,每天上班跟春运一样,如果我能换到以前的大冲上班那就幸福了,可惜,换不得。

尤其是我这个站等车的多的一笔,上班公交挤的不行,车满的时候只有少部分人能硬挤上去。通常我只会用两个字来形容这种人:“公交怪”

想当年我朋友瘦的像只猴还能上去,老子身高182体重72kg挤个公交,不成问题,反手一个阻挡,闷声发大财,前面的阿姨你快点阿姨,别磨磨唧唧的,快上去啊阿姨,嗯?你还想挤掉我?你能挤掉我?你能挤掉我!我当场!把车吃了!

....

咳咳,挤公交是不可能挤公交滴,因为今天我发现了一个可以定制路线的网约巴士公众号【深圳xxx】

但是呢,票经常会被抢光,同时我还我发现,有时候会有人退票,这时候就有空余票了,关键是我不可能时时都在公众号上盯着,于是,我就写了一个抢票+短信通知的小工具

获取接口信息

查看页面结构

这个就是订票页面,显示当前月的车票情况,根据图示,红色为已满,绿色为已购,灰色为不可选  如果是可选就是白色的小方块,并且在下面显示余票,如下图所示:

 我们打算这么做,

  1. 定时抓取返回的接口信息

  2. 根据接口返回值判断是否有余票

好,审查下源代码看下接口信息,等等,微信浏览器没办法审查源代码,于是

使用chrome 调试微信公众号网页页面

首先面临个问题,如果直接copy公众号网页Url在chrome打开的话,就会显示这个画面,他被302重定向到了这个页面,所以是行不通的,只有获取OAuth2.0授权才能进去

所以我们得先通过抓包工具,知道手机访问微信公众号网页的时候,需要带什么信息过去,这时候我们就得借助抓包工具,因为我电脑是Mac,用不了 Fiddler,我用的是 Charles花瓶,就是下面这位仁兄

 借助这个工具,我们只需3步就可以轻松搞定手机数据抓包:

  1. 获取本机IP地址和端口

  2. 设置代理手机上网

  3. 依次执行上面两步

获取本机IP地址和端口

第一步,找到端口号,一般默认是8088,但是为了确认可以打开 ProxyProxySetting看下,哦原来我之前设置成了8888

 然后找到 Charles的 helpLocalIPAddress,点击它就会看到自己的本机地址,找到本机地址记下来,然后进行下一步

设置代理手机上网

首先保证手机跟电脑连接的是同一个wifi,然后在wifi设置那里会有设置代理信息,比如我的猴米...不对,小米9手机!设置如下:

输入上一步获取主机名,端口号就ok了 

输入完成,点击确定后。 Charles就会弹出一个对话框,问你是否同意接入代理,点击确定allow就行了。

用手机访问目标网页

我们用手机访问微信公众号【深圳x出行】进入到抢票页面后,发现 Charles已经成功抓包到了网页信息,当我们进入这个抢票页面的时候,他会发起两个请求,一个是获取document文档内容,一个post请求获取票务信息。

仔细分析了下,大概明白了业务逻辑:

整个项目技术站是java+jsp,传统写法,用户身份验证主要是cookie+session方案,前端这一块主要是使用 jQuery

当用户进入页面的时候,会携带查询参数,如起始站点,时间,车次等信息和cookie请求document文档, 也就是圈起来的这一块, 

 而我们想要的核心内容:日历表,一开始是不显示的

因为还要在请求一次

第二次请求,携带cookie和以上的查询参数发起一个post请求,获取当月的车票信息,也就是日历表内容

下面这个是请求当月票务信息,然而发现他返回的是一堆html节点

好吧...估计是获取到之后直接 append到 div里面的,然后渲染生成日历表内容 

接着在手机上操作,选择两个日期,然后点击下单,发送购票请求,拉取购票接口,我们看下购票接口的请求和返回内容:

 看下request 内容,根据字段的意思大概明白是线路,时间,以及车票金额,还有支付方式

在看看返回的内容:返回一个json字符串数据,里面大概涵盖了下单的成功返回码,时间,id号等等信息 

记录所需要的信息内容

根据上面的分析,总结下内容:整个项目用户身份验证是使用 cookie和 session方案,请求数据用的是 form data方式,请求字段啥的我们也都清楚,唯独有一点,就是请求余票的时候,返回的是html节点代码,而不是我们预期的json数据,这样就有个麻烦,我们没办法一目了然的明白他余票的时候是如何显示的

所以我们只能通过 chrome进行调试,才能得出他是如何判断余票的。

我们找个记事本,记录下信息,记录的内容有:

  1. 请求余票接口和购票接口的 url地址

  2. cookie信息

  3. 各自的 request参数字段

  4. user-Agent信息

  5. 各自的 response返回内容

设置chrome

有以上信息后,我们就可以开始用chrome调试了, 首先打开 MoretoolsNetworkconditions

 把 user-Agent填入到 Custom里面

Charles抓包本地请求

因为我们要把获取到的cookie填入到chrome里面,以我们的用户身份去访问网页,所以我们需要在请求目标地址的时候,改包修改cookie

首先我们需要开启 macOSProxy,抓包我们的http请求

 打开chrome访问目标网址,我们可以看到 Charles上已经抓包到了我们访问的目标url地址,然后给目标url地址打上断点,方便调试

 然后再次访问,这时候断点就生效了,弹出一个tab名为 breakpoints,可以看到之所以我们还是不能访问到目标网址,是因为 sessionId不对,所以我们把抓取到的 cookie在填入到里面,点击 execute

 这时候,能够正确跳到目标页面了。

 大概看了下他整体布局,和 jQuery代码 CSS代码,特别是日历表那一块

审查了下元素发现:

  1. 小方块的结构为:

class="b">

这里为日期

如果有余票则显示余票数量

  1. td的样式名为 a代表不可选

  2. 样式名为 e代表已满

  3. 样式名为 d代表已购

  4. 样式名为 b则是我们要找的,代表可选,也就是有余票

到这一步,整个购票流程就清楚了

到时候我们通过Node.js请求的时候,处理返回数据,用正则去判断是否有余票的class名 b ,有余票的话,在获取div里面的余票数量内容就Ok了

Node.js 请求目标接口

分析需要开发的功能点

写代码之前我们需要想好功能点,我们需要什么功能:

  1. 请求余票接口

  2. 定时请求任务

  3. 有余票则自动请求购票接口下订单

  4. 调用腾讯云短信api接口发送短信通知

  5. 多个用户抢票功能

  6. 抢某个日期的票

首先 mkdir ticket 创建名为ticket的文件夹,接着 cd ticket进入文件夹 npm init一路瞎几把回车也无妨。下面开始安装依赖,根据上面的功能需求,我们大概需要:

  1. 请求工具,这里看个人习惯,你也可以使用原生的 http.request,我这里选择用的是 axios,毕竟 axios在node端底层也是调用 http.request

cnpm install axios --save

  1. 定时任务 node-schedule

cnpm install node-schedule --save

  1. node端选择dom节点工具 cheerio

cnpm install cheerio --save

  1. 腾讯发短信的依赖包 qcloudsms_js

cnpm install qcloudsms_js

  1. 热更新包,诺豆的妈妈, nodemon (其实不用也可以)

cnpm install nodemon --save-dev

开发请求余票接口

接着 touch index.js创建核心js文件,开始编码:

首先引入所有依赖

const axios = require('axios')

const querystring = require("querystring"); //序列化对象,用qs也行,都一样

let QcloudSms = require("qcloudsms_js");

let cheerio = require('cheerio');

let schedule = require('node-schedule');

然后我们先定义请求参数,来一个obj

let obj = {

data: {

lineId: 111130, //路线id

vehTime: 0722, //发车时间,

startTime: 0751, //预计上车时间

onStationId: 564492, //预定的站点id

offStationId: 17990,//到站id

onStationName: '宝安交通运输局③', //预定的站点名称

offStationName: "深港产学研基地",//预定到站名称

tradePrice: 0,//总金额

saleDates: '17',//车票日期

beginDate: '',//订票时间,滞空,用于抓取到余票后填入数据

},

phoneNumber: 123123123, //用户手机号,接收短信的手机号

cookie: 'JSESSIONID=TESTCOOKIE', // 抓取到的cookie

day: "17" //定17号的票,这个主要是用于抢指定日期的票,滞空则为抢当月所有余票

}

接着声明一个名为 queryTicket的类,为啥要用类呢,因为基于第五个需求点,多个用户抢票的时候,我们分别 new一下就行了,

同时我们希望能够记录请求余票的次数,和当抢到票后自动停止查询余票的操作,所以给他加上个计数变量 times和是否停止的变量,布尔值 stop

编写代码:

class QueryTicket{

/**

*Creates an instance of QueryTicket.

* @param {Object} { data, phoneNumber, cookie, day }

* @param data {Object} 请求余票接口的requery参数

* @param phoneNumber {Number} 用户手机号,短信需要用到

* @param cookie {String} cookie信息

* @params day {String} 某日的票,如'18'

* @memberof QueryTicket 请求余票接口

*/

constructor({ data, phoneNumber, cookie, day }) {

this.data = data

this.cookie = cookie

this.day = day

this.phoneNumber = phoneNumber

this.postData = querystring.stringify(data)

this.times = 0; //记录次数

let stop = false //通过特定接口才能修改stop值,防止外部随意串改

this.getStop = function () { //获取是否停止

return stop

}

this.setStop = function (ifStop) { //设置是否停止

stop = ifStop

}

}

}

下面开始定义原型方法,为了方便维护,我们把逻辑拆分成各个函数

class QueryTicket{

constructor({ data, phoneNumber, cookie, day }) {

//constructor代码...

}

init(){}//初始化

handleQueryTicket(){}//查询余票的逻辑

requestTicket(){} //调用查询余票接口

handleBuyTicket(){} //购票相关逻辑

requestOrder(){}//调用购票接口

handleInfoUser(){}//通知用户的逻辑

sendMSg(){} //发短信接口

}

所有数据都是基于查询余票的操作,因此我们先开发这部分功能

class QueryTicket{

constructor({ data, phoneNumber, cookie, day }) {

//constructor代码...

}

//初始化,因为涉及到异步请求,所以我们使用`async await`

async init(){

let ticketList = await this.handleQueryTicket() //返回查询到的余票数组

}

//查询余票的逻辑

handleQueryTicket(){

let ticketList = [] //余票数组

let res = await this.requestTicket()

this.times++ //计数器,记录请求查询多少次

let str = res.data.replace(/\\/g, "") //格式化返回值

let $ = cheerio.load(`<div class="main">${str}</div>`) // cheerio载入查询接口response的html节点数据

let list = $(".main").find(".b") //查找是否有余票的dom节点

// 如果没有余票,打印出请求多少次,然后返回,不执行下面的代码

if (!list.length) {

console.log(`用户${this.phoneNumber}:无票,已进行${this.times}次`)

return

}

// 如果有余票

list.each((idx, item) => {

let str = $(item).html() //str这时格式是21&$x4F59;0

//最后一个span 的内容其实"余0",也就是无票,只不过是被转码了而已

//因此要在下一步对其进行格式化

let arr = str.split(/||\&\#x4F59\;/).filter(item => !!item === true)

let data = {

day: arr[0],

ticketLeft: arr[1]

}

//如果是要抢指定日期的票

if (this.day) {

//如果有指定日期的余票

if (parseInt(data.day) === parseInt(data.day)) {

ticketList.push(data)

}

} else {

//如果不是,则返回查询到的所有余票

ticketList.push(data)

}

})

return ticketList

}

//调用查询余票接口

requestTicket(){

return axios.post('http://weixin.xxxx.net/ebus/front/wxQueryController.do?BcTicketCalendar', this.postData, {

headers: {

'Content-Type': 'application/x-www-form-urlencoded',

'User-Agent': "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12A365 MicroMessenger/5.4.1 NetType/WIFI",

"Cookie": this.cookie

}

})

}

handleBuyTicket(){} //购票相关逻辑

requestOrder(){}//调用购票接口

handleInfoUser(){}//通知用户的逻辑

sendMSg(){} //发短信接口

}

来解释下那行正则, cheerio抓取到的dom是长这样的,第一个 span内容是日期,第二个是余票数量  所以我们要把它格式化变成这种数组,也就是 ticketList 

开发购票功能

首先我们在 init方法里做个判断,如果有余票才去购票,没有余票购个毛

class QueryTicket{

constructor({ data, phoneNumber, cookie, day }) {

//constructor代码...

}

//初始化

async init(){

let ticketList = await this.handleQueryTicket()

//如果有余票

if (ticketList.length) {

//把余票传入购票逻辑方法,返回短信通知所需要的数据

let resParse = await this.handleBuyTicket(ticketList)

}

}

//查询余票的逻辑

async handleQueryTicket(){

// 查询余票代码...

}

//调用查询余票接口

requestTicket(){

//调用查询余票接口代码...

}

//购票相关逻辑

async handleBuyTicket(ticketList){

let year = new Date().getFullYear() //年份,

let month = new Date().getMonth() + 1 //月份,拼接购票日期用得上,因为余票接口只返回几号

let {

onStationName,//起始站点名

offStationName,//结束站点名

lineId,//线路id

vehTime,//发车时间

startTime,//预计上车时间

onStationId,//上车的站台id

offStationId //到站的站台id

} = this.data // 初始化的数据

let station = `${onStationName}-${offStationName}` //站点,发短信时候用到:"宝安交通局-深港产学研基地"

let dateStr = ""; //车票日期

let tickAmount = "" //总张数

ticketList.forEach(item => {

dateStr = dateStr + `${year}-${month}-${item.day},`

tickAmount = tickAmount + `${item.ticketLeft}张,`

})

let buyTicket = {

lineId,//线路id

vehTime,//发车时间

startTime,//预计上车时间

onStationId,//上车的站点id

offStationId,//目标站点id

tradePrice: '5', //金额

saleDates: dateStr.slice(0, -1),

payType: '2' //支付方式,微信支付

}

// 调用购票接口

let data = querystring.stringify(buyTicket)

let res = await this.requestOrder(data) //返回json数据,是否购票成功等等

//把发短信所需要数据都要传入

return Object.assign({}, JSON.parse(res.data), { queryParam: { dateStr, tickAmount, startTime, station } })

}//购票相关逻辑

//调用购票接口

requestOrder(obj){

return axios.post('http://weixin.xxxx.net/ebus/front/wxQueryController.do?BcTicketBuy', obj, {

headers: {

'Content-Type': 'application/x-www-form-urlencoded',

'User-Agent': "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12A365 MicroMessenger/5.4.1 NetType/WIFI",

"Cookie": this.cookie

}

})

}

handleInfoUser(){}//通知用户的逻辑

sendMSg(){} //发短信接口

}

到这里,查询余票,购票这两个核心操作已经完成。

目前还剩下,如何通知用户是否购票成功。

之前我尝试过使用qq邮箱的smtp服务,抢票成功后发送邮件通知,但是我觉得吧,并不好用,主要是我没有打开邮箱的习惯,没网也收不到,所以,并没有采纳这个方案。

加上之前我注册过企业认证的公众号,腾讯云免费送了我1000条短信通知,而且短信也比较直观,所以我这里就安装腾讯云的SDK,部署了一套发短信的功能。

腾讯云短信的相关内容

其实看看文档就行了,我也是copy文档,注意看短信单发那部分

https://cloud.tencent.com/document/product/382/3772

如果跟我一样有企业认证的话,看快速入门这里就行了,一步步跟着操作  看下短信正文, {Number}这些里面的数字是变量。

就是说短信的模板是固定的,但是里面有 {Number}的内容可以自定义

调用的时候,里面的数字对应着传过去的参数数组序号,{1}代表数组[0]参数,以此类推 

提交审核,审核一般很快就通过,也就是几十万毫秒吧

 

开发通知功能

class QueryTicket{

constructor({ data, phoneNumber, cookie, day }) {

//constructor代码...

}

//初始化

async init(){

let ticketList = await this.handleQueryTicket()

//如果有余票

if (ticketList.length) {

//把余票传入购票逻辑方法,返回短信通知所需要的数据

let resParse = await this.handleBuyTicket(ticketList)

//执行通知逻辑

this.handleInfoUser(resParse)

}

}

//查询余票的逻辑

async handleQueryTicket(){

// 查询余票代码...

}

//调用查询余票接口

requestTicket(){

//调用查询余票接口代码...

}

//购票相关逻辑

async handleBuyTicket(ticketList){

//购票代码...

}

//调用购票接口

requestOrder(obj){

//购票接口请求代码...

}

//通知用户的逻辑

async handleInfoUser(parseData){

//获取上一步购票的response数据和我们拼接的数据

let { returnCode, returnData: { main: { lineName, tradePrice } }, queryParam: { dateStr, tickAmount, startTime, station } } = parseData

//如果购票成功,则返回500

if (returnCode === "500") {

let res = await this.sendMsg({

dateStr, //日期

tickAmount: tickAmount.slice(0, -1), //总张数

station, //站点

lineName, //巴士名称/路线名称

tradePrice,//总价

startTime,//出发时间

phoneNumber: this.phoneNumber,//手机号

})

//如果发信成功,则不再进行抢票操作

if (res.result === 0 && res.errmsg === "OK") {

this.setStop(true)

} else {

//失败不做任何操作

console.log(res.errmsg)

}

} else {

//失败不做任何操作

console.log(resParse['returnInfo'])

}

}

//发短信接口

sendMSg(){

let { dateStr, tickAmount, station, lineName, phoneNumber, startTime, tradePrice } = obj

let appid = 140034324; // SDK AppID 以1400开头

// 短信应用 SDK AppKey

let appkey = "asdfdsvajwienin23493nadsnzxc";

// 短信模板 ID,需要在短信控制台中申请

let templateId = 7839; // NOTE: 这里的模板ID`7839`只是示例,真实的模板 ID 需要在短信控制台中申请

// 签名

let smsSign = "测试短信"; // NOTE: 签名参数使用的是`签名内容`,而不是`签名ID`。这里的签名"腾讯云"只是示例,真实的签名需要在短信控制台申请

// 实例化 QcloudSms

let qcloudsms = QcloudSms(appid, appkey);

let ssender = qcloudsms.SmsSingleSender();

// 这里的params就是短信里面可以自定义的内容,也就是填入{1}{2}..的内容

let params = [dateStr, station, lineName, startTime, tickAmount, tradePrice];

//用promise来封装下异步操作

return new Promise((resolve, reject) => {

ssender.sendWithParam(86, phoneNumber, templateId, params, smsSign, "", "", function (err, res, resData) {

if (err) {

reject(err)

} else {

resolve(resData)

}

});

})

}

}

如果发信成功,返回 result:0 

到这里,大部分需求已经完成了,还剩下一个定时任务

定时任务

也声明一个类,这里我们用到的是 schedule

// 定时任务

class SetInter {

constructor({ timer, fn }) {

this.timer = timer // 每几秒执行

this.fn = fn //执行的回调

this.rule = new schedule.RecurrenceRule(); //实例化一个对象

this.rule.second = this.setRule() // 调用原型方法,schedule的语法而已

this.init()

}

setRule() {

let rule = [];

let i = 1;

while (i < 60) {

rule.push(i)

i += this.timer

}

return rule //假设传入的timer为5,则表示定时任务每5秒执行一次

// [1, 6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 56]

}

init() {

schedule.scheduleJob(this.rule, () => {

this.fn() // 定时调用传入的回调方法

});

}

}

多个用户抢票

假设我们有两个用户要抢票,所以定义两个obj,实例化下 QueryTicket

data: { //用户1

lineId: 111130,

vehTime: 0722,

startTime: 0751,

onStationId: 564492,

offStationId: 17990,

onStationName: '宝安交通运输局③',

offStationName: "深港产学研基地",

tradePrice: 0,

saleDates: '',

beginDate: '',

},

phoneNumber: 123123123,

cookie: 'JSESSIONID=TESTCOOKIE',

day: "17"

}

let obj2 = { //用户2

data: {

lineId: 134423,

vehTime: 1820,

startTime: 1855,

onStationId: 4322,

offStationId: 53231,

onStationName: '百度国际大厦',

offStationName: "裕安路口",

tradePrice: 0,

saleDates: '',

beginDate: '',

},

phoneNumber: 175932123124,

cookie: 'JSESSIONID=TESTCOOKIE',

day: ""

}

let ticket = new QueryTicket(obj) //用户1

let ticket2 = new QueryTicket(obj2) //用户2

new SetInter({

timer: 1, //每秒执行一次,建议5秒,不然怕被ip拉黑,我这里只是为了方便下面截图

fn: function () {

[ticket,ticket2].map(item => { //同时进行两个用户的抢票

if (!item.getStop()) { //调用实例的原型方法,判断是否停止抢票,如果没有则继续抢

item.init()

} else { // 如果抢到票了,则不继续抢票

console.log('stop')

}

})

}

})

node index.js 运行下,跑起来了

 如果他抢到票的话,我就会收到短信通知:

 打开手机,看下订单信息

 搞定,收工

写在最后

其实可以在此基础上还能添加更多功能,比如直接抓取登录接口获取cookie,指定路线抢票,还有错误处理啊啥的

值得注意的是,请求接口不能太频繁,最好控制在5秒一次的频率,不然会给别人造成困扰,也容易被ip拉黑

如果想把它做成一个完整的项目,建议使用ts加持 ,关于ts我推荐阅读这篇JD前端写的文章

https://juejin.im/post/5d8efeace51d45782b0c1bd6

希望各位能有所收获

最后

短信接口抓包_[实战] 实现抢票小工具amp;短信通知提醒相关推荐

  1. 注册登录页面代码用js判断是否填入信息_(实战)Node.js 实现抢票小工具amp;短信通知提醒...

    作者:西岚 https://juejin.im/post/5dadd0236fb9a04de04d968e 写在前言 要知道在深圳上班是非常痛苦的事情,特别是我上班的科兴科技园这一块,去的人非常多,每 ...

  2. 注册登录页面代码用js判断是否填入信息_(实战)Node.js 实现抢票小工具amp;amp;短信通知提醒...

    作者:西岚 本文经作者 @西岚 授权分享,文末点击阅读原文可跳转原文查看. 写在前言 要知道在深圳上班是非常痛苦的事情,特别是我上班的科兴科技园这一块,去的人非常多,每天上班跟春运一样,如果我能换到以 ...

  3. (实战)Node.js 实现抢票小工具短信通知提醒

    作者:西岚 https://juejin.im/post/5dadd0236fb9a04de04d968e 写在前言 要知道在深圳上班是非常痛苦的事情,特别是我上班的科兴科技园这一块,去的人非常多,每 ...

  4. java 阿里大于短信接口_阿里大于发送短信使用教程指导

    阿里大于发送短信: 准备工作: (1)应用管理 --> 应用列表 --> 创建应用,从而获取APP Key和APP Secret (2)获取短信签名.短信模板 准备工作完成之后的短信模板 ...

  5. mob sdk vue 短信验证_短信接口那些事儿,你知道多少?

    短信接口那些事儿,你知道多少? 无数宣传报道,很多人都了解短信接口在企业和用户.企业跟员工之间连接的方便性,而被很多企业商家所应用.只是有些人们还是对短信接口使用原则和用途不是很明白,今天小编就带给大 ...

  6. python短信接口_短信接口DEMO-PYTHON

    云通讯平台-PYTHON短信接口开发示例 #!/usr/bin/python #2.0+++ import requests#导入request模块 import json import hashli ...

  7. java短信接口 调用_带你了解短信接口的调用

    查看接口说明 image.png 注册账号 注册成功的页面如下: image.png 设置短信内容的签名 比如:将短信签名设置为yzc image.png 查取短信接口密钥 image.png 设计J ...

  8. java网站短信接口_网云JAVA短信接口API

    final String userName = "注册用户名"; final String key = "接口鉴权KEY"; final String mobi ...

  9. 一信通短信接口对接_短信接口对接流程

    下面是php开发语言短信接口接入到项目中的demo示例: // ① 该代码仅供接入动力思维乐信短信接口参考使用,客户可根据实际需要自行编写: // ② 支持发送验证码短信.触发通知短信等: // ③ ...

最新文章

  1. mysql查询赋值、修改拼接字符串
  2. 记录服务器连接jupyter notebook过程
  3. 打印机十大共性故障解决方法
  4. HTML、CSS、JS都有哪些区别
  5. ImageView一例
  6. 一般图最大匹配——带花树
  7. 大咖开讲:一小时学会.NET MVC开发的那些事儿
  8. 【Vue】【Router】手动跳转用 this.$router.push() 时 $router 未定义的问题
  9. 写给大数据开发初学者的话4
  10. 浙江义乌发现桥头遗址,将5000年中华文明,再前推4000年?
  11. neu1250矩阵快速幂哪~~
  12. mysql面试题sql语句_数据库MySQL经典面试题之SQL语句
  13. 解题:NOI 2016 优秀的拆分
  14. Python max函数中key的用法
  15. Hash算法及常见碰撞解决方法
  16. 辣椒app软件测试,testflight辣椒视频APP
  17. 聚观早报 | 通信行程卡正式宣布下线;《三体》首日播放量破1亿
  18. SDN学习继续(二)
  19. 在线培训考核系统源码
  20. 查询指定数据库指定表的指定字段的SQL语句

热门文章

  1. 通信网基础作业答案整理
  2. django 内置标签与过滤器
  3. python信用卡识别_在python中验证信用卡号码
  4. 如何从零开始学习软件测试
  5. [英语语法]句法之定语从句
  6. \t \n \n\t在python中的用法
  7. 量子计算机qled,量子点发光原理详解
  8. Echarts引入省级地图(简便快捷,以浙江省为例)
  9. 实验三:Windows7操作系统安全
  10. 【PyTorch深度学习项目实战100例】—— 基于CNN实现书法字体风格识别任务 | 第62例