使用Nest + uni实现微信小程序登录授权【一人搞定前后端】

2,140 阅读2分钟

前期准备:申请小程序并获得appid、secret

一、通过小程序获取code和iv以及encryptedData

以下为小程序代码

// template
<template>
<button class="confirm" @tap="actionHandle">登录</button>
</template>

//js
<script setup lang="ts">
import { reactive } from 'vue';

const state = reactive({
	code: ''
})
async function actionHandle(e: any){
	uni.showLoading({
		title: '正在登录中',
		mask: true
	})
	await login() // 一定要先获取code,并且code只能使用一次,一次后需要重新生成
	uni.getUserProfile({
		desc: 'Wexin', // 这个参数是必须的
		success: res => {
		const params = {
		    iv: res.iv,
		    encryptedData: res.encryptedData,
		    code: state.code
		}
		try{
            uni.request({
                url: 'http://localhost:3000/user/login',
                data: params,
                method: 'POST'
            })
		}catch(e){
			console.log(e)
		}
		}
	})
}

function login() {
	uni.login().then(res => {
		state.code = res.code
	})
}
</script>

nest使用nest g resource user生成user模块,并连接数据库【nest简易上手教程

二、login接口接收数据并解密用户数据

建立一张简陋的user表

// src/User/entities

import {
    Column,
    CreateDateColumn,
    Entity,
    PrimaryGeneratedColumn,
    UpdateDateColumn,
  } from 'typeorm';
  
  @Entity()
  export class User {
    @PrimaryGeneratedColumn()
    id: number;
  
    @CreateDateColumn()
    createTime: Date;
  
    @UpdateDateColumn()
    updateTime: Date;
    
    @Column('text')
    openid: string

    @Column({
      default: false,
    })
    isDelete: false;
  
    @Column('text')
    nickname: string;

    @Column('text')
    avatar: string;

    @Column()
    gender: number;
  
    @Column('text', { default: null })
    mobile: string;

    @Column('text')
    appid: string

    @Column('text', { default: null })
    token: string;
  }
  
// src/User/dto/login.dto.ts
import { IsNotEmpty } from 'class-validator';

export class LoginDTO {
  readonly iv: string;

  readonly encryptedData: string;

  @IsNotEmpty({ message: 'code不能为空' })
  readonly code: string;
}

// src/User/user.controller.ts
@Post('/login')
  login(@Body() loginDTO: LoginDTO) {
    return this.userService.login(loginDTO);
  }

建完保存就会发现数据库中自动生成了一张user表

三、获取session_key,生成token,加入jwt

yarn add @nestjs/passport passport passport-local @nestjs/jwt passport-jwt

yarn add -D @types/passport-local @types/passport-jwt

// src/customer/user.module.ts
import { Module } from '@nestjs/common';
import {TypeOrmModule} from '@nestjs/typeorm'
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User } from './entities/user.entity';
import {HttpModule} from '@nestjs/axios'
import { JwtModule } from '@nestjs/jwt'

@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
  HttpModule,
  JwtModule.register({
    secret: 'dsdgfgjhjk', // 密钥
    signOptions: {expiresIn: '8h'} // 过期时间
  })
],
  controllers: [UserController],
  providers: [UserService]
})
export class UserModule {}

//src/customer/customer.controller.ts

@ApiOkResponse({ description: '登陆', type: TokenResponse })
  @Post('/login')
  login(@Body() loginDTO: LoginDTO) {
    return this.customerService.login(loginDTO);
  }

接下来是写逻辑的地方

// src/customer/user.service.ts
import { Injectable, HttpException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { HttpService } from '@nestjs/axios';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { LoginDTO } from './dto/login.dto';
import {AxiosResponse} from 'axios'
import { map } from 'rxjs/operators';
import {WXBizDataCrypt} from './WXBizDataCrypt'
import { User } from './entities/user.entity';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
    private readonly httpService: HttpService,
    private readonly jwtService: JwtService
    ){}
  private appid = '你的appid'
  private secret = '微信公众平台获取的secret'
  private grant_type = 'authorization_code'

  async login(loginDTO: LoginDTO): Promise<any>{
    const {code, iv, encryptedData} = loginDTO
    const url = `https://api.weixin.qq.com/sns/jscode2session?grant_type=${this.grant_type}&appid=${this.appid}&secret=${this.secret}&js_code=${code}`
    const info = await this.getInfo(url) // 获取openid和session_key
    let token = ''
        // 如果openid不存在则为新用户
        const hasUser = await this.userRepository.findOne({ where: { openid: info.data.openid } });
          if (hasUser) {
          // 直接取用户token
         token = hasUser.token
        } else {
          // 注册插入一条新信息
          const pc = new WXBizDataCrypt(this.appid, info.data?.session_key)
          const data = pc.decryptData(encryptedData, iv)
          const newUser: User = new User();
          newUser.openid = info.data.openid
          newUser.nickname = data.nickName
          newUser.appid = this.appid
          newUser.avatar = data.avatarUrl
          newUser.gender = data.gender
          token = await this.certificate(newUser)
          newUser.token = token
          await this.userRepository.save(newUser);
        }
        return {
            token
        }
  }
    // 生成 token
    async certificate(user: User) {
      const payload = { 
        openid: user.openid,
        nickname: user.nickname,
      };
      const token = this.jwtService.sign(payload);
      return token
    }
    
  getInfo(url): Promise<AxiosResponse> {
    return this.httpService.post(url).pipe(map(response => response)).toPromise()
  }

小程序调用login接口获得token存在本地,或者用postman试一下

WX20220708-093536.png