编程课应该怎么制作?
编程的乐趣应来自实用主义,我大学本科第一门Java编程课,几乎劝退了所有同学,因为那些教学代码不实用且无趣,一点图形化的内容都没有,而实用的编程课应早早展现图形化的成果, 于是我选择开一门前后端齐头并进的全栈课程, 本课程将从零开始, 教授大量实用的图形化案例, 并给出完整源码.本文涉及到的代码完全开源在Github,地址: https://github.com/zhaoolee/it/tree/main/src/Class001
本文是基于 Node.js 和 JavaScript的全栈编程教程, 虽然Node.js 和 JavaScript都被人称为js
, 编程语法也近似, 但两者的确是两门不同的语言;
JavaScript是前端编程语言, 运行在浏览器, 可以用来做各种网页特效, 数值运算, 以及数据请求, JavaScript的独特之处在于, 它没有竞争对手, 浏览器内可以运行的编程语言就它一个, 只要学前端, 就离不开Node.js;
Node.js 是后端编程语言, 也就是传统意义的编程语言, Node.js可以读取本地计算机的文件, 进行极为复杂的运算, 做机器学习, 深度学习, 与Python, C, C++, GoLang等语言算得上竞争对手, Node.js的特色是与JavaScript的语法极为相似, 学会了JavaScript的人, 再去学习Node.js, 会显得非常容易;
本门课程目标是培养一批会写码, 能整活儿的全栈工程师, 全栈工程师也可以被称作全干工程师, 技能丰富, 能独立完成产品的研发, 学完这门课程, 你也可以写出自己的小程序.
学编程, 需要让电脑可以运行代码, 想要运行代码,则需要安装编程语言的开发包。
JavaScript代码可以在浏览器直接运行, 安装Chrome浏览器即可.
Chrome下载地址: https://google.cn/chrome
Node.js 代码需要从官网下载开发包并安装
Node.js官网下载页面
https://nodejs.org/zh-cn/download/
- Windows安装包
https://nodejs.org/dist/v16.13.0/node-v16.13.0-x64.msi
- 通过以上链接, 下载安装包, 右键, 安装(也可以直接双击安装)
- NEXT
- 同意条款,下一步
- 保存默认安装位置, 下一步
默认安装位置为 C:\Program Files\nodejs\
- 下一步
- 下一步
- 开始安装
- 允许更改
- 等待
- 完成
- 搜索PowerShell 并使用管理员身份打开
- 输入
node -v
, 如果能查看到Node.js的版本, 则说明安装成功了
以上是Windows 安装Node.js的传统方案,现在微软官方推出了WSL (Windows Subsystem for Linux)方案,也就是在Windows里安装一个Linux子系统, 可以在Windows使用Linux中超好用的zsh工具(自动路径补全,自动命令提示,文本多彩高亮), 效果惊艳,强烈推荐! 当你用过了zsh, 你就再也无法忍受Windows超级难用的命令行工具了。
Windows安装 WSL教程 程序员福音!Win10下使用oh-my-zsh全攻略,让Win10开发更顺滑…
安装好WSL后, 按照本文Linux安装Node.js部分,直接安装Node.js即可,只需几行命令,方便又快捷。
另外说明一下,安装好Linux后,我们的程序可以直接在Linux中运行,Linux和Windows相当于两台机器,二者有独立的IP, 我们可以通过在Linux中运行 ifconfig | grep inet
来查看Linux的ip, 后文中默认的 localhost:3000
, 也应该修改为 Linux的ip:3000
如果你想更多了解Linux命令,可以查看这个网站 Linux清单
macOS 安装Node.js
下载安装包: https://nodejs.org/dist/v16.13.0/node-v16.13.0.pkg
- 打开安装包
- 继续
- 继续
- 同意(只能同意,不同意就不让用)
- 继续
- 安装
- 验证身份,进行安装
- 安装成功, 关闭对话框
- 安装包建议留一份,重装可以用
- 打开终端
- Linux 安装Node.js
Linux 做编程其实是最爽的,不用考虑Windows乱七八糟的路径问题,开源免费。
如果你追求颜值,比较推荐的Linux桌面发行版是Deepin,Deepin是一款国产Linux, 本地化做的很棒,Deepin镜像免费下载地址 https://www.deepin.org/zh/download/ 清华大学Deepin镜像源直接下载链接 https://mirrors.tuna.tsinghua.edu.cn/deepin-cd/20.2.4/deepin-desktop-community-20.2.4-amd64.iso
在Linux安装Node.js的操作,我在Deepin上完成
- 打开终端
Linux安装Node.js仅需几行命令
cd /opt/
sudo wget https://nodejs.org/dist/v16.13.0/node-v16.13.0-linux-x64.tar.xz
sudo tar xvf node-v16.13.0-linux-x64.tar.xz
sudo echo "export NODE_HOME=/opt/node-v16.13.0-linux-x64" >> ~/.bashrc
sudo echo "export PATH=\$NODE_HOME/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
node -v
- 获取安装包并解压安装包
安装VSCode
VSCode是微软推出的一款代码编辑器,开源免费,功能强大,Windows, macOS, Linux均有对应的版本,且功能一致。
VSCode 官网: https://code.visualstudio.com/
下载页面 https://code.visualstudio.com/Download
下载VSCode 安装包后,双击安装即可。
- 打开VSCode软件, 安装中文语言包
安装完成后,重启VSCode即可生效
VSCode是一个神奇的软件,它不仅可以写代码,还可以搞各种其妙的小插件,比如可以输入 zhihu
搜索知乎插件并安装
安装成功后,即可在写代码累的时候,刷刷知乎,放松一下(职业程序员摸鱼神器)
如果你是个追番达人,可以搜索插件 daily-anime, 安装成功后,使用Alt+L组合键,即可开始享用
我们来写第一段代码
- 在VSCode中新建文件
- 保存文件到桌面
- 在HelloWorld.js 中加入以下代码
const http = require('http');
http.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: 纯文本 text/plain
// 编码类型: utf-8
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
// 返回内容
let content = "来自互联网之神的祝福\n\n";
// 循环十次,不断增加返回内容
for(let i = 0; i < 10; i++){
content = content + "Hello World==>" + i + "\n"
}
// 发送响应数据 "Hello World"
response.end(content);
}).listen(8888);
// 终端打印如下信息
console.log('请用浏览器访问 http://127.0.0.1:8888/');
- 新建终端
- 右键顶部的标签,左键点击复制路径
- 输入命令
- 按回车键, 运行程序
- 访问 http://127.0.0.1:8888
Node.js 程序运行成功!
使用动态的数据源
一套完整的程序,可以分为客户端和服务端,客户端与服务端通过 API (Application Programming Interface,应用程序接口, 简称接口)进行数据交互,很多金融云服务商,会提供一些现成的 API 进行售卖,有些按时间付费,有些按使用(调用)次数付费,我们这里用恒生云交易日历API进行一波调用演示。
API 文档地址: https://www.hs.net/openplat-front/service/serviceDetail/983
通过文档截图可知,我们必填的请求参数,有三个,分别是 证券市场代号secu_market
, 开始日期 strat_date
, 结束日期 end_date
如何通过API向后端程序发起请求?
我们用恒生云的API,就要先申请恒生云的调用权限,于是我申请了一个免费的试用
说明:恒生云入口 https://hs.net , 如果你也想申请 可以注册时用我的邀请码 FP3GUX
, 如果不申请也没关系,我对已经请求的数据做了数据缓存,你运行我提供的程序包,即可获得数据,学习用足够了。
我们使用恒生云的接口,还需要, access_token
, token_type
这两个参数,access_token
, token_type
相当于你的临时身份ID,而这个临时身份ID则需要使用恒生云提供的key和secret, 进行获取;以上规则在 https://www.hs.net/doc/100500_200550.html 里有详细的说明
我这里提供通过key和secret获取Authorization的代码(key和secret两个字段我留空了,换成自己免费申请的即可)。
const https = require('https');
// key 和 secret 要通过注册恒生云获取
const key = "********";
const secret = "************";
// 获得 access_token 文档地址 https://www.hs.net/doc/100500_200550.html
const Authorization = "Basic " + Buffer.from(key+":"+secret).toString('base64');
console.log("==Authorization==>>", Authorization);
const postData = "grant_type=client_credentials";
const token_req = https.request({
hostname: 'sandbox.hscloud.cn',
path: '/oauth2/oauth2/token',
method: "POST",
headers : {
'Authorization': Authorization,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
},
}, (token_res)=>{
token_res.on('data', (chunk) => {
console.log(`BODY: ${chunk}`);
const token_info = JSON.parse(chunk);
const access_token = token_info["access_token"];
const token_type = token_info["token_type"];
console.log('token_info==>>', token_info);
console.log('access_token==>>', access_token);
console.log('token_type==>>', token_type);
}
)})
token_req.write(postData);
token_req.end();
- 有acccess_token, 和 token_type后,我们可以开始获取交易日接口的信息了
图中代码
const https = require('https');
const key = "5ac2e1ee-789e-4d6b-a52d-f533b99d6a36";
const secret = "***********";
// 查看代码前,请仔细阅读文档 文档地址 https://www.hs.net/wiki/api/983_gildataastock_v1_commontable_tadingday.html
const Authorization = "Basic " + Buffer.from(key + ":" + secret).toString('base64')
console.log("==Authorization==>>", Authorization);
const postData = "grant_type=client_credentials";
let token_req = https.request({
hostname: 'sandbox.hscloud.cn',
path: '/oauth2/oauth2/token',
method: "POST",
headers: {
'Authorization': Authorization,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
},
}, (token_res) => {
token_res.on('data', (chunk) => {
console.log(`BODY: ${chunk}`);
const token_info = JSON.parse(chunk);
const access_token = token_info["access_token"];
const token_type = token_info["token_type"]
// 开始请求
// secu_market=77 77代指美国纳斯达克证券交易所 , start_data=2018-01-01 代指开始时间为2018-01-01, end_date=2020-01-05代指结束时间为2020-01-05
const tadingday_post_data = `secu_market=77&start_date=2018-01-01&end_date=2020-01-05`;
const tadingday_authorization = token_type + " " + access_token;
let tadingday_res_body = "";
const tadingday_req = https.request({
method: "POST",
hostname: 'sandbox.hscloud.cn',
path: '/gildataastock/v1/commontable/tadingday',
headers: {
"Authorization": tadingday_authorization,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': tadingday_post_data.length
}
}, (tadingday_res) => {
// 由于返回的数据量过于庞大, 服务器只能将数据分段返回给我们, 我们每次收到分段都会触发data事件, 于是我们将每次返回的数据累加起来
tadingday_res.on('data', (res_data) => {
tadingday_res_body = tadingday_res_body + res_data;
})
// 当程序触发 end事件时, 我们将已经获得的数据打印到终端上
tadingday_res.on('end', ()=>{
console.log(tadingday_res_body);
})
})
tadingday_req.write(tadingday_post_data);
tadingday_req.end();
});
})
token_req.write(postData);
token_req.end();
为了能更好的展示这些数据,我们可以为这些数据做个网页
最终效果
我们写代码是为了实现功能,而Node.js的优质工具包非常多,我们可以用很少的代码,实现复杂的功能,为了更好的使用和管理工具包,我们需要新建一个工程。
我们在终端输入 node -v
, 并回车,可以看到Node.js的版本;如果我们使用 npm -v
也可以看到 npm的版本
npm是随同Node.js一起安装的包管理工具, npm 全称(node.js package manage), 我们使用 which node 和which npm 可以查看到, node和npm安装在同一个目录
创建工程的第一步是给工程起个名字,为了避免出现乱七八糟的编码转换问题,工程和工程内的文件都使用英文字符命名
我们通过命令行创建一个名为tadingday的文件夹,并以此文件夹作为工程目录,进行初始化
mkdir tadingday
cd tadingday
npm init -y
在开发过程中,我们需要用到两个软件包
nodemon
:可以监听文件内容变化,当文件内容变化时,自动重新运行程序,提升开发效率
express
: 大名鼎鼎的Node.js服务器工具包,支持通过中间件,为指定文件夹直接提供http服务,可以方便地解析前端请求参数,并返回数据。
安装nodemon和express
npm install nodemon -D
npm install express
在项目文件夹中运行以上两行代码,即可顺利安装 nodemon和express
安装成功后,我们项目文件夹会多出文件 package-lock.json, 以及文件夹 node_modules,原有 package.json文件的内容也会改变;
对于node_modules和package-lock.json, 我们知道它们存在的意义就好了,即使一不小心删除了,也能通过npm install, 重新安装回来。
而package.json则是非常重要的文件,我们需要理解其中的配置信息,并使用nodemon自定义一些指令
- package.json重要字段解读
为了实现通过网页直接输入参数, 查询恒生的交易日数据,我们需要使用express, 将我们以前的Node.js脚本改写成,可以接受前端请求,返回相应数据的服务端程序;这个服务端程序不仅能将以前在终端打印的数据返回给浏览器,还需要将写好的网页返回给浏览器。
const https = require('https');
const express = require('express');
const app = express()
app.use(express.static('html'))
app.use(express.json()) // for parsing application/json
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
const port = 3000;
const key = "*******";
const secret = "*******";
// 文档地址 https://www.hs.net/wiki/api/983_gildataastock_v1_commontable_tadingday.html
// 示例访问链接 http://localhost:3000/gildataastock/v1/commontable/tadingday?end_date=2021-03-01&start_date=2021-01-01&secu_market=77
async function get_token() {
const tadingday_authorization = await new Promise((resolve, reject) => {
const Authorization = "Basic " + Buffer.from(key + ":" + secret).toString('base64')
console.log("==Authorization==>>", Authorization);
const postData = "grant_type=client_credentials";
let token_res_data = "";
let token_req = https.request({
hostname: 'sandbox.hscloud.cn',
path: '/oauth2/oauth2/token',
method: "POST",
headers: {
'Authorization': Authorization,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
},
}, (token_res) => {
token_res.on('data', (chunk) => {
token_res_data = token_res_data + chunk;
});
token_res.on('end', () => {
console.log(`BODY: ${token_res_data}`);
const token_info = JSON.parse(token_res_data);
const access_token = token_info["access_token"];
const token_type = token_info["token_type"]
const tadingday_authorization = token_type + " " + access_token;
console.log('==tadingday_authorization==>>', tadingday_authorization);
resolve(tadingday_authorization);
})
})
token_req.write(postData);
token_req.end();
})
return tadingday_authorization;
}
app.get('/gildataastock/v1/commontable/tadingday', async (req, res) => {
const tadingday_authorization = await get_token();
console.log('req==>>', req);
const secu_market = req.query.secu_market;
const start_date = req.query.start_date;
const end_date = req.query.end_date;
// 开始请求
const tadingday_post_data = `secu_market=${secu_market}&start_date=${start_date}&end_date=${end_date}`;
let tadingday_res_body = "";
const tadingday_req = https.request({
method: "POST",
hostname: 'sandbox.hscloud.cn',
path: '/gildataastock/v1/commontable/tadingday',
headers: {
"Authorization": tadingday_authorization,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': tadingday_post_data.length
}
}, (tadingday_res) => {
tadingday_res.on('data', (res_data) => {
tadingday_res_body = tadingday_res_body + res_data;
})
tadingday_res.on('end', () => {
console.log(`BODY: ${tadingday_res_body}`);
tadingday_res_body = JSON.parse(tadingday_res_body);
// console.log('=res_data==>>', tadingday_res_body)
res.send(tadingday_res_body)
})
})
console.log('tadingday_post_data===>>', tadingday_post_data)
tadingday_req.write(tadingday_post_data);
tadingday_req.end();
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
运行这个服务端程序后
我们可以通过访问
http://localhost:3000/gildataastock/v1/commontable/tadingday?end_date=2021-03-01&start_date=2021-01-01&secu_market=77
直接从浏览器获取数据
这段代码里使用了express内置的static中间件,直接将html文件夹中的网页内容,作为服务端文件返回
我们访问 http://localhost:3000/tadingday/index.html 也就能自动获取tadingday文件夹中的index.html文件
index.html的网页内容如下
交易日批量查询
交易日批量查询
开始日期
结束日期
选择类型
这段html里还使用了 jQuery 和BootStrap这两个经典前端包,内容放在static文件夹中
static 可以从github下载,地址 https://github.com/zhaoolee/it/tree/main/src/Class001/tadingday/html/tadingday/static
新建一个SQLite本地数据库,将请求数据保存到数据库中
首先,我们需要安装一个Node.js操作sqlite的依赖包 better-sqlite3
npm i better-sqlite3
- 在项目根目录新建一个文件夹
db
-
通过better-sqlite3新建数据库并创建数据表
// 初始化数据库 const db = require('better-sqlite3')(path.join(__dirname, "..", "data", "tadingday.sqlite")); // 新建数据表 // secu_market: 证券市场 if_trading_day:是否交易日 if_week_end:是否周末 if_month_end:是否月末 if_quarter_end:是否季末 if_year_end:是否年末 trading_date:交易日期 db.exec(` CREATE TABLE IF NOT EXISTS tadingday_info ( secu_market VARCHAR(50), if_trading_day VARCHAR(10), if_week_end VARCHAR(10), if_month_end VARCHAR(10), if_quarter_end VARCHAR(10), if_year_end VARCHAR(10), trading_date VARCHAR(16), request_secu_market VARCHAR(16), PRIMARY KEY(request_secu_market, trading_date) ); `);
安装一个sqlite数据查看插件Sqlite Viewer
安装插件Sqlite Viewer后可以查看数据库,数据表
在数据库中,一行数据被称作一条记录, 每一列的列名被称作字段
使用Sql往数据库插入记录的语法
INSERT INTO table_name (字段名1,字段名2,字段名3,...) VALUES (字段名1对应的值,字段名2对应的值,字段名3对应的值,...);
代码思路:当请求获得数据时,调用sava_data
函数, 将请求获得的数据,存储到本地sqlite数据库中
// 往本地数据库插入数据
const insert_tadingday_info = db.prepare(`INSERT INTO tadingday_info (
secu_market,
if_trading_day,
if_week_end,
if_month_end,
if_quarter_end,
if_year_end,
trading_date,
request_secu_market
) values (
@secu_market,
@if_trading_day,
@if_week_end,
@if_month_end,
@if_quarter_end,
@if_year_end,
@trading_date,
@request_secu_market
);`);
// 返回给前端后,存储到数据库
tadingday_res.on('end', () => {
console.log(`BODY: ${tadingday_res_body}`);
tadingday_res_body = JSON.parse(tadingday_res_body);
res.send(tadingday_res_body)
// 存入数据库
tadingday_res_body["data"].map((value) => {
value['request_secu_market'] = secu_market
sava_data(value)
})
})
// 使用sqlite数据库存储请求来的数据
function sava_data(tadingday_info_atom) {
const { secu_market, if_trading_day, if_week_end, if_month_end, if_quarter_end, if_year_end, trading_date, request_secu_market } = tadingday_info_atom;
try {
insert_tadingday_info.run(
{
"secu_market": secu_market,
"if_trading_day": if_trading_day,
"if_week_end": if_week_end,
"if_month_end": if_month_end,
"if_quarter_end": if_quarter_end,
"if_year_end": if_year_end,
"trading_date": trading_date,
"request_secu_market": request_secu_market
}
);
} catch (e) {
// console.log(e);
}
}
在数据源失效时,查询本地数据库获取数据
使用Sql往从数据库获取记录的语法
SELECT 字段名(如果选择所有字段则使用星号\*) FROM 表名 WHERE 条件;
SQL代码思路:
选择交易日期trading_date字段 小于结束时间end_date 并且 大于开始时间start_date 并且 证券市场代号request_secu_market 等于特定的值
select * from tadingday_info
WHERE
trading_date >= @start_date AND
trading_date <= @end_date AND
request_secu_market = @request_secu_market;`
完整代码
// 从本地数据库取数据
const select_tadingday_info = db.prepare(`select * from tadingday_info
WHERE
trading_date >= @start_date AND
trading_date <= @end_date AND
request_secu_market = @request_secu_market;`
);
// 从本地数据库获取数据
function select_data(value) {
const { start_date, end_date, request_secu_market } = value;
console.log("==从本地数据库取数据==")
try {
const result_data = select_tadingday_info.all(
{
"start_date": start_date,
"end_date": end_date,
"request_secu_market": request_secu_market
}
);
console.log('=result_data=>>', result_data);
return result_data
} catch (e) {
// console.log(e);
}
}
运行完整工程代码
运行方法
git clone https://github.com/zhaoolee/it.git --depth=1
cd it/src/Class001/tadingday
npm install
npm run dev004
启动项目的小工具包
启动项目使用了4个小工具包
nodemon的作用是监听特定文件夹,文件内容的变化,当文件产生变化时(保存文件可触发),重新启动项目,让刚刚编写的代码生效。
opener的作用是在浏览器启动新标签页,打开特定url的网页
concurrently 的作用是同时运行多个指令,我们运行004-tadingday-server-and-sqlite.js 文件时,会产生阻塞;而concurrently可以让我们同时运行004-tadingday-server-and-sqlite.js文件和 opener打开特定页面的指令。
wait-on 是一个判断条件的包,我们启用后台服务程序会监听3000端口;但后台服务启动需要一些时间,如果3000端口还未被绑定时,浏览器就已经开始请求,那就会出现请求失败,而wait-on包可以帮我们做一个判断,当3000端口被使用的时候,再使用浏览器打开特定页面,这样就不会出现打开浏览器白屏的情况
"scripts": {
"dev004": "concurrently 'nodemon ./src/004-tadingday-server-and-sqlite.js --watch ./' 'wait-on tcp:3000 && opener http://localhost:3000/tadingday'"
}
package.json中scripts的语法小技巧:
scripts 的指令可以非常灵活的自定义,都是键值对的形式,键为dev004
, 后面的一长串就是值, 这个值需要使用整体引号包裹,如果内部出现子指令,则需要通过单引号包裹。
&&
符号代表了先后关系,前面一个指令执行完成了,后面的指令才会执行。
本文永久更新地址(欢迎来读留言,写评论):
https://www.v2fy.com/p/2021-10-27-node_js-1635325734000