Angular http
<p> 前端应用都需要通过 HTTP 协议与后端服务器通讯,<code>@angular/common/http</code> 中的 <code>HttpClient</code> 类为 Angular 应用程序提供的 API 来实现 HTTP 客户端功能。它基于浏览器提供的 <code>XMLHttpRequest</code> 接口。 <code>HttpClient</code> 带来的其它优点包括:可测试性、强类型的请求和响应对象、发起请求与接收响应时的拦截器支持,以及更好的、基于可观察(Observable)对象的 API 以及流式错误处理机制。</p><h3>1.使用http服务</h3>
<p>在根模块<code>AppModule</code>导入<span class="typ">HttpClientModule。因为大多数应用中,多处多都要使用到http服务,所以直接导入到根模块去。</span><span class="pln"><br></span></p>
<div class="cnblogs_code">
<pre>import { NgModule } <span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">@angular/core</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { BrowserModule } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">@angular/platform-browser</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { HttpClientModule } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">@angular/common/http</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
],
declarations: [
AppComponent,
],
bootstrap: [ AppComponent ]
})
export </span><span style="color: rgba(0, 0, 255, 1)">class</span> AppModule {}</pre>
</div>
<p>然后就可以在服务中使用http服务了</p>
<div class="cnblogs_code">
<pre>import { Injectable } <span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">@angular/core</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { HttpClient } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">@angular/common/http</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
@Injectable()
export </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UserService {
constructor(</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> http: HttpClient) { }<br><br>getUsers():<span class="typ">Observable<span class="pun"><Array<span class="pun"><<span class="typ">userInfo<span class="pun">>></span></span></span></span></span>{<br><span class="pln"> <span class="kwd">return<span class="pln"> <span class="kwd">this<span class="pun">.<span class="pln">http<span class="pun">.<span class="kwd">get<span class="pun"><Array<userInfo><span class="typ"><span class="pun">>(url<span class="kwd"><span class="pun"><span class="pln"><span class="pun">);</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><br>}
}</span></pre>
</div>
<p><strong>2.封装httpService</strong></p>
<p><strong> </strong>通常一个应用我们不会直接使用HttpClient,数据的访问没有这么简单,我们可能会对header 有统一的添加,错误处理,数据处理,网络异常等情况处理。如果不封装一个服务那么我们直接使用HttpClient,会出现大量的重复,冗余杂乱的代码,无法统一标准化,在长期的开发以及测试中会变得麻烦和增加维护成本。这就是为什么在一个应用程序中,无论项目多小,我们都要把数据访问封装一个单独的服务中去的原因。</p>
<p>这个是HttpClient.post 方法,我们可以看到他的返回类型是某个值的 RxJS Observable。</p>
<p><img src="https://img2018.cnblogs.com/i-beta/833855/202001/833855-20200122134103942-971074461.png"></p>
<p>默认情况下把响应体当做无类型的 JSON 对象进行返回,如果指定了模版类型Array<userInfo>,他就会返回一个此类型的对象回来,但是具体还是得取决于数据接口返回来的数据。</p>
<p><span style="font-family: monospace">参考上面的UserService服务里的getUsers。下面是某个组件调用。</span></p>
<p> </p>
<div class="cnblogs_code">
<pre>users: Array<User><span style="color: rgba(0, 0, 0, 1)">;
showUser() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.userService.getUsers()
.subscribe((data:Array</span><User>) => <span style="color: rgba(0, 0, 255, 1)">this</span>.users =<span style="color: rgba(0, 0, 0, 1)"> { ...data });
}</span></pre>
</div>
<p> </p>
<p><strong>返回数据结果统一处理</strong></p>
<p>通常我们API返回来的数据结构是统一的规范化结果,有利于项目前后端分离,团队合作开发。</p>
<div class="cnblogs_code">
<pre>export <span style="color: rgba(0, 0, 255, 1)">class</span> ResultData<T><span style="color: rgba(0, 0, 0, 1)"> {</span><span style="color: rgba(0, 0, 0, 1)">
success: boolean;
msg</span>?: <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">;
data</span>?<span style="color: rgba(0, 0, 0, 1)">: T;
errorcode</span>?: <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">;
timestamp</span>?<span style="color: rgba(0, 0, 0, 1)">: number;
}</span></pre>
</div>
<p>例子在下面👇</p>
<p><strong>错误处理</strong></p>
<p> 当你发起一个网络请求,你不知道服务器会给你返回什么,你不知道请求是否能到达服务器,会不会出现网络堵塞,服务器错误等等等的问题,这时候就需要要捕获错误,并对错误进行处理。使用 RxJS 的 catchError() 操作符来建立对 Observable 结果的处理管道(pipe)</p>
<div class="cnblogs_code">
<pre>//post 请求<br><span style="color: rgba(0, 0, 255, 1)">public</span> post(url: <span style="color: rgba(0, 0, 255, 1)">string</span>, data = {}): Observable<any><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.http.post(url, data, httpOptions).pipe(
map(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.extractData),
catchError(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.handleError)//获取了由 HttpClient 方法返回的 Observable,并把它们通过管道传给错误处理器
);
}
<br>// 返回结果
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> extractData(res: Response) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> res <span style="color: rgba(0, 0, 255, 1)">as</span> ResultData<any><span style="color: rgba(0, 0, 0, 1)">;
}<br>
// 错误消息类
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> handleError(error: HttpErrorResponse) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (error.error instanceof ErrorEvent) {
console.error(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">An error occurred:</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, error.error.message);
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
console.error(
`Backend returned code ${error.status}, ` </span>+<span style="color: rgba(0, 0, 0, 1)"> `body was: ${error.error}`
);
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> throwError(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Something bad happened; please try again later.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p id="retry"><strong><code>retry()</code></strong></p>
<p> 有时候,错误只是临时性的,只要重试就可能会自动消失。RxJS 库提供了几个 retry 操作符,retry()可以对失败的 Observable 自动重新订阅几次。对 HttpClient 方法调用的结果进行重新订阅会导致重新发起 HTTP 请求。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> post(url: <span style="color: rgba(0, 0, 255, 1)">string</span>, data = {}): Observable<any><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.http.post(url, data, httpOptions).pipe(
map(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.extractData),
retry(</span><span style="color: rgba(128, 0, 128, 1)">3</span>),<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">重试失败的请求,最多3次</span>
catchError(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.handleError)
);
}</span></pre>
</div>
<p><strong>添加请求头</strong></p>
<p> 在向API发起请求很多需要添加额外的请求头。 比如,它们可能需要一个 Content-Type 头来显式定义请求体的 MIME 类型。 也可能服务器会需要一个认证用的令牌(token)。</p>
<p>HeroesService 在 httpOptions 对象中就定义了一些这样的请求头,并把它传给每个 HttpClient 的保存型方法。</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> getJsonHeader(header?<span style="color: rgba(0, 0, 0, 1)">: HttpHeaders): HttpHeaders {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (header == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
header </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HttpHeaders();
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> header.<span style="color: rgba(0, 0, 255, 1)">set</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">application/json</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)<br> .set('Authorization', 'Bearer ' + this.token);
}</span></pre>
</div>
<div class="cnblogs_code">
<pre>addUser (user: UserInfo): Observable<boolean><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.http.post<boolean>(url, user, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.getJsonHeader())
.pipe(
catchError(</span><span style="color: rgba(0, 0, 255, 1)">this</span>.handleError(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">addUser</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, user))
);
}</span></pre>
</div>
<p><strong>3.拦截请求</strong></p>
<p> http 拦截机制是 <code>@angular/common/http</code> 中的主要特性之一。 可以声明一些拦截器,用于监视和记录从应用发送到服务器的 HTTP 请求。 拦截器还可以用监视和转换从服务器返回到本应用的那些响应。实现拦截器,就要实现一个实现了 <code>HttpInterceptor</code> 接口中的 <code>intercept()</code> 方法的类。大多数拦截器拦截都会在传入时检查请求,然后把(可能被修改过的)请求转发给 <code>next</code> 对象的 <code>handle()</code> 方法,而 <code>next</code> 对象实现了 <code>HttpHandler</code> 接口。</p>
<div class="cnblogs_code">
<pre>import { HttpRequest, HttpHandler, HttpEvent } <span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">@angular/common/http</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { Observable } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">rxjs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
export </span><span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> HttpInterceptor {
intercept(req: HttpRequest</span><any>, next: HttpHandler): Observable<HttpEvent<any>><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<div class="cnblogs_code">
<pre>import { Injectable } <span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">@angular/core</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { HttpRequest, HttpHandler, HttpResponse } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">@angular/common/http</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { finalize, tap } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">rxjs/operators</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { HttpInterceptor } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">./HttpInterceptor</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { LOGACTION } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">../types/model</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { V5LogSQLService } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">../v5logSql.service</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
@Injectable()
export </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> NoopInterceptor implements HttpInterceptor {
constructor(</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> log: V5LogSQLService) { }
intercept(req: HttpRequest</span><any><span style="color: rgba(0, 0, 0, 1)">, next: HttpHandler) {
</span><span style="color: rgba(0, 0, 255, 1)">const</span> startTime =<span style="color: rgba(0, 0, 0, 1)"> Date.now();
let status: number;
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> next.handle(req).pipe(
tap(
</span><span style="color: rgba(0, 0, 255, 1)">event</span> =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">event</span><span style="color: rgba(0, 0, 0, 1)"> instanceof HttpResponse) {
status </span>= <span style="color: rgba(0, 0, 255, 1)">event</span><span style="color: rgba(0, 0, 0, 1)">.status;
}
},
error </span>=> status =<span style="color: rgba(0, 0, 0, 1)"> error.status
),
finalize(() </span>=><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.log.log(LOGACTION.HTTP, <span style="color: rgba(128, 0, 0, 1)">''</span> , JSON.stringify({ elapsedTime: (Date.now() - startTime) + <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ms</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, url: req.urlWithParams, status: status, param: req.body }));
})
);
}
}</span></pre>
</div>
<p> next 对象表示拦截器链表中的下一个拦截器。 这个链表中的最后一个 next 对象就是 HttpClient 的后端处理器(backend handler),它会把请求发给服务器,并接收服务器的响应。大多数的拦截器都会调用 next.handle(),以便这个请求流能走到下一个拦截器,并最终传给后端处理器。 拦截器也可以不调用 next.handle(),使这个链路短路,并返回一个带有人工构造出来的服务器响应的 自己的 Observable,这是一种常见的中间件模式。</p>
<p><strong>使用这个拦截器</strong><br> 这个 NoopInterceptor 就是一个由 Angular 依赖注入 (DI)系统管理的服务。 像其它服务一样,你也必须先提供这个拦截器类,应用才能使用它。</p>
<p>由于拦截器是 HttpClient 服务的(可选)依赖,所以你必须在提供 HttpClient 的同一个(或其各级父注入器)注入器中提供这些拦截器。 那些在 DI 创建完 HttpClient 之后再提供的拦截器将会被忽略。由于在 AppModule 中导入了 HttpClientModule,导致本应用在其根注入器中提供了 HttpClient。所以你也同样要在 AppModule 中提供这些拦截器。</p>
<p>注意 <code>multi: true</code> 选项。 这个必须的选项会告诉 Angular <code>HTTP_INTERCEPTORS</code> 是一个多重提供商的令牌,表示它会注入一个多值的数组,而不是单一的值。</p>
<p>在从 @angular/common/http 中导入了 HTTP_INTERCEPTORS 注入令牌之后,编写如下的 NoopInterceptor 提供商注册语句:</p>
<p>AppModule根模块</p>
<div class="cnblogs_code">
<pre>{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: <span style="color: rgba(0, 0, 255, 1)">true</span> },</pre>
</div>
<p>创建一个封装桶(barrel)文件,用于把所有拦截器都收集起来,一起提供给 <code>httpInterceptorProviders</code> 数组。</p>
<div class="cnblogs_code">
<pre>import { HTTP_INTERCEPTORS } <span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">@angular/common/http</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
import { NoopInterceptor } </span><span style="color: rgba(0, 0, 255, 1)">from</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">./noop-interceptor</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
export </span><span style="color: rgba(0, 0, 255, 1)">const</span> httpInterceptorProviders =<span style="color: rgba(0, 0, 0, 1)"> [
{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> },
];</span></pre>
</div>
<p>使用:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">providers: [
httpInterceptorProviders
],</span></pre>
</div>
<p> 拦截请求还有,拦截的顺序,以及httpEvent等。 <code>HttpRequest</code> 和 <code>HttpResponse是只读的,只可对其监视 or 日志记录,要想修改该请求,就要先克隆它,并修改这个克隆体,然后再把这个克隆体传给 <code>next.handle(),</code>你可以用一步操作中完成对请求的克隆和修改。具体看 https://angular.cn/guide/http#intercepting-requests-and-responses,因为我对这个不熟现实开发中并未使用,仅仅使用拦截器记录监听请求记录日志而已</code><code>。 </code></p>
<p> 拦截器的使用场景有:设置默认请求头,记日志,缓存,返回多值可观察对象,监听进度事件等。</p>
<p><strong>4.其他</strong></p>
<p>1.前面提到过默认情况下把响应体当做无类型的 JSON 对象进行返回,如果请求返回来的是文件流等,就要另外做处理。</p>
<p>例如</p>
<div class="cnblogs_code">
<pre>getTextFile(filename: <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span>.http.<span style="color: rgba(0, 0, 255, 1)">get</span>(filename, {responseType: <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">})
.pipe(
tap(
data </span>=> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.log(filename, data),
error </span>=> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.logError(filename, error)
)
);
}</span></pre>
</div>
<p>2.RxJS 是一个库,用于把异步调用和基于回调的代码组合成函数式(functional)的、响应式(reactive)的风格。 很多 Angular API,包括 HttpClient 都会生成和消费 RxJS 的 Observable。在整个httpService中我们用了RxJS库提供的几个操作符 catchError Observable retry 等,学习RxJS,有利于开发者在整个应用程序开发的开发,可以提高效率,以及代码质量。</p>
<p> </p>
<p>此随笔乃本人学习工作记录,如有疑问欢迎在下面评论,转载请标明出处。</p>
<p>如果对您有帮助请动动鼠标右下方给我来个赞,您的支持是我最大的动力。</p><br><br>
来源:https://www.cnblogs.com/huangenai/p/12218982.html
頁:
[1]