前台角度
主要资源
- 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';
- 然后在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; } };
后台角度
主要资源
- 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 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('服务器运行中')