node,MySQL,Oracle连接数据库,增删改查,mockjs,中文乱码,end会调用2次,write
本期专栏耗时的个人尝试猜想后实践确认猜想后的发现:node和mysql数据库连接时,用end显示数据到网页会让查询部分等等调用2次,用write就没有这种情况。


目录:
例子1:使用mockjs生成随机数据后,用axios接收处理,拼接成城市下拉框
http://mockjs.com/
https://github.com/nuysoft/Mock/wiki
mock
npm install mockjs --save-dev
mock是随机生成数据,模拟数据库的数据等等的东西。属性名和生成规则用| 分隔。mock基本的语法说明如下。
axios获取mock生成的数据中也用了解构语法与map
Mk.js
mock.js
index.js
运行
npm start
作业
例子2:1、使用Mock数据创建一个学历下拉框
例子3:2、使用Mock数据创建一个数据表格(使用antd实现)
shuffle
@integer(1,120)
Dg_axios_mock.js
Dg_axios_mock_data.js
index.js
例子4:用Oracle连接数据库,Vscode中使用nodejs查看一张表的所有数据,打印在控制台,并且增加一条数据(增删改其实基本一样的代码,就是使用的SQL语句不一样)

关于 Userinfo表的SQL语句
ora.js
例子5:用MySQL连接数据库,Vscode中使用nodejs查看一张表的所有数据,打印在控制台,并且增加一条数据(增删改其实基本一样的代码,就是使用的SQL语句不一样)
增加数据如果单个写时,只执行一次增加的部分的代码,
如果和查询数据写在一起,就会运行一次服务时,会执行2次增加部分的代码
mysql.js
MySQL中表相关的SQL语句和执行方法
严格模式下生命周期会被调用两次。假设没有更新state或者props,render函数又执行了两次,很有可能是哪个地方用了React.StrictMode
Node.js 连接 MySQL,增删改查
//处理中文乱码 START
resp.writeHead(200,{'Context-Type':'text/html'});
resp.write('<head><meta charset="utf-8"/></head>');
//处理中文乱码 END

例子1:使用mockjs生成随机数据后,用axios接收处理,拼接成城市下拉框
一、什么是Mock:可以自己构造后台数据,不需要写复杂的后台代码了
二、安装:npm install mockjs --save-dev
三、创建mock.js文件
四、mock数据生成语法

http://mockjs.com/

https://github.com/nuysoft/Mock/wiki

mock

npm install mockjs --save-dev

mock是随机生成数据,模拟数据库的数据等等的东西。属性名和生成规则用| 分隔。mock基本的语法说明如下。


axios获取mock生成的数据中也用了解构语法与map

Mk.js
//src/comp/Mk.js
import React from 'react';
import axios from 'axios';
//引入mock数据模块
import '../mock';
class Mk extends React.Component{
constructor(){
super();
//设置下拉框的初始状态
this.state = {
citys: []
};
}
componentDidMount(){
//请求数据
axios.get('/demo')
.then(res => {
const {citys} = res.data.data;
let opts = citys.map(c => (<option id={c.id} key={c.id}>
{c.address}
</option>));
this.setState({
citys: opts
});
});
}
render(){
return <h1><select>{this.state.citys}</select></h1>;
}
}
export default Mk;

mock.js
//src/mock.js
import Mock from 'mockjs';
//使用mockjs模拟数据
Mock.mock('/demo', {
"ret":0,
"data":
{
"citys|5-10": [{ /*5-10表示随机生成含5到10个数组元素的一个数组,
这里的数组元素一般是含各种自定义属性的object,stus为这个数组的名字*/
'name': '@cname', // @cname表示name这一属性会自动生成的内容是中文名称(@cname应该是固定的写法)
'id|+1': 1, // 属性值自动加 1,初始值为1
'score|0-100.1': 1, /* 整数部分在10到1000之间,保留1位小数,
1只是用来确定类型(确定为数字类型),1写成0或其他数字也可以。
XX- XXX.1-3应该是表示整数部分保留xx到XXX之间,
小数部分保留1到2位小数*/
'birth': '@date("yyyy-MM-dd")', // 日期
'address': '@city(false)', // 中国城市,true表示显示省份,如果写false就是不显示省份,只显示城市
}]
}
});




index.js

/******************/
//目录——>
// reactjs必备源码部分
// 个人添加的导入antd.css的代码部分
// 个人添加的导入组件的代码部分
// 个人科普——>
// 个人注释:Dg是表格组件
// FormZuJian.js是表单组件
//个人总结的导入代码模板:import 自定义文件别名(一般我习惯和导入文件名统一) from 文件路径;
/******************/
//reactjs必备源码部分 START
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
//reactjs必备源码部分 END
//(Router是要自己写的路由的部分,暂时我不写和不用导入) START
import Router from './route';
//(Router是要自己写的路由的部分,暂时我不写和不用导入) END
//个人添加的导入antd.css的代码部分 START
import 'antd/dist/antd.css';
//个人添加的导入antd.css的代码部分 END
//个人添加的导入组件的代码部分 START
import MyTable from './comp/MyTable';
import Dg from './comp/Dg';
import FormZuJian from './comp/FormZuJian';
import Cascade from './comp/Cascade';
import Dg_axios from './comp/Dg_axios';
import ChongMingYanZheng from './comp/ChongMingYanZheng';
import Mk from './comp/Mk';
//个人添加的导入组件的代码部分 END
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<Mk/>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
/*可选择的组件
<Dg /> :表格组件
<FormZuJian /> :表单组件
<Cascade /> :联级选择(下拉框联动)组件
<Dg_axios/>:是用了axios技术接收数据的用了antd的表格组件
<Router />:路由组件(跳转功能)
<ChongMingYanZheng/>:是更简单的Ajax的重名验证例子组件
<Mk/>:使用了mockjs技术的城市下拉框组件
*/

运行
npm start


作业:
例子2:1、使用Mock数据创建一个学历下拉框
Education_select_mock.js

import React from 'react';
import axios from 'axios';
//导入Education_select_data_mock.js文件,获取其中的数据:
import '../Education_select_mock_data';
class Education_select_mock extends React.Component{
constructor(){
super();
//一般都是通过 声明constructor中的this.state来设置初始状态:
this.state = {
arr: []
};
}
componentDidMount(){
/*使用axios请求数据,获取/sshc这个自定义的范围路径的数据后,
进行处理,显示数据:*/
axios.get('/sshc')
.then(res => {
const {arr} = res.data.data;
let NewData = arr.map(o => (<option id={o.id} key={o.id}>
{o.name} {o.Education}
</option>));
/*个人理解,解释,解析:一般都是通过this.setState来设置刷新状态,
让this.state声明的属性值被动态赋值后被获取: */
this.setState({
arr: NewData
});
});
}
render(){
return <h1><select>{this.state.arr}</select></h1>;
}
}
export default Education_select_mock;

Education_select_mock_data.js

import Mock from 'mockjs';
Mock.mock('/sshc', {
"ret":0,
"data":
{
"arr|2-3": [{
'id|+1': 1,
'name':'@cname',
'Education|1': ['小学的学历','初中的学历','高中的学历','大学的学历'],
}]
}
});

index.js

/******************/
//目录——>
// reactjs必备源码部分
// 个人添加的导入antd.css的代码部分
// 个人添加的导入组件的代码部分
// 个人科普——>
// 个人注释:Dg是表格组件
// FormZuJian.js是表单组件
//个人总结的导入代码模板:import 自定义文件别名(一般我习惯和导入文件名统一) from 文件路径;
/******************/
//reactjs必备源码部分 START
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
//reactjs必备源码部分 END
//(Router是要自己写的路由的部分,暂时我不写和不用导入) START
import Router from './route';
//(Router是要自己写的路由的部分,暂时我不写和不用导入) END
//个人添加的导入antd.css的代码部分 START
import 'antd/dist/antd.css';
//个人添加的导入antd.css的代码部分 END
//个人添加的导入组件的代码部分 START
import MyTable from './comp/MyTable';
import Dg from './comp/Dg';
import FormZuJian from './comp/FormZuJian';
import Cascade from './comp/Cascade';
import Dg_axios from './comp/Dg_axios';
import ChongMingYanZheng from './comp/ChongMingYanZheng';
import Mk from './comp/Mk';
import Education_select_mock from './comp/Education_select_mock';
//个人添加的导入组件的代码部分 END
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<Education_select_mock/>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
/*可选择的组件
<Dg /> :表格组件
<FormZuJian /> :表单组件
<Cascade /> :联级选择(下拉框联动)组件
<Dg_axios/>:是用了axios技术接收数据的用了antd的表格组件
<Router />:路由组件(跳转功能)
<ChongMingYanZheng/>:是更简单的Ajax的重名验证例子组件
<Mk/>:使用了mockjs技术的城市下拉框组件
<Education_select_mock/>:使用了mockjs技术的学历下拉框组件
*/


例子3:2、使用Mock数据创建一个数据表格(使用antd实现)

shuffle

/*shuffle:洗牌的意思。
"@shuffle(['德艺双馨', '自律','失败者','酷', '非常受欢迎'],2,3)"表示从数组中随机选取
2到3个对象*/



Dg_axios_mock.js

//src/comp/Dg.js
import { Table, Tag, Space } from 'antd';
import React from 'react';
//npm install axios --save
import axios from 'axios';
//解决页面跳转时,history属性为空的问题
// import { withRouter } from "react-router-dom";
//导入Dg_axios_mock_data.js文件,获取其中的数据(不可以少):
import '../Dg_axios_mock_data';
//自定义组件
class Dg_axios_mock extends React.Component {
constructor(){
super();
this.state = {
myDate: []
}
}
componentDidMount(){
// //请求后台数据
// axios.get('http://localhost:8777')
// .then(res => {
// //res就是后台请求的数据
// //console.log(res.data);
// this.setState({
// myDate: res.data
// });
// });
axios.get('/url')
.then(res => {
// const {NewData} = res.data.data.arr;
// let NewData = myDate;
this.setState({
myDate: res.data.data.arr
});
console.log(res.data.data.arr);
});
}
// detail(name){
// //name触发点击事件和传的name的值(一般数据库连接时,详情界面知道id就可以传所有信息等等)
// //console.log(n);
// //跳转到Detail页面
// this.props.history.push('/dl/' + name);
// }
render() {
//表头
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
// render: name => <a onClick={this.detail.bind(this,name)}>{name}</a>,
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
// render: text => <a onClick={this.detail.bind(this,text)}>{text}</a>,
},
{
title: '地址',
dataIndex: 'address',
key: 'address',
},
{
title: '标签',
key: 'tags',
dataIndex: 'tags',
render: tags => (
<>
{tags.map(tag => {
let color = tag.length > 3 ? 'geekblue' : 'green';
if (tag === 'loser') {
color = 'volcano';
}
return (
<Tag color={color} key={tag}>
{tag.toUpperCase()}
</Tag>
);
})}
</>
),
},
{
title: '操作',
key: 'action',
render: (text, record) => (
<Space size="middle">
<a>邀请 {record.name}</a>
<a>删除</a>
</Space>
),
},
];
return <Table columns={columns} dataSource={this.state.myDate} />
}
}
export default Dg_axios_mock;
// export default withRouter(Dg_axios_mock);

Dg_axios_mock_data.js

import Mock from 'mockjs';
Mock.mock('/url', {
"ret":0,
"data":
{
"arr|23-33": [{
'key|+1': 1,
'name':'@cname',
'age':'@integer(1,120)',
'address':'@city(true)',
'tags|':"@shuffle(['德艺双馨', '自律','失败者','酷', '非常受欢迎'],2,3)",
/*shuffle:洗牌的意思。
"@shuffle(['德艺双馨', '自律','失败者','酷', '非常受欢迎'],2,3)"表示从数组中随机选取
2到3个对象*/
}]
}
});
/* const data = [
{
key: '1',
name: '刘德华',
age: 32,
address: '中国香港',
tags: ['德艺双馨', '自律'],
},
{
key: '2',
name: '汪方',
age: 42,
address: '武汉',
tags: ['失败者'],
},
{
key: '3',
name: '吴彦祖',
age: 32,
address: '阿拉斯加',
tags: ['酷', '非常受欢迎'],
},
]; */

index.js

/******************/
//目录——>
// reactjs必备源码部分
// 个人添加的导入antd.css的代码部分
// 个人添加的导入组件的代码部分
// 个人科普——>
// 个人注释:Dg是表格组件
// FormZuJian.js是表单组件
//个人总结的导入代码模板:import 自定义文件别名(一般我习惯和导入文件名统一) from 文件路径;
/******************/
//reactjs必备源码部分 START
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
//reactjs必备源码部分 END
//(Router是要自己写的路由的部分,暂时我不写和不用导入) START
import Router from './route';
//(Router是要自己写的路由的部分,暂时我不写和不用导入) END
//个人添加的导入antd.css的代码部分 START
import 'antd/dist/antd.css';
//个人添加的导入antd.css的代码部分 END
//个人添加的导入组件的代码部分 START
import MyTable from './comp/MyTable';
import Dg from './comp/Dg';
import FormZuJian from './comp/FormZuJian';
import Cascade from './comp/Cascade';
import Dg_axios from './comp/Dg_axios';
import ChongMingYanZheng from './comp/ChongMingYanZheng';
import Mk from './comp/Mk';
import Education_select_mock from './comp/Education_select_mock';
import Dg_axios_mock from './comp/Dg_axios_mock';
//个人添加的导入组件的代码部分 END
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<Dg_axios_mock/>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
/*可选择的组件
<Dg /> :表格组件
<FormZuJian /> :表单组件
<Cascade /> :联级选择(下拉框联动)组件
<Dg_axios/>:是用了axios技术接收数据的用了antd的表格组件
<Router />:路由组件(跳转功能)
<ChongMingYanZheng/>:是更简单的Ajax的重名验证例子组件
<Mk/>:使用了mockjs技术的城市下拉框组件
<Education_select_mock/>:使用了mockjs技术的学历下拉框组件
<Dg_axios_mock/>:使用了antd和mockjs技术的表格组件
*/




例子4:用Oracle连接数据库,Vscode中使用nodejs查看一张表的所有数据,打印在控制台,并且增加一条数据(增删改其实基本一样的代码,就是使用的SQL语句不一样)

开启服务

登录Oracle

关于 Userinfo表的SQL语句
--drop table Userinfo
create table Userinfo(
id number primary key,
act varchar2(30) not null,
pwd varchar2(30) not null,
birth date
);
--drop sequence seq_Userinfo
create sequence seq_Userinfo
start with 1 --起始值是1
increment by 1 --增长的值
maxvalue 999999999 --序列号的最大值
minvalue 1 --序列号的最小值
nocycle --是否循环
cache 10; --预存
insert into Userinfo values(seq_Userinfo.nextval,'黑黑','pwd1',to_date('2020-06-06','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'红红','pwd2',to_date('2020-06-07','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'蓝蓝','pwd3',to_date('2020-06-08','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'666','pwd4',to_date('2020-06-06','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'999','pwd5',to_date('2020-06-10','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'888','pwd6',to_date('2020-06-11','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'诗书画唱','pwd4',to_date('2020-06-06','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'三连','pwd5',to_date('2020-06-10','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'关注','pwd6',to_date('2020-06-11','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'诗书画唱1','pwd4',to_date('2020-06-06','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'诗书画唱2','pwd4',to_date('2020-06-06','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'诗书画唱3','pwd4',to_date('2020-06-06','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'诗书画唱4','pwd4',to_date('2020-06-06','yyyy-mm-dd'));
insert into Userinfo values(seq_Userinfo.nextval,'诗书画唱5','pwd4',to_date('2020-06-06','yyyy-mm-dd'));
--select * from Userinfo
select * from userinfo WHERE act like '%诗%'
--rows=5,page=2
--end = rows * page=10
--start = (page - 1) * rows + 1=6
--start表示>=号后面的值,end表示<=号后面的值
select * from
(select p1.*,rownum r1 from Userinfo p1
where rownum <= 10)
where r1 >=6 and act like '%诗%'
【这个不是条件查询后的分页查询,而是分页查询后的条件查询】
select * from (select t.*,rownum rn from
(select * from userinfo WHERE act like '%诗%' ) t
where rownum <= 10) where rn >= 6
【这个是条件查询后的分页查询,是常用的,如果组合查询后的数据很少,比如只有1条,
就难看出效果】

ora.js

// //引入oracledb模块
// const { connectionClass } = require('oracledb');
// let oracledb=require('oracledb');
// //设置oracle连接参数
// let config={
// user:'X',//数据库链接账号
// password:'sshcPwd',//密码
// connectString:'127.0.0.1:1521/orcl'//数据库的IP地址,端口号和数据库名
// }
// //获取数据库连接对象conn
// oracledb.getConnection(config,function(err,conn){
// if(err){//如果连接数据库出现错误
// console.log(err.message);
// return;
// }
// console.log('数据库连接成功'+conn);
// // 查询
// connection.execute("select * from userinfo",function(err,result){
// if(err){
// console.error(err.message);
// return;
// }
// //打印返回的表结构
// console.log(JSON.stringify(result.metaData));
// //打印返回的行数据
// console.log(JSON.stringify(result.rows));
// doRelease(connection);//自定义方法
// });
// });
let http = require('http');
let url = require('url');
//npm install oracledb -g
let oracledb = require('oracledb');
http.createServer(function(req,res){
//参数解析
let ps = url.parse(req.url,true).query;
//数据库连接
const conf = {
user: 'X',//用户名
password: 'sshcPwd',//密码
connectString: '127.0.0.1:1521/orcl'//连接字符串
};
oracledb.getConnection(conf,function(err,conn){
if(err) {
console.log(err.message);
return;
}
// 查询所有(可以封装成一个方法后调用) START:
conn.execute('select * from userinfo',function(err,rs){
if(err) {
console.log(err.message);
return;
}
//打印所有的数据
let data = rs.rows;
// for(let record of data) {
// //打印每行记录
// //console.log(record[1]);
// let [id,act,pwd,birth] = record;
// let o = {id,act,pwd,birth};
// console.log(o);
// }
let newData = data.map(ay => {
let [id,act,pwd,birth] = ay;
return {id,act,pwd,birth};
});
// console.log("查询所有的数据:"+newData);这样写可能打印不出数据。
console.log("查询所有的数据:");
console.log(newData);
});
// 查询所有 END
//新增 START:
let sql = 'insert into userinfo values(seq_userinfo.nextval,:1,:2,:3)';
conn.execute(sql,['诗书画唱好帅!','帅到想给他三连!',new Date()],{autoCommit:true},function(err,rs){
if(err) {
console.log(err.message);
return;
}
let count = rs.rowsAffected;
console.log('插入了' + count + '条数据');
});
//新增 END
});
}).listen(8081);
console.log('服务器启动成功,网页上输入http://localhost:8081的访问路径就可以控制台打印数据等等')

输入node ora 后按回车键

输入http://localhost:8081访问网页



例子5:用MySQL连接数据库,Vscode中使用nodejs查看一张表的所有数据,打印在控制台,并且增加一条数据(增删改其实基本一样的代码,就是使用的SQL语句不一样)
npm install mysql -g

开启MySQL的服务

输入注册码NAVJ-W56S-3YUU-MVHV登录MySQL

增加数据如果单个写时,只执行一次增加的部分的代码,
如果和查询数据写在一起,就会运行一次服务时,会执行2次增加部分的代码
mysql.js
let http = require('http');
let url = require('url');
let mysql = require('mysql');
//npm install mysql -g
//创建一个简单的后台服务程序
http.createServer(function(req,resp){
console.log('注释掉其他内容的,看createServer调用几次的打印语句');
/*测试过,createServer是调用一次的,
当查询mysql中一个表的所有数据的时候,其实会执行2次。不用管,
发到网页时仍然是执行一次的数据,增加数据如果单个写时,只执行一次增加的部分的代码,
如果和查询数据写在一起,就会运行一次服务时,会执行2次增加部分的代码*/
//从url路径中解析传递过来的参数
//req.url就是在浏览器地址栏输入的路径
let ps = url.parse(req.url,true).query;
// let op = ps.op;
//个人理解:op是地址栏传过来的参数
// console.log(op);
//获取数据库的连接对象
const conn = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
port: 3306,
database: 'firstjsp'
});
//console.log(conn);
//连接数据库
conn.connect();
//查询所有:
let sql = 'select * from News';
// let bid = ps.bid;
/*个人理解[bid]是设置sql语句中的?的占位符的值的内容,
没有的话就是设置为[]*/
conn.query(sql,[],function(err,result){
if(err) {
console.log(err.message);
}
/* resp.end是把数据显示在网页上的方法 */
//处理中文乱码 START
resp.writeHead(200,{'Context-Type':'text/html'});
resp.write('<head><meta charset="utf-8"/></head>');
//处理中文乱码 END
console.log(JSON.stringify(result));
/* resp.end(JSON.stringify(result));
使用这段代码时render函数会执行两次 ,所以我暂时不用 。 */
});
// conn.end();
//增加数据(只执行这个部分不会被调用2次,和上面的查询语句结合就回执行2次) START
let sql2 = 'insert into News(content,pubtime ) values (?,?);';
conn.query(sql2,["666",'2021-1-1'],function(err,rs){
// if(err) {
// console.log(err.message);
// }
// resp.end(JSON.stringify(rs));
// if(err) {
// console.log(err.message);
// return;
// }
if(err){
console.log('error');
return;
}
console.log('-----------------新增成功----------------');
let count = rs.affectedRows;
console.log('插入了' + count + '条数据');
});
//增加数据(只执行这个部分不会被调用2次,和上面的查询语句结合就回执行2次) END
// 增加数据
// var addsql = 'insert into News(content,pubtime) values (?,?);';
// var addsqlparams = ["诗书画唱创作出了很好的作品!",'2021-1-1'];
// conn.query(addsql,addsqlparams,function (err,result) {
// if(err){
// console.log('error');
// return;
// }
// console.log('-----------------新增成功----------------');
// console.log(result);
// console.log('-----------------结束----------------');
// })
/*if(op === 'getLatest') {//获取最新的期刊数据
let sql = 'select * from classic order by `index` desc limit 0,1';
conn.query(sql,[],function(err,result){
if(err) {
console.log(err.message);
} else {
//将查询到的数据传递到前台去
resp.end(JSON.stringify(result[0]));
}
});
} else if(op === 'like') {//更新点赞状态
let nums = ps.nums;//点赞的次数
let status = ps.status;//点赞的状态
nums = status === 'like' ? parseInt(nums) + 1 : (nums - 1);
status = status === 'like' ? 1 : 0;
let index = ps.index;//点赞的期刊号
let sql = 'update classic set fav_nums = ?,like_status = ? '
+ 'where `index` = ?';
conn.query(sql,[nums,status,index],function(err,result){
if(err) {
console.log(err.message);
}
resp.end();
});
} else if(op === 'getClassic'){
let index = ps.index;
let status = ps.status;
if(status === 'next') {
index ++;
} else {
index --;
}
let sql = 'select * from classic where `index` = ?'
conn.query(sql,[index],function(err,result){
if(err) {
console.log(err.message);
}
resp.end(JSON.stringify(result[0]));
});
} else if(op === 'getHotList') {
let sql = 'select t1.*,t2.fav_nums,t2.like_status from book t1,`like` t2 where t1.id = t2.bookid';
conn.query(sql,[],function(err,result){
if(err) {
console.log(err.message);
}
resp.end(JSON.stringify(result));
});
} else if(op === 'getBookDetail') {
let sql = 'select * from book where id = ?';
let bid = ps.bid;
conn.query(sql,[bid],function(err,result){
if(err) {
console.log(err.message);
}
resp.end(JSON.stringify(result));
});
} else if(op === 'getBookComments') {
let sql = 'select * from comments where bookid = ?';
let bid = ps.bid;
conn.query(sql,[bid],function(err,result){
if(err) {
console.log(err.message);
}
resp.end(JSON.stringify(result));
});
} else if(op === 'getBookLike') {
let sql = 'select * from `like` where bookid = ?';
let bid = ps.bid;
conn.query(sql,[bid],function(err,result){
if(err) {
console.log(err.message);
}
resp.end(JSON.stringify(result));
});
} else if(op === 'query') {
let q = ps.q;
let start = parseInt(ps.start);
let pageSize = parseInt(ps.pageSize);
let sql = 'select t1.*,t2.fav_nums,t2.like_status from book t1,`like` t2 where t1.id = t2.bookid and author like ? or title like ? limit ?,?';
q = '%' + q + '%';
conn.query(sql,[q,q,start,pageSize],function(err,result){
if(err) {
console.log(err.message);
}
//查询总记录条数,将记录条数放到result中去
resp.end(JSON.stringify(result));
});
} else if(op === 'getHot') {
resp.end('{"hot":["Python","哈利·波特","村上春树","东野圭吾","白夜行","韩寒","金庸","王小波"]}');
} else if(op === 'likeBook') {
const bid = ps.bid;
const status = ps.status;
let sql = 'update `like` set fav_nums = fav_nums '
+ (status === 'like' ? '+' : '-') +' 1,like_status = '
+ (status === 'like' ? 1 : 0) +' where bookid = ?';
conn.query(sql,[bid],function(err,result){
if(err) {
console.log(err.message);
}
resp.end(JSON.stringify(result));
});
} else if(op === 'postComment') {
let bid = ps.bid;
let comment = ps.comment;
//判断表中是否有这条评论
let sql = 'select count(*) ct from comments where bookid = ? and content = ?';
conn.query(sql,[bid,comment],function(err,result){
if(err) {
console.log(err.message);
}
let count = result[0].ct
if(count > 0) {//在数据库中已经有这条评论了
sql = 'update comments set nums = nums + 1 where bookid = ? and content = ?';
conn.query(sql,[bid,comment],function(er,rs){
if(er) {
console.log(er.message);
}
resp.end(JSON.stringify(rs));
});
} else {//在数据库中没有这条评论
sql = 'insert into comments (bookid,content,nums) values(?,?,?)';
conn.query(sql,[bid,comment,1],function(er,rs){
if(er) {
console.log(er.message);
}
resp.end(JSON.stringify(rs));
});
}
});
}*/
}).listen(8889);
console.log('后台服务创建成功,可以通过http://localhost:8889/的方式访问');

MySQL中表相关的SQL语句和执行方法
鼠标右键,点击“运行所选”

create table News(
ID int primary key auto_increment,
content varchar(100) not null,
pubtime varchar(100) not null
);
insert into News(
content ,
pubtime
) values ("世界卫生组织总干事特德罗斯阿德哈诺姆格布雷耶苏斯在日内瓦宣布新型冠状病毒感染的肺炎被命名为-19"
,'2020-2-11');
insert into News(
content ,
pubtime
) values ("国家卫健委发布《关于修改新型冠状病毒英文命名的通知》决定将“新型冠状病毒”英文名称修改为-19与世界卫生组织命名一致且中文名称不变",'2020-2-21');
insert into News(
content ,
pubtime
) values ("《中华人民共和国民法典》《中华人民共和国档案法》《中华人民共和国退役军人保障法》实施",'2021-1-1');
insert into News(
content ,
pubtime
) values ("自2021年起每年1月10日为中国人民警察节",'2021-1-10');





严格模式下生命周期会被调用两次。假设没有更新state或者props,render函数又执行了两次,很有可能是哪个地方用了React.StrictMode。
当然,目前我认为可能与render无关,我认为和 resp.end(JSON.stringify(result)); 有关。因为可能没有用到render(但我老师说是render的原因,严格模式下render函数会执行,可能渲染2次),但是这个情况,目前其实没有太多影响(增加部分和查询部分分开写就可以不调用2次了。可能可行的方法:设置一个变量,比如i或state状态机等等,第二次调用时,if判断出是第2次,就停止第2次调用查询等部分。以上是个人理解,可能有误,请斧正,我有时有空等时会更改等等。)

这样写后执行2次查询和增加数据的代码部分

这样写,查询和增加部分就都只执行1次

注释掉查询部分,增加部分就只执行1次

本期专栏耗时的个人尝试猜想后实践确认猜想后的发现:node和mysql数据库连接时,用end显示数据到网页会让查询部分等等调用2次,用write就没有这种情况。
经过多次尝试,猜想,实践确认,终于解决更多问题等等了!


Node.js 连接 MySQL,增删改查
菜鸟教程
https://www.runoob.com/nodejs/nodejs-mysql.html


