云函数手撸用户体系

举报
代码哈士奇 发表于 2021/03/30 10:14:39 2021/03/30
【摘要】 使用华为云函数工作流实现用户系统数据库为云数据库MySQL其它服务商云函数 通用 只需修改index.js返回参数即可主要有用户注册 用户登陆 邮箱发送验证码 邮箱验证码校检 邮箱绑定 邮箱解绑 邮箱验证码登陆 生成token 校验token 其它功能可以在此基础上拓展纯手撸代码 云函数环境为nodejs12.13由于我比较穷 就不带大家使用短信服务了 短信发送验证码和邮箱验证码逻辑...

使用华为云函数工作流实现用户系统
数据库为云数据库MySQL
其它服务商云函数 通用 只需修改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

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}</span> ,有效时间为${time/60}分钟, 请勿泄露并及时验证</p>
						
							<div style="margin-top: 50px;"></div>
							<p style="color: red;font-size: 25px;">请勿回复</p>
						</div>
					</body>
				</html>`
	};
   await transporter.sendMail(message)
	return {
			code:0,
			msg:"邮件已发送,如果没有收到,请检查邮箱"
	}
}

module.exports = {sendCode};


配置数据连接

新建db.js

引入dmhsq-mysql-pool并配置

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
引入验证码发送以及数据库操作模块

const {user,codes} = require("./db.js");
const sendCode = require("./email.js");
const md5 = require("js-md5")

注册模块

逻辑如下
需要用户名和密码
注册时 密码会加密一次 存入数据库
注册成功会自动登录并返回 token token过期时间

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过期时间

const login = async (username, password) => {
	password = md5(password)

	let res = await user.where({
		username,
		password
	}).get()
	if (!res.data.length) {
		return {
			code: 9001,
			msg: "用户名或者密码错误"
		}
	} 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: "登陆失败",
				data: qres
			}
		}

	}
}

token校检

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

//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;
}

邮箱发送验证码

逻辑如下
如果邮箱发送成功 则会生成验证码入库
可以发送登录或者绑定或者解除绑定验证码
也可以自定义类型

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
}

邮箱验证码校验

逻辑如下
邮箱验证码 类型均正确为通过

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

需要用户名 邮箱 以及验证码
如果用户已经绑定 就告知已经绑定
否则 绑定

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

需要用户名 邮箱 以及验证码
如果用户还未绑定 就告知还未绑定
否则 解除绑定

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

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;
}

完成后 导出模块

module.exports = {
	sign,
	login,
	sendEmailCode,
	checkCode,
	bind,
	unbind,
	checkCodeLogin,
	checkTokenk
}

使用

在index.js引入

const userCenter = require("./user.js")

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

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

	const output = {
		'statusCode': 200,
		'headers': {
			'Content-Type': 'application/json'
		},
		'isBase64Encoded': false,
		'body': 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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。