前端发送邮箱部分
This commit is contained in:
parent
bdb986c468
commit
f121dca589
@ -9,6 +9,7 @@ import Layout from '@/views/Layout'
|
||||
import UserAdmin from "@/views/admin/UserAdmin";
|
||||
import NeedLogin from "@/components/NeedLogin";
|
||||
import Index from "@/views/Index";
|
||||
import SignUpEmail from "@/views/SignUpEmail";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{ path: "*", Component: Root },
|
||||
@ -23,6 +24,7 @@ function Root() {
|
||||
<Route path="/*" element={<p>404</p>} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/register_email" element={<SignUpEmail />} />
|
||||
|
||||
<Route element={<NeedLogin><Outlet /></NeedLogin>}>
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
|
@ -117,4 +117,15 @@ export async function editUser(u: EditUser, token: string, uid: string) {
|
||||
body: JSON.stringify(u)
|
||||
})
|
||||
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)
|
||||
}
|
@ -40,6 +40,8 @@ export interface ApiConfig {
|
||||
captcha: captcha
|
||||
AllowChangeName: boolean
|
||||
serverName: string
|
||||
NeedEmail: boolean
|
||||
AllowDomain: string[]
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
|
@ -64,6 +64,9 @@ export default function SignIn() {
|
||||
console.warn(v)
|
||||
if (v instanceof ApiErr) {
|
||||
switch (v.code) {
|
||||
case 10:
|
||||
setErr("验证码错误")
|
||||
return
|
||||
case 6:
|
||||
setErr("密码或用户名错误")
|
||||
return
|
||||
|
@ -69,6 +69,9 @@ export default function SignUp() {
|
||||
console.warn(v)
|
||||
if (v instanceof ApiErr) {
|
||||
switch (v.code) {
|
||||
case 10:
|
||||
setRegErr("验证码错误")
|
||||
return
|
||||
case 3:
|
||||
setRegErr("邮箱已存在")
|
||||
return
|
||||
|
176
frontend/src/views/SignUpEmail.tsx
Normal file
176
frontend/src/views/SignUpEmail.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -63,6 +63,8 @@ type Config struct {
|
||||
Captcha Captcha `json:"captcha"`
|
||||
AllowChangeName bool
|
||||
ServerName string `json:"serverName"`
|
||||
NeedEmail bool
|
||||
AllowDomain []string
|
||||
}
|
||||
|
||||
type EditUser struct {
|
||||
|
@ -81,6 +81,7 @@ func newSkinApi(handel *handle.Handel, userHandel *handle.UserHandel, adminHande
|
||||
r.Post("/user/reg", userHandel.Reg())
|
||||
r.Post("/user/login", userHandel.Login())
|
||||
r.Get("/config", handel.GetConfig())
|
||||
r.Post("/user/reg_email", userHandel.SendRegEmail())
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(adminHandel.NeedAuth)
|
||||
|
@ -63,9 +63,15 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func()
|
||||
handleError := handelerror.NewHandleError(logger)
|
||||
httpClient := ProvideHttpClient()
|
||||
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)
|
||||
userHandel := handle.NewUserHandel(handleError, validate, userSerice, logger, textureService)
|
||||
userHandel := handle.NewUserHandel(handleError, validate, userService, logger, textureService)
|
||||
adminService := service.NewAdminService(authService, client, c, cache)
|
||||
adminHandel := handle.NewAdminHandel(handleError, adminService, validate)
|
||||
httpHandler := route.NewRoute(yggdrasil3, handel, c, handler, userHandel, adminHandel)
|
||||
|
@ -52,16 +52,23 @@ func NewEmail(pri *rsa.PrivateKey, c config.Config, cache cache.Cache) (*EmailSe
|
||||
}, 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))
|
||||
return e.emailConfig[i]
|
||||
return e.emailConfig[i], nil
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
err := m.From(u.Name)
|
||||
err = m.From(u.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SendRegVerify: %w", err)
|
||||
}
|
||||
@ -114,7 +121,7 @@ func (e EmailService) SendVerifyUrl(ctx context.Context, email string, interval
|
||||
u := url.URL{
|
||||
Host: host,
|
||||
Scheme: "http",
|
||||
Path: "/test?" + code,
|
||||
Path: "/register?code=" + url.QueryEscape(code),
|
||||
}
|
||||
|
||||
if e.config.WebBaseUrl != "" {
|
||||
|
@ -258,7 +258,7 @@ func (w *UserService) SendRegEmail(ctx context.Context, email, CaptchaToken, hos
|
||||
|
||||
err = w.emailService.SendVerifyUrl(ctx, email, 60, host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SendRegEmail: %w", ErrNotAllowDomain)
|
||||
return fmt.Errorf("SendRegEmail: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -35,5 +35,7 @@ func (w *WebService) GetConfig(ctx context.Context) model.Config {
|
||||
},
|
||||
ServerName: w.config.ServerName,
|
||||
AllowChangeName: !w.config.OfflineUUID,
|
||||
NeedEmail: w.config.Email.Enable,
|
||||
AllowDomain: w.config.Email.AllowDomain,
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user