React与Koa一起打造一个仿稀土掘金全栈个人博客(技术篇)
前言
我的个人博客样式布局是仿的稀土掘金 ,个人博客线上网址为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
模块页面
首页
登录注册
文章详情
文章评论
圈子
写圈子
搜索页
权限页
写文章
项目配置
项目目录
在这里插入图片描述
前台搭建项目步骤
一、使用稳定依赖管理工具
推荐你使用淘宝源
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 样式和修改变量。
首先在src目录下创建一个App.less文件,编辑内容如下:
@import '~antd/dist/antd.less';
1
然后在App.jsx内引入App.less文件(上面已经编辑过App.jsx文件的这里不用管)
然后安装 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'); //查看是否存在token
return 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(
// 使用中间件 thunk
applyMiddleware(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>© {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/"> 鲁ICP备19020856号-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;
}
};
源码
后台主要是用了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 something
console.log(err);
}
conn.query(updateStr, modSqlParams, function (err, results) {
if (err) {
//do something
throw err;
}
conn.release(); //释放连接
})
})
});
// 检查token
const checkToken = function (tokenid) {
return new Promise((resolve) => {
if (tokenid) {
//校验tokenid
jwt.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 something
console.log(err);
}
conn.query(sql, function (err, results) {
if (err) {
//do something
reject(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 something
console.log(err);
}
conn.query(sql, function (err, results) {
if (err) {
//do something
reject(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 something
console.log(err);
}
conn.query(userStr, function (err, results) {
if (err) {
//do something
reject(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 something
console.log(err);
}
conn.query(updateStr, modSqlParams, function (err, results) {
if (err) {
//do something
throw 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 something
console.log(err);
}
conn.query(userStr, function (err, result) {
if (err) {
//do something
reject(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 something
console.log(err);
}
conn.query(insertStr, function (err, results) {
if (err) {
//do something
throw 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 something
console.log(err);
}
conn.query(userStr, async function (err) {
if (err) {
//do something
reject(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 something
console.log(err);
}
conn.query(userStr, function (err, results) {
if (err) {
//do something
reject(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 something
console.log(err);
}
conn.query(updateStr, modSqlParams, function (err) {
if (err) {
//do something
throw 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 something
console.log(err);
}
conn.query(insetStr, modSqlParams, function (err) {
if (err) {
//do something
throw err;
} conn.release(); //释放连接
})
})
resolve(json);
} else {
resolve({ success: false, resultCode: 5, message: '操作太频繁,请明天再发哦' });
}
}
conn.release(); //释放连接
})
})
})
}
// 用户信息方法
const getInfo = function (tokenid) {
return new Promise((resolve) => {
if (tokenid) {
//校验tokenid
jwt.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//获取前端请求头发送过来的tokenid
let 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//获取前端请求头发送过来的tokenid
let 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 something
console.log(err);
}
conn.query(userStr, function (err) {
if (err) {
//do something
throw 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//获取前端请求头发送过来的tokenid
let 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
微信公众号:前端历劫之路