Лучший способ изучить технологию — это что-то с ней сделать.
Когда я изучал Node, я почувствовал, что могу бороться и сопротивляться после прочтения, и на следующий день я вернулся к тому мальчику, которым был раньше. Жаль, что это был не Чжан Уцзи.После прочтения фехтования Тайцзи он забыл повесить И Тяньцзяня. Если вы забудете его после прочтения, то вы его забудете. Поэтому я решил сделать проект, чтобы закрепить свои знания.
Сначала взгляните на следующую часть карты эффектов.
Весь проект представляет собой полностью разделенный внешний и внутренний проект, включая три хранилища: внутренний интерфейс, внутреннюю страницу и переднюю страницу.
Пользователи могут добавлять, удалять, изменять и проверять магазин и продукты питания, регистрируясь в качестве фонового администратора, и соответствующий магазин и продукты будут отображаться в интерфейсе. Весь бэкенд-проект начинается сeggдля рамы,mysqlв качестве базы данных сtypescriptразвивать,Он включает одиннадцать таблиц базы данных и около сорока интерфейсов.. На внутренних и внешних страницах используются обычныеvue+element-ui+vuex+vue-routerразвивать. Что касается развертывания, поскольку это личный проект, я решил использовать технологию, которую раньше не использовал, и построил Jenkins.Автоматически извлекайте и выполняйте сценарии через jenkins для создания образов Docker для автоматизации развертывания проектов vue.. Весь процесс достаточно завершен для личного проекта.
Интернет-адрес:
Ссылка на проект:
Система управления фоном на основе vue + element-ui
Примечание. Для системы управления фоном я имею в виду только здесьСистема управления фоном на основе vue + element-uiБизнес-логика кода глубоко не изучается, потому что используемый стек технологий не тот. Поскольку я впервые использую Node.js для выполнения проекта, я обычно не использую Node.js в компании, и я ссылался на несколько разрозненных статей, но у новичков определенно будет некрасивое отношение к тому, чтобы делать вещи таким же образом.Если вы делаете что-то неразумное, пожалуйста, исправьте это, Самое большое преимущество программистов в том, что они знают свои ошибки и исправляют их, а я не более того.
За кулисами
Используемая технология
- Node.js
- Egg
- MySql
- Redis
- TypeScript
реализовать функцию
- логин регистрации администратора
- Добавлять и изменять магазины
- Добавляйте и изменяйте магазинную еду
- Посмотреть список продуктов
- Посмотреть списки компаний
- Просмотр сегодняшних данных и общих данных
- Настройки информации администратора
- ...
Для общего построения проекта вы можете обратиться к учебнику, предоставленному на официальном сайте яйца.Там есть подробные уроки и подробные каталоги.Мы не будем говорить о рутинных функциях добавления,удаления,модификации и проверки здесь.Мы сосредоточимся на универсальности весь проект и выполнение более хлопотных функций.
Инкапсуляция общих функций
- инкапсуляция ответа на запрос
/*
* @Descripttion: controller基类
* @version:
* @Author: 笑佛弥勒
* @Date: 2019-08-06 16:46:01
* @LastEditors: 笑佛弥勒
* @LastEditTime: 2020-03-09 10:43:37
*/
import { Controller } from "egg"
export class BaseController extends Controller {
/**
* @Descripttion: 请求成功
* @Author: 笑佛弥勒
* @param {status} 状态
* @param {data} 响应数据
* @return:
*/
success(status: number, message: string, data?: any) {
if (data) {
this.ctx.body = {
status: status,
message: message,
data: data
}
} else {
this.ctx.body = {
status: status,
message: message
}
}
}
/**
* @Descripttion: 失败
* @Author: 笑佛弥勒
* @param {status} 状态
* @param {data} 错误提示
* @return:
*/
fail(status: number, message: string) {
this.ctx.body = {
status: status || 500,
message: message,
};
}
- перечисляемый класс
/*
* @Descripttion: 枚举类
* @version: 1.0
* @Author: 笑佛弥勒
* @Date: 2020-03-14 10:07:36
* @LastEditors: 笑佛弥勒
* @LastEditTime: 2020-03-28 23:02:47
*/
export enum Status {
Success = 200, // 成功
SystemError = 500, // 系统错误
InvalidParams = 1001, // 参数错误
LoginOut = 1003, // 未登录
LoginFail = 1004, // 登录失效
CodeError = 1005, // 验证码错误
InvalidRequest = 1006, // 无效请求
TokenError = 1007 // token失效
}
В силу исторических причин текущего проекта компании, есть разные форматы ответа, возвращаемые фоном, и везде разбросаны коды состояния, что не очень дружелюбно к интерфейсу.Здесь я инкапсулирую ответ всего проекта, и все контроллеры наследуются от этого Base класса, так что фоновая разработка тоже удобна, а фронтенд лучше может писать какой-то общий код.
- Инкапсуляция общего кода
Для многих общих функций, таких как функция загрузки изображений в этом проекте, функция создания папки, случайная генерация рейтингов магазинов и рейтингов блюд и т. д., эти коды, которые имеют мало общего с бизнесом и повторяются, все должны быть упакованы для обслуживания. , яйцо предоставляет нам хорошее вспомогательное расширение, функции, написанные в вспомогательном расширении, могут быть вызваны через this.ctx.helper в глобальном масштабе проекта, например, для генерации случайных продаж в магазине
/**
* @Descripttion: 生成范围内随机数,[lower, upper)
* @Author: 笑佛弥勒
* @param {lower} 最小值
* @param {upper} 最大值
* @return:
*/
export function random(lower, upper) {
return Math.floor(Math.random() * (upper - lower)) + lower;
}
В процессе запроса его можно вызвать с помощью метода, предоставляемого яйцом.
mon_sale: this.ctx.helper.random(1000, 20000)
- Проверка параметров внешнего запроса
Для проверки параметров фронтенда, если параметров много, то проверка в нашем бизнес-коде будет иметь большую кучу кодов обнаружения, связанных с проверкой.Например, при создании магазина связанных параметров больше десятка отправлено из интерфейса. , Такой вид довольно неприятный. Когда я сам его разрабатывал, я управлял проверкой параметров через валидацию, предоставленную яйцом. Плагин валидации здесь должен загружаться сам по себе при запуске.
/**
* @Descripttion: 插件加载完成后加入校验规则
* @Author: 笑佛弥勒
* @param {type}
* @return:
*/
public async willReady() {
const directory = path.join(this.app.config.baseDir, 'app/validate');
this.app.loader.loadToApp(directory, 'validate');
}
После загрузки можно использовать пользовательские правила в коде, например, правила проверки используются в коде для создания магазина, логика выглядит относительно понятно.
public async createMerchants() {
let params = this.ctx.request.body
console.log(params)
try {
this.ctx.validate({ params: "addMerchants" }, { params: params })
} catch (error) {
this.fail(Status.InvalidParams, error)
return
}
try {
await this.ctx.service.merchants.createMerchants(params)
this.success(Status.Success, '创建商户成功')
} catch (error) {
this.ctx.logger.error(`-----创建商户错误------`, error)
this.ctx.logger.error(`入参params:${params}`)
this.fail(Status.SystemError, error)
}
}
Реализация функции
- Функция регистрации входа
Функция входа и регистрации - очень распространенная функция, и логика реализации аналогична. Сначала получаем учетную запись пользователя, проверяем, есть ли эта запись в базе данных, а затем сравниваем, верен ли пароль. Если нет, выполняем новую операцию и зашифровать пароль пользователя. Для сгенерированного файла cookie для входа зашифрованная строка создается подключаемым модулем egg-jwt, а затем зашифрованная строка сохраняется через Redis.Когда пользователь запрашивает интерфейс, который необходимо войти в систему, фон удалит файл cookie. в яйце и в редисе.Сделайте сравнение, сделайте проверку статуса входа, здесь другой момент,В яйце куки в миллисекундах, я не смотрел внимательно, и я не мог найти ошибку во время разработки.Я раздавил несколько мышей.Конкретная логика реализации следующая.
public async login() {
const { ctx } = this
let { mobile, password } = this.ctx.request.body
try {
ctx.validate({ mobile: "mobile" })
ctx.validate({ password: { type: "string", min: 1, max: 10 } })
} catch (error) {
this.fail(Status.InvalidParams, error)
return
}
let res = await ctx.service.admin.hasUser(mobile)
// 加密密码
password = utility.md5(password)
let token = ''
if (!res) {
try {
await ctx.service.admin.createUser(mobile, password)
// 生成token
await this.ctx.helper.loginToken({ mobile: mobile, password: password }).then((res) => token = res) // 取到生成token
await this.app.redis.set(mobile, token, 'ex', 7200) // 保存到redis
ctx.cookies.set('authorization', token, {
httpOnly: true, // 默认就是 true
maxAge: 1000 * 60 * 60, // egg中是以毫秒为单位的
domain: this.app.config.env == 'prod' ? '120.79.131.113' : 'localhost'
}) // 保存到cookie
this.success(Status.Success, '注册成功')
} catch (error) {
ctx.logger.error(`-----用户注册失败------`, error)
ctx.logger.error(`入参params:mobile:${mobile}、password:${password}`)
this.fail(Status.SystemError, "用户注册失败")
}
} else {
if (res.password == password) {
await this.ctx.helper.loginToken({ mobile: mobile, password: password }).then((res) => token = res) // 取到生成token
await this.app.redis.set(mobile, token, 'ex', 7200) // 保存到redis
ctx.cookies.set('authorization', token, {
httpOnly: true, // 默认就是 true
maxAge: 1000 * 60 * 60, // egg中是以毫秒为单位的
domain: this.app.config.env == 'prod' ? '120.79.131.113' : 'localhost'
}) // 保存到cookie
ctx.body = { data: { token, expires: this.config.login_token_time }, code: 1, msg: '登录成功' } // 返回
this.success(Status.Success, '登录成功')
} else {
this.fail(Status.SystemError, "密码错误")
}
}
}
Тем не менее, этот метод реализации все еще немного проблематичен, существует два основных метода аутентификации пользователя.
- session+cookie
- жетон жетон
Преимущества и недостатки этих двух методов заключаются в том, что сеансу необходимо сохранить идентификатор сеанса на сервере, а файл cookie, отправленный из внешнего интерфейса, сравнивается с идентификатором сеанса, хранящимся на сервере, для реализации проверки пользователя. токен токен должен генерировать зашифрованную строку через jwt. , когда внешний интерфейс запрашивает, зашифрованная строка отправляется в фоновый режим, а фон проверяет действительность зашифрованной строки.Метод jwt означает, что фону не нужно сохранить зашифрованную строку, а приведенный выше метод использует jwt для создания зашифрованной строки, а затем снова ее проверить, да. Странно, я изменю его, когда у меня будет время.
- промежуточное ПО входа
В процессе разработки ко многим интерфейсам можно получить доступ только через вход в систему. Невозможно добавить проверку входа во все интерфейсы, требующие входа в систему. Мы можем добавить промежуточное программное обеспечение в интерфейс. Яйцо основано на луковой модели, а промежуточное программное обеспечение может быть используется в интерфейсе. Перед доступом выполните некоторые блокирующие ограничения.
/* * @Descripttion: 登陆验证 * @version: 1.0 * @Author: 笑佛弥勒 * @Date: 2019-12-31 23:59:22 * @LastEditors: 笑佛弥勒 * @LastEditTime: 2020-03-28 23:06:09 */module.exports = (options, app) => { return async function userInterceptor(ctx, next) { let authToken = ctx.cookies.get('authorization') // 获取header里的authorization if (authToken) { const res = ctx.helper.verifyToken(authToken) // 解密获取的Token if (res) { // 此处使用redis进行保存 let redis_token = '' res.email ? redis_token = await app.redis.get(res.email) : redis_token = await app.redis.get(res.mobile) // 获取保存的token if (authToken === redis_token) { res.email ? app.redis.expire(res.email, 7200) : app.redis.expire(res.mobile, 7200) // 重置redis过期时间 await next() } else { ctx.body = { status: 1004, message: '登录态失效' } } } else { ctx.body = { status: 1004, message: '登录态失效' } } } else { ctx.body = { status: 1003, message: '请登陆后再进行操作' } } }}
Затем вы можете использовать его в маршруте, который должен войти в систему
export function admin(app) {
const { router, controller } = app
const jwt = app.middleware.jwt({}, app)
router.post('/api/admin/login', controller.admin.login)
router.post('/api/admin/logOut', jwt, controller.admin.logOut)
router.post('/api/admin/updateAvatar', jwt, controller.admin.updateAvatar)
router.post('/api/admin/getAdminCount', jwt, controller.admin.getAdminCount)
router.get('/api/admin/findAdminByPage', jwt, controller.admin.findAdminByPage)
router.get('/api/admin/totalData', jwt, controller.admin.totalData)
router.get('/api/admin/getShopCategory', jwt, controller.admin.getShopCategory)
router.get('/api/admin/getCurrentAdmin', jwt, controller.admin.getCurrentAdmin)
router.get('/api/admin/isLogin', controller.admin.isLogin)
}
- Сбор и классификация городов по всей стране
При выборе города на фронтенде необходимо разделить город по его инициалам.
С точки зрения реализации, первым шагом является получение всех городов страны через API, предоставленный AutoNavi, а затем извлечение и классификация первых букв городов в соответствии со сторонней библиотекой пиньинь. запросы, ip моего сервера блокируется AutoNavi, заблокируйте его, сохраните результат в redis, и redis больше не запрашивает данные.
/**
* @Descripttion: 获取全国所有城市
* @Author: 笑佛弥勒
* @param {type}
* @return:
*/
export async function getAllCity() {
let url = `https://restapi.amap.com/v3/config/district?keywords=&subdistrict=2&key=44b1b802a3d72663f2cb9c3288e5311e`;
var options = {
method: "get",
url: url,
headers: {
"Content-Type": "application/json",
Accept: "application/json" // 需指定这个参数 否则 在特定的环境下 会引起406错误
}
};
return await new Promise((resolve, reject) => {
request(options, function(err, res, body) {
if (err) {
reject(err);
} else {
body = JSON.parse(body);
if (body.status == 0) {
reject(err);
} else {
let cityList: Array<Object> = [];
getAllCityList(cityList, body.districts);
cityList = orderByPinYin(cityList);
resolve(cityList);
}
}
});
});
}
// 给全国城市根据拼音分组
function orderByPinYin(cityList) {
const newCityList: Array<Object> = [];
const title = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z"
];
for (let i = 0; i < title.length; i++) {
let items: Array<Object> = [];
newCityList.push({
name: title[i],
items: []
});
for (let j = 0; j < cityList.length; j++) {
let indexLetter = pinyin(cityList[j].name.substring(0, 1), {
style: pinyin.STYLE_FIRST_LETTER // 设置拼音风格
})[0][0].toUpperCase(); // 提取首字母
if (indexLetter === title[i]) {
items.push(cityList[j]);
}
}
newCityList[i]["items"] = items;
}
return newCityList;
}
// 递归获取全部城市列表
function getAllCityList(cityList: Array<Object>, parent: any) {
let exception: Array<string> = ["010", "021", "022", "023"]; // 四个直辖市另外处理
for (let i = 0; i < parent.length; i++) {
if (parent[i].level === "province") {
if (exception.includes(parent[i].citycode)) {
parent[i].districts = [];
parent[i].level = "city";
cityList.push(parent[i]);
} else {
cityList.push(...parent[i].districts);
}
} else {
getAllCityList(cityList, parent[i].districts);
}
}
}
Есть и некоторые функции.Если вам интересно, вы можете клонировать проект и посмотреть сами.
внешний интерфейс
Фоновая система управления представляет собой обычный vue+element-ui, который является относительно распространенным.Я не буду здесь вдаваться в подробности, а в основном расскажу о разрабатываемом мышлении и проблемах, с которыми сталкивается пользователь.
Используемая технология
- vue
- vuex
- vue-router
- cube-ui
- Axios
- ....
реализовать функцию
- Зарегистрировать функцию входа
- Добавление, удаление, изменение и поиск адреса пользователя
- Отображение списка компаний
- Отображение страницы сведений о продавце
- список продуктов
- Страница сведений о еде
- Поиск продавца
- ....
- мобильная раскладка
В проекте используется amfe-flexible+px2rem-loader для адаптации к мобильному терминалу.
добавить в package.json
"plugins": {
"autoprefixer": {},
"postcss-px2rem": {
"remUnit": 37.5
}
}
- axios выполняет унифицированный запрос и перехват
Основная цель здесь — перехватить ответ, всплывающее напоминание, когда запрос является ненормальным, перейти на страницу входа в систему, когда состояние пользователя является ненормальным, и добавить параметр перенаправления, чтобы гарантировать, что предыдущая страница может быть возвращена после входа в систему.
// 添加响应拦截器
AJAX.interceptors.response.use(
function(response) {
const loginError = [10003, 10004]
if (loginError.includes(response.data.status)) {
router.push({
path: '/vue/login/index.html',
query: { redirect: location.href.split('/vue')[1] }
})
} else if (response.data.status != 200) {
Toast.$create({
time: 2000,
type: 'txt',
txt: response.data.message
}).show()
} else {
return response.data
}
},
function(error) {
// 对响应错误做点什么,比如400、401、402等等
if (error && error.response) {
console.log(error.response)
}
return Promise.reject(error)
}
)
- Интегрированный API карты Gaode
Такие поиски адресов — это все данные, возвращаемые вызовом API карты Gaode, который инкапсулирован здесь с помощью миксинов.
/*
* @Descripttion: 高德地图mixins
* @version: 1.0
* @Author: 笑佛弥勒
* @Date: 2020-01-20 20:41:57
* @LastEditors: 笑佛弥勒
* @LastEditTime: 2020-03-07 21:04:19
*/
import { mapGetters } from 'vuex'
// 高德地图定位
export const AMapService = {
data() {
return {
mapObj: '',
positionFinallyFlag: false,
currentPosition: '正在定位...', // 当前地址
locationFlag: false, // 定位结果
longitude: '', // 经度
latitude: '', // 纬度
searchRes: [] // 搜索结果
}
},
computed: {
// 当前城市
currentCity() {
return this.getCurrentCity()
}
},
methods: {
...mapGetters('address', ['getCurrentCity']),
initAMap() {
this.mapObj = new AMap.Map('iCenter')
},
// 定位
geoLocation() {
const that = this
this.initAMap()
this.mapObj.plugin('AMap.Geolocation', function() {
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true, // 是否使用高精度定位,默认:true
timeout: 5000, // 超过5秒后停止定位,默认:无穷大
noIpLocate: 0
})
geolocation.getCurrentPosition((status, result) => {
if (status === 'complete') {
that.longitude = result.position.lng
that.latitude = result.position.lat
that.currentPosition = result.formattedAddress
that.locationFlag = true
} else {
that.locationFlag = false
that.currentPosition = '定位失败'
const toast = that.$createToast({
time: 2000,
type: 'txt',
txt: '定位失败'
})
toast.show()
}
that.positionFinallyFlag = true
})
})
},
// 高德地图搜索服务
searchPosition(keyword) {
const that = this
AMap.plugin('AMap.Autocomplete', function() {
// 实例化Autocomplete
var autoOptions = {
// city 限定城市,默认全国
city: that.currentCity || '全国',
citylimit: false
}
var autoComplete = new AMap.Autocomplete(autoOptions)
autoComplete.search(keyword, function(status, result) {
// 搜索成功时,result即是对应的匹配数据
if (status === 'complete' && result.info === 'OK') {
that.$nextTick(() => {
that.searchRes = []
that.searchRes = result.tips
})
}
})
})
}
}
}
Здесь также есть небольшой момент, возвращаемые результаты мы будем выделять в соответствии с нашими входными данными, например, я ввел Baoan на картинке выше, и Baoan выделен в списке результатов, здесь я использую обычное сопоставление.
filters: {
format(text, stress, keyword) {
if (stress) {
const reg = new RegExp(keyword, 'ig')
return text.replace(reg, item => {
return `<span style="color:#666">${item}</span>`
})
} else {
return text
}
}
},
- Единое управление API, маршрутизатором и vuex
Здесь я следую методу управления проектом нашей компании, разделяю маршрутизацию интерфейса и данные vuex через функции, а затем выставляю их через index.js.
К некоторым страницам можно получить доступ только войдя в систему.Есть также ограничения на стороне защиты маршрутизации.Пока needLogin добавляется к сути маршрутизации, ее можно контролировать.
router.beforeEach(async(to, from, next) => {
// 做些什么,通常权限控制就在这里做哦
// 必须写next()哦,不然你的页面就会白白的,而且不报错,俗称"代码下毒"
if (to.meta.needLogin) {
const res = await api.isLogin()
if (!res.data) {
router.push({
path: '/vue/login/index.html',
query: { redirect: to.path.split('/vue')[1] }
})
}
store.commit('common/SETUSERINFO', res.data || {})
}
next()
})
- Управление иконками
Иконки в проекте все импортированные векторные иконки Али.После регистрации учетной записи на официальном сайте библиотеки векторных иконок Али создайте новый склад, добавьте все нужные вам иконки на новый склад, а затем введите онлайн-ссылку в vue Его можно использовать напрямую без каких-либо хлопот, и он даже не стоит денег.
@font-face {
font-family: 'iconfont'; /* project id 1489393 */
src: url('//at.alicdn.com/t/font_1489393_8te3wqguyau.eot');
src: url('//at.alicdn.com/t/font_1489393_8te3wqguyau.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_1489393_8te3wqguyau.woff2') format('woff2'),
url('//at.alicdn.com/t/font_1489393_8te3wqguyau.woff') format('woff'),
url('//at.alicdn.com/t/font_1489393_8te3wqguyau.ttf') format('truetype'),
url('//at.alicdn.com/t/font_1489393_8te3wqguyau.svg#iconfont') format('svg');
}
.iconfont{
font-family:"iconfont" !important;
font-size:16px;font-style:normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
-moz-osx-font-smoothing: grayscale;
}
- Потяните вниз, чтобы обновить пакет
Выпадающее меню для обновления — наиболее распространенная функция, логика почти каждой используемой страницы одинакова, здесь она также инкапсулирована, чтобы избежать повторной разработки.
/*
* @Descripttion: 加载更多Mixins
* @version: 1.0
* @Author: 笑佛弥勒
* @Date: 2020-01-26 15:39:12
* @LastEditors : 笑佛弥勒
* @LastEditTime : 2020-02-10 23:15:57
*/
export default {
data() {
return {
page: 1,
pageSize: 20,
requireFinallyFlag: true, // 当次请求是否完成
totalPage: 1,
allLoaded: false // 数据是否全部加载完成
}
},
mounted() {
document.addEventListener('scroll', this.handleScroll)
},
destroyed() {
document.removeEventListener('scroll', this.handleScroll)
},
methods: {
handleScroll() {
const windowHeight = document.documentElement.clientHeight
const scrollTop = document.documentElement.scrollTop
const bodyHeight = document.body.scrollHeight
const totalHeight = parseFloat(windowHeight + scrollTop, 10)
// 考虑不同浏览器的交互,可能顶部条隐藏之类的,导致页面高度变高
const browserOffset = 60
if (bodyHeight < totalHeight + browserOffset && this.page <= this.totalPage && this.requireFinallyFlag) {
this.page++
if (this.page > this.totalPage) {
this.allLoaded = true
} else {
this.requireFinallyFlag = false
this.loadingMore()
}
}
}
}
}
- Переключение между страницами A, B, C, проблема с сохранением данных
Возьмите страницу B в качестве промежуточной страницы, страницы A->B, B должны быть совершенно новыми страницами, страницы B->C->B, B должны сохранить предыдущее содержимое, этот проект является примером добавления адреса, введите новый адрес в первый раз Требуется совершенно новая страница, перейдите на страницу поиска адреса в процессе выбора адреса, а после возврата добавьте новую страницу, чтобы сохранить ранее заполненную информацию. Перед этим требованием я сначала сохраняю выравнивание страницы B, а затем оцениваю имя следующего маршрута, чтобы увидеть, нужно ли сбрасывать параметры. Конечно, это все еще относительно мало. Вот еще одна идея.include
, будет кешироваться только компонент, имя которого совпадает. Мы можем динамически удалить эту переменную через vuex, чтобы добиться желаемого эффекта. Если следующей страницей является страница выбора адреса, кешируем компонент, в противном случае удаляем кеш компонента.
beforeRouteLeave(to, from, next) {
console.log('--------------beforeRouteLeave----------')
if (to.name == 'searchAddress') {
this.ADDCACHE('AddAddress')
} else {
this.DELCACHE('AddAddress')
}
next()
},
Развертывание проекта
Готов к работе:
- Подать заявку на доменное имя
- купить сервер
- Установите необходимое программное обеспечение (git, node, mysql, nginx, docker...)
- Будьте готовы шагнуть в яму...
- Я купил и доменное имя, и сервер на Alibaba Cloud.Более хлопотно то, что доменное имя нужно сделать резервной копией.Это займет некоторое время.Я не планировал покупать доменное имя,но будет проблема ., система фонового управления и внешний интерфейс имеют один и тот же IP-адрес, поэтому файлы cookie будут связаны друг с другом., и, наконец, заставили купить доменное имя.
- Конфигурация доменного имени, для этого требуется настройка IP-адреса вашего сервера и вашего доменного имени в фоновом режиме Alibaba Cloud.Далее идет настройка nginx.Есть два момента.Первый – указать доменное имя на адрес вашего сервера при доступе к доменному имени , а второй — для прямого доступа к доменному имени. Вам нужно изменить доменное имя на адрес вашей домашней страницы.
server{
listen 80;
server_name www.smileele.net;
rewrite ^/$ http://$host/vue/main/index.html$1 break;
location / {
proxy_pass http://120.79.131.113:9529/;
}
}
Так как это http, то он слушает порт 80. При посещении www.smileele.net он меняется на www.smileele.net/vue/main/index.html, а www.smileele.net соответствует ip
- Чтобы написать файл Dockerfile, я только контейнеризирую проект vue с помощью docker, поэтому единственное программное обеспечение, которое необходимо загрузить в контейнер docker, — это node и nginx.Содержимое файла выглядит следующим образом.
FROM node:12.14.0
WORKDIR /app
COPY package*.json ./
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
RUN cnpm install
COPY ./ /app
RUN npm run build
FROM nginx
RUN mkdir /app
COPY --from=0 /app/dist /app
COPY nginx.conf /etc/nginx/nginx.conf
Укажите версию узла и загрузите его, установите рабочий каталог в каталог /app, установите зависимости и упакуйте их. Загрузите nginx, скопируйте содержимое дистрибутива, который был достаточно дешев, в каталог приложения и замените каталог конфигурации nginx.
Файлы конфигурации в nginx следующие:перекрестный доментакже решил здесьserver{
listen 8080;
server_name 120.79.131.113;
root /app; # 指向目录
index index.html;
location /api {
proxy_pass http://120.79.131.113:7001;
}
location / {
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
- Сборка Docker, для облегчения автоматического развертывания Jenkins предоставляется файл сценария
docker run -p 9529:8080 -d --name ele_index_vue ele/index/vue:$image_version;
После сопоставления порта в контейнере и порта хоста хост может получить доступ к содержимому образа, обратившись к 9529.
- Установите Jenkins и подключитесь к собственному GitHub. Здесь Jenkins также устанавливается через докер.
Выше приведено введение проекта.Если вам интересно, вы можете скачать проект и посмотреть.Если вам нужен дизайн таблицы базы данных, вы можете добавить меня.Я могу отправить его вам.WeChat: smile_code_0312
адрес гитхаба:
Наконец, у меня есть планы сменить работу в последнее время, и я прошу вас представить меня, переднюю часть 19-й курицы-новичка, скромную охоту за работой.