(实战)Node.js 实现抢票小工具短信通知提醒
作者:西岚
https://juejin.im/post/5dadd0236fb9a04de04d968e
写在前言
要知道在深圳上班是非常痛苦的事情,特别是我上班的科兴科技园这一块,去的人非常多,每天上班跟春运一样,如果我能换到以前的大冲上班那就幸福了,可惜,换不得。
尤其是我这个站等车的多的一笔,上班公交挤的不行,车满的时候只有少部分人能硬挤上去。通常我只会用两个字来形容这种人:“公交怪”
想当年我朋友瘦的像只猴还能上去,老子身高182体重72kg挤个公交,不成问题,反手一个阻挡,闷声发大财,前面的阿姨你快点阿姨,别磨磨唧唧的,快上去啊阿姨,嗯?你还想挤掉我?你能挤掉我?你能挤掉我!我当场!把车吃了!
....
咳咳,挤公交是不可能挤公交滴,因为今天我发现了一个可以定制路线的网约巴士公众号【xxxxxx】
但是呢,票经常会被抢光,同时我还我发现,有时候会有人退票,这时候就有空余票了,关键是我不可能时时都在公众号上盯着,于是,我就写了一个抢票+短信通知的小工具
获取接口信息
查看页面结构
这个就是订票页面,显示当前月的车票情况,根据图示,红色为已满,绿色为已购,灰色为不可选
如果是可选就是白色的小方块,并且在下面显示余票,如下图所示:
我们打算这么做,
定时抓取返回的接口信息
根据接口返回值判断是否有余票
好,审查下源代码看下接口信息,等等,微信浏览器没办法审查源代码,于是
使用chrome 调试微信公众号网页页面
首先面临个问题,如果直接copy公众号网页Url在chrome打开的话,就会显示这个画面,他被302重定向到了这个页面,所以是行不通的,只有获取OAuth2.0授权才能进去
所以我们得先通过抓包工具,知道手机访问微信公众号网页的时候,需要带什么信息过去,这时候我们就得借助抓包工具,因为我电脑是Mac,用不了Fiddler
,我用的是Charles
花瓶,就是下面这位仁兄
借助这个工具,我们只需3步就可以轻松搞定手机数据抓包:
获取本机IP地址和端口
设置代理手机上网
依次执行上面两步
获取本机IP地址和端口
第一步,找到端口号,一般默认是8088,但是为了确认可以打开Proxy
/Proxy Setting
看下,哦原来我之前设置成了8888
然后找到Charles
的help
/Local IP Address
,点击它就会看到自己的本机地址,找到本机地址记下来,然后进行下一步
设置代理手机上网
首先保证手机跟电脑连接的是同一个wifi,然后在wifi设置那里会有设置代理信息,比如我的猴米...不对,小米9手机!设置如下:
输入上一步获取主机名,端口号就ok了
输入完成,点击确定后。Charles
就会弹出一个对话框,问你是否同意接入代理,点击确定allow就行了。
用手机访问目标网页
我们用手机访问微信公众号【xxxx】进入到抢票页面后,发现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
进行调试,才能得出他是如何判断余票的。
我们找个记事本,记录下信息,记录的内容有:
请求余票接口和购票接口的
url
地址cookie
信息各自的
request
参数字段user-Agent
信息各自的
response
返回内容
设置chrome
有以上信息后,我们就可以开始用chrome调试了, 首先打开More tools
/Network conditions
把user-Agent
填入到Custom
里面
Charles抓包本地请求
因为我们要把获取到的cookie填入到chrome里面,以我们的用户身份去访问网页,所以我们需要在请求目标地址的时候,改包修改cookie
首先我们需要开启 macOS Proxy
,抓包我们的http请求
打开chrome访问目标网址,我们可以看到Charles
上已经抓包到了我们访问的目标url地址,然后给目标url地址打上断点,方便调试
然后再次访问,这时候断点就生效了,弹出一个tab名为break points
,可以看到之所以我们还是不能访问到目标网址,是因为sessionId
不对,所以我们把抓取到的cookie
在填入到里面,点击execute
这时候,能够正确跳到目标页面了。
大概看了下他整体布局,和jQuery
代码CSS
代码,特别是日历表那一块
审查了下元素发现:
**小方块的结构为:**
<td class="b">
<span>这里为日期</span>
<span>如果有余票则显示余票数量</span>
</td>
td的样式名为
a
代表不可选样式名为
e
代表已满样式名为
d
代表已购样式名为
b
则是我们要找的,代表可选,也就是有余票
到这一步,整个购票流程就清楚了
到时候我们通过Node.js请求的时候,处理返回数据,用正则去判断是否有余票的class名b
,有余票的话,在获取div里面的余票数量内容就Ok了
Node.js 请求目标接口
分析需要开发的功能点
写代码之前我们需要想好功能点,我们需要什么功能:
请求余票接口
定时请求任务
有余票则自动请求购票接口下订单
调用腾讯云短信api接口发送短信通知
多个用户抢票功能
抢某个日期的票
首先mkdir ticket
创建名为ticket的文件夹,接着cd ticket
进入文件夹npm init
一路瞎几把回车也无妨。 下面开始安装依赖,根据上面的功能需求,我们大概需要:
请求工具,这里看个人习惯,你也可以使用原生的`http.request`,我这里选择用的是`axios`,毕竟`axios`在node端底层也是调用`http.request`
cnpm install axios --save
定时任务 `node-schedule`
cnpm install node-schedule --save
node端选择dom节点工具 `cheerio`
cnpm install cheerio --save
腾讯发短信的依赖包 `qcloudsms_js`
cnpm install qcloudsms_js
热更新包,诺豆的妈妈,`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, //路线idvehTime: 0722, //发车时间,startTime: 0751, //预计上车时间onStationId: 564492, //预定的站点idoffStationId: 17990,//到站idonStationName: '宝安交通运输局③', //预定的站点名称offStationName: "深港产学研基地",//预定到站名称tradePrice: 0,//总金额saleDates: '17',//车票日期beginDate: '',//订票时间,滞空,用于抓取到余票后填入数据},phoneNumber: 123123123, //用户手机号,接收短信的手机号cookie: 'JSESSIONID=TESTCOOKIE', // 抓取到的cookieday: "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 = cookiethis.day = daythis.phoneNumber = phoneNumberthis.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这时格式是<span>21</span><span>&$x4F59;0</span>//最后一个span 的内容其实"余0",也就是无票,只不过是被转码了而已//因此要在下一步对其进行格式化let arr = str.split(/<span>|<\/span>|\&\#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,//线路idvehTime,//发车时间startTime,//预计上车时间onStationId,//上车的站台idoffStationId //到站的站台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,//线路idvehTime,//发车时间startTime,//预计上车时间onStationId,//上车的站点idoffStationId,//目标站点idtradePrice: '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文档,注意看短信单发那部分
cloud.tencent.com/document/pr…
如果跟我一样有企业认证的话,看快速入门这里就行了,一步步跟着操作
看下短信正文,{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//如果购票成功,则返回500if (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 } = objlet appid = 140034324; // SDK AppID 以1400开头// 短信应用 SDK AppKeylet appkey = "asdfdsvajwienin23493nadsnzxc";// 短信模板 ID,需要在短信控制台中申请let templateId = 7839; // NOTE: 这里的模板ID`7839`只是示例,真实的模板 ID 需要在短信控制台中申请// 签名let smsSign = "测试短信"; // NOTE: 签名参数使用的是`签名内容`,而不是`签名ID`。这里的签名"腾讯云"只是示例,真实的签名需要在短信控制台申请// 实例化 QcloudSmslet 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: { //用户1lineId: 111130,vehTime: 0722,startTime: 0751,onStationId: 564492,offStationId: 17990,onStationName: '宝安交通运输局③',offStationName: "深港产学研基地",tradePrice: 0,saleDates: '',beginDate: '',},phoneNumber: 123123123,cookie: 'JSESSIONID=TESTCOOKIE',day: "17"
}
let obj2 = { //用户2data: {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) //用户2new 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
希望各位能有所收获
最后
欢迎关注【前端瓶子君】✿✿ヽ(°▽°)ノ✿
欢迎关注「前端瓶子君」,回复「算法」,加入前端算法源码编程群,每日一刷(工作日),每题瓶子君都会很认真的解答哟!
回复「交流」,吹吹水、聊聊技术、吐吐槽!
回复「阅读」,每日刷刷高质量好文!
如果这篇文章对你有帮助,「在看」是最大的支持
》》面试官也在看的算法资料《《
“在看和转发”就是最大的支持
(实战)Node.js 实现抢票小工具短信通知提醒相关推荐
- 注册登录页面代码用js判断是否填入信息_(实战)Node.js 实现抢票小工具amp;amp;短信通知提醒...
作者:西岚 本文经作者 @西岚 授权分享,文末点击阅读原文可跳转原文查看. 写在前言 要知道在深圳上班是非常痛苦的事情,特别是我上班的科兴科技园这一块,去的人非常多,每天上班跟春运一样,如果我能换到以 ...
- 注册登录页面代码用js判断是否填入信息_(实战)Node.js 实现抢票小工具amp;短信通知提醒...
作者:西岚 https://juejin.im/post/5dadd0236fb9a04de04d968e 写在前言 要知道在深圳上班是非常痛苦的事情,特别是我上班的科兴科技园这一块,去的人非常多,每 ...
- Node.js 做一个抢票小工具!
今天带领大家一起用Node.js 实现抢票小工具&短信通知提醒 作者:西岚 https://mp.weixin.qq.com/s/AqQgDB-0dUp2ScLkqxvLZg 获取接口信息 查 ...
- 短信接口抓包_[实战] 实现抢票小工具amp;短信通知提醒
作者:西岚 写在前言 要知道在深圳上班是非常痛苦的事情,特别是我上班的科兴科技园这一块,去的人非常多,每天上班跟春运一样,如果我能换到以前的大冲上班那就幸福了,可惜,换不得. 尤其是我这个站等车的多的 ...
- 认识Web前端、Web后端、桌面app和移动app新开发模式 - 基于Node.js环境和VS Code工具...
认识Web.桌面和移动app新开发模式 - 基于Node.js环境和VS Code工具 一.开发环境的搭建(基于win10) 1.安装node.js和npm 到node.js官网下载安装包(包含npm ...
- node.js云学堂微信小程序学习系统的设计与实现毕业设计源码011735
Node.js云学堂微信小程序 摘要 信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课 ...
- python脚本抢优惠券_Python写京东抢券小工具
这是一个可以帮助我们抢券的小工具.不一定能抢到,只是增加了概率. 首先我们需要安装python环境 这里我们选用Anaconda,它可以方便的管理python包和环境. 下载地址https://www ...
- Python实现12306自动抢票小程序
项目描述: 本程序通过网络爬虫技术,通过抓包分析出一次购票过程中出现的所有请求,最后通过Python程序一步步实现模拟浏览器进行请求.本程序通过调用云打码平台实现登录时候验证码校验,并能实现不断地监控 ...
- php js asp.net,WebMatrix 3: 方便好用的 ASP.NET、PHP、以及 node.js 網站開發工具
WebMatrix 3: 方便好用的 ASP.NET.PHP.以及 node.js 網站開發工具 05/02/2013 5 分钟可看完 本文内容 WebMatrix 是由 Microsoft 所開發的 ...
最新文章
- .Net Core扩展 SharpPlugs简单上手
- winform中捕获程序未处理的所有异常
- 分类模型的评估方法-精确率(Precision)
- 具体解释站点沙盒期的原因表现与解决的方法
- JSON 序列化 与 反序列化
- 苹果春季新品发布会来了:将推iPhone13 Pro系列紫色版
- 1流明等于多少lux_要想投影仪画质清晰,投影仪流明和对比度不得不看
- 你有必要不沾计算机一段时间英语,新人教版八年级英语下册unit 1必背词组及句子.docx...
- Excel文件怎样加密?这两种方法值得收藏!
- ECharts基础学习 (第二天)
- 用虚拟鸭子CYBERDUCK同步RACKSPACE CLOUD文件
- Vanishing Point Detection 消影点/消失点/灭点检测代码学习整理笔记
- 使用ABAP批量下载有道云笔记中的图片
- fuchsia中virtio 后端实现
- STC12系列单片机的1T模式和12T模式
- 贴片电阻电容封装尺寸对照(转)(主要为了看电阻封装尺寸与功率关系)
- cello2.0安装及相关背景
- 概率论与数理统计学习笔记——第二章
- Texpad for mac(专业的LaTeX编辑工具)
- 汽车销售管理系统java
热门文章
- Android WebView监听console错误信息
- flask_mail通过qq发送邮箱
- 复现Thinkphp5 5.0.22/5.1.29远程代码执行漏洞
- 修改android预览分辨率,wm命令使用方法(修改android 分辨率)修改
- 哈工大视听觉信号处理——听觉部分报告——一种智能家居命令词识别系统的设计
- 【kali-权限提升】(4.2.3)社会工程学工具包:二维码组合攻击
- 如何将 Visual Paradigm 桌面客户端连接到不同的 VP Online 存储库丨使用教程
- Ubuntu出现device not managed 如何解决?
- Excel文件打开后不能编辑,可以这样处理
- 运行mysql时,提示Table ‘performance_schema.session_variables’ doesn’t exis