50、重新探索歌词查找器的开发实现

重新探索歌词查找器的开发实现

1. 前端部分
1.1 用户界面搭建

为了创建 LyricsFinder V2 的用户界面,我们将安装并使用 React Bootstrap(https://react-bootstrap.github.io),这是一个基于广受欢迎的 Bootstrap(https://getbootstrap.com)CSS 库构建的 React UI 组件库。该库提供了近 30 个组件,足以构建新的用户界面。

当然,和 Angular 以及 Vue.js 一样,React 也有许多其他的 UI 工具包可供选择。这里我们选择了一个与之前不同的流行库,让你了解另一种界面组装方式。以下是一些其他的 React UI 工具包,你可以参考:
- https://dev.to/kayis/react-ui-kits-3fm2
- https://www.codeinwp.com/blog/react-ui-component-libraries-frameworks
- https://hackernoon.com/23-best-react-ui-component-libraries-and-frameworks-250a81b2ac42

为了处理页面之间的导航,我们将使用 React Router(https://reacttraining.com/react-router),这是一个流行的 React 开源路由器。后续会介绍如何安装和使用这些工具。

相关参考链接:
- React Bootstrap: https://react-bootstrap.github.io
- Bootstrap: https://getbootstrap.com

1.2 API 调用

前端应用将使用 Apollo GraphQL 客户端结合 React Apollo(https://github.com/apollographql/react-apollo)和 Apollo Boost(https://github.com/apollographql/apollo-client/tree/master/packages/apollo-boost)来获取艺术家、歌曲和歌词的信息。简单来说,我们会使用 GraphQL 客户端发送 GraphQL 查询并获取所需数据,后续章节会详细介绍。

2. 后端部分
2.1 后端技术栈

为了实现后端应用,我们将结合以下技术:
- 平台/运行时:Node.js
- 框架:NestJS(https://nestjs.com)
- 查询语言/API:GraphQL 结合 Apollo GraphQL

利用这些技术,我们将创建一个后端系统,通过 GraphQL API 公开艺术家、歌曲和歌词的信息。创建的 GraphQL API 实际上会使用之前版本应用中开发的 MusicService 从 MusixMatch API 获取数据,因此它类似于一个 API 网关层。

相关参考链接:
- GraphQL: https://graphql.org/learn
- NestJS: https://nestjs.com
- Node.js: https://nodejs.org/en
- Apollo: https://www.apollographql.com
- GraphQL 插件 for NestJS: https://github.com/nestjs/graphql

2.2 TypeScript 的使用

我们将在前端和后端都使用 TypeScript。TypeScript 不仅仅局限于前端应用开发,它是一种可以在技术栈的各个层面使用的编程语言。

3. 项目搭建
3.1 创建项目

我们准备了项目的初始骨架,可将其复制到工作区。骨架的目的是让我们专注于新元素,而不是重复之前的代码集成工作。

前端文件夹中是使用 create-react-app 并启用 TypeScript 创建的 React 应用,后端文件夹中是使用 NestJS CLI 创建的 NestJS 应用,并且已经包含了之前的 MusicService 类和相关代码。

由于 React 默认使用 yarn,本章也将使用它。你也可以使用 npm(如使用 npm ci 命令),但要注意可能需要一些调整,因为初始项目没有包含锁文件,可能会导致依赖版本不同,出现不可预测的行为。

如果你还没有安装 yarn,可以使用以下方法:
- 最简单的方法是利用已安装的 npm 全局安装 yarn: npm install --global yarn
- 也可以参考 https://yarnpkg.com/lang/en/docs/install 中的其他方法。

如果你已经安装了 yarn,可以进行以下操作:
1. 将项目骨架文件夹复制到工作区。
2. 在前端和后端文件夹中分别使用 yarn install 安装项目依赖。

3.2 项目结构和约定

项目顶层有两个主要文件夹:backend 和 frontend,用于清晰分离前后端应用。

在实际项目中,可能会使用 Nrwl NX(https://github.com/nrwl/nx)来管理整个工作区,它可以方便地共享代码和优化构建,但这里暂不涉及。

前后端项目的源代码都放在 src 文件夹中,文件名使用小写连字符格式,React 组件也如此,除了 create-react-app 创建的默认文件,你也可以重命名以保持结构统一。

前端 React 应用的组件放在 src/components 文件夹中,这里只放无状态组件;对于有状态组件(即页面),将创建一个 pages 文件夹。

4. 后端应用创建与配置
4.1 后端项目创建

后端文件夹已经有一个起始点,我们使用 NestJS CLI 创建初始后端项目: npx @nestjs/cli new backend 。为了方便构建后端,之前使用的 MusicService 类已复制到 backend/src/musixmatch 目录,并进行了一些 NestJS 适配。

4.2 启动开发服务器

此应用需要同时运行两个独立的服务器:
- 后端 Node/NestJS 应用服务器,用于公开 GraphQL API。
- 前端 React 应用服务器,用于消费 GraphQL API。

启动命令如下:
1. 在后端文件夹中使用 yarn start:dev 启动后端服务器。
2. 在前端文件夹中使用 yarn start 启动前端服务器。

注意,一开始后端应用可能无法正常工作,后续会解决这个问题。之后需要保持两个终端窗口打开,前端开始向后端发送请求时,两个服务器必须并行启动,应用才能正常工作。通常,前端服务器监听 3000 端口,后端服务器监听 4000 端口。

4.3 添加必要依赖

由于要使用 GraphQL,需要向后端添加一些依赖:
1. 进入后端文件夹。
2. 执行命令: yarn add @nestjs/graphql apollo-server-express graphql-tools graphql type-graphql

这些包将用于实现 GraphQL API,同时我们安装了 NestJS 的 GraphQL 插件,方便开发。

4.4 实现 GraphQL API 与首个解析器
  • 添加 MusixMatch API 密钥 :在开始编写代码前,需要将 MusixMatch API 密钥添加到 backend/src/api-key.ts 文件中,否则代码无法从 MusixMatch 获取数据。如果还没有密钥,需要获取一个。这是应用正常运行的必要步骤。
  • 创建歌曲模块 :在终端进入后端文件夹,使用 NestJS CLI 执行命令: npx @nestjs/cli generate module song ,这将在项目中创建歌曲的 NestJS 模块,可用于清晰分离应用的不同部分。
  • 创建歌曲 GraphQL 解析器 :使用 GraphQL 插件提供的命令: npx @nestjs/cli g resolver song ,该命令会生成一个空的 GraphQL 解析器,并将其注册到模块中。
  • 创建歌曲 DTO 并为 GraphQL 装饰 :创建一个 song.dto.ts 文件,路径为 src/song,内容如下:
import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
export class SongDto {
  @Field(() => ID)
  id: string;
  @Field()
  name: string;
  @Field()
  artistId: string;
  @Field(() => Boolean)
  hasLyrics: boolean;
  @Field(() => [String])
  genres: string[];
}

这个类定义了歌曲的基本信息,使用 @ObjectType() 装饰器将其配置为 GraphQL 可识别的类型,每个字段使用 @Field 装饰器让 GraphQL 考虑这些字段。

  • 实现歌曲解析器并利用 NestJS 特性 :创建并打开 backend/src/song/song.resolver.ts 文件,内容如下:
import { Args, Query, Resolver } from '@nestjs/graphql';
import { MusicService } from '../musixmatch/services';
import { Inject } from '@nestjs/common';
import { TYPES } from '../musixmatch/ioc/types';
import { SongDto } from './song.dto';
import { Observable } from 'rxjs';

@Resolver('Song')
export class SongResolver {
  constructor(
    @Inject(TYPES.MUSIC_SERVICE)
    private readonly musicService: MusicService,
  ) {}

  @Query(() => [SongDto])
  songs(@Args('value') value: string): Observable<SongDto[]> {
    return this.musicService.findSongs(value);
  }
}

这里使用 @Inject() 装饰器注入 MusicService, @Resolver 装饰器表明这是一个解析器, @Query 描述了解析器解析的对象类型, @Args 关联查询参数和解析器函数参数。解析器接收查询的搜索文本,传递给 MusicService 从 MusixMatch API 获取结果,整个过程是异步的。

4.5 导入和配置 GraphQL NestJS 模块

打开 backend/src/app.module.ts 文件,内容如下:

@Module({
  imports: [
    GraphQLModule.forRoot({
      debug: true,
      playground: true,
      autoSchemaFile: 'schema.gql',
    }),
    // ...
  ],
  // ...
})
export class AppModule {}

这里加载并配置了 GraphQLModule,启用了调试模式和 playground,方便在浏览器中调试和测试 GraphQL API。选择了 Code First 方法,通过 autoSchemaFile: 'schema.gql' 选项让 NestJS GraphQL 插件根据 DTO 和解析器中的装饰器自动生成 GraphQL 模式文件。

4.6 使用 GraphQL playground 测试 API

当 SongModule 正确定义且 GraphQLModule 加载到 NestJS 应用的 app 模块后,就可以测试 API 了。
1. 执行 yarn start:dev 启动后端服务器。
2. 打开浏览器,访问 http://localhost:4000/graphql,会看到之前启用的 playground。
3. 在左侧面板清空内容,输入以下查询:

{
  songs(value: "never gonna give you up") {
    id,
    name,
    hasLyrics
  }
}

这个查询请求匹配歌曲标题的歌曲的 id、name 和 hasLyrics 属性。在 playground 中可以使用 Ctrl + 空格键(macOS 为 cmd + 空格键)进行自动补全。执行查询后,右侧会显示结果,证明 API 正常工作。

5. 后端添加艺术家和歌词支持

为了让 GraphQL API 支持查找艺术家和加载歌曲歌词,需要创建相应的模块和 GraphQL 解析器。

在后端文件夹中执行以下命令:

npx @nestjs/cli g mo artist
npx @nestjs/cli g r artist
npx @nestjs/cli g mo song-lyrics
npx @nestjs/cli g r song-lyrics

注意, mo module 的简写, r resolver 的简写。

接下来,需要为艺术家和歌曲歌词创建 DTO。

艺术家 DTO 文件 backend/src/artist/artist.dto.ts 内容如下:

import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
export class ArtistDto {
  @Field(() => ID)
  id: string;
  @Field()
  name: string;
}

对应的解析器文件 backend/src/artist/artist.resolver.ts 内容如下:

import { Query, Resolver, Args } from '@nestjs/graphql';
import { TYPES } from '../musixmatch/ioc/types';
import { MusicService } from '../musixmatch/services';
import { ArtistDto } from './artist.dto';
import { Inject } from '@nestjs/common';

@Resolver('Artist')
export class ArtistResolver {
  constructor(
    @Inject(TYPES.MUSIC_SERVICE)
    private readonly musicService: MusicService,
  ) {}

  @Query(() => [ArtistDto])
  async artists(@Args('name') name: string): Promise<ArtistDto[]> {
    return this.musicService.findArtists(name).toPromise();
  }
}

这里同样注入并使用 MusicService,使用了异步函数并返回 Promise。

歌曲歌词 DTO 文件 src/song-lyrics/song-lyrics.dto.ts 内容如下:

import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
export class SongLyricsDto {
  @Field(() => ID)
  id: string;
  @Field()
  copyright: string;
  @Field(() => Boolean)
  explicit: boolean;
  @Field()
  // 原文档此处未完整,可根据实际情况补充
}

以下是整个开发流程的 mermaid 流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B(创建项目):::process
    B --> C(配置前后端环境):::process
    C --> D(后端添加依赖):::process
    D --> E(创建后端模块和解析器):::process
    E --> F(配置 GraphQL 模块):::process
    F --> G(测试后端 API):::process
    G --> H(前端搭建界面和配置路由):::process
    H --> I(前端配置 API 调用):::process
    I --> J([完成]):::startend

通过以上步骤,我们逐步完成了 LyricsFinder V2 的前后端开发,从项目搭建到后端 API 实现和测试,再到前端界面和 API 调用的配置,整个过程涵盖了多个技术点和工具的使用,希望能帮助你更好地理解和实践相关开发。

重新探索歌词查找器的开发实现

6. 前端开发与后端集成
6.1 前端界面开发

前端使用 React 结合 React Bootstrap 进行界面开发。首先,确保已经安装了所需的依赖:

yarn add react-bootstrap bootstrap

index.tsx 中引入 Bootstrap 的 CSS:

import 'bootstrap/dist/css/bootstrap.min.css';

接下来可以开始创建组件,例如创建一个搜索框组件 SearchBox.tsx

import React from 'react';
import { Form, InputGroup, Button } from 'react-bootstrap';

const SearchBox: React.FC = () => {
  return (
    <InputGroup className="mb-3">
      <Form.Control
        placeholder="搜索歌曲、艺术家"
        aria-label="搜索歌曲、艺术家"
        aria-describedby="basic-addon2"
      />
      <Button variant="outline-secondary" id="button-addon2">
        搜索
      </Button>
    </InputGroup>
  );
};

export default SearchBox;

App.tsx 中使用该组件:

import React from 'react';
import SearchBox from './SearchBox';

const App: React.FC = () => {
  return (
    <div className="container">
      <h1>歌词查找器</h1>
      <SearchBox />
    </div>
  );
};

export default App;
6.2 前端配置 Apollo GraphQL 客户端

为了让前端能够与后端的 GraphQL API 进行交互,需要配置 Apollo GraphQL 客户端。安装所需依赖:

yarn add @apollo/client graphql

src 目录下创建 apolloClient.ts 文件:

import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';

const httpLink = new HttpLink({
  uri: 'http://localhost:4000/graphql',
});

const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
});

export default client;

index.tsx 中使用该客户端:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ApolloProvider } from '@apollo/client';
import client from './apolloClient';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);
6.3 前端发起 GraphQL 查询

SearchBox.tsx 中添加查询逻辑,当用户点击搜索按钮时发起查询。首先,定义查询:

import { gql } from '@apollo/client';

const SEARCH_SONGS = gql`
  query SearchSongs($value: String!) {
    songs(value: $value) {
      id
      name
      hasLyrics
    }
  }
`;

然后修改 SearchBox.tsx 组件:

import React, { useState } from 'react';
import { Form, InputGroup, Button } from 'react-bootstrap';
import { useQuery } from '@apollo/client';
import { SEARCH_SONGS } from './queries';

const SearchBox: React.FC = () => {
  const [searchValue, setSearchValue] = useState('');
  const { loading, error, data } = useQuery(SEARCH_SONGS, {
    variables: { value: searchValue },
    skip: searchValue === '',
  });

  const handleSearch = () => {
    // 这里可以添加更多逻辑,例如验证输入等
    console.log('搜索值:', searchValue);
  };

  return (
    <div>
      <InputGroup className="mb-3">
        <Form.Control
          placeholder="搜索歌曲、艺术家"
          aria-label="搜索歌曲、艺术家"
          aria-describedby="basic-addon2"
          value={searchValue}
          onChange={(e) => setSearchValue(e.target.value)}
        />
        <Button variant="outline-secondary" id="button-addon2" onClick={handleSearch}>
          搜索
        </Button>
      </InputGroup>
      {loading && <p>加载中...</p>}
      {error && <p>错误: {error.message}</p>}
      {data && (
        <ul>
          {data.songs.map((song: any) => (
            <li key={song.id}>
              {song.name} - {song.hasLyrics ? '有歌词' : '无歌词'}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default SearchBox;
7. 优化与扩展
7.1 性能优化
  • 缓存策略 :在 Apollo 客户端中,可以配置缓存策略,避免重复请求相同的数据。例如,在 apolloClient.ts 中可以对缓存进行更精细的配置:
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';

const httpLink = new HttpLink({
  uri: 'http://localhost:4000/graphql',
});

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        songs: {
          keyArgs: ['value'],
          merge(existing, incoming) {
            return incoming;
          },
        },
      },
    },
  },
});

const client = new ApolloClient({
  link: httpLink,
  cache,
});

export default client;
  • 代码分割 :使用 React.lazy 和 Suspense 进行代码分割,减少初始加载时间。例如,将一些组件进行懒加载:
const SearchBox = React.lazy(() => import('./SearchBox'));

const App: React.FC = () => {
  return (
    <div className="container">
      <h1>歌词查找器</h1>
      <React.Suspense fallback={<div>加载中...</div>}>
        <SearchBox />
      </React.Suspense>
    </div>
  );
};
7.2 功能扩展
  • 添加分页功能 :在后端的 GraphQL API 中添加分页支持,前端根据分页信息展示不同页面的数据。
  • 支持更多查询类型 :例如按艺术家、流派等进行查询,在后端添加相应的解析器和查询逻辑,前端添加对应的查询和界面元素。

以下是前端开发和优化的主要步骤表格:
| 步骤 | 操作 | 命令或代码 |
| ---- | ---- | ---- |
| 安装 React Bootstrap | 安装依赖并引入 CSS | yarn add react-bootstrap bootstrap ,在 index.tsx 引入 CSS |
| 创建搜索框组件 | 创建 SearchBox.tsx 并在 App.tsx 使用 | 见 SearchBox.tsx App.tsx 代码 |
| 配置 Apollo 客户端 | 安装依赖并创建 apolloClient.ts | yarn add @apollo/client graphql ,见 apolloClient.ts 代码 |
| 发起 GraphQL 查询 | 在 SearchBox.tsx 定义查询并添加查询逻辑 | 见 SearchBox.tsx 代码 |
| 性能优化 | 配置缓存策略、代码分割 | 见缓存配置和代码分割代码 |
| 功能扩展 | 添加分页、更多查询类型 | 后端添加解析器,前端添加查询和界面元素 |

以下是前端开发和优化的 mermaid 流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始前端开发]):::startend --> B(安装 React Bootstrap):::process
    B --> C(创建搜索框组件):::process
    C --> D(配置 Apollo 客户端):::process
    D --> E(发起 GraphQL 查询):::process
    E --> F{是否需要优化?}:::decision
    F -->|是| G(性能优化):::process
    F -->|否| H{是否需要扩展功能?}:::decision
    G --> H
    H -->|是| I(功能扩展):::process
    H -->|否| J([完成前端开发]):::startend
    I --> J

通过以上前端开发、与后端集成以及优化扩展的步骤,我们完成了一个完整的歌词查找器应用的开发。这个应用可以让用户搜索歌曲、艺术家信息,并根据需要进行性能优化和功能扩展。

复杂几何的多球近似MATLAB类及多球模型的比较 MATLAB类Approxi提供了一个框架,用于使用具有迭代缩放的聚集球体模型来近似解剖体积模型,以适应目标体积和模型比较。专为骨科、生物力学和计算几何应用而开发。 MATLAB class for multi-sphere approximation of complex geometries and comparison of multi-sphere models 主要特点: 球体模型生成 1.多球体模型生成:与Sihaeri的聚集球体算法的接口 2.音量缩放 基于体素的球体模型和参考几何体的交集。 迭代缩放球体模型以匹配目标体积。 3.模型比较:不同模型体素占用率的频率分析(多个评分指标) 4.几何分析:原始曲面模型和球体模型之间的顶点到最近邻距离映射(带颜色编码结果)。 如何使用: 1.代码结构:Approxi类可以集成到相应的主脚本中。代码的关键部分被提取到单独的函数中以供重用。 2.导入:将STL(或网格)导入MATLAB,并确保所需的函数,如DEM clusteredSphere(populateSpheres)和inpolyhedron,已添加到MATLAB路径中 3.生成多球体模型:使用DEM clusteredSphere方法从输入网格创建多球体模型 4.运行体积交点:计算多球体模型和参考几何体之间的基于体素的交点,并调整多球体模型以匹配目标体积 5.比较和可视化模型:比较多个多球体模型的体素频率,并计算多球体模型与原始表面模型之间的距离,以进行2D/3D可视化 使用案例: 骨科和生物力学体积建模 复杂结构的多球模型形状近似 基于体素拟合度量的模型选择 基于距离的患者特定几何形状和近似值分析 优点: 复杂几何的多球体模型 可扩展模型(基于体素)-自动调整到目标体积 可视化就绪输出(距离图)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值