├── 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 |
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 |
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 |
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;
--------------------------------------------------------------------------------