Commit c5f4dc3b authored by 郭志伟's avatar 郭志伟

feat(index.js): 封装请求包

parents
Pipeline #851 canceled with stages
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
node_modules/*
build/*
config/*
dist/*
lib/*
static/*
\ No newline at end of file
module.exports = {
root: true,
env: {
node: true,
browser: true,
},
extends: ["eslint:recommended", "plugin:prettier/recommended"],
parserOptions: {
parser: "babel-eslint"
},
// TODO 配置强制使用模板字符串
rules: {
'prettier/prettier': 'error',
'no-empty': ["error", { "allowEmptyCatch": true }],
'arrow-parens': 'off', // allow paren-less arrow functions
'generator-star-spacing': 'off', // allow async-await
'no-unused-vars': 'error', // disabled no ununsed var
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // no use debugger in production
'indent': [2, 2, { SwitchCase: 1 }], // 2 space for tab for perttier
'space-before-function-paren': ['error', 'never'], // no space in function name for perttier
}
};
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
.eslintcache
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
// prettier.config.js or .prettierrc.js
module.exports = {
printWidth: 100, // 设置prettier单行输出(不折行)的(最大)长度
tabWidth: 2, // 设置工具每一个水平缩进的空格数
semi: true, // 在语句末尾添加分号
singleQuote: true, // 使用单引号而非双引号
arrowParens: 'avoid', // 为单行箭头函数的参数添加圆括号,参数个数为1时可以省略圆括号
rangeStart: 0, // 只格式化某个文件的一部分
rangeEnd: Infinity, // 只格式化某个文件的一部分
};
# webpack-cos-cdn-upload-plugin
webpack静态资源上传腾讯云插件
## 如何打包
1. 全局安装rollup
2. npm run build
3. 更新版本号,npm publish 到私服
## 如何使用
```js
// webpack config
const WebpackCosCdnUploadPlugin = require('webpack-cos-cdn-upload-plugin');
plugins: [
new WebpackCosCdnUploadPlugin()
]
```
## Props
### options
对象中包含以下参数,如果不设置会走默认配置
| 名称 | 说明 | 默认值 |
| -------------- | ------------------------------------------------------------ | ----------------------------------------------- |
| SecretId | 腾讯云SecretId | `AKIDVlxtVqOK9i0wc0m0e7C5saATZnl2xvUx` |
| SecretKey | 腾讯云SecretKey | `NWQ3VlmWeFtIQHrDI6F9oCheMq41lGVV` |
| ImageBucket | 图片资源存放Bucket | `image-1258270469` |
| MiscBucket | 逻辑资源存放Bucket | `misc-1258270469` |
| ImageHost | 图片资源访问域名 | `https://img.lkbang.net` |
| MiscHost | 逻辑资源访问域名 | `https://misc.lkbang.net` |
| AppId | 腾讯云Bucket AppId | `1258270469` |
| Region | 腾讯云Bucket 存放区域 | `ap-beijing` |
| UseSTS | 是否使用临时授权,默认不使用,**临时授权有访问频率限制** | `false` |
| ProjectName | 自定义项目名称,会影响资源访问地址,默认以打包目录名称的md5 hash前8位作为项目名称 | `''` |
| TestEnvSkip | 测试环境是否上传,默认不上传 | `true` |
| PluginName | 插件名称 | `webpack-cos-cdn-upload-plugin` |
| PluginNameAbbr | 插件名称缩写,输出用 | `COS2CDN` |
| MiscExtMap | 逻辑资源扩展名名单,可将非逻辑资源扩展名,添加到此处 | `[ '.css', '.js', '.json', '.mainfest' ]` |
| FontsExtMap | 字体资源扩展名名单,字体文件单独存放 | `[ '.ttf', '.woff', '.woff2', '.otf', '.svg' ]` |
| IgnoreExtMap | 不上传资源扩展名名单 | `[ '.map' ]` |
## TODO
- [ ] upload方法可扩展
module.exports = {
presets: [ '@babel/env' ],
plugins: [
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining'
]
};
/*
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/en/configuration.html
*/
module.exports = {
modulePaths: [
'./',
],
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/jh/qv5nc8p56mq32pf12m_r8zy40000gn/T/jest_dx",
// Automatically clear mock calls and instances between every test
// clearMocks: false,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
// coverageDirectory: undefined,
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
// coverageProvider: "babel",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
moduleDirectories: [
'node_modules',
'bower_components',
],
// An array of file extensions your modules use
moduleFileExtensions: [
'js',
'json',
'jsx',
'ts',
'tsx',
'node',
],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: 'node',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
testURL: 'http://localhost',
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
transform: {
'.jsx?$': 'babel-jest',
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
{
"compilerOptions": {
"target": "es2017",
"allowSyntheticDefaultImports": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"~@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"],
"include": ["src"]
}
This diff is collapsed.
{
"name": "@qg/ui-request",
"version": "0.0.4",
"description": "axios策略请求封装",
"main": "dist/index.js",
"scripts": {
"test": "jest",
"build": "rollup -c",
"lint": "eslint ./ --fix",
"jest": "jest",
"version": "npm run build && git add -A",
"postversion": "git push --no-verify"
},
"author": "gzw",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.3",
"@babel/plugin-transform-runtime": "^7.13.2",
"@babel/preset-env": "^7.12.1",
"@rollup/plugin-babel": "^5.2.1",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-eslint": "^8.0.1",
"babel-jest": "^26.6.3",
"eslint": "^7.20.0",
"eslint-config-egg": "^9.0.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.5",
"eslint-plugin-prettier": "^3.3.1",
"husky": "^5.1.1",
"jest": "^26.6.3",
"prettier": "^2.2.1",
"rollup": "^2.39.1",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-visualizer": "^4.2.0"
},
"dependencies": {
"axios": "^0.19.2"
}
}
import babel from '@rollup/plugin-babel';
import eslint from '@rollup/plugin-eslint';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import visualizer from 'rollup-plugin-visualizer';
import pkg from './package.json';
export default {
input: 'src/index.js',
external: ['webpack-log'],
output: [
{
file: pkg.main,
format: 'cjs',
},
],
plugins: [
babel({
exclude: 'node_modules/**',
presets: [ '@babel/preset-env' ],
}),
eslint({
throwOnError: true,
include: ['src/**/*.ts'],
exclude: ['node_modules/**', 'lib/**'],
}),
commonjs(),
terser(),
visualizer(),
],
};
import { isAndroid, isIOS, isWechat } from './utils';
import localStorage from './localStorage';
function getURLSearchParams(json) {
if (!json) return '';
const dataArray = Object.keys(json).map(key => {
if (json[key] === undefined) return '';
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]);
});
return dataArray.filter(item => item !== undefined && item !== null).join('&');
}
function getVccChannel() {
return isWechat
? localStorage.get('vccChannel') || ''
: (isAndroid ? '159905' : isIOS ? '159904' : localStorage.get('vccChannel')) || '';
}
export default function strategyModes(toastFn = () => {}) {
return {
service: {
response(res) {
const { data, config } = res;
// api需要返回原始响应
const returnRawData = config.rawData;
if (returnRawData) return [data, null];
const success =
(data.code === 0 && data.business_code === 0) ||
(data.code === '0000' && data.businessCode === '0000');
// 业务完成,返回有效载荷
if (success) {
return [data.data, null];
}
// 接下来处理异常业务
const msg = data.msg || '服务异常';
if (!config.hideToast || !data.noAlert) {
toastFn(msg);
}
// 给用户提示信息,明确发生错误
// 请求参数和响应数据都可以控制不提示
let error = new Error(msg);
error.response = data;
console.error(error);
// 业务代码可以根据response进行再处理
return [null, error];
},
request(cfg) {
const { method, hideVccChannel } = cfg;
cfg.headers['X-Auth-Token'] = localStorage.get('vccToken') || '';
if (cfg.customHeader) {
Object.assign(cfg.headers, cfg.customHeader);
}
if (cfg.creditToken) {
cfg.headers['X-Auth-Token'] = localStorage.get('creditToken');
}
if (!hideVccChannel) {
cfg.headers['vccChannel'] = getVccChannel();
}
if (method === 'post' && cfg.emulateJSON) {
cfg.headers['Content-Type'] = 'application/x-www-form-urlencoded';
cfg.data = getURLSearchParams(cfg.data);
}
return cfg;
}
}
};
};
import axios from 'axios';
import strategyModes from './config';
const ERR_MESSAGE_MAP = {
status: {
400: '错误请求',
401: '您未登录或登录超时,请重新登录',
403: '拒绝访问',
404: '请求错误,未找到该资源',
405: '请求方法未允许',
408: '请求超时',
500: '服务器端出错',
501: '网络未实现',
502: '网络错误',
503: '服务不可用',
504: '网络超时',
505: 'http版本不支持该请求'
}
};
const defaultStratege = {
default: {
request(cfg) {
return cfg;
},
response(res) {
return res.data;
}
}
};
const defaultConfig = {
timeout: 15000
};
class HttpRequest {
constructor(strategy = {}, config = {}, toastFn = () => {}, loadingFn = () => {}) {
this.CancelToken = axios.CancelToken;
this.instance = axios.create({ ...defaultConfig, ...config });
this.pending = {};
this.reqNum = 0;
this.timeId = null;
this.toastFn = toastFn;
this.loadingFn = loadingFn;
this.strategyModes = { ...strategyModes(this.toastFn), ...strategy };
this.strategyModes.default = defaultStratege;
this.initRequestInterceptors();
this.initResponseInterceptors();
return this.instance;
}
beforeRequest() {
this.reqNum++;
clearTimeout(this.timeId);
this.timeId = setTimeout(() => {
this.loadingFn(true);
}, 1300);
}
afterRequest() {
this.reqNum--;
if (this.reqNum <= 0) {
this.clearRequest();
this.loadingFn(false);
}
}
clearRequest() {
clearTimeout(this.timeId);
}
setStrategy(strategy = {}, cover = false) {
this.strategyModes = cover ? strategy : { ...this.strategyModes, ...strategy };
}
setLoadingFn(fn = () => {}) {
this.loadingFn = fn;
}
setToastFn(fn = () => {}) {
this.toastFn = fn;
}
initRequestInterceptors() {
const self = this;
this.instance.interceptors.request.use(
config => {
!config.hideLoading && self.beforeRequest(config.url || '');
// 发起请求时,取消掉当前正在进行的相同请求
if (self.pending[config.url]) {
self.pending[config.url]('取消重复请求');
}
config.cancelToken = new self.CancelToken(c => (self.pending[config.url] = c));
// 使用默认响应处理策略
if (!config.strategy) {
config.strategy = 'service';
}
if (self.strategyModes[config.strategy].request) {
config = self.strategyModes[config.strategy].request(config);
}
return config;
},
error => {
return [null, error];
}
);
}
initResponseInterceptors() {
const self = this;
this.instance.interceptors.response.use(
response => {
if (response.config.url) {
!response.config.hideLoading && self.afterRequest();
delete self.pending[response.config.url || ''];
}
return self.strategyModes[response.config.strategy].response(response);
},
err => {
self.afterRequest();
let message = '';
let showToast = true;
if (err.message === 'Network Error' && !err.response) {
// 网络异常: 错误域名,
message = '服务不可用';
} else if (err.response && err.response.status) {
message =
ERR_MESSAGE_MAP.status[err.response.status] || `未知异常码: ${err.response.status}`;
} else if (err.message === '取消重复请求') {
message = '取消重复请求';
showToast = false;
}
showToast && this.toastFn(message || '服务异常,请稍后重试');
const error = new Error(message);
console.error(error);
return [null, error];
}
);
}
}
export default HttpRequest;
export default {
get(key) {
let result = window.localStorage.getItem(key);
try {
return JSON.parse(result);
} catch (e) {
return result;
}
},
set(key, value) {
let toString = Object.prototype.toString;
if (toString.call(value) === '[object Array]' || toString.call(value) === '[object Object]') {
value = JSON.stringify(value);
}
return window.localStorage.setItem(key, value);
},
remove(key) {
return window.localStorage.removeItem(key);
},
clear() {
return window.localStorage.clear();
}
};
const ua = window.navigator.userAgent.toLowerCase();
// 判断微信环境
export const isWechat = ua.match(/MicroMessenger/i) == "micromessenger";
// 判断IOS环境
export const isIOS = /iphone|ipad|ipod/.test(ua);
// 判读Android环境
export const isAndroid = /android/.test(ua);
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment