├── backend ├── README.md ├── config.js ├── routes │ ├── index.js │ ├── articles.js │ └── users.js ├── wares │ └── methods.js ├── .gitignore ├── models │ ├── article.js │ ├── index.js │ └── user.js ├── package.json ├── app.js └── utils │ └── jwt.js ├── frontend ├── README.md ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── src │ ├── utils │ │ └── jwt.js │ ├── history.js │ ├── containers │ │ ├── Home.js │ │ ├── App.js │ │ ├── Signin.js │ │ ├── Signup.js │ │ └── AddArticle.js │ ├── store │ │ ├── actions │ │ │ ├── article.js │ │ │ └── user.js │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── article.js │ │ │ └── user.js │ │ ├── sagas │ │ │ ├── index.js │ │ │ ├── loaduser.js │ │ │ ├── article.js │ │ │ ├── register.js │ │ │ └── login.js │ │ ├── index.js │ │ └── action-types.js │ ├── api │ │ ├── article.js │ │ ├── user.js │ │ └── index.js │ ├── index.js │ └── componets │ │ └── Header.js ├── .gitignore └── package.json └── .gitignore /backend/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | ### 用户需要从数据库中添加 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | .idea -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yjdjiayou/jwt-demo/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/utils/jwt.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | 3 | export function decode(token) { 4 | return jwt.decode(token); 5 | } -------------------------------------------------------------------------------- /backend/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | PORT: 8080, 3 | DB_URL: 'mongodb://localhost:27017/UserSystem', 4 | SECRET: '666' 5 | }; -------------------------------------------------------------------------------- /frontend/src/history.js: -------------------------------------------------------------------------------- 1 | import {createBrowserHistory} from 'history'; 2 | 3 | let history = createBrowserHistory(); 4 | export default history; -------------------------------------------------------------------------------- /frontend/src/containers/Home.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | export default class Home extends Component { 4 | render() { 5 | return
Home
6 | } 7 | } -------------------------------------------------------------------------------- /backend/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | router.get('/', function (req, res) { 4 | res.send('hello'); 5 | }); 6 | module.exports = router; -------------------------------------------------------------------------------- /frontend/src/store/actions/article.js: -------------------------------------------------------------------------------- 1 | import * as types from '../action-types'; 2 | 3 | export default { 4 | addArticle(payload) { 5 | return {type: types.ADD_ARTICLE, payload}; 6 | } 7 | } -------------------------------------------------------------------------------- /frontend/src/api/article.js: -------------------------------------------------------------------------------- 1 | import {post} from './index'; 2 | 3 | function addArticle(body) { 4 | return post('/articles/add', body).then(response => response.data); 5 | } 6 | 7 | export default { 8 | addArticle 9 | } -------------------------------------------------------------------------------- /frontend/src/store/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | import user from './user'; 3 | import article from './article'; 4 | import {routerReducer} from 'react-router-redux'; 5 | 6 | let reducers = combineReducers({ 7 | user, 8 | article, 9 | router: routerReducer 10 | }); 11 | export default reducers; -------------------------------------------------------------------------------- /frontend/src/api/user.js: -------------------------------------------------------------------------------- 1 | import {post} from './index'; 2 | 3 | function login(body) { 4 | return post('/users/signin', body).then(response => response.data); 5 | } 6 | 7 | function register(body) { 8 | return post('/users/signup', body).then(response => response.data); 9 | } 10 | 11 | export default { 12 | login, 13 | register 14 | } -------------------------------------------------------------------------------- /backend/wares/methods.js: -------------------------------------------------------------------------------- 1 | module.exports = () => (req, res, next) => { 2 | res.success = function (data) { 3 | res.json({ 4 | code: 0, 5 | data 6 | }); 7 | }; 8 | res.error = function (error) { 9 | res.json({ 10 | code: 1, 11 | error 12 | }); 13 | }; 14 | next(); 15 | }; -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | .idea 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | .idea 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /backend/models/article.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | let connection = require('./index'); 3 | let define = { 4 | title: {type: String, required: true}, 5 | content: String 6 | }; 7 | let ArticleSchema = new mongoose.Schema(define, {timestamps: true}); 8 | // 增加文章和查看文章列表 9 | // 查看文章必须登录后才能看 增加文章必须管理员才能增加 10 | let Article = connection.model("Article", ArticleSchema); 11 | module.exports = Article; -------------------------------------------------------------------------------- /frontend/src/store/sagas/index.js: -------------------------------------------------------------------------------- 1 | import {takeEvery, put, call, all, race} from 'redux-saga/es/effects'; 2 | import {loginFlow} from "./login"; 3 | import {watchLoadUser} from "./loaduser"; 4 | import {watchAddArticle} from "./article"; 5 | import {watchRegister} from "./register"; 6 | 7 | 8 | export default function* rootSaga() { 9 | yield all([loginFlow(), watchRegister(), watchLoadUser(), watchAddArticle()]); 10 | } -------------------------------------------------------------------------------- /frontend/src/store/actions/user.js: -------------------------------------------------------------------------------- 1 | import * as types from '../action-types'; 2 | 3 | export default { 4 | login(payload) { 5 | return {type: types.LOGIN, payload}; 6 | }, 7 | logout() { 8 | return {type: types.LOGOUT}; 9 | }, 10 | register(payload) { 11 | return {type: types.REGISTER, payload}; 12 | }, 13 | loadUser() { 14 | return {type: types.LOAD_USER}; 15 | } 16 | } -------------------------------------------------------------------------------- /backend/models/index.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const {DB_URL} = require('../config'); 3 | mongoose.set('useCreateIndex', true); 4 | const db = mongoose.createConnection(DB_URL, {useNewUrlParser: true, useUnifiedTopology: true}); 5 | 6 | db.on('error', () => { 7 | console.log('Mongoose connection error') 8 | }); 9 | 10 | db.on('connected', () => { 11 | console.log('Mongoose connection success') 12 | }); 13 | 14 | module.exports = db; -------------------------------------------------------------------------------- /frontend/src/store/reducers/article.js: -------------------------------------------------------------------------------- 1 | import * as types from "../action-types"; 2 | 3 | let initState = {error: null}; 4 | export default function (state = initState, action) { 5 | switch (action.type) { 6 | case types.ADD_ARTICLE_FAIL: 7 | return {...state, error: action.error}; 8 | case types.ADD_ARTICLE_SUCCESS: 9 | return {...state, error: null}; 10 | default: 11 | return state; 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon ./app.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "cors": "^2.8.4", 15 | "express": "^4.16.3", 16 | "jsonwebtoken": "^8.3.0", 17 | "mongoose": "^5.2.1", 18 | "morgan": "^1.9.0" 19 | } 20 | } -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.css' 2 | import React, {Componnet} from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import {Provider} from 'react-redux'; 5 | import store from './store'; 6 | import App from './containers/App'; 7 | import {ConnectedRouter} from 'react-router-redux'; 8 | import history from './history'; 9 | 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | , document.querySelector('#root') 17 | ); -------------------------------------------------------------------------------- /frontend/src/store/sagas/loaduser.js: -------------------------------------------------------------------------------- 1 | import {takeEvery, put, call, all} from 'redux-saga/es/effects'; 2 | import * as types from '../action-types'; 3 | import {decode} from '../../utils/jwt'; 4 | import {push} from 'react-router-redux'; 5 | 6 | function* loadUser() { 7 | let jwtToken = window.localStorage.getItem('jwtToken'); 8 | if (jwtToken) { 9 | let user = decode(jwtToken); 10 | yield put({ type: types.LOGIN_SUCCESS, user }); 11 | yield put(push('/')); 12 | } 13 | } 14 | export function* watchLoadUser() { 15 | yield takeEvery(types.LOAD_USER, loadUser); 16 | } -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import {createStore, applyMiddleware} from 'redux'; 2 | import reducers from './reducers'; 3 | import createSagaMiddleware from 'redux-saga'; 4 | import logger from 'redux-logger'; 5 | import rootSaga from './sagas'; 6 | import {routerMiddleware} from 'react-router-redux'; 7 | import history from '../history'; 8 | 9 | let router = routerMiddleware(history); 10 | let sagaMiddleware = createSagaMiddleware(); 11 | let store = createStore(reducers, applyMiddleware(sagaMiddleware, router, logger)); 12 | sagaMiddleware.run(rootSaga); 13 | window.store = store; 14 | export default store; -------------------------------------------------------------------------------- /frontend/src/store/sagas/article.js: -------------------------------------------------------------------------------- 1 | import {takeEvery, put, call, all} from 'redux-saga/es/effects'; 2 | import * as types from '../action-types'; 3 | import articleApi from '../../api/article'; 4 | import {push} from 'react-router-redux'; 5 | 6 | function* addArticle(action) { 7 | let { payload } = action;//{title,content} 8 | try { 9 | let response = yield call(articleApi.addArticle, payload); 10 | yield put({ type: types.ADD_ARTICLE_SUCCESS }); 11 | yield put(push('/')); 12 | } catch (error) { 13 | yield put({ type: types.ADD_ARTICLE_FAIL, error }); 14 | } 15 | } 16 | export function* watchAddArticle() { 17 | yield takeEvery(types.ADD_ARTICLE, addArticle); 18 | } -------------------------------------------------------------------------------- /frontend/src/store/action-types.js: -------------------------------------------------------------------------------- 1 | // 发出登录请求 2 | export const LOGIN = 'LOGIN'; 3 | // 登录成功之后 4 | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; 5 | // 登录失败 6 | export const LOGIN_FAIL = 'LOGIN_FAIL'; 7 | 8 | // 发出退出请求 9 | export const LOGOUT = 'LOGOUT'; 10 | // 退出成功之后 11 | export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; 12 | 13 | // 注册用户 14 | export const REGISTER = 'REGISTER'; 15 | // 注册成功 16 | export const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; 17 | // 注册失败 18 | export const REGISTER_FAIL = 'REGISTER_FAIL'; 19 | 20 | 21 | // 从 localStorage 中加载用户 22 | export const LOAD_USER = 'LOAD_USER'; 23 | 24 | // 增加文章 25 | export const ADD_ARTICLE = 'ADD_ARTICLE'; 26 | export const ADD_ARTICLE_SUCCESS = 'ADD_ARTICLE_SUCCESS'; 27 | export const ADD_ARTICLE_FAIL = 'ADD_ARTICLE_FAIL'; -------------------------------------------------------------------------------- /backend/routes/articles.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const Article = require('../models/article'); 3 | const jwt = require('../utils/jwt'); 4 | const router = express.Router(); 5 | 6 | // /aritcles/list 查看文章列表 7 | router.get('/list', jwt.verify(), async (req, res) => { 8 | try { 9 | let articles = await Artcle.find(); 10 | res.success(articles); 11 | } catch (error) { 12 | res.error(error); 13 | } 14 | }); 15 | 16 | // /aritcles/add 增加一个新的文章 17 | router.post('/add', jwt.verify(true), async (req, res) => { 18 | let article = new Article(req.body); 19 | try { 20 | await article.save(); 21 | res.success(article); 22 | } catch (error) { 23 | res.error(error); 24 | } 25 | }); 26 | 27 | module.exports = router; -------------------------------------------------------------------------------- /frontend/src/store/reducers/user.js: -------------------------------------------------------------------------------- 1 | import * as types from "../action-types"; 2 | 3 | let initState = {user: null, error: null}; 4 | 5 | export default function (state = initState, action) { 6 | switch (action.type) { 7 | case types.LOGIN_SUCCESS: 8 | return {...state, user: action.user, error: null}; 9 | case types.LOGIN_FAIL: 10 | return {...state, user: null, error: action.error}; 11 | case types.LOGOUT_SUCCESS: 12 | return {...state, user: null, error: null}; 13 | case types.REGISTER_SUCCESS: 14 | return {...state, error: null}; 15 | case types.REGISTER_FAIL: 16 | return {...state, user: null, error: action.error}; 17 | default: 18 | return state; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.0", 7 | "bootstrap": "^3.3.7", 8 | "history": "^4.7.2", 9 | "jsonwebtoken": "^8.3.0", 10 | "react": "^16.4.1", 11 | "react-dom": "^16.4.1", 12 | "react-redux": "^5.0.7", 13 | "react-router-dom": "^4.3.1", 14 | "react-router-redux": "^5.0.0-alpha.9", 15 | "redux": "^4.0.0", 16 | "redux-logger": "^3.0.6", 17 | "redux-saga": "^0.16.0" 18 | }, 19 | "devDependencies": { 20 | "react-scripts": "1.1.4" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test --env=jsdom", 26 | "eject": "react-scripts eject" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/store/sagas/register.js: -------------------------------------------------------------------------------- 1 | import {takeEvery, put, call, all, race} from 'redux-saga/es/effects'; 2 | import * as types from '../action-types'; 3 | import userApi from '../../api/user'; 4 | import {push} from 'react-router-redux'; 5 | 6 | function* register(action) { 7 | let { payload } = action; 8 | try { 9 | let response = yield call(userApi.register, payload); 10 | let username = response.data.username; 11 | if(username){ 12 | yield put({ type: types.REGISTER_SUCCESS}); 13 | yield put(push('/users/signin')); 14 | } 15 | } catch (error) { 16 | yield put({ type: types.REGISTER_FAIL, error }); 17 | } 18 | } 19 | 20 | export function* watchRegister() { 21 | yield takeEvery(types.REGISTER, register); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /backend/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const {PORT} = require('./config'); 3 | const morgan = require('morgan'); 4 | //跨域资源共享 5 | const cors = require('cors'); 6 | const indexRouter = require('./routes/index'); 7 | const usersRouter = require('./routes/users'); 8 | const articlesRouter = require('./routes/articles'); 9 | const methods = require('./wares/methods'); 10 | 11 | const app = express(); 12 | 13 | // 跨域插件 14 | app.use(cors()); 15 | // 写日志的中间件,每当接收到客户端的请求后,会向控制台打印日志 16 | app.use(morgan('dev')); 17 | app.use(express.json()); 18 | app.use(express.urlencoded({extended: true})); 19 | app.use(methods()); 20 | // 如果访问根路径的话交由 indexRouter 处理 21 | app.use('/', indexRouter); 22 | // 如果访问的是 /users 路径的话交由 usersRouter 处理 23 | app.use('/users', usersRouter); 24 | app.use('/articles', articlesRouter); 25 | app.listen(PORT, () => console.log(`服务已经在${PORT}上启动`)); 26 | -------------------------------------------------------------------------------- /frontend/src/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import history from '../history'; 3 | 4 | const BASE_URL = 'http://localhost:8080'; 5 | 6 | //拦截器,在向后台发出请求的时候,可以动态的修改配置 7 | // $.ajax({url,method,headers}) 8 | axios.interceptors.request.use((config) => { 9 | let jwtToken = window.localStorage.getItem('jwtToken'); 10 | if (jwtToken) { 11 | config.headers.authorization = jwtToken; 12 | } 13 | return config; 14 | }); 15 | 16 | axios.interceptors.response.use(res => { 17 | if (res.data.code !== 0) { 18 | return Promise.reject(res.data.error); 19 | } 20 | return res; 21 | }, error => { 22 | if (error.response.status >= 400 && error.response.status > 500) { 23 | history.push('/users/login'); 24 | } 25 | return Promise.reject(error.response.data.error); 26 | }); 27 | 28 | export function post(url, body) { 29 | return axios.post(BASE_URL + url, body); 30 | } -------------------------------------------------------------------------------- /frontend/src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component, Fragment} from 'react'; 2 | import {Link, Route} from 'react-router-dom'; 3 | import Signup from './Signup'; 4 | import Signin from './Signin'; 5 | import Home from './Home'; 6 | import AddArticle from './AddArticle'; 7 | import Header from '../componets/Header'; 8 | 9 | export default class App extends Component { 10 | render() { 11 | return ( 12 | 13 |
14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /frontend/src/store/sagas/login.js: -------------------------------------------------------------------------------- 1 | import {takeEvery, put, call, all} from 'redux-saga/es/effects'; 2 | import * as types from '../action-types'; 3 | import userApi from '../../api/user'; 4 | import {decode} from '../../utils/jwt'; 5 | import {push} from 'react-router-redux'; 6 | 7 | function* login(action) { 8 | let { payload } = action; 9 | try { 10 | let response = yield call(userApi.login, payload); 11 | let jwtToken = response.data.jwtToken; 12 | // 把得到的 token 保存在本地硬盘上 13 | window.localStorage.setItem('jwtToken', jwtToken); 14 | let user = decode(jwtToken); 15 | yield put({ type: types.LOGIN_SUCCESS, user }); 16 | yield put(push('/')); 17 | } catch (error) { 18 | yield put({ type: types.LOGIN_FAIL, error }); 19 | 20 | } 21 | } 22 | 23 | function* logout() { 24 | window.localStorage.removeItem('jwtToken'); 25 | yield put({ type: types.LOGOUT_SUCCESS }); 26 | yield put(push('/users/signin')) 27 | } 28 | 29 | export function* loginFlow() { 30 | yield takeEvery(types.LOGIN, login); 31 | yield takeEvery(types.LOGOUT, logout); 32 | } -------------------------------------------------------------------------------- /backend/routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const User = require('../models/user'); 3 | const jwt = require('../utils/jwt'); 4 | const router = express.Router(); 5 | 6 | // 用户注册,当用户以 POST 方式向服务器提交 /users/signup 请求的时候 7 | router.post('/signup', async (req, res) => { 8 | let user = new User(req.body); 9 | try { 10 | await user.save(); 11 | res.success({username: user.username}); 12 | } catch (error) { 13 | res.error(error); 14 | } 15 | }); 16 | 17 | router.post('/signin', async (req, res) => { 18 | let user = req.body; 19 | try { 20 | let doc = await User.findOne({username: user.username},(err,doc)=>{ 21 | console.log('err=>',err); 22 | console.log('doc=>',doc); 23 | }); 24 | if (doc && doc.comparePassword(user.password)) { 25 | let jwtToken = jwt.sign({id: doc._id, username: doc.username, admin: doc.admin}); 26 | res.success({jwtToken}); 27 | } else { 28 | res.error('用户名或密码错误!'); 29 | } 30 | } catch (error) { 31 | res.error(error); 32 | } 33 | }); 34 | 35 | module.exports = router; -------------------------------------------------------------------------------- /backend/utils/jwt.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const {SECRET} = require('../config'); 3 | 4 | function sign(payload) { 5 | return jwt.sign(payload, SECRET, { 6 | expiresIn: 600 //单位是秒 7 | }); 8 | } 9 | 10 | // mustAdmin 是否必须是管理员才能访问 11 | let verify = (mustAdmin) => (req, res, next) => { 12 | // 取得客户端发过来的 token 13 | let jwtToken = req.headers.authorization; 14 | if (jwtToken) { 15 | jwt.verify(jwtToken, SECRET, (err, payload) => { 16 | //1. token 验证失败 17 | //2. 验证成功但是 token 过期了 18 | if (err) { 19 | if (err.name === 'TokenExpiredError') { 20 | res.status(401).error('token已经过期'); 21 | } else { 22 | res.status(401).error('token是无效的'); 23 | } 24 | } else { 25 | // 如果说要求必须管理员才能继续,那么还要看此登录的用户是不是管理员 26 | if (mustAdmin) { 27 | let {admin} = payload; 28 | if (admin) { 29 | next(); 30 | } else { 31 | res.status(401).error('你不是管理员!无权执行此操作!'); 32 | } 33 | } else { 34 | next(); 35 | } 36 | } 37 | }) 38 | } else { 39 | res.status(401).error('请提供jwtToken'); 40 | } 41 | }; 42 | 43 | module.exports = { 44 | sign, 45 | verify 46 | }; -------------------------------------------------------------------------------- /frontend/src/containers/Signin.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import actions from '../store/actions/user'; 4 | 5 | class Signin extends Component { 6 | handleSubmit = (event) => { 7 | event.preventDefault(); 8 | let username = this.username.value; 9 | let password = this.password.value; 10 | let user = {username, password}; 11 | this.props.login(user); 12 | }; 13 | 14 | render() { 15 | return ( 16 | 17 |

登录

18 |
19 |
20 |
21 | 22 | this.username = input}/> 23 |
24 |
25 | 26 | this.password = input}/> 27 |
28 |
29 | 30 |
31 |
32 |
33 | ) 34 | } 35 | } 36 | 37 | export default connect( 38 | state => state.user, 39 | actions 40 | )(Signin); -------------------------------------------------------------------------------- /frontend/src/containers/Signup.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import actions from '../store/actions/user'; 4 | 5 | 6 | class Signup extends Component { 7 | handleSubmit = (event) => { 8 | event.preventDefault(); 9 | let username = this.username.value; 10 | let password = this.password.value; 11 | let user = {username, password, admin: true}; 12 | this.props.register(user); 13 | }; 14 | 15 | render() { 16 | return ( 17 | 18 |

注册

19 |
20 |
21 |
22 | 23 | this.username = input}/> 24 |
25 |
26 | 27 | this.password = input}/> 28 |
29 |
30 | 31 |
32 |
33 |
34 | ) 35 | } 36 | } 37 | 38 | export default connect( 39 | state => state.user, 40 | actions 41 | )(Signup); -------------------------------------------------------------------------------- /frontend/src/componets/Header.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {Link, Route} from 'react-router-dom'; 4 | import actions from '../store/actions/user'; 5 | 6 | class Header extends Component { 7 | componentDidMount() { 8 | this.props.loadUser(); 9 | } 10 | 11 | render() { 12 | const {user,logout} = this.props; 13 | return ( 14 | 36 | ) 37 | } 38 | } 39 | 40 | export default connect( 41 | state => state.user, actions 42 | )(Header); -------------------------------------------------------------------------------- /frontend/src/containers/AddArticle.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import actions from '../store/actions/article'; 4 | 5 | class AddArticle extends Component { 6 | handleSubmit = (event) => { 7 | event.preventDefault(); 8 | let title = this.title.value; 9 | let content = this.content.value; 10 | let article = {title, content}; 11 | this.props.addArticle(article); 12 | }; 13 | 14 | render() { 15 | const {error} = this.props; 16 | return ( 17 |
18 |
19 | 20 | this.title = input}/> 21 |
22 |
23 | 24 | this.content = input}/> 25 |
26 |
27 | 28 |
29 | { 30 | error &&
31 |
32 | {error.toString()} 33 |
34 |
35 | } 36 |
37 | ) 38 | } 39 | } 40 | 41 | export default connect( 42 | state => state.article, 43 | actions 44 | )(AddArticle); -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /backend/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | let connection = require('./index'); 3 | const bcrypt = require('bcryptjs'); 4 | 5 | let define = { 6 | // 保存文档的时候要检查此用户名是否唯一 7 | username: {type: String, unique: true}, 8 | password: String, 9 | // 如果 admin 为 true 表示是管理员,如果为 false 则不是管理员 10 | admin: {type: Boolean, default: false} 11 | }; 12 | 13 | // Scheme 没有操作数据库的能力 14 | let UserSchema = new mongoose.Schema(define, {timestamps: true}); 15 | 16 | // 这种机制也类似于 express 中的中间件 17 | // 在保存之前执行一个函数 18 | UserSchema.pre('save', function (next) { 19 | // 第一步先生成盐值 20 | bcrypt.genSalt((err, salt) => { 21 | // 通过原始的密码和盐值计算哈希值 22 | bcrypt.hash(this.password, salt, (err, hash) => { 23 | this.password = hash; 24 | next(); 25 | }) 26 | }); 27 | }); 28 | 29 | // 通过给 methods 增加属性可以给实例扩展方法,让实例直接调用即可 30 | UserSchema.methods.comparePassword = function (passowrd) { 31 | return bcrypt.compareSync(passowrd, this.password); 32 | }; 33 | 34 | let User = connection.model("User", UserSchema); 35 | 36 | /** 37 | * 新增 38 | */ 39 | /** 40 | * 方法一: 41 | */ 42 | // User.create({ username: 'abc', password: 'abc',admin:true}, (err, doc) => { 43 | // console.log(err); 44 | // console.log(doc); 45 | // }); 46 | 47 | // _id 是 mongodb 帮我们生成的一个主键,不会重复,可以用来标识每一个文档 48 | // __v 是内部使用,用来加锁解决并发问题 49 | 50 | /** 51 | * 方法二: 52 | */ 53 | // let user1 = new User({ username: 'abc', password: 'abc'}); 54 | // console.log(user1); 55 | // 调用 save 方法可以把自己保存到数据库里 56 | // user1.save((err, doc) => { 57 | // console.log(err); 58 | // console.log(doc); 59 | // }); 60 | 61 | /** 62 | * 删除 63 | */ 64 | // 第一个参数是删除的条件 65 | // User.remove({ username: 'abc' }, (err, result) => { 66 | // console.log(result); 67 | // // { ok: 1, n: 1 } ok=1 表示操作成功 n=1 表示删除的条数 68 | // // 更新的话只会更新匹配记录的第一条,删除的默认会删除所有的记录 69 | // }); 70 | 71 | User.find({},function(err,doc){ 72 | doc.forEach((item)=>{ 73 | console.log(item.username); 74 | }); 75 | }); 76 | 77 | module.exports = User; --------------------------------------------------------------------------------