Доброе утро, сегодня, поделиться с людьми для передней для всехAPIНебольшой опыт и взгляд на многоуровневую архитектуру. Архитектурный дизайн — это бесконечная дорога, лучшего нет, есть только лучше. Этот принцип применим к различным сценариям проектирования программного обеспечения,APIДизайн слоя не является исключением.Если вы чувствуете, что при вызове интерфейса еще много слотов, это означает, что ваша архитектура слоя интерфейса нуждается в оптимизации. Сегодня я беруvue + axiosНапример, чтобы разобраться с некоторыми из моих опытов и идей для вас.
боль каменного века
позвонить напрямуюaxios, Это действительно болезненно, каждое место, куда вы звоните, должно оценивать статус ответа, и слишком много избыточных кодов.
import axios from "axios"
axios.get('/usercenter/user/page?pageNo=1&pageSize=10').then(res => {
const data = res.data
// 判断请求状态,success字段为true代表成功,视前后端约束而定
if (data.success) {
// 结果成功后的业务代码
} else {
// 结果失败后的业务代码
}
})
Это кажется действительно неудобным, каждый раз, когда вызывается интерфейс, столько повторяющейся работы!
Бронзовый век, вполне удовлетворительно
Для решения прямого вызоваaxiosболевые точки, мы обычно используемPromiseправильноaxiosВторичная инкапсуляция, централизованное суждение о статусе ответа интерфейса и внешнем воздействииget, post, put, deleteЖдатьhttpметод.
вторичная упаковка аксиос
import axios from "axios"
import router from "@/router"
import { BASE_URL } from "@/router/base-url"
import { errorMsg } from "@/utils/msg";
import { stringify } from "@/utils/helper";
// 创建axios实例
const v3api = axios.create({
baseURL: process.env.BASE_API,
timeout: 10000
});
// axios实例默认配置
v3api.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded';
v3api.defaults.transformRequest = data => {
return stringify(data)
}
// 返回状态拦截,进行状态的集中判断
v3api.interceptors.response.use(
response => {
const res = response.data;
if (res.success) {
return Promise.resolve(res)
} else {
// 内部错误码处理
if (res.code === 1401) {
errorMsg(res.message || '登录已过期,请重新登录!')
router.replace({ path: `${BASE_URL}/login` })
} else {
// 默认的错误提示
errorMsg(res.message || '网络异常,请稍后重试!')
}
return Promise.reject(res);
}
},
error => {
if (/timeout\sof\s\d+ms\sexceeded/.test(error.message)) {
// 超时
errorMsg('网络出了点问题,请稍后重试!')
}
if (error.response) {
// http状态码判断
switch (error.response.status) {
// http status handler
case 404:
errorMsg('请求的资源不存在!')
break
case 500:
errorMsg('内部错误,请稍后重试!')
break
case 503:
errorMsg('服务器正在维护,请稍等!')
break
}
}
return Promise.reject(error.response)
}
)
// 处理get请求
const get = (url, params, config = {}) => v3api.get(url, { ...config, params })
// 处理delete请求,为了防止和关键词delete冲突,方法名定义为deletes
const deletes = (url, params, config = {}) => v3api.delete(url, { ...config, params })
// 处理post请求
const post = (url, params, config = {}) => v3api.post(url, params, config)
// 处理put请求
const put = (url, params, config = {}) => v3api.put(url, params, config)
export default {
get,
deletes,
post,
put
}
Вызывающий больше не определяет статус запроса
import api from "@/api";
methods: {
getUserPageData() {
api.get('/usercenter/user/page?pageNo=1&pageSize=10').then(res => {
// 状态已经集中判断了,这里直接写成功的逻辑
// 业务代码......
const result = res.result;
}).catch(res => {
// 失败的情况写在catch中
})
}
}
асинхронный/ожидающий дооснащения
Использование семантических асинхронных функций
methods: {
async getUserPageData() {
try {
const res = await api.get('/usercenter/user/page?pageNo=1&pageSize=10')
// 业务代码......
const { result } = res;
} catch(error) {
// 失败的情况写在catch中
}
}
}
существующие проблемы
- Степень семантики ограничена, и вызов интерфейса по-прежнему требует запроса интерфейса.
url - внешний интерфейс
apiСлой сложно поддерживать, если меняется внутренний интерфейс, внешний интерфейс нужно сильно менять. - если
UIСуществуют различия между моделью данных компонента и структурой данных, необходимой для внутреннего интерфейса.Обработка данных должна выполняться перед вызовом каждого интерфейса, чтобы сгладить различия, такие как[1,2,3]Перемена1,2,3Это (конечно, это только самый простой пример). Таким образом, если данные обрабатываются небрежно, у вызывающего абонента высока вероятность ошибок! - Трудно выполнить специальные сценарии, например, сценарий запроса, внутренние требования, если введено ключевое слово поиска.
keyword, надо позвонить/user/searchИнтерфейс, если ключевое слово не введено, его можно только вызвать/user/pageинтерфейс. Если каждый вызывающий абонент должен оценить, было ли введено ключевое слово, а затем решить, какой интерфейс вызывать, какова, по вашему мнению, вероятность ошибки и раздражает ли его использование? - В продукте сказано, что эти сценарии нужно оптимизировать, и по умолчанию стоит сортировка по времени создания в порядке убывания. стирать и менять по одному?
- ......
Итак, как решить эти проблемы? Пожалуйста, наберитесь терпения и смотрите...
Железный век, это круто
Решение, которое я имею в виду, состоит в том, чтобы добавить еще один слой между базовой инкапсуляцией и вызывающей стороной.APIУровень адаптации (уровень адаптации, принимающий специальное значение), уровень адаптации унифицирует обработку, включая параметры обработки, обработку заголовка запроса, специализированную обработку, для извлечения более семантического способа, позволяющего вызывающему абоненту звонить в стиле «Дурак», а не находить интерфейсurlи беспокоясь о повторяющейся работе со структурами данных, поместитеViewModelМодель данных, привязанная к слою, передается непосредственно на уровень адаптации для унифицированной обработки.
Согласуйте микросервисную архитектуру
Во-первых, чтобы согласовать внутреннюю микросервисную архитектуру, во внешнемAPIЗвонок разделен на три модуля.
├─api
index.js axios底层封装
├─base 负责调用基础服务,basecenter
├─iot 负责调用物联网服务,iotcenter
└─user 负责调用用户相关服务,usercenter
Под каждым модулем определяется единое пространство имен микросервисов, например/src/api/user/index.js:
export const namespace = 'usercenter';
функциональный модуль
Каждая функция имеет независимыйjsМодуль, взяв в качестве примера интерфейс, связанный с управлением ролями, модуль/src/api/user/role.js
import api from '../index'
import { paramsFilter } from "@/utils/helper";
import { namespace } from "./index"
const feature = 'role'
// 添加角色
export const addRole = params => api.post(`/${namespace}/${feature}/add`, paramsFilter(params));
// 删除角色
export const deleteRole = id => api.deletes(`/${namespace}/${feature}/delete`, { id });
// 更新角色
export const updateRole = params => api.put(`/${namespace}/${feature}/update`, paramsFilter(params));
// 条件查询角色
export const findRoles = params => api.get(`/${namespace}/${feature}/find`, paramsFilter(params));
// 查询所有角色,不传参调用find接口代表查询所有角色
export const getAllRoles = () => findRoles();
// 获取角色详情
export const getRoleDetail = id => api.get(`/${namespace}/${feature}/detail`, { id });
// 分页查询角色
export const getRolePage = params => api.get(`/${namespace}/${feature}/page`, paramsFilter(params));
// 搜索角色
export const searchRole = params => params.keyword ? api.get(`/${namespace}/${feature}/search`, paramsFilter(params)) : getRolePage(params);
-
Каждый интерфейс основан на
RESTfulстиль, вызывая приращение(api.post)удалять(api.deletes)изменять(api.put)чек об оплате(api.get) базового метода и внешнего вывода семантического метода. -
называется
urlОн состоит из трех частей в формате:/微服务命名空间/特性命名空间/方法 -
Соглашение об именовании функций уровня адаптации интерфейса:
-
- Добавлен:
addXXX - удалять:
deleteXXX - обновить:
updateXXX - Запросить записи по ID:
getXXXDetail - Условный запрос для записи:
findOneXXX - Условный запрос:
findXXXs - Запросить все записи:
getAllXXXs - Пейджинговый запрос:
getXXXPage - поиск:
searchXXX - Остальные интерфейсы персонализации названы в соответствии с семантикой
- Добавлен:
Решать проблему
-
Высшая степень семантики, сотрудничество
vscodeФункция подсказки кода , не будьте слишком круты для использования! -
Быстрая реакция на изменения интерфейса, унифицированная обработка адаптационным слоем
-
Централизованная обработка данных (для обработки общедоступных данных мы используем
paramsFilterРешите для особых случаев, а затем разберитесь отдельно), звонящий может чувствовать себя непринужденно при ведении бизнеса. -
Для удовлетворения специальных сценариев, буддийская система имеет дело с друзьями и продуктами
- Для сценария запроса по ключевому слову, упомянутого в предыдущем разделе, мы оцениваем, существует ли
keywordполе, решите позвонитьsearchещеpageинтерфейс. Нам просто нужно разоблачитьsearchRoleметод, вызывающей стороне нужно только вызватьsearchRoleметод, никаких других соображений не требуется.
export const searchRole = params => params.keyword ? api.get(`/${namespace}/${feature}/search`, paramsFilter(params)) : getRolePage(params);- Для заказа продукта при внезапном увеличении спроса мы можем работать с параметрами по умолчанию, чтобы сделать слой адаптации.
Во-первых, мы создаем новый, предназначенный для управления параметрами по умолчанию.
js,Такие какsrc/api/default-options.js// 默认按创建时间降序的参数对象 export const SORT_BY_CREATETIME_OPTIONS = { sortField: 'createTime', // desc代表降序,asc是升序 sortType: 'desc' }Далее делаем централизованную обработку на уровне адаптации интерфейса
import api from '../index' import { SORT_BY_CREATETIME_OPTIONS } from "../default-options" import { paramsFilter } from "@/utils/helper"; import { namespace } from "./index" const feature = 'role' export const getRolePage = params => api.get(`/${namespace}/${feature}/page`, paramsFilter({ ...SORT_BY_CREATETIME_OPTIONS, ...params }));SORT_BY_CREATETIME_OPTIONSОно помещается впереди, чтобы удовлетворить другие требования сортировки, а поле сортировки, переданное вызывающей стороной, может переопределить параметры по умолчанию. - Для сценария запроса по ключевому слову, упомянутого в предыдущем разделе, мы оцениваем, существует ли
издеваться над первым
идеально подходитAPIДизайн слоев определенно неотделимmockиз. Прежде чем серверная часть предоставит интерфейс, внешний интерфейс должен быть разработан параллельно с использованием смоделированных данных, иначе прогресс не может быть гарантирован. Так как же разработать интерфейс, максимально совместимый с реальным интерфейсом?mockЧто насчет системы? Я просто поделюсь здесь.
- Сначала создайте
mockпреданныйaxiosПример
мы вsrcновый каталогmockкаталог и вsrc/mock/index.jsпростой пакетaxiosПример
// 仅限模拟数据使用
import axios from "axios"
const mock = axios.create({
baseURL: ''
});
// 返回状态拦截
mock.interceptors.response.use(
response => {
return Promise.resolve(response.data)
},
error => {
return Promise.reject(error.response)
}
)
export default mock
-
mockОн также разделен на модули дляusercenterУправление ролями в микросервисахmockИнтерфейс как пример
├─mock
index.js mock底层axios封装
├─user 负责调用基础服务,usercenter
├─role
├─index.js
мы вsrc/mock/user/role/index.jsПростое моделирование интерфейса для получения всех ролейgetAllRoles
import mock from "@/mock";
export const getAllRoles = () => mock.get('/static/mock/user/role/getAllRoles.json')
Как видите, мыmockполучено из интерфейсаstatic/mockв каталогеjsonданные. Поэтому нам нужно подготовиться в соответствии с интерфейсным документом или согласованной структурой данных.getAllRoles.jsonданные
{
"success": true,
"result": {
"pageNo": 1,
"pageSize": 10,
"total": 2,
"list": [
{
"id": 1,
"createTime": "2019-11-19 12:53:05",
"updateTime": "2019-12-03 09:53:41",
"name": "管理员",
"code": "管理员",
"description": "一个拥有部分权限的管理员角色",
"sort": 1,
"menuIds": "789,2,55,983,54",
"menuNames": "数据字典, 后台, 账户信息, 修改密码, 账户中心"
},
{
"id": 2,
"createTime": "2019-11-27 17:18:54",
"updateTime": "2019-12-01 19:14:30",
"name": "前台测试",
"code": "前台测试",
"description": "一个拥有部分权限的前台测试角色",
"sort": 2,
"menuIds": "15,4,1",
"menuNames": "油耗统计, 车联网, 物联网监管系统"
}
]
},
"message": "请求成功",
"code": 0
}
- Давайте посмотрим
mockКак ты сделал это?
Давайте сначала посмотрим на вызывающий метод реального интерфейса.
import { getAllRoles } from "@/api/user/role";
created() {
this.getAllRolesData()
},
methods: {
async getAllRolesData() {
const res = await getAllRoles()
console.log(res)
}
}
Такmockкак это сделать? очень просто, просто положиmockзаменить метод, указанный вapiпредоставленный метод.
// import { getAllRoles } from "@/api/user/role";
import { getAllRoles } from "@/mock/user/role";
Видно, что этоmockСоответствие между методом и вызовом реального интерфейса довольно высокое, когда интерфейс официально отлажен, вам нужно только настроить закомментированный код, и переход очень плавный!
- Обратите внимание, что в производственной среде для предотвращения упаковки
static/mockсодержимое каталогаcopyприбытьdistкаталог, нам нужно настроитьCopyWebpackPlugin,кvue-cli@2Например, мы модифицируемwebpack.base.conf.jsВот и все.
const devMode = process.env.NODE_ENV === 'development';
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: devMode ? config.dev.assetsSubDirectory : config.build.assetsSubDirectory,
ignore: devMode ? '' : 'mock/**/*'
}
])
Паровой век, действительно ароматный
Далее представьте, используйте type-safetypescript, пусть передний конецAPIУровень действительно реализует программирование интерфейса документа, стандартизируя входные параметры, выходные параметры, дополнительные параметры и т. д., что повышает удобство сопровождения и значительно снижает вероятность ошибок на этапе кодирования. Хотя он все еще находится в стадии рефакторинга, я хотел бы сказать, чтоtypescriptОн действительно ароматный, я вдруг скучаю по немуAngularПрошло два года, ждуvue3.0способенtypescriptСочетается более идеально ...
Электрический век, больше воображения
Будущее открывает безграничные возможности. Перед лицом все более сложных и разнообразных бизнес-сценариев мы будем извлекать лучшие архитектурные и дизайнерские шаблоны. В настоящее время существует незрелая идея, может ли он быть более стандартизированным в дизайне интерфейса, и в то же время внутренние документы интерфейса вывода извлекатьAPI jsonструктуры данных, как? внешний интерфейсAPI json,пройти черезnodejsВозможность файлового программирования, автоматическая генерация кода внешнего интерфейса, свободные руки.
Эпилог
Конечно, все вышеизложенное — лишь часть моего опыта и предположений. Это то, что я могу придумать в пределах своих возможностей. Надеюсь, это поможет некоторым нуждающимся студентам. Если у больших парней больше опыта, вы можете подсказать.
Прошлые основные моменты:
яTusi, небольшой фронтенд-лидер начинающей компании, все еще беспокоящийся о ежедневном написании бесконечных бизнес-кодов, накоплении технологий на пути к полировке продуктов и изучении путей роста. Если вы похожи на меня и думаете о собственном технологическом росте и ценности, добро пожаловать, добавьте меня в WeChat для общения и обсуждения, WeChat IDice_lloly. я буду в публичном аккаунтеБольшой салон фронтенд техникии апплетБлог ТусиСинхронизируйте содержимое блога, приходите и дразните меня!