首页 > 解决方案 > 具有 Contentful 和 TransferState 的 Angular Universal

问题描述

我在内容和角度通用方面遇到了一些真正的问题。我已经尝试了很多方法来尝试加载内容并在查看页面源中呈现,但我尝试过的一切都失败了。我得到的最接近的方法是使用TransferState,我在一个涵盖 API 调用的教程中找到了它。

我创建了这个类:

import { Injectable } from '@angular/core';
import { TransferState } from '@angular/platform-browser';
import { Observable, from } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class TransferHttpService {
  constructor(
    private transferHttp: TransferState,
    private httpClient: HttpClient
  ) {}
  get(url, options?): Observable<any> {
    return this.getData(url, options, () => {
      return this.httpClient.get(url, options);
    });
  }
  post(url, body, options?): Observable<any> {
    return this.getData(url, options, () => {
      return this.httpClient.post(url, body, options);
    });
  }
  delete(url, options?): Observable<any> {
    return this.getData(url, options, () => {
      return this.httpClient.delete(url, options);
    });
  }
  put(url, body, options?): Observable<any> {
    return this.getData(url, options, () => {
      return this.httpClient.put(url, body, options);
    });
  }
  getData(url, options, callback: () => Observable<any>): Observable<any> {
    const optionsString = options ? JSON.stringify(options) : '';
    let key = `${url + optionsString}`;
    try {
      return this.resolveData(key);
    } catch (e) {
      console.log('In catch', key);
      return callback().pipe(
        tap((data) => {
          console.log('cache set', key);
          this.setCache(key, data);
        })
      );
    }
  }
  resolveData(key) {
    let resultData: any;
    if (this.hasKey(key)) {
      resultData = this.getFromCache(key);
      console.log('got cache', key);
    } else {
      throw new Error();
    }
    return from(Promise.resolve(resultData));
  }
  setCache(key, value) {
    this.transferHttp.set(key, value);
  }
  getFromCache(key) {
    return this.transferHttp.get(key, null); // null set as default value
  }
  hasKey(key) {
    return this.transferHttp.hasKey(key);
  }
}

然后在我注入的地方HttpClient,我用我的新类 ( TransferHttpService) 替换它工作正常。使用内容时,我将旧服务更新为:

import { Injectable } from '@angular/core';
import { createClient, Entry } from 'contentful';
import { documentToHtmlString } from '@contentful/rich-text-html-renderer';
import { Observable, from } from 'rxjs';

import { environment } from '@environments/environment';
import { Content, Page, Menu, MenuItem, Footer, Image } from '@models';
import { TransferHttpService } from './transfer-http.service';

@Injectable({
  providedIn: 'root',
})
export class ContentfulService {
  private client = createClient({
    space: environment.space,
    accessToken: environment.cdaAccessToken,
  });

  constructor(private http: TransferHttpService) {}

  getMetadata(): Observable<any> {
    return this.http.getData('metadata', null, () => {
      return from(
        this.client
          .getEntries({
            content_type: 'metadata',
          })
          .then((response) => response.items[0])
      );
    });
  }

  getPages(): Observable<Page[]> {
    return this.http.getData('pages', null, () => {
      return from(
        this.client
          .getEntries({
            content_type: 'page',
            include: 3,
          })
          .then((response) => {
            let pages = response.items.map((item) =>
              this.createPage(item, this.createContent)
            );
            return pages;
          })
      );
    });
  }

  getNavigation(): Observable<Menu> {
    return this.http.getData('navigation', null, () => {
      return from(
        this.client
          .getEntries({
            content_type: 'navigationMenu',
          })
          .then((response) => this.createMenu(response.items[0]))
      );
    });
  }

  getFooter(): Observable<Footer> {
    return this.http.getData('footer', null, () => {
      return from(
        this.client
          .getEntries({
            content_type: 'footer',
            include: 2,
          })
          .then((response) => this.createFooter(response.items[0]))
      );
    });
  }

  public createImage(component: any): Image {
    if (!component) return;
    return {
      title: component.fields.title,
      url: component.fields.file.url,
      alt: component.fields.file.fileName,
    };
  }

  private createFooter(component: any): Footer {
    return {
      title: component.fields.title,
      htmlContent: documentToHtmlString(component.fields.content),
      careersText: component.fields.careersText,
      careersLink: component.fields.careersLink,
      cookiePolicyLink: component.fields.cookiePolicyLink,
      privacyPolicyLink: component.fields.privacyPolicyLink,
      termsLink: component.fields.termsLink,
    };
  }

  private createMenu(menu: any): Menu {
    return {
      title: menu.fields.title,
      links: menu.fields.links.map(this.createMenuItem),
    };
  }

  private createMenuItem(item: any): MenuItem {
    return {
      path: item.fields.path,
      text: item.fields.linkText,
    };
  }

  private createPage(page: Entry<any>, createContent: any): Page {
    return {
      title: page.fields['title'],
      slug: page.fields['path'],
      linkText: page.fields['linkText'],
      content: page.fields['content'].map(createContent),
    };
  }

  private createContent(component: Entry<any>): Content {
    return {
      type: component.sys.contentType.sys.id,
      fields: component.fields,
    };
  }
}

如您所见,在处理完响应后,我正在对 observable 执行createClient getEntries承诺,然后将其传递给负责设置/获取缓存响应TransferHttpService的方法。getData

我怀疑这是问题所在,但页眉和页脚似乎总是被缓存。


我一直在玩这个,当我将TransferHttpService更改为此时,我得到了更好的结果:

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import {
  TransferState,
  StateKey,
  makeStateKey,
} from '@angular/platform-browser';
import { Observable, from } from 'rxjs';
import { tap } from 'rxjs/operators';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

@Injectable({ providedIn: 'root' })
export class TransferHttpService {
  constructor(
    protected transferState: TransferState,
    private httpClient: HttpClient,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {}

  request<T>(
    method: string,
    uri: string | Request,
    options?: {
      body?: any;
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      reportProgress?: boolean;
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      method,
      uri,
      options,
      (method: string, url: string, options: any) => {
        return this.httpClient.request<T>(method, url, options);
      }
    );
  }

  /**
   * Performs a request with `get` http method.
   */
  get<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'get',
      url,
      options,
      (_method: string, url: string, options: any) => {
        return this.httpClient.get<T>(url, options);
      }
    );
  }

  /**
   * Performs a request with `post` http method.
   */
  post<T>(
    url: string,
    body: any,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getPostData<T>(
      'post',
      url,
      body,
      options,
      // tslint:disable-next-line:no-shadowed-variable
      (_method: string, url: string, body: any, options: any) => {
        return this.httpClient.post<T>(url, body, options);
      }
    );
  }

  /**
   * Performs a request with `put` http method.
   */
  put<T>(
    url: string,
    _body: any,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'body';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getPostData<T>(
      'put',
      url,
      _body,
      options,
      (_method: string, url: string, _body: any, options: any) => {
        return this.httpClient.put<T>(url, _body, options);
      }
    );
  }

  /**
   * Performs a request with `delete` http method.
   */
  delete<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'delete',
      url,
      options,
      (_method: string, url: string, options: any) => {
        return this.httpClient.delete<T>(url, options);
      }
    );
  }

  /**
   * Performs a request with `patch` http method.
   */
  patch<T>(
    url: string,
    body: any,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getPostData<T>(
      'patch',
      url,
      body,
      options,
      // tslint:disable-next-line:no-shadowed-variable
      (
        _method: string,
        url: string,
        body: any,
        options: any
      ): Observable<any> => {
        return this.httpClient.patch<T>(url, body, options);
      }
    );
  }

  /**
   * Performs a request with `head` http method.
   */
  head<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'head',
      url,
      options,
      (_method: string, url: string, options: any) => {
        return this.httpClient.head<T>(url, options);
      }
    );
  }

  /**
   * Performs a request with `options` http method.
   */
  options<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'options',
      url,
      options,
      // tslint:disable-next-line:no-shadowed-variable
      (_method: string, url: string, options: any) => {
        return this.httpClient.options<T>(url, options);
      }
    );
  }

  // tslint:disable-next-line:max-line-length
  getData<T>(
    method: string,
    uri: string | Request,
    options: any,
    callback: (
      method: string,
      uri: string | Request,
      options: any
    ) => Observable<any>
  ): Observable<T> {
    let url = uri;

    if (typeof uri !== 'string') {
      url = uri.url;
    }

    const tempKey = url + (options ? JSON.stringify(options) : '');
    const key = makeStateKey<T>(tempKey);
    try {
      return this.resolveData<T>(key);
    } catch (e) {
      console.log('in catch', key);
      return callback(method, uri, options).pipe(
        tap((data: T) => {
          if (isPlatformBrowser(this.platformId)) {
            // Client only code.
            // nothing;
          }
          if (isPlatformServer(this.platformId)) {
            console.log('set cache', key);
            this.setCache<T>(key, data);
          }
        })
      );
    }
  }

  private getPostData<T>(
    _method: string,
    uri: string | Request,
    body: any,
    options: any,
    callback: (
      method: string,
      uri: string | Request,
      body: any,
      options: any
    ) => Observable<any>
  ): Observable<T> {
    let url = uri;

    if (typeof uri !== 'string') {
      url = uri.url;
    }

    const tempKey =
      url +
      (body ? JSON.stringify(body) : '') +
      (options ? JSON.stringify(options) : '');
    const key = makeStateKey<T>(tempKey);

    try {
      return this.resolveData<T>(key);
    } catch (e) {
      return callback(_method, uri, body, options).pipe(
        tap((data: T) => {
          if (isPlatformBrowser(this.platformId)) {
            // Client only code.
            // nothing;
          }
          if (isPlatformServer(this.platformId)) {
            this.setCache<T>(key, data);
          }
        })
      );
    }
  }

  private resolveData<T>(key: StateKey<T>): Observable<T> {
    const data = this.getFromCache<T>(key);
    console.log(data);

    if (!data) {
      throw new Error();
    }

    if (isPlatformBrowser(this.platformId)) {
      console.log('get cache', key);
      // Client only code.
      this.transferState.remove(key);
    }
    if (isPlatformServer(this.platformId)) {
      console.log('we are the server');
      // Server only code.
    }

    return from(Promise.resolve<T>(data));
  }

  private setCache<T>(key: StateKey<T>, data: T): void {
    return this.transferState.set<T>(key, data);
  }

  private getFromCache<T>(key: StateKey<T>): T {
    return this.transferState.get<T>(key, null);
  }
}

在花了几个小时之后,我发现只有在对我的服务器进行 API 调用时,内容才有效,这很奇怪......


啊!我发誓,这不应该这么难。如果我有这样的组件:

import { Component, OnInit } from '@angular/core';

import { Page } from '@models';
import { ContentfulService, TransferHttpService } from '@services';
import { environment } from '@environments/environment';

@Component({
  templateUrl: './pages.component.html',
  styleUrls: ['./pages.component.scss'],
})
export class PagesComponent implements OnInit {
  public pages: Page[];

  constructor(
    private brandService: TransferHttpService,
    private contentfulService: ContentfulService
  ) {}

  ngOnInit(): void {
    this.listPages();
    this.listBrands();
  }

  private listPages(): void {
    this.contentfulService
      .getPages()
      .subscribe((pages: any) => (this.pages = pages));
  }

  private listBrands(): void {
    this.brandService
      .get(`${environment.apiUrl}/brands/simple`)
      .subscribe((response: any) => {
        console.log(response);
      });
  }
}

当我运行本地服务器时,npm run dev:ssr我在控制台中得到了这个:

在此处输入图像描述

这是完美的。一切都在我的View Page Source中。但!如果我删除该listBrands方法(因为不需要它,我只是将其用作测试)。我明白了:

在此处输入图像描述

哪个不好;什么都没有缓存,我的查看页面源没有页眉、页脚、页面或元数据.....

我不明白发生了什么事。为什么当我有另一个 API 调用到不同的服务器时它会起作用?

标签: angularcontentfulangular-transfer-state

解决方案


我设法弄清楚这一点,内容使用HttpClient但确实为您提供了使用适配器的选项。它提到了 axios,但我真的不知道那是什么。我花了好几个小时到处玩,发现我可以TransferHttpService像这样在适配器中使用我自己的:

private client = createClient({
  space: environment.space,
  accessToken: environment.cdaAccessToken,
  adapter: async (config: any) => {
    config.adapter = null;
    const response = await this.http
      .request(config.method, `${config.baseURL}/${config.url}`, {
        headers: {
          Accept: config.headers['Accept'],
          Authorization: config.headers['Authorization'],
          'Content-Type': config.headers['Content-Type'],
          'X-Contentful-User-Agent':
            config.headers['X-Contentful-User-Agent'],
        },
        params: config.params,
      })
      .toPromise();
    return {
      data: response,
    };
  },
});

TransferHttpService看起来像这样:

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import {
  TransferState,
  StateKey,
  makeStateKey,
} from '@angular/platform-browser';
import { Observable, from } from 'rxjs';
import { tap } from 'rxjs/operators';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

@Injectable({ providedIn: 'root' })
export class TransferHttpService {
  constructor(
    protected transferState: TransferState,
    private httpClient: HttpClient,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {}

  request<T>(
    method: string,
    uri: string | Request,
    options?: {
      body?: any;
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      reportProgress?: boolean;
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      method,
      uri,
      options,
      (method: string, url: string, options: any) => {
        return this.httpClient.request<T>(method, url, options);
      }
    );
  }

  /**
   * Performs a request with `get` http method.
   */
  get<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'get',
      url,
      options,
      (_method: string, url: string, options: any) => {
        return this.httpClient.get<T>(url, options);
      }
    );
  }

  /**
   * Performs a request with `post` http method.
   */
  post<T>(
    url: string,
    body: any,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getPostData<T>(
      'post',
      url,
      body,
      options,
      // tslint:disable-next-line:no-shadowed-variable
      (_method: string, url: string, body: any, options: any) => {
        return this.httpClient.post<T>(url, body, options);
      }
    );
  }

  /**
   * Performs a request with `put` http method.
   */
  put<T>(
    url: string,
    _body: any,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'body';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getPostData<T>(
      'put',
      url,
      _body,
      options,
      (_method: string, url: string, _body: any, options: any) => {
        return this.httpClient.put<T>(url, _body, options);
      }
    );
  }

  /**
   * Performs a request with `delete` http method.
   */
  delete<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'delete',
      url,
      options,
      (_method: string, url: string, options: any) => {
        return this.httpClient.delete<T>(url, options);
      }
    );
  }

  /**
   * Performs a request with `patch` http method.
   */
  patch<T>(
    url: string,
    body: any,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getPostData<T>(
      'patch',
      url,
      body,
      options,
      // tslint:disable-next-line:no-shadowed-variable
      (
        _method: string,
        url: string,
        body: any,
        options: any
      ): Observable<any> => {
        return this.httpClient.patch<T>(url, body, options);
      }
    );
  }

  /**
   * Performs a request with `head` http method.
   */
  head<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'head',
      url,
      options,
      (_method: string, url: string, options: any) => {
        return this.httpClient.head<T>(url, options);
      }
    );
  }

  /**
   * Performs a request with `options` http method.
   */
  options<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'options',
      url,
      options,
      // tslint:disable-next-line:no-shadowed-variable
      (_method: string, url: string, options: any) => {
        return this.httpClient.options<T>(url, options);
      }
    );
  }

  // tslint:disable-next-line:max-line-length
  getData<T>(
    method: string,
    uri: string | Request,
    options: any,
    callback: (
      method: string,
      uri: string | Request,
      options: any
    ) => Observable<any>
  ): Observable<T> {
    let url = uri;

    if (typeof uri !== 'string') {
      url = uri.url;
    }

    const tempKey = url + (options ? JSON.stringify(options) : '');
    const key = makeStateKey<T>(tempKey);
    try {
      return this.resolveData<T>(key);
    } catch (e) {
      //console.log('in catch', key);
      return callback(method, uri, options).pipe(
        tap((data: T) => {
          if (isPlatformBrowser(this.platformId)) {
            // Client only code.
            // nothing;
          }
          if (isPlatformServer(this.platformId)) {
            //console.log('set cache', key);
            this.setCache<T>(key, data);
          }
        })
      );
    }
  }

  private getPostData<T>(
    _method: string,
    uri: string | Request,
    body: any,
    options: any,
    callback: (
      method: string,
      uri: string | Request,
      body: any,
      options: any
    ) => Observable<any>
  ): Observable<T> {
    let url = uri;

    if (typeof uri !== 'string') {
      url = uri.url;
    }

    const tempKey =
      url +
      (body ? JSON.stringify(body) : '') +
      (options ? JSON.stringify(options) : '');
    const key = makeStateKey<T>(tempKey);

    try {
      return this.resolveData<T>(key);
    } catch (e) {
      return callback(_method, uri, body, options).pipe(
        tap((data: T) => {
          if (isPlatformBrowser(this.platformId)) {
            // Client only code.
            // nothing;
          }
          if (isPlatformServer(this.platformId)) {
            this.setCache<T>(key, data);
          }
        })
      );
    }
  }

  private resolveData<T>(key: StateKey<T>): Observable<T> {
    const data = this.getFromCache<T>(key);

    if (!data) {
      throw new Error();
    }

    if (isPlatformBrowser(this.platformId)) {
      //console.log('get cache', key);
      // Client only code.
      this.transferState.remove(key);
    }
    if (isPlatformServer(this.platformId)) {
      //console.log('we are the server');
      // Server only code.
    }

    return from(Promise.resolve<T>(data));
  }

  private setCache<T>(key: StateKey<T>, data: T): void {
    return this.transferState.set<T>(key, data);
  }

  private getFromCache<T>(key: StateKey<T>): T {
    return this.transferState.get<T>(key, null);
  }
}

就是这样;一旦我这样做了,一切都开始工作了。


推荐阅读