feat: first commit
This commit is contained in:
79
src/http/api.ts
Normal file
79
src/http/api.ts
Normal file
@ -0,0 +1,79 @@
|
||||
// export const DOMAIN = 'http://10.147.18.100:8082';
|
||||
export const DOMAIN = 'https://neighbourhood.cn';
|
||||
|
||||
|
||||
export const BASE_URL = `${DOMAIN}/api`;
|
||||
|
||||
export enum API {
|
||||
// 用户级别
|
||||
LOGIN = '/user/login', // 登录
|
||||
SET_PHONE = '/user/setPhone', // 设置手机号
|
||||
USER = '/user/get', // 获取用户信息
|
||||
USER_UPDATE = '/user/update', // 更新用户信息
|
||||
APP_MODE_SWITCH = '/user/switchRole',
|
||||
// subscribe
|
||||
SUBSCRIBE = '/user/subscribe',
|
||||
CANCEL_SUBSCRIBE = '/user/cancelSubscribe',
|
||||
// 群
|
||||
FOLLOW_GROUP = '/userGroup/follow', // 关注群
|
||||
GROUPS = '/userGroup/list', // 获取我的群列表
|
||||
GROUP_DETAIL = '/userGroup/get', // 获取群的详细信息
|
||||
BATCH_PUBLISH_GROUP_LIST = '/userGroup/listGroupCountByCity', // 支持群发的群列表
|
||||
SIMPLE_GROUP_LIST = '/userGroup/listAll', // 群发可用群简单信息
|
||||
// 位置
|
||||
LOCATION = '/location/get', // 获取位置信息
|
||||
// 通告
|
||||
GET_JOB_CATEGORIES = '/category/list', // 获取通告分类
|
||||
GET_JOB_LIST = '/job/search', // 获取通告列表数据
|
||||
GET_MY_JOB_LIST = '/job/searchMyJobs', // 获取我的/我关注通告列表数据
|
||||
GET_MY_JOB_LIST_V2 = '/user/searchMyJobs', // 获取我的/我关注通告列表数据
|
||||
GET_JOB_DETAIL = '/job/get', // 获取通告详情
|
||||
GET_JOB_MANAGE_LIST = '/job/myjob/list', // 获取通告管理列表
|
||||
CREATE_JOB = '/job/create',
|
||||
UPDATE_JOB = '/job/update',
|
||||
PUBLISH_JOB = '/job/release',
|
||||
CLOSE_JOB = '/job/close',
|
||||
MY_DECLARED_JOB_LIST = '/user/searchMyDeclared', // 我报单的通告列表
|
||||
MY_BROWSED_JOB_LIST = '/user/searchMyBrowsed', // 我浏览过的通告列表
|
||||
MY_RECOMMEND_JOB_LIST = '/user/listMyRecommendJobs', // 获取我的通告推荐列表
|
||||
// 付费产品
|
||||
GET_PRODUCT_LIST = '/product/listMyProduct', // 获取付费产品列表信息
|
||||
GET_PRODUCT_DETAIL = '/product/getMyProductDetail', // 查询某个付费产品的详细信息
|
||||
PRODUCT_USE_RECORD = '/product/getProductUseRecord', // 查询某个产品是否已经解锁
|
||||
USE_PRODUCT = '/product/use', // 使用某个产品扣费/次数
|
||||
ALLOW_BUY_PRODUCT = '/product/allowBuyProduct', // 是否可以购买某个产品
|
||||
CS_QR_CODE = '/customerService/get', // 客服微信二维码
|
||||
CREATE_PAY_ORDER = '/payOrder/create', // 创建支付订单
|
||||
GET_PAY_ORDER = '/payOrder/get', // 订单查询
|
||||
// 资料库
|
||||
SAVE_VIDEOS = '/resume/video/create', // 保存视频列表
|
||||
CREATE_PROFILE = '/resume/create', // 创建模卡身份信息
|
||||
GET_PROFILE = '/resume/get', // 获取主播模卡详情
|
||||
READ_PROFILE = '/resume/read', // 从模卡列表访问模卡
|
||||
VIEW_SHARE_PROFILE = '/resume/readByShareCode', // 分享访问模卡
|
||||
GET_PROFILE_SHARE_CODE = '/resume/getShareCode',
|
||||
UPDATE_PROFILE = '/resume/update', // 更新模卡
|
||||
UPDATE_PROFILE_STATUS = '/resume/updateStatus',
|
||||
// 七牛
|
||||
GET_QI_NIU_TOKEN = '/file/token/get',
|
||||
GET_VIDEO_INFO = '/file/url/get',
|
||||
// 企业
|
||||
CERTIFICATION = '/boss/auth',
|
||||
// 主播
|
||||
GET_ANCHOR_LIST = '/resume/list',
|
||||
// message
|
||||
MESSAGE_REMAIN_PUSH_TIMES = '/user/remainPushTimes',
|
||||
MESSAGE_CHAT_LIST = '/chat/list',
|
||||
MESSAGE_UNREAD_COUNT = '/chat/msg/getUnReadMsgCount',
|
||||
MESSAGE_CREATE_CHAT = '/chat/create',
|
||||
MESSAGE_CHAT_WATCH = '/chat/watch',
|
||||
MESSAGE_CHAT_WATCH_GET = '/chat/watch/get',
|
||||
MESSAGE_CHAT = '/chat/msg/getIndexMessage',
|
||||
MESSAGE_CHAT_NEW = '/chat/msg/list',
|
||||
MESSAGE_SEND_TEXT = '/chat/msg/send',
|
||||
MESSAGE_SEND_ACTION = '/chat/action/send',
|
||||
MESSAGE_GET_ACTION = '/chat/action/get',
|
||||
MESSAGE_GET_ACTION_DETAIL = '/chat/action/getActionDetail',
|
||||
MESSAGE_CONFIRM_ACTION = '/chat/action/confirm',
|
||||
MESSAGE_LIST_STATUS = '/chat/msg/listStatus',
|
||||
}
|
40
src/http/constant.ts
Normal file
40
src/http/constant.ts
Normal file
@ -0,0 +1,40 @@
|
||||
export const TOKEN_KEY = '__boluo_user_token__';
|
||||
export const TOKEN_EXPIRES_TIME = '__boluo_user_token_expires_time__';
|
||||
|
||||
export enum HTTP_STATUS {
|
||||
SUCCESS = 200,
|
||||
FAIL = 417,
|
||||
}
|
||||
|
||||
export enum RESPONSE_ERROR_CODE {
|
||||
INVALID_PARAMETER = 'INVALID_PARAMETER',
|
||||
NOT_FUND = 'NOT_FUND',
|
||||
INNER_EXCEPTION = 'INNER_EXCEPTION',
|
||||
SYSTEM_ERROR = 'SYSTEM_ERROR',
|
||||
NEED_LOGIN = 'NEED_LOGIN',
|
||||
// 模卡分享
|
||||
RESUME_NOT_FUND = 'RESUME_NOT_FUND', // 模卡记录不存在
|
||||
BOSS_NOT_AUTH = 'BOSS_NOT_AUTH', // 企业未认证
|
||||
BOSS_CANNOT_READ_RESUME = 'BOSS_CANNOT_READ_RESUME', // 企业要先查阅过模卡才能分享
|
||||
USER_NOT_FUND = 'USER_NOT_FUND', // 用户不存在
|
||||
RESUME_SHARE_CODE_NOT_FUND = 'RESUME_SHARE_CODE_NOT_FUND', // 模卡分享记录不存在
|
||||
RESUME_CANNOT_BE_READ = 'RESUME_CANNOT_BE_READ', // 模卡被限制访问
|
||||
RESUME_SHARE_CODE_EXPIRED = 'RESUME_SHARE_CODE_EXPIRED', // 分享码已过期
|
||||
USER_IS_NOT_ACTIVE = 'USER_IS_NOT_ACTIVE', // 用户状态异常
|
||||
// 模卡查看
|
||||
NO_PUBLISHED_JOB = 'NO_PUBLISHED_JOB', // 没有发布的模卡
|
||||
INSUFFICIENT_BALANCE = 'INSUFFICIENT_BALANCE', // 聊天或者模卡查看超出限制
|
||||
INSUFFICIENT_FREE_BALANCE = 'INSUFFICIENT_FREE_BALANCE', // 免费查看次数(未购买会员)超限
|
||||
BOSS_VIP_EXPIRED = 'BOSS_VIP_EXPIRED', // 会员过期
|
||||
}
|
||||
|
||||
export const RESPONSE_ERROR_INFO: { [key in RESPONSE_ERROR_CODE]?: string } = {
|
||||
[RESPONSE_ERROR_CODE.USER_IS_NOT_ACTIVE]: '用户状态异常',
|
||||
[RESPONSE_ERROR_CODE.RESUME_NOT_FUND]: '模卡记录不存在',
|
||||
[RESPONSE_ERROR_CODE.BOSS_NOT_AUTH]: '企业未认证',
|
||||
[RESPONSE_ERROR_CODE.BOSS_CANNOT_READ_RESUME]: '您需要先发布认证通告才可分享',
|
||||
[RESPONSE_ERROR_CODE.USER_NOT_FUND]: '用户不存在',
|
||||
[RESPONSE_ERROR_CODE.RESUME_SHARE_CODE_NOT_FUND]: '模卡分享记录不存在',
|
||||
[RESPONSE_ERROR_CODE.RESUME_CANNOT_BE_READ]: '模卡被限制访问',
|
||||
[RESPONSE_ERROR_CODE.RESUME_SHARE_CODE_EXPIRED]: '分享码已过期',
|
||||
};
|
12
src/http/error.ts
Normal file
12
src/http/error.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export class HttpError extends Error {
|
||||
constructor(
|
||||
message?: string,
|
||||
public errorCode?: string,
|
||||
public errorMsg?: string,
|
||||
public traceId?: string
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
info = () => `[HttpError]: errorCode: ${this.errorCode}, errorMsg: ${this.errorMsg}, traceId: ${this.traceId}`;
|
||||
}
|
137
src/http/index.ts
Normal file
137
src/http/index.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import Taro, { UploadTask } from '@tarojs/taro';
|
||||
|
||||
import { HttpError } from '@/http/error';
|
||||
import { safeJsonParse } from '@/utils/common';
|
||||
|
||||
import { API, BASE_URL } from './api';
|
||||
import { HTTP_STATUS, RESPONSE_ERROR_CODE } from './constant';
|
||||
import interceptors from './interceptor';
|
||||
import { IRequestResponse } from './type';
|
||||
import { isTokenExpired, refreshToken } from './utils';
|
||||
|
||||
interface IRequestOption<T = BL.Anything> extends Taro.request.Option<T> {
|
||||
contentType?: 'application/json' | 'application/x-www-form-urlencoded';
|
||||
blRetryTime?: number;
|
||||
}
|
||||
|
||||
interface IUploadOption extends Taro.uploadFile.Option {
|
||||
blRetryTime?: number;
|
||||
onProgress?: UploadTask.OnProgressUpdateCallback;
|
||||
}
|
||||
|
||||
interceptors.forEach(interceptor => Taro.addInterceptor(interceptor));
|
||||
|
||||
class Http {
|
||||
private throwHttpError = (resp: Taro.request.SuccessCallbackResult<IRequestResponse>) => {
|
||||
const { statusCode, data } = resp;
|
||||
const errorCode = data?.code || 'null';
|
||||
const errorMsg = data?.msg || 'unknown';
|
||||
const traceId = data?.traceId;
|
||||
console.error(
|
||||
`http request fail, httpCode: ${statusCode}, errorCode: ${errorCode}, errorMsg: ${errorMsg}, traceId: ${traceId}`
|
||||
);
|
||||
throw new HttpError('request fail', errorCode, errorMsg, traceId);
|
||||
};
|
||||
|
||||
private isNeedLoginError = (
|
||||
options: { blRetryTime?: number },
|
||||
resp: Taro.request.SuccessCallbackResult<IRequestResponse>
|
||||
) => {
|
||||
if (options.blRetryTime && options.blRetryTime >= 3) {
|
||||
return false;
|
||||
}
|
||||
const { statusCode, data } = resp;
|
||||
return statusCode !== HTTP_STATUS.SUCCESS && data?.code === RESPONSE_ERROR_CODE.NEED_LOGIN;
|
||||
};
|
||||
|
||||
private uploadFile = (options: IUploadOption) => {
|
||||
const { onProgress, ...option } = options;
|
||||
let offProgressUpdate: (() => void) | null = null;
|
||||
return new Promise((resolve, reject) => {
|
||||
option.success = resolve;
|
||||
option.fail = reject;
|
||||
const task = Taro.uploadFile(option);
|
||||
onProgress && task.onProgressUpdate(onProgress);
|
||||
onProgress && (offProgressUpdate = () => task.offProgressUpdate(onProgress));
|
||||
})
|
||||
.then((resp: Taro.uploadFile.SuccessCallbackResult) => {
|
||||
if (resp.statusCode === 200) {
|
||||
return safeJsonParse(resp.data.replace(/\uFEFF/g, ''));
|
||||
} else if (this.isNeedLoginError(option, resp as Taro.request.SuccessCallbackResult)) {
|
||||
option.blRetryTime = (option.blRetryTime || 0) + 1;
|
||||
return refreshToken().then(() => this.uploadFile(option));
|
||||
} else {
|
||||
this.throwHttpError(resp as Taro.request.SuccessCallbackResult);
|
||||
}
|
||||
})
|
||||
.finally(() => offProgressUpdate?.());
|
||||
};
|
||||
|
||||
private request = (option: IRequestOption) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
option.success = resolve;
|
||||
option.fail = reject;
|
||||
Taro.request(option);
|
||||
}).then((resp: Taro.request.SuccessCallbackResult) => {
|
||||
if (resp.statusCode === HTTP_STATUS.SUCCESS) {
|
||||
return resp.data;
|
||||
} else if (this.isNeedLoginError(option, resp)) {
|
||||
option.blRetryTime = (option.blRetryTime || 0) + 1;
|
||||
return refreshToken().then(() => this.request(option));
|
||||
} else {
|
||||
this.throwHttpError(resp);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private baseUpload = (params: IUploadOption) => {
|
||||
const { url, ...otherParams } = params;
|
||||
const option: IUploadOption = {
|
||||
// 默认超时时间三分钟
|
||||
timeout: 1000 * 60 * 3,
|
||||
...otherParams,
|
||||
blRetryTime: 0,
|
||||
url: url.startsWith('http') ? url : BASE_URL + url,
|
||||
header: { 'content-type': 'multipart/form-data' },
|
||||
};
|
||||
|
||||
return this.uploadFile(option);
|
||||
};
|
||||
|
||||
private baseRequest = (params: IRequestOption, method: Taro.request.Option['method'] = 'GET') => {
|
||||
const { url, data, ...otherParams } = params;
|
||||
const contentType = params.contentType || 'application/json';
|
||||
const option: IRequestOption = {
|
||||
...otherParams,
|
||||
blRetryTime: 0,
|
||||
url: BASE_URL + url,
|
||||
data,
|
||||
method: method,
|
||||
header: { 'content-type': contentType },
|
||||
};
|
||||
return this.request(option);
|
||||
};
|
||||
|
||||
async init() {
|
||||
if (isTokenExpired()) {
|
||||
await refreshToken();
|
||||
}
|
||||
}
|
||||
|
||||
get = <T = BL.Anything>(url: API, params?: Omit<IRequestOption<T>, 'url'>): Promise<T> => {
|
||||
const option = { url, ...params };
|
||||
return this.baseRequest(option);
|
||||
};
|
||||
|
||||
post = <T = BL.Anything>(url: API, params?: Omit<IRequestOption<T>, 'url'>): Promise<T> => {
|
||||
const option = { url, ...params };
|
||||
return this.baseRequest(option, 'POST');
|
||||
};
|
||||
|
||||
upload = <T = BL.Anything>(url: API | string, params?: Omit<IUploadOption, 'url'>): Promise<T> => {
|
||||
const option = { url, ...params } as IUploadOption;
|
||||
return this.baseUpload(option);
|
||||
};
|
||||
}
|
||||
|
||||
export default new Http();
|
38
src/http/interceptor.ts
Normal file
38
src/http/interceptor.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { getRoleType } from '@/utils/app';
|
||||
import { isDev } from '@/utils/common';
|
||||
|
||||
import { getToken } from './utils';
|
||||
|
||||
const tokenInterceptor: Taro.interceptor = (chain: Taro.Chain) => {
|
||||
const requestParams = chain.requestParams;
|
||||
const token = getToken();
|
||||
// console.log('tokenInterceptor', requestParams.url, token);
|
||||
if (token) {
|
||||
requestParams.header = {
|
||||
...requestParams.header,
|
||||
Authorization: 'Bearer ' + token,
|
||||
};
|
||||
}
|
||||
return chain.proceed(requestParams).then(res => res);
|
||||
};
|
||||
|
||||
const roleInterceptor: Taro.interceptor = (chain: Taro.Chain) => {
|
||||
const requestParams = chain.requestParams;
|
||||
const roleType = getRoleType();
|
||||
requestParams.header = {
|
||||
...requestParams.header,
|
||||
'role-type': roleType,
|
||||
};
|
||||
return chain.proceed(requestParams).then(res => res);
|
||||
};
|
||||
|
||||
const interceptors = [tokenInterceptor, roleInterceptor];
|
||||
|
||||
if (isDev()) {
|
||||
// logInterceptor - 用于打印请求的相关信息
|
||||
interceptors.push(Taro.interceptors.logInterceptor);
|
||||
}
|
||||
|
||||
export default interceptors;
|
9
src/http/type.ts
Normal file
9
src/http/type.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { RESPONSE_ERROR_CODE } from './constant';
|
||||
|
||||
export interface IRequestResponse {
|
||||
data: BL.Anything;
|
||||
// 请求出错时才会返回下面几个字段
|
||||
code: RESPONSE_ERROR_CODE;
|
||||
msg: string;
|
||||
traceId: string;
|
||||
}
|
65
src/http/utils.ts
Normal file
65
src/http/utils.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { LoginResponse } from '@/types/login';
|
||||
|
||||
import http from '.';
|
||||
import { API } from './api';
|
||||
import { TOKEN_KEY, TOKEN_EXPIRES_TIME } from './constant';
|
||||
|
||||
let token = '';
|
||||
let _fetchTokenPromise: Promise<string> | null = null;
|
||||
|
||||
const getCode = (): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.login({
|
||||
success: res => resolve(res.code),
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const clearToken = () => {
|
||||
token = '';
|
||||
Taro.setStorageSync(TOKEN_KEY, '');
|
||||
Taro.setStorageSync(TOKEN_EXPIRES_TIME, 0);
|
||||
};
|
||||
|
||||
const requestToken = (): Promise<string> => {
|
||||
return getCode()
|
||||
.then(code => {
|
||||
return http.post<LoginResponse>(API.LOGIN, { data: { code } }).then(data => {
|
||||
const newToken = data?.token || '';
|
||||
const expires = data?.expires || 0;
|
||||
if (newToken) {
|
||||
token = newToken;
|
||||
Taro.setStorageSync(TOKEN_KEY, newToken);
|
||||
}
|
||||
if (expires) {
|
||||
Taro.setStorageSync(TOKEN_EXPIRES_TIME, Date.now() + expires);
|
||||
}
|
||||
return newToken;
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
_fetchTokenPromise = null;
|
||||
});
|
||||
};
|
||||
|
||||
export const isTokenExpired = () => (Taro.getStorageSync(TOKEN_EXPIRES_TIME) || 0) < Date.now();
|
||||
|
||||
export const refreshToken = () => {
|
||||
if (_fetchTokenPromise) {
|
||||
return _fetchTokenPromise;
|
||||
}
|
||||
clearToken();
|
||||
_fetchTokenPromise = requestToken();
|
||||
return _fetchTokenPromise;
|
||||
};
|
||||
|
||||
export const getToken = () => {
|
||||
if (token) {
|
||||
return token;
|
||||
}
|
||||
token = Taro.getStorageSync(TOKEN_KEY);
|
||||
return token;
|
||||
};
|
Reference in New Issue
Block a user