前端发送邮箱部分

This commit is contained in:
xmdhs 2023-11-24 22:27:31 +08:00
parent bdb986c468
commit f121dca589
No known key found for this signature in database
GPG Key ID: E809D6D43DEFCC95
12 changed files with 223 additions and 8 deletions

View File

@ -9,6 +9,7 @@ import Layout from '@/views/Layout'
import UserAdmin from "@/views/admin/UserAdmin"; import UserAdmin from "@/views/admin/UserAdmin";
import NeedLogin from "@/components/NeedLogin"; import NeedLogin from "@/components/NeedLogin";
import Index from "@/views/Index"; import Index from "@/views/Index";
import SignUpEmail from "@/views/SignUpEmail";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ path: "*", Component: Root }, { path: "*", Component: Root },
@ -23,6 +24,7 @@ function Root() {
<Route path="/*" element={<p>404</p>} /> <Route path="/*" element={<p>404</p>} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} /> <Route path="/register" element={<Register />} />
<Route path="/register_email" element={<SignUpEmail />} />
<Route element={<NeedLogin><Outlet /></NeedLogin>}> <Route element={<NeedLogin><Outlet /></NeedLogin>}>
<Route path="/profile" element={<Profile />} /> <Route path="/profile" element={<Profile />} />

View File

@ -118,3 +118,14 @@ export async function editUser(u: EditUser, token: string, uid: string) {
}) })
return await apiGet<unknown>(r) return await apiGet<unknown>(r)
} }
export async function sendRegEmail(email: string, captchaToken: string) {
const r = await fetch(root() + "/api/v1/user/reg_email", {
method: "POST",
body: JSON.stringify({
"email": email,
"captchaToken": captchaToken
})
})
return await apiGet<unknown>(r)
}

View File

@ -40,6 +40,8 @@ export interface ApiConfig {
captcha: captcha captcha: captcha
AllowChangeName: boolean AllowChangeName: boolean
serverName: string serverName: string
NeedEmail: boolean
AllowDomain: string[]
} }
export interface UserInfo { export interface UserInfo {

View File

@ -64,6 +64,9 @@ export default function SignIn() {
console.warn(v) console.warn(v)
if (v instanceof ApiErr) { if (v instanceof ApiErr) {
switch (v.code) { switch (v.code) {
case 10:
setErr("验证码错误")
return
case 6: case 6:
setErr("密码或用户名错误") setErr("密码或用户名错误")
return return

View File

@ -69,6 +69,9 @@ export default function SignUp() {
console.warn(v) console.warn(v)
if (v instanceof ApiErr) { if (v instanceof ApiErr) {
switch (v.code) { switch (v.code) {
case 10:
setRegErr("验证码错误")
return
case 3: case 3:
setRegErr("邮箱已存在") setRegErr("邮箱已存在")
return return

View File

@ -0,0 +1,176 @@
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import CssBaseline from '@mui/material/CssBaseline';
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import TextField from '@mui/material/TextField';
import { useRequest, useTitle } from 'ahooks';
import { getConfig, sendRegEmail } from '@/apis/apis';
import { useEffect, useRef, useState } from 'react';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import CaptchaWidget from '@/components/CaptchaWidget';
import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget'
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import { useNavigate } from "react-router-dom";
import { ApiErr } from '@/apis/error';
import Loading from '@/components/Loading';
export default function SignUpEmail() {
const [err, setErr] = useState("");
const [domain, setDomain] = useState("");
const [email, setEmail] = useState("")
const captchaRef = useRef<CaptchaWidgetRef>(null)
const [captchaToken, setCaptchaToken] = useState("");
const [open, setOpen] = useState(false);
useTitle("注册")
const navigate = useNavigate();
const [helperText, setHelperText] = useState("")
const [loading, setLoading] = useState(false);
const server = useRequest(getConfig, {
cacheKey: "/api/v1/config",
staleTime: 60000,
onError: e => {
console.warn(e)
setErr(String(e))
}
})
useEffect(() => {
if (server.data?.AllowDomain.length != 0) {
setDomain(server.data?.AllowDomain[0] ?? "")
}
}, [server.data?.AllowDomain])
const emailonChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setEmail(e.target.value)
if (e.target.value == "") {
setHelperText("邮箱不得为空")
}
setHelperText("")
}
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (email == "") {
setHelperText("邮箱不得为空")
}
const sendEmail = (() => {
if (domain != "") {
return `${email}@${domain}`
}
return email
})()
if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(sendEmail)){
setHelperText("邮箱格式错误")
return
}
if (server.data?.captcha.type != "" && captchaToken == "") {
return
}
setLoading(true)
sendRegEmail(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => {
captchaRef.current?.reload()
console.warn(e)
if (e instanceof ApiErr) {
switch (e.code) {
case 10:
setErr("验证码错误")
return
case 11:
setErr("暂时无法对此邮箱发送邮件")
return
}
}
setErr(String(e))
}).finally(() => setLoading(false))
}
const handleClose = () => {
navigate("/")
}
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
</Typography>
<Box component="form" noValidate onSubmit={onSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}>
<Grid item xs={12} sx={{ display: 'grid', columnGap: '3px', gridTemplateColumns: "1fr auto" }}>
<TextField fullWidth
required
name="email"
label="邮箱"
value={email}
helperText={helperText}
error={helperText != ""}
onChange={emailonChange}
/>
{
server.data?.AllowDomain.length != 0 &&
<FormControl>
<InputLabel></InputLabel>
<Select label="域名" value={domain} onChange={v => setDomain(v.target.value)}>
{server.data?.AllowDomain.map(v => <MenuItem value={v}>@{v}</MenuItem>)}
</Select>
</FormControl>
}
</Grid>
<Grid item xs={12}>
<CaptchaWidget ref={captchaRef} onSuccess={setCaptchaToken} />
</Grid>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
</Button>
</Box>
</Box>
<Snackbar anchorOrigin={{ vertical: 'top', horizontal: 'center' }} open={err !== ""}>
<Alert onClose={() => setErr("")} severity="error">{err}</Alert>
</Snackbar>
<Dialog open={open}>
<DialogTitle></DialogTitle>
<DialogContent>
<Typography></Typography>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}></Button>
</DialogActions>
</Dialog>
{loading && <Loading />}
</Container>
)
}

View File

@ -63,6 +63,8 @@ type Config struct {
Captcha Captcha `json:"captcha"` Captcha Captcha `json:"captcha"`
AllowChangeName bool AllowChangeName bool
ServerName string `json:"serverName"` ServerName string `json:"serverName"`
NeedEmail bool
AllowDomain []string
} }
type EditUser struct { type EditUser struct {

View File

@ -81,6 +81,7 @@ func newSkinApi(handel *handle.Handel, userHandel *handle.UserHandel, adminHande
r.Post("/user/reg", userHandel.Reg()) r.Post("/user/reg", userHandel.Reg())
r.Post("/user/login", userHandel.Login()) r.Post("/user/login", userHandel.Login())
r.Get("/config", handel.GetConfig()) r.Get("/config", handel.GetConfig())
r.Post("/user/reg_email", userHandel.SendRegEmail())
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
r.Use(adminHandel.NeedAuth) r.Use(adminHandel.NeedAuth)

View File

@ -63,9 +63,15 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func()
handleError := handelerror.NewHandleError(logger) handleError := handelerror.NewHandleError(logger)
httpClient := ProvideHttpClient() httpClient := ProvideHttpClient()
captchaService := captcha.NewCaptchaService(c, httpClient) captchaService := captcha.NewCaptchaService(c, httpClient)
userSerice := service.NewUserSerice(c, client, captchaService, authService, cache) emailService, err := email.NewEmail(privateKey, c, cache)
if err != nil {
cleanup2()
cleanup()
return nil, nil, err
}
userService := service.NewUserSerice(c, client, captchaService, authService, cache, emailService)
textureService := service.NewTextureService(client, c, cache) textureService := service.NewTextureService(client, c, cache)
userHandel := handle.NewUserHandel(handleError, validate, userSerice, logger, textureService) userHandel := handle.NewUserHandel(handleError, validate, userService, logger, textureService)
adminService := service.NewAdminService(authService, client, c, cache) adminService := service.NewAdminService(authService, client, c, cache)
adminHandel := handle.NewAdminHandel(handleError, adminService, validate) adminHandel := handle.NewAdminHandel(handleError, adminService, validate)
httpHandler := route.NewRoute(yggdrasil3, handel, c, handler, userHandel, adminHandel) httpHandler := route.NewRoute(yggdrasil3, handel, c, handler, userHandel, adminHandel)

View File

@ -52,16 +52,23 @@ func NewEmail(pri *rsa.PrivateKey, c config.Config, cache cache.Cache) (*EmailSe
}, nil }, nil
} }
func (e EmailService) getRandEmailUser() EmailConfig { func (e EmailService) getRandEmailUser() (EmailConfig, error) {
if len(e.emailConfig) == 0 {
return EmailConfig{}, fmt.Errorf("没有可用的邮箱账号")
}
i := rand.Intn(len(e.emailConfig)) i := rand.Intn(len(e.emailConfig))
return e.emailConfig[i] return e.emailConfig[i], nil
} }
func (e EmailService) SendEmail(ctx context.Context, to string, subject, body string) error { func (e EmailService) SendEmail(ctx context.Context, to string, subject, body string) error {
u := e.getRandEmailUser() u, err := e.getRandEmailUser()
if err != nil {
return fmt.Errorf("SendRegVerify: %w", err)
}
m := mail.NewMsg() m := mail.NewMsg()
err := m.From(u.Name) err = m.From(u.Name)
if err != nil { if err != nil {
return fmt.Errorf("SendRegVerify: %w", err) return fmt.Errorf("SendRegVerify: %w", err)
} }
@ -114,7 +121,7 @@ func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval
u := url.URL{ u := url.URL{
Host: host, Host: host,
Scheme: "http", Scheme: "http",
Path: "/test?" + code, Path: "/register?code=" + url.QueryEscape(code),
} }
if e.config.WebBaseUrl != "" { if e.config.WebBaseUrl != "" {

View File

@ -258,7 +258,7 @@ func (w *UserService) SendRegEmail(ctx context.Context, email, CaptchaToken, hos
err = w.emailService.SendVerifyUrl(ctx, email, 60, host) err = w.emailService.SendVerifyUrl(ctx, email, 60, host)
if err != nil { if err != nil {
return fmt.Errorf("SendRegEmail: %w", ErrNotAllowDomain) return fmt.Errorf("SendRegEmail: %w", err)
} }
return nil return nil
} }

View File

@ -35,5 +35,7 @@ func (w *WebService) GetConfig(ctx context.Context) model.Config {
}, },
ServerName: w.config.ServerName, ServerName: w.config.ServerName,
AllowChangeName: !w.config.OfflineUUID, AllowChangeName: !w.config.OfflineUUID,
NeedEmail: w.config.Email.Enable,
AllowDomain: w.config.Email.AllowDomain,
} }
} }