プロジェクト紹介
本プロジェクトは、Angularの練習用として作成したWebアプリケーションです。Bangumi上のアニメを表示・検索するためのもので、Bangumi APIを使用しています。
本プロジェクトは、GitHub Actionsを使用してGitHub Pagesへ自動的にデプロイされます。
プロジェクト名
my-angular-project-test
プロジェクトの目的
- Angularベースの静的Webサイトをデプロイする
- GitHub Actionsによる自動デプロイを練習する
- APIを呼び出して機能を実装する
- Cognitoを使用してユーザー認証を行う
- インターセプターを使用してリクエストを処理する
- ガードを使用してページを保護する
プロジェクトの技術スタック
- Angular 16
- TypeScript
- HTML
- CSS
- GitHub Actions
- Cognito
環境準備
環境要件
- Node.js バージョン20以上
- Angular CLI
インストール手順
-
Node.jsをインストールする
<https://nodejs.org/en/download/> -
Angular CLIをインストールする
npm install -g @angular/cli -
プロジェクトをインストールする
git clone <https://github.com/dreaife/my-angular-project-test.git>cd my-angular-project-testnpm install
プロジェクト構成
ディレクトリ構成
本プロジェクトはAngular CLIを使用して作成されており、構成は次のとおりです。
my-angular-project-test/├── src/│ ├── app/│ │ ├── environment/│ │ │ ├── environment.ts│ │ ├── components/│ │ │ ├── login/│ │ │ ├── home/│ │ │ ├── search/│ │ ├── guards/│ │ │ ├── auth.guard.ts│ │ ├── interceptors/│ │ │ ├── auth.interceptor.ts│ │ ├── services/│ │ │ ├── auth.service.ts│ │ │ ├── bgm.service.ts│ │ ├── app.component.ts│ ├── index.html│ ├── main.ts├── ...各ディレクトリの説明は次のとおりです。
src/app
プロジェクトのメインディレクトリで、すべてのコンポーネント、サービス、インターセプター、ガードなどが含まれています。src/environments
環境設定ファイルのディレクトリで、開発環境と本番環境の設定が含まれています。src/components
プロジェクトの主要コンポーネント用ディレクトリで、すべてのページコンポーネントが含まれています。loginコンポーネントはログインページで、CognitoのSDKを呼び出してログインを行います。homeコンポーネントはアニメカレンダーページで、bgm.service.getCalendarを呼び出してデータを取得し、表示します。searchコンポーネントは検索ページで、bgm.service.searchを呼び出してデータを取得し、表示します。
src/guards
プロジェクトの主要なガード用ディレクトリです。ログインが必要なページを保護するためのauth.guard.tsガードが含まれており、未ログインの場合はログインページへリダイレクトします。src/interceptors
プロジェクトの主要なインターセプター用ディレクトリです。リクエストに認証情報を追加するためのauth.interceptor.tsインターセプターが含まれています。src/services
プロジェクトの主要なサービス用ディレクトリです。ログインやログアウトなどを処理するauth.service.tsサービスと、Bangumi APIを呼び出すbgm.service.tsサービスが含まれています。src/main.ts
Angularアプリケーションを起動するための、プロジェクトのメインエントリーファイルです。
主要機能の実装
Cognitoを使用したユーザー認証
src/app/services/auth.service.ts では、CognitoのSDKを使用してユーザー認証を行います。
Cognitoを使用する前に、AWS Cognitoでユーザープールを作成し、Cognitoの認証ドメインを設定してから、アプリクライアントを作成してクライアントIDを取得する必要があります。
取得したIDを使用して、src/app/environment/environment.ts にCognitoの設定情報を記述します。
ログイン
cognitoUser.authenticateUserメソッドでログインし、成功後にidTokenまたはaccessTokenをsessionStorageへ保存します。
ヒント:
未検証のユーザーは、最初に新しいパスワードを設定する必要があります。この場合はnewPasswordRequiredメソッドをオーバーライドし、resolve(\{ newPasswordRequired: true, cognitoUser \})を設定します。これにより、ログインページの表示内容を切り替え、ユーザーに新しいパスワードの設定を促します。
実装コード:
signIn(username: string, password: string): Promise<any> { const authenticationDetails = new AuthenticationDetails({ Username: username, Password: password });
const userData = { Username: username, Pool: this.userPool }; const cognitoUser = new CognitoUser(userData);
return new Promise((resolve, reject) => { cognitoUser.authenticateUser(authenticationDetails, { onSuccess: (result) => { // 获取 Tokens const idToken = result.getIdToken().getJwtToken(); const accessToken = result.getAccessToken().getJwtToken(); const refreshToken = result.getRefreshToken().getToken();
// console.log('idToken', idToken); // console.log('accessToken', accessToken); // console.log('refreshToken', refreshToken);
// 将 idToken 或 accessToken 存储到 sessionStorage 作为 userToken sessionStorage.setItem('userToken', accessToken);
// 保存 Tokens 或在需要的地方使用 resolve({ idToken, accessToken, refreshToken });
// 登录成功后重定向到主页 this.router.navigate(['/']); }, onFailure: (err) => { reject(err.message || JSON.stringify(err)); }, newPasswordRequired: (userAttributes, requiredAttributes) => { // 触发新密码需求,提示前端进行新密码设置 resolve({ newPasswordRequired: true, cognitoUser }); } }); }); }ユーザーが新しいパスワードを設定する際は、completeNewPasswordメソッドを呼び出し、cognitoUser.completeNewPasswordChallengeメソッドで新しいパスワードを設定します。
// 设置新密码方法 completeNewPassword(cognitoUser: CognitoUser, newPassword: string): Promise<any> { return new Promise((resolve, reject) => { cognitoUser.completeNewPasswordChallenge(newPassword, {}, { onSuccess: (session) => resolve(session), onFailure: (err) => reject(err.message || JSON.stringify(err)) }); }); }登録
cognitoUser.signUpメソッドで登録し、成功後にユーザー名とパスワードをCognitoへ保存します。その後、ページをログインページへリダイレクトします。
// 注册方法 signUp(username: string, password: string, email: string): Promise<any> { return new Promise((resolve, reject) => { const attributeList : CognitoUserAttribute[] = []; attributeList.push(new CognitoUserAttribute({ Name: 'email', Value: email }));
this.userPool.signUp(username, password, attributeList, [], (err, result) => { if (err) { reject(err.message || JSON.stringify(err)); } else { resolve(result?.user); } }); }); }ログアウト
cognitoUser.signOutメソッドでログアウトし、ログアウト後にsessionStorage内のuserTokenを削除します。
logout() { // 登出 this.userPool.getCurrentUser()?.signOut(); sessionStorage.removeItem('userToken'); this.router.navigate(['/login']); }ログインページ
ログインページは src/app/components/login/login.component.ts です。CognitoのSDKを使用してログインし、成功後にidTokenまたはaccessTokenをsessionStorageへ保存します。
ページの表示内容はauthModeで制御します。authModeには次の種類があります。
- login:ログインページ
- register:登録ページ
- forgotPassword:パスワードを忘れた場合のページ
- confirmSignUp:認証ページ
- resetPassword:パスワード再設定ページ
対応するボタンをクリックすると、authServiceのswitchModeメソッドが呼び出されてauthModeが切り替わり、ページの表示内容も切り替わります。
ページの実装:
switchMode(mode: 'login' | 'register' | 'forgotPassword' | 'confirmSignUp' | 'resetPassword') { this.authMode = mode; this.message = '';}
onSubmit() { if (this.authMode === 'login') { this.authService.signIn(this.username, this.password).then( (resp) => { if (resp.newPasswordRequired) { // 初次登录需要重置密码,显示浮窗 this.showNewPasswordModal = true; this.cognitoUser = resp.cognitoUser; } else { // 登录成功 this.message = '登录成功!'; } }).catch(err => { this.message = `登录失败:${err}`; }); } else if (this.authMode === 'register') { this.authService.signUp(this.username, this.password, this.email).then( () => { this.message = '注册成功!请检查邮箱并输入验证码。'; this.authMode = 'confirmSignUp'; }, (err) => (this.message = `注册失败:${err}`) ); } else if (this.authMode === 'forgotPassword') { this.authService.forgotPassword(this.username).then( () => { this.message = '验证码已发送,请检查邮箱并输入验证码和新密码。'; this.authMode = 'resetPassword'; }, (err) => (this.message = `发送验证码失败:${err}`) ); } else if (this.authMode === 'confirmSignUp') { this.authService.confirmSignUp(this.username, this.code).then( () => (this.message = '验证成功!请登录。'), (err) => (this.message = `验证失败:${err}`) ); } else if (this.authMode === 'resetPassword') { this.authService.confirmPassword(this.username, this.code, this.newPassword).then( () => { this.message = '密码重置成功!请使用新密码登录。'; this.authMode = 'login'; // 切换回登录页面 }, (err) => (this.message = `密码更新失败:${err}`) ); } }アニメカレンダーページ
アニメカレンダーページは src/app/components/home/home.component.ts です。bgm.service.getCalendarを呼び出してデータを取得し、表示します。
ページの初期化時にngOnInitメソッドを呼び出し、データを取得して表示します。
ngOnInit() : void { // this.bgmService.getCalendar().subscribe(data => console.log(data)); // this.bgmService.getSubject('482850').subscribe(data => console.log(data)); this.bgmService.getCalendar().subscribe((data:any[]) => { this.weeklyData = Array(7).fill(null).map((_, index) => ({ day: this.daysOfWeek[index], items: data .find((d: any) => d.weekday.id === index + 1) ?.items.filter((item: any) => item.collection?.doing >= 100) || [] })); }); }
navigateToItem(id: string) { this.router.navigate(['/items', id]); }
// 显示浮窗并加载数据 openModal(itemId: string): void { this.bgmService.getSubject(itemId).subscribe((data) => { this.selectedItem = data; this.showModal = true; }); } // 关闭浮窗 closeModal(): void { this.showModal = false; this.selectedItem = null; }
// 辅助方法:查找 infobox 中的官方网站 URL getOfficialWebsite(): string | null { if (!this.selectedItem || !this.selectedItem.infobox) return null; const website = this.selectedItem.infobox.find((info: any) => info.key === '官方网站'); return website ? website.value : null; }検索ページ
検索ページは src/app/components/search/search.component.ts です。bgm.service.searchを呼び出してデータを取得し、表示します。
ページではtitleで検索キーワードを受け取り、optionsで表示内容を制御します。optionsには次の種類があります。
- limit:1ページあたりの表示件数
- type:タイプ
- meta_tags:メタタグ
- tag:タグ
- air_date:放送日
- rating:評価
- rank:ランキング
- nsfw:成人向けコンテンツを含めるかどうか
- page:ページ番号
APIへリクエストを送信する際は、titleとoptionsを再構成してから送信します。リクエストの再構成は次のとおりです。
// 搜索方法 onSearch(): void { if (this.searchQuery.trim()) { this.isLoading = true; this.errorMessage = '';
// 配置搜索选项 const options = { limit: this.limit, type: this.type, meta_tags: this.meta_tags, tag: this.tag, air_date: this.air_date, rating: this.rating, rank: this.rank, nsfw: this.nsfw, page: this.page };
this.bgmService.searchSubject(this.searchQuery, options).subscribe( (response: any) => { this.searchResults = response.data; // 提取 data 数组 this.totalResults = response.total; // 提取总数 this.isLoading = false; }, (error) => { this.errorMessage = '搜索失败,请重试。'; this.isLoading = false; } ); } }インターセプターによる認証情報の追加
src/app/interceptors/auth.interceptor.ts では、インターセプターを使用してリクエストに認証情報を追加します。
インターセプターの実装:
export const authInterceptor: HttpInterceptorFn = (req, next) => { const authToken = environment.bgm.authToken;
if (req.url.startsWith('<https://api.bgm.tv/v0>')) { # 如果请求地址以https://api.bgm.tv/v0开头,则添加认证信息 const authReq = req.clone({ setHeaders: { Authorization: `Bearer ${authToken}`, # 添加认证信息 } }); return next(authReq); } return next(req);};インターセプターを使用するには、app.config.tsでインターセプターを設定する必要があります。
export const appConfig: ApplicationConfig = { providers: [ provideHttpClient( withInterceptors([authInterceptor]) ) ]};ガードによるページの保護
src/app/guards/auth.guard.ts では、ガードを使用してログインが必要なページを保護します。未ログインの場合は、ログインページへリダイレクトします。
ガードの実装:
export const authGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); const router = inject(Router);
if (authService.isLoggedIn()) { return true; } else { // 没有登录,重定向到 /login router.navigate(['/login']); return false; }};ガードを使用するには、ルーティング設定でガードを指定する必要があります。
{ path: '', component: HomeComponent, canActivate: [authGuard] }, # 主页需要登录プロジェクトの起動
-
プロジェクトを起動する
ng serve -
URLへアクセスする
プロジェクトのデプロイ
-
プロジェクトをローカルでビルドする
ng build -
自動デプロイ
本プロジェクトは、GitHub Actionsを使用してGitHub Pagesへ自動的にデプロイされます。コードをGitHubへpushするたびに、GitHub Actionsがpushイベントを検出し、プロジェクトを自動的にビルドしてGitHub Pagesへデプロイします。
GitHub Actionsによるプロジェクトの自動デプロイを設定するため、設定ファイル
.github/workflows/main.ymlを作成します。内容は次のとおりです。
# GitHub Actions 工作流,用于将项目部署到 GitHub Pagesname: Deploy to GitHub Pages# 触发条件:当推送到 master 分支时触发on:push:branches:- master # 或者你要监控的分支名称jobs:build-and-deploy:# 使用最新的 Ubuntu 作为运行环境runs-on: ubuntu-lateststeps:# 第一步:检出代码- name: Checkout codeuses: actions/checkout@v3# 第二步:设置 Node.js 环境- name: Setup Node.jsuses: actions/setup-node@v3with:node-version: '20' # 请根据项目需求修改 Node.js 版本# 第三步:安装项目依赖- name: Install dependenciesrun: npm install# 第四步:生成环境配置文件 environment.ts- name: Generate environment.tsrun: |# 创建 src/app/environment 目录(如果不存在)mkdir -p src/app/environment# 生成 environment.ts 文件,包含 Cognito 和 Bangumi API 的配置信息echo "export const environment = {production: true,cognito: {userPoolId: '$COGNITO_USER_POOL_ID',clientId: '$COGNITO_CLIENT_ID',domain: '$COGNITO_DOMAIN'},bgm: {url: '<https://api.bgm.tv/v0>',authToken: '$BGM_AUTH_TOKEN',userAgent: 'dreaife/my-angular-project-test'}};" > src/app/environment/environment.ts # 生成环境配置文件# 列出生成的文件以确认ls src/app/environment# 第五步:构建项目- name: Build projectrun: npm run build -- --configuration production --base-href "/my-angular-project-test/" # 构建项目# 第六步:部署到 GitHub Pages- name: Deploy to GitHub Pagesuses: JamesIves/github-pages-deploy-action@v4with:# browser 为构建输出的文件夹,内部文件包含 index.htmlfolder: dist/my-angular-project/browser # 请根据实际输出路径填写token: ${{ secrets.TOKEN }}# 环境变量配置,使用 GitHub Secrets 存储敏感信息env:COGNITO_USER_POOL_ID: ${{ secrets.COGNITO_USER_POOL_ID }}COGNITO_CLIENT_ID: ${{ secrets.COGNITO_CLIENT_ID }}COGNITO_DOMAIN: ${{ secrets.COGNITO_DOMAIN }}BGM_AUTH_TOKEN: ${{ secrets.BGM_AUTH_TOKEN }}GITHUB_TOKEN: ${{ secrets.TOKEN }}
この記事が役に立ったときは、ぜひ他の人に共有してください!
一部の情報は古い可能性があります





