数据库与身份认证

文章目录

  • 数据库与身份认证
    • 1、SQL 的相关学习
      • 1. SQL 的概念
      • 2. SQL 语句学习
        • a. 查询数据(select)、插入数据(insert into)、更新数据(update)、删除数据(delete)
        • b. where 条件、and 和 or 运算符
        • c. order by 排序、count(*) 函数、AS 设置列别名
    • 2、Express 中操作 MySQL
      • 2.1、在项目中操作数据库的步骤
      • 2.2、安装与配置 mysql 模块
        • a. 安装 mysql 模块
        • b. 配置 mysql 模块
        • c. 测试数据库连接是否成功
      • 2.3、使用 mysql 操作数据库
        • a. 查询数据
        • b. 插入数据
        • c. 插入数据的便捷方式
        • d. 更新数据
        • e. 更新数据便捷方式
        • f. 删除数据
        • g. 标记删除
    • 3、前后端的身份认证
      • 3.1、Web 开发模式
        • a. 服务端渲染的 Web 开发模式
        • b. 前后端分离的 Web 开发模式
        • c. 如何选择 Web 开发模式
      • 3.2、身份认证
      • 3.3、Session 认证机制
        • a. HTTP 协议的无状态性和 Cookie 的相关知识
        • b. 在 Express 中使用 Session 认证
          • Session 的工作原理
          • 安装并配置 express-session 中间件
          • 向 session 中存数据
          • 从 session 中取数据
          • 清空 session
      • 3.4、JWT 认证机制
        • a. Session 认证的局限性
        • b. JWT的概念
        • c. 在 Express 中 使用 JWT
          • 安装与配置 JWT 相关的包
          • 定义 secret 密钥
          • 登录成功后生成 JWT 字符串
          • 将 JWT 字符串还原为 JSON 对象
          • 使用 req.user 获取用户信息
          • 捕获解析 JWT 失败后产生的错误
    • extra. 连接数据库报错

1、SQL 的相关学习

1. SQL 的概念

SQL(Structured Query Language)是结构化查询语言,专门用来访问和处理数据库的编程语言。能够让我们以编程的形式,操作数据库里的数据

  1. SQL 是一门数据库编程语言
  2. 使用 SQL 语言编写出来的代码叫做 SQL 语句
  3. SQL 语言只能在关系型数据库中使用
    • 关系型数据库,例如:MySQL、Oracle、SQL Server
    • 非关系型数据库,例如:Mongodb

2. SQL 语句学习

a. 查询数据(select)、插入数据(insert into)、更新数据(update)、删除数据(delete)

/*ps: sql语句中单行注释用 -- 多行注释用 /* ... */sql中关键字大小写不敏感
*/-- SELECT 语句用于从表中查询数据,执行的结果被存储在一个结果集中(称为结果集)-- 从 table_name 表中查询出所有数据
SELECT * FROM table_name
-- 从 table_name 表中查询出列名为 row_name 的列数据
SELECT row_name FROM table_name-- INSERT INTO 语句用于向数据表中插入新的数据行-- 向指定的表中插入如下几列数据,列的值通过 values 一一指定
-- 列和值要一一对应,多个列和多个值之间,使用英文的逗号分隔
INSERT INTO table_name (row1, row2, ...) VALUES (value1, value2, ...)-- UPDATE 语句用于修改表中的数据/*
1. UPDATE 指定要更新哪个表中的数据
2. SET 指定列对应的新值(修改多个值时使用英文逗号分隔)
3. WHERE 指定更新的条件
*/
UPDATE table_name SET row_name = value WHERE condition-- DELETE 语句用于删除表中的行-- 从指定的表中,根据 WHERE 条件,删除对应的数据行
DELETE FROM table_name WHERE condition

b. where 条件、and 和 or 运算符

-- WHERE 字句用于限定选择的标准。在 SELECT、UPDATE、DELETE 语句中,皆可使用 WHERE 子句来限定选择的标准SELECT row_name FROM table_name WHERE row operator value;
UPDATE table_name SET row=newValue WHERE row operator value;
DELETE FROM table_name WHERE row operator value;-- AND 和 OR 可在 WHERE 子句中把两个或多个条件结合起来
-- AND 表示必须同时满足多个条件,相当于 js 中的 && 运算符
-- OR 表示只要满足任意一个条件即可,相当于 js 中的 || 运算符

以下是可在 WHERE 子句中使用的运算符

还有 in(); is null; or; and; not like 等模糊查询

c. order by 排序、count(*) 函数、AS 设置列别名

ORDER BY 语句用于根据指定的列对结果集进行排序,**默认按照升序(ASC)**对记录进行排序,如果想要降序排列,可以使用关键词 DESC 进行设置。

注意:指定多种排序时使用英文的逗号进行分隔

COUNT(*) 函数用于返回查询结果的总数据条数

使用 AS 可以为列设置别名

-- 对 user 表中的数据,按照 id 字段进行升序排序(默认升序排序)
SELECT * FROM user ORDER BY id
SELECT * FROM user ORDER BY id ASC-- 对 user 表中的数据,按照 id 字段进行降序排序
SELECT * FROM user ORDER BY id DESC-- 对 user 表中的数据,先按照 status 进行降序排序,再按照 username 字母的顺序进行升序排序
SELECT * FROM user ORDER BY status DESC, username ASCSELECT COUNT(*) FROM table_name
-- 查询 user 表中 status 为 0 的总数据条数
SELECT COUNT(*) FROM user WHERE status=0-- 将列名称从 COUNT(*) 修改为 total
SELECT COUNT(*) AS total FROM user WHERE status=0

2、Express 中操作 MySQL

2.1、在项目中操作数据库的步骤

  1. 安装操作 MySQL 数据库的第三方模块(mysql)
  2. 通过 mysql 模块连接到 MySQL 数据库
  3. 通过 mysql 模块执行 SQL 语句

2.2、安装与配置 mysql 模块

a. 安装 mysql 模块

mysql 是托管于 npm 上的第三方模块,提供了在 Node.js 项目中连接和操作 MySQL 数据库的能力。

npm install mysql

b. 配置 mysql 模块

使用 mysql 模块操作 MySQL 数据库之前,必须先对 mysql 模块进行必要的配置

// 1. 导入 mysql 模块
const mysql = require('mysql')
// 2. 建立与 MySQL 数据库的连接关系
const db = mysql.createPool({host: 'http://127.0.0.1',  // 数据库的 IP 地址user: 'root',  // 登录数据库的账号password: '123456',  // 登录数据库的密码database: 'my_db_01'  // 指定要操作哪个数据库
})

c. 测试数据库连接是否成功

// 测试 mysql 模块能否正常工作
db.query('select 1', (err, results) => {// mysql 模块工作期间报错if (err) {return console.log(err.message)}// 能够成功执行 SQL 语句,正确结果为 [ RowDataPacket { '1': 1 } ]console.log(results)
})

2.3、使用 mysql 操作数据库

a. 查询数据

// 查询 user 表中的所有数据
const sqlStr = 'select * from user'
db.query(sqlStr, (err, results) => {// 查询数据失败if(err) return console.log(err.message)// 查询数据成功// 如果执行的是 select 查询语句,则执行的结果是数组console.log(results)
})

b. 插入数据

// 向 user 表中插入 username 为 Spider-Man,password 为 pcc321 的数据
// 1. 要插入到 user 表中的数据对象
const user = { username: 'Spider-Man', password: 'pcc321'}
// 2. 待执行的 SQL 语句,其中英文的 ? 表示占位符
const sqlStr = 'INSERT INTO user (username, password) VALUES (?, ?)'
// 3. 使用数组的形式,依次为 ? 占位符指定具体的值
db.query(sqlStr, [user.username, user.password], (err, results) => {//失败if (err) { return console.log(err.message)}// 成功// 如果执行的是 insert into 插入语句,则 results 是一个对象// 可以通过 affectedRows 属性,来判断是否插入数据成功if(results.affectedRows === 1) {console.log('插入数据成功!') }
})

c. 插入数据的便捷方式

// 插入数据的便捷方式
const user = { username: 'Spider-Man2', password: 'pcc4321'}
// 待执行的 SQL 语句
const sqlStr = 'INSERT INTO user SET ?'
// 执行 SQL 语句
db.query(sqlStr, user, (err, results) => {//失败if (err) { return console.log(err.message)}// 成功if(results.affectedRows === 1) {console.log('插入数据成功!') }
})

d. 更新数据

// 1. 要更新的数据对象
const user = { id: 7, username: 'iron-man', password: '000'}
// 2. 要执行的 SQL 语句
const sqlStr = 'UPDATE user SET username=?, password=? WHERE id=?'
// 3. 调用 db.query() 执行 SQL 语句的同时,使用数组依次为占位符指定具体的值
db.query(sqlStr, [user.username, user.password, user.id], (err, results) => {//失败if (err) { return console.log(err.message)}// 成功// 执行 update 语句之后,执行的结果 results 也是一个对象,可以通过 affectedRows 属性判断是否更新成功if(results.affectedRows === 1) {console.log('更新数据成功!') }
})

e. 更新数据便捷方式

更新表数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下便捷方式快速更新数据

// 更新数据的便捷方式
const user = { id: 7, username: 'iron-man01', password: '0000'}
// 定义 SQL 语句
const sqlStr = 'UPDATE user SET ? WHERE id=?'
// 执行 SQL 语句
db.query(sqlStr, [user, user.id], (err, results) => {//失败if (err) { return console.log(err.message)}// 成功// 执行 update 语句之后,执行的结果 results 也是一个对象,可以通过 affectedRows 属性判断是否更新成功if(results.affectedRows === 1) {console.log('更新数据成功!') }
})

f. 删除数据

// 1. 待执行的 SQL 语句
const sqlStr = 'DELETE FROM user WHERE id=?'
// 2. 调用 db.query() 执行 SQL 语句的同时,为占位符指定具体的值
// 注意:如果 SQL 语句中有多个占位符,则必须使用数组为每个占位符指定具体的值
//       如果 SQL 语句中只有一个占位符,则可以省略数组
db.query(sqlStr, 7, (err, results) => {//失败if (err) { return console.log(err.message)}// 成功// 执行 delete 语句之后,执行的结果 results 也是一个对象,也包 affectedRows 属性if(results.affectedRows === 1) {console.log('删除数据成功!') }
})

g. 标记删除

使用 DELETE 语句会真正把数据从表中删除。保险起见,推荐使用标记删除的形式,来模拟删除的动作

标记删除,就是在表中设置类似于 status 这样的状态字段,来标记当前这条数据是否被删除。

当用户执行了删除的动作时,并不需要执行 DELETE 语句把数据删除,而是执行 UPDATE 语句,将这条数据对应的 status 字段标记为删除即可

// 标记删除:使用 UPDATE 语句替代 DELETE 语句,只更新数据的状态,并没有真正删除
db.query('UPDATE user SET status=1 where id=?', 6, (err, results) => {//失败if (err) { return console.log(err.message)}// 成功if(results.affectedRows === 1) {console.log('删除数据成功!') }
})

3、前后端的身份认证

3.1、Web 开发模式

a. 服务端渲染的 Web 开发模式

服务端渲染的概念:服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。因而客户端不需要使用 Ajax 这样的技术额外请求页面的数据

app.get('/index.html', (req, res) => {// 1. 要渲染的数据const user = { name: 'zs', age: 20 }// 2. 服务器端通过字符串的拼接,动态生成 HTML 内容const html = '<h1>姓名:${user.name}, 年龄:${user.age}</h1>'// 3. 把生成好的页面内容响应给客户端,客户端拿到的是真实的 HTML 页面res.send(html)})

服务端渲染的优缺点:

b. 前后端分离的 Web 开发模式

概念:前后端分离的开发技术,依赖于 Ajax 技术的广泛应用,在这种开发模式下,后端只负责提供 API 接口,前端使用 Ajax 调用接口

前后端分离的优缺点:

c. 如何选择 Web 开发模式

不谈业务场景而盲目选择使用何种开发模式都是耍流氓。

  • 比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的 SEO,这时就需要使用服务器端渲染
  • 而类似后台管理项目,交互性比较强,不需要考虑 SEO,这是就可以使用前后端分离的开发模式
  • 为了同时兼顾首页的渲染速度前后端分离的开发效率,一些网站采用了首屏服务器端渲染 + 其他页面前后端分离的开发模式。

3.2、身份认证

身份认证(Authentication)又称 “身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。

在 Web 开发中,涉及到身份认证的场景,例如:各大网站的手机验证码登录邮箱密码登录二维码登录等。

身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户

对于服务端渲染前后端分离这两种开发模式来说,分别有着不同的身份认证方案:

  1. 服务端渲染推荐使用 Session 认证机制
  2. 前后端分离推荐使用 JWT 认证机制

3.3、Session 认证机制

a. HTTP 协议的无状态性和 Cookie 的相关知识

HTTP 协议的无状态性,指的是客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态。使用 Cookie 可以突破 HTTP 无状态的限制。

Cookie 是存储在用户浏览器中的一段不超过 4KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、适用范围的可选属性组成。

不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动当前域名下所有未过期的 Cookie 一同发送到服务器。

Cookie的几大特性

  1. 自动发送
  2. 域名独立
  3. 过期时限
  4. 4KB 限制

Cookie 在身份认证中的作用:

客户端第一次请求服务器时,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中。

随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。

Cookie 不具有安全性:Cookie存储在浏览器中,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性,不建议服务器将重要的隐私数据,通过 Cookie 的形式发给浏览器。

注意:千万不要使用 Cookie 存储重要且隐私的数据!

b. 在 Express 中使用 Session 认证

Session 的工作原理

安装并配置 express-session 中间件
// 安装 express-session 中间件
npm install express-session// 通过 app.use() 来注册 session 中间件
// 1. 导入 session 中间件
const session = require('express-session')// 2. 配置 session 中间件
app.use(session({secret: 'keyboard cat',  // secret 属性的值可以为任意字符串resave: false,           // 固定写法saveUninitialized: true  // 固定写法
}))
向 session 中存数据

当 express-session 中间件配置成功后,即可通过 req.session 来访问和使用 session 对象,从而存储用户的关键信息:

app.post('/api/login', (req, res) => {// 判断用户提交的登录信息是否正确if (req.body.username !== 'admin' || req.body.password !== '000000') {return res.send({ status: 1, msg: '登录失败' })}// 将登录成功后的用户信息,保存到 session 中// 注意:只有 express-session 中间件配置成功后, req 中才会有 session 这个属性req.session.user = req.body  // 存储用户信息req.session.isLogin = true   // 存储登录状态res.send({ status: 0, msg: '登录成功' })
})
从 session 中取数据

可以直接从 req.session 对象上获取之前存储的数据

// 获取用户姓名的接口
app.get('/api/username', (req, res) => {// 从 session 中获取用户的名称,响应给客户端if(!req.session.isLogin) {return res.send({ status: 1, msg: 'fail!' })}res.send({status: 0,msg: 'success!',username: req.session.user.username})
})
清空 session

调用 req.session.destory() 函数,即可清空服务器保存的 session 信息

// 退出登录的接口
app.post('/api/logout', (req, res) => {// 清空 session 信息req.session.destroy() res.send({status: 0,msg: '退出登录成功'})
})

3.4、JWT 认证机制

a. Session 认证的局限性

Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口时,需要做很多额外的配置,才能实现跨域 Session 认证。

注意:

  • 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制
  • 当前端需要跨域请求后端接口的时候,推荐使用 JWT 认证机制

b. JWT的概念

JWT(JSON Web Token)是目前最流行跨域认证解决方案

JWT 的工作原理:用户的信息通过 Token 字符串的形式,保存在客户端浏览器中,服务器通过还原 Token 字符串的形式来认证用户身份

JWT 的组成部分:JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。三者之间使用英文的 “.” 分隔,格式如下:

Header.Payload.Signature

各部分代表的含义:

  • Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
  • Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性

JWT 的使用方式:客户端收到服务器返回的 JWT 之后,通常会将它存储在 localStoragesessionStorage 中。此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下:

Authorization: Bearer <token>

c. 在 Express 中 使用 JWT

安装与配置 JWT 相关的包

两个 JWT 的相关包:

  • jsonwebtoken 用于生成 JWT 字符串
  • express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
定义 secret 密钥

为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,需要专门定义一个用于加密和解密的 secret密钥

  1. 生成 JWT 字符串时,使用 secret 密钥对用户信息进行加密,最终得到加密好的 JWT 字符串
  2. 当把 JWT 字符串解析还原成 JSON 对象时,需要使用 secret 密钥进行解密
const secretKey = 'sakura_tt'
登录成功后生成 JWT 字符串

调用 jsonwebtoken 包提供的 sign() 方法,将用户信息加密成 JWT 字符串

app.post('/api/login', (req, res) => {// 将 req.body 请求体中的数据,转存为 userinfo 常量const userinfo = req.body// 登录失败if (userinfo.username !== 'admin' || userinfo.password !== '000000') {return res.send({status: 400,message: '登录失败!'})}// 登录成功// 3. 登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串,并通过 token 属性发送给客户端// 参数1:用户的信息对象// 参数2:加密的密钥// 参数3:配置对象,可以配置当前 token 的有效期const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })res.send({status: 200,message: '登录成功',token: tokenStr,  //要发送给客户端的 token 字符串})
})
将 JWT 字符串还原为 JSON 对象

客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证。此时,服务器可以通过 express-jwt 这个中间件,自动将客户端发送过来的 Token 解析还原成 JSON 对象:

// expressJWT({ secret: secretKey }) 就是使用密钥 secretKey 来对 JWT 字符串进行解密
// .unless({ path: [/^\/api\//] }) 用来指定哪些接口不需要访问权限app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
使用 req.user 获取用户信息

当express-jwt 这个中间件配置成功之后,即可在那些有权限的接口中,使用 req.user 对象来访问从 JWT 字符串中解析出来的用户信息。

app.get('/admin/getinfo', (req, res) => {res.send({status: 200,message: '获取用户信息成功!',data: req.user  //要发送给客户端的用户信息})
})

向服务器传入 token 时,需要在 token 字符串前加上 'Bearer '

捕获解析 JWT 失败后产生的错误

使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行,我们可以通过 Express 的错误中间件,捕获这个错误并进行相关的处理

// 使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {// 错误是由 token 解析失败导致的if(err.name === 'UnauthorizedError') {return res.send({status: 401,message: '无效的token'})}res.send({status: 500, message: '未知的错误'})
})

extra. 连接数据库报错

报错提示:

Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol request
ed by server; consider upgrading MySQL client

报错原因:

mysql8.0以上加密方式,Node还不支持。

解决方案:

打开 MySQL 命令行终端输入命令(123456 是我本地登录 mysql 的密码)

alter user 'root'@'localhost' identified with mysql_native_password by '1234';
flush privileges;

然后就解决了,如果不行就下载个低版本的 mysql。

flush privileges 命令本质上的作用是将当前 user 和 privilige 表中的用户信息/权限设置从 mysql 库(MySQL 数据库的内置库)中提取到内存里。经常用在改密码和授权超用户。

Node.js 学习之数据库与身份认证相关推荐

  1. 【前端——Node.js】:Express、数据库与身份认证

    一.Express 1.express路由 (1)路由 路由就是映射关系.在Express中,路由是指客户端的请求与服务器处理函数之间的映射关系 (2)路由匹配的过程 (3)模块化路由 为了方便对路由 ...

  2. node.js学习总结:node.js的内置模块,模块化,npm与包 express,前后端身份认证 JWT认证机制

    node.js学习总结 什么是node.js node.js的内置模块 fs系统模块 path路径模块 http模块 模块化 npm与包 express express路由 express+mysql ...

  3. 数据库与身份认证(数据库的基本概念,安装并配置 MySQL,MySQL 的基本使用,在项目中操作 MySQL,前后端的身份认证)

    theme: channing-cyan 数据库与身份认证 1. 数据库的基本概念 1.1 什么是数据库 数据库(database)是用来组织.存储和管理数据的仓库. 当今世界是一个充满着数据的互联网 ...

  4. 数据库与身份认证——黑马课程笔记

    数据库与身份认证 1.数据库的基本概念 2.安装并配置MySQL 3.MySQL的基本使用 3.1使用MySQL workbench管理数据库 1.连接数据库 2.了解主界面的组成部分 3.创建数据库 ...

  5. node.js学习总结

    NodeJS介绍 1.概述: Node.js是基于Chrome JavaScript运行时建立的一个平台,实际上它是对Google Chrome V8引擎 进行了封装,它主要用于创建快速的.可扩展的网 ...

  6. node.js学习笔记14—微型社交网站

    node.js学习笔记14-微型社交网站 1.功能分析 微博是以用户为中心,因此需要有注册和登录功能. 微博最核心的功能是信息的发表,这个功能包括许多方面,包括:数据库访问,前端显示等. 一个完整的微 ...

  7. Node.js 学习 ——nodemon 运行报错解决

    Node.js 学习 --nodemon 运行报错解决 报错记录 nodemon : 无法加载文件 C:\Users\Administrator.DESKTOP-0RUBNO7\AppDat on.p ...

  8. 千锋Node.js学习笔记

    千锋Node.js学习笔记 文章目录 千锋Node.js学习笔记 写在前面 1. 认识Node.js 2. NVM 3. NPM 4. NRM 5. NPX 6. 模块/包与CommonJS 7. 常 ...

  9. 唤醒手腕 - 前端服务器端开发 Node.Js 学习笔记(学习中,更新中)

    唤醒手腕 - Node.Js 学习笔记 唤醒手腕个人的学习记录,时间在2021年12月13日 ~ 2021年12月14日,学习方式看官方文档和B站视频,如有错误或者代码问题的地方,欢迎C站大佬能够帮忙 ...

最新文章

  1. 谷歌 notification 测试 页面
  2. 哪种脚本语言最适合你!
  3. CSS 同级元素浮动分析小结
  4. java不同环境_Spring Boot系列 – 5. 不同的环境使用不同的配置
  5. 【NC51 合并k个已排序的链表】K路归并
  6. JS你可能还不知道的一些知识点(一)
  7. mysql8.0windows,Windows下mysql 8.0.12 安装详细教程
  8. python server client_python 实现简单client与server | 学步园
  9. Java8新特性_接口中的默认方法
  10. python字符串的10个常用方法总结
  11. C-Free 5.0下载和安装教程
  12. [转载] python实现堆排序用类的方法_python实现堆排序的实例讲解
  13. java8foreach_Java forEach – Java 8 forEach
  14. 转载:浅谈程序员的数学修养
  15. PHP Yar - 学习/实践
  16. 计组--CISC和RISC特点和区别
  17. 【微机原理】8088/8086CPU引脚
  18. C++结构体嵌套结构体
  19. 火山安卓开发支付宝自动转账功能
  20. 清理Virtualbox虚拟机VDI镜像文件的空间大小

热门文章

  1. Linux学习134 Unit 5
  2. 骚操作之Python微信远程控制摄像头!然后嘿嘿嘿!
  3. java 发起HTTPS请求-SSL客户端
  4. 基于AChartEngine绘制股票走势图----分时图一(走势柱状)
  5. 多项式插值中的一些定理证明
  6. PyGame每日一练——五子棋小游戏
  7. Java内存回收程序可否在指定的时间释放内存对象
  8. 我的世界java边境之地_我的世界边境之地怎么去?minecraft边境之地
  9. 笔杆网试用---功能篇(一)
  10. 采用Fuel Gauge可能出现的几种电量现象及解释