需求:只能登陆的用户访问主页,没有登陆就跳转到登陆页。
一、划分目录
- control: 分离处理接口的方法
- dao: 处理数据库的基本操作(无逻辑增删改查)
- service: 有逻辑的增删改查(例如新增用户:1.先判断用户是否存在 2. 在添加用户)
- static: 存储静态资源
- utils: 存储工具文件
- vaild: 存放需要权限认证的页面, 也就是登陆了才能访问的页面
server.js: 启动服务主程序的文件
sqlServer.js: 启动连接sql的服务文件
二、安装模块
npm install express 安装 express 框架
npm install body-parser 安装body-parser 用于解析post请求的参数
npm install cookie-parser 安装cookie-parser 用于解析cookie
npm install mysql 安装mysql 用于连接mysql数据库
npm install mime 安装mime 用于解析 媒体类型 例如: text/html、application/json等
创建数据库表
create database test;
create table _user(
u_id bigint primary key auto_increment,
u_name varchar(40) not null,
u_pass varchar(80) not null,
u_eamil varchar(35) not null
)
搭登陆页面
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LOGIN</title>
<link rel="stylesheet" href="reset.min.css">
<!--IMPORT CSS-->
<style>
* {
font-family: "Helvetica Neue", Helvetica, "PingDang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "微软雅黑", Arial;
font-size: 13px;
}
body, html {
width: 100%;
height: 100%;
}
body {
background: #eef2f5;
}
.login-box {
position: relative;
top: 30%;
left: 50%;
transform: translateX(-50%);
box-sizing: border-box;
width: 300px;
background: #fff;
}
.padding-box {
padding: 0 25px;
}
.check-l-box {
text-align: center;
height: 50px;
line-height: 50px;
}
.check-l-box > span {
cursor: pointer;
}
.check-l-box > span.mark {
border-bottom: 1px solid black;
}
/*登陆表单*/
.message-form {
color: #7b7b7b;
display: none;
}
.message-form > label {
line-height: 35px;
display: block;
}
.message-form > label > input {
display: block;
width: 100%;
height: 28px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border: 1px solid #ddd;
}
.message-form > label > input:focus {
outline: 0;
box-shadow: 0 0 8px lightblue;
}
#login-submit, #reg-submit {
margin-top: 10px;
margin-bottom: 10px;
height: 30px;
width: 100%;
border: 1px solid #ddd;
}
.message-form.check {
display: block;
}
.tips {
text-align: center;
color: red;
}
</style>
</head>
<body>
<section class="login-box">
<div class="padding-box">
<div class="check-l-box">
<span class="mark">登陆</span>
<span>注册</span>
</div>
<!-- 提示: 后期发送请求返回告诉用户信息 -->
<p class="tips"></p>
<!-- 登陆表单 -->
<form class="message-form check" method="post" action="/user/login">
<label for="account">
用户名:<input type="text" id="account" name="account">
</label>
<label for="password">
密码:<input type="password" id="password" name="password">
</label>
<input type="submit" value="登陆" id="login-submit">
</form>
<!-- 注册表单 -->
<form class="message-form" method="post" action="/user/reg">
<label for="reg-account">
用户名:<input type="text" id="reg-account" name="reg_account">
</label>
<label for="reg-password">
密码:<input type="password" id="reg-password" name="reg_password">
</label>
<label for="user-email">
邮箱:<input type="text" id="user-email" name="user_email"/>
</label>
<input type="submit" value="注册" id="reg-submit">
</form>
</div>
</section>
<!-- IMPORT JS -->
<script>
// 登陆/注册按钮切换
let forms = document.querySelectorAll('.message-form'),
marks = document.querySelectorAll('.check-l-box>span')
let clearCheck = () => {
for (let i = 0; i < forms.length; i++) {
forms[i].className = 'message-form'
marks[i].className = ''
}
}
for (let i = 0; i < marks.length; i++) {
marks[i].addEventListener('click', function () {
clearCheck();
this.className = 'mark'
forms[i].className = 'message-form check'
}, false)
}
// 发送请求部分
let loginBtn = document.querySelector('#login-submit'),
regBtn = document.querySelector('#reg-submit'),
tip = document.querySelector('.tips');
// 简单封装ajax请求 封装为pomise
let sendReq = (method, path, pram) => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open(method, path)
xhr.onreadystatechange = function () {
if (xhr.status === 200 && xhr.readyState === 4) {
resolve(xhr.responseText)
}
}
xhr.onerror = () => {
reject()
}
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8')
xhr.send(pram)
})
}
loginBtn.onclick = function (ev) {
let account = document.querySelector('#account'),
password = document.querySelector('#password'),
par = 'account' + '=' + account.value + '&' + 'password' + '=' + password.value;
console.log(par)
sendReq('POST', '/user/login', par).then(res => {
// 密码正确跳转,没有正确则显示错误信息
try {
res = JSON.parse(res)
res.code === 0 ? tip.innerText = res.message : location.href = 'http://localhost:8080';
} catch (e) {
location.href = 'http://localhost:8080';
}
})
ev.preventDefault()
}
regBtn.onclick = function (ev) {
let account = document.querySelector('#reg-account'),
password = document.querySelector('#reg-password'),
email = document.querySelector('#user-email'),
par = 'account' + '=' + account.value + '&' + 'password' + '=' + password.value + '&email=' + email.value;
sendReq('POST', '/user/reg', par).then(res => {
let {code, message} = JSON.parse(res);
tip.innerText = message
if (code === 1) {
clearCheck()
marks[0].className = 'mark'
forms[0].className = 'message-form check'
}
})
ev.preventDefault()
}
</script>
</body>
</html>
写完上面的代码我们就可以得到这么一个页面
主页搭建
主页代码十分简单,只要用于测试
注: 主页需要放在valid文件夹下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>我是主页</h2>
</body>
</html>
编写工具方法
在utils目录中: 案例中主要是把 读文件 / sql 操作封装为promise
- promiseFS.js
/*
* 将fs中的常用 I/O 操作封装为promise版本
* */
let fs = require('fs'),
path = require('path'),
resultObj = {};
let suffixHandle = (pathname) => {
let suffixReg = /\.(PNG|JPG|JPEG|WEBP|ICO|BMP|SVG|MP4|MP3|M3U8|WAV|OGG)$/i
return suffixReg.test(pathname)
}
// READ-FILE / READ-DIR / MK-DIR / RM-DIR / UN-LINK /
/*
* 读取文件时 需要使用编码 以及过滤 富媒体 文件
* */
['readFile', 'readdir', 'mkdir', 'rmdir', 'unlink'].forEach(item => {
resultObj[item] = function (pathname, encoding = 'utf8') {
return new Promise((resolve, reject) => {
let callback = function (err, res) {
!err ? resolve(res) : reject(err)
}
// 如果是富媒体编码就为null
suffixHandle() ? encoding = null : null
pathname = path.resolve(pathname)
if (item !== 'readFile') {
encoding = callback
encoding = null
}
fs[item](pathname, encoding, callback)
})
}
});
// WRITE-FILE / APPEND-FILE
['writeFile', 'appendFile'].forEach(item => {
resultObj[item] = function (pathname, content, encoding = 'utf8') {
// 支持JSON类型数据
(typeof content === 'object' && content !== null) ? content = JSON.stringify(content) : null
// 将传入的内容转为 对象
typeof content !== 'string' ? content += '' : null
return new Promise((resolve, reject) => {
let callback = function (err, res) {
!err ? resolve(res) : reject(err)
}
pathname = path.resolve(pathname)
fs[item](pathname, content, encoding, callback)
})
}
})
// COPYFILE
resultObj['copyFile'] = function (pathname1, pathname2) {
return new Promise((resolve, reject) => {
// 解析路径 为了防止路径错误
pathname1 = path.resolve(pathname1)
pathname2 = path.resolve(pathname2)
let callback = function (err) {
!err ? resolve() : reject(err)
}
fs['copyFile'](pathname1, pathname2, callback)
})
}
module.exports = resultObj
- promiseSQL
/**
* con: 连接sql的connection对象
* sql: sql语句
*/
let querySql = (con, sql) => {
return new Promise((resolve, reject) => {
con.query(sql, (err, result) => {
if (err) reject(err)
resolve(result)
})
})
}
module.exports = {
querySql
}
连接数据库
在dao目录下创建两个文件
- baseSql.js: 放着无逻辑sql操作
let promiseSQL = require('../utils/promiseSQL');
/**
* @param con: sql连接对象
* @param table: 需要查询的表
* @param condition: 需要查询的条件 Object
* */
let query = (con, table, condition) => {
let cKeys = Object.keys(condition),
pram1, pram2 = '';
if (cKeys.length !== 0) {
pram1 = `where ${cKeys[0]}='${condition[cKeys[0]]}'`;
if (cKeys.length === 2) {
pram2 = `and ${cKeys[1]}=${condition[cKeys[1]]}`
}
}
return promiseSQL.querySql(con, `select * from ${table} ${pram1} ${pram2}`)
}
/**
* @param con: sql连接对象
* @param table: 需要插入的表
* @param condition: 需要插入的对象
* */
let insert = (con, table, condition) => {
let cKeys = Object.keys(condition)
if (cKeys.length === 0) return new Promise.reject('插入无效')
let attr = '', val = '';
cKeys.forEach(key => {
attr += key + ','
val += `'${condition[key]}',`
})
attr = attr.substring(0, attr.length - 1)
val = val.substring(0, val.length - 1)
let sql = `insert into ${table + '(' + attr + ')'} values ${'(' + val + ')'}`
return promiseSQL.querySql(con, sql)
}
module.exports = {
query,
insert
}
sqlConfig.js: 配置sql文件
module.exports = {
host:'localhost',
user:'root',
password:'123456',
database:'test'
}
编写服务操作
- loginService.js用于查询用户和增加用户
/* 关于登陆的查询服务 */
let baseSql = require('../dao/baseSql')
// 查询用户
let queryUser = (con, uname, password) => {
let queryObj = {u_name: uname}
if (password) {
queryObj.u_pass = password
}
return baseSql.query(con, '_user', queryObj)
}
// 插入用户
let insertUser = (con, uname, upassword, email) => {
let insertObj = {u_name: uname, u_pass: upassword, u_email: email}
return baseSql.insert(con, '_user', insertObj)
}
module.exports = {
queryUser,
insertUser
}
编写controller(登陆/注册逻辑)
control文件夹下新建loginController.js
- loginController.js
let loginService = require('../service/loginService'),
connection = require('../sqlServer')
/* 登陆逻辑 */
let loginHandle = (req, res) => {
res.clearCookie('userState')
let {account, password} = req.body
res.status(200)
res.type('application/json')
// 传入空字符
if (!account || !password) {
res.send({
code: 0,
message: '请输入账号或密码!'
})
}
// 查询数据库 验证用户是否正确
loginService.queryUser(connection, account, password).then(result => {
let check = result.length === 0
// 用户不存在
if (check) {
res.send({code: 0, message: '用户名或密码错误'})
} else {
// 保存登陆信息到cookie
res.cookie('userState', {isLogin: true, username: account})
res.redirect('/')
}
})
}
/* 注册逻辑 */
let regHandle = (req, res) => {
let {account, password, email} = req.body;
// 查询用户是否存在
loginService.queryUser(connection, account).then(result => {
let check = result.length !== 0;
// 用户名存在
if (check) {
return Promise.reject()
}
return Promise.resolve()
}).then(result => {
// 不存在就添加用户
return loginService.insertUser(connection, account, password, email)
}).catch(() => {
res.send({code: 0, message: '用户已经存在'})
return Promise.reject()
}).then(result => {
// 添加用户成功
res.send({code: 1, message: '创建账号成功'})
}).catch(() => {
})
}
module.exports = {
regHandle,
loginHandle
}
编写服务(sql连接/服务器)
- sqlServer.js:连接数据库
const mysql = require('mysql'),
sqlConfig = require('./dao/sqlConfig');
// 创建连接
let connection = mysql.createConnection(sqlConfig)
// 连接数据库
connection.connect((err) => {
if (err) throw err
console.log('连接成功')
})
// 导出连接对象
module.exports = connection
- server.js: 主程序,启动服务器
const express = require('express'),
bodyParser = require('body-parser'),
promiseFS = require('../utils/promiseFS'),
cookieParser = require('cookie-parser'),
{regHandle, loginHandle} = require('./control/loginController')
// 创建服务
let app = express()
app.listen(8080, () => console.log('SUCCESS!'))
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(bodyParser.json({
extended: true
}))
app.use(cookieParser())
app.use(express.static('./static'))
// 处理路径
app.use((req, res, next) => {
if (req.method === 'GET') {
let {url} = req,
cookies = req.cookies;
url === '/' ? url = '/index.html' : null;
// 已经登陆
if (!!cookies.userState) {
res.type('text/html')
res.status(200)
promiseFS.readFile(`./valid${url}`).then(result => {
res.send(result)
}).catch(err => {
res.redirect('/index.html')
})
return
}
if (url === '/index.html' && !cookies.userState) {
res.redirect('/login.html')
}
}
next()
})
// 登陆接口
app.post('/user/login', loginHandle)
// 注册接口
app.post('/user/reg', regHandle)
测试
执行node server: 执行server文件启动服务
登陆效果测试
注册效果测试