npm run dev
搭建node.js服务器,引入express.js,配置package.json
npm i express
搭建项目入口文件
const express = require('express')
const app = express()
app.listen(5100, () => {
console.log('express is running at http://127.0.0.1:5100');
})
"scripts": {
"start": "node index.js",
"serve": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
npm run serve
npm run start
npm i body-parser
使用body-parser获取前端传送过来的数据 首先第一步引入
const bodyParser = require('body-parser');
//对body-parser进行配置
app.use( bodyParser.urlencoded({extended: true}) )
//设置完毕之后,会在req对象上面新增一个req.body的一个对象
再来说说node后台对这两种请求格式的处理:
首先是最新express版本4.7.2:
4.7.2版本的express没有了bodyParser方法,需要另外安装body-parser模板:于是另外安装了body-parser模板1.5.2版本
使用代码如下:
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());
使用bodyParser.urlencoded(),使node后台支持了第一种请求体. 使用bodyParser.json(),使node后台支持了第二种请求体.
后定义的不会覆盖先定义的… 也就是说,这段代码同时支持了这两种请求体. 另外,虽然请求体的格式不同,但是经过node解析后,他们最终得到的都是json格式的对象.
express4.18.1版本不需要使用这样的方法获取post请求的数据了。 req.body
npm i bcrypt
相关参数
const bcrypt = require('bcrypt');
const saltRounds = 10;
const myPlaintextPassword = 's0/\/\P4$$w0rD';
const someOtherPlaintextPassword = 'not_bacon';
使用
const bcrypt = require('bcrypt');
bcrypt.genSalt(saltRounds, function(err, salt) {
bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
// Store hash in your password DB.
});
});
这块不适用于mysql,如果是mysql加密可以使用crypto
npm i gravatar
var gravatar = require('gravatar');
gravatar.url(email);
gravatar.url(email, options);
gravatar.url(email, options, protocol);
gravatar.profile_url(email);
gravatar.profile_url(email, options);
gravatar.profile_url(email, options, protocol);
var gravatar = require('gravatar');
var url = gravatar.url('emerleite@gmail.com', {s: '200', r: 'pg', d: 'mm'});
//returns //www.gravatar.com/avatar/93e9084aa289b7f1f5e4ab6716a56c3b?s=200&r=pg&d=404
var unsecureUrl = gravatar.url('emerleite@gmail.com', {s: '100', r: 'x', d: 'retro'}, false);
//returns http://www.gravatar.com/avatar/93e9084aa289b7f1f5e4ab6716a56c3b?s=100&r=x&d=retro
var secureUrl = gravatar.url('emerleite@gmail.com', {s: '100', r: 'x', d: 'retro'}, true);
//returns https://s.gravatar.com/avatar/93e9084aa289b7f1f5e4ab6716a56c3b?s=100&r=x&d=retro
var httpUrl = gravatar.url('emerleite@gmail.com', {protocol: 'http', s: '100'});
//returns http://www.gravatar.com/avatar/93e9084aa289b7f1f5e4ab6716a56c3b?s=100
var httpsUrl = gravatar.url('emerleite@gmail.com', {protocol: 'https', s: '100'});
//returns https://s.gravatar.com/avatar/93e9084aa289b7f1f5e4ab6716a56c3b?s=100
var profile1 = gravatar.profile_url('emerleite@gmail.com', {protocol: 'https'});
//returns https://secure.gravatar.com/93e9084aa289b7f1f5e4ab6716a56c3b.json
var profile2 = gravatar.profile_url('emerleite@gmail.com', {protocol: 'http', format:'qr'});
//returns http://www.gravatar.com/93e9084aa289b7f1f5e4ab6716a56c3b.qr
const gravatar = require('gravatar');
var avatars = gravatar.url(req.body.email, { s: '200', r: 'pg', d: 'mm' });
这样的话它会生成一个网址,只要你的邮箱在www.gravatar.com里注册过的化就会有一个头像
npm i jsonwebtoken
const jwt = require('jsonwebtoken')s
const rule = { id: result[0].id, name: result[0].name }
由于id,和name都是通过数据库获取过来的,所以用的这种方式
jwt.sign("规则", "加密名字", "过期时间", "箭头函数")
jwt.sign(rule, key.secretOrkey, { expiresIn: 3600 }, (err, token) => {
if (err) return err.message
return res.send({
code: 1,
msg: `Bearer ${token}`
})
})
这里的rule就是上面所说的校验规则,key.secretOrkey就是加密名字,只不过封装到了外部,expiresIn:3600,就说明是3600秒,msg返回的就是生成的Token
// $router get api/user/current
// @desc return current user
// @access private
router.get("/current", "验证Token", (req, res) => {
res.send({
code: 1,
msg: '验证成功'
})
})
我们需要在"验证Token"那里对Token进行验证
npm install passport-jwt
npm i passport
passport
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return done(null, user);
});
}
));
这个方法中的User.findOne方法可以删除,写上自己的业务逻辑,我用的是mysql,所以没有用User.findeOne方法
初始化passport
app.use(passport.initialize());
passport-jwt
new JwtStrategy(options, verify)
index.js(项目入口文件)
const passport = require("passport")
//在入口文件中配置和引用
//初始化passport
app.use(passport.initialize());
require("./config/passport")(passport)
这里单独抽离了passport配置文件,同时将上面引入的passprot进行引入
config/passport.js
const db = require("../db/index")
const JwtStrategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt
const key = require("../config/key")
const opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = key.secretOrkey;
module.exports = passport => {
passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
// { id: 1, name: 'wz', iat: 1660299838, exp: 1660303438 }
console.log(jwt_payload);
sqlcc = `SELECT * FROM user WHERE id=${jwt_payload.id};`
db.query(sqlcc, (err, result) => {
console.log(result[0]);
if (err) {
return done(null, err.message)
}
if (result.length != 0) {
return done(null, result[0])
} else {
return done(null, false)
}
})
}));
}
注意,生成的token应该带有统一的格式。一个正确的token应该是这样的
Bearer ${token}
,注意Bearer和生成的Token之间的空格。
接下来在需要校验的地方验证Token
// $router get api/user/current
// @desc return current user
// @access private
router.get("/current", passport.authenticate("jwt", { session: false }), (req, res) => {
res.send({
code: 1,
msg: req.user
})
})
注意,在需要使用的路由中,添加第二个参数
passport.authenticate("jwt", { session: false })
验证token如果通过,就会执行passport.use
里的方法,同时jwt_jpayload会输出结果,得到结果后,我们在用id在User表中查询数据,然后通过req.user(数据库表名)拿到。// { id: 1, name: 'wz', iat: 1660299838, exp: 1660303438 } console.log(jwt_payload);
npm install jsonwebtoken express-jwt
注意
jsonwebtoken用于生成JWT字符串
express-jwt用于将Jwt字符串解析为JSON对象
module.exports = {
keyOringe: "secret"
}
将设置的key值暴露出去
const jwt = require("jsonwebtoken")
if (password == tools.setPassword(results[0].password)) {
//如果成功,传1,页面跳转交给vue
+ const rule = {
+ id: results[0].id,
+ adminname: results[0].adminname
+ }
+ const tokenstr = jwt.sign(rule, key.keyOringe, { expiresIn: 3600 })
+ res.send({
code: 1,
msg: '登陆成功',
+ token: tokenstr
})
}
- 调用 jwt.sign() 方法生成JWT字符串 并通过 token 属性发送给客户端
- 参数1: 用户的信息对象
- 参数2: 加密的密钥
- 参数3: 配置对象 可以配置当前 token 的有效期
- rule只是抽离出去,其实直接写也没问题
在中间件中使用
const { expressjwt: jwt } = require("express-jwt")
const key = require('../config/key')
router.use(jwt({
secret: key.keyOringe,
algorithms: ["HS256"],
}).unless({ path: ["/admin/login", "/admin/register"] })
)
之后使用私有接口去访问
//$route GET admin/banner/
//@desc 修改轮播图接口
router.get('/', (req, res) => {
console.log(req);
res.send({
code: 1,
msg: '获取用户信息成功',
data: req.auth
})
})
这样就会返回带有用户信息的Token了!
捕获解析JWT失败后产生的错误
当使用express-jwt解析Token字符串时,如果客户端发送的Token字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行,可以通过Express的错误中间件,捕获这个错误并且进行相关的处理,示例代码如下:
//在所有路由后面定义错误中间件
//使用全局错误处理中间件 捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
//判断是否由 Token 解析失败导致的
if (err.name == 'UnauthorizedError') {
return res.send({
status: 401,
message: '无效的Token'
})
}
res.send({
status: 500,
message: '未知的错误'
})
})
传递单个接口信息使用动态路由传递id,使用req.params获取值
router.get("/query/:id", (req, res) => {
console.log(req.params);
const { id } = req.params
const sqlcc = 'xxx'
db.query(sqlcc, (err, result) => {
if (err) {
return res.send({
code: 0,
msg: err.message
})
}
if (result.length == 0) {
res.send({
code: 0,
msg: "查询失败"
})
} else {
res.send({
code: 1,
msg: "查询成功",
data: result
})
}
})
})
vue create milinku
npm i concurrently
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"start": "npm run serve"
},
"scripts": {
+ "milinku-install": "npm install --prefix milinku",
+ "mulinku": "npm start --prefix milinku",
"start": "node index.js",
"serve": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1",
+ "dev": "concurrently \"npm run serve\" \"npm run mulinku\""
},
npm install --prefix milinku
这行命令是指定安装依赖包到milinku目录下
npm start --prefix milinku
这行命令是启动指定目录的目录,由于之前在mulinku/package.json中设置了
"start": "npm run serve"
,所以这里其实就是启动了mulinku项目.
使用currently实现连载
"dev": "concurrently \"npm run serve\" \"npm run mulinku\""
其中npm run serve
是启动前台,npm run mulinku
是启动后台
在项目中添加相关样式和相关代码和组件后,引入到vue-router
import NotFound from '@/views/404'
{
path: '*',
name: '/404',
component: NotFound
},
这样在匹配不到任何路由的时候就会找到当前的404组件
mounted() {
let data = new Date();
Datestr = `${data.getFullYear()}-${data.getMonth() + 1}-${data.getDate()}`;
this._data.registerUser.date = Datestr;
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert("submit!");
} else {
console.log("error submit!!");
return false;
}
});
},
注意这里的forName就是表单的ref:
ref="ruleForm"
,这个方法会对表单进行校验.
npm i axios
import axios from './config/axios'
Vue.prototype.$axios = axios
@/config/axios.js 在该文件中编写代码
import axios from 'axios'
import { Loading,Message } from 'element-ui';
let loading;
function startLoading() {
loading = Loading.service(
{
lock: true,
text: '拼命加载中...',
background: 'rgba(0,0,0,.7)'
}
);
}
function endLoading() {
loading.close();
}
//请求拦截
//响应拦截
export default axios
引入加载插件,定义了两个方法,一个用来开启动画,一个用来结束动画,
lock
:true是锁定页面,用户无法操作.text
:加载动画时,显示的文本.background
:加载动画时的背景.
//请求拦截
axios.interceptors.request.use(config => {
//加载动画
startLoading();
return config;
}, error => {
return Promise.reject(error);
})
//响应拦截
axios.interceptors.response.use(response => {
//结束加载动画
endLoading();
return response;
}, error => {
//错误提醒
endLoading();
Message.error(error.response.data)
return Promise.reject(error)
})
这里使用了element-ui的Message模块,用来给用户反馈请求结果
在vue.config.js里进行配置
devServer: {
proxy: {
//配置跨域
'/api': {
target: 'http://127.0.0.1:5100/api/',
ws: true,
changOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
拿到后台传过来的token,就先保存到localStorage
里
localStorage.setItem("milink", msg);
msg里的就是token信息
使用localStorage.milink
获取token,milink是我们之前设置过的
在router/index.js里进行配置全局路由守卫
//路由守卫
router.beforeEach((to, from, next) => {
const isLogin = localStorage.milink ? true : false
if (to.path == '/login' || to.path == '/register') {
next();
} else {
isLogin ? next() : next("/login")
}
})
这时,如果有名称为milink的token,就可以通过,否则就会重新定向到"/login"路由.
//请求拦截
axios.interceptors.request.use(config => {
//加载动画
startLoading();
+ if (localStorage.milink) {
+ //设置统一的请求头
+ config.headers.Authorization = localStorage.milink
}
return config;
}, error => {
return Promise.reject(error);
})
//获取错误状态码
const { status } = error.response;
+ if (status == 401) {
+ //token失效
+ Message.error("Token失效,请重新登陆")
+ //清除token
+ localStorage.removeItem('milink');
+ //跳转到登陆页面
+ router.push('/login')
}
注意:使用router的前提是引入router
npm i jwt-decode
jwt-decode
import jwt from "jwt-decode";
const decode = jwt(msg);
输出msg
{
avatar: "//www.gravatar.com/avatar/bf58432148b643a8b4c41c3901b81d1b?s=200&r=pg&d=mm"
exp: 1660471454 //生成时间
iat: 1660467854 //失效时间
id: 11
identity: "employee"
name: "test"
}
这里设计到了数据的校验,所以可能不太一样. 可以去store/index.js里去看代码
isEmpty(value) {
return (
value == undefined ||
value == null ||
(typeof value == "object" && Object.keys(value).length === 0) ||
(typeof value == "string" && value.trim().length === 0)
);
},
这些小方法可以复用,挺灵活
2.将解析出来的值添加到Vuex中
this.$store.dispatch("setAuthenticated", !this.isEmpty(decode));
this.$store.dispatch("setUser", decode);
created() {
if (localStorage.milink) {
const decode = jwt(localStorage.milink);
this.$store.dispatch("setAuthenticated", !isEmpty(decodethis));
this.$store.dispatch("setUser", decode);
}
},
将该方法挂载到create函数中.
logout() {
//清除token
localStorage.removeItem("milink");
//设置store
this.$store.dispatch("clearCurrentState");
//跳转
this.$router.push("/login");
},
清除token,然后设置store,将状态值设置为空。
clearCurrentState: ({ commit }) => {
commit(types.SET_AUTHENTICATED, false);
commit(types.SET_USER, null)
}
在前台将vuex里的数据清除
Navigation
导航栏 导航栏中的数据使用v-for指令渲染到页面上。
表单 使用模板表单
首先要有
配置表格的列
<el-table-column prop="type" label="收支类型" width="150"> </el-table-column>
注意这里prop属性,对应的是请求回来数据的属性名,这样才能显示出来。
getProfile() {
this.$axios
.get("/api/profile/query")
.then((res) => {
this.tableData = res.data.data;
console.log(res);
})
.catch((err) => {
console.log(err);
});
},
将数据保存到tableData[]
里
<el-table-column prop="date" label="创建时间" width="250">
<template slot-scope="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px; color: #f56767;">{{ scope.row.date }}</span>
</template>
</el-table-column>
在该案例中,我们可以根据自己的需要指定相关模板,这里就设置了字体图标,同时通过style可以设置对应的颜色。
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/index/home' }"
>首页</el-breadcrumb-item
>
<el-breadcrumb-item>资金管理</el-breadcrumb-item>
<el-breadcrumb-item>资金流水</el-breadcrumb-item>
</el-breadcrumb>
同时可以配置跳转路由,都可以去element-ui上去找到这些配置
<el-dialog
title="添加订单"
:visible.sync="dialogVisible.show"
width="30%"
:close-on-click-modal="false"
:before-close="handleClose"
>
//表单内容
</el-dialog>
props: {
dialogVisible: Object,
},
参数的话我们定义为Object类型,同时在父组件Fundlist.vue中,将其引入并注册,同时将值传入当前组件中,从而达到显示于隐藏效果。
<Dialog :dialogVisible="dialog"></Dialog>
data() {
return {
dialog: {
show: false,
},
};
},
描述,也就是说在添加成功后,让表单自动刷新一次。
子组件中使用自定义事件,让父组件中的方法去执行.
//自定义事件,让表单重新刷新
this.$emit("update");
<Dialog :dialogVisible="dialog" @update="getProfile"></Dialog>
执行指定方法,从而让表单刷新。
这里在父组件中传递过相关参数后,子组件根据不同的选项进行渲染,主要体现在两方面。
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage4"
:page-sizes="[100, 200, 300, 400]"
:page-size="100"
layout="total, sizes, prev, pager, next, jumper"
:total="400">
</el-pagination>
这里的写死的参数都可以在data里定义后传上去的。有两个方法:
handleSizeChange
,handleCurrentChange
allTableData
,我们在allTableData中使用filter方法过滤数据后,赋给TableData
在表格中渲染出来。想要让数据显示出来,1.设置盒子宽度高度.2.给数据。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。