iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 8
0
Modern Web

用Angular打造完整後台系列 第 8

day08 共用service(二)

簡述

說明會有哪些常見的服務是所有組件都會用的。

功能

開發過程中有幾種服務是必然用到的。

  • logger.service.ts
  • web.service.ts
  • data.service.ts
  • user.service.ts
  • login.service.ts

檔案結構

-src
  |-app
    |-app.component.css
    |-app.component.html
    |-app.component.ts
    |-app.module.ts
    |-service
        |-logger.service.ts
        |-web.service.ts
        |-data.service.ts
        |-user.service.ts
        |-login.service.ts

實作

(一) DataService

實務上在接後端 API 的時候,
如果 request 有誤或是 Database 撈出來的 response 不符合預期,
在傳送給前端時會標註 errorcode
並有文件註明 errorcode 的各個編號含義。

但目前使用 json-server
所以會在模擬後端回傳值收到後再包一層errorcode

data.service.ts

import { Injectable } from "@angular/core";
import { Observable } from "rxjs/internal/Observable";
import { map } from "rxjs/operators";
import { WebService } from "./web.service";
import { ApiUrl } from "../config/config";
import { IModel, IUser } from "../model/data";
import { IData, IPage } from "../model/base";
import { Md5 } from "ts-md5";

export interface IFilter {
  key: string;
  val: string | number;
}

@Injectable({
  providedIn: "root"
})
export class DataService {
  apiUrl = ApiUrl;

  constructor(private webService: WebService) {}

  private setUrl(position: string, filters?: IFilter[], id?: number): string {
    let url = `${this.apiUrl}/${position}/`;
    if (!!id) {
      url += `${id}`;
    }
    if (!!filters && !!filters.length) {
      url += `?`;
      filters.forEach((filter: IFilter, index: number) => {
        if (!!index) {
          url += `&&`;
        }
        url += `${filter.key}=${filter.val}`;
      });
    }
    return url;
  }

  connect(token: string): Observable<IData> {
    let url = this.setUrl("admins", [
      { key: "token", val: token },
      { key: "_embed", val: "holds" }
    ]);
    return this.webService.getF(url).pipe(
      map((res: IModel[]) => {
        let errocode = 0;

        if (!res || !res.length) {
          errocode = 2;
        }
        if (!!res[0]) {
          let user = <IUser>res[0];
          if (user.status != 1) {
            errocode = 3;
          }
        }
        this.webService.setHeaders(token);
        return <IData>{
          res: res,
          errorcode: errocode
        };
      })
    );
  }

  resData(res: IModel[] | IModel, errocode: number): IData {
    return <IData>{
      res: res,
      errorcode: errocode
    };
  }

  getData(position: string, id?: number, filters?: IFilter[], pageObj?: IPage): Observable<IData> {
    let url = this.setUrl(position, filters, id);
    return this.webService.getF(url).pipe(
      map((res: IModel[]) => {
        let errocode = 0;
        if (!!pageObj) {
          pageObj.length = res.length;
          res["pageObj"] = pageObj;
          res = this.rangeData(res, pageObj);
        }
        return this.resData(res, errocode);
      })
    );
  }

  insertOne(position: string, obj: IModel): Observable<IData> {
    let url = this.setUrl(position);
    return this.webService.postF(url, obj).pipe(
      map((res: IModel) => {
        let errocode = 0;
        if (!res || !res.id) {
          errocode = 4;
        }
        return this.resData(res, errocode);
      })
    );
  }

  updateOne(position: string, obj: IModel, id: number): Observable<IData> {
    let url = this.setUrl(position, null, id);
    return this.webService.patchF(url, obj).pipe(
      map((res: IModel) => {
        let errocode = 0;
        if (!res) {
          errocode = 5;
        }
        return this.resData(res, errocode);
      })
    );
  }

  deleteOne(position: string, id: number): Observable<IData> {
    let url = this.setUrl(position, null, id);
    return this.webService.delF(url).pipe(
      map((res: IModel) => {
        let errocode = 0;
        if (Object.keys(res).length !== 0) {
          errocode = 6;
        }
        return this.resData(res, errocode);
      })
    );
  }

  //加上新增或是更新日期
  checkData(obj: IModel, operatorId: number, isInsert = true): IModel {
    let insert = <IModel>{
      insertBy: operatorId,
      inserted: new Date().getTime()
    };
    let update = <IModel>{
      updateBy: operatorId,
      updated: new Date().getTime()
    };
    if (isInsert) {
      obj = { ...obj, ...insert, ...update };
    } else {
      //update
      obj = { ...obj, ...update };
    }
    return obj;
  }

  rangeData(obj: IModel[], pageObj: IPage): IModel[] {
    let start = pageObj.pageIndex * pageObj.pageSize;
    let end = start + pageObj.pageSize;
    if (pageObj.length < end) {
      end = pageObj.length;
    }
    return obj.slice(start, end);
  }

  makeToken(account: string): string {
    return <string>Md5.hashStr(`${account}`);
  }
}

專案內使用的加密是ts-md5
https://github.com/cotag/ts-md5

  • setUrl():組合url。

不管是使用哪種 Http 的請求方法,
都必須要指定所需的 url。

url 以來區分前後:
前面就是需要資料的指定位置
如:http://localhost:3000/customers/

後面就是需要資料的篩選或是關聯其他表
如:http://localhost:3000/admins/?_embed=holds

--

  • connect(token):與後端連接。

流程上後台登入頁,管理者輸入帳號密碼後,
此時後端會回傳 token,
然後進入後台主頁時,會再把 token 送到後端,
驗證身份訊息來得到後台相關資料。

可能有人會想說,為什麼 token 送回來還要再送過去?

因一開始登入的帳號密碼送出後,後端會有個規則依據此帳號密碼來
形成一組 token 並加上有效期間,並傳回去給前端。

後續的請求包含需要此登入者身分的請求,
都需要夾帶著 token 送過去,
後端會在回應請求前,確定 token 有效,
才會去撈 Database 的資料再回傳。

不少後端實作其實是判斷帳號密碼成功後直接形成 token
以及此登入者的身份都一次包好送回去。

--

  • 後面四個CRUD的function
    用意只是確保回傳值並加上一層 errorcode

--

  • 每一個後端送回的回傳值都將會是:
//model/base.ts
export interface IData {
  res: IModel | IModel[];
  errorcode: number;
}

範例碼

https://stackblitz.com/edit/ngcms-service


上一篇
day07 共用service(一)
下一篇
day09 共用service(三)
系列文
用Angular打造完整後台30

尚未有邦友留言

立即登入留言