Compare commits
4 Commits
24cb8e9843
...
fac8be4dd0
Author | SHA1 | Date | |
---|---|---|---|
fac8be4dd0 | |||
de01ffd7c7 | |||
91b5cce39b | |||
ef6380395c |
@ -1,6 +1,7 @@
|
|||||||
import { RedisOptions } from '@/modules/core/types';
|
import { RedisOptions } from '@/modules/core/types';
|
||||||
|
|
||||||
export const redis: () => RedisOptions = () => ({
|
export const redis: () => RedisOptions = () => ({
|
||||||
host: '127.0.0.1',
|
host: '192.168.50.137',
|
||||||
port: 6379,
|
port: 6379,
|
||||||
|
password: '123456&Qw',
|
||||||
});
|
});
|
||||||
|
@ -2,9 +2,9 @@ import { Configure } from '@/modules/config/configure';
|
|||||||
import { SmsOptions } from '@/modules/core/types';
|
import { SmsOptions } from '@/modules/core/types';
|
||||||
|
|
||||||
export const sms: (configure: Configure) => SmsOptions = (configure) => ({
|
export const sms: (configure: Configure) => SmsOptions = (configure) => ({
|
||||||
sign: configure.env.get('SMS_CLOUD_SING', '极客科技'),
|
sign: configure.env.get('SMS_CLOUD_SING', 'liuyi'),
|
||||||
region: configure.env.get('SMS_CLOUD_REGION', 'ap-guangzhou'),
|
region: configure.env.get('SMS_CLOUD_REGION', 'ap-guangzhou'),
|
||||||
appid: configure.env.get('SMS_CLOUD_APPID', '1400437232'),
|
appid: configure.env.get('SMS_CLOUD_APPID', 'app-id'),
|
||||||
secretId: configure.env.get('SMS_CLOUD_ID', 'your-secret-id'),
|
secretId: configure.env.get('SMS_CLOUD_ID', 'your-secret-id'),
|
||||||
secretKey: configure.env.get('SMS_CLOUD_KEY', 'your-secret-key'),
|
secretKey: configure.env.get('SMS_CLOUD_KEY', 'your-secret-key'),
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
import { createUserConfig } from '@/modules/user/config';
|
import { createUserConfig } from '@/modules/user/config';
|
||||||
|
|
||||||
export const user = createUserConfig(() => ({}));
|
export const user = createUserConfig(() => ({
|
||||||
|
hash: 10,
|
||||||
|
}));
|
||||||
|
@ -4,7 +4,13 @@ import { DynamicModule, Module, ModuleMetadata } from '@nestjs/common';
|
|||||||
import { isArray, isNil, omit } from 'lodash';
|
import { isArray, isNil, omit } from 'lodash';
|
||||||
|
|
||||||
import { RedisService, SmsService, SmtpService } from '@/modules/core/services';
|
import { RedisService, SmsService, SmtpService } from '@/modules/core/services';
|
||||||
import { QueueOptions, RedisOptions, SmsOptions, SmtpOptions } from '@/modules/core/types';
|
import type {
|
||||||
|
QueueOptions,
|
||||||
|
RedisOption,
|
||||||
|
RedisOptions,
|
||||||
|
SmsOptions,
|
||||||
|
SmtpOptions,
|
||||||
|
} from '@/modules/core/types';
|
||||||
|
|
||||||
import { createQueueOptions, createRedisOptions } from '@/options';
|
import { createQueueOptions, createRedisOptions } from '@/options';
|
||||||
|
|
||||||
@ -17,7 +23,9 @@ export class CoreModule {
|
|||||||
const providers: ModuleMetadata['providers'] = [];
|
const providers: ModuleMetadata['providers'] = [];
|
||||||
const exports: ModuleMetadata['exports'] = [];
|
const exports: ModuleMetadata['exports'] = [];
|
||||||
let imports: ModuleMetadata['imports'] = [];
|
let imports: ModuleMetadata['imports'] = [];
|
||||||
const redis = createRedisOptions(await configure.get<RedisOptions>('redis'));
|
const redis: RedisOption[] | undefined = createRedisOptions(
|
||||||
|
await configure.get<RedisOptions>('redis'),
|
||||||
|
);
|
||||||
if (!isNil(redis)) {
|
if (!isNil(redis)) {
|
||||||
providers.push({
|
providers.push({
|
||||||
provide: RedisService,
|
provide: RedisService,
|
||||||
|
@ -151,7 +151,7 @@ export interface DynamicRelation {
|
|||||||
/**
|
/**
|
||||||
* 嵌套对象
|
* 嵌套对象
|
||||||
*/
|
*/
|
||||||
export type NestedRecord = Record<string, Record<string, any>>;
|
export type NestedRecord = Record<string, RecordAny>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* core模块参数选项
|
* core模块参数选项
|
||||||
@ -159,6 +159,9 @@ export type NestedRecord = Record<string, Record<string, any>>;
|
|||||||
export interface CoreOptions {
|
export interface CoreOptions {
|
||||||
database?: () => TypeOrmModuleOptions;
|
database?: () => TypeOrmModuleOptions;
|
||||||
sms?: () => SmsOptions;
|
sms?: () => SmsOptions;
|
||||||
|
smtp?: () => SmtpOptions;
|
||||||
|
redis?: () => RedisOptions;
|
||||||
|
queue?: () => QueueOptions;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 腾讯云短信驱动配置
|
* 腾讯云短信驱动配置
|
||||||
|
@ -19,6 +19,23 @@ export function defaultUserConfig(configure: Configure): UserConfig {
|
|||||||
3600 * 30,
|
3600 * 30,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
captcha: {
|
||||||
|
sms: {
|
||||||
|
login: {
|
||||||
|
template: configure.env.get('SMS_LOGIN_CAPTCHA_CLOUD', 'your-id'),
|
||||||
|
},
|
||||||
|
register: {
|
||||||
|
template: configure.env.get('SMS_REGISTER_CAPTCHA_CLOUD', 'your-id'),
|
||||||
|
},
|
||||||
|
'retrieve-password': {
|
||||||
|
template: configure.env.get('SMS_RETRIEVE_PASSWORD_CAPTCHA_CLOUD', 'your-id'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
register: {},
|
||||||
|
'retrieve-password': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,3 +40,68 @@ export const TokenConst = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ALLOW_GUEST = 'allowGuest';
|
export const ALLOW_GUEST = 'allowGuest';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码发送数据DTO验证组
|
||||||
|
*/
|
||||||
|
export enum CaptchaDtoGroups {
|
||||||
|
// 发送短信登录验证码
|
||||||
|
PHONE_LOGIN = 'phone-login',
|
||||||
|
// 发送邮件登录验证码
|
||||||
|
EMAIL_LOGIN = 'email-login',
|
||||||
|
// 发送短信注册验证码
|
||||||
|
PHONE_REGISTER = 'phone-register',
|
||||||
|
// 发送邮件注册验证码
|
||||||
|
EMAIL_REGISTER = 'email-register',
|
||||||
|
// 发送找回密码的短信验证码
|
||||||
|
PHONE_RETRIEVE_PASSWORD = 'phone-retrieve-password',
|
||||||
|
// 发送找回密码的邮件验证码
|
||||||
|
EMAIL_RETRIEVE_PASSWORD = 'email-retrieve-password',
|
||||||
|
// 发送登录用户密码重置的短信验证码
|
||||||
|
PHONE_RESET_PASSWORD = 'phone-reset-password',
|
||||||
|
// 发送登录用户密码重置的邮件验证码
|
||||||
|
EMAIL_RESET_PASSWORD = 'email-reset-password',
|
||||||
|
// 发送手机号绑定或更改的短信验证码
|
||||||
|
BOUND_PHONE = 'bound-phone',
|
||||||
|
// 发送邮箱地址绑定或更改的邮件验证码
|
||||||
|
BOUND_EMAIL = 'bound-email',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码操作类别
|
||||||
|
*/
|
||||||
|
export enum CaptchaActionType {
|
||||||
|
// 登录操作
|
||||||
|
LOGIN = 'login',
|
||||||
|
// 注册操作
|
||||||
|
REGISTER = 'register',
|
||||||
|
// 找回密码操作
|
||||||
|
RETRIEVE_PASSWORD = 'retrieve-password',
|
||||||
|
// 重置密码操作
|
||||||
|
RESET_PASSWORD = 'reset-password',
|
||||||
|
// 手机号或邮箱地址绑定操作
|
||||||
|
ACCOUNT_BOUND = 'account-bound',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码类型
|
||||||
|
*/
|
||||||
|
export enum CaptchaType {
|
||||||
|
SMS = 'sms',
|
||||||
|
EMAIL = 'email',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码异步列队名称
|
||||||
|
*/
|
||||||
|
export const SEND_CAPTCHA_QUEUE = 'send-captcha-queue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信验证码任务处理名称
|
||||||
|
*/
|
||||||
|
export const SMS_CAPTCHA_JOB = 'sms-captcha-job';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮件验证码任务处理名称
|
||||||
|
*/
|
||||||
|
export const EMAIL_CAPTCHA_JOB = 'mail-captcha-job';
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { PickType } from '@nestjs/swagger';
|
import { OmitType, PickType } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { IsDefined, IsUUID, Length } from 'class-validator';
|
import { Length } from 'class-validator';
|
||||||
|
|
||||||
import { IsPassword } from '@/modules/core/constraints/password.constraint';
|
import { IsPassword } from '@/modules/core/constraints/password.constraint';
|
||||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||||
import { UserCommonDto } from '@/modules/user/dtos/user.common.dto';
|
import { UserCommonDto } from '@/modules/user/dtos/user.common.dto';
|
||||||
|
|
||||||
import { UserValidateGroup } from '../constants';
|
import { CaptchaDtoGroups, UserValidateGroup } from '../constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新用户信息
|
* 更新用户信息
|
||||||
@ -19,13 +19,6 @@ export class UpdateAccountDto extends PickType(UserCommonDto, ['username', 'nick
|
|||||||
*/
|
*/
|
||||||
@DtoValidation({ groups: [UserValidateGroup.CHANGE_PASSWORD] })
|
@DtoValidation({ groups: [UserValidateGroup.CHANGE_PASSWORD] })
|
||||||
export class UpdatePasswordDto extends PickType(UserCommonDto, ['password', 'plainPassword']) {
|
export class UpdatePasswordDto extends PickType(UserCommonDto, ['password', 'plainPassword']) {
|
||||||
/**
|
|
||||||
* 待更新的用户ID
|
|
||||||
*/
|
|
||||||
@IsUUID(undefined, { message: '用户ID格式不正确', groups: [UserValidateGroup.USER_UPDATE] })
|
|
||||||
@IsDefined({ groups: ['update'], message: '用户ID必须指定' })
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 旧密码:用户在更改密码时需要输入的原密码
|
* 旧密码:用户在更改密码时需要输入的原密码
|
||||||
*/
|
*/
|
||||||
@ -33,3 +26,20 @@ export class UpdatePasswordDto extends PickType(UserCommonDto, ['password', 'pla
|
|||||||
@Length(8, 50, { message: '密码长度不得少于$constraint1', always: true })
|
@Length(8, 50, { message: '密码长度不得少于$constraint1', always: true })
|
||||||
oldPassword: string;
|
oldPassword: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对手机/邮箱绑定验证码进行验证
|
||||||
|
*/
|
||||||
|
export class AccountBoundDto extends PickType(UserCommonDto, ['code', 'phone', 'email']) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定或更改手机号验证
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.BOUND_PHONE] })
|
||||||
|
export class PhoneBoundDto extends OmitType(AccountBoundDto, ['email'] as const) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定或更改邮箱验证
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.BOUND_EMAIL] })
|
||||||
|
export class EmailBoundDto extends OmitType(AccountBoundDto, ['phone'] as const) {}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { PickType } from '@nestjs/swagger';
|
import { PickType } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||||
import { UserValidateGroup } from '@/modules/user/constants';
|
import { CaptchaDtoGroups, UserValidateGroup } from '@/modules/user/constants';
|
||||||
import { UserCommonDto } from '@/modules/user/dtos/user.common.dto';
|
import { UserCommonDto } from '@/modules/user/dtos/user.common.dto';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -9,6 +9,18 @@ import { UserCommonDto } from '@/modules/user/dtos/user.common.dto';
|
|||||||
*/
|
*/
|
||||||
export class CredentialDto extends PickType(UserCommonDto, ['credential', 'password']) {}
|
export class CredentialDto extends PickType(UserCommonDto, ['credential', 'password']) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过手机验证码登录
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.PHONE_LOGIN] })
|
||||||
|
export class PhoneLoginDto extends PickType(UserCommonDto, ['phone', 'code'] as const) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过邮箱验证码登录
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.EMAIL_LOGIN] })
|
||||||
|
export class EmailLoginDto extends PickType(UserCommonDto, ['email', 'code'] as const) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 普通方式注册用户
|
* 普通方式注册用户
|
||||||
*/
|
*/
|
||||||
@ -19,3 +31,47 @@ export class RegisterDto extends PickType(UserCommonDto, [
|
|||||||
'password',
|
'password',
|
||||||
'plainPassword',
|
'plainPassword',
|
||||||
] as const) {}
|
] as const) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过手机验证码注册
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.PHONE_REGISTER] })
|
||||||
|
export class PhoneRegisterDto extends PickType(UserCommonDto, ['phone', 'code'] as const) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过邮件验证码注册
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.EMAIL_REGISTER] })
|
||||||
|
export class EmailRegisterDto extends PickType(UserCommonDto, ['email', 'code'] as const) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过登录凭证找回密码
|
||||||
|
*/
|
||||||
|
export class RetrievePasswordDto extends PickType(UserCommonDto, [
|
||||||
|
'credential',
|
||||||
|
'code',
|
||||||
|
'password',
|
||||||
|
'plainPassword',
|
||||||
|
] as const) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过手机号找回密码
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.EMAIL_RETRIEVE_PASSWORD] })
|
||||||
|
export class PhoneRetrievePasswordDto extends PickType(UserCommonDto, [
|
||||||
|
'phone',
|
||||||
|
'code',
|
||||||
|
'password',
|
||||||
|
'plainPassword',
|
||||||
|
] as const) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过邮箱地址找回密码
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.EMAIL_RETRIEVE_PASSWORD] })
|
||||||
|
export class EmailRetrievePasswordDto extends PickType(UserCommonDto, [
|
||||||
|
'email',
|
||||||
|
'code',
|
||||||
|
'password',
|
||||||
|
'plainPassword',
|
||||||
|
] as const) {}
|
||||||
|
80
src/modules/user/dtos/captcha.dto.ts
Normal file
80
src/modules/user/dtos/captcha.dto.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { PickType } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||||
|
|
||||||
|
import { CaptchaDtoGroups } from '../constants';
|
||||||
|
|
||||||
|
import { UserCommonDto } from './user.common.dto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮件或短信验证码消息
|
||||||
|
*/
|
||||||
|
export class CaptchaMessage extends PickType(UserCommonDto, ['phone', 'email']) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信验证码DTO类型
|
||||||
|
*/
|
||||||
|
export class PhoneCaptchaMessageDto extends PickType(CaptchaMessage, ['phone'] as const) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮件验证码DTO类型
|
||||||
|
*/
|
||||||
|
export class EmailCaptchaMessageDto extends PickType(CaptchaMessage, ['email'] as const) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过已登录账户发送验证码消息
|
||||||
|
*/
|
||||||
|
export class UserCaptchaMessageDto extends PickType(UserCommonDto, ['type']) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过用户凭证发送验证码消息
|
||||||
|
*/
|
||||||
|
export class CredentialCaptchaMessageDto extends PickType(UserCommonDto, ['credential']) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送登录验证码短信
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.PHONE_LOGIN] })
|
||||||
|
export class LoginPhoneCaptchaDto extends PhoneCaptchaMessageDto {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送登录验证码邮件
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.EMAIL_LOGIN] })
|
||||||
|
export class LoginEmailCaptchaDto extends EmailCaptchaMessageDto {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送注册验证码短信
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.PHONE_REGISTER] })
|
||||||
|
export class RegisterPhoneCaptchaDto extends PhoneCaptchaMessageDto {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送注册验证码邮件
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.PHONE_REGISTER] })
|
||||||
|
export class RegisterEmailCaptchaDto extends EmailCaptchaMessageDto {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送找回密码短信
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.EMAIL_RETRIEVE_PASSWORD] })
|
||||||
|
export class RetrievePasswordPhoneCaptchaDto extends PhoneCaptchaMessageDto {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送找回密码邮件
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.EMAIL_RETRIEVE_PASSWORD] })
|
||||||
|
export class RetrievePasswordEmailCaptchaDto extends EmailCaptchaMessageDto {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送手机绑定短信
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.BOUND_PHONE] })
|
||||||
|
export class BoundPhoneCaptchaDto extends PhoneCaptchaMessageDto {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮箱绑定邮件
|
||||||
|
*/
|
||||||
|
@DtoValidation({ groups: [CaptchaDtoGroups.BOUND_EMAIL] })
|
||||||
|
export class BoundEmailCaptchaDto extends EmailCaptchaMessageDto {}
|
@ -1,11 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { IsEmail, IsNotEmpty, IsOptional, Length } from 'class-validator';
|
import { IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsOptional, Length } from 'class-validator';
|
||||||
|
|
||||||
import { IsMatch } from '@/modules/core/constraints/match.constraint';
|
import { IsMatch } from '@/modules/core/constraints/match.constraint';
|
||||||
import { IsPassword } from '@/modules/core/constraints/password.constraint';
|
import { IsPassword } from '@/modules/core/constraints/password.constraint';
|
||||||
import { IsMatchPhone } from '@/modules/core/constraints/phone.number.constraint';
|
import { IsMatchPhone } from '@/modules/core/constraints/phone.number.constraint';
|
||||||
import { IsUnique, IsUniqueExist } from '@/modules/database/constraints';
|
import { IsUnique, IsUniqueExist } from '@/modules/database/constraints';
|
||||||
import { UserValidateGroup } from '@/modules/user/constants';
|
import { CaptchaType, UserValidateGroup } from '@/modules/user/constants';
|
||||||
import { UserEntity } from '@/modules/user/entities/user.entity';
|
import { UserEntity } from '@/modules/user/entities/user.entity';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,4 +111,11 @@ export class UserCommonDto {
|
|||||||
@IsMatch('password', false, { message: '两次输入密码不同', always: true })
|
@IsMatch('password', false, { message: '两次输入密码不同', always: true })
|
||||||
@IsNotEmpty({ message: '请再次输入密码以确认', always: true })
|
@IsNotEmpty({ message: '请再次输入密码以确认', always: true })
|
||||||
plainPassword: string;
|
plainPassword: string;
|
||||||
|
|
||||||
|
@IsNumberString(undefined, { message: '验证码必须为数字', always: true })
|
||||||
|
@Length(6, 6, { message: '验证码长度错误', always: true })
|
||||||
|
code!: string;
|
||||||
|
|
||||||
|
@IsEnum(CaptchaType)
|
||||||
|
type: CaptchaType;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { OmitType, PartialType, PickType } from '@nestjs/swagger';
|
import { OmitType, PartialType, PickType } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { IsDefined, IsEnum, IsOptional, IsUUID } from 'class-validator';
|
import { Transform } from 'class-transformer';
|
||||||
|
import { IsBoolean, IsDefined, IsEnum, IsOptional, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||||
|
import { toBoolean } from '@/modules/core/helpers';
|
||||||
import { IsDataExist } from '@/modules/database/constraints';
|
import { IsDataExist } from '@/modules/database/constraints';
|
||||||
import { PermissionEntity, RoleEntity } from '@/modules/rbac/entities';
|
import { PermissionEntity, RoleEntity } from '@/modules/rbac/entities';
|
||||||
import { PaginateWithTrashedDto } from '@/modules/restful/dtos/paginate-width-trashed.dto';
|
import { PaginateWithTrashedDto } from '@/modules/restful/dtos/paginate-width-trashed.dto';
|
||||||
@ -69,6 +71,7 @@ export class UpdateUserDto extends PartialType(CreateUserDto) {
|
|||||||
/**
|
/**
|
||||||
* 查询用户列表的Query数据验证
|
* 查询用户列表的Query数据验证
|
||||||
*/
|
*/
|
||||||
|
@DtoValidation({ type: 'query', skipMissingProperties: true })
|
||||||
export class QueryUserDto extends PaginateWithTrashedDto {
|
export class QueryUserDto extends PaginateWithTrashedDto {
|
||||||
/**
|
/**
|
||||||
* 角色ID:根据角色来过滤用户
|
* 角色ID:根据角色来过滤用户
|
||||||
@ -96,6 +99,13 @@ export class QueryUserDto extends PaginateWithTrashedDto {
|
|||||||
@IsEnum(UserOrderType)
|
@IsEnum(UserOrderType)
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
orderBy?: UserOrderType;
|
orderBy?: UserOrderType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤激活状态
|
||||||
|
*/
|
||||||
|
@Transform(({ value }) => toBoolean(value))
|
||||||
|
@IsBoolean()
|
||||||
|
actived?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
45
src/modules/user/entities/captcha.entity.ts
Normal file
45
src/modules/user/entities/captcha.entity.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
import { CaptchaActionType, CaptchaType } from '@/modules/user/constants';
|
||||||
|
|
||||||
|
@Entity('user_captcha')
|
||||||
|
export class CaptchaEntity {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Column({ comment: '验证码' })
|
||||||
|
code!: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: CaptchaActionType,
|
||||||
|
comment: '验证操作类型',
|
||||||
|
})
|
||||||
|
action!: CaptchaActionType;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: CaptchaType,
|
||||||
|
comment: '验证码类型',
|
||||||
|
})
|
||||||
|
type!: CaptchaType;
|
||||||
|
|
||||||
|
@Column({ comment: '手机号/邮箱地址' })
|
||||||
|
value!: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({
|
||||||
|
comment: '创建时间',
|
||||||
|
})
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({
|
||||||
|
comment: '更新时间',
|
||||||
|
})
|
||||||
|
updatedAt!: Date;
|
||||||
|
}
|
@ -63,6 +63,9 @@ export class UserEntity {
|
|||||||
@Column({ comment: '用户邮箱', length: 256, nullable: true, unique: true })
|
@Column({ comment: '用户邮箱', length: 256, nullable: true, unique: true })
|
||||||
email?: string;
|
email?: string;
|
||||||
|
|
||||||
|
@Column({ comment: '用户状态,是否激活', default: false })
|
||||||
|
actived?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户创建时间
|
* 用户创建时间
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import { CaptchaActionType, CaptchaType } from '@/modules/user/constants';
|
||||||
|
import { CaptchaEntity } from '@/modules/user/entities/captcha.entity';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户配置类型
|
* 自定义用户模块配置
|
||||||
*/
|
*/
|
||||||
export interface UserConfig {
|
export interface UserConfig {
|
||||||
/**
|
/**
|
||||||
@ -10,6 +13,8 @@ export interface UserConfig {
|
|||||||
* jwt token的生成配置
|
* jwt token的生成配置
|
||||||
*/
|
*/
|
||||||
jwt: JwtConfig;
|
jwt: JwtConfig;
|
||||||
|
|
||||||
|
captcha?: CustomCaptchaConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,3 +44,76 @@ export interface JwtPayload {
|
|||||||
*/
|
*/
|
||||||
iat: number;
|
iat: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认用户模块配置
|
||||||
|
*/
|
||||||
|
export interface DefaultUserConfig {
|
||||||
|
hash: number;
|
||||||
|
jwt: Pick<Required<JwtConfig>, 'tokenExpired' | 'refreshTokenExpired'>;
|
||||||
|
captcha: DefaultCaptchaConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义验证码配置
|
||||||
|
*/
|
||||||
|
export interface CustomCaptchaConfig {
|
||||||
|
[CaptchaType.SMS]?: {
|
||||||
|
[key in CaptchaActionType]?: Partial<SmsCaptchaOption>;
|
||||||
|
};
|
||||||
|
[CaptchaType.EMAIL]?: {
|
||||||
|
[key in CaptchaActionType]?: Partial<EmailCaptchaOption>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认验证码配置
|
||||||
|
*/
|
||||||
|
export interface DefaultCaptchaConfig {
|
||||||
|
[CaptchaType.SMS]: {
|
||||||
|
[key in CaptchaActionType]: CaptchaOption;
|
||||||
|
};
|
||||||
|
[CaptchaType.EMAIL]: {
|
||||||
|
[key in CaptchaActionType]: Omit<EmailCaptchaOption, 'template'>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用验证码选项
|
||||||
|
*/
|
||||||
|
export interface CaptchaOption {
|
||||||
|
limit: number; // 验证码发送间隔时间
|
||||||
|
expired: number; // 验证码有效时间
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机验证码选项
|
||||||
|
*/
|
||||||
|
export interface SmsCaptchaOption extends CaptchaOption {
|
||||||
|
template: string; // 云厂商短信推送模板ID
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件验证码选项
|
||||||
|
*/
|
||||||
|
export interface EmailCaptchaOption extends CaptchaOption {
|
||||||
|
subject: string; // 邮件主题
|
||||||
|
template?: string; // 模板路径
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务传给消费者的数据类型
|
||||||
|
*/
|
||||||
|
export interface SendCaptchaQueueJob {
|
||||||
|
captcha: { [key in keyof CaptchaEntity]: CaptchaEntity[key] };
|
||||||
|
option: SmsCaptchaOption | EmailCaptchaOption;
|
||||||
|
otherVars?: RecordAny;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码正确性验证
|
||||||
|
*/
|
||||||
|
export type CaptchaValidate<T extends RecordAny = RecordNever> = T & {
|
||||||
|
value: string;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
@ -21,3 +21,10 @@ export async function encrypt(configure: Configure, password: string) {
|
|||||||
export function decrypt(password: string, hashed: string) {
|
export function decrypt(password: string, hashed: string) {
|
||||||
return bcrypt.compareSync(password, hashed);
|
return bcrypt.compareSync(password, hashed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机验证码
|
||||||
|
*/
|
||||||
|
export function generateCaptchaCode() {
|
||||||
|
return Math.random().toFixed(6).slice(-6);
|
||||||
|
}
|
||||||
|
@ -69,7 +69,7 @@ export const createOptions: CreateOptions = {
|
|||||||
* 生成Redis配置
|
* 生成Redis配置
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
export const createRedisOptions = (options: RedisOptions) => {
|
export const createRedisOptions = (options: RedisOptions): RedisOption[] | undefined => {
|
||||||
if (isNil(options)) {
|
if (isNil(options)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user