feat: 手机验证码作为用户凭证

This commit is contained in:
archer
2023-04-16 19:53:50 +08:00
parent 36dad6df33
commit faf722fa15
20 changed files with 375 additions and 167 deletions

View File

@@ -11,6 +11,9 @@
"format": "prettier --config \"./.prettierrc.js\" --write \"./src/**/*.{ts,tsx,scss}\""
},
"dependencies": {
"@alicloud/dysmsapi20170525": "^2.0.23",
"@alicloud/openapi-client": "^0.4.5",
"@alicloud/tea-util": "^1.4.5",
"@chakra-ui/icons": "^2.0.17",
"@chakra-ui/react": "^2.5.1",
"@emotion/react": "^11.10.6",

179
pnpm-lock.yaml generated
View File

@@ -1,6 +1,9 @@
lockfileVersion: 5.4
specifiers:
'@alicloud/dysmsapi20170525': ^2.0.23
'@alicloud/openapi-client': ^0.4.5
'@alicloud/tea-util': ^1.4.5
'@chakra-ui/icons': ^2.0.17
'@chakra-ui/react': ^2.5.1
'@emotion/react': ^11.10.6
@@ -61,6 +64,9 @@ specifiers:
zustand: ^4.3.5
dependencies:
'@alicloud/dysmsapi20170525': registry.npmmirror.com/@alicloud/dysmsapi20170525/2.0.23
'@alicloud/openapi-client': registry.npmmirror.com/@alicloud/openapi-client/0.4.5
'@alicloud/tea-util': registry.npmmirror.com/@alicloud/tea-util/1.4.5
'@chakra-ui/icons': registry.npmmirror.com/@chakra-ui/icons/2.0.17_react@18.2.0
'@chakra-ui/react': registry.npmmirror.com/@chakra-ui/react/2.5.1_e6pzu3hsaqmql4fl7jx73ckiym
'@emotion/react': registry.npmmirror.com/@emotion/react/11.10.6_pmekkgnqduwlme35zpnqhenc34
@@ -124,6 +130,117 @@ devDependencies:
packages:
registry.npmmirror.com/@alicloud/credentials/2.2.6:
resolution: {integrity: sha512-jG+msY77dHmAF3x+8VTy7fEgORyXLHmDci8t92HeipBdCHsPptDegA++GEwKgR7f6G4wvafYt+aqMZ1iligdrQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/credentials/-/credentials-2.2.6.tgz}
name: '@alicloud/credentials'
version: 2.2.6
dependencies:
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
httpx: registry.npmmirror.com/httpx/2.2.7
ini: registry.npmmirror.com/ini/1.3.8
kitx: registry.npmmirror.com/kitx/2.1.0
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/@alicloud/dysmsapi20170525/2.0.23:
resolution: {integrity: sha512-C02xj9S2ZPL13SciChlIY3s5+PiOM13jEGZSn+L92aiWYCBqTlpx9UMwNKBNWImMSOlG71IOSYfsQggaoIY+4Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/dysmsapi20170525/-/dysmsapi20170525-2.0.23.tgz}
name: '@alicloud/dysmsapi20170525'
version: 2.0.23
dependencies:
'@alicloud/endpoint-util': registry.npmmirror.com/@alicloud/endpoint-util/0.0.1
'@alicloud/openapi-client': registry.npmmirror.com/@alicloud/openapi-client/0.4.5
'@alicloud/openapi-util': registry.npmmirror.com/@alicloud/openapi-util/0.3.1
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
'@alicloud/tea-util': registry.npmmirror.com/@alicloud/tea-util/1.4.5
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/@alicloud/endpoint-util/0.0.1:
resolution: {integrity: sha512-+pH7/KEXup84cHzIL6UJAaPqETvln4yXlD9JzlrqioyCSaWxbug5FUobsiI6fuUOpw5WwoB3fWAtGbFnJ1K3Yg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/endpoint-util/-/endpoint-util-0.0.1.tgz}
name: '@alicloud/endpoint-util'
version: 0.0.1
dependencies:
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
kitx: registry.npmmirror.com/kitx/2.1.0
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/@alicloud/gateway-spi/0.0.8:
resolution: {integrity: sha512-KM7fu5asjxZPmrz9sJGHJeSU+cNQNOxW+SFmgmAIrITui5hXL2LB+KNRuzWmlwPjnuA2X3/keq9h6++S9jcV5g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/gateway-spi/-/gateway-spi-0.0.8.tgz}
name: '@alicloud/gateway-spi'
version: 0.0.8
dependencies:
'@alicloud/credentials': registry.npmmirror.com/@alicloud/credentials/2.2.6
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/@alicloud/openapi-client/0.4.5:
resolution: {integrity: sha512-x1blwhfPOVkH/JCLWFssFRWDL0C75RToun9AwhNV+84gqJB2/GUipm3quHGLon8JiQ0DQ9YBUho2rukSoAvhJQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/openapi-client/-/openapi-client-0.4.5.tgz}
name: '@alicloud/openapi-client'
version: 0.4.5
dependencies:
'@alicloud/credentials': registry.npmmirror.com/@alicloud/credentials/2.2.6
'@alicloud/gateway-spi': registry.npmmirror.com/@alicloud/gateway-spi/0.0.8
'@alicloud/openapi-util': registry.npmmirror.com/@alicloud/openapi-util/0.3.1
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
'@alicloud/tea-util': registry.npmmirror.com/@alicloud/tea-util/1.4.5
'@alicloud/tea-xml': registry.npmmirror.com/@alicloud/tea-xml/0.0.2
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/@alicloud/openapi-util/0.3.1:
resolution: {integrity: sha512-6mGT+hs+SXismZi/CEkjPhhbn2U3qTT/Qv/RXAYFA1DC3Jk4/YaX3N7RtpgdzOhdD7uI8XtNkaULKHZY3BrtxQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/openapi-util/-/openapi-util-0.3.1.tgz}
name: '@alicloud/openapi-util'
version: 0.3.1
dependencies:
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
'@alicloud/tea-util': registry.npmmirror.com/@alicloud/tea-util/1.4.5
kitx: registry.npmmirror.com/kitx/2.1.0
sm3: registry.npmmirror.com/sm3/1.0.3
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/@alicloud/tea-typescript/1.8.0:
resolution: {integrity: sha512-CWXWaquauJf0sW30mgJRVu9aaXyBth5uMBCUc+5vKTK1zlgf3hIqRUjJZbjlwHwQ5y9anwcu18r48nOZb7l2QQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/tea-typescript/-/tea-typescript-1.8.0.tgz}
name: '@alicloud/tea-typescript'
version: 1.8.0
dependencies:
'@types/node': registry.npmmirror.com/@types/node/12.20.55
httpx: registry.npmmirror.com/httpx/2.2.7
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/@alicloud/tea-util/1.4.5:
resolution: {integrity: sha512-7NuThYUi90/ivT/ORKusm0NVKlc1khPTtlzTR77xEqSBt7d24Ee/Lo70hx9PWP28nHpIZ1gM0NKYBtpq7HUDlg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/tea-util/-/tea-util-1.4.5.tgz}
name: '@alicloud/tea-util'
version: 1.4.5
dependencies:
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
kitx: registry.npmmirror.com/kitx/2.1.0
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/@alicloud/tea-xml/0.0.2:
resolution: {integrity: sha512-Xs7v5y7YSNSDDYmiDWAC0/013VWPjS3dQU4KezSLva9VGiTVPaL3S7Nk4NrTmAYCG6MKcrRj/nGEDIWL5KRoPg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/tea-xml/-/tea-xml-0.0.2.tgz}
name: '@alicloud/tea-xml'
version: 0.0.2
dependencies:
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
'@types/xml2js': registry.npmmirror.com/@types/xml2js/0.4.11
xml2js: registry.npmmirror.com/xml2js/0.4.23
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/@ampproject/remapping/2.2.0:
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.0.tgz}
name: '@ampproject/remapping'
@@ -5031,6 +5148,18 @@ packages:
version: 0.7.31
dev: false
registry.npmmirror.com/@types/node/12.20.55:
resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/-/node-12.20.55.tgz}
name: '@types/node'
version: 12.20.55
dev: false
registry.npmmirror.com/@types/node/14.18.42:
resolution: {integrity: sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/-/node-14.18.42.tgz}
name: '@types/node'
version: 14.18.42
dev: false
registry.npmmirror.com/@types/node/18.14.0:
resolution: {integrity: sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/-/node-18.14.0.tgz}
name: '@types/node'
@@ -5133,6 +5262,14 @@ packages:
'@types/webidl-conversions': registry.npmmirror.com/@types/webidl-conversions/7.0.0
dev: false
registry.npmmirror.com/@types/xml2js/0.4.11:
resolution: {integrity: sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/xml2js/-/xml2js-0.4.11.tgz}
name: '@types/xml2js'
version: 0.4.11
dependencies:
'@types/node': registry.npmmirror.com/@types/node/18.14.0
dev: false
registry.npmmirror.com/@typescript-eslint/parser/5.52.0_7kw3g6rralp5ps6mg3uyzz6azm:
resolution: {integrity: sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.52.0.tgz}
id: registry.npmmirror.com/@typescript-eslint/parser/5.52.0
@@ -7650,6 +7787,17 @@ packages:
- supports-color
dev: false
registry.npmmirror.com/httpx/2.2.7:
resolution: {integrity: sha512-Wjh2JOAah0pdczfqL8NC5378G7jMt0Zcpn8U+yyxAiejjlagzSTQgJHuVvka2VNPQlKfoGehYRc79WKq9E4gDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/httpx/-/httpx-2.2.7.tgz}
name: httpx
version: 2.2.7
dependencies:
'@types/node': registry.npmmirror.com/@types/node/14.18.42
debug: registry.npmmirror.com/debug/4.3.4
transitivePeerDependencies:
- supports-color
dev: false
registry.npmmirror.com/human-signals/3.0.1:
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/human-signals/-/human-signals-3.0.1.tgz}
name: human-signals
@@ -8284,6 +8432,14 @@ packages:
commander: registry.npmmirror.com/commander/8.3.0
dev: false
registry.npmmirror.com/kitx/2.1.0:
resolution: {integrity: sha512-C/5v9MtIX7aHGOjwn5BmrrbNkJSf7i0R5mRzmh13GSAdRqQ7bYQo/Su2pTYNylFicqKNTVX3HML9k1u8k51+pQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/kitx/-/kitx-2.1.0.tgz}
name: kitx
version: 2.1.0
dependencies:
'@types/node': registry.npmmirror.com/@types/node/12.20.55
dev: false
registry.npmmirror.com/kleur/4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz}
name: kleur
@@ -10598,6 +10754,12 @@ packages:
is-fullwidth-code-point: registry.npmmirror.com/is-fullwidth-code-point/4.0.0
dev: true
registry.npmmirror.com/sm3/1.0.3:
resolution: {integrity: sha512-KyFkIfr8QBlFG3uc3NaljaXdYcsbRy1KrSfc4tsQV8jW68jAktGeOcifu530Vx/5LC+PULHT0Rv8LiI8Gw+c1g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sm3/-/sm3-1.0.3.tgz}
name: sm3
version: 1.0.3
dev: false
registry.npmmirror.com/smart-buffer/4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz}
name: smart-buffer
@@ -11618,6 +11780,16 @@ packages:
- supports-color
dev: false
registry.npmmirror.com/xml2js/0.4.23:
resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xml2js/-/xml2js-0.4.23.tgz}
name: xml2js
version: 0.4.23
engines: {node: '>=4.0.0'}
dependencies:
sax: registry.npmmirror.com/sax/1.1.6
xmlbuilder: registry.npmmirror.com/xmlbuilder/11.0.1
dev: false
registry.npmmirror.com/xmlbuilder/10.1.1:
resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz}
name: xmlbuilder
@@ -11625,6 +11797,13 @@ packages:
engines: {node: '>=4.0'}
dev: false
registry.npmmirror.com/xmlbuilder/11.0.1:
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz}
name: xmlbuilder
version: 11.0.1
engines: {node: '>=4.0'}
dev: false
registry.npmmirror.com/xregexp/2.0.0:
resolution: {integrity: sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xregexp/-/xregexp-2.0.0.tgz}
name: xregexp

View File

@@ -9,7 +9,7 @@ wx号: fastgpt123
### 快速开始
1. 使用邮箱注册账号。
1. 使用手机号注册账号。
2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。
3. 如果填写了自己的 openai 账号,使用时会直接用你的账号。如果没有填写,需要付费使用平台的账号。
4. 进入模型页,创建一个模型,建议直接用 ChatGPT。

View File

@@ -1,50 +1,55 @@
import { GET, POST, PUT } from './request';
import { createHashPassword, Obj2Query } from '@/utils/tools';
import { ResLogin } from './response/user';
import { EmailTypeEnum } from '@/constants/common';
import { UserAuthTypeEnum } from '@/constants/common';
import { UserType, UserUpdateParams } from '@/types/user';
import type { PagingData, RequestPaging } from '@/types';
import { BillSchema, PaySchema } from '@/types/mongoSchema';
import { adaptBill } from '@/utils/adapt';
export const sendCodeToEmail = ({ email, type }: { email: string; type: `${EmailTypeEnum}` }) =>
GET('/user/sendEmail', { email, type });
export const sendAuthCode = ({
username,
type
}: {
username: string;
type: `${UserAuthTypeEnum}`;
}) => GET('/user/sendAuthCode', { username, type });
export const getTokenLogin = () => GET<UserType>('/user/tokenLogin');
export const postRegister = ({
email,
phone,
password,
code
}: {
email: string;
phone: string;
code: string;
password: string;
}) =>
POST<ResLogin>('/user/register', {
email,
phone,
code,
password: createHashPassword(password)
});
export const postFindPassword = ({
email,
username,
code,
password
}: {
email: string;
username: string;
code: string;
password: string;
}) =>
POST<ResLogin>('/user/updatePasswordByCode', {
email,
username,
code,
password: createHashPassword(password)
});
export const postLogin = ({ email, password }: { email: string; password: string }) =>
export const postLogin = ({ username, password }: { username: string; password: string }) =>
POST<ResLogin>('/user/loginByPassword', {
email,
username,
password: createHashPassword(password)
});

View File

@@ -1,4 +1,4 @@
export enum EmailTypeEnum {
export enum UserAuthTypeEnum {
register = 'register',
findPassword = 'findPassword'
}

View File

@@ -1,6 +1,6 @@
import { useState, useMemo, useCallback } from 'react';
import { sendCodeToEmail } from '@/api/user';
import { EmailTypeEnum } from '@/constants/common';
import { sendAuthCode } from '@/api/user';
import { UserAuthTypeEnum } from '@/constants/common';
let timer: any;
import { useToast } from './useToast';
@@ -19,11 +19,11 @@ export const useSendCode = () => {
}, [codeCountDown]);
const sendCode = useCallback(
async ({ email, type }: { email: string; type: `${EmailTypeEnum}` }) => {
async ({ username, type }: { username: string; type: `${UserAuthTypeEnum}` }) => {
setCodeSending(true);
try {
await sendCodeToEmail({
email,
await sendAuthCode({
username,
type
});
setCodeCountDown(60);

View File

@@ -7,24 +7,24 @@ import { generateToken } from '@/service/utils/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { email, password } = req.body;
const { username, password } = req.body;
if (!email || !password) {
if (!username || !password) {
throw new Error('缺少参数');
}
await connectToDatabase();
// 检测邮箱是否存在
const authEmail = await User.findOne({
email
// 检测用户是否存在
const authUser = await User.findOne({
username
});
if (!authEmail) {
throw new Error('邮箱未注册');
if (!authUser) {
throw new Error('用户未注册');
}
const user = await User.findOne({
email,
username,
password
});

View File

@@ -5,23 +5,29 @@ import { User } from '@/service/models/user';
import { AuthCode } from '@/service/models/authCode';
import { connectToDatabase } from '@/service/mongo';
import { generateToken } from '@/service/utils/tools';
import { EmailTypeEnum } from '@/constants/common';
import { UserAuthTypeEnum } from '@/constants/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { email, code, password } = req.body;
const { phone, code, password } = req.body;
if (!email || !code || !password) {
if (!phone || !code || !password) {
throw new Error('缺少参数');
}
const reg = /^1[3456789]\d{9}$/;
if (!reg.test(phone)) {
throw new Error('手机号格式错误');
}
await connectToDatabase();
// 验证码校验
// 验证码校验. 注册只接收手机号
const authCode = await AuthCode.findOne({
email,
username: phone,
code,
type: EmailTypeEnum.register,
type: UserAuthTypeEnum.register,
expiredTime: { $gte: Date.now() }
});
@@ -31,15 +37,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 重名校验
const authRepeat = await User.findOne({
email
username: phone
});
if (authRepeat) {
throw new Error('邮箱已被注册');
throw new Error('手机号已被注册');
}
const response = await User.create({
email,
username: phone,
password
});
@@ -50,6 +56,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
throw new Error('获取用户信息异常');
}
// 删除验证码记录
await AuthCode.deleteMany({
username: phone
});
jsonRes(res, {
data: {
token: generateToken(user._id),

View File

@@ -2,28 +2,27 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { AuthCode } from '@/service/models/authCode';
import { connectToDatabase, User } from '@/service/mongo';
import { sendCode } from '@/service/utils/sendEmail';
import { EmailTypeEnum } from '@/constants/common';
import { connectToDatabase } from '@/service/mongo';
import { sendPhoneCode, sendEmailCode } from '@/service/utils/sendNote';
import { UserAuthTypeEnum } from '@/constants/common';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('1234567890', 6);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { email, type } = req.query as { email: string; type: `${EmailTypeEnum}` };
const { username, type } = req.query as { username: string; type: `${UserAuthTypeEnum}` };
if (!email || !type) {
if (!username || !type) {
throw new Error('缺少参数');
}
await connectToDatabase();
let code = '';
for (let i = 0; i < 6; i++) {
code += Math.floor(Math.random() * 10);
}
let code = nanoid();
// 判断 1 分钟内是否有重复数据
const authCode = await AuthCode.findOne({
email,
username,
type,
expiredTime: { $gte: Date.now() + 4 * 60 * 1000 } // 如果有一个记录的过期时间,大于当前+4分钟说明距离上次发送还没到1分钟。因为默认创建时过期时间是未来5分钟
});
@@ -34,13 +33,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 创建 auth 记录
await AuthCode.create({
email,
username,
type,
code
});
if (username.includes('@')) {
await sendEmailCode(username, code, type);
} else {
// 发送验证码
await sendCode(email as string, code, type as `${EmailTypeEnum}`);
await sendPhoneCode(username, code);
}
jsonRes(res, {
message: '发送验证码成功'

View File

@@ -5,13 +5,13 @@ import { User } from '@/service/models/user';
import { AuthCode } from '@/service/models/authCode';
import { connectToDatabase } from '@/service/mongo';
import { generateToken } from '@/service/utils/tools';
import { EmailTypeEnum } from '@/constants/common';
import { UserAuthTypeEnum } from '@/constants/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { email, code, password } = req.body;
const { username, code, password } = req.body;
if (!email || !code || !password) {
if (!username || !code || !password) {
throw new Error('缺少参数');
}
@@ -19,9 +19,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 验证码校验
const authCode = await AuthCode.findOne({
email,
username,
code,
type: EmailTypeEnum.findPassword,
type: UserAuthTypeEnum.findPassword,
expiredTime: { $gte: Date.now() }
});
@@ -32,16 +32,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 更新对应的记录
await User.updateOne(
{
email
username
},
{
password
}
);
// 根据 email 获取用户信息
// 根据 username 获取用户信息
const user = await User.findOne({
email
username
});
if (!user) {

View File

@@ -14,7 +14,7 @@ interface Props {
}
interface RegisterType {
email: string;
username: string;
code: string;
password: string;
password2: string;
@@ -36,10 +36,10 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
const { codeSending, sendCodeText, sendCode, codeCountDown } = useSendCode();
const onclickSendCode = useCallback(async () => {
const check = await trigger('email');
const check = await trigger('username');
if (!check) return;
sendCode({
email: getValues('email'),
username: getValues('username'),
type: 'findPassword'
});
}, [getValues, sendCode, trigger]);
@@ -47,12 +47,12 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
const [requesting, setRequesting] = useState(false);
const onclickFindPassword = useCallback(
async ({ email, code, password }: RegisterType) => {
async ({ username, code, password }: RegisterType) => {
setRequesting(true);
try {
loginSuccess(
await postFindPassword({
email,
username,
code,
password
})
@@ -78,23 +78,24 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
FastGPT
</Box>
<form onSubmit={handleSubmit(onclickFindPassword)}>
<FormControl mt={8} isInvalid={!!errors.email}>
<FormControl mt={8} isInvalid={!!errors.username}>
<Input
placeholder="邮箱"
placeholder="邮箱/手机号"
size={mediaLgMd}
{...register('email', {
required: '邮箱不能为空',
{...register('username', {
required: '邮箱/手机号不能为空',
pattern: {
value: /^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/,
message: '邮箱错误'
value:
/(^1[3456789]\d{9}$)|(^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$)/,
message: '邮箱/手机号格式错误'
}
})}
></Input>
<FormErrorMessage position={'absolute'} fontSize="xs">
{!!errors.email && errors.email.message}
{!!errors.username && errors.username.message}
</FormErrorMessage>
</FormControl>
<FormControl mt={8} isInvalid={!!errors.email}>
<FormControl mt={8} isInvalid={!!errors.username}>
<Flex>
<Input
flex={1}

View File

@@ -13,7 +13,7 @@ interface Props {
}
interface LoginFormType {
email: string;
username: string;
password: string;
}
@@ -29,12 +29,12 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
const [requesting, setRequesting] = useState(false);
const onclickLogin = useCallback(
async ({ email, password }: LoginFormType) => {
async ({ username, password }: LoginFormType) => {
setRequesting(true);
try {
loginSuccess(
await postLogin({
email,
username,
password
})
);
@@ -59,20 +59,21 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
FastGPT
</Box>
<form onSubmit={handleSubmit(onclickLogin)}>
<FormControl mt={8} isInvalid={!!errors.email}>
<FormControl mt={8} isInvalid={!!errors.username}>
<Input
placeholder="邮箱"
placeholder="邮箱/手机号"
size={mediaLgMd}
{...register('email', {
required: '邮箱不能为空',
{...register('username', {
required: '邮箱/手机号不能为空',
pattern: {
value: /^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/,
message: '邮箱错误'
value:
/(^1[3456789]\d{9}$)|(^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$)/,
message: '邮箱/手机号格式错误'
}
})}
></Input>
<FormErrorMessage position={'absolute'} fontSize="xs">
{!!errors.email && errors.email.message}
{!!errors.username && errors.username.message}
</FormErrorMessage>
</FormControl>
<FormControl mt={8} isInvalid={!!errors.password}>

View File

@@ -14,7 +14,7 @@ interface Props {
}
interface RegisterType {
email: string;
phone: string;
password: string;
password2: string;
code: string;
@@ -36,10 +36,10 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
const { codeSending, sendCodeText, sendCode, codeCountDown } = useSendCode();
const onclickSendCode = useCallback(async () => {
const check = await trigger('email');
const check = await trigger('phone');
if (!check) return;
sendCode({
email: getValues('email'),
username: getValues('phone'),
type: 'register'
});
}, [getValues, sendCode, trigger]);
@@ -47,12 +47,12 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
const [requesting, setRequesting] = useState(false);
const onclickRegister = useCallback(
async ({ email, password, code }: RegisterType) => {
async ({ phone, password, code }: RegisterType) => {
setRequesting(true);
try {
loginSuccess(
await postRegister({
email,
phone,
code,
password
})
@@ -78,23 +78,23 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
FastGPT
</Box>
<form onSubmit={handleSubmit(onclickRegister)}>
<FormControl mt={8} isInvalid={!!errors.email}>
<FormControl mt={8} isInvalid={!!errors.phone}>
<Input
placeholder="邮箱"
placeholder="手机号"
size={mediaLgMd}
{...register('email', {
required: '邮箱不能为空',
{...register('phone', {
required: '手机号不能为空',
pattern: {
value: /^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/,
message: '邮箱错误'
value: /^1[3456789]\d{9}$/,
message: '手机号格式错误'
}
})}
></Input>
<FormErrorMessage position={'absolute'} fontSize="xs">
{!!errors.email && errors.email.message}
{!!errors.phone && errors.phone.message}
</FormErrorMessage>
</FormControl>
<FormControl mt={8} isInvalid={!!errors.email}>
<FormControl mt={8} isInvalid={!!errors.phone}>
<Flex>
<Input
flex={1}

View File

@@ -51,8 +51,8 @@ const NumberSetting = () => {
</Box>
<Flex mt={6} alignItems={'center'}>
<Box flex={'0 0 60px'}>:</Box>
<Box>{userInfo?.email}</Box>
<Box flex={'0 0 60px'}>:</Box>
<Box>{userInfo?.username}</Box>
</Flex>
<Box mt={6}>
<Flex alignItems={'center'}>

View File

@@ -2,7 +2,7 @@ import { Schema, model, models, Model } from 'mongoose';
import { AuthCodeSchema as AuthCodeType } from '@/types/mongoSchema';
const AuthCodeSchema = new Schema({
email: {
username: {
type: String,
required: true
},

View File

@@ -3,7 +3,8 @@ import { hashPassword } from '@/service/utils/tools';
import { PRICE_SCALE } from '@/constants/common';
import { UserModelSchema } from '@/types/mongoSchema';
const UserSchema = new Schema({
email: {
username: {
// 可以是手机/邮箱,新的验证都只用手机
type: String,
required: true,
unique: true // 唯一

View File

@@ -1,63 +0,0 @@
import * as nodemailer from 'nodemailer';
import { EmailTypeEnum } from '@/constants/common';
import dayjs from 'dayjs';
const myEmail = process.env.MY_MAIL;
let mailTransport = nodemailer.createTransport({
// host: 'smtp.qq.email',
service: 'qq',
secure: true, //安全方式发送,建议都加上
auth: {
user: myEmail,
pass: process.env.MAILE_CODE
}
});
const emailMap: { [key: string]: any } = {
[EmailTypeEnum.register]: {
subject: '注册 FastGPT 账号',
html: (code: string) => `<div>您正在注册 FastGPT 账号,验证码为:${code}</div>`
},
[EmailTypeEnum.findPassword]: {
subject: '修改 FastGPT 密码',
html: (code: string) => `<div>您正在修改 FastGPT 账号密码,验证码为:${code}</div>`
}
};
export const sendCode = (email: string, code: string, type: `${EmailTypeEnum}`) => {
return new Promise((resolve, reject) => {
const options = {
from: `"FastGPT" ${myEmail}`,
to: email,
subject: emailMap[type]?.subject,
html: emailMap[type]?.html(code)
};
mailTransport.sendMail(options, function (err, msg) {
if (err) {
console.log('send email error->', err);
reject('邮箱异常');
} else {
resolve('');
}
});
});
};
export const sendTrainSucceed = (email: string, modelName: string) => {
return new Promise((resolve, reject) => {
const options = {
from: `"FastGPT" ${myEmail}`,
to: email,
subject: '模型训练完成通知',
html: `你的模型 ${modelName} 已于 ${dayjs().format('YYYY-MM-DD HH:mm')} 训练完成!`
};
mailTransport.sendMail(options, function (err, msg) {
if (err) {
console.log('send email error->', err);
reject('邮箱异常');
} else {
resolve('');
}
});
});
};

View File

@@ -0,0 +1,72 @@
import * as nodemailer from 'nodemailer';
import { UserAuthTypeEnum } from '@/constants/common';
import dayjs from 'dayjs';
import Dysmsapi, * as dysmsapi from '@alicloud/dysmsapi20170525';
// @ts-ignore
import * as OpenApi from '@alicloud/openapi-client';
// @ts-ignore
import * as Util from '@alicloud/tea-util';
const myEmail = process.env.MY_MAIL;
const mailTransport = nodemailer.createTransport({
// host: 'smtp.qq.phone',
service: 'qq',
secure: true, //安全方式发送,建议都加上
auth: {
user: myEmail,
pass: process.env.MAILE_CODE
}
});
const emailMap: { [key: string]: any } = {
[UserAuthTypeEnum.register]: {
subject: '注册 FastGPT 账号',
html: (code: string) => `<div>您正在注册 FastGPT 账号,验证码为:${code}</div>`
},
[UserAuthTypeEnum.findPassword]: {
subject: '修改 FastGPT 密码',
html: (code: string) => `<div>您正在修改 FastGPT 账号密码,验证码为:${code}</div>`
}
};
export const sendEmailCode = (email: string, code: string, type: `${UserAuthTypeEnum}`) => {
return new Promise((resolve, reject) => {
const options = {
from: `"FastGPT" ${myEmail}`,
to: email,
subject: emailMap[type]?.subject,
html: emailMap[type]?.html(code)
};
mailTransport.sendMail(options, function (err, msg) {
if (err) {
console.log('send email error->', err);
reject('发生邮件异常');
} else {
resolve('');
}
});
});
};
export const sendPhoneCode = async (phone: string, code: string) => {
const accessKeyId = process.env.aliAccessKeyId;
const accessKeySecret = process.env.aliAccessKeySecret;
const signName = process.env.aliSignName;
const templateCode = process.env.aliTemplateCode;
const endpoint = 'dysmsapi.aliyuncs.com';
const sendSmsRequest = new dysmsapi.SendSmsRequest({
phoneNumbers: phone,
signName,
templateCode,
templateParam: `{"code":${code}}`
});
const config = new OpenApi.Config({ accessKeyId, accessKeySecret, endpoint });
const client = new Dysmsapi(config);
const runtime = new Util.RuntimeOptions({});
const res = await client.sendSmsWithOptions(sendSmsRequest, runtime);
if (res.body.code !== 'OK') {
return Promise.reject(res.body.message || '发送短信失败');
}
};

View File

@@ -11,7 +11,7 @@ export type ServiceName = 'openai';
export interface UserModelSchema {
_id: string;
email: string;
username: string;
password: string;
balance: number;
openaiKey: string;
@@ -20,7 +20,7 @@ export interface UserModelSchema {
export interface AuthCodeSchema {
_id: string;
email: string;
username: string;
code: string;
type: 'register' | 'findPassword';
expiredTime: number;

7
src/types/user.d.ts vendored
View File

@@ -1,11 +1,6 @@
export enum UserNumberEnum {
phone = 'phone',
wx = 'wx'
}
export interface UserType {
_id: string;
email: string;
username: string;
openaiKey: string;
balance: number;
}