Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/frontend_theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

const FrontendThemeCookieName = "frontend_theme"
const FrontendThemeCookieMaxAge = 60 * 60 * 24 * 365
const FrontendThemeSessionKey = "frontend_theme"

func NormalizeFrontendTheme(theme string) string {
theme = strings.ToLower(strings.TrimSpace(theme))
Expand Down
190 changes: 102 additions & 88 deletions controller/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ func Login(c *gin.Context) {
// setup session & cookies and then return user info
func setupLogin(user *model.User, c *gin.Context) {
model.UpdateUserLastLoginAt(user.Id)
if theme := common.NormalizeFrontendTheme(user.GetSetting().FrontendTheme); theme != "" {
theme := common.NormalizeFrontendTheme(user.GetSetting().FrontendTheme)
if theme != "" {
common.SetFrontendThemeCookie(c, theme)
}
session := sessions.Default(c)
Expand All @@ -101,6 +102,11 @@ func setupLogin(user *model.User, c *gin.Context) {
session.Set("role", user.Role)
session.Set("status", user.Status)
session.Set("group", user.Group)
if theme != "" {
session.Set(common.FrontendThemeSessionKey, theme)
} else {
session.Delete(common.FrontendThemeSessionKey)
}
err := session.Save()
if err != nil {
common.ApiErrorI18n(c, i18n.MsgUserSessionSaveFailed)
Expand Down Expand Up @@ -392,6 +398,11 @@ func GetSelf(c *gin.Context) {
userSetting := user.GetSetting()
if theme := common.NormalizeFrontendTheme(userSetting.FrontendTheme); theme != "" {
common.SetFrontendThemeCookie(c, theme)
session := sessions.Default(c)
if common.Interface2String(session.Get(common.FrontendThemeSessionKey)) != theme {
session.Set(common.FrontendThemeSessionKey, theme)
_ = session.Save()
}
}

// 构建响应数据,包含用户信息和权限
Expand Down Expand Up @@ -636,133 +647,136 @@ func UpdateSelf(c *gin.Context) {
return
}

// 检查是否是用户设置更新请求 (sidebar_modules 或 language)
if sidebarModules, sidebarExists := requestData["sidebar_modules"]; sidebarExists {
userId := c.GetInt("id")
userId := c.GetInt("id")
settingChanged := false
updatedTheme := ""
var updatedSetting *dto.UserSetting

for _, key := range []string{"sidebar_modules", "frontend_theme", "language"} {
if _, ok := requestData[key]; ok {
settingChanged = true
break
}
}

if settingChanged {
user, err := model.GetUserById(userId, false)
if err != nil {
common.ApiError(c, err)
return
}

// 获取当前用户设置
currentSetting := user.GetSetting()

// 更新sidebar_modules字段
if sidebarModulesStr, ok := sidebarModules.(string); ok {
if sidebarModules, ok := requestData["sidebar_modules"]; ok {
sidebarModulesStr, valid := sidebarModules.(string)
if !valid {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
return
}
currentSetting.SidebarModules = sidebarModulesStr
}

// 保存更新后的设置
user.SetSetting(currentSetting)
if err := user.Update(false); err != nil {
common.ApiErrorI18n(c, i18n.MsgUpdateFailed)
return
if frontendTheme, ok := requestData["frontend_theme"]; ok {
themeStr, valid := frontendTheme.(string)
if !valid {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
return
}
themeStr = common.NormalizeFrontendTheme(themeStr)
if themeStr == "" {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
return
}
currentSetting.FrontendTheme = themeStr
updatedTheme = themeStr
}
common.ApiSuccessI18n(c, i18n.MsgUpdateSuccess, nil)
return

if language, ok := requestData["language"]; ok {
langStr, valid := language.(string)
if !valid {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
return
}
currentSetting.Language = langStr
}

updatedSetting = &currentSetting
}

// 检查是否是 UI 风格更新请求
if frontendTheme, themeExists := requestData["frontend_theme"]; themeExists {
userId := c.GetInt("id")
user, err := model.GetUserById(userId, false)
if err != nil {
common.ApiError(c, err)
return
hasProfileUpdate := false
for _, key := range []string{"username", "display_name", "password", "original_password"} {
if _, ok := requestData[key]; ok {
hasProfileUpdate = true
break
}
}

currentSetting := user.GetSetting()
themeStr, ok := frontendTheme.(string)
if !ok {
if hasProfileUpdate {
var user model.User
requestDataBytes, err := common.Marshal(requestData)
if err != nil {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
return
}
themeStr = strings.ToLower(strings.TrimSpace(themeStr))
if themeStr != "default" && themeStr != "classic" {
err = common.Unmarshal(requestDataBytes, &user)
if err != nil {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
return
}
currentSetting.FrontendTheme = themeStr

user.SetSetting(currentSetting)
if err := user.Update(false); err != nil {
common.ApiErrorI18n(c, i18n.MsgUpdateFailed)
if user.Password == "" {
user.Password = "$I_LOVE_U" // make Validator happy :)
}
if err := common.Validate.Struct(&user); err != nil {
common.ApiErrorI18n(c, i18n.MsgInvalidInput)
return
}
common.SetFrontendThemeCookie(c, themeStr)

common.ApiSuccessI18n(c, i18n.MsgUpdateSuccess, nil)
return
}

// 检查是否是语言偏好更新请求
if language, langExists := requestData["language"]; langExists {
userId := c.GetInt("id")
user, err := model.GetUserById(userId, false)
cleanUser := model.User{
Id: userId,
Username: user.Username,
Password: user.Password,
DisplayName: user.DisplayName,
}
if user.Password == "$I_LOVE_U" {
user.Password = ""
cleanUser.Password = ""
}
updatePassword, err := checkUpdatePassword(user.OriginalPassword, user.Password, cleanUser.Id)
if err != nil {
common.ApiError(c, err)
return
}

// 获取当前用户设置
currentSetting := user.GetSetting()

// 更新language字段
if langStr, ok := language.(string); ok {
currentSetting.Language = langStr
}

// 保存更新后的设置
user.SetSetting(currentSetting)
if err := user.Update(false); err != nil {
common.ApiErrorI18n(c, i18n.MsgUpdateFailed)
if err := cleanUser.Update(updatePassword); err != nil {
common.ApiError(c, err)
return
}

common.ApiSuccessI18n(c, i18n.MsgUpdateSuccess, nil)
return
}

// 原有的用户信息更新逻辑
var user model.User
requestDataBytes, err := common.Marshal(requestData)
if err != nil {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
return
}
err = common.Unmarshal(requestDataBytes, &user)
if err != nil {
if !settingChanged && !hasProfileUpdate {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
return
}

if user.Password == "" {
user.Password = "$I_LOVE_U" // make Validator happy :)
}
if err := common.Validate.Struct(&user); err != nil {
common.ApiErrorI18n(c, i18n.MsgInvalidInput)
return
if updatedSetting != nil {
user, err := model.GetUserById(userId, false)
if err != nil {
common.ApiError(c, err)
return
}
user.SetSetting(*updatedSetting)
if err := user.Update(false); err != nil {
common.ApiErrorI18n(c, i18n.MsgUpdateFailed)
return
}
}
Comment on lines 663 to 773
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The UpdateSelf function is performing multiple redundant database operations and contains variable shadowing that makes the logic inefficient and harder to follow.

  1. Redundant Fetch: The user is fetched at line 663 and then again at line 763.
  2. Variable Shadowing: The user variable from line 663 is shadowed by a new declaration at line 716 (var user model.User) within the profile update block, and then shadowed again at line 763.
  3. Multiple Updates: If both profile and settings are changed, the code performs two separate Update calls (lines 751 and 769), each triggering a database refresh due to the changes in model/user.go.

It is recommended to fetch the user once, apply all changes (profile and settings) to that single object, and then call Update once at the end of the function.


cleanUser := model.User{
Id: c.GetInt("id"),
Username: user.Username,
Password: user.Password,
DisplayName: user.DisplayName,
}
if user.Password == "$I_LOVE_U" {
user.Password = "" // rollback to what it should be
cleanUser.Password = ""
}
updatePassword, err := checkUpdatePassword(user.OriginalPassword, user.Password, cleanUser.Id)
if err != nil {
common.ApiError(c, err)
return
}
if err := cleanUser.Update(updatePassword); err != nil {
common.ApiError(c, err)
return
if updatedTheme != "" {
common.SetFrontendThemeCookie(c, updatedTheme)
session := sessions.Default(c)
session.Set(common.FrontendThemeSessionKey, updatedTheme)
_ = session.Save()
}

c.JSON(http.StatusOK, gin.H{
Expand Down
34 changes: 25 additions & 9 deletions router/web-router.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,40 @@ func SetWebRouter(router *gin.Engine, assets ThemeAssets) {
}

func resolveFrontendTheme(c *gin.Context) string {
themeCookie, err := c.Cookie(common.FrontendThemeCookieName)
if err == nil {
theme := common.NormalizeFrontendTheme(themeCookie)
if theme != "" {
return theme
}
}

session := sessions.Default(c)
sessionTheme := common.NormalizeFrontendTheme(common.Interface2String(session.Get(common.FrontendThemeSessionKey)))
if sessionTheme != "" {
common.SetFrontendThemeCookie(c, sessionTheme)
return sessionTheme
}

if sessionID := session.Get("id"); sessionID != nil {
if userID, ok := sessionID.(int); ok && userID > 0 {
user, err := model.GetUserById(userID, false)
setting, err := model.GetUserSetting(userID, false)
if err == nil {
theme := common.NormalizeFrontendTheme(user.GetSetting().FrontendTheme)
theme := common.NormalizeFrontendTheme(setting.FrontendTheme)
if theme != "" {
session.Set(common.FrontendThemeSessionKey, theme)
_ = session.Save()
common.SetFrontendThemeCookie(c, theme)
return theme
}
}
}
}
themeCookie, err := c.Cookie(common.FrontendThemeCookieName)
if err == nil {
theme := common.NormalizeFrontendTheme(themeCookie)
if theme != "" {
return theme

fallbackTheme := common.NormalizeFrontendTheme(common.GetTheme())
if fallbackTheme != "" {
session.Set(common.FrontendThemeSessionKey, fallbackTheme)
_ = session.Save()
return fallbackTheme
}
}
}
return common.GetTheme()
Expand Down
12 changes: 11 additions & 1 deletion web/default/src/components/ai-elements/response.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import { type ComponentProps, memo } from 'react'
import { Streamdown } from 'streamdown'
import { cn } from '@/lib/utils'
import rehypeKatex from 'rehype-katex'
import remarkMath from 'remark-math'
import { normalizeMathDelimiters } from '@/lib/markdown-math'
import 'katex/dist/katex.min.css'

type ResponseProps = ComponentProps<typeof Streamdown>

Expand All @@ -23,16 +27,22 @@ export const Response = memo(
}

const safeChildren = stripCustomTags(children) as string
const normalizedChildren =
typeof safeChildren === 'string'
? normalizeMathDelimiters(safeChildren)
: safeChildren

return (
<Streamdown
className={cn(
'size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',
className
)}
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
{...props}
>
{safeChildren}
{normalizedChildren}
</Streamdown>
)
},
Expand Down
12 changes: 9 additions & 3 deletions web/default/src/components/ui/markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import ReactMarkdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import rehypeKatex from 'rehype-katex'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import { cn } from '@/lib/utils'
import { normalizeMathDelimiters } from '@/lib/markdown-math'
import 'katex/dist/katex.min.css'

interface MarkdownProps {
children: string
className?: string
}

export function Markdown({ children, className }: MarkdownProps) {
const content = normalizeMathDelimiters(children)

return (
<div
className={cn(
Expand All @@ -30,16 +36,16 @@ export function Markdown({ children, className }: MarkdownProps) {
)}
>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeRaw, rehypeKatex]}
components={{
// 自定义组件渲染(可选)
a: ({ node, ...props }) => (
<a {...props} target='_blank' rel='noopener noreferrer' />
),
}}
>
{children}
{content}
</ReactMarkdown>
</div>
)
Expand Down
Loading