ZhangYang's Blog

【实战】js+node.js实现注册登录

登录与注册

UI

dribbble

流程

1.需求分析

  • 登录(邮箱、密码)
  • 注册(邮箱、密码、重复密码)
  • 重置密码(邮箱)
  • 已登录

2.寻找后端代码

LeanCloud

或者用Node.js写一个后端,然后买一个服务器或VPS来运行Node.js,确保接口可用

3.写代码-测试-部署-改进

注册表单

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<section>
<h1>登录</h1>
<form name="login" action="/login" method="post">
<div>
<label>邮箱
<input type="text" name="email">
</label>
</div>
<div>
<label>密码
<input type="password" name="password">
</label>
</div>
<input type="submit" value="登录">
</form>
</section>
<section>
<h1>注册</h1>
<form name="signUp" action="/signUp" method="post">
<div>
<label>邮箱
<input type="text" name="email">
</label>
<span name="email_error" class="error"></span>
</div>
<div>
<label>密码
<input type="password" name="password">
</label>
<span name="password_error" class="error"></span>
</div>
<div>
<label>确认密码
<input type="password" name="password_confirmation">
</label>
<span name="password_confirmation_error" class="error"></span>
</div>
<input type="submit" value="注册">
</form>
</section>
<section>
<h1>重置密码</h1>
<form name="resetPassword" action="/resetPassword" method="post">
<div>
<label>重置密码
<input type="text" name="email">
</label>
</div>
<input type="submit" value="请求重置密码">
</form>
</section>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="main.js"></script>
</body>
</html>
// main.js
let $signUpFrom = $('form[name=signUp]')
$signUpFrom.on('submit',(e)=>{
e.preventDefault()
let string = $signUpFrom.serialize()
let email = $signUpFrom.find('[name=email]').val()
let password = $signUpFrom.find('[name=password]').val()
let password_confirmation = $signUpFrom.find('[name=password_confirmation]').val()
let errors = {}
// check email
if(email.indexOf('@') <= 0){
errors.email = '邮箱不合法'
}
if(password.length < 6){
errors.password = '密码太短'
}
if(password_confirmation !== password){
errors.password_confirmation = '两次输入密码不匹配'
}
$signUpFrom.find('span[name$=_error]').each(function(){
$(this).text('')
})
if (Object.keys(errors).length !==0) {
for(var key in errors){
let value = errors[key]
$signUpFrom.find(`span[name=${key}_error]`).text(value)
}
return
}
$.ajax({
url: $signUpFrom.attr('action'),
method: $signUpFrom.attr('method'),
data: string,
success: function(response){
let object = JSON.parse(response) //浏览器上运行
console.log(object)
}
})
})

后台代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// server.js
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
if(!port){
console.log('请指定端口号好不啦?\nnode server.js 8888 这样不会吗?')
process.exit(1)
}
var server = http.createServer(function(request, response){
var parsedUrl = url.parse(request.url, true)
var pathWithQuery = request.url
var queryString = ''
if(pathWithQuery.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) }
var path = parsedUrl.pathname
var query = parsedUrl.query
var method = request.method
/******** 从这里开始看,上面不要看 ************/
if(path === '/'){
response.statusCode = 200
var string = fs.readFileSync('./index.html')
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.end(string)
}else if(path === '/signUp' && method === 'POST'){
getPostData(request,function(postData){
let {email,password,password_confirmation} = postData
let errors = {}
// check email
if(email.indexOf('@') <= 0){
errors.email = '邮箱不合法'
}
if(password.length < 6){
errors.password = '密码太短'
}
if(password_confirmation !== password){
errors.password_confirmation = '两次输入密码不匹配'
}
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.end(JSON.stringify(errors))
})
}else if(path === '/main.js'){
let string = fs.readFileSync('./main.js')
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.end(string)
}else{
response.statusCode = 404
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.end('找不到对应的路径,你需要自行修改index.js')
}
/******** 代码结束,下面不要看 ************/
console.log(method + ' ' + request.url)
})
function getPostData(request,callback){
data = ''
request.on('data',(postData) =>{
data += postData.toString()
})
request.on('end',()=>{
let array = data.split('&')
let postData = {}
for (var i = 0; i < array.length; i++) {
let parts = array[i].split('=')
let key = decodeURIComponent(parts[0])
let value = decodeURIComponent(parts[1])
postData[key] = value
}
callback.call(null,postData)
})
}
server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度然后用电饭煲打开 http://localhost:' + port)

登录表单

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// index.html
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<section>
<h1>登录</h1>
<form name="login" action="/login" method="post">
<div>
<label>邮箱
<input type="text" name="email">
</label>
</div>
<div>
<label>密码
<input type="password" name="password">
</label>
</div>
<input type="submit" value="登录">
</form>
</section>
<section>
<h1>注册</h1>
<form name="signUp" action="/signUp" method="post">
<div>
<label>邮箱
<input type="text" name="email">
</label>
<span name="email_error" class="error"></span>
</div>
<div>
<label>密码
<input type="password" name="password">
</label>
<span name="password_error" class="error"></span>
</div>
<div>
<label>确认密码
<input type="password" name="password_confirmation">
</label>
<span name="password_confirmation_error" class="error"></span>
</div>
<input type="submit" value="注册">
</form>
</section>
<section>
<h1>重置密码</h1>
<form name="resetPassword" action="/resetPassword" method="post">
<div>
<label>重置密码
<input type="text" name="email">
</label>
</div>
<input type="submit" value="请求重置密码">
</form>
</section>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="main.js"></script>
</body>
</html>
// home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>
// main.js
// 登录
let $loginForm = $('form[name=login]')
$loginForm.on('submit',(e)=>{
e.preventDefault()
let string = $loginForm.serialize()
$.ajax({
url: $loginForm.attr('action'),
method: $loginForm.attr('method'),
data: string,
success: function(){
location.href = './home'
},
error: function(){}
})
})
// 注册
let $signUpFrom = $('form[name=signUp]')
$signUpFrom.on('submit',(e)=>{
e.preventDefault()
let string = $signUpFrom.serialize()
// check email
let errors = checkForm($signUpFrom)
if (Object.keys(errors).length !==0) {
showErrors($signUpFrom,errors)
}else{
$.ajax({
url: $signUpFrom.attr('action'),
method: $signUpFrom.attr('method'),
data: string,
success: function(response){
location.href = './home'
},
error: function(xhr){
let errors = JSON.parse(xhr.responseText)
showErrors($signUpFrom,errors)
}
})
}
})
function checkForm($signUpFrom){
let email = $signUpFrom.find('[name=email]').val()
let password = $signUpFrom.find('[name=password]').val()
let password_confirmation = $signUpFrom.find('[name=password_confirmation]').val()
let errors = {}
if(email.indexOf('@') <= 0){
errors.email = '邮箱不合法'
}
if(password.length < 6){
errors.password = '密码太短'
}
if(password_confirmation !== password){
errors.password_confirmation = '两次输入密码不匹配'
}
return errors
}
function showErrors($signUpFrom,errors){
$signUpFrom.find('span[name$=_error]').each(function(){
$(this).text('')
})
for(var key in errors){
let value = errors[key]
$signUpFrom.find(`span[name=${key}_error]`).text(value)
}
}

后端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// server.js
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
if(!port){
console.log('请指定端口号好不啦?\nnode server.js 8888 这样不会吗?')
process.exit(1)
}
var server = http.createServer(function(request, response){
var parsedUrl = url.parse(request.url, true)
var pathWithQuery = request.url
var queryString = ''
if(pathWithQuery.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) }
var path = parsedUrl.pathname
var query = parsedUrl.query
var method = request.method
/******** 从这里开始看,上面不要看 ************/
if(path === '/'){
response.statusCode = 200
var string = fs.readFileSync('./index.html')
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.end(string)
}else if(path === '/signUp' && method === 'POST'){
getPostData(request,function(postData){
let errors = checkPostData(postData)
if(Object.keys(errors).length === 0){
let {email,password} = postData
let user = {
email : email,
passwordHash : yangHash(password)
}
// 写数据库
let dbString = fs.readFileSync('./db.json','utf-8')
let dbObject = JSON.parse(dbString)
dbObject.users.push(user)
let dbString2 = JSON.stringify(dbObject)
fs.writeFileSync('./db.json',dbString2,{encoding: 'utf-8'})
}else{
response.statusCode = 400
}
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.end(JSON.stringify(errors))
})
}else if(path === '/login' && method === 'POST'){
// 读数据库
getPostData(request,function(postData){
let dbString = fs.readFileSync('./db.json','utf-8')
let dbObject = JSON.parse(dbString)
let users = dbObject.users
let {email,password} = postData
let found
for(var i = 0;i < users.length;i++){
if (users[i].email === email && users[i].passwordHash === yangHash(password)) {
found = users[i]
break
}
}
if(found){
// 标记该用户登录
response.setHeader('Set-Cookie',['logined=true;expires=1000;path=/;','user_id='+email+'; expires=123456789;path=/;'])
response.end('')
}else{
response.statusCode = 400
let errors = { email : '没有注册或密码错误'}
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.end(JSON.stringify(errors))
}
})
}else if(path === '/main.js'){
let string = fs.readFileSync('./main.js')
response.setHeader('Content-Type', 'application/javascript;charset=utf-8')
response.end(string)
}else if(path === '/home'){
var cookies = parseCookies(request.headers.cookie)
response.setHeader('Content-Type', 'text/html;charset=utf-8')
if (cookies.logined === 'true') {
response.end(`${cookies.user_id}已登录`)
}else{
let string = fs.readFileSync('./home.html')
response.end(string)
}
}else{
response.statusCode = 404
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.end('找不到对应的路径,你需要自行修改index.js')
}
/******** 代码结束,下面不要看 ************/
console.log(method + ' ' + request.url)
})
function getPostData(request,callback){
data = ''
request.on('data',(postData) =>{
data += postData.toString()
})
request.on('end',()=>{
let array = data.split('&')
let postData = {}
for (var i = 0; i < array.length; i++) {
let parts = array[i].split('=')
let key = decodeURIComponent(parts[0])
let value = decodeURIComponent(parts[1])
postData[key] = value
}
callback.call(null,postData)
})
}
function checkPostData(postData){
let {email,password,password_confirmation} = postData
let errors = {}
// check email
if(email.indexOf('@') <= 0){
errors.email = '邮箱不合法'
}
if(password.length < 6){
errors.password = '密码太短'
}
if(password_confirmation !== password){
errors.password_confirmation = '两次输入密码不匹配'
}
return errors
}
function yangHash(string){
return 'yang' + string + 'yang'
}
function parseCookies(cookie) { // JSON.parse
try{
return cookie.split(';').reduce(
function(prev, curr) {
var m = / *([^=]+)=(.*)/.exec(curr);
var key = m[1];
var value = decodeURIComponent(m[2]);
prev[key] = value;
return prev;
},
{ }
);
}catch(error){
return {}
}
}
function stringifyCookies(cookies) { //JSON.stringify
var list = [ ];
for (var key in cookies) {
list.push(key + '=' + encodeURIComponent(cookies[key]));
}
return list.join('; ');
}
server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度然后用电饭煲打开 http://localhost:' + port)
// db.json
{"users":[]}