mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 11:06:23 +00:00
feat: add new color themes and styles for rose, violet, and yellow
- Introduced new CSS files for rose, violet, and yellow themes with custom color variables. - Implemented dark mode styles for each theme. - Created a color data structure to manage theme colors in the console settings. feat: implement image cropper component - Added an image cropper component for user profile picture editing. - Integrated the image cropper into the user profile page. feat: enhance console sidebar with user permissions - Defined sidebar items with permission checks for admin and editor roles. - Updated user center navigation to reflect user permissions. feat: add user profile and security settings - Developed user profile page with avatar upload and editing functionality. - Implemented user security settings for password and email verification. feat: create reusable dialog and OTP input components - Built a dialog component for modal interactions. - Developed an OTP input component for email verification. fix: improve file handling utilities - Added utility functions for file URI generation. - Implemented permission checks for user roles in the common utilities.
This commit is contained in:
@ -1,57 +1,69 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
CaptchaTypeDisable = "disable" // 禁用验证码
|
||||
CaptchaTypeHCaptcha = "hcaptcha" // HCaptcha验证码
|
||||
CaptchaTypeTurnstile = "turnstile" // Turnstile验证码
|
||||
CaptchaTypeReCaptcha = "recaptcha" // ReCaptcha验证码
|
||||
ContextKeyUserID = "user_id" // 上下文键:用户ID
|
||||
ContextKeyRemoteAddr = "remote_addr" // 上下文键:远程地址
|
||||
ContextKeyUserAgent = "user_agent" // 上下文键:用户代理
|
||||
ModeDev = "dev"
|
||||
ModeProd = "prod"
|
||||
RoleUser = "user" // 普通用户 仅有阅读和评论权限
|
||||
RoleEditor = "editor" // 能够发布和管理自己内容的用户
|
||||
RoleAdmin = "admin"
|
||||
EnvKeyBaseUrl = "BASE_URL" // 环境变量:基础URL
|
||||
EnvKeyCaptchaProvider = "CAPTCHA_PROVIDER" // captcha提供者
|
||||
EnvKeyCaptchaSecreteKey = "CAPTCHA_SECRET_KEY" // captcha站点密钥
|
||||
EnvKeyCaptchaUrl = "CAPTCHA_URL" // 某些自托管的captcha的url
|
||||
EnvKeyCaptchaSiteKey = "CAPTCHA_SITE_KEY" // captcha密钥key
|
||||
EnvKeyLocationFormat = "LOCATION_FORMAT" // 环境变量:时区格式
|
||||
EnvKeyLogLevel = "LOG_LEVEL" // 环境变量:日志级别
|
||||
EnvKeyMode = "MODE" // 环境变量:运行模式
|
||||
EnvKeyJwtSecrete = "JWT_SECRET" // 环境变量:JWT密钥
|
||||
EnvKeyPasswordSalt = "PASSWORD_SALT" // 环境变量:密码盐
|
||||
EnvKeyTokenDuration = "TOKEN_DURATION" // 环境变量:令牌有效期
|
||||
EnvKeyMaxReplyDepth = "MAX_REPLY_DEPTH" // 环境变量:最大回复深度
|
||||
EnvKeyTokenDurationDefault = 30 // Token有效时长
|
||||
EnvKeyRefreshTokenDurationDefault = 6000000 // refresh token有效时长
|
||||
EnvKeyRefreshTokenDuration = "REFRESH_TOKEN_DURATION" // 环境变量:刷新令牌有效期
|
||||
EnvKeyRefreshTokenDurationWithRemember = "REFRESH_TOKEN_DURATION_WITH_REMEMBER" // 环境变量:记住我刷新令牌有效期
|
||||
KVKeyEmailVerificationCode = "email_verification_code:" // KV存储:邮箱验证码
|
||||
KVKeyOidcState = "oidc_state:" // KV存储:OIDC状态
|
||||
ApiSuffix = "/api/v1" // API版本前缀
|
||||
OidcUri = "/user/oidc/login" // OIDC登录URI
|
||||
OidcProviderTypeMisskey = "misskey" // OIDC提供者类型:Misskey
|
||||
OidcProviderTypeOauth2 = "oauth2" // OIDC提供者类型:GitHub
|
||||
DefaultBaseUrl = "http://localhost:3000" // 默认BaseUrl
|
||||
TargetTypePost = "post"
|
||||
TargetTypeComment = "comment"
|
||||
OrderByCreatedAt = "created_at" // 按创建时间排序
|
||||
OrderByUpdatedAt = "updated_at" // 按更新时间排序
|
||||
OrderByLikeCount = "like_count" // 按点赞数排序
|
||||
OrderByCommentCount = "comment_count" // 按评论数排序
|
||||
OrderByViewCount = "view_count" // 按浏览量排序
|
||||
OrderByHeat = "heat"
|
||||
MaxReplyDepthDefault = 3 // 默认最大回复深度
|
||||
HeatFactorViewWeight = 1 // 热度因子:浏览量权重
|
||||
HeatFactorLikeWeight = 5 // 热度因子:点赞权重
|
||||
HeatFactorCommentWeight = 10 // 热度因子:评论权重
|
||||
PageLimitDefault = 20 // 默认分页大小
|
||||
CaptchaTypeDisable = "disable" // 禁用验证码
|
||||
CaptchaTypeHCaptcha = "hcaptcha" // HCaptcha验证码
|
||||
CaptchaTypeTurnstile = "turnstile" // Turnstile验证码
|
||||
CaptchaTypeReCaptcha = "recaptcha" // ReCaptcha验证码
|
||||
ContextKeyUserID = "user_id" // 上下文键:用户ID
|
||||
ContextKeyRemoteAddr = "remote_addr" // 上下文键:远程地址
|
||||
ContextKeyUserAgent = "user_agent" // 上下文键:用户代理
|
||||
ModeDev = "dev"
|
||||
ModeProd = "prod"
|
||||
RoleUser = "user" // 普通用户 仅有阅读和评论权限
|
||||
RoleEditor = "editor" // 能够发布和管理自己内容的用户
|
||||
RoleAdmin = "admin"
|
||||
DefaultFileBasePath = "./data/uploads"
|
||||
EnvKeyBaseUrl = "BASE_URL" // 环境变量:基础URL
|
||||
EnvKeyCaptchaProvider = "CAPTCHA_PROVIDER" // captcha提供者
|
||||
EnvKeyCaptchaSecreteKey = "CAPTCHA_SECRET_KEY" // captcha站点密钥
|
||||
EnvKeyCaptchaUrl = "CAPTCHA_URL" // 某些自托管的captcha的url
|
||||
EnvKeyCaptchaSiteKey = "CAPTCHA_SITE_KEY" // captcha密钥key
|
||||
EnvKeyFileDriverType = "FILE_DRIVER_TYPE"
|
||||
EnvKeyFileBasepath = "FILE_BASEPATH"
|
||||
EnvKeyFileWebdavUrl = "FILE_WEBDAV_URL"
|
||||
EnvKeyFileWebdavPassword = "FILE_WEBDAV_PASSWORD"
|
||||
EnvKeyFileWebdavPolicy = "FILE_WEBDAV_POLICY"
|
||||
EnvKeyFileWebdavUser = "FILE_WEBDAV_USER"
|
||||
EnvKeyLocationFormat = "LOCATION_FORMAT" // 环境变量:时区格式
|
||||
EnvKeyLogLevel = "LOG_LEVEL" // 环境变量:日志级别
|
||||
EnvKeyMode = "MODE" // 环境变量:运行模式
|
||||
EnvKeyJwtSecrete = "JWT_SECRET" // 环境变量:JWT密钥
|
||||
EnvKeyPasswordSalt = "PASSWORD_SALT" // 环境变量:密码盐
|
||||
EnvKeyTokenDuration = "TOKEN_DURATION" // 环境变量:令牌有效期
|
||||
EnvKeyMaxReplyDepth = "MAX_REPLY_DEPTH" // 环境变量:最大回复深度
|
||||
EnvKeyTokenDurationDefault = 500 // Token有效时长
|
||||
EnvKeyRefreshTokenDurationDefault = 6000000 // refresh token有效时长
|
||||
EnvKeyRefreshTokenDuration = "REFRESH_TOKEN_DURATION" // 环境变量:刷新令牌有效期
|
||||
EnvKeyRefreshTokenDurationWithRemember = "REFRESH_TOKEN_DURATION_WITH_REMEMBER" // 环境变量:记住我刷新令牌有效期
|
||||
FileDriverTypeLocal = "local"
|
||||
FileDriverTypeWebdav = "webdav"
|
||||
FileDriverTypeS3 = "s3"
|
||||
KVKeyEmailVerificationCode = "email_verification_code:" // KV存储:邮箱验证码
|
||||
KVKeyOidcState = "oidc_state:" // KV存储:OIDC状态
|
||||
ApiSuffix = "/api/v1" // API版本前缀
|
||||
OidcUri = "/user/oidc/login" // OIDC登录URI
|
||||
OidcProviderTypeMisskey = "misskey" // OIDC提供者类型:Misskey
|
||||
OidcProviderTypeOauth2 = "oauth2" // OIDC提供者类型:GitHub
|
||||
DefaultBaseUrl = "http://localhost:3000" // 默认BaseUrl
|
||||
TargetTypePost = "post"
|
||||
TargetTypeComment = "comment"
|
||||
WebdavPolicyProxy = "proxy"
|
||||
WebdavPolicyRedirect = "redirect"
|
||||
OrderByCreatedAt = "created_at" // 按创建时间排序
|
||||
OrderByUpdatedAt = "updated_at" // 按更新时间排序
|
||||
OrderByLikeCount = "like_count" // 按点赞数排序
|
||||
OrderByCommentCount = "comment_count" // 按评论数排序
|
||||
OrderByViewCount = "view_count" // 按浏览量排序
|
||||
OrderByHeat = "heat"
|
||||
MaxReplyDepthDefault = 3 // 默认最大回复深度
|
||||
HeatFactorViewWeight = 1 // 热度因子:浏览量权重
|
||||
HeatFactorLikeWeight = 5 // 热度因子:点赞权重
|
||||
HeatFactorCommentWeight = 10 // 热度因子:评论权重
|
||||
PageLimitDefault = 20 // 默认分页大小
|
||||
)
|
||||
|
||||
var (
|
||||
OrderByEnumPost = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByLikeCount, OrderByCommentCount, OrderByViewCount, OrderByHeat} // 帖子可用的排序方式
|
||||
OrderByEnumComment = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByCommentCount} // 评论可用的排序方式
|
||||
OrderByEnumPost = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByLikeCount, OrderByCommentCount, OrderByViewCount, OrderByHeat} // 帖子可用的排序方式
|
||||
OrderByEnumComment = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByCommentCount} // 评论可用的排序方式
|
||||
)
|
||||
|
@ -2,8 +2,9 @@ package errs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"net/http"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
)
|
||||
|
||||
// ServiceError 业务错误结构
|
||||
|
@ -2,10 +2,13 @@ package filedriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/LiteyukiStudio/spage/pkg/constants"
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"github.com/snowykami/neo-blog/pkg/utils"
|
||||
)
|
||||
|
||||
type FileDriver interface {
|
||||
@ -18,19 +21,30 @@ type FileDriver interface {
|
||||
}
|
||||
|
||||
type DriverConfig struct {
|
||||
Type string `mapstructure:"file.driver.type"`
|
||||
BasePath string `mapstructure:"file.driver.base_path"`
|
||||
WebDavUrl string `mapstructure:"file.driver.webdav.url"`
|
||||
WebDavUser string `mapstructure:"file.driver.webdav.user"`
|
||||
WebDavPassword string `mapstructure:"file.driver.webdav.password"`
|
||||
WebDavPolicy string `mapstructure:"file.driver.webdav.policy"` // proxy|redirect
|
||||
Type string
|
||||
BasePath string
|
||||
WebDavUrl string
|
||||
WebDavUser string
|
||||
WebDavPassword string
|
||||
WebDavPolicy string
|
||||
}
|
||||
|
||||
func GetWebdavDriverConfig() *DriverConfig {
|
||||
return &DriverConfig{
|
||||
Type: utils.Env.Get(constant.EnvKeyFileDriverType, constant.FileDriverTypeLocal),
|
||||
BasePath: utils.Env.Get(constant.EnvKeyFileBasepath, constant.DefaultFileBasePath),
|
||||
WebDavUrl: utils.Env.Get(constant.EnvKeyFileWebdavUrl),
|
||||
WebDavUser: utils.Env.Get(constant.EnvKeyFileWebdavUser),
|
||||
WebDavPassword: utils.Env.Get(constant.EnvKeyFileWebdavPassword),
|
||||
WebDavPolicy: utils.Env.Get(constant.EnvKeyFileWebdavPolicy),
|
||||
}
|
||||
}
|
||||
|
||||
func GetFileDriver(driverConfig *DriverConfig) (FileDriver, error) {
|
||||
switch driverConfig.Type {
|
||||
case constants.FileDriverLocal:
|
||||
case constant.FileDriverTypeLocal:
|
||||
return NewLocalDriver(driverConfig), nil
|
||||
case constants.FileDriverWebdav:
|
||||
case constant.FileDriverTypeWebdav:
|
||||
return NewWebDAVClientDriver(driverConfig), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported file driver type: %s", driverConfig.Type)
|
||||
|
@ -1,10 +1,11 @@
|
||||
package filedriver
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
)
|
||||
|
||||
type LocalDriver struct {
|
||||
|
@ -3,9 +3,11 @@ package filedriver
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/LiteyukiStudio/spage/pkg/constants"
|
||||
"github.com/LiteyukiStudio/spage/pkg/resps"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"github.com/snowykami/neo-blog/pkg/resps"
|
||||
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
@ -48,7 +50,7 @@ func (d *WebDAVClientDriver) Open(ctx *app.RequestContext, p string) (io.ReadClo
|
||||
}
|
||||
|
||||
func (d *WebDAVClientDriver) Get(ctx *app.RequestContext, p string) {
|
||||
if d.config.WebDavPolicy == constants.WebDavPolicyRedirect {
|
||||
if d.config.WebDavPolicy == constant.WebdavPolicyRedirect {
|
||||
ctx.Redirect(302, []byte(d.config.WebDavUrl+d.fullPath(p)))
|
||||
return
|
||||
} else {
|
||||
|
@ -1 +1,31 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
)
|
||||
|
||||
// FilePath 根据哈希值生成文件路径,前4位为目录位hash[0:4]/hash
|
||||
func FilePath(hash string) (dir, file string) {
|
||||
dir = hash[0:4]
|
||||
file = hash
|
||||
return
|
||||
}
|
||||
|
||||
func FileHashFromStream(file multipart.File) (string, error) {
|
||||
// 创建哈希计算器
|
||||
hash := sha256.New()
|
||||
|
||||
// 将文件流内容拷贝到哈希计算器
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 计算哈希值并转换为十六进制字符串
|
||||
hashInBytes := hash.Sum(nil)
|
||||
hashString := hex.EncodeToString(hashInBytes)
|
||||
|
||||
return hashString, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user