feat: first commit

This commit is contained in:
eleanor.mao
2025-03-31 22:34:22 +08:00
commit d25187c9c8
390 changed files with 57031 additions and 0 deletions

79
src/http/api.ts Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
};