全栈开发在线教育平台项目笔记

156 阅读5分钟

专业技能

  • 企业级全栈项目,包含三个项目,2w行代码,涵盖移动端、PC端、服务端
  • 登陆注册、图片上传、列表、表单、路由菜单、JWT、数据库设计、装饰器编写、H5屏幕自适应、长列表、项目部署发布

项目流程示意图

image.png

image.png

image.png

React18的并发渲染原理

后面再学

Nest.js基本概念

如何理解后端

后端:数据=多种来源+加工 image.png

后端处理数据的流程:

image.png

NestJS有哪些概念?

4个设计模式+多个装饰器+一堆模块

4个设计模式

  • OOP(面向对象编程)
  • AOP(面向切面编程)
  • IoC(控制反转)
  • DI(依赖注入)

什么是装饰器

它可以让其他函数或者类在不需要做任何代码修改的前提下增加额外功能。

Moudle模块

image.png

使用TypeORM定义数据表结构并完成CRUD

创建User表

src\modules\user\models\user.entity.ts

import { Column, Entity } from 'typeorm';
import { IsNotEmpty } from 'class-validator';

@Entity('user')
export class User {
    @Column({
        comment: '昵称',
        default: '',
    })
    @IsNotEmpty()
    name: string;
    @Column({
        comment: '描述信息',
        default: '',
    })
    desc: string;
    @Column({
        comment: '手机号',
        nullable: true,
    })
    tel: string;
    @Column({
        comment: '密码',
        nullable: true,
    })
    password: string;
    @Column({
        comment: '账户信息',
        nullable: true,
    })
    account: string;
}

src\app.module.ts

/* eslint-disable prettier/prettier */
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    TypeOrmModule.forRoot(
      {
        type: 'mysql',
        host: 'localhost',
        port: 3306,
        username: 'root',
        password: 'LanQing@0817',
        database: 'water-drop',
        entities: [`${__dirname}/../modules/**/*.entity.{ts,js}`],
        logging: true,
        synchronize: true,
        autoLoadEntities: true
      }
    )
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

然后在MySQL workBench新建一个water-drop数据库,再启动项目。发现water-drop数据库依然没有创建user表,得新增一个User模块 src\modules\user\user.module.ts

import { TypeOrmModule } from '@nestjs/typeorm';
import { Module, ConsoleLogger } from '@nestjs/common';

import { User } from './models/user.entity'

@Module({
    imports: [TypeOrmModule.forFeature([User])],
    providers: [ConsoleLogger],
    exports: []
})
export class UserModule { }

image.png

项目跑起来还是报错了,得给user表加一个id

src\modules\user\models\user.entity.ts

/* eslint-disable prettier/prettier */
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { IsNotEmpty } from 'class-validator';

@Entity('user')
export class User {
    @PrimaryGeneratedColumn('uuid')
    id: string;
    @Column({
        comment: '昵称',
        default: '',
    })
    @IsNotEmpty()
    name: string;
    @Column({
        comment: '描述信息',
        default: '',
    })
    desc: string;
    @Column({
        comment: '手机号',
        nullable: true,
    })
    tel: string;
    @Column({
        comment: '密码',
        nullable: true,
    })
    password: string;
    @Column({
        comment: '账户信息',
        nullable: true,
    })
    account: string;
}

这样再跑起来项目,终于能新增User表了!

image.png

对User表进行增删改查的操作

创建这个表后,需要对这个User表进行增删改查的操作,首先我们需要创建一个Services。

image.png

src\modules\user\user.service.ts

/* eslint-disable prettier/prettier */
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { User } from './models/user.entity'
import { DeepPartial, Repository } from "typeorm";

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User) private UserRepository: Repository<User>
    ) { }
    // 新增一个用户
    async create(entity: DeepPartial<User>): Promise<boolean> {
        const res = await this.UserRepository.insert(entity)
        console.log('res', res)
        if (res && res.raw.affectedRows > 0) {
            return true
        }
        return false
    }
}

src\modules\user\user.module.ts

/* eslint-disable prettier/prettier */
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module, ConsoleLogger } from '@nestjs/common';
import { UserService } from './user.service';
import { User } from './models/user.entity'

@Module({
    imports: [TypeOrmModule.forFeature([User])],
    providers: [ConsoleLogger, UserService],
    exports: [UserService]
})
export class UserModule { }

src\app.controller.ts

/* eslint-disable prettier/prettier */
import { Controller, Get } from '@nestjs/common';
import { UserService } from './modules/user/user.service';

@Controller()
export class AppController {
  constructor(private readonly userService: UserService) { }
  @Get('/create')
  async create(): Promise<boolean> {
    return await this.userService.create({
      name: '水滴超级管理员',
      desc: '管理员',
      tel: '8800888',
      password: '123456',
      account: 'admin'
    })
  }
}

在浏览器访问地址:http://localhost:3000/create 调用接口

这样就能新增用户数据了

image.png

仿照这个新增用户功能,再增加删除、更新、查找功能 src\modules\user\user.service.ts

/* eslint-disable prettier/prettier */
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { User } from './models/user.entity'
import { DeepPartial, Repository } from "typeorm";

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User) private UserRepository: Repository<User>
    ) { }
    // 新增一个用户
    async create(entity: DeepPartial<User>): Promise<boolean> {
        const res = await this.UserRepository.insert(entity)
        if (res && res.raw.affectedRows > 0) {
            return true
        }
        return false
    }
    // 删除一个用户
    async del(id: string): Promise<boolean> {
        const res = await this.UserRepository.delete(id)
        if (res && res.affected > 0) {
            return true
        }
        return false
    }
    // 更新一个用户
    async update(id: string, entity: DeepPartial<User>): Promise<boolean> {
        const res = await this.UserRepository.update(id, entity)
        if (res && res.affected > 0) {
            return true
        }
        return false
    }
    // 查询一个用户
    async find(id: string): Promise<User> {
        const res = await this.UserRepository.findOne({
            where: { id }
        })
        return res
    }
}

src\app.controller.ts

/* eslint-disable prettier/prettier */
import { Controller, Get } from '@nestjs/common';
import { UserService } from './modules/user/user.service';
import { User } from './modules/user/models/user.entity'
@Controller()
export class AppController {
  constructor(private readonly userService: UserService) { }
  @Get('/create')
  async create(): Promise<boolean> {
    return await this.userService.create({
      name: '水滴超级管理员',
      desc: '管理员',
      tel: '8800888',
      password: '123456',
      account: 'admin'
    })
  }
  @Get('/del')
  async del(): Promise<boolean> {
    return await this.userService.del('1c9689a7-7e52-4d74-973e-91ee87986f75')
  }
  @Get('/update')
  async update(): Promise<boolean> {
    return await this.userService.update('2ca4547c-5988-4ee5-bc7a-cd7f25628e79', {
      name: '兰青',
      desc: '管理员',
      tel: '8800888',
      password: '123456',
      account: 'admin'
    })
  }
  @Get('/find')
  async find(): Promise<User> {
    return await this.userService.find('2ca4547c-5988-4ee5-bc7a-cd7f25628e79')
  }
}

GraphQL

什么是GraphQL

GraphQL是一种用于应用编程接口(API)的查询语言和服务器端运行时,它可以使客户端准确地获得所需的数据,没有任何冗余。

  • GraphQL没有数据冗余
  • GraphQL可以合并接口
  • GrapQL可以做类型校验
  • GrapQL有强大的接口调试工具
  • GrapQL可以自动生成接口文档
  • Nest可以与GrapQL完美结合

使用GraphQL创建API

步骤:

  1. 安装包
  2. 注册module
  3. 创建resolver,定义API
  4. 使用工具调试

第一步:首先需要安装4个包:

  1. graphql:基础包
  2. apollo-server-express:启动graphQL server
  3. @nestjs/graphql:工具包,包括所有grapgQL的所有功能
  4. @nestjs/apollo:graphQL的驱动程序

第二步:注册GraphQLModule

image.png

Driver是什么

首先要知道一个GraphQL API的组成 image.png

为了实现一个地址实现多个API,就需要这个Driver image.png

第三步:创建Resolver,定义API

image.png

结合项目实操

第一步:安装四个包 npm i @nestjs/graphql @nestjs/apollo graphql apollo-server-express

第二步:注册GraphQLModule

app.module.ts

/* eslint-disable prettier/prettier */
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './modules/user/user.module';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver } from '@nestjs/apollo';

@Module({
  imports: [
    TypeOrmModule.forRoot(
      {
        type: 'mysql',
        host: 'localhost',
        port: 3306,
        username: 'root',
        password: 'LanQing@0817',
        database: 'water-drop',
        entities: [`${__dirname}/../modules/**/*.entity.{ts,js}`],
        logging: true,
        synchronize: true,
        autoLoadEntities: true
      }
    ),
    GraphQLModule.forRoot({
      driver: ApolloDriver,
      autoSchemaFile: true
    }),
    UserModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

第三步:创建Resolver,定义API

src\modules\user\user.resolver.ts

/* eslint-disable prettier/prettier */
import { Mutation, Resolver, Args, Query } from '@nestjs/graphql';
import { UserService } from './user.service';
import { UserInput } from './dto/user-input.type';
import { UserType } from './dto/user.type';

@Resolver()
export class UserResolver {
    constructor(private readonly userService: UserService) { }
    @Mutation(() => Boolean)
    async create(@Args('params') params: UserInput): Promise<boolean> {
        return await this.userService.create(params)
    }

    @Query(() => UserType)
    async find(@Args('id') id: string): Promise<UserType> {
        return await this.userService.find(id)
    }
}

src\modules\user\dto\user-input.type.ts

/* eslint-disable prettier/prettier */

import { Field, InputType } from "@nestjs/graphql";

@InputType()
export class UserInput {
    @Field()
    name: string;
    @Field()
    desc: string;
}

src\modules\user\dto\user.type.ts

/* eslint-disable prettier/prettier */

import { Field, ObjectType } from "@nestjs/graphql";

@ObjectType()
export class UserType {
    @Field()
    id?: string;
    @Field()
    name?: string;
    @Field()
    desc: string;
    @Field()
    account: string;
}

src\modules\user\user.module.ts 引入Resolver

/* eslint-disable prettier/prettier */
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module, ConsoleLogger } from '@nestjs/common';
import { UserService } from './user.service';
import { User } from './models/user.entity'
import { UserResolver } from './user.resolver';

@Module({
    imports: [TypeOrmModule.forFeature([User])],
    providers: [ConsoleLogger, UserService, UserResolver],
    exports: [UserService]
})
export class UserModule { }

至此编码就已经完成了,可以使用调试工具调试,作用类似于postMan了,浏览器访问:http://localhost:8320/graphQL

image.png

传参可以查询到数据:

image.png

再新增更新、删除接口 src\modules\user\user.resolver.ts

/* eslint-disable prettier/prettier */
import { Mutation, Resolver, Args, Query } from '@nestjs/graphql';
import { UserService } from './user.service';
import { UserInput } from './dto/user-input.type';
import { UserType } from './dto/user.type';

@Resolver()
export class UserResolver {
    constructor(private readonly userService: UserService) { }
    @Mutation(() => Boolean, { description: '新增用户' })
    async create(@Args('params') params: UserInput): Promise<boolean> {
        return await this.userService.create(params)
    }
    @Query(() => UserType, { description: '使用 ID 查询用户' })
    async find(@Args('id') id: string): Promise<UserType> {
        return await this.userService.find(id)
    }
    @Mutation(() => Boolean, { description: '更新用户' })
    async update(@Args('id') id: string, @Args('params') params: UserInput): Promise<boolean> {
        return await this.userService.update(id, params)
    }
    @Mutation(() => Boolean, { description: '删除一个用户' })
    async del(@Args('id') id: string): Promise<boolean> {
        return await this.userService.del(id)
    }
}

脚手架与使用Vite初始化前端项目

脚手架具备三个能力:初始化项目、打包代码文件、启动web server

water-drop-mobile项目封装一些React Hooks

好用的hooks库:ahooks ahooks.js.org/hooks/use-h…

\water-drop-mobile\src\hooks直接看git仓库吧

使用Apollo创建GraphQL Service(移动端)

image.png

1.初始化ApolloClient

src\utils\apollo.ts

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

export const client = new ApolloClient({
    uri: "http://localhost:8320/graphql",
    cache: new InMemoryCache(),
});

2.包裹ApolloProvider

src\main.tsx

import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { ApolloProvider } from '@apollo/client'
import { client } from './utils/apollo.ts'

createRoot(document.getElementById('root')!).render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
)

3.定义 gql Schema

src\graphql\demo.ts

import { gql } from '@apollo/client'

export const FIND = gql`
query find($id:String!){
	find(id:$id){
        name
        desc
		id
    }
}
`

export const UPDATE = gql`
mutation update($id:String!,$params:UserInput!){
  update(id:$id,params:$params)
} 
`

4.使用useQuery、useMutation

APP.tsx做一个简单的小demo

import { useState } from 'react'
import { FIND, UPDATE } from './graphql/demo'
import { useMutation, useQuery } from '@apollo/client'
import './App.css'
const App = () => {
  const [name, setName] = useState('')
  const [desc, setDesc] = useState('')

  const { loading, data } = useQuery(FIND, {
    variables: {
      id: '6b8d5449-b0e6-43ec-8aeb-b741851a5ce6'
    }
  })

  const [update] = useMutation(UPDATE)

  const onChangeNameHandler = (v: React.ChangeEvent<HTMLInputElement>) => {

    setName(v.target.value)
  }

  const onChangeDescHandler = (v: React.ChangeEvent<HTMLInputElement>) => {
    setDesc(v.target.value)
  }

  const onClickHandler = () => {
    update({
      variables: {
        "id": "6b8d5449-b0e6-43ec-8aeb-b741851a5ce6",
        "params": {
          "name": name,
          "desc": desc
        }
      }
    })
  }

  return (
    <div>
      <p>
        data:{JSON.stringify(data)}
      </p>
      <p>
        loading:{`${loading}`}
      </p>
      <p>
        name:
        <input onChange={onChangeNameHandler} />
      </p>
      <p>
        desc:
        <input onChange={onChangeDescHandler} />
      </p>
      <p>
        <button type="button" onClick={onClickHandler}>更新</button>
      </p>
    </div>
  )
}

export default App

使用 Apollo-server 快速创建 mock 数据

GraphQL的Mock如何做:

  • 一个Apollo Server
  • 一个接口服务
  • 类型数据随机
  • 特殊字段随机 image.png

image.png

water-drop-mobile

mock/index.js

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { addMocksToSchema } from '@graphql-tools/mock';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { faker } from '@faker-js/faker/locale/zh_CN';

const typeDefs = `#graphql
  type UserType {
  id: String!
  name: String!
  desc: String!

  """账户信息"""
  account: String!
}

type Query {
  """使用 ID 查询用户"""
  find(id: String!): UserType!
}

type Mutation {
  """新增用户"""
  create(params: UserInput!): Boolean!

  """更新用户"""
  update(id: String!, params: UserInput!): Boolean!

  """删除一个用户"""
  del(id: String!): Boolean!
}

input UserInput {
  name: String!
  desc: String!
}
`;

const resolvers = {
    UserType: {
        name: () => faker.name.lastName() + faker.name.firstName(),
    },
};

// highlight-start
const mocks = {
    Int: () => 6,
    Float: () => 22.1,
    String: () => 'hello',
};
// highlight-end

const server = new ApolloServer({
    schema: addMocksToSchema({
        schema: makeExecutableSchema({ typeDefs, resolvers }),
        mocks,
        preserveResolvers: true,
    }),
});

const { url } = await startStandaloneServer(server, { listen: { port: 8888 } });

console.log(`🚀 Server listening at: ${url}`);

src\utils\apollo.ts

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

export const client = new ApolloClient({
    uri: "http://localhost:8888/graphql",
    cache: new InMemoryCache(),
});

drop-server

src\app.module.ts image.png

/* eslint-disable prettier/prettier */
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './modules/user/user.module';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver } from '@nestjs/apollo';

@Module({
  imports: [
    TypeOrmModule.forRoot(
      {
        type: 'mysql',
        host: 'localhost',
        port: 3306,
        username: 'root',
        password: 'LanQing@0817',
        database: 'water-drop',
        entities: [`${__dirname}/../modules/**/*.entity.{ts,js}`],
        logging: true,
        synchronize: true,
        autoLoadEntities: true
      }
    ),
    GraphQLModule.forRoot({
      driver: ApolloDriver,
      autoSchemaFile: './schema.gql'
    }),
    UserModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

再重新启动服务,就会在项目根目录下生成一个schema.gql

# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

type UserType {
  id: String!
  name: String!
  desc: String!

  """账户信息"""
  account: String!
}

type Query {
  """使用 ID 查询用户"""
  find(id: String!): UserType!
}

type Mutation {
  """新增用户"""
  create(params: UserInput!): Boolean!

  """更新用户"""
  update(id: String!, params: UserInput!): Boolean!

  """删除一个用户"""
  del(id: String!): Boolean!
}

input UserInput {
  name: String!
  desc: String!
}

将图片上传到阿里云OSS

在正式开发项目之前我们需要解决一个问题

一般业务的图片处理方式

一般业务中的图片处理方式:

  • 图片放在代码里
  • 图片直接传到CDN上(图床)

OSS与CDN服务

OSS:对象存储(存储)

CDN:内容分发网络(加速)

image.png

国内的OSS服务有哪些

阿里云、腾讯云、百度云、七牛云

如何拥有阿里云OSS服务

image.png

image.png

image.png

image.png

两种API上传的方式

  • 服务端转发(比较保险,不用主要是因为图片经过服务端也需要收费,费用会加倍,性能也不一定好,)
  • Web直传(服务端生成一个签名,把签名传递给浏览器端,浏览器端直接传给OSS。所以选择Web直传的方式)

面试重点:什么是CSS Modules

为什么要用CSS Modules

  • 样式隔离
  • 与less结合
  • 良好的开发体验

css Modules最佳实践

image.png

image.png

image.png