云函数手撸用户体系

使用云函数实现用户系统 数据库为腾讯云TDSQL 其它服务商云函数 通用 只需修改index.js返回参数即可 主要有用户注册 用户登陆 邮箱发送验证码 邮箱验证码校检 邮箱绑定 邮箱解绑 邮箱验证码登陆 生成token 校验token 其它功能可以在此基础上拓展 纯手撸代码 云函数环境为nodejs12.13 由于我比较穷 就不带大家使用短信服务了 短信发送验证码和邮箱验证码逻辑差不多

主要为 安装并且依赖包 配置邮箱服务 配置数据库连接 封装用户模块 调用封装的用户模块

用户模块为主要

以下操作 在本地执行

下载依赖包

npm install dmhsq-mysql-pool 操作数据库

npm install nodemailer 邮件发送服务

npm install js-md5 md5加密

操作数据库以及邮件发送详情可以看

华为函数工作流云函数操作云MySQL数据库实现邮箱验证码发送以及校验

使用华为云函数实现邮件发送

目前目录结构为

图片.png

其中index.js是云函数入口文件

配置邮箱服务(封装邮箱模块)

需要拿到SMTP的授权码 具体为找到邮箱设置

图片.png

之前的文章已经配置过

我们直接上代码 由于目前邮箱只负责发验证码 我就把验证码发送直接写成固定的了

其中 code为验证码 time为有效时间

新建email.js

代码语言:txt
复制
const nodemailer = require('nodemailer')

const transporter = nodemailer.createTransport({
service: 'xx', // qq,126等等.
auth: {
user: 'xxxxx@xxx.com',
pass: 'xxxx'
}
});

const sendCode = (code,time) => {
let message = {
from: "验证码<xxxx@xx.com>",
to: email,
subject: "验证码服务",
html: `<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div>
<p style="font-size: 20px;">欢迎您使用,您的验证码为
<span style="color: blue;font-size: 30px;font-weight: 800;">{code}&lt;/span&gt; ,有效时间为{time/60}分钟, 请勿泄露并及时验证</p>

						&lt;div style=&#34;margin-top: 50px;&#34;&gt;&lt;/div&gt;
						&lt;p style=&#34;color: red;font-size: 25px;&#34;&gt;请勿回复&lt;/p&gt;
					&lt;/div&gt;
				&lt;/body&gt;
			&lt;/html&gt;`
};

await transporter.sendMail(message)
return {
code:0,
msg:"邮件已发送,如果没有收到,请检查邮箱"
}
}

module.exports = {sendCode};

配置数据连接

新建db.js

引入dmhsq-mysql-pool并配置

代码语言:txt
复制
const database = require("dmhsq-mysql-pool");
const configs = {
host: 'xxxxx',
port: 'xxxxx',
user: 'xxxx',
password: 'xxxxx',
database: "xxxx"
}
let user = new database(configs).table("user")
let codes = new database(configs).table("email")
module.exports = {
user,
codes
};

数据库为腾讯云TDSQL

这里使用简单的数据表

用户表如下

图片.png

验证码表如下

图片.png

编写用户管理模块

新建user.js

引入验证码发送以及数据库操作模块

代码语言:txt
复制
const {user,codes} = require("./db.js");
const sendCode = require("./email.js");
const md5 = require("js-md5")

注册模块

逻辑如下

需要用户名和密码

注册时 密码会加密一次 存入数据库

注册成功会自动登录并返回 token token过期时间

代码语言:txt
复制
const sign = async (username, password) => {
const dfp = password
password = md5(password);
let isH = await user.where({username}).get();
if(isH.data.length>0){
return {
code: 5742,
msg: "用户名已被占用",
}
}
const _id = md5(Math.random().toString(36)).substr(0, 10);
let res = await user.add({
username,
password,
_id
}).get();
let rsp = {
code: 5741,
msg: "注册失败",
}
if (res.code == 0) {
let userRes = await login(username, dfp);
rsp = {
code: 0,
msg: "注册成功"
}
if (userRes.code == 0) {
rsp.data = userRes.userInfo
}
}
return rsp;
}

登录模块

逻辑如下 如果用户密码输入正确 会生成一个token 以及token过期时间

以及最后一次登录时间 也就是本次登录的时间 入库

登录成功返回 token token过期时间

代码语言:txt
复制
const login = async (username, password) => {
password = md5(password)

let res = await user.where({
	username,
	password
}).get()
if (!res.data.length) {
	return {
		code: 9001,
		msg: &#34;用户名或者密码错误&#34;
	}
} else {
	let token = md5(md5(Math.random().toString(36)) + md5(Math.random().toString(36)));
	const tokenExpired = parseInt(Date.parse(new Date()).toString().substr(0, 10)) + 72000;
	const last_login_time = parseInt(Date.parse(new Date()).toString().substr(0, 10));
	let qres = await user.updata({
		token_expired: tokenExpired,
		token,
		last_login_time
	}).where({username}).get();
	if (qres.code == 0) {
		return {
			code: 0,
			userInfo: {
				token,
				tokenExpired,
				username
			}
		}
	} else {
		return {
			code: 9002,
			msg: &#34;登陆失败&#34;,
			data: qres
		}
	}

}

}

token校检

逻辑如下 通过token可以获取对应用户的用户信息

代码语言:txt
复制
//token校检
const checkToken = async (token) =>{
const reqs = await user.where({token}).get();
let res = {}
if(reqs.data.length>0){
const userInfos = reqs.data[0]
const check_time = userInfos.token_expired;
const now_time = parseInt(Date.parse(new Date()).toString().substr(0, 10));
if(check_time>now_time){
res = {
code:0,
userInfo:{
username:userInfos.username
}
}
}else{
res = {
code:7412,
msg:"token过期"
}
}
}else{
res = {code:7417,msg:"token非法"}
}
return res;
}

邮箱发送验证码

逻辑如下

如果邮箱发送成功 则会生成验证码入库

可以发送登录或者绑定或者解除绑定验证码

也可以自定义类型

代码语言:txt
复制
const sendEmailCode = async (email,type) => {
const randomStr = '00000' + Math.floor(Math.random() * 1000000)
const code = randomStr.substring(randomStr.length - 6);
let time = 3600
const check_time = parseInt(Date.parse(new Date()).toString().substr(0, 10)) + time;
let res = {}
res = await sendCode(email, code, time)
if (res.code == 0) {
await codes.add({
email,
code,
check_time,
state: 0,
type
}).get();
} else {
res = {
code: 4046,
msg: "发送失败"
}
}
return res
}

邮箱验证码校验

逻辑如下

邮箱验证码 类型均正确为通过

代码语言:txt
复制
const checkCode = async (email,code,type) =>{
let result = await codes.where({
email,
code,
type
}).sort({
check_time: "DESC"
}).get();
let data = result.data;
let res = {}
if (data.length == 0) {
res = {
code: 4048,
msg: "验证码错误"
}
} else {
data = data[0]
const check_times = parseInt(Date.parse(new Date()).toString().substr(0, 10));
if (data.state == 0 & data.check_time > check_times) {
await codes.updata({
state: 1
}).where({
email
}).get()
res = {
code: 0,
msg: "验证通过"
}
} else if (data.check_time < check_times) {
res = {
code: 4044,
msg: "验证码失效"
}
} else if (data.state == 1) {
res = {
code: 4045,
msg: "验证码已经验证"
}
} else {
res = {
code: 4048,
msg: "验证码错误"
}
}
}
return res;
}

邮箱绑定

进行此步骤之前需要校检token获取username

需要用户名 邮箱 以及验证码

如果用户已经绑定 就告知已经绑定

否则 绑定

代码语言:txt
复制
const bind = async (username,email,code) => {
const check_code = await checkCode(email,code,"bind");
const check_user = await user.where({username}).get();
let res = {}
if(check_user.data.length>0){
res = {
code:74174,
msg:"用户已经绑定邮箱"
}
}else{
if(check_code.code==0){
const datas = await user.updata({email}).where({username}).get();
if(datas.code==0){
res = {
code:0,
msg:"绑定成功"
}
}
}else{
res = check_code
}
}
return res;
}

邮箱解除绑定

进行此步骤之前需要校检token获取username

需要用户名 邮箱 以及验证码

如果用户还未绑定 就告知还未绑定

否则 解除绑定

代码语言:txt
复制
const unbind = async (username,email,code) => {
const check_code = await checkCode(email,code,"bind");
const check_user = await user.where({username,email}).get();
let res = {}
if(check_user.data.length==0){
res = {
code:74175,
msg:"用户还未绑定邮箱"
}
}else{
if(check_code.code==0){
const datas = await user.updata({email:""}).where({username}).get();
if(datas.code==0){
res = {
code:0,
msg:"解除绑定成功"
}
}
}else{
res = check_code
}
}
return res;
}

邮箱验证码校检登录

逻辑如下

根据验证码 邮箱 以及验证码类型查询数据库

如果数据库 存在符合数据 且状态为0则验证通过

验证通过则生成token token过期时间 最后一次登录时间入库

返回 token token过期时间 email

代码语言:txt
复制
const checkCode = async (email, code) => {
const ress = await checkCode(email,code,"login")
const isH = await user.where({email}).get();
if(isH.data.length==0){
return {
code:9003,
msg:"非法邮箱(邮箱未绑定用户)"
}
}
if(ress.code==0){
let token = md5(md5(Math.random().toString(36)) + md5(Math.random().toString(36)));
const tokenExpired = parseInt(Date.parse(new Date()).toString().substr(0, 10)) + 72000;
const last_login_time = parseInt(Date.parse(new Date()).toString().substr(0, 10));
let qres = await user.updata({
token_expired: tokenExpired,
token,
last_login_time
}).where({email}).get();
if (qres.code == 0) {
res = {
code: 0,
userInfo: {
token,
tokenExpired,
email
}
}
} else {
res = {
code: 9002,
msg: "登陆失败",
data: qres
}
}
}
return res;
}

完成后 导出模块

代码语言:txt
复制
module.exports = {
sign,
login,
sendEmailCode,
checkCode,
bind,
unbind,
checkCodeLogin,
checkTokenk
}

使用

在index.js引入

代码语言:txt
复制
const userCenter = require("./user.js")

exports.handler = async (event, context ,callback) => {

let noCheckAction = [&#39;sign&#39;, &#39;checkToken&#39;, &#39;login&#39;, &#39;checkCode&#39;, &#39;loginByEmail&#39;, &#39;emailCode&#39;]
let params = event.queryStringParameters;
let res = {}
const {
	action
} = params
if (noCheckAction.indexOf(action) === -1) {
	if (!params.token) {
		res = {
			code: 401,
			msg: &#39;缺少token&#39;
		}
		const output = {
			&#39;statusCode&#39;: 200,
			&#39;headers&#39;: {
				&#39;Content-Type&#39;: &#39;application/json&#39;
			},
			&#39;isBase64Encoded&#39;: false,
			&#39;body&#39;: JSON.stringify(res),
		}
		callback(null, output);
	}else{
		let datas = await userCenter.checkToken(params.token)
		if (datas.code != 0) {
			res = datas
			const output = {
				&#39;statusCode&#39;: 200,
				&#39;headers&#39;: {
					&#39;Content-Type&#39;: &#39;application/json&#39;
				},
				&#39;isBase64Encoded&#39;: false,
				&#39;body&#39;: JSON.stringify(res),
			}
			callback(null, output);
		}else{
			params.username = datas.userInfo.username;
		}
	}
	
}
switch (action) {
	case &#34;sign&#34;: {
		const {
			username,
			password
		} = params;
		res = await userCenter.sign(username, password);
		break;
	}
	case &#34;login&#34;: {
		const {
			username,
			password
		} = params;
		res = await userCenter.login(username, password)
		break;
	}
	case &#34;emailCode&#34;: {
		const {
			email,
			type
		} = params;
		res = await userCenter.sendEmailCode(email, type)
		break;
	}
	case &#34;checkCode&#34;: {
		const {
			email,
			code,
			type
		} = params;
		res = await userCenter.checkCode(email, code, type)
		break;
	}
	case &#34;bind&#34;: {
		const {
			username,
			email,
			code
		} = params;
		res = await userCenter.bind(username, email, code)
		break;
	}
	case &#34;unbind&#34;: {
		const {
			username,
			email,
			code
		} = params;
		res = await userCenter.unbind(username, email, code)
		break;
	}
	case &#34;loginByEmail&#34;: {
		const {
			email,
			code
		} = params;
		res = await userCenter.checkCodeLogin(email, code)
		break;
	}
	case &#34;checkToken&#34;: {
		const {
			token
		} = params;
		res = await userCenter.checkToken(token)
		break;
	}
	default: {
		res = {
			code: 403,
			msg: &#34;非法访问&#34;
		};
		break;
	}
}


const output = {
	&#39;statusCode&#39;: 200,
	&#39;headers&#39;: {
		&#39;Content-Type&#39;: &#39;application/json&#39;
	},
	&#39;isBase64Encoded&#39;: false,
	&#39;body&#39;: JSON.stringify(res),
}
callback(null, output);

}

上传代码

将整个目录文件打成zip压缩包

如下

图片.png
图片.png

创建云函数的时候选择上传代码

或者创建完选择也可以

创建触发器

图片.png

测试

注册

注册成功自动登录返回用户 token token过期时间

图片.png

注册时 用户名已被占用

图片.png

登录

登录成功返回用户 token token过期时间

图片.png

用户名或者密码错误

图片.png

绑定邮箱

获取邮箱验证码

图片.png
图片.png

绑定前

图片.png

绑定后

图片.png
图片.png

绑定失败

图片.png

解除绑定

图片.png
图片.png

解除绑定失败

图片.png

邮箱验证码验证失败

图片.png
图片.png
图片.png

邮箱验证码登录

通过邮箱登录 不会返回用户名 会返回邮箱

图片.png
图片.png

获取用户信息

通过checkToken

图片.png

如果请求action不在switch case中

图片.png

如果token不正确

图片.png

如果token不携带

图片.png