一、创建项目结构

前端Vue-cli

1
vue create 项目名

后端Express

(1)安装express-generator

1
npm install express-generator -g

(2) 创建express项目工程

1
2
3
4
express -e 项目名
cd 项目名
npm install
npm install express mongoose bcryptjs jsonwebtoken cors body-parser

二、创建数据库

1、先创建集合,然后插入数据

image-20231029161139100

2、创建文件夹db/index.js

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
//引入mongoose
const mongoose = require('mongoose')
//禁用严格查询
mongoose.set("strictQuery", false);
/**
*
* @param {链接成功回调} success
* @param {链接失败回调} error
*/
module.exports = function (success, error) {
if (typeof error !== 'function') {
error = () => {
console.log('链接失败')
}
}
//链接数据库
mongoose.connect('mongodb://127.0.0.1:27017/email')
//设置回调
mongoose.connection.on('open', () => {
success()
})
mongoose.connection.on('error', () => {
error()
})
mongoose.connection.on('close', () => {
console.log('链接关闭')
})
}

3、在启动bin/www 下面链接数据库

image-20231029161533739

三、创建前端登录和注册页面

前端登录组件

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
<template>
<div class="login-page">
<!-- 登录页面 -->
<div class="login">
<div class="login-box">
<!-- <div class="login-title">登录</div> -->
<form>
<div class="form-group clearfix">
<label for="username" class="leftfix"
><img src="../../assets/img/denglu-copy.png" alt=""
/></label>
<input
type="text"
class="leftfix"
id="username"
name="username"
placeholder="请输入用户名"
v-model="username"
/>
</div>
<div class="form-group clearfix">
<label for="password" class="leftfix"
><img src="../../assets/img/mima.png" alt="" /></label
>&nbsp;&nbsp;&nbsp;
<input
type="password"
class="leftfix"
id="password"
name="password"
placeholder="请输入密码"
v-model="password"
/>
</div>
<div class="login-bottom">
<input
type="button"
class="btn-login"
value="登录"
@click="login"
/>
<label> <input type="checkbox" checked="checked" />记住密码 </label>
<label>
<router-link to="/register">注册账号</router-link>
</label>
</div>
</form>
</div>
</div>
</div>
</template>

前端注册组件

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
<template>
<div class="container">
<div class="registration-form">
<h2>用户注册</h2>
<form @submit.prevent="register">
<div class="form-group">
<label for="username">用户名</label>
<input
type="text"
id="username"
v-model="userData.username"
required
placeholder="请输入用户名"
/>
</div>
<div class="form-group">
<label for="password">密码</label>
<input
type="password"
id="password"
v-model="userData.password"
required
placeholder="请输入密码"
style="margin-left: 33px"
/>
</div>
<button type="submit" @click="register">注册</button>
</form>
</div>
</div>
</template>

四、后端逻辑

4.1.登录

1、新创建一个models文件夹,然后在下面创建一个UserModel.js

1
2
3
4
5
6
7
8
9
var mongoose = require('mongoose')
//创建文档结构对象,设置集合中 文档的属性和属性值类型
let userSchema = new mongoose.Schema({
username: String,
password: String,
})
//创建模型对象--这个对象可以完成 增删改查操作 第一个参数是集合名称
let UserModel = mongoose.model('users', userSchema)
module.exports = UserModel

2、定义登录路由和处理逻辑

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
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const UserModel = require('../models/UserModel'); // 导入用户模型

// 登录路由
router.post('/login', async (req, res) => {
const { username, password } = req.body;
try {
// 查询用户
const user = await UserModel.findOne({ username });

if (!user) {
return res.status(404).json({ message: '用户不存在' });
}

// 验证密码
const isPasswordValid = await bcrypt.compare(password, user.password);

if (!isPasswordValid) {
return res.status(401).json({ message: '密码不正确' });
}

// 生成JWT令牌
const token = jwt.sign({ userId: user._id, username: user.username }, 'secretKey', {
expiresIn: '1h',
});

// 返回令牌给前端
res.status(200).json({ token });
} catch (err) {
console.error(err);
res.status(500).json({ message: '服务器错误' });
}
});
module.exports = router;

3、新创建一个middleware文件夹,然后在下面创建一个auth.js

创建一个中间件来验证用户的身份

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
const jwt = require('jsonwebtoken');

// 生成 JWT 令牌的函数
function generateToken(username) {
// 密钥,应该是一个安全的秘密字符串
const secretKey = 'your-secret-key';
// const crypto = require('crypto');
// const secretKey = crypto.randomBytes(32).toString('hex');

// 用户信息,你可以在令牌中包含需要的用户数据
const payload = {
username: username
};

// 生成令牌
const token = jwt.sign(payload, secretKey, {
expiresIn: '1d'
});

return token;
}

module.exports.generateToken = generateToken;

// 验证 JWT 令牌的中间件
module.exports.verifyToken = function (req, res, next) {
const token = req.header('x-auth-token'); // 获取令牌
// res.setHeader('x-auth-token', token);
// // 检查是否存在令牌
// console.log('token', token);

next();
};

4、分离路由 和 设置前缀

app.js中

image-20231029163506066

4.2 注册

1、定义注册路由和处理逻辑

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
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const UserModel = require('../models/UserModel');

router.post('/register', async (req, res) => {
const { username, password } = req.body;

try {
const existingUser = await UserModel.findOne({ username });

if (existingUser) {
return res.status(400).json({ message: '用户名已存在' });
}

const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);

const newUser = new UserModel({
username,
password: hashedPassword
});

await newUser.save();

const payload = {
username: newUser.username
};

const token = jwt.sign(payload, 'secretKey', {
expiresIn: '1d'
});

res.json({ token });
} catch (error) {
console.error(error);
res.status(500).json({ message: '服务器错误' });
}
});

module.exports = router;

2、分离路由 和设置前缀

同上

五、前端逻辑

登录

在前端,使用Axios库来发送HTTP请求到后端。在 Login.vueRegister.vue 组件中添加数据绑定、表单验证、事件处理以及Axios请求

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
<script>
import axios from "axios";

export default {
data() {
return {
username: "",
password: "",
};
},
name: "loginPage",
methods: {
async login() {
const response = await axios
.post("http://localhost:3000/users/login", {
username: this.username,
password: this.password,
})
.then((res) => {
// 200
console.log("res", res);
const token = res.data.token;

// 存储令牌到本地存储
localStorage.setItem("token", token);
// 导航到受保护的页面
this.$router.push("/indexMain");
})
.catch((err) => {
this.$message.error(err.response.data);
});
},
},
};
</script>

注册

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
 <script>
import axios from "axios";
export default {
data() {
return {
userData: {
username: "",
password: "",
},
isRegistering: false, // 控制按钮禁用状态
};
},
name: "register",
methods: {
async register() {
if (this.isRegistering) {
// 防止重复点击
return;
}
this.isRegistering = true;
const response = await axios
.post("http://localhost:3000/register/register", {
username: this.userData.username,
password: this.userData.password,
})
.then((res) => {
const token = res.data.token;
// 存储令牌到本地存储
localStorage.setItem("token", token);

// 导航到受保护的页面或其他需要的页面
this.$router.push("/login");
})
.catch((err) => {
this.$message.error(err.response.data);
})
.finally(() => {
this.isRegistering = false; // 启用按钮
});
},
},
};
</script>