知識庫 / Spring / Spring Cloud RSS 訂閱

Spring Cloud – 添加 Angular 4 教程

Spring Cloud
HongKong
5
02:33 PM · Dec 06 ,2025

1. 概述

在上一篇 Spring Cloud 文章中,我們已將 Zipkin 支持添加到我們的應用程序中。 在本文中,我們將向我們的堆棧中添加一個前端應用程序。

到目前為止,我們一直在完全在後端構建我們的雲應用程序。 但如果應用程序沒有用户界面,那麼一個 Web 應用程序有什麼用? 在本文中,我們將通過將單頁應用程序集成到我們的項目中的解決方案。

我們將使用 AngularBootstrap 構建此應用程序。 Angular 代碼的風格感覺與編寫 Spring 應用程序非常相似,這對於 Spring 開發者來説是一個自然的過渡! 雖然前端代碼將使用 Angular,但本文的內容可以輕鬆擴展到任何前端框架,只需少量的努力。

在本文中,我們將構建一個 Angular 應用程序並將其連接到我們的雲服務。 我們將演示如何通過 SPA 和 Spring Security 之間的身份驗證進行集成。 我們還將演示如何使用 Angular 對 HTTP 通信的支持來訪問應用程序的數據。

2. 網關變更

在前台構建完成後,我們將切換到基於表單的登錄方式,併為特權用户安全化UI的部分進行調整。 這需要對我們的網關安全配置進行修改。

2.1. 更新 HttpSecurity

首先,讓我們更新我們在網關 SecurityConfig.java 類中的 configure(HttpSecurity http) 方法:

@Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
    http.formLogin()
        .authenticationSuccessHandler(
            new RedirectServerAuthenticationSuccessHandler("/home/browser/index.html"))
        .and().authorizeExchange()
        .pathMatchers("/book-service/**", "/rating-service/**", "/login*", "/").permitAll()
        .pathMatchers("/eureka/**").hasRole("ADMIN")
        .anyExchange().authenticated()
        .and().logout().and().csrf().disable()
        .httpBasic(withDefaults());
    return http.build();
}

首先,我們添加了一個默認成功 URL,指向 /home/browser/index.html,因為這是我們的 Angular 應用運行的地方。接下來,我們配置了 ant 匹配器,允許所有請求通過網關,除非是 Eureka 資源。這將把所有安全檢查委託給後端服務。

接下來,我們移除了註銷成功 URL,因為默認重定向回登錄頁面即可正常工作。

2.2. 添加主端點

接下來,我們添加一個端點以返回已認證的用户。這將用於我們的 Angular 應用中進行登錄並識別用户擁有的角色。這將幫助我們控制用户在網站上可以執行的操作。

在網關項目下,添加一個 <em >AuthenticationController</em> 類:

@RestController
public class AuthenticationController {
 
    @GetMapping("/me")
    public Principal getMyUser(Principal principal) {
        return principal;
    }
}

控制器返回當前已登錄的用户對象給調用者。這使我們能夠獲取所有控制 Angular 應用所需的信息。

2.3. 添加登陸頁面

讓我們添加一個簡單的登陸頁面,以便用户在訪問應用程序的根目錄時看到一些內容。

<em src/main/resources/static</em> 中,添加一個 <em index.html</em> 文件,其中包含指向登錄頁面的鏈接:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Book Rater Landing</title>
</head>
<body>
    <h1>Book Rater</h1>
    <p>So many great things about the books</p>
    <a href="/login">Login</a>
</body>
</html>

3. Angular CLI 與啓動項目

在開始新的 Angular 項目之前,請務必安裝最新版本的 Node.js 和 npm

3.1. 安裝 Angular CLI

為了開始,我們需要使用 npm 下載並安裝 Angular 命令行界面。 打開終端並運行:

npm install -g @angular/cli

這將下載並全局安裝 CLI。

3.2. 安裝新項目

在終端中,導航到網關項目目錄,進入網關/src/main 文件夾。創建一個名為“angular”的目錄,並進入該目錄。從這裏運行:

ng new ui

請耐心等待;CLI 正在設置一個全新的項目,並使用 npm 下載所有 JavaScript 依賴項。這個過程通常需要花費數分鐘。

ng 命令是 Angular CLI 的快捷方式,new 參數指示 CLI 創建一個新的項目,而 ui 命令則為項目指定名稱。

3.3. 運行項目

完成 新命令 的執行後,請導航到創建的 ui 文件夾並運行:

ng serve

項目構建完成後,請訪問 http://localhost:4200。您應該在瀏覽器中看到以下內容:

恭喜!我們剛剛構建了一個 Angular 應用!

3.4. 安裝 Bootstrap

使用 npm 安裝 Bootstrap。 在 ui 目錄下運行以下命令:

npm install [email protected] --save

這將下載 Bootstrap 到 node_modules 文件夾中。

ui 目錄下,打開 angular.json 文件。 這是用於配置項目某些屬性的文件。 找到 projects > styles 屬性,並添加 Bootstrap CSS 類的文件位置:

"styles": [
    "styles.css",
    "../node_modules/bootstrap/dist/css/bootstrap.min.css"
],

這將指示 Angular 將 Bootstrap 包含在項目構建的編譯後的 CSS 文件中。

3.5. 設置構建輸出目錄

接下來,我們需要告訴 Angular 將構建文件放置在哪裏,以便我們的 Spring Boot 應用可以提供它們。 Spring Boot 可以從 resources 文件夾的兩個位置提供文件:

  • src/main/resources/static
  • src/main/resource/public

由於我們已經在 static 文件夾中提供了一些資源供 Eureka 使用,並且每次構建 Angular 應用時,Angular 會刪除此文件夾,因此我們應該將 Angular 應用構建到 public 文件夾中。

再次打開 angular.json 文件,找到 options > outputPath 屬性,並更新該 string

"outputPath": "../../resources/static/home",

如果 Angular 項目位於 src/main/angular/ui 目錄下,則構建輸出將位於 src/main/resources/public 文件夾中。如果應用程序位於其他文件夾中,則需要修改此字符串以正確設置位置。

3.6. 使用 Maven 自動化構建

最後,我們將設置一個自動化構建,以便在編譯代碼時自動運行。此 Ant 任務將每當運行“mvn compile”時,運行 Angular CLI 構建任務。 將此步驟添加到網關的 POM.xml 中,以確保每次編譯時都能獲取最新的 UI 更改:

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <executions>
        <execution>
            <phase>generate-resources</phase>
            <configuration>
                <tasks>
                    <exec executable="cmd" osfamily="windows"
                      dir="${project.basedir}/src/main/angular/ui">
                        <arg value="/c"/>
                        <arg value="ng"/>
                        <arg value="build"/>
                    </exec>
                    <exec executable="/bin/sh" osfamily="mac"
                      dir="${project.basedir}/src/main/angular/ui">
                        <arg value="-c"/>
                        <arg value="ng build"/>
                    </exec>
                </tasks>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

需要注意的是,此配置要求 Angular CLI 必須存在於類路徑中。將此腳本推送到沒有該依賴的環境中,將會導致構建失敗。

現在讓我們開始構建我們的 Angular 應用程序!

4. Angular

在本教程的這一部分,我們將為我們的頁面構建一個身份驗證機制。我們使用基本身份驗證,並遵循一個簡單的流程來實現它。

用户有一個登錄表單,他們可以在其中輸入用户名和密碼。

接下來,我們使用他們的憑據創建一個 base64 身份驗證令牌,並請求 “/me” 端點。該端點返回一個 Principal 對象,其中包含該用户的角色。

最後,我們將憑據和 principal 存儲在客户端,以便在後續請求中使用。

讓我們看看如何完成這項工作!

4.1. 模板

在網關項目中,導航至 src/main/angular/ui/src/app 目錄,並打開 app.component.html 文件。這是 Angular 首次加載的模板,用户登錄後將在此模板中出現。

在這裏,我們將添加一些代碼以顯示一個包含登錄表單的導航欄:

<nav class="navbar navbar-toggleable-md navbar-inverse fixed-top bg-inverse">
    <button class="navbar-toggler navbar-toggler-right" type="button" 
      data-toggle="collapse" data-target="#navbarCollapse" 
      aria-controls="navbarCollapse" aria-expanded="false" 
      aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
    </button>
    <a class="navbar-brand" href="#">Book Rater 
        <span *ngIf="principal.isAdmin()">Admin</span></a>
    <div class="collapse navbar-collapse" id="navbarCollapse">
    <ul class="navbar-nav mr-auto">
    </ul>
    <button *ngIf="principal.authenticated" type="button" 
      class="btn btn-link" (click)="onLogout()">Logout</button>
    </div>
</nav>

<div class="jumbotron">
    <div class="container">
        <h1>Book Rater App</h1>
        <p *ngIf="!principal.authenticated" class="lead">
        Anyone can view the books.
        </p>
        <p *ngIf="principal.authenticated && !principal.isAdmin()" class="lead">
        Users can view and create ratings</p>
        <p *ngIf="principal.isAdmin()"  class="lead">Admins can do anything!</p>
    </div>
</div>

這段代碼設置了一個帶有 Bootstrap 類別的導航欄。導航欄內嵌入了一個內聯登錄表單。Angular 使用這種標記與 JavaScript 動態交互,以渲染頁面的各個部分並控制諸如表單提交之類的操作。

例如,語句(ngSubmit)=”onLogin(f)”僅僅指示當表單提交時調用方法“onLogin(f)”並將表單傳遞給該函數。在jumbotron div 中,我們有段落標籤,它們將根據我們的 principal 對象的狀態動態顯示。

接下來,讓我們編寫支持此模板的 Typescript 文件。

4.2. TypeScript

從同一目錄下打開 app.component.ts 文件。在該文件中,我們將添加所有必要的 TypeScript 屬性和方法,以使模板功能正常工作:

import {Component} from '@angular/core';
import {RouterOutlet} from '@angular/router';
import {Principal} from "./principal";
import {HttpClientModule, HttpResponse} from "@angular/common/http";
import {Book} from "./book";
import {HttpService} from "./http.service";
import {CommonModule} from '@angular/common';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, CommonModule, HttpClientModule],
  providers: [HttpService],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'  
})
export class AppComponent {
  selectedBook: Book = null;
  principal: Principal = new Principal(false, []);
  loginFailed: boolean = false;

  constructor(private httpService: HttpService){}

  ngOnInit(): void {
    this.httpService.me()
      .subscribe((response) => {
        let principalJson = response.json();
        this.principal = new Principal(principalJson.authenticated, principalJson.authorities);
      }, (error) => {
        console.log(error);
      });
  }

  onLogout() {
    this.httpService.logout()
      .subscribe((response) => {
        if (response.status === 200) {
          this.loginFailed = false;
          this.principal = new Principal(false, []);
          window.location.replace(response.url);
        }
      }, (error) => {
        console.log(error);
      });
  }
}

這個類鈎取 Angular 生命週期方法 ngOnInit()。在該方法中,我們調用 /me 端點以獲取用户的當前角色和狀態。這將決定用户在主頁上看到的內容。此方法將在此組件創建時觸發,這是檢查用户屬性以獲取應用程序中權限的好時機。

我們還有一個 onLogout() 方法,它會註銷我們的用户並恢復該頁面的狀態到其原始設置。

這裏有一些魔法在發生,即 httpService 屬性,該屬性在構造函數中聲明。Angular 在運行時將此屬性注入到我們的類中。Angular 管理單例服務類的實例並使用構造函數注入進行注入,就像 Spring 一樣!

接下來,我們需要定義 HttpService 類。

4.3. HttpService

在同一目錄下創建名為 “http.service.ts” 的文件。 在該文件中添加以下代碼以支持登錄和註銷方法:

import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
import {HttpClientModule, HttpResponse, HttpClient, HttpHeaders, HttpRequest, HttpContext} from "@angular/common/http";

@Injectable({providedIn: 'root'})
export class HttpService {

  constructor(private http: HttpClient) { }

  me(): Observable {
    return this.http.get("/me", {'headers': this.makeOptions()})
  }

  logout(): Observable {
    return this.http.post("/logout", '', {'headers': this.makeOptions()})
  }

  private makeOptions(): HttpHeaders {
    return new HttpHeaders({'Content-Type': 'application/json'});
  }
}

在這一類中,我們使用 Angular 的 DI 構造注入了另一個依賴項。這次是 HttpClient 類。該類處理所有 HTTP 通信,並由框架提供。

這些方法都使用 Angular 的 HTTP 庫執行 HTTP 請求。每個請求還會指定在標頭中的內容類型。

現在我們需要做一件更多的事情,將 HttpService 註冊到依賴注入系統中。打開 app.component.ts 文件並找到 providers 屬性。將 HttpService 添加到該數組。結果應如下所示:

providers: [HttpService],

4.4. 添加 Principal

接下來,讓我們在 TypeScript 代碼中添加我們的 Principal DTO 對象。 在同一目錄下添加一個名為 “principal.ts” 的文件,並添加以下代碼:

export class Principal {
    public authenticated: boolean;
    public authorities: Authority[] = [];
    public credentials: any;

    constructor(authenticated: boolean, authorities: any[], credentials: any) {
        this.authenticated = authenticated;
        authorities.map(
          auth => this.authorities.push(new Authority(auth.authority)))
        this.credentials = credentials;
  }

    isAdmin() {
        return this.authorities.some(
          (auth: Authority) => auth.authority.indexOf('ADMIN') > -1)
    }
}

export class Authority {
    public authority: String;

    constructor(authority: String) {
        this.authority = authority;
    }
}

我們添加了 Principal 類和 Authority 類。這些是兩個 DTO 類,與 Spring 應用中的 POJO 類似。因此,我們不需要將這些類註冊到 Angular 的 DI 系統中。

接下來,讓我們配置一個重定向規則,將未知請求重定向到我們應用程序的根目錄。

4.5. 404 處理

讓我們回到 Java 代碼中,對網關服務進行導航。在 GatewayApplication 類中添加一個名為 ErrorPageConfig 的新類:

@Component
public class ErrorPageConfig implements ErrorPageRegistrar {
 
    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {
        registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,
          "/home/index.html"));
    }

}

此類將識別任何 404 響應並重定向用户到 “/home/index.html”。 在單頁應用程序中,我們這樣處理所有未指向專用資源的流量,因為客户端應處理所有可導航的路由。

現在我們準備啓動此應用程序並查看我們構建的內容!

<h3><strong >4.6. 構建與查看</strong ></h3
<p>現在,從 gateway 文件夾運行 “<em >mvn compile</em ></p>
<p>這將編譯我們的 Java 源代碼併為 Angular 應用構建到 public 文件夾。 接下來啓動其他雲應用程序:<em >config</em ></p>
<p>、<em >discovery</em ></p>
<p>和 <em >zipkin</em ></p>
<p>然後運行 gateway 項目。 當服務啓動時,導航到 <em >http://localhost:8080</em > 以查看我們的應用。 我們應該看到類似的內容:</p>
<img src="/file/story/attachments/image/l/e6298a1a-430c-4525-b537-30ae0f4f505a">
<p>接下來,請按照鏈接進入登錄頁面:</p>
<img src="/file/story/attachments/image/l/f9eb41e9-3f5c-4566-b239-a4f6e7145417">
<p>使用用户/密碼憑據登錄。 點擊“登錄”,我們應該被重定向到 /home/index.html,其中我們的單頁應用加載。</p>
<img src="/file/story/attachments/image/l/b694121c-00bc-4f6e-911f-03236ada45fa">
<p>看起來我們的 <em >jumbotron</em > 正在指示我們已作為用户登錄! 現在,通過點擊右上角的鏈接註銷,並使用 <em >admin/admin</em > 憑據登錄一次。</p>
<img src="/file/story/attachments/image/l/613e4762-f1bb-44a7-aa1d-318b17503ad9">
<p>一切正常! 現在我們已作為管理員登錄。</p>

5. 結論

在本文中,我們看到了如何輕鬆地將單頁應用程序集成到我們的雲系統中。我們使用了現代框架,並將一個可工作的安全配置集成到我們的應用程序中。

通過這些示例,嘗試編寫一些代碼來調用 book-servicerating-service。 鑑於我們現在已經有了發起 HTTP 調用和將數據連接到模板的示例,這應該相對容易。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.