То, что я познакомлю вас сегодня, — это в основном незавершенная внутренняя и внешняя часть нашей CMS-системы с полным стеком.Если вы мало знаете об истории проекта и стеке технологий, вы можете проверить мои предыдущие статьи.
Реализовать проект полного стека CMS от 0 до 1 на основе nodeJS (включено)
Реализовать проект полного стека CMS от 0 до 1 на основе nodeJS (средний)
Резюме
В этой статье в основном будет представлено следующее содержание:
- Внедрение пользовательского промежуточного программного обеспечения koa и restful API
- koa маршрутизация и реализация сервисного уровня
- Базовое использование и навыки мопса шаблонизатора
- Реализация фоновой страницы управления vue и совместного использования исходного кода
- Конкретная реализация и совместное использование исходного кода клиентской стойки React
- Развертывание pm2 и настройка сервера nginx
Так как есть много деталей реализации каждого технического пункта, рекомендуется сначала изучить соответствующий контент, и вы можете связаться со мной, если вы не понимаете. Если вы просто хотите узнать о vue или о реакции, вы можете сразу перейти к части 4 статьи.
текст
1. Внедрить собственное промежуточное ПО koa и Restful API
Приложение Koa — это объект, который содержит набор функций промежуточного программного обеспечения, организованных и выполняемых в виде стека. Мы можем использовать интерфейс использования и асинхронную функцию, предоставляемую koa, для настройки некоторого промежуточного программного обеспечения. Промежуточное программное обеспечение, используемое для печати журналов, выглядит следующим образом:
// logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});
Для получения дополнительной информации о koa вы можете перейти на официальный сайт, чтобы узнать, и мы официально приступим к процессу внедрения промежуточного программного обеспечения.
Когда я представил CMS в первой главе, я вырезал структуру каталогов и иерархию. Мы нашли каталог промежуточных программ в исходном коде. Во-первых, давайте посмотрим на common.js. В этом файле хранится наше основное промежуточное программное обеспечение. в целом определено следующее промежуточное ПО:
Исходный код выглядит следующим образом:
import logger from 'koa-logger';
import koaBody from 'koa-body';
import session from 'koa-session';
import cors from 'koa2-cors';
import sessionStore from '../lib/sessionStore';
import redis from '../db/redis';
import statisticsSchema from '../db/schema/statistics';
// 设置日志
export const Logger = app => app.use(logger())
// 处理请求体
export const KoaBody = app => app.use(koaBody())
// 配置跨域资源共享
export const Cors = app => app.use(cors({
origin: function(ctx) {
if (ctx.url.indexOf('/api') > -1) {
return false;
}
return '*';
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
maxAge: 5,
credentials: true,
allowMethods: ['GET'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Requested-With'],
})
)
// 设置session
export const Session = app => {
app.keys = ['xujiang']
const SESSION_CONFIG = {
key: 'zxzkCMS',
maxAge: 12 * 60 * 60 * 1000, // session的失效时间,设置为半天
store: new sessionStore(redis),
signed: true
}
app.use(session(SESSION_CONFIG, app));
}
// 统计网站数据
export const siteStatistics = app => app.use(async (ctx, next) => {
if(ctx.url.indexOf('articleList?iSaJAx=isAjax') > -1) {
const views = await statisticsSchema.hget('views')
statisticsSchema.hmset('views', +views + 1)
}
await next()
})
На самом деле реализовать промежуточное ПО очень просто.Нам нужно только создать собственную асинхронную бизнес-функцию в параметрах app.use, например, siteStatistics.Вы можете обратиться к этому методу для создания кастомного промежуточного ПО.
На достижении восстановленного API мы должны реализовать инфраструктурный слой. Descorator.js может видеть файлы под исходным библиотеком. Примерно разделен на несколько элементов:
Эта реализация потребует больше знаний es6+, включая модификаторы, символы и т. д. Если вы не понимаете, вы можете связаться со мной.
2.koa маршрутизация и реализация сервисного уровня
В этой части в основном используется модель MVC. Ранее мы определили базовый класс маршрутизации, чтобы мы могли формально обрабатывать серверный бизнес. Мы можем определить различные бизнес-интерфейсы в соответствии с модулями и единообразно управлять ими через контроллер маршрутизации.
- уровень маршрутизатора
// router/statistics
import { controller, get } from '../lib/decorator'
import {
getSiteStatistics
} from '../service/statistics'
@controller('/api/v0/siteStatistics')
class statisticsController {
/**
* 获取所有统计数据
* @param {*} ctx
* @param {*} next
*/
@get('/all')
async getSiteStatistics(ctx, next) {
const res = await getSiteStatistics()
if(res && !Array.isArray(res)) {
ctx.status = 200
ctx.body = {
data: res,
state: 200
}
}else {
ctx.status = 500
ctx.body = {
data: res ? res.join(',') : '服务器错误',
state: 500
}
}
}
}
export default statisticsController
- сервисный уровень
// service用来处理业务逻辑和数据库操作
import statisticsSchema from '../db/schema/statistics'
export const getSiteStatistics = async () => {
const result = await statisticsSchema.hgetall()
return result
}
Здесь мы даем простой пример для понимания каждого. Что касается разработки модулей, таких как admin и config, он аналогичен, что может быть обработано в соответствии с вашими собственными потребностями бизнеса. Код для других модулей был написан и можно найти в моем github. Если вы не понимаете, вы можете общаться со мной.
3. Базовое использование и навыки мопса шаблонизатора
Механизм шаблонов не находится в центре внимания проекта, и механизмы шаблонов, такие как jade и ejs, не участвуют в проекте, но в качестве внешнего интерфейса полезно знать о них больше. Здесь я кратко представлю pug (то есть обновленную версию jade).
Чтобы использовать механизм шаблонов в проекте koa, мы можем использовать koa-views для рендеринга, конкретное использование выглядит следующим образом:
/***** koa-view基本使用 *****/
import views from 'koa-views';
app.use(views(resolve(__dirname, './views'), { extension: 'pug' }));
app.use(async (ctx, next) => {
await ctx.render('index', {
name: 'xujiang',
years: '248岁'
})
});
Файл мопса конкретной страницы:
- index.pug
- layout/default
pug использует отступ для указания уровня кода, и может использоваться такой синтаксис, как наследование.Если вам интересно, вы можете обратиться к официальному веб-сайту pug для обучения. Подробное введение здесь не приводится.
4. Реализация фоновой страницы управления Vue и обмен исходным кодом
Сначала мы рассмотрим организационную структуру фона управления vue:
// type.ts
export interface State {
name: string;
isLogin: boolean;
config: Config;
[propName: string]: any; // 用来定义可选的额外属性
}
export interface Config {
header: HeaderType,
banner: Banner,
bannerSider: BannerSider,
supportPay: SupportPay
}
export interface HeaderType {
columns: string[],
height: string,
backgroundColor: string,
logo: string
}
export interface Banner {
type: string,
label: string[],
bgUrl: string,
bannerList: any[]
}
export interface BannerSider {
tit: string,
imgUrl: string,
desc: string
}
export interface SupportPay {
tit: string,
imgUrl: string
}
// 处理相应的类型
export interface Response {
[propName: string]: any;
}
Содержание мутации следующее:
//action.ts
import {
HeaderType,
Banner,
BannerSider,
SupportPay,
Response
} from './type'
import http from '../utils/http'
import { uuid, formatTime } from '../utils/common'
import { message } from 'ant-design-vue'
export default {
/**配置 */
setConfig(context: any, paylod: HeaderType) {
http.get('/config/all').then((res:Response) => {
context.commit('setConfig', res.data)
}).catch((err:any) => {
message.error(err.data)
})
},
/**header */
saveHeader(context: any, paylod: HeaderType) {
http.post('/config/setHeader', paylod).then((res:Response) => {
message.success(res.data)
context.commit('saveHeader', paylod)
}).catch((err:any) => {
message.error(err.data)
})
},
/**banner */
saveBanner(context: any, paylod: Banner) {
http.post('/config/setBanner', paylod).then((res:Response) => {
message.success(res.data)
}).catch((err:any) => {
message.error(err.data)
})
},
/**文章列表 */
getArticles(context: any) {
http.get('article/all').then((res:Response) => {
context.commit('getArticles', res.data);
}).catch((err:any)=>{
message.error(err.data)
})
},
addArticle(context: any, paylod: any) {
paylod.id = uuid(8, 10);
paylod.time = formatTime(Date.now(), '/');
paylod.views = 0;
paylod.flover = 0;
return new Promise((resolve:any, reject:any) => {
http.post('/article/saveArticle', paylod).then((res:Response) => {
context.commit('addArticle', paylod)
message.success(res.data)
resolve()
}).catch((err:any) => {
message.error(err.data)
reject()
})
})
}
// ...
};
Вот несколько типовых действий, которые удобно изучить и понять каждому.Для дальнейшего улучшения мы можем инкапсулировать на его основе baseAction, что может уменьшить большую часть повторно используемой информации.Здесь можно попробовать инкапсулировать волну. Наконец, мы равномерно вводим его в индекс:
import Vue from 'vue';
import Vuex from 'vuex';
import { state } from './state';
import mutations from './mutation';
import actions from './action';
Vue.use(Vuex);
export default new Vuex.Store({
state,
mutations,
actions,
});
Управление vuex таким образом также полезно для последующей масштабируемости и ремонтопригодности.
В части страницы vue вы можете ознакомиться с общими модулями страницы и функциональными точками на основе вариантов использования и моделей данных из предыдущих статей об узлах, поэтому я не буду подробно обсуждать их здесь. Рассмотрим несколько ключевых моментов:
- Как обеспечить правильное расположение навигации по обновлению страницы
- Как сделать кастомное кеширование при переключении страниц
- Как смоделировать ПК и просмотреть на мобильном
- Как использовать расширенный API vuex для реализации механизма мониторинга данных
- Как сделать авторизацию при входе
Далее, я прямо вырезаю свой план для вашей справки.
1. Как обеспечить правильное позиционирование навигации по обновлению страницы
// layout.vue
// 页面路由表
const routeMap: any = {
'/': '1',
'/banner': '2',
'/bannerSider': '3',
'/article': '4',
'/addArticle': '4',
'/support': '5',
'/imgManage': '6',
'/videoManage': '7',
'/websiteAnalysis': '8',
'/admin': '9',
};
// 监听路由变化,匹配当前选中导航
@Watch('$route')
private routeChange(val: Route, oldVal: Route) {
// do something
if(val.path.indexOf('/preview') < 0) {
this.curSelected = routeMap[val.path] || routeMap[oldVal.path];
}
}
2. Как сделать кастомное кеширование при переключении страниц
В качестве кэша используем keep-alive, и передаем значение ключа в завернутое им представление маршрутизации, чтобы определить, нужно ли кешировать в следующий раз:
<template>
<div id="app">
<keep-alive>
<router-view :key="key" />
</keep-alive>
</div>
</template>
<script lang="ts">
import { Vue } from 'vue-property-decorator';
import Component from 'vue-class-component';
@Component
export default class App extends Vue {
get key() {
// 缓存除预览页面之外的其他页面
console.log(this.$route.path)
if(this.$route.path.indexOf('/preview') > -1) {
return '0'
}else if(this.$route.path === '/login') {
return '1'
}else {
return '2'
}
}
}
</script>
Поскольку наша задача заключается в обновлении до последних данных при переключении страниц предварительного просмотра и управления, мы не используем кеш и вызываем последние данные при переключении этих двух модулей. То же самое верно для входа в систему, а распределенное кэширование выполняется путем установки разных ключей.
3. Как смоделировать предпросмотр ПК и мобильных устройств
Чтобы реализовать предварительный просмотр, я в основном использую симуляцию на основе ширины и определяю экран ПК и мобильного устройства, определяя маршрут предварительного просмотра. Если вы не понимаете, вы можете связаться со мной.Конечно, вы также можете использовать iframe для моделирования.
4. Как использовать vuex advanced API для реализации механизма мониторинга данных
Здесь в разрезе непосредственно код:
public created() {
let { id, label } = this.$route.query;
this.type = id ? 1 : 0;
if(id) {
// 监听vuex中文章数据的变化,变化则触发action显示文章数据
// 注:这里这么做是为了防止页面刷新数据丢失
let watcher = this.$store.watch(
(state,getter) => {
return state.articles
},
() => {
this.getDetail(id, label, watcher)
}
)
if(Object.keys(this.$store.state.articles).length) {
this.getDetail(id, label, watcher)
}
}
}
Мы используем часы vuex для отслеживания изменений в хранилище, а затем выполняем соответствующую обработку. API часов принимает два параметра обратного вызова. Первый обратный вызов возвращает значение. Если значение изменится, он вызовет обратный вызов второго параметра. например, заметки и обратные вызовы с реагирующими хуками.
5. Как выполнить аутентификацию при входе
Аутентификация при входе в основном предназначена для согласования набора правил с серверной службой.Фоновая проверка проверяет, следует ли войти в систему или есть ли у нее разрешение на работу с определенным модулем.Как правило, интерфейсная служба уведомляется через соответствующие данные ответ. Здесь мы в основном говорим об аутентификации при входе. Если текущий пользователь не вошел в систему или сеанс истек, сервер узла вернет 401, чтобы внешний интерфейс мог выполнить операцию перенаправления:
//http模块封装
import axios from 'axios'
import qs from 'qs'
axios.interceptors.request.use(config => {
// loading
return config
}, error => {
return Promise.reject(error)
})
axios.interceptors.response.use(response => {
return response
}, error => {
return Promise.resolve(error.response)
})
function checkStatus (response) {
// loading
// 如果http状态码正常,则直接返回数据
if(response) {
if (response.status === 200 || response.status === 304) {
return response.data
// 如果不需要除了data之外的数据,可以直接 return response.data
} else if (response.status === 401) {
location.href = '/login';
} else {
throw response.data
}
} else {
throw {data:'网络错误'}
}
}
// axios默认参数配置
axios.defaults.baseURL = '/api/v0';
axios.defaults.timeout = 10000;
export default {
post (url, data) {
return axios({
method: 'post',
url,
data: qs.stringify(data),
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).then(
(res) => {
return checkStatus(res)
}
)
},
get (url, params) {
return axios({
method: 'get',
url,
params, // get 请求时带的参数
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).then(
(res) => {
return checkStatus(res)
}
)
},
del (url, params) {
return axios({
method: 'delete',
url,
params, // get 请求时带的参数
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}).then(
(res) => {
return checkStatus(res)
}
)
}
}
Что касается конкретных настроек перехватчика запросов axios и перехватчика ответов, мы можем работать и добавлять собственную логику в соответствии с конкретным бизнесом.
5. Конкретная реализация и совместное использование исходного кода внешнего интерфейса клиента реакции.
Для реактивной части я в основном использую веб-пакет, который я создал сам для упаковки модулей.Если вы хотите изучить веб-пакет, вы можете обратиться к моей конфигурации веб-пакета.В настоящее время упакованные файлы совместимы с ie9+. Реагирующая стойка регистрации в основном включает в себя:
import React, { useState, useEffect } from "react"
import { Carousel } from 'antd'
import ArticleItem from '../../components/ArticleItem'
import { isPC, ajax, unparam } from 'utils/common'
import './index.less'
function Home(props) {
let [articles, setArticles] = useState([])
let { search } = props.location
function getArticles(cate = '', num = 10, page = 0) {
ajax({
url: '/article/articleList',
method: 'get',
data: { cate, num, page }
}).then(res => {
setArticles(res.data || [])
}).catch(err => console.log(err))
}
if(search && sessionStorage.getItem('prevCate') !== search) {
getArticles(unparam(search).cate)
sessionStorage.setItem('prevCate', search)
}
useEffect(() => {
getArticles()
return () => {
sessionStorage.removeItem('prevCate')
}
}, [])
return <div className="home-wrap">
<div className="banner-wrap">
{
isPC ?
<React.Fragment>
<div className="banner-sider">
<div className="tit">{ props.bannerSider.tit }</div>
<img src={props.bannerSider.imgUrl} alt="" />
<div className="desc">{ props.bannerSider.desc }</div>
</div>
{
+props.banner.type ?
<Carousel autoplay className="banner">
{
props.banner.bannerList.map((item, i) => (
<div key={i}>
<a className="banner-img" href="" style={{ backgroundImage: 'url('+ item.imgUrl +')'}}>
<p className="tit">{ item.tit }</p>
</a>
</div>
))
}
</Carousel>
:
<div className="banner">
<div className="banner-img" style={{backgroundImage: 'url('+ props.banner.bgUrl +')'}}>
{
props.banner.label.map((item, i) => (
<span className="banner-label" style={{left: 80*(i+1) + 'px'}} key={i}>
{ item }
</span>
))
}
</div>
</div>
}
</React.Fragment>
:
<Carousel autoplay className="banner">
{
props.banner.bannerList.map((item, i) => (
<a className="banner-img" href="" key={i} style={{ backgroundImage: 'url('+ item.imgUrl +')'}}>
<p className="tit">{ item.tit }</p>
</a>
))
}
</Carousel>
}
</div>
<div className="article-list">
<div className="tit">最新文章</div>
{
articles.map((item, i) => (
<ArticleItem {...item} key={i} />
))
}
</div>
</div>
}
export default Home
Детали статьи:
import React, { useState, useEffect } from "react"
import { Button, Modal, Skeleton, Icon } from 'antd'
import { ajax, unparam } from 'utils/common'
import QTQD from 'images/logo.png'
import './index.less'
function ArticleDetail(props) {
let [isPayShow, setIsPayShow] = useState(false)
let [detail, setDetail] = useState(null)
let [likeNum, setLikeNum] = useState(0)
let [articleContent, setArticleContent] = useState(null)
let [isShowLike, setShowLike] = useState(false)
function toggleModal(flag) {
setIsPayShow(flag)
}
function getcontent(url) {
ajax({
url
}).then(res => {
setArticleContent(res.content)
})
}
function showLike() {
if(!isShowLike) {
ajax({
url: `/article/likeArticle/${unparam(props.location.search).id}`,
method: 'post'
}).then(res => {
setShowLike(true)
setLikeNum(prev => prev + 1)
})
}
}
useEffect(() => {
ajax({
url: `/article/${unparam(props.location.search).id}`
}).then(res => {
setDetail(res.data)
setLikeNum(res.data.flover)
getcontent(res.data.articleUrl)
})
return () => {
};
}, [])
return !detail ? <Skeleton active />
:
<div className="article-wrap">
<div className="article">
<div className="tit">{ detail.tit }</div>
<div className="article-info">
<span className="article-type">{ detail.label }</span>
<span className="article-time">{ detail.time }</span>
<span className="article-views"><Icon type="eye" /> { detail.views }</span>
<span className="article-flover"><Icon type="fire" /> { likeNum }</span>
</div>
<div className="article-content" dangerouslySetInnerHTML={{__html: articleContent}}></div>
<div className="article-ft">
<div className="article-label">
</div>
<div className="support-author">
<p>给作者打赏,鼓励TA抓紧创作!</p>
<div className="support-wrap">
<Button className="btn-pay" type="danger" ghost onClick={() => toggleModal(true)}>赞赏</Button>
<Button className="btn-flover" type="primary" onClick={showLike} disabled={isShowLike}>{ !isShowLike ? '点赞' : '已赞'}({likeNum})</Button>
{
isShowLike && <Icon type="like" className="like-animation" />
}
</div>
</div>
</div>
</div>
<div className="sider-bar">
<h2>友情赞助</h2>
<div className="sider-item">
<img src={QTQD} alt=""/>
<p>公众号《趣谈前端》</p>
</div>
</div>
<Modal
visible={isPayShow}
onCancel={() => toggleModal(false)}
width="300px"
footer={null}
>
<div className="img-wrap">
<img src={props.supportPay.imgUrl} alt={props.supportPay.tit} />
<p>{ props.supportPay.tit }</p>
</div>
</Modal>
</div>
}
export default ArticleDetail
Поскольку стойку регистрации относительно просто реализовать, что касается того, как определить маршрутизатор и как использовать скелетный экран, я написал полный комментарий в коде, и вы можете связаться со мной, если вы заинтересованы.
Развертывание 6.pm2 и настройка сервера nginx
Я дам подробное введение в содержание pm2 для сохранения сервера и nginx для многосайтовой конфигурации и как оптимизировать код.Я надеюсь, что каждый может что-то получить.Если вы хотите изучить исходный код проекта, вы можете обратить внимание на публичный аккаунт «Интересный интерфейс» 》Присоединяйтесь к нам, чтобы учиться и обсуждать вместе.
больше рекомендаций
- Реализовать проект полного стека CMS от 0 до 1 на основе nodeJS (средний)
- Сведения о запуске на стороне сервера для реализации проекта полного стека CMS от 0 до 1 на основе nodeJS
- Реализовать проект полного стека CMS от 0 до 1 на основе nodeJS (включено)
- Краткое изложение основных знаний по теме "Продвинутое программирование на JavaScript"
- Используйте css3, чтобы реализовать фоновую анимацию потрясающего интервьюера (расширенный исходный код)
- Вспомните проблему межстраничной связи в старом проекте и фронтальную реализацию функции скачивания файлов
- 5 минут, чтобы научить вас писать фиктивный сервер данных с помощью nodeJS
- Реализация и применение бинарного дерева и бинарного дерева поиска в JavaScript
- Реализация небольшой игры на проигрывателе с помощью JavaScript и C3
- Научу вас использовать 200 строк кода, чтобы написать любовную мини-игру Dou Pin Le H5 (с исходным кодом)
- Изучение и обобщение решений для внешней интеграции на основе экологии react/vue.
- Как написать собственную библиотеку js менее чем из 200 строк кода)
- Картинка, чтобы научить вас быстро играть в vue-cli3
- 3 минуты, чтобы научить вас использовать нативный js для реализации компонента предварительного просмотра загрузки файлов с мониторингом прогресса
- Использование Angular8 и API карты Baidu для разработки «списка туров»
- js реализация базового алгоритма поиска и тест производительности при 1,7 миллионах данных
- Как сделать интерфейсный код в 60 раз быстрее
- Vue advanced advanced series — играйте с vue и vuex с машинописным текстом