上一节介绍了好友模块,这一节介绍和好友模块中的控件有关的三个服务程序。
用HttpClient拦截器发送用户认证信息
在进入好友模块之前,需要向服务器发送认证信息,在这里使用angular的HttpClient拦截器进行发送。
拦截器的官方解释为:HTTP 拦截机制是 @angular/common/http 中的主要特性之一。 使用这种拦截机制,你可以声明一些拦截器,用它们监视和转换从应用发送到服务器的 HTTP 请求。 拦截器还可以用监视和转换从服务器返回到本应用的那些响应。 多个选择器会构成一个“请求/响应处理器”的双向链表。如果想详细了解拦截器,可以看官方文档
我们利用拦截器在每次向服务器请求朋友列表时将认证信息加入到头部。
具体代码如下:
import { Injectable } from '@angular/core'; import { HttpEvent,HttpInterceptor,HttpHandler,HttpRequest } from '@angular/common/http'; import { Observable } from 'rxjs/observable'; import { AuthTokenService } from './authtoken.service'; @Injectable() export class AuthInterceptor implements HttpInterceptor{ constructor( private tokenServ: AuthTokenService ){} intercept(req: HttpRequest<any>,next: HttpHandler) : Observable<HttpEvent<any>>{ //获取认证信息 const auth = this.tokenServ.getToken(); //克隆request,加入新的头信息 const authReq = req.clone({headers:req.headers.set('Authorization','Bearer ' + auth)}); return next.handle(authReq); } }
要实现拦截器,就要实现一个实现了 HttpInterceptor 接口中的 intercept() 方法的类(AuthInterceptor)。在intercept()方法中先通过AuthTokenService的getToken()方法取得认证信息。这些认证信息是在登录或注册成功后由服务器发回来的jwt认证信息。服务器如何发送这些信息请参考第三节的内容,认证信息的内容是登录或认证的用户ID。因为HttpRequest 实例的属性却是只读(readonly)的,要修改请求信息只能先克隆它。在这里利用clone()方法在请求的头部信息中加入认证信息( clone() 方法的哈希型参数允许你在复制出克隆体的同时改变该请求的某些特定属性)。最后调用 next.handle(),以便这个请求流能走到下一个拦截器,并最终传给后端处理器。
最后还需要向模块这个拦截器,这个AuthInterceptor拦截器就是一个由 Angular 依赖注入 (DI)系统管理的服务,你必须在提供 HttpClient 的同一个(或其各级父注入器)注入器中提供这些拦截器。在好友模块的providers中加入
{ provide: HTTP_INTERCEPTORS,useClass: AuthInterceptor,multi:true }
现在在好友模块中每个发送到服务器的请求都会在头部加上认证信息。
补充内容:服务器jwt认证中间件
express的jwt中间件定义代码如下:
var expressJwt = require('express-jwt'); //使用jwt拦截 app.use(expressJwt({ secret: 'secret' })); //处置jwt异常 app.use(function (err,req,res,next) { if (err.name === 'UnauthorizedError') { res.status(401).send({ 'code': 401,'msg': 'invalid token' }); } }); app.use('/friends',friends);
一定要把处理friends访问的路由放到jwt中间件后面,不然jwt无法进行验证。
利用路由守卫保证未登录用户无法访问好友信息
在上一节介绍路由时,在路由配置中加入了canActivate: [AuthGuardService],这是angular路由守卫服务,路由守卫的作用在官方文档中的解释如下:
现在,任何用户都能在任何时候导航到任何地方。 但有时候这样是不对的。
该用户可能无权导航到目标组件。
可能用户得先登录(认证)。
在显示目标组件前,你可能得先获取某些数据。
在离开组件前,你可能要先保存修改。
你可能要询问用户:你是否要放弃本次更改,而不用保存它们?
你可以往路由配置中添加守卫,来处理这些场景。
守卫返回一个值,以控制路由器的行为:
如果它返回 true,导航过程会继续
如果它返回 false,导航过程会终止,且用户会留在原地。
在这里我们利用路由守卫要求用户先登录才能导航到birthday模块中的控件。
代码如下:
import { Injectable } from '@angular/core'; import { CanActivate,ActivatedRouteSnapshot,RouterStateSnapshot,Router } from '@angular/router'; import { UserService } from './user.service'; import { AuthTokenService } from './authtoken.service'; @Injectable() export class AuthGuardService implements CanActivate { constructor( private tokenServe: AuthTokenService,private router: Router) { } canActivate(route: ActivatedRouteSnapshot,state: RouterStateSnapshot) { if (this.tokenServe.getToken() !== null) { return true; } this.router.navigate(['/login']); return false; } }
路由守卫类也是一个注入服务类,它需要实现CanActivate接口的canActivate()方法。canActivate()方法实现了守卫代码。代码很简单,从AuthTokenService类的getToken()中获取认证信息的值,如果有就返回true,如果没有就导航到登录页面。并返回false。
最后记住在birthday模块中providers中加入AuthGuardService。
birthday.service数据提供服务介绍
BirthdayService类为birthday模块提供了数据服务,代码如下:
import { Injectable } from '@angular/core'; import { HttpClient,HttpHeaders,HttpErrorResponse } from '@angular/common/http'; import { UserService } from '../user.service'; import 'rxjs/add/operator/map'; export class Friend { constructor( public fid: number,public fname: string,public fbirth: Date,public fnumber: string,public femail: string,public fgroup: string,public state: string,public photo: string,public uid:number ) { } } @Injectable() export class BirthdayService { constructor( private userServ: UserService,private http: HttpClient) { } //获取全部朋友信息 getFriends() { return this.http.get('http://localhost:3000/friends/friend-list',{ observe: 'response'});} //获取单个朋友信息 getFriend(id: number | string) { return this.getFriends().map(res => { if (res.body['code'] === '200') { return res.body['results'].find(result => result.fid === +id);} }); } //修改朋友信息 editFriend(friend: Friend){ const body = {'value':friend,'operate':'edit'}; return this.http.post('http://localhost:3000/friends/editfriend',body); } //新建朋友信息 newFriend(friend: Friend){ const body = {'value':friend,'operate':'new'}; return this.http.post('http://localhost:3000/friends/editfriend',body); } //删除好友 deleteFriend(friend:Friend){ const body = {'value':friend,'operate':'delete'}; return this.http.post('http://localhost:3000/friends/editfriend',body); } //错误处理 handleError(err: HttpErrorResponse): string { if (err.error instanceof Error) { return '发生错误,错误信息:' + err.error.message; } else { console.log(`Backend returned code ${err.status},body was: ${err.error['msg']}`); return err.error['msg']; } } }
首先在类外定义了一个Friend类,在这个类中定义了friend信息。BirthdayService类的主要功能有6部分:
- 获取全部朋友信息。通过HttpClient的get方法发送获取到全部的friend信息的请求。
- 获取单个朋友信息。getFriends()方法返回的是一个Observable对象,利用Observable的map()函数的回调找到对应id的单个friend对象,并继续发射Observable对象。
- 修改朋友信息。将修改后的friend信息post到服务器。在发送的body中,除了修改后的friend对象,还发送了一个字符串属性:'operate':'edit',用于区分是修改friend还是新建friend,这了的edit代表修改信息。(具体的服务器操作代码将在下一章介绍)。
- 新建朋友信息。和修改friend信息同理,只不过将body中的'operate'改为'new'。
- 删除好友。也和修改friend信息同理,只不过将body中的'operate'改为'delete'。
- 错误处理。如果是客户端(angular代码)出了错,会抛出一个 Error 类型的异常,由此判断如果错误的类型是Error类型,就表示前端出错,返回一条错误信息:'发生错误,错误信息:' + err.error.message;。如果是后端出错,就打印出错误状态和信息。
关于birthday模块的服务程序就介绍完了。下一章将要介绍服务器端express框架如何处理这些请求。今天将我的代码传到了github上,方便大家参考。地址如下:
前端:https://github.com/db991400/b...
后端:https://github.com/db991400/b...