fix(phase2a): 代码审查修复 - 8项关键/重要问题

安全修复:
- WebSocket Token 增加 Redis 有效性校验(已登出用户无法建立 WS)

功能修复:
- GetRecommendFriends 改为批量查询,正确返回用户名/昵称/头像
- 上下线通知:OnlineService 通过接口注入获取好友列表推送状态变更
- 管理端在线用户 API 补充用户名信息

代码质量:
- 所有 json.Marshal/Redis 错误增加检查与日志
- ContactController 13 个 endpoint 统一走 handleError 业务错误映射
- 管理端 Controller 补全包注释、函数注释和结构化日志
- 前端 5 个联系人页面 avatar 工具函数抽取到 utils/avatar.js

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-03-02 18:48:28 +08:00
parent 8ec0261427
commit 2c59500e27
26 changed files with 632 additions and 153 deletions

View File

@@ -53,23 +53,12 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useContactStore } from '@/store/contact'
import { getAvatarColor, getInitial } from '@/utils/avatar'
const contactStore = useContactStore()
const loading = ref(true)
const processingMap = reactive({})
const AVATAR_COLORS = ['#7C3AED', '#2563EB', '#0891B2', '#059669', '#D97706', '#DC2626', '#4F46E5']
const getAvatarColor = (name) => {
if (!name) return AVATAR_COLORS[0]
return AVATAR_COLORS[name.charCodeAt(0) % AVATAR_COLORS.length]
}
const getInitial = (name) => {
if (!name) return '?'
return name.charAt(0).toUpperCase()
}
const handleUnblock = (user) => {
if (processingMap[user.user_id]) return
uni.showModal({

View File

@@ -66,6 +66,7 @@
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useContactStore } from '@/store/contact'
import { getAvatarColor, getInitial } from '@/utils/avatar'
const contactStore = useContactStore()
const loading = ref(true)
@@ -73,18 +74,6 @@ const friend = ref(null)
const userId = ref(0)
const processing = ref(false)
const AVATAR_COLORS = ['#7C3AED', '#2563EB', '#0891B2', '#059669', '#D97706', '#DC2626', '#4F46E5']
const getAvatarColor = (name) => {
if (!name) return AVATAR_COLORS[0]
return AVATAR_COLORS[name.charCodeAt(0) % AVATAR_COLORS.length]
}
const getInitial = (name) => {
if (!name) return '?'
return name.charAt(0).toUpperCase()
}
const currentGroupName = computed(() => {
if (!friend.value || !friend.value.group_id) return '默认分组'
const group = contactStore.groups.find(g => g.id === friend.value.group_id)
@@ -128,7 +117,7 @@ const editRemark = () => {
}
const selectGroup = () => {
const groups = [{ id: 0, name: '默认分组' }, ...contactStore.groups]
const groups = [{ id: null, name: '默认分组' }, ...contactStore.groups]
const names = groups.map(g => g.name)
uni.showActionSheet({

View File

@@ -116,6 +116,7 @@ import { ref, onMounted, computed } from 'vue'
import { useContactStore } from '@/store/contact'
import { useWebSocketStore } from '@/store/websocket'
import { useUserStore } from '@/store/user'
import { getAvatarColor, getInitial } from '@/utils/avatar'
import CustomTabBar from '@/components/CustomTabBar.vue'
const contactStore = useContactStore()
@@ -125,19 +126,6 @@ const userStore = useUserStore()
const searchKeyword = ref('')
const loading = ref(true)
const AVATAR_COLORS = ['#7C3AED', '#2563EB', '#0891B2', '#059669', '#D97706', '#DC2626', '#7C3AED', '#4F46E5']
const getAvatarColor = (name) => {
if (!name) return AVATAR_COLORS[0]
const code = name.charCodeAt(0)
return AVATAR_COLORS[code % AVATAR_COLORS.length]
}
const getInitial = (name) => {
if (!name) return '?'
return name.charAt(0).toUpperCase()
}
const filteredFriends = computed(() => {
if (!searchKeyword.value) return contactStore.friendList
const kw = searchKeyword.value.toLowerCase()

View File

@@ -68,23 +68,12 @@
<script setup>
import { ref, onMounted, reactive } from 'vue'
import { useContactStore } from '@/store/contact'
import { getAvatarColor, getInitial } from '@/utils/avatar'
const contactStore = useContactStore()
const loading = ref(true)
const processingMap = reactive({})
const AVATAR_COLORS = ['#7C3AED', '#2563EB', '#0891B2', '#059669', '#D97706', '#DC2626', '#4F46E5']
const getAvatarColor = (name) => {
if (!name) return AVATAR_COLORS[0]
return AVATAR_COLORS[name.charCodeAt(0) % AVATAR_COLORS.length]
}
const getInitial = (name) => {
if (!name) return '?'
return name.charAt(0).toUpperCase()
}
const formatTime = (dateStr) => {
if (!dateStr) return ''
const date = new Date(dateStr)

View File

@@ -110,6 +110,7 @@
import { ref, reactive, onMounted } from 'vue'
import contactApi from '@/api/contact'
import { useContactStore } from '@/store/contact'
import { getAvatarColor, getInitial } from '@/utils/avatar'
const contactStore = useContactStore()
@@ -121,18 +122,6 @@ const recommendLoading = ref(true)
const recommendations = ref([])
const addingMap = reactive({})
const AVATAR_COLORS = ['#7C3AED', '#2563EB', '#0891B2', '#059669', '#D97706', '#DC2626', '#4F46E5']
const getAvatarColor = (name) => {
if (!name) return AVATAR_COLORS[0]
return AVATAR_COLORS[name.charCodeAt(0) % AVATAR_COLORS.length]
}
const getInitial = (name) => {
if (!name) return '?'
return name.charAt(0).toUpperCase()
}
const doSearch = async () => {
const kw = keyword.value.trim()
if (!kw) {
@@ -143,7 +132,7 @@ const doSearch = async () => {
searchLoading.value = true
try {
const res = await contactApi.searchUsers(kw)
searchResults.value = res.data || []
searchResults.value = res.data?.list || []
} catch (e) {
console.error('搜索用户失败', e)
searchResults.value = []