Blame view

Yi.Vben5.Vue3/docs/src/en/guide/essentials/server.md 11.3 KB
515fceeb   “wangming”   框架初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
  # Server Interaction and Data Mocking
  
  ::: tip Note
  
  This document explains how to use Mock data and interact with the server in a development environment, involving technologies such as:
  
  - [Nitro](https://nitro.unjs.io/) A lightweight backend server that can be deployed anywhere, used as a Mock server in the project.
  - [axios](https://axios-http.com/docs/intro) Used to send HTTP requests to interact with the server.
  
  :::
  
  ## Interaction in Development Environment
  
  If the frontend application and the backend API server are not running on the same host, you need to proxy the API requests to the API server in the development environment. If they are on the same host, you can directly request the specific API endpoint.
  
  ### Local Development CORS Configuration
  
  ::: tip Hint
  
  The CORS configuration for local development has already been set up. If you have other requirements, you can add or adjust the configuration as needed.
  
  :::
  
  #### Configuring Local Development API Endpoint
  
  Configure the API endpoint in the `.env.development` file at the project root directory, here it is set to `/api`:
  
  ```bash
  VITE_GLOB_API_URL=/api
  ```
  
  #### Configuring Development Server Proxy
  
  In the development environment, if you need to handle CORS, configure the API endpoint in the `vite.config.mts` file under the corresponding application directory:
  
  ```ts{8-16}
  // apps/web-antd/vite.config.mts
  import { defineConfig } from '@vben/vite-config';
  
  export default defineConfig(async () => {
    return {
      vite: {
        server: {
          proxy: {// [!code focus:11]
            '/api': {
              changeOrigin: true,
              rewrite: (path) => path.replace(/^\/api/, ''),
              // mock proxy
              target: 'http://localhost:5320/api',
              ws: true,
            },
          },
        },
      },
    };
  });
  ```
  
  #### API Requests
  
  Based on the above configuration, we can use `/api` as the prefix for API requests in our frontend project, for example:
  
  ```ts
  import axios from 'axios';
  
  axios.get('/api/user').then((res) => {
    console.log(res);
  });
  ```
  
  At this point, the request will be proxied to `http://localhost:5320/api/user`.
  
  ::: warning Note
  
  From the browser's console Network tab, the request appears as `http://localhost:5555/api/user`. This is because the proxy configuration does not change the local request's URL.
  
  :::
  
  ### Configuration Without CORS
  
  If there is no CORS issue, you can directly ignore the [Configure Development Server Proxy](./server.md#configure-development-server-proxy) settings and set the API endpoint directly in `VITE_GLOB_API_URL`.
  
  Configure the API endpoint in the `.env.development` file at the project root directory:
  
  ```bash
  VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
  ```
  
  ## Production Environment Interaction
  
  ### API Endpoint Configuration
  
  Configure the API endpoint in the `.env.production` file at the project root directory:
  
  ```bash
  VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
  ```
  
  ::: tip How to Dynamically Modify API Endpoint in Production
  
  Variables starting with `VITE_GLOB_*` in the `.env` file are injected into the `_app.config.js` file during packaging. After packaging, you can modify the corresponding API addresses in `dist/_app.config.js` and refresh the page to apply the changes. This eliminates the need to package multiple times for different environments, allowing a single package to be deployed across multiple API environments.
  
  :::
  
  ### Cross-Origin Resource Sharing (CORS) Handling
  
  In the production environment, if CORS issues arise, you can use `nginx` to proxy the API address or enable `cors` on the backend to handle it (refer to the mock service for examples).
  
  ## API Request Configuration
  
  The project comes with a default basic request configuration based on `axios`, provided by the `@vben/request` package. The project does not overly complicate things but simply wraps some common configurations. If there are other requirements, you can add or adjust the configurations as needed. Depending on the app, different component libraries and `store` might be used, so under the `src/api/request.ts` folder in the application directory, there are corresponding request configuration files. For example, in the `web-antd` project, there's a `src/api/request.ts` file where you can configure according to your needs.
  
  ### Request Examples
  
  #### GET Request
  
  ```ts
  import { requestClient } from '#/api/request';
  
  export async function getUserInfoApi() {
    return requestClient.get<UserInfo>('/user/info');
  }
  ```
  
  #### POST/PUT Request
  
  ```ts
  import { requestClient } from '#/api/request';
  
  export async function saveUserApi(user: UserInfo) {
    return requestClient.post<UserInfo>('/user', user);
  }
  
  export async function saveUserApi(user: UserInfo) {
    return requestClient.put<UserInfo>('/user', user);
  }
  
  export async function saveUserApi(user: UserInfo) {
    const url = user.id ? `/user/${user.id}` : '/user/';
    return requestClient.request<UserInfo>(url, {
      data: user,
      // OR PUT
      method: user.id ? 'PUT' : 'POST',
    });
  }
  ```
  
  #### DELETE Request
  
  ```ts
  import { requestClient } from '#/api/request';
  
  export async function deleteUserApi(userId: number) {
    return requestClient.delete<boolean>(`/user/${userId}`);
  }
  ```
  
  ### Request Configuration
  
  The `src/api/request.ts` within the application can be configured according to the needs of your application:
  
  ```ts
  /**
   * This file can be adjusted according to business logic
   */
  import type { HttpResponse } from '@vben/request';
  
  import { useAppConfig } from '@vben/hooks';
  import { preferences } from '@vben/preferences';
  import {
    authenticateResponseInterceptor,
    errorMessageResponseInterceptor,
    RequestClient,
  } from '@vben/request';
  import { useAccessStore } from '@vben/stores';
  
  import { message } from 'ant-design-vue';
  
  import { useAuthStore } from '#/store';
  
  import { refreshTokenApi } from './core';
  
  const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
  
  function createRequestClient(baseURL: string) {
    const client = new RequestClient({
      baseURL,
    });
  
    /**
     * Re-authentication Logic
     */
    async function doReAuthenticate() {
      console.warn('Access token or refresh token is invalid or expired. ');
      const accessStore = useAccessStore();
      const authStore = useAuthStore();
      accessStore.setAccessToken(null);
      if (preferences.app.loginExpiredMode === 'modal') {
        accessStore.setLoginExpired(true);
      } else {
        await authStore.logout();
      }
    }
  
    /**
     * Refresh token Logic
     */
    async function doRefreshToken() {
      const accessStore = useAccessStore();
      const resp = await refreshTokenApi();
      const newToken = resp.data;
      accessStore.setAccessToken(newToken);
      return newToken;
    }
  
    function formatToken(token: null | string) {
      return token ? `Bearer ${token}` : null;
    }
  
    // Request Header Processing
    client.addRequestInterceptor({
      fulfilled: async (config) => {
        const accessStore = useAccessStore();
  
        config.headers.Authorization = formatToken(accessStore.accessToken);
        config.headers['Accept-Language'] = preferences.app.locale;
        return config;
      },
    });
  
    // Deal Response Data
    client.addResponseInterceptor<HttpResponse>({
      fulfilled: (response) => {
        const { data: responseData, status } = response;
  
        const { code, data } = responseData;
  
        if (status >= 200 && status < 400 && code === 0) {
          return data;
        }
        throw Object.assign({}, response, { response });
      },
    });
  
    // Handling Token Expiration
    client.addResponseInterceptor(
      authenticateResponseInterceptor({
        client,
        doReAuthenticate,
        doRefreshToken,
        enableRefreshToken: preferences.app.enableRefreshToken,
        formatToken,
      }),
    );
  
    // Generic error handling; if none of the above error handling logic is triggered, it will fall back to this.
    client.addResponseInterceptor(
      errorMessageResponseInterceptor((msg: string, error) => {
        // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
        // 当前mock接口返回的错误字段是 error 或者 message
        const responseData = error?.response?.data ?? {};
        const errorMessage = responseData?.error ?? responseData?.message ?? '';
        // 如果没有错误信息,则会根据状态码进行提示
        message.error(errorMessage || msg);
      }),
    );
  
    return client;
  }
  
  export const requestClient = createRequestClient(apiURL);
  
  export const baseRequestClient = new RequestClient({ baseURL: apiURL });
  ```
  
  ### Multiple API Endpoints
  
  To handle multiple API endpoints, simply create multiple `requestClient` instances, as follows:
  
  ```ts
  const { apiURL, otherApiURL } = useAppConfig(
    import.meta.env,
    import.meta.env.PROD,
  );
  
  export const requestClient = createRequestClient(apiURL);
  
  export const otherRequestClient = createRequestClient(otherApiURL);
  ```
  
  ## Refresh Token
  
  The project provides a default logic for refreshing tokens. To enable it, follow the configuration below:
  
  - Ensure the refresh token feature is enabled
  
  Adjust the `preferences.ts` in the corresponding application directory to ensure `enableRefreshToken='true'`.
  
  ```ts
  import { defineOverridesPreferences } from '@vben/preferences';
  
  export const overridesPreferences = defineOverridesPreferences({
    // overrides
    app: {
      enableRefreshToken: true,
    },
  });
  ```
  
  Configure the `doRefreshToken` method in `src/api/request.ts` as follows:
  
  ```ts
  // Adjust this to your token format
  function formatToken(token: null | string) {
    return token ? `Bearer ${token}` : null;
  }
  
  /**
   * Refresh token logic
   */
  async function doRefreshToken() {
    const accessStore = useAccessStore();
    // Adjust this to your refresh token API
    const resp = await refreshTokenApi();
    const newToken = resp.data;
    accessStore.setAccessToken(newToken);
    return newToken;
  }
  ```
  
  ## Data Mocking
  
  ::: tip Production Environment Mock
  
  The new version no longer supports mock in the production environment. Please use real interfaces.
  
  :::
  
  Mock data is an indispensable part of frontend development, serving as a key link in separating frontend and backend development. By agreeing on interfaces with the server side in advance and simulating request data and even logic, frontend development can proceed independently, without being blocked by the backend development process.
  
  The project uses [Nitro](https://nitro.unjs.io/) for local mock data processing. The principle is to start an additional backend service locally, which is a real backend service that can handle requests and return data.
  
  ### Using Nitro
  
  The mock service code is located in the `apps/backend-mock` directory. It does not need to be started manually and is already integrated into the project. You only need to run `pnpm dev` in the project root directory. After running successfully, the console will print `http://localhost:5320/api`, and you can access this address to view the mock service.
  
  [Nitro](https://nitro.unjs.io/) syntax is simple, and you can configure and develop according to your needs. For specific configurations, you can refer to the [Nitro documentation](https://nitro.unjs.io/).
  
  ## Disabling Mock Service
  
  Since mock is essentially a real backend service, if you do not need the mock service, you can configure `VITE_NITRO_MOCK=false` in the `.env.development` file in the project root directory to disable the mock service.
  
  ```bash
  # .env.development
  VITE_NITRO_MOCK=false
  ```