mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 11:06:23 +00:00
feat: add email verification and password reset functionality
- Introduced environment variables for database and email configurations. - Implemented email verification code generation and validation. - Added password reset feature with email verification. - Updated user registration and profile management APIs. - Refactored user security settings to include email and password updates. - Enhanced console layout with internationalization support. - Removed deprecated settings page and integrated global settings. - Added new reset password page and form components. - Updated localization files for new features and translations.
This commit is contained in:
@ -1,129 +1,129 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"context"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/snowykami/neo-blog/internal/ctxutils"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/internal/repo"
|
||||
"github.com/snowykami/neo-blog/pkg/filedriver"
|
||||
"github.com/snowykami/neo-blog/pkg/resps"
|
||||
"github.com/snowykami/neo-blog/pkg/utils"
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/snowykami/neo-blog/internal/ctxutils"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/internal/repo"
|
||||
"github.com/snowykami/neo-blog/pkg/filedriver"
|
||||
"github.com/snowykami/neo-blog/pkg/resps"
|
||||
"github.com/snowykami/neo-blog/pkg/utils"
|
||||
)
|
||||
|
||||
type FileController struct{}
|
||||
|
||||
func NewFileController() *FileController {
|
||||
return &FileController{}
|
||||
return &FileController{}
|
||||
}
|
||||
|
||||
func (f *FileController) UploadFileStream(ctx context.Context, c *app.RequestContext) {
|
||||
// 获取文件信息
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
logrus.Error("无法读取文件: ", err)
|
||||
resps.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
// 获取文件信息
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
logrus.Error("无法读取文件: ", err)
|
||||
resps.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
group := string(c.FormValue("group"))
|
||||
name := string(c.FormValue("name"))
|
||||
group := string(c.FormValue("group"))
|
||||
name := string(c.FormValue("name"))
|
||||
|
||||
// 初始化文件驱动
|
||||
driver, err := filedriver.GetFileDriver(filedriver.GetWebdavDriverConfig())
|
||||
if err != nil {
|
||||
logrus.Error("获取文件驱动失败: ", err)
|
||||
resps.InternalServerError(c, "获取文件驱动失败")
|
||||
return
|
||||
}
|
||||
// 初始化文件驱动
|
||||
driver, err := filedriver.GetFileDriver(filedriver.GetWebdavDriverConfig())
|
||||
if err != nil {
|
||||
logrus.Error("获取文件驱动失败: ", err)
|
||||
resps.InternalServerError(c, "获取文件驱动失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 校验文件哈希
|
||||
if hashForm := string(c.FormValue("hash")); hashForm != "" {
|
||||
dir, fileName := utils.FilePath(hashForm)
|
||||
storagePath := filepath.Join(dir, fileName)
|
||||
if _, err := driver.Stat(c, storagePath); err == nil {
|
||||
resps.Ok(c, "文件已存在", map[string]any{"hash": hashForm})
|
||||
return
|
||||
}
|
||||
}
|
||||
// 校验文件哈希
|
||||
if hashForm := string(c.FormValue("hash")); hashForm != "" {
|
||||
dir, fileName := utils.FilePath(hashForm)
|
||||
storagePath := filepath.Join(dir, fileName)
|
||||
if _, err := driver.Stat(c, storagePath); err == nil {
|
||||
resps.Ok(c, "文件已存在", map[string]any{"hash": hashForm})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 打开文件
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
logrus.Error("无法打开文件: ", err)
|
||||
resps.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
// 打开文件
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
logrus.Error("无法打开文件: ", err)
|
||||
resps.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// 计算文件哈希值
|
||||
hash, err := utils.FileHashFromStream(src)
|
||||
if err != nil {
|
||||
logrus.Error("计算文件哈希失败: ", err)
|
||||
resps.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
// 计算文件哈希值
|
||||
hash, err := utils.FileHashFromStream(src)
|
||||
if err != nil {
|
||||
logrus.Error("计算文件哈希失败: ", err)
|
||||
resps.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 根据哈希值生成存储路径
|
||||
dir, fileName := utils.FilePath(hash)
|
||||
storagePath := filepath.Join(dir, fileName)
|
||||
// 保存文件
|
||||
if _, err := src.Seek(0, io.SeekStart); err != nil {
|
||||
logrus.Error("无法重置文件流位置: ", err)
|
||||
resps.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := driver.Save(c, storagePath, src); err != nil {
|
||||
logrus.Error("保存文件失败: ", err)
|
||||
resps.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
// 数据库索引建立
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
resps.InternalServerError(c, "获取当前用户失败")
|
||||
return
|
||||
}
|
||||
fileModel := &model.File{
|
||||
Hash: hash,
|
||||
UserID: currentUser.ID,
|
||||
Group: group,
|
||||
Name: name,
|
||||
}
|
||||
// 根据哈希值生成存储路径
|
||||
dir, fileName := utils.FilePath(hash)
|
||||
storagePath := filepath.Join(dir, fileName)
|
||||
// 保存文件
|
||||
if _, err := src.Seek(0, io.SeekStart); err != nil {
|
||||
logrus.Error("无法重置文件流位置: ", err)
|
||||
resps.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := driver.Save(c, storagePath, src); err != nil {
|
||||
logrus.Error("保存文件失败: ", err)
|
||||
resps.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
// 数据库索引建立
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
resps.InternalServerError(c, "获取当前用户失败")
|
||||
return
|
||||
}
|
||||
fileModel := &model.File{
|
||||
Hash: hash,
|
||||
UserID: currentUser.ID,
|
||||
Group: group,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
if err := repo.File.Create(fileModel); err != nil {
|
||||
logrus.Error("数据库索引建立失败: ", err)
|
||||
resps.InternalServerError(c, "数据库索引建立失败")
|
||||
return
|
||||
}
|
||||
resps.Ok(c, "文件上传成功", map[string]any{"hash": hash, "id": fileModel.ID})
|
||||
if err := repo.File.Create(fileModel); err != nil {
|
||||
logrus.Error("数据库索引建立失败: ", err)
|
||||
resps.InternalServerError(c, "数据库索引建立失败")
|
||||
return
|
||||
}
|
||||
resps.Ok(c, "文件上传成功", map[string]any{"hash": hash, "id": fileModel.ID})
|
||||
}
|
||||
|
||||
func (f *FileController) GetFile(ctx context.Context, c *app.RequestContext) {
|
||||
fileIdString := c.Param("id")
|
||||
fileId, err := strconv.ParseUint(fileIdString, 10, 64)
|
||||
if err != nil {
|
||||
logrus.Error("无效的文件ID: ", err)
|
||||
resps.BadRequest(c, "无效的文件ID")
|
||||
return
|
||||
}
|
||||
fileModel, err := repo.File.GetByID(uint(fileId))
|
||||
if err != nil {
|
||||
logrus.Error("获取文件信息失败: ", err)
|
||||
resps.InternalServerError(c, "获取文件信息失败")
|
||||
return
|
||||
}
|
||||
driver, err := filedriver.GetFileDriver(filedriver.GetWebdavDriverConfig())
|
||||
if err != nil {
|
||||
logrus.Error("获取文件驱动失败: ", err)
|
||||
resps.InternalServerError(c, "获取文件驱动失败")
|
||||
return
|
||||
}
|
||||
filePath := filepath.Join(utils.FilePath(fileModel.Hash))
|
||||
driver.Get(c, filePath)
|
||||
fileIdString := c.Param("id")
|
||||
fileId, err := strconv.ParseUint(fileIdString, 10, 64)
|
||||
if err != nil {
|
||||
logrus.Error("无效的文件ID: ", err)
|
||||
resps.BadRequest(c, "无效的文件ID")
|
||||
return
|
||||
}
|
||||
fileModel, err := repo.File.GetByID(uint(fileId))
|
||||
if err != nil {
|
||||
logrus.Error("获取文件信息失败: ", err)
|
||||
resps.InternalServerError(c, "获取文件信息失败")
|
||||
return
|
||||
}
|
||||
driver, err := filedriver.GetFileDriver(filedriver.GetWebdavDriverConfig())
|
||||
if err != nil {
|
||||
logrus.Error("获取文件驱动失败: ", err)
|
||||
resps.InternalServerError(c, "获取文件驱动失败")
|
||||
return
|
||||
}
|
||||
filePath := filepath.Join(utils.FilePath(fileModel.Hash))
|
||||
driver.Get(c, filePath)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package v1
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/cloudwego/hertz/pkg/common/utils"
|
||||
@ -184,6 +185,9 @@ func (u *UserController) VerifyEmail(ctx context.Context, c *app.RequestContext)
|
||||
resps.BadRequest(c, resps.ErrParamInvalid)
|
||||
return
|
||||
}
|
||||
if verifyEmailReq.Email == "" {
|
||||
resps.BadRequest(c, resps.ErrParamInvalid)
|
||||
}
|
||||
resp, err := u.service.RequestVerifyEmail(&verifyEmailReq)
|
||||
if err != nil {
|
||||
serviceErr := errs.AsServiceError(err)
|
||||
@ -194,11 +198,63 @@ func (u *UserController) VerifyEmail(ctx context.Context, c *app.RequestContext)
|
||||
}
|
||||
|
||||
func (u *UserController) ChangePassword(ctx context.Context, c *app.RequestContext) {
|
||||
// TODO: 实现修改密码功能
|
||||
var updatePasswordReq dto.UpdatePasswordReq
|
||||
if err := c.BindAndValidate(&updatePasswordReq); err != nil {
|
||||
resps.BadRequest(c, resps.ErrParamInvalid)
|
||||
return
|
||||
}
|
||||
ok, err := u.service.UpdatePassword(ctx, &updatePasswordReq)
|
||||
if err != nil {
|
||||
resps.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
resps.BadRequest(c, "Failed to change password")
|
||||
return
|
||||
}
|
||||
resps.Ok(c, resps.Success, nil)
|
||||
}
|
||||
|
||||
func (u *UserController) ResetPassword(ctx context.Context, c *app.RequestContext) {
|
||||
var resetPasswordReq dto.ResetPasswordReq
|
||||
if err := c.BindAndValidate(&resetPasswordReq); err != nil {
|
||||
resps.BadRequest(c, resps.ErrParamInvalid)
|
||||
return
|
||||
}
|
||||
email := strings.TrimSpace(string(c.GetHeader(constant.HeaderKeyEmail)))
|
||||
if email == "" {
|
||||
resps.BadRequest(c, "Email header is required")
|
||||
return
|
||||
}
|
||||
resetPasswordReq.Email = email
|
||||
ok, err := u.service.ResetPassword(&resetPasswordReq)
|
||||
if err != nil {
|
||||
resps.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
resps.BadRequest(c, "Failed to reset password")
|
||||
return
|
||||
}
|
||||
resps.Ok(c, resps.Success, nil)
|
||||
}
|
||||
|
||||
func (u *UserController) ChangeEmail(ctx context.Context, c *app.RequestContext) {
|
||||
// TODO: 实现修改邮箱功能
|
||||
email := strings.TrimSpace(string(c.GetHeader(constant.HeaderKeyEmail)))
|
||||
if email == "" {
|
||||
resps.BadRequest(c, "Email header is required")
|
||||
return
|
||||
}
|
||||
ok, err := u.service.UpdateEmail(ctx, email)
|
||||
if err != nil {
|
||||
resps.InternalServerError(c, err.Error())
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
resps.BadRequest(c, "Failed to change email")
|
||||
return
|
||||
}
|
||||
resps.Ok(c, resps.Success, nil)
|
||||
}
|
||||
|
||||
func (u *UserController) GetCaptchaConfig(ctx context.Context, c *app.RequestContext) {
|
||||
|
Reference in New Issue
Block a user