GraphQL

优缺点

  • 优点

    • 微服务架构下的接口聚合
    • 根据 schema 自动生成接口文档
    • 前端查询结果可控,灵活性
  • 缺点

    • 权限控制粒度
    • 对于复杂查询存在性能瓶颈 - 虽然可以减少前端请求,但是数据库查询会成为性能瓶颈
    • 对于前端来说,增加了上手难度,与传统 restfull api 使用习惯相差很大
    • 有一些业务场景实现不了

前端使用

  1. 安装 graphql 依赖

pnpm i @apollo/client graphql

  1. 安装 graphql vite 插件

pnpm i -D @rollup/plugin-graphql

  1. 在 vite.config.ts 中引入插件
// vite.config.ts
import { URL, fileURLToPath } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import graphql from "@rollup/plugin-graphql";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [graphql()],
});
  1. 定义 API client

如果有多个服务,可以定义多个 client ,每个 client 相当于一个 gql 请求实例

// src/api/client/index.ts
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client/core";
import { setContext } from "@apollo/client/link/context";
import { onError } from "@apollo/client/link/error";

const httpLink = new HttpLink({
  uri: "/api/graphql",
});

const link = onError((error) => {
  if (error.graphQLErrors && error.graphQLErrors.length) {
    error.graphQLErrors.forEach((e) => { console.log(e) });
  }
});

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem("token");
  const _headers = {
    ...headers,
  };
  if (token) {
    _headers["Authorization"] = `${token}`;
  }
  _headers["x-requested-with"] = "XMLHttpRequest";
  return {
    headers: {
      ..._headers,
    },
  };
});

const client = new ApolloClient({
  uri: "/api/graphql",
  link: errorLink.concat(authLink.concat(httpLink)),
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: {
      // errorPolicy: 'none',
      fetchPolicy: "no-cache",
    },
    query: {
      // errorPolicy: 'none',
      fetchPolicy: "no-cache",
    },
    mutate: {
      errorPolicy: "none",
      fetchPolicy: "no-cache",
    },
  },
});
export default client;
  1. 定义接口
// src/api/user/gql/signin.gql
mutation signin($name: String!, $pwd: String!) {
  signin(name: $name, pwd: $pwd) {
    token
  }
}
// src/api/user/index.ts
import Client from "../../client";
import signin from "./gql/signin.gql";

export function login(name: string, pwd: string) {
  return portalClient.mutate({
    mutation: signin,
    variables: {
      name,
      pwd,
    },
  });
}
  1. 在 vite.config.ts 中设置代理 api
// vite.config.ts
export default defineConfig({
    server: {
        host: true,
        open: true,
        proxy: {
            "/api/graphql": {
            target: "http://172.21.44.154:6543/",
            changeOrigin: true, //是否允许跨域
            rewrite: (path) => {
                return path.replace(/^\/api/, "");
            },
            },
        },
    },
})
  1. 使用
import { appAPI } from "@/api";

async function handleLogin() {
  const { login } = appAPI.User;
  const res = await login(account.value, password.value);
  const { token } = res.data.signIn;
  if (token) {
    localStorage.setItem("token", token);
  }
}
  1. 解决 ts 项目中 gql 文件类型问题
// src/typings/global.d.ts
declare module "*.gql" {
  import type { DocumentNode } from "graphql";
  const value: DocumentNode;
  export default value;
}
// tsconfig.json
{
  "files": ["typings/global.d.ts"],
}

注意:遇到启动 GraphQL 内部报错的问题,请检查 GraphQL 相关依赖版本