feat: 代码片段功能与 Markdown 内嵌片段渲染、文档截图等
This commit is contained in:
@@ -767,6 +767,18 @@ func (s *ChatService) Kick(operatorID, roomID, targetID uint, oversee bool) erro
|
||||
return s.removeMember(roomID, targetID)
|
||||
}
|
||||
|
||||
// DisplayName 取用户群聊展示名(昵称优先,退回用户名);用户不存在/封禁时返回空串
|
||||
func (s *ChatService) DisplayName(userID uint) string {
|
||||
var u model.User
|
||||
if err := s.db.Select("id", "nickname", "username").First(&u, userID).Error; err != nil {
|
||||
return ""
|
||||
}
|
||||
if u.Nickname != "" {
|
||||
return u.Nickname
|
||||
}
|
||||
return u.Username
|
||||
}
|
||||
|
||||
// SendSystemMessage 向房间落库一条系统提示消息(sender_id=0,is_system=true)。
|
||||
// 只推进房间 updated_at,不推进 last_message_id:系统提示不作为会话预览,
|
||||
// 也不参与排序顶置,避免禁言/解禁等管理动作在会话列表伪装成「新消息」。
|
||||
|
||||
@@ -24,7 +24,7 @@ const (
|
||||
|
||||
// HeaderNavBuiltinKeys 内置菜单 key 白名单(顺序即默认顺序);
|
||||
// 与前端 lib/headerNav.ts 的 HEADER_NAV_BUILTIN_KEYS 保持一致。
|
||||
var HeaderNavBuiltinKeys = []string{"home", "leaderboard", "library"}
|
||||
var HeaderNavBuiltinKeys = []string{"home", "leaderboard", "library", "snippets"}
|
||||
|
||||
// HeaderNavItem 顶部导航菜单项:
|
||||
//
|
||||
|
||||
@@ -25,18 +25,19 @@ func newHeaderNavTestService(t *testing.T) *SettingService {
|
||||
func TestHeaderNavDefaultWhenMissing(t *testing.T) {
|
||||
s := newHeaderNavTestService(t)
|
||||
got := s.HeaderNav()
|
||||
if len(got) != 3 ||
|
||||
if len(got) != 4 ||
|
||||
got[0] != (HeaderNavItem{Type: HeaderNavItemBuiltin, Key: "home"}) ||
|
||||
got[1] != (HeaderNavItem{Type: HeaderNavItemBuiltin, Key: "leaderboard"}) ||
|
||||
got[2] != (HeaderNavItem{Type: HeaderNavItemBuiltin, Key: "library"}) {
|
||||
got[2] != (HeaderNavItem{Type: HeaderNavItemBuiltin, Key: "library"}) ||
|
||||
got[3] != (HeaderNavItem{Type: HeaderNavItemBuiltin, Key: "snippets"}) {
|
||||
t.Fatalf("missing row should fall back to builtin defaults, got %+v", got)
|
||||
}
|
||||
pub, err := s.Public()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(pub.HeaderNav) != 3 {
|
||||
t.Fatalf("Public().HeaderNav len = %d, want 3", len(pub.HeaderNav))
|
||||
if len(pub.HeaderNav) != 4 {
|
||||
t.Fatalf("Public().HeaderNav len = %d, want 4", len(pub.HeaderNav))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +134,7 @@ func TestNormalizeHeaderNavDedupesBuiltins(t *testing.T) {
|
||||
|
||||
func TestParseHeaderNavJSONTolerant(t *testing.T) {
|
||||
// 坏 JSON 回落默认
|
||||
if got := parseHeaderNavJSON("{bad"); len(got) != 3 {
|
||||
if got := parseHeaderNavJSON("{bad"); len(got) != 4 {
|
||||
t.Fatalf("bad json should fall back to defaults, got %+v", got)
|
||||
}
|
||||
// 空数组 = 全部隐藏
|
||||
|
||||
@@ -85,6 +85,7 @@ const (
|
||||
RateAdSubmit = "ad_submit" // 广告申购 5/分钟(按 IP)
|
||||
RateCaptcha = "captcha" // 验证码刷新 30/分钟
|
||||
RateTelemetry = "telemetry" // 页面浏览埋点 120/分钟(按 IP)
|
||||
RateSnippet = "snippet" // 代码片段创建/Fork 10/分钟
|
||||
)
|
||||
|
||||
// DefaultRateLimiter 创建默认速率限制器
|
||||
@@ -102,5 +103,6 @@ func DefaultRateLimiter() *RateLimiter {
|
||||
rl.SetLimit(RateAdSubmit, 5) // 广告申购 5/分钟
|
||||
rl.SetLimit(RateCaptcha, 30) // 验证码 30/分钟
|
||||
rl.SetLimit(RateTelemetry, 120) // 埋点 120/分钟
|
||||
rl.SetLimit(RateSnippet, 10) // 代码片段 10/分钟
|
||||
return rl
|
||||
}
|
||||
|
||||
1022
backend/service/snippet.go
Normal file
1022
backend/service/snippet.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user