浙里办H5 mgop请求封装

前言

两周前水了一篇浙里办H5 React工程埋点,有JY私信问mgop请求的问题。不过我们项目的请求封装不是我做的,这里放一下我们项目的 mgop 请求封装。

注意:官方文档时常更新,文中出现的版本和上报的参数很可能在某次版本迭代后需要修改,一切代码开发需要以官网最新文档为主🫤。

引入 ZWJSBridge

在 head 标签中引入 bridge 文件。

开发时 bridge 版本为 1.1.0。

html 复制代码
<script type="text/javascript" src="https://assets.zjzwfw.gov.cn/assets/ZWJSBridge/1.1.0/zwjsbridge.js"></script>

封装 mogop 请求

封装 mogop 请求,从 localStorage 中取出 token 加到 haeder 中。在开发过程中我们发现 mgop 的 cookie 不会自动加到 header里,从接口返回里截取出来又比较麻烦。我们就根据之前支付宝小程序的开发经验,将 token 放到 response 中返回,前端手动将 token 存到 localStorage 中。

ts 复制代码
import { mgop } from "@aligov/jssdk-mgop";

type RequestResponse<T> = {
  code: number;
  data: T;
  message: string;
};

/**
 * 使用 mgop 发送请求
 * 使用typescript时无需填入type、dataType
 * @param api 调用的接口路径,是在irs开发者平台中注册的rpc的api名称
 * @param data 请求的params/data。
 */
function mgopRequest<T>(api: string, data?: any): Promise<RequestResponse<T>> {
  console.info(api, "mgop请求入参:", data);
  const saToken = window.localStorage.getItem("saToken");
  const Config = {
    host: "https://mapi.zjzwfw.gov.cn/",
    appKey: "应用appKey",
    header: {
      // 1:转发至测试环境联调;如上生产就注释掉
      // isTestUrl: "1",
      satoken: saToken || "",
    },
  };
  return new Promise((resolve, reject) => {
    const mgopReceiveObj = {
      api,
      data,
      ...Config,
      onSuccess: async (res: any) => {
        console.log("Mgop成功::", res);
        try {
          const result: RequestResponse<T> = await handleOnMgopSuccess(
            res,
            api
          );
          return resolve(result);
        } catch (error) {
          return reject(error);
        }
      },
      onFail: async (error: any) => {
        console.log("Mgop失败::", error);
        try {
          await handleOnMgopFailed(error, api);
        } catch (error) {
          reject(error);
        }
      },
    };
    mgop(mgopReceiveObj);
  });
}

/**
 * mgop请求成功响应拦截器:该回调方法在mgop官方请求成功时做处理。
 * 对我方接口返回的数据结构做判断,当errorCode 不为 200 的情况都会被处理成异常,200时为正常获取我方接口数据。
 * @param res mgop请求成功返回结构
 * @param api 当前请求的api
 * @returns
 */
function handleOnMgopSuccess<T>(
  res: any,
  api: string
): Promise<RequestResponse<T>> {
  return new Promise((resolve, reject) => {
    const code = parseInt(res.data?.errorCode);
    const message = res.data?.message || "无详情";
    // 代表了业务异常的情况,根据项目具体需要做处理
    if (code !== 200) {
      const error = { code, message, api };
      return reject(error);
    } else {
      // 返回成功时调用数据
      resolve(res.data);
    }
  });
}

/**
 * mgop请求失败响应拦截器:
 * 对浙里办应用的mgop请求接口返回的数据结构做判断。
 * 这个拦截器返回的 Promise 永远是 reject,并且对返回的异常结构做了处理。
 * 手动处理成统一的结构后(包括成功响应拦截器中的业务异常)的原因是
 * mgop 在不同终端返回的结构不同,造成了开发时的心智负担。
 */
/**
 * mgop请求失败响应拦截器:
 * 这个拦截器返回的 Promise 永远是 reject,并且对返回的异常结构做了处理。
 * @param error
 * @param api
 * @returns
 */
function handleOnMgopFailed(error: any, api: string) {
  return new Promise((resolve, reject) => {
    if (error.errorMessage && error.errorCode) {
      const { errorCode: code, errorMessage: message } = error;
      const myError = { code, message, api };
      return reject(myError);
    } else if (error?.ret[0]) {
      const message = error.ret[0];
      const code = parseInt(error.ret[0]);
      const myError = { code, message, api };
      return reject(myError);
    } else {
      const message = "未知系统异常";
      const code = 500;
      const myError = { code, message, api };
      return reject(myError);
    }
  });
}

export { mgopRequest, RequestResponse };

具体请求使用

ts 复制代码
import { mgopRequest, RequestResponse } from "./mgopRequest";

/**
 * 测试用接口,用于判断当前API访问环境。
 * @returns
 */
const getTestData = () => {
  return mgopRequest("mgop.xxx.test.doTest.COPY.SgVhTMTm");
};

/**
 * 测试用接口,用于判断当前API访问环境。
 * @returns
 */
const authTest = () => {
  return mgopRequest("mgop.xxx.test.authTest");
};

/**
 * 单点登录获取用户信息。
 * @param ticketId ticketId
 */
export const login = (data: {
  ticketId: string;
}): Promise<RequestResponse<UserInfoType>> => {
  return mgopRequest("mgop.xxx.xxx.login", data);
};

// 列表分页
export const getRecords = (
  data: queryClientType
): Promise<RequestResponse<AuthRecordType>> => {
  return mgopRequest("mgop.xxx.xxx.pageRecord", data);
};

// 信息
export const getInfo = (data: {
  authCode: string;
}): Promise<RequestResponse<InfoType>> => {
  return mgopRequest("mgop.xxx.xxx.info", data);
};

/**
 * 声明同意/拒绝(浙里办)
 * @param ifAgree 是否同意
 * @param callbackToken 安全token
 * @returns
 */
const AAA = (data: { ifAgree: boolean; callbackToken: string }) => {
  return mgopRequest("mgop.xxx.xxx.AAA", data);
};

/**
 * 取消(浙里办)
 * @param id 请求id
 * @returns
 */
const cancel = (data: {
  id: string;
}): Promise<RequestResponse<any>> => {
  return mgopRequest("mgop.xxx.xxx.cancel", data);
};

export { authTest, getTestData, getInfo, login, AAA, cancel };

获取票据 getTicket

封装获取 ticketId 的方法。

ts 复制代码
/**
 * 获取票据
 * @returns
 */
const ZWJSBridge = window.ZWJSBridge //记得declare一下

const getTicket = async () => {
  console.info('获取票据');
  let ticket = '';

  if (ZWJSBridge.ssoTicket) {
    const ssoFlag = await ZWJSBridge.ssoTicket({});
    if (ssoFlag && ssoFlag.result === true) {
      // 使用 浙里办统一单点登录组件
      console.info('使用 浙里办统一单点登录组件');
      if (ssoFlag.ticketId) {
        console.info('应用方服务端单点登录接口');
        ticket = ssoFlag.ticketId;
        return ticket;
      } else {
        console.warn(
          '当浙里办单点登录失败或登录态失效时调用 ZWJSBridge.openLink 方法重新获取 ticketId',
        );
        ZWJSBridge.openLink({
          type: 'reload',
        }).then((res: any) => {
          ticket = res.ticketId;
          return res.ticket;
        });
      }
    } else {
      console.error(
        '异常情况:当前环境不支持浙里办统一单点登录',
      );

      Toast.show({ content: '业务异常,请稍后再试' });
      // // 异常情况:当前环境不支持浙里办统一单点登录
    }
  } else {
    console.error(
      '异常情况:ZWJSBridge 加载异常',
    );
  }
  return ticket;
};

单点登录 ssoLogin

在 App.jsx 中获取 ticketId 并登录。

tsx 复制代码
// ...
const ssoLogin = useCallback(async () => {
    const ticketId = await getTicket()
    
    console.log(ticketId)
    login({ ticketId: ticketId })
      .then((res) => {
        const { saToken = '' } = res.data || {};
        window.localStorage.setItem('saToken', saToken);
        setUserInfo({ ...res.data, ticketId } as UserInfoType);
      })
      .catch(() => {
        window.localStorage.removeItem('saToken')
        clearUserInfo();
      });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

 useEffect(() => {
    setGlobalTheme(); // 设置全局样式主题
    setIPhoneApp();
    needRecord()  // 打开埋点信息上报
    ssoLogin();
    return () => {
      window.localStorage.clear()
      document.cookie = ''
      queryClient.clear();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
// ...

据说也不一定必须使用 mgop 来进行接口请求,在真机测试环境联调时,我们发现正常的 axios 也能调通,并且有听说其他老大哥的过去老项目就没走 mgop 。

相关推荐
zqx_79 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己25 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2341 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河1 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai2 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端