Commit ab9ee8ee by chunhong.mu

feat(@common/sdk): 提取SDK相关代码到公共包

parent 81e6ab5b
/**
* 认证处理器接口
* 用于解耦 HTTP 请求层与登录服务层之间的依赖
*/
export interface AuthHandlers { export interface AuthHandlers {
getDeviceInfoWidthCache: () => void
clearLoginMsg: () => void clearLoginMsg: () => void
logout: () => void logout: (jump?: boolean, loginUrl?: string) => void
afterSdkLogin: () => Promise<any> afterSdkLogin: () => Promise<any>
getDeviceInfoWidthCache: () => void
}
let handlers: AuthHandlers | null = null
/**
* 注册认证处理器
*/
export function registerAuthHandlers(h: AuthHandlers) {
handlers = h
} }
export interface AuthHandlerConfig { /**
noNeedAuthPages: string[] * 获取已注册的认证处理器
*/
export function getAuthHandlers(): AuthHandlers | null {
return handlers
} }
import { decodeJwt } from "uniplat-sdk" import type { AxiosAdapter } from "axios"
import type { Index } from "uniplat-sdk"
import { decodeJwt, UniplatSdk } from "uniplat-sdk"
import { UICore } from "../config/ui-core"
export function decodeToken<T>(token: string): T { /**
return decodeJwt<T>(token) * 登录 API 名称枚举
} */
export enum LoginApiName {
export interface TokenInfo { Login = "login",
uid: number RefreshToken = "refreshToken",
orgId: number Logout = "logout",
token: string
} }
/**
* Passport Token 控制器
* 负责 Token 的保存、验证、获取用户信息等
*/
export class PassportTokenController { export class PassportTokenController {
static hasToken(): boolean { private static readonly key = "JWT_TOKEN"
try { private static baseUrl = ""
const token = uni.getStorageSync("token") private static ssr = false
return !!token
} catch { public static setupBaseUrl(baseUrl: string) {
return false this.baseUrl = baseUrl
return this
}
public static clearToken() {
UICore.storage.removeItem(this.key)
}
public static useSsrMode() {
this.ssr = true
return this
}
public static saveToken2Storage(token: string, adapter?: AxiosAdapter) {
if (token && this.isTokenValid(token)) {
if (token.length > 600) {
const sdk = new UniplatSdk()
if (!this.baseUrl) {
throw new Error("请先调用 setupBaseUrl 初始化")
}
if (adapter) {
sdk.connect({
baseUrl: this.baseUrl,
axiosAdapter: adapter as any,
})
} else {
sdk.connect({ baseUrl: this.baseUrl })
}
return sdk
.getPassportLogin()
.login(`Bearer ${token}`)
.then((r) => {
const o = r as Index.loginByPassportRequestResult
if (o.jwt) {
this.ssr && (UICore.core.global.ssr = true)
UICore.storage.setItem(this.key, o.jwt)
}
})
} else {
this.ssr && (UICore.core.global.ssr = true)
UICore.storage.setItem(this.key, token)
return Promise.resolve()
}
} }
return Promise.reject(new Error("无效 Token"))
} }
static getUid(): number { public static isTokenValid(token: string) {
try { try {
const token = uni.getStorageSync("token") const decode = decodeJwt<{ exp: string }>(token)
if (!token) return 0 return +decode.exp * 1000 > new Date().valueOf()
const decoded = decodeToken<TokenInfo>(token)
return decoded.uid || 0
} catch { } catch {
return 0 return false
} }
} }
static getToken(): string { public static hasToken() {
try { const cache =
return uni.getStorageSync("token") || "" UICore.storage.getItem(this.key) ||
} catch { (UICore.core && UICore.core.global.jwtToken)
return "" if (cache) {
if (this.isTokenValid(cache)) {
return cache
}
UICore.storage.removeItem(this.key)
return null
} }
return cache
}
public static checkTokenExit() {
const cache =
UICore.storage.getItem(this.key) ||
(UICore.core && UICore.core.global.jwtToken)
return cache
}
/**
* 刷新 Token
*/
public refershToken(sdk: UniplatSdk) {
return sdk
.domainService(
"uniplat_base",
"system.auth",
LoginApiName.RefreshToken,
)
.request<
Record<string, unknown>,
Record<string, unknown>,
{ is_super: boolean; jwt: string }
>("post")
} }
static setToken(token: string): void { public static getUid() {
uni.setStorageSync("token", token) const token = this.hasToken()
if (token) {
const tokenInfo = decodeJwt<{ user_id: string }>(token)
return tokenInfo.user_id
}
return ""
} }
static clearToken(): void { public static getOid() {
uni.removeStorageSync("token") const token = this.hasToken()
if (token) {
const tokenInfo = decodeJwt<{ oid: string }>(token)
return tokenInfo.oid
}
return ""
} }
} }
/**
* 解码 Token
*/
export function decodeToken<T>(token: string): T {
return decodeJwt<T>(token)
}
import type { AxiosAdapter } from "axios"
import type {
metaRow,
SdkListRowPredict,
SdkListRowPredictObject,
UniplatSdk,
} from "uniplat-sdk"
import { UniplatSdkExtender } from "uniplat-sdk"
/**
* 环境枚举
*/
export enum Environment {
Dev = "dev",
Test = "test",
Pro = "pro",
Stage = "stage",
}
/**
* 客户端 ID 枚举
*/
export enum ClientId {
None = "",
QqxbApp = "qqxb-hrm-app-ios",
QqtxzMp = "qqxb_mini_app",
FlbMp = "qqxb_flb_mini_app",
WebLogin = "teamix-personal-center-web-pc",
RenCaiKuaiDiMp = "welfare_bean_wechat_mini_app",
YuZhiPinMp = "chongqing_job_mini_app",
RenCaiKuaiDiH5 = "welfare_bean_qqxb_h5",
RenCaiKuaiDiPc = "welfare_bean_agent_pc",
Hrs100 = "hrs100-front-web-pc",
QqxbOfficial = "qqxb-official-web",
FastServiceWeb = "teamix-fast-service-web",
FastServiceMerchant = "teamix-fast-service-merchant",
Hrs100WechatMiniApp = "hrs100_wechat_mini_app",
Donghu = "donghu-web-pc",
}
/**
* Passport 配置
*/
const qqxbPassportConfig = new Map<Environment, string>([
[
Environment.Dev,
"http://191env1.report.qinqinxiaobao-passport.qqxb.jinsehuaqin.com:8800",
],
[
Environment.Test,
"http://191env1.report.qinqinxiaobao-passport.qqxb.jinsehuaqin.com:8800",
],
[Environment.Pro, "https://passport.qinqinxiaobao.com"],
[Environment.Stage, "https://pre-passport.qinqinxiaobao.com"],
])
/**
* 微信分销商 URL 配置
*/
export const wxDistributerUrlConfig = new Map<Environment, string>([
[Environment.Dev, "http://wechat.test.teammix.qqxb.cn"],
[Environment.Test, "https://pre-wx-distributer.teammix.com"],
[Environment.Pro, "https://wx-distributer.teammix.com"],
[Environment.Stage, "https://pre-wx-distributer.teammix.com"],
])
/**
* UI 核心配置类
* 负责 SDK 初始化配置、客户端 ID、Passport URL 等
*/
export class UICore {
private static sdk: UniplatSdk | null = null
private static adapter: AxiosAdapter | null = null
private static client = ClientId.None
private static passportUrl = ""
private static wxDistributerUrl = ""
private static readonly handler = new UniplatSdkExtender()
public static setupSdk(
sdk: UniplatSdk,
params?: {
client?: ClientId
envir?: Environment
config?: AxiosAdapter
},
) {
this.sdk = sdk
if (params) {
this.adapter = params.config as AxiosAdapter
this.client = params.client as ClientId
}
if (params?.envir) {
this.passportUrl = qqxbPassportConfig.get(params.envir) as string
this.wxDistributerUrl = wxDistributerUrlConfig.get(
params.envir,
) as string
}
if (!this.passportUrl) {
this.passportUrl = qqxbPassportConfig.get(Environment.Dev) as string
}
if (!this.wxDistributerUrl) {
this.wxDistributerUrl = wxDistributerUrlConfig.get(
Environment.Dev,
) as string
}
}
public static get core() {
return this.sdk as UniplatSdk
}
public static get storage() {
return UICore.core?.global.storage || localStorage
}
public static get adapterConfig() {
return this.adapter as AxiosAdapter
}
public static get clientId() {
return this.client
}
public static get passport() {
return this.passportUrl
}
public static get wxDistributer() {
return this.wxDistributerUrl
}
/**
* 构建 Passport 登出 URL
*/
public static buildPassport4Logout(redirect?: string) {
if (redirect) {
return `${this.passportUrl}?redirectUrl=${redirect}&logout=1`
}
return `${this.passportUrl}?logout=1`
}
public static forgetPassword() {
return `${this.passportUrl}/forget`
}
public static register() {
return `${this.passportUrl}/register?redirectUrl=${encodeURIComponent(
window.location.href,
)}`
}
public static buildActionParameter(parameter: { [key: string]: any }) {
return this.handler.buildActionParameter(parameter)
}
public static buildRows<T>(
rows: metaRow[],
predicts: SdkListRowPredict[] | SdkListRowPredictObject,
) {
return this.handler.buildRows<T>(rows, predicts)
}
public static buildRow<T>(
item: metaRow,
predicts: SdkListRowPredict[] | SdkListRowPredictObject,
) {
return this.handler.buildRow<T>(item, predicts)
}
}
export type { AuthHandlerConfig, AuthHandlers } from "./auth/auth-handlers" // Auth 相关
export { getAuthHandlers, registerAuthHandlers } from "./auth/auth-handlers"
export type { AuthHandlers } from "./auth/auth-handlers"
export { decodeToken, PassportTokenController } from "./auth/token-manager" export { decodeToken, PassportTokenController } from "./auth/token-manager"
export { LoginApiName } from "./auth/token-manager"
// 配置
export { ClientId, Environment, UICore, wxDistributerUrlConfig } from "./config/ui-core"
// HTTP 相关
export { uniAdapter } from "./http/adapter" export { uniAdapter } from "./http/adapter"
// 监控
export { monitor, Product } from "./monitor/controller"
export type { SdkMonitorOption } from "./monitor/controller"
// SDK 核心
export { SdkCore } from "./sdk-core" export { SdkCore } from "./sdk-core"
export type { SdkConfig, SdkInitOptions } from "./sdk-types" export type { SdkConfig, SdkInitOptions } from "./sdk-types"
// 服务层
export { BaseServiceWithSdk } from "./services/base-service"
export { default as BaseService } from "./services/base-service"
// 工具
export { buildFilePath, buildImage } from "./utils/image-builder" export { buildFilePath, buildImage } from "./utils/image-builder"
export { SdkController } from "./utils/sdk-controller"
import type { AxiosAdapter, AxiosResponse } from "axios"
import type { UniplatSdk } from "uniplat-sdk"
import Axios from "axios"
/**
* 产品枚举
*/
export enum Product {
Default = "default",
QqxbWeixin = "qqxb-weixin",
QqxbApp = "qqxb-app",
Fulibao = "fulibao",
HrManager = "hr-manager",
Hrs100 = "hrs100",
HrsApp = "hrs-app",
BiJie = "bi-jie",
Cashier = "cashier",
Uniplat = "uniplat",
DeShengJiuYeBao = "de-sheng-jiu-ye-bao",
QqxbPassport = "qqxb-passport",
RenCaiKuaiDi = "ren-cai-kuai-di",
SiJiLianDong = "si-ji-lian-dong",
YiZhiPin = "yi-zhi-pin",
FastServiceMerchant = "fast-service-merchant",
}
enum ProductTable {
Default = "",
Login = "login",
Error = "error",
}
export interface SdkMonitorOption {
userAgent?: boolean
envir: string
product: Product
call?: (r: any) => void
monitorMsgHandler?: (msg: string[], r: AxiosResponse) => void
}
/**
* Web 监控类
* 负责错误监控、日志上报
*/
class WebMonitor {
private key = ""
private envir = "dev"
private product = Product.Default
private readonly url = "https://pre-hrs-monitor.hrs100.com"
private adapter: AxiosAdapter | undefined
public updateKey(key: string) {
this.key = key
return this
}
private buildHeaders() {
return {
headers: { authorization: "cdd0a34e-f537-4e5b-808e-2ba06af21845" },
adapter: this.adapter,
}
}
private enable() {
return this.envir !== "dev"
}
private envirString() {
if (this.envir === "dev") {
return "[Dev]"
}
if (this.envir === "stage") {
return "[Stage]"
}
return ""
}
public log(msg: any) {
return Axios.post(
this.url,
{
type: ProductTable.Login,
product: this.product,
msg: `${this.envirString()} ${msg} `,
key: this.key,
},
this.buildHeaders(),
)
}
public error(msg: any) {
if (this.enable()) {
return Axios.post(
this.url,
{
type: ProductTable.Error,
product: this.product,
msg: `${this.envirString()} ${msg} `,
key: this.key,
},
this.buildHeaders(),
)
}
return Promise.resolve()
}
public useSdk(
sdk: UniplatSdk,
options: SdkMonitorOption,
adapter?: AxiosAdapter,
) {
this.envir = options.envir
this.product = options.product
this.adapter = adapter
sdk &&
sdk.events.addUniversalErrorResponseCallback(
(r: AxiosResponse<any>) => {
options.call && options.call(r)
if (this.enable()) {
const msg: string[] = []
msg.push(
`URL: ${decodeURIComponent(r.config.url as string)}`,
)
msg.push(`Token: ${sdk.global.getCurrentToken()}`)
const header = r.config.headers
if (header) {
header.CurrentOrg &&
msg.push(`CurrentOrg: ${header.CurrentOrg}`)
header.Scenes &&
msg.push(`Scenes: ${header.Scenes}`)
}
options &&
options.userAgent &&
msg.push(`UserAgent: ${window.navigator.userAgent}`)
r.config &&
r.config.params &&
msg.push(
`Params: ${JSON.stringify(r.config.params)}`,
)
if (r.config && r.config.data) {
const form = r.config.data as FormData
if (form.getAll) {
const p = form.getAll("parameters")
for (const item of p) {
msg.push(`Payload: ${item}`)
}
} else {
msg.push(
`Payload: ${JSON.stringify(r.config.data)}`,
)
}
}
if (window !== undefined) {
msg.push(`Location: ${window.location.href}`)
}
if (r.data) {
r.data.msg && msg.push(`Message: ${r.data.msg}`)
r.data.error &&
msg.push(
`Exception: ${(
(r.data.error as string) || ""
).substring(0, 500)}`,
)
}
options.monitorMsgHandler &&
options.monitorMsgHandler(msg, r)
r && r.config && this.error(msg.join("\n"))
}
},
)
return Promise.resolve(this.enable() ? this.key : "")
}
}
export const monitor = new WebMonitor()
import type { SdkCore } from "../sdk-core"
/**
* 基础请求服务类
* 提供静态 get/post 方法,用于简单的 HTTP 请求
*/
export default class BaseService {
protected sdk!: SdkCore
public static get<T>(url: string, params?: any) {
// 注意:此方法需要子类或实例提供 sdk 实例
throw new Error("sdk instance not set")
}
public static post<T>(url: string, data?: any) {
throw new Error("sdk instance not set")
}
}
/**
* 增强版 BaseService,支持传入 sdk 实例
*/
export class BaseServiceWithSdk {
constructor(protected sdk: SdkCore) {}
public get<T>(url: string, params?: any) {
return this.sdk.getAxios().get<{}, T>(url, { params })
}
public post<T>(url: string, data?: any) {
return this.sdk.getAxios().post<{}, T>(url, data)
}
}
import type { UniplatSdk } from "uniplat-sdk"
/**
* SDK 控制器
* 负责构建图片路径、文件路径等
*/
export class SdkController {
public static buildImage(
sdk: UniplatSdk,
url: string,
w?: number,
h?: number,
view?: boolean,
) {
if (/^http/.test(url)) {
// 将错误的"//fs"转换为"/fs"
return url.replace(/\/\/fs/, "/fs")
}
// 移除前面可能存在的"/"
const t = url.replace(/^\/(.*)/, "$1")
return (
sdk.global.baseUrl +
sdk.mediaController.buildThumbnail(t, w, h, view)
)
}
public static buildFilePath(
sdk: UniplatSdk,
url: string,
notForceDownload?: boolean,
) {
if (/^http/.test(url)) {
return url
}
if (url.startsWith("/")) {
return sdk.global.baseUrl + url.replace("/", "")
}
return (
sdk.global.baseUrl +
url +
(notForceDownload
? `?parameters=${encodeURIComponent(
JSON.stringify({
forceDownload: false,
}),
)}`
: "")
)
}
}
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 sign in to comment