前言

我的个人博客样式布局是仿的稀土掘金 ,个人博客线上网址为https://www.maomin.club/ ,也可以百度搜索前端历劫之路 。为了浏览体验,可以用PC浏览器浏览。

本篇文章将分为前台角度与后台角度来分析我是怎么开发的。

前台角度

主要资源

  • react.js
  • ant Design
  • for-editor
  • axios
  • craco-less
  • immutable
  • react-loadable
  • react-redux
  • react-router-dom
  • react-transition-group
  • redux
  • redux-immutable
  • redux-thunk
  • styled-components

模块页面

  1. 首页

  2. 登录注册

  3. 文章详情

  4. 文章评论

  5. 圈子

  6. 写圈子

  7. 搜索页

  8. 权限页

  9. 写文章

项目配置

项目目录

前台搭建项目步骤

一、使用稳定依赖管理工具

推荐你使用淘宝源

npm config set registry https://registry.npm.taobao.org

还有就是搭配依赖管理工具yarn

二、使用官方React脚手架
create-react-app my-project
三、精简项目文件夹

使用脚手架搭建的初始文件夹是这样的。

那么我们需要精简一下。注意原来的App.js我改成App.jsx。因为 React 使用 JSX 来替代常规的 JavaScript,所以用JSX比较好。

下面我们将要编辑几个文件:
src/index.js

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';ReactDOM.render(<App />,document.getElementById('root')
);

public/index.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><link rel="shortcut icon"  href="./bitbug_favicon.ico" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta name="theme-color" content="#FFB90F" /><meta name="keywords" content="前端历劫之路"><meta name="description" content="如何从前端小仙历劫成为一个前端大神呢?这里就有答案。" /><title>前端历劫之路</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body>
</html>

App.jsx文件内的内容什么意思现在可以先不用去关心,可以先放这。

src/App.jsx

// App.jsx
import React from 'react';
import { Provider } from 'react-redux';
import store from './store/';
import Router from './router';
import {BrowserRouter} from 'react-router-dom';
import {Main} from './styled/'
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import { GlobalStyle } from '../src/styled/index';
import HeaderArea from './components/layout/Header';
import './App.less';const Body = () => {return (<div><BrowserRouter><GlobalStyle /><HeaderArea /><Main><Router /></Main></BrowserRouter></div>)
}const App = () => {return (<div><Provider store={store}><TransitionGroup appear={true} ><CSSTransition timeout={10000} classNames='fade'><Body /></CSSTransition></TransitionGroup></Provider></div>)
};export default App;
四、创建文件夹

src目录下分别创建以下几个文件夹

五、安装依赖

dependencies:

  • antd
  • axios
  • for-editor
  • immutable
  • react-loadable
  • react-redux
  • react-router-dom
  • react-transition-group
  • redux
  • redux-immutable
  • redux-thunk
  • styled-components
六、配置自定义主题

按照 配置主题 的要求,自定义主题需要用到类似 less-loader 提供的 less 变量覆盖功能。我们可以引入 craco-less 来帮助加载 less 样式和修改变量。

  1. 首先在src目录下创建一个App.less文件,编辑内容如下:
@import '~antd/dist/antd.less';
  1. 然后在App.jsx内引入App.less文件(上面已经编辑过App.jsx文件的这里不用管)
  2. 然后安装 craco-less 并创建修改 craco.config.js(存放在项目根目录下) 文件如下:
// craco.config.js
const CracoLessPlugin = require('craco-less');
const theme = require ('./theme');module.exports = {plugins: [{plugin: CracoLessPlugin,options: {lessLoaderOptions: {modifyVars: theme.theme,javascriptEnabled: true,},},}],
};
// theme.js
const theme = {'@primary-color': '#FFB90F', // 全局主色'@link-color': '#1890ff', // 链接色'@success-color': '#52c41a', // 成功色'@warning-color': '#faad14', // 警告色'@error-color': '#f5222d', // 错误色'@font-size-base': '14px', // 主字号'@heading-color': 'rgba(0, 0, 0, 0.85)', // 标题色'@text-color': 'rgba(0, 0, 0, 0.65)', // 主文本色'@text-color-secondary': 'rgba(0, 0, 0, 0.45)', // 次文本色'@disabled-color': 'rgba(0, 0, 0, 0.25)', // 失效色'@border-radius-base': '4px', // 组件/浮层圆角'@border-color-base': '#d9d9d9', // 边框色'@box-shadow-base': '0 2px 8px rgba(0, 0, 0, 0.15)' // 浮层阴影
}exports.theme = theme
七、路由懒加载

在router文件夹下创建index.js和routes.js。

routes.js

// routes.js
// 路由配置
import React from 'react';
import {Route } from 'react-router-dom';import {Home,About,Details,Write,Circle,Noauth,Search} from './routes'const APPRouter = () =>(<div><Route exact={true} path="/" component={Home}/><Route exact={true} path="/about/" component={About}/><Route exact={true} path="/details/:id/" component={Details} /><Route exact={true} path="/write" component={Write} /><Route exact={true} path="/circle" component={Circle} /><Route exact={true} path="/noauth" component={Noauth} /><Route exact={true} path="/search" component={Search} /></div>
);export default APPRouter;

index.js

// index.js
// 页面组件
import loadable from '../util/loadable';export const Home = loadable(()=> import('../views/Home/'));
export const About = loadable(()=> import('../views/About/'));
export const Details = loadable(()=> import('../views/Details'));
export const Write = loadable(()=> import('../views/Write'));
export const Circle = loadable(()=> import('../views/Circle'));
export const Noauth = loadable(()=>import('../components/modules/Noauth'))
export const Search = loadable(()=>import('../views/Search'))

在util文件夹下创建一个loadable.js。

loadable.js

// loadable.js
// 懒加载组件
import React from 'react';
import Loadable from 'react-loadable';
import styled from 'styled-components';
import { Spin } from 'antd';const loadingComponent =()=>{return (<Loading><Spin />   </Loading>)
};export default (loader,loading = loadingComponent)=>{return Loadable({loader,loading});
};const Loading = styled.div`text-align: center;margin:50vh 0;
`;
八、全局样式与样式组件

这里我们使用styled-components这个依赖写样式组件,因为在react.js中存在组件样式污染的缘故。
在styled创建一个index.js。

index.js

// index.js
// 全局样式
import styled,{createGlobalStyle} from 'styled-components';export const Content = styled.div`border-radius: 2px;width: 100%;padding:20px;margin:20px 0;border:1px solid #f4f4f4;background:#fff;box-sizing:border-box;
`export const Main = styled.div`position: relative;margin: 100px auto 20px;width: 100%;max-width: 960px;
`;
export const GlobalStyle = createGlobalStyle`
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video{margin: 0;padding: 0;border: 0;font-size: 100%;font: inherit;font-weight: normal;vertical-align: baseline;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section{display: block;
}
ol, ul, li{list-style: none;
}
blockquote, q{quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after{content: '';content: none;
}
table{border-collapse: collapse;border-spacing: 0;
}
a{color: #7e8c8d;text-decoration: none;-webkit-backface-visibility: hidden;
}
::-webkit-scrollbar{width: 5px;height: 5px;
}
::-webkit-scrollbar-track-piece{background-color: rgba(0, 0, 0, 0.2);-webkit-border-radius: 6px;
}
::-webkit-scrollbar-thumb:vertical{height: 5px;background-color: rgba(125, 125, 125, 0.7);-webkit-border-radius: 6px;
}
::-webkit-scrollbar-thumb:horizontal{width: 5px;background-color: rgba(125, 125, 125, 0.7);-webkit-border-radius: 6px;
}
html, body{width: 100% !important;background:#E8E8E8;font-size: 12px;font-family: Avenir,-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji,sans-serif;
}
body{line-height: 1;-webkit-text-size-adjust: none;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
html{overflow-y: scroll;
}
.clearfix:before,
.clearfix:after{content: " ";display: inline-block;height: 0;clear: both;visibility: hidden;
}
.clearfix{*zoom: 1;
}
.ovf{overflow:hidden;
}
.dn{display: none;
}
/*自定义全局*/
p{margin:10px;
}
.fade-enter {opacity: 0;
}
.fade-enter-active {opacity: 1;transition: all .5s;
}
.fade-exit {opacity: 1;transition: all .5s;
}
.fade-exit-active {opacity: 0;
}
.hide{opacity: 0;height: 0px;transform: translatey(-100px);}::-webkit-scrollbar {width:5px;height:5px;
}
::-webkit-scrollbar-track {width: 5px;background-color:#fff;-webkit-border-radius: 10px;-moz-border-radius: 10px;border-radius:10px;
}
::-webkit-scrollbar-thumb {background-clip:padding-box;min-height:28px;-webkit-border-radius: 10px;-moz-border-radius: 10px;border-radius:10px;
}
::-webkit-scrollbar-thumb:hover {background-color:#FFB90F;
}
`;
九、封装axios请求

在request文件夹下创建api.js和http.js。

api.js
存放api接口。

// api.js
// 接口地址
import {get,post} from './http';
const url= 'https://www.maomin.club/myblog/'; // api
// post格式
export const reg = g => post(`${url}register`, g); // 注册
export const log = g => post(`${url}login`, g); // 登录
export const write = g => post(`${url}write`, g); // 写文章
export const circle = g => post(`${url}circle`, g); // 发圈子
export const getCircle = g => post(`${url}getCircle`, g); // 获取圈子
export const uploadImg = g => post(`${url}uploadImg`, g); // 写文章上传图片
export const getListapi = g => post(`${url}getList`, g); // 获取文章列表
export const getDetails = g => post(`${url}getDetails`, g); // 获取文章详情
export const comment = g => post(`${url}comment`, g); // 发送评论
export const getComment = g => post(`${url}getComment`, g); // 获取评论
export const getinfo = g => post(`${url}getinfo`, g) // 获取用户信息
// get格式
export const alllist = g =>get(`${url}getAllList`,g);//获取所有文章列表

http.js
请求配置。

// http.js
// axios配置
import axios from 'axios';
import { message} from 'antd';
// 请求拦截器
axios.interceptors.request.use(config => {if (localStorage.getItem('Authorization')) {config.headers.Authorization = localStorage.getItem('Authorization'); //查看是否存在tokenreturn config;} else if (config.isUpload) {config.headers = { 'Content-Type': 'multipart/form-data' } // 根据参数是否启用form-data方式return config;} else {config.headers = { 'Content-Type': 'application/json;charset=utf-8' }return config;}},error => {return Promise.error(error)})// 响应拦截器
axios.interceptors.response.use(// 服务码是200的情况response => {if (response.status === 200) {switch (response.data.resultCode) {// token过期case 2:message.error('登录过期,请重新登录');localStorage.removeItem('Authorization');setTimeout(() => {window.location.href="/";}, 1000);break;case 3:message.error('未登录');break;case 4:message.error('请输入正确的账号或者密码');break;default:break;}return Promise.resolve(response);} else {return Promise.reject(response)}},// 服务器状态码不是200的情况error => {if (error.response.status) {switch (error.response.status) {// 404请求不存在case 404:alert('网络请求不存在');break;// 其他错误,直接抛出错误提示default:alert('error.response.data.message');}return Promise.reject(error.response)}}
)/*** get方法,对应get请求* @param {String} url [请求的url地址]* @param {Object} params [请求时携带的参数]*/
export function get(url, params, config = {add: ''
}) {return new Promise((resolve, reject) => {axios.get(url, {params: params}, config).then(res => {resolve(res.data)}).catch(err => {reject(err.data)})})
}
/*** post方法,对应post请求* @param {String} url [请求的url地址]* @param {Object} params [请求时携带的参数]*/
export function post(url, params, config = {isUpload: false
}) {return new Promise((resolve, reject) => {axios.post(url, params, config).then(res => {resolve(res.data)}).catch(err => {reject(err.data)})})
}
十、状态管理总配置

在store文件夹创建一个index.js和reducer.js。因为每个页面模块都有一个状态,所以我们在这个项目里采用分模块。然后我们现在的需要做的是统一管理它们每一个模块。

index.js

// index.js
// 全局store配置
import {createStore,applyMiddleware,compose} from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';// redux-devtools 配置
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;const enhancer = composeEnhancers(// 使用中间件 thunkapplyMiddleware(thunk)
);
const store = createStore(reducer,enhancer);export default store;

reducer.js

// reducer.js
// 分模块Reducer
import { combineReducers } from 'redux-immutable';
import { reducer as homeReducer } from '../views/Home/store/';
import { reducer as layoutReducer } from '../components/layout/store';
import { reducer as aboutReducer } from '../views/About/store';
import { reducer as detailsReducer } from '../views/Details/store';const reducer = combineReducers({home: homeReducer,layout:layoutReducer,about:aboutReducer,details:detailsReducer
});export default reducer;
十一、页面模块与组件模块

因页面过多,这里只展示首页模块,其他逻辑思想大差不差,如果想详细了解的可以加我微信。
在views文件夹创建一个Home文件夹。依次创建如下图所示文件:

index.jsx
页面组件。

// index.jsx
import React, { useEffect, Fragment } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { Pagination, Spin } from 'antd';
import styled from 'styled-components';
import { LeftView, RightView, Item, ContentBox, InfoBox, Meta, Title, ImgBox, SidebarBlock, ImgBlock, MoreBlock } from './styleJs/style';
import { actionsCreator } from './store/';const mapStateToProps = (state) => {return {datalist: state.getIn(['home', 'datalist']),page: state.getIn(['home', 'page']),defaultCurrent: state.getIn(['home', 'defaultCurrent'])}
};const mapDispatchToProps = (dispatch) => {return {getdata(v) {dispatch(actionsCreator.getList(v))},pageChange(v) {dispatch(actionsCreator.changePage(v))}}
};
const Loading = styled.div`text-align: center;margin:34vh 0;
`;
const Home = (props) => {const { datalist, getdata, page, defaultCurrent, pageChange } = props;const newList = datalist.toJS();useEffect(() => {getdata(defaultCurrent);}, [defaultCurrent, getdata])return (<div><LeftView>{page === 0 ? <Loading><Spin tip="Loading..." /></Loading> : <div><div style={{ 'height': '624px' }}>{newList.map((item) => {return (<Fragment key={item.id}><Link to={'/details/' + item.id}><Item><ContentBox><InfoBox><Meta>{item.tab}</Meta><Title>{item.title}</Title></InfoBox><ImgBox srci={item.context.substring(item.context.indexOf("<img src='"), item.context.indexOf("' alt=''>")).replace("<img src='", "")}></ImgBox></ContentBox></Item></Link></Fragment>)})}</div><div style={{ 'margin': '20px' }}><Pagination defaultCurrent={defaultCurrent} total={page}  pageSize={6} onChange={pageChange}></Pagination></div></div>}</LeftView><RightView><SidebarBlock><ImgBlock src={require("../../assets/images/gzh.jpg")} /></SidebarBlock><SidebarBlock><ImgBlock src={require("../../assets/images/wx.jpg")} /></SidebarBlock><MoreBlock><div>&copy; {new Date().getFullYear()}<span>maomin.club</span>版权所有</div><a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=37021302000701">公安备案号 37021302000701</a><a href="http://www.beian.miit.gov.cn/">ICP19020856-1</a></MoreBlock></RightView></div>)
}export default connect(mapStateToProps, mapDispatchToProps)(Home);

styles/style.js
home页面的样式。

// style.js
import styled, {keyframes }  from 'styled-components';const fadeIn = keyframes`from {opacity:0;}to {opacity:1;}
`export const LeftView = styled.div`border-radius: 2px;width: 700px;margin-right: 21.667rem;border:1px solid #f4f4f4;background:#fff;box-sizing:border-box;animation:${fadeIn}1s ease-in;
`
export const RightView = styled.div`position: absolute;top: 0;right: 0;width:20rem;@media (max-width: 960px){display: none;}
`
export const Item = styled.div`border-bottom: 1px solid rgba(178,186,194,.15);
`
export const ContentBox = styled.div`display: flex;align-items: center;padding: 1.5rem 2rem;
`
export const InfoBox = styled.div`flex: 1 1 auto;display: flex;flex-direction: column;justify-content: center;min-width: 0;
`
export const Meta = styled.div`color: #b2bac2;
`
export const Title = styled.div`margin: 1rem 0 1rem;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;font-size: 1.4rem;font-weight: 600;line-height: 1.2;color: #2e3135;
`export const ImgBox = styled.div`background-image:url('${props => props.srci}');background-repeat: no-repeat;background-size: cover;flex: 0 0 auto;width: 5rem;height: 5rem;background-color:#f4f4f4;margin-left: 2rem;background-color: #fff;border-radius: 2px;background-position: 50%;animation:${fadeIn}1s ease-in;
`
export const SidebarBlock = styled.div`background-color: #fff;box-shadow: 0 1px 2px 0 rgba(0,0,0,.05);border-radius: 2px;margin-bottom: 1.3rem;font-size: 1.16rem;line-height: 1.29;color: #333;
`
export const ImgBlock = styled.img`width:100%;animation:${fadeIn}1s ease-in;
`
export const MoreBlock =styled.div`background-color: transparent;box-shadow: none;a{display:block;line-height:22px;text-decoration: none;cursor: pointer;color: #909090;}div {line-height:22px;}span{margin:0 5px;}
`

store/actionsCreator.js
react-thunk作用:使我们可以在action中返回函数,而不是只能返回一个对象。然后我们可以在函数中做很多事情,比如发送异步的ajax请求。

// actionsCreator.js
import {actionsTypes} from './index';
import {getListapi} from '../../../request/api';
import {fromJS} from 'immutable';const dataList =(data,page) =>{return {type:actionsTypes.DATA_LIST,data:fromJS(data),page:fromJS(page)}
};
const currentPage = (p) =>{return {type:actionsTypes.CHANGE_PAGE,current:p}}
export const getList = (p) =>{return (dispatch) =>{let postData ={page:p}getListapi(postData).then((res)=>{const data = res.data;const page = res.page;const action = dataList(data,page);dispatch(action);}).catch((err)=>{console.log(err);})}
};export const changePage=(page)=>{return (dispatch) =>{const action = currentPage(page);dispatch(action);}}

store/actionsTypes.js

// actionsTypes.js
export const DATA_LIST = 'home/DATA_LIST';
export const CHANGE_PAGE = 'home/CHANGE_PAGE';

store/index.js
home页面的store配置。

// index.js
import reducer from './reducer';
import * as actionsTypes from './actionsTypes';
import * as actionsCreator from './actionsCreator';export { reducer, actionsCreator,actionsTypes};

store/reducer.js
由于是不可变的,可以放心的对对象进行任意操作。在 React 开发中,频繁操作state对象或是 store ,配合 immutableJS 快、安全、方便。

// reducer.js
import {actionsTypes} from './index';
import {fromJS} from 'immutable';let defaultState = fromJS({datalist: [],page:0,defaultCurrent:1
});export default (state = defaultState, action) => {switch (action.type) {case actionsTypes.DATA_LIST:return state.merge({'datalist':action.data,'page':action.page})case actionsTypes.CHANGE_PAGE:return state.set('defaultCurrent',action.current)default:return state;}
};

后台角度

主要资源

  • https
  • fs
  • path
  • koa
  • koa-router
  • koa2-cors
  • jsonwebtoken
  • koa-body
  • koa-static
  • koa-sslify
  • mysql
  • node-schedule

源码

后台主要是用了Koa模块,下面的源码是基于https环境。数据库是采用了创建地址池的方法,数据库的连接池负责分配,管理和释放数据库链接的。它允许应用程序重复使用一个现有的数据库的链接。而不是重新创建一个。地址池这里可以优化,这里为了看的更清楚,统一放在了一个文件里。具体详解请看下面的注释。

// app.js
var https = require("https");//https服务
var fs = require("fs");
var path = require('path');
var Koa = require('koa');
var Router = require('koa-router');
var cors = require('koa2-cors');
var jwt = require('jsonwebtoken');
var koaBody = require('koa-body'); //文件保存库
var serve = require('koa-static');
var enforceHttps = require('koa-sslify').default;
var mysql = require('mysql');
var schedule = require('node-schedule');
var app = new Koa();
app.use(enforceHttps());
var router = new Router();
var secretkey = ''; // token的key// 这是我的https配置文件可忽略
var options = {key: fs.readFileSync('https/2_www.maomin.club.key'),cert: fs.readFileSync('https/1_www.maomin.club_bundle.crt')
}// 存文件配置
const home = serve(path.join(__dirname) + '/public/');
app.use(home);
app.use(koaBody({multipart: true
}));// 跨域
const allowOrigins = ["https://www.maomin.club/"
];
app.use(cors({origin: function (ctx) {if (allowOrigins.includes(ctx.header.origin)) {return ctx.header.origin;}return false;},exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],maxAge: 5,credentials: true,withCredentials: true,allowMethods: ['GET', 'POST', 'DELETE'],allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}));// 创建地址池
var pool = mysql.createPool({host: '', // 主机port: 3306, // 端口user: '', // 用户password: '', // 密码database: '', // 数据库multipleStatements: true, // 允许每个mysql语句有多条查询connectionLimit: 100 // 最大连接数
})// 数据库操作
// 定时置3
schedule.scheduleJob('10 0 0 * * *', function () {console.log('update!')var updateStr = 'UPDATE login SET count = ?';var modSqlParams = [3];pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(updateStr, modSqlParams, function (err, results) {if (err) {//do somethingthrow err;} conn.release(); //释放连接})})
});// 检查token
const checkToken = function (tokenid) {return new Promise((resolve) => {if (tokenid) {//校验tokenidjwt.verify(tokenid, secretkey, function (err, decoded) { // decoded:指的是tokneid解码后用户信息if (err) {   //如果tokenid过期则会执行err的代码块resolve({ success: false, resultCode: 2, message: err });} else {resolve("notime");}})} else { resolve({ success: false, resultCode: 3, message: '未登录' }) }})
}let json = {};
// 通用查询方法
const query = function (sql) {return new Promise((resolve, reject) => {pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(sql, function (err, results) {if (err) {//do somethingreject(error);} else {//return data or anything you want do!resolve(results);}conn.release(); //释放连接})})})
}// 分页
let all = "";
const page = function (sql, p) {return new Promise((resolve, reject) => {pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(sql, function (err, results) {if (err) {//do somethingreject(error);} else {//return data or anything you want do!var allCount = results[0][0]['COUNT(*)'];all = allCount;var allPage = parseInt(allCount) / p;var pageStr = allPage.toString();if (pageStr.indexOf('.') > 0) {allPage = parseInt(pageStr.split('.')[0]) + 1;}var List = results[1];resolve(List)}conn.release(); //释放连接})})})
}// 登录方法
const logQuery = function (userStr, token) {return new Promise((resolve, reject) => {pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(userStr, function (err, results) {if (err) {//do somethingreject(error);} else {//return data or anything you want do!if (results.length !== 0) {var dataString = JSON.stringify(results);var data = JSON.parse(dataString);json['message'] = '登录成功';json['resultCode'] = 200;json['username'] = data[0].username;json['token'] = token;var updateStr = 'UPDATE login SET token = ? WHERE Id = ?';var modSqlParams = [token, data[0].id];pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(updateStr, modSqlParams, function (err, results) {if (err) {//do somethingthrow err;} conn.release(); //释放连接})})resolve(json);} else {resolve({ success: false, resultCode: 4, message: '请输入正确的账号或密码' });}}conn.release(); //释放连接})})})
}//注册方法
const regQuery = function (userStr, name, passwd, token, count) {return new Promise((resolve, reject) => {pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(userStr, function (err, result) {if (err) {//do somethingreject(error);} else {//return data or anything you want do!if (result.length > 0) {json['message'] = '用户已经存在';json['resultCode'] = 1;} else {json['message'] = '注册成功';json['token'] = token;json['username'] = name;json['count'] = count;json['resultCode'] = 200;var insertStr = `insert into login (username, password,token,count) values ("${name}", "${passwd}","${token}","${count}")`;pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(insertStr, function (err, results) {if (err) {//do somethingthrow err;} conn.release(); //释放连接})})}resolve(json)}conn.release(); //释放连接})})})
}// 评论方法
const commentQuery = function (userStr, aid) {return new Promise((resolve, reject) => {pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(userStr, async function (err) {if (err) {//do somethingreject(error);} else {//return data or anything you want do!json['message'] = '评论成功';json['success'] = true;let sql = `select aid,username,com from comment where aid="${aid}"`;let results = await query(sql);json['data'] = results;resolve(json);}conn.release(); //释放连接})})})
}// 发圈子方法
const setCount = function (userStr, username, imgsrc, inputValue, td) {return new Promise((resolve, reject) => {pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(userStr, function (err, results) {if (err) {//do somethingreject(error);} else {//return data or anything you want do!var dataString = JSON.stringify(results);var data = JSON.parse(dataString);if (data[0].count > 0) {var newCount = data[0].count - 1;json['message'] = '发表成功';json['resultCode'] = 200;json['success'] = true;json['count'] = newCount;// 次数减一var updateStr = 'UPDATE login SET count = ? WHERE username = ?';var modSqlParams = [newCount, username];pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(updateStr, modSqlParams, function (err) {if (err) {//do somethingthrow err;} conn.release(); //释放连接})})// 存入圈子数据库var insetStr = `insert into circle (username, imgsrc, inputValue, td) values ("${username}","${imgsrc}","${inputValue}","${td}")`pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(insetStr, modSqlParams, function (err) {if (err) {//do somethingthrow err;} conn.release(); //释放连接})})resolve(json);} else {resolve({ success: false, resultCode: 5, message: '操作太频繁,请明天再发哦' });}}conn.release(); //释放连接})})})
}// 用户信息方法
const getInfo = function (tokenid) {return new Promise((resolve) => {if (tokenid) {//校验tokenidjwt.verify(tokenid, secretkey, function (err, decoded) { // decoded:指的是tokneid解码后用户信息if (err) {   //如果tokenid过期则会执行err的代码块resolve({ success: false, resultCode: 2, message: err });} else {resolve(decoded);}})} else { resolve({ success: false, resultCode: 3, message: '未登录' }) }})
}// 获取用户信息
router.post('/getinfo', async (ctx, next) => {var tokenid = ctx.request.body.token;let results = await getInfo(tokenid);ctx.body = results;
})// 注册
router.post('/register', async (ctx, next) => {let name = ctx.request.body.username;let passwd = ctx.request.body.password;let count = 3;let token = jwt.sign({username: name}, secretkey, {expiresIn: 60 * 60 * 12 // 12h});let userStr = `select * from login where username="${name}"`;let results = await regQuery(userStr, name, passwd, token, count);ctx.body = results
});// 登录
router.post('/login', async (ctx, next) => {let name = ctx.request.body.username;let passwd = ctx.request.body.password;let token = jwt.sign({username: name}, secretkey, {expiresIn: 60 * 60 * 12 // 12h});let userStr = `select username,password,id from login where username="${name}" and password="${passwd}"`;let results = await logQuery(userStr, token);ctx.body = results
});// 写评论
router.post('/comment', async (ctx, next) => {let aid = ctx.request.body.aid;let username = ctx.request.body.username;let com = ctx.request.body.com;let td = ctx.request.body.td;var tokenid = ctx.request.headers.authorization//获取前端请求头发送过来的tokenidlet trueFlase = await checkToken(tokenid);if (trueFlase === "notime") {let userStr = `insert into comment (aid, username, com, td) values ("${aid}","${username}","${com}","${td}")`let results = await commentQuery(userStr, aid);ctx.body = results;} else {ctx.body = trueFlase;}
})// 获取评论
router.post('/getComment', async (ctx, next) => {var start = (ctx.request.body.page - 1) * 3;let aid = ctx.request.body.aid;var count = `SELECT * FROM comment WHERE aid="${aid}"`;let allnum = await query(count);const len = allnum.length;var sql = `SELECT COUNT(*) FROM comment ORDER BY id DESC;SELECT * FROM comment WHERE aid="${aid}" ORDER BY id DESC limit${start},3`;let results = await page(sql, 3);ctx.body = {data: results,page: len}
}
)// 写文章
router.post('/write', async (ctx, next) => {let title = ctx.request.body.title;let tab = ctx.request.body.tab;let context = ctx.request.body.context;var tokenid = ctx.request.headers.authorization//获取前端请求头发送过来的tokenidlet trueFlase = await checkToken(tokenid);if (trueFlase === "notime") {var userStr = `insert into article (title, tab, context) values ("${title}","${tab}","${context}")`pool.getConnection(function (err, conn) {if (err) {//do somethingconsole.log(err);}conn.query(userStr, function (err) {if (err) {//do somethingthrow err;} conn.release(); //释放连接})})        ctx.body = { success: true, message: '发送成功' } // echo the result back} else {ctx.body = trueFlase;}});// 写文章上传图片
router.post('/uploadImg', async (ctx, next) => {if (ctx.request.files.file) {var file = ctx.request.files.file;// 创建可读流var reader = fs.createReadStream(file.path);// 修改文件的名称var myDate = new Date();var newFilename = myDate.getTime() + '.' + file.name.split('.')[1];var targetPath = path.join(__dirname, './public/images/') + `${newFilename}`;//创建可写流var upStream = fs.createWriteStream(targetPath);// 可读流通过管道写入可写流reader.pipe(upStream);var imgsrc = 'https://www.maomin.club/myblog/images/' + newFilename;ctx.body = {success: true,imgsrc: imgsrc};}
})// 发圈子
router.post('/circle', async (ctx, next) => {if (ctx.request.files.file) {var file = ctx.request.files.file;// 创建可读流var reader = fs.createReadStream(file.path);// 修改文件的名称var myDate = new Date();var newFilename = myDate.getTime() + '.' + file.name.split('.')[1];var targetPath = path.join(__dirname, './public/images/') + `${newFilename}`;//创建可写流var upStream = fs.createWriteStream(targetPath);// 可读流通过管道写入可写流reader.pipe(upStream);var imgsrc = 'https://www.maomin.club/myblog/images/' + newFilename;} else {var imgsrc = ""}let username = ctx.request.body.username;let inputValue = ctx.request.body.inputValue;let td = ctx.request.body.td;var tokenid = ctx.request.headers.authorization//获取前端请求头发送过来的tokenidlet trueFlase = await checkToken(tokenid);if (trueFlase === "notime") {let userStr = `select count from login where username="${username}"`;let results = await setCount(userStr, username, imgsrc, inputValue, td);ctx.body = results;} else {ctx.body = trueFlase;}
});// 获取圈子
router.post('/getCircle', async (ctx, next) => {var start = (ctx.request.body.page - 1) * 3;var sql = 'SELECT COUNT(*) FROM circle ORDER BY id DESC; SELECT * FROM circle ORDER BY id DESC limit ' + start + ',3';let results = await page(sql, 3);ctx.body = {data: results,page: all}
});// 获取文章列表(分页)
router.post('/getList', async (ctx, next) => {var start = (ctx.request.body.page - 1) * 6;var sql = 'SELECT COUNT(*) FROM article ORDER BY id DESC; SELECT * FROM article ORDER BY id DESC limit ' + start + ',6';let results = await page(sql, 6);ctx.body = {data: results,page: all}
});// 获取文章列表(全部)
router.get('/getAllList', async (ctx, next) => {var sql = "select * from article";let results = await query(sql);ctx.body = results
});// 获取文章详情
router.post('/getDetails', async (ctx, next) => {const id = ctx.request.body.id;var sql = `select * from article where id="${id}"`;let results = await query(sql);ctx.body = results
});//使用路由中间件
app.use(router.routes()).use(router.allowedMethods());https.createServer(options, app.callback()).listen(8410);
console.log('服务器运行中')

作者:Vam的金豆之路

主要领域:前端开发

我的微信:maomin9761

微信公众号:前端历劫之路


React与Koa一起打造一个仿稀土掘金全栈个人博客(技术篇)相关推荐

  1. React+Egg.js实现全栈个人博客

    React+Egg.js实现全栈个人博客 这是一个个人博客软件,前台和后台使用的都是React,后端使用egg.js,地址 前台 文章列表 1.png 文章详情 2.png 后台管理系统 添加文章 3 ...

  2. vue全家桶+koa2+mongoDB打造全栈社区博客

    背景 一直以来都想自己编写一个自己的社区博客,后来在网上找了一下,最后决定参考慕课网的一个社区项目,决定改用vue2.6+AntdForVue+koa2+mongoose实现一套社区博客. 简介 这是 ...

  3. 自定义view高仿稀土掘金loading闪动字体效果

    注:该文章同步发布到稀土掘金:链接 前言 由于通勤时间较长,在路上总会有时间刷刷文章.稀土掘金就是常用的一个app(这里非广告,哈哈哈).前段时间,发表了篇文章:# 使用CollapsingToolb ...

  4. 如何培养一个搞垮公司的全栈工程师?

    作者| Mr.K   整理| Emma 来源| 技术领导力(ID:jishulingdaoli) 人生导师 尼古拉斯·赵四先生说过,"世界上没有什么事情是撸代码解决不了的,如果有,那就再撸一 ...

  5. 作为一个程序员我为什么要写博客?

    作为一个程序员我为什么要写博客?从2012-05-22的第一篇博文,到现在累计原创:523篇,转载:182篇,译文:8篇,转载的基本不会占用多少时间,我们来计算一下原创的+译文:这里假设平均写一篇博文 ...

  6. 什么?还能在网站上藏一个老婆?(怎么在给个人博客或者网站添加基于Live2D的动漫模型)

    什么?还能在网站上藏一个老婆??? 父老乡亲们第一次访问本菜鸟的网站时,第一眼看到的肯定是我偷偷放在左下角的老婆啦~(/滑稽) 那么问题来了,父老乡亲们如果也想把老婆放上去,该怎么做呢? 首先,就要引 ...

  7. 使用CollapsingToolbarLayout高仿稀土掘金个人中心页

    注:该文章也同步更新到稀土掘金:链接 前言 CollapsingToolbarLayout是android MaterialDeign提供的一个组件,通过搭配AppBarLayout可实现toolba ...

  8. react 对象克隆_如何使用React&GraphQL(Dune World Edition)创建全栈Yelp克隆

    react 对象克隆 I must not fear. Fear is the mind-killer. Fear is the little-death that brings total obli ...

  9. python皮同_Python OpenCV 图像的双线性插值算法,全网最细致的算法说明_橡皮擦,一个逗趣的互联网高级网虫-CSDN博客...

    原文作者:梦想橡皮擦 原文标题:Python OpenCV 图像的双线性插值算法,全网最细致的算法说明 发布时间:2021-02-17 20:55:32 Python OpenCV 365 天学习计划 ...

最新文章

  1. mysql 主从单表_MySQL主从复制单表或者多表
  2. 快速获取OpenCV库(Lib)文件下的所有文件的目录名~
  3. 【华科考研复试机试题】华中科技大学考研复试机试题解题报告
  4. 查找字符串中首个非重复字符
  5. 2017云栖大会·杭州峰会:《在线用户行为分析:基于流式计算的数据处理及应用》之《数据可视化:构建实时动态运营数据分析大屏》篇...
  6. [09-01]JavaScript 基础语法
  7. java-接口与多态-
  8. java 防并发_并发:如何防止两个不同类中的两个方法同时运行?
  9. tar (child): .tgz\r:无法 open: 没有那个文件或目录
  10. 恶意软件伪装“正规军”,撕开Booster Cleaner“画皮”下的真相
  11. Civil 3D 二次开发 新建CLR项目出现错误C2143
  12. android 播放直播流,安卓大部分浏览器播放HLS协议直播流会从头开始
  13. 奔图cp2510dn linux,奔图CP2510DN驱动
  14. HealthKit详解
  15. 卡诺模型案例分析_KANO模型案例分析---来自58学车
  16. 多模态学习方法综述(期刊论文)
  17. We Will Rock You
  18. Python实现Iris数据集(鸢尾花卉数据集)kmeans聚类
  19. 买二手苹果macbook被骗真实经历
  20. Arduino 驱动TM1638显示十位的共阳数码管

热门文章

  1. 频谱仪RBW带宽和VBW带宽
  2. Hive必须了解的技能有哪些?万字博客带你掌握Hive
  3. 【深圳-招聘】全球领先的电路图 WebIDE 招募资深前端
  4. ARM中汇编程序实例小笔记
  5. ORB-SLAM2:(二)Monocular/Stereo/RGB-D数据集
  6. ssm高校专升本考试管理系统 毕业设计-附源码201631
  7. 卅年史诗!地球上出现过的CPU完全收藏 - (26-28) x86架构下的其它CPU巨鳄 Cyrix,NexGen,IDT/Transmeta(组图)
  8. php fpm进程监控插件,munin 配置php-fpm监控
  9. 全国计算机会过期吗,计算机二级​证会过期吗
  10. 中兴智能视觉大数据报道:人脸识别厉害的还在后头!