使用云函数实现用户系统 数据库为腾讯云TDSQL 其它服务商云函数 通用 只需修改index.js返回参数即可 主要有用户注册 用户登陆 邮箱发送验证码 邮箱验证码校检 邮箱绑定 邮箱解绑 邮箱验证码登陆 生成token 校验token 其它功能可以在此基础上拓展 纯手撸代码 云函数环境为nodejs12.13 由于我比较穷 就不带大家使用短信服务了 短信发送验证码和邮箱验证码逻辑差不多
主要为 安装并且依赖包 配置邮箱服务 配置数据库连接 封装用户模块 调用封装的用户模块 用户模块为主要
以下操作 在本地执行
下载依赖包
npm install dmhsq-mysql-pool 操作数据库 npm install nodemailer 邮件发送服务 npm install js-md5 md5加密
操作数据库以及邮件发送详情可以看 华为函数工作流云函数操作云MySQL数据库实现邮箱验证码发送以及校验 使用华为云函数实现邮件发送
目前目录结构为 其中index.js是云函数入口文件
配置邮箱服务(封装邮箱模块)
需要拿到SMTP的授权码 具体为找到邮箱设置 之前的文章已经配置过 我们直接上代码 由于目前邮箱只负责发验证码 我就把验证码发送直接写成固定的了 其中 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 这里使用简单的数据表 用户表如下
验证码表如下
编写用户管理模块
新建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压缩包 如下
创建云函数的时候选择上传代码 或者创建完选择也可以
创建触发器
测试
注册
注册成功自动登录返回用户 token token过期时间
注册时 用户名已被占用
登录
登录成功返回用户 token token过期时间
用户名或者密码错误
绑定邮箱
获取邮箱验证码
绑定前
绑定后
绑定失败
解除绑定
解除绑定失败
邮箱验证码验证失败
邮箱验证码登录
通过邮箱登录 不会返回用户名 会返回邮箱
获取用户信息
通过checkToken