From c4b1de03f6feb5164238cfe3589a09c4b61485f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=97=A0=E9=A3=8E?= Date: Fri, 14 Apr 2023 18:16:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/user/config.ts | 9 + src/modules/user/controller/admin/info.ts | 11 ++ src/modules/user/controller/app/login.ts | 50 ++++++ src/modules/user/entity/info.ts | 15 +- src/modules/user/entity/wx.ts | 18 +- src/modules/user/service/login.ts | 192 ++++++++++++++++++++++ src/modules/user/service/sms.ts | 14 ++ 7 files changed, 298 insertions(+), 11 deletions(-) create mode 100644 src/modules/user/controller/admin/info.ts create mode 100644 src/modules/user/controller/app/login.ts create mode 100644 src/modules/user/service/login.ts diff --git a/src/modules/user/config.ts b/src/modules/user/config.ts index 5a2cad1..66bb78d 100644 --- a/src/modules/user/config.ts +++ b/src/modules/user/config.ts @@ -36,5 +36,14 @@ export default () => { secret: 'xxx', }, }, + // jwt + jwt: { + // token 过期时间,单位秒 + expire: 60 * 60 * 2, + // 刷新token 过期时间,单位秒 + refreshExpire: 60 * 60 * 24 * 30, + // jwt 秘钥 + secret: '093243e6ce8', + }, } as ModuleConfig; }; diff --git a/src/modules/user/controller/admin/info.ts b/src/modules/user/controller/admin/info.ts new file mode 100644 index 0000000..b83da4b --- /dev/null +++ b/src/modules/user/controller/admin/info.ts @@ -0,0 +1,11 @@ +import { CoolController, BaseController } from '@cool-midway/core'; +import { UserInfoEntity } from '../../entity/info'; + +/** + * 用户信息 + */ +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: UserInfoEntity, +}) +export class AdminUserInfoController extends BaseController {} diff --git a/src/modules/user/controller/app/login.ts b/src/modules/user/controller/app/login.ts new file mode 100644 index 0000000..1e36cc8 --- /dev/null +++ b/src/modules/user/controller/app/login.ts @@ -0,0 +1,50 @@ +import { CoolController, BaseController } from '@cool-midway/core'; +import { Body, Get, Inject, Post, Query } from '@midwayjs/core'; +import { UserLoginService } from '../../service/login'; +import { BaseSysLoginService } from '../../../base/service/sys/login'; + +/** + * 登录 + */ +@CoolController() +export class AppUserLoginController extends BaseController { + @Inject() + userLoginService: UserLoginService; + + @Inject() + baseSysLoginService: BaseSysLoginService; + + @Post('/mini', { summary: '小程序登录' }) + async miniLogin(@Body() body) { + const { code, encryptedData, iv } = body; + return this.ok(await this.userLoginService.mini(code, encryptedData, iv)); + } + + @Post('/mp', { summary: '公众号登录' }) + async mp(@Body('code') code: string) { + return this.ok(await this.userLoginService.mp(code)); + } + + @Post('/phone', { summary: '手机号登录' }) + async phone(@Body('phone') phone: string, @Body('smsCode') smsCode: string) { + return this.ok(await this.userLoginService.phone(phone, smsCode)); + } + + @Get('/captcha', { summary: '图片验证码' }) + async captcha( + @Query('type') type: string, + @Query('width') width: number, + @Query('height') height: number + ) { + return this.ok(await this.baseSysLoginService.captcha(type, width, height)); + } + + @Post('/smsCode', { summary: '验证码' }) + async smsCode( + @Body('phone') phone: string, + @Body('captchaId') captchaId: string, + @Body('code') code: string + ) { + return this.ok(); + } +} diff --git a/src/modules/user/entity/info.ts b/src/modules/user/entity/info.ts index 24ee5ba..44048f0 100644 --- a/src/modules/user/entity/info.ts +++ b/src/modules/user/entity/info.ts @@ -6,23 +6,26 @@ import { Column, Entity, Index } from 'typeorm'; */ @Entity('user_info') export class UserInfoEntity extends BaseEntity { - @Index() - @Column({ comment: '第三方登录的唯一ID,如:微信、QQ等' }) + @Index({ unique: true }) + @Column({ comment: '登录唯一ID', nullable: true }) unionid: string; - @Column({ comment: '头像' }) + @Column({ comment: '头像', nullable: true }) avatarUrl: string; - @Column({ comment: '昵称' }) + @Column({ comment: '昵称', nullable: true }) nickName: string; @Index({ unique: true }) - @Column({ comment: '手机号' }) + @Column({ comment: '手机号', nullable: true }) phone: string; @Column({ comment: '性别 0-未知 1-男 2-女', default: 0 }) gender: number; - @Column({ comment: '状态 0-正常 1-禁用', default: 0 }) + @Column({ comment: '状态 0-禁用 1-正常', default: 1 }) status: number; + + @Column({ comment: '登录方式 0-小程序 1-公众号 2-H5', default: 0 }) + loginType: number; } diff --git a/src/modules/user/entity/wx.ts b/src/modules/user/entity/wx.ts index 925aad0..888e830 100644 --- a/src/modules/user/entity/wx.ts +++ b/src/modules/user/entity/wx.ts @@ -1,11 +1,19 @@ import { BaseEntity } from '@cool-midway/core'; -import { Column, Entity } from 'typeorm'; +import { Column, Entity, Index } from 'typeorm'; /** * 微信用户 */ @Entity('user_wx') export class UserWxEntity extends BaseEntity { + @Index() + @Column({ comment: '微信unionid', nullable: true }) + unionid: string; + + @Index() + @Column({ comment: '微信openid' }) + openid: string; + @Column({ comment: '头像', nullable: true }) avatarUrl: number; @@ -15,15 +23,15 @@ export class UserWxEntity extends BaseEntity { @Column({ comment: '性别 0-未知 1-男 2-女', default: 0 }) gender: number; - @Column({ comment: '语言' }) + @Column({ comment: '语言', nullable: true }) language: number; - @Column({ comment: '城市' }) + @Column({ comment: '城市', nullable: true }) city: number; - @Column({ comment: '省份' }) + @Column({ comment: '省份', nullable: true }) province: number; - @Column({ comment: '国家' }) + @Column({ comment: '国家', nullable: true }) country: number; } diff --git a/src/modules/user/service/login.ts b/src/modules/user/service/login.ts new file mode 100644 index 0000000..67cddf1 --- /dev/null +++ b/src/modules/user/service/login.ts @@ -0,0 +1,192 @@ +import { Config, Inject, Provide } from '@midwayjs/decorator'; +import { BaseService, CoolCommException } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import { UserInfoEntity } from '../entity/info'; +import { UserWxService } from './wx'; +import * as jwt from 'jsonwebtoken'; +import { UserWxEntity } from '../entity/wx'; +import { CoolFile } from '@cool-midway/file'; +import { BaseSysLoginService } from '../../base/service/sys/login'; +import { UserSmsService } from './sms'; + +/** + * 登录 + */ +@Provide() +export class UserLoginService extends BaseService { + @InjectEntityModel(UserInfoEntity) + userInfoEntity: Repository; + + @InjectEntityModel(UserWxEntity) + userWxEntity: Repository; + + @Inject() + userWxService: UserWxService; + + @Config('module.user.jwt') + jwtConfig; + + @Inject() + baseSysLoginService: BaseSysLoginService; + + @Inject() + file: CoolFile; + + @Inject() + userSmsService: UserSmsService; + + /** + * 发送手机验证码 + * @param phone + * @param captchaId + * @param code + */ + async smsCode(phone, captchaId, code) { + // 1、检查图片验证码 2、发送短信验证码 + const check = await this.baseSysLoginService.captchaCheck(captchaId, code); + if (!check) { + throw new CoolCommException('图片验证码错误'); + } + this.userSmsService.sendSms(phone); + } + + /** + * 手机登录 + * @param phone + * @param smsCode + */ + async phone(phone, smsCode) { + // 1、检查短信验证码 2、登录 + const check = await this.userSmsService.checkCode(phone, smsCode); + if (check) { + let user: any = await this.userInfoEntity.findOneBy({ phone }); + if (!user) { + user = { phone, unionid: phone, loginType: 2 }; + await this.userInfoEntity.insert(user); + } + return this.token({ userId: user.id }); + } + } + + /** + * 公众号登录 + * @param code + */ + async mp(code: string) { + let wxUserInfo = await this.userWxService.mpUserInfo(code); + if (wxUserInfo) { + delete wxUserInfo.privilege; + wxUserInfo = await this.saveWxInfo({ + openid: wxUserInfo.openid, + unionid: wxUserInfo.unionid, + avatarUrl: wxUserInfo.headimgurl, + nickName: wxUserInfo.nickname, + gender: wxUserInfo.sex, + city: wxUserInfo.city, + province: wxUserInfo.province, + country: wxUserInfo.country, + }); + return this.wxLoginToken(wxUserInfo); + } else { + throw new Error('微信登录失败'); + } + } + + /** + * 保存微信信息 + * @param wxUserInfo + * @returns + */ + async saveWxInfo(wxUserInfo) { + const find: any = {}; + if (wxUserInfo.unionid) { + find.unionid = wxUserInfo.unionid; + } + if (wxUserInfo.openid) { + find.openid = wxUserInfo.openid; + } + let wxInfo: any = await this.userWxEntity.findOneBy(find); + if (wxInfo) { + delete wxUserInfo.avatarUrl; + wxUserInfo.id = wxInfo.id; + } else { + // 微信的链接会失效,需要保存到本地 + wxUserInfo.avatarUrl = await this.file.downAndUpload( + wxUserInfo.avatarUrl + ); + } + await this.userWxEntity.save(wxUserInfo); + return wxUserInfo; + } + + /** + * 小程序登录 + * @param code + * @param encryptedData + * @param iv + */ + async mini(code, encryptedData, iv) { + let wxUserInfo = await this.userWxService.miniUserInfo( + code, + encryptedData, + iv + ); + if (wxUserInfo) { + // 保存 + wxUserInfo = await this.saveWxInfo(wxUserInfo); + return this.wxLoginToken(wxUserInfo); + } + } + + /** + * 微信登录 获得token + * @param wxUserInfo 微信用户信息 + * @returns + */ + async wxLoginToken(wxUserInfo) { + const unionid = wxUserInfo.unionid ? wxUserInfo.unionid : wxUserInfo.openid; + let userInfo: any = await this.userInfoEntity.findOneBy({ unionid }); + if (!userInfo) { + userInfo = { + unionid, + nickName: wxUserInfo.nickName, + avatarUrl: wxUserInfo.avatarUrl, + gender: wxUserInfo.gender, + }; + await this.userInfoEntity.insert(userInfo); + return this.token({ userId: userInfo.id }); + } + } + + /** + * 获得token + * @param info + * @returns + */ + async token(info) { + const { expire, refreshExpire } = this.jwtConfig; + return { + expire, + token: await this.generateToken(info), + refreshExpire, + refreshToken: await this.generateToken(info, true), + }; + } + + /** + * 生成token + * @param tokenInfo 信息 + * @param roleIds 角色集合 + */ + async generateToken(info, isRefresh = false) { + const { expire, refreshExpire, secret } = this.jwtConfig; + const tokenInfo = { + isRefresh, + ...info, + }; + return jwt.sign(tokenInfo, secret, { + expiresIn: isRefresh ? refreshExpire : expire, + }); + } +} diff --git a/src/modules/user/service/sms.ts b/src/modules/user/service/sms.ts index 96de380..cdc435b 100644 --- a/src/modules/user/service/sms.ts +++ b/src/modules/user/service/sms.ts @@ -30,6 +30,20 @@ export class UserSmsService extends BaseService { ); } + /** + * 验证验证码 + * @param phone + * @param code + * @returns + */ + async checkCode(phone, code) { + const cacheCode = await this.cacheManager.get(`sms:${phone}`); + if (cacheCode === code) { + return true; + } + return false; + } + /** * 发送短信 * @param phone