同步状态

This commit is contained in:
18631081161 2026-01-29 02:44:15 +08:00
parent af5ceac9a7
commit 79072bc060
27 changed files with 475 additions and 110 deletions

View File

@ -1,6 +1,129 @@
<script>
import { useConfigStore } from './store/config.js'
import { useUserStore } from './store/user.js'
import { useChatStore, MessageType } from './store/chat.js'
import signalR from './utils/signalr.js'
//
const ExchangeStatus = {
PENDING: 0,
ACCEPTED: 1,
REJECTED: 2
}
// SignalR
let globalExchangeResponseHandler = null
let globalReceiveMessageHandler = null
/**
* 初始化全局 SignalR 连接和事件监听
* 用于在用户不在聊天页面时也能接收交换响应等通知
*/
async function initGlobalSignalR() {
const userStore = useUserStore()
const chatStore = useChatStore()
if (!userStore.isLoggedIn) {
console.log('[App] 用户未登录,跳过 SignalR 连接')
return
}
try {
// SignalR
await signalR.connect()
console.log('[App] 全局 SignalR 连接成功')
//
if (globalExchangeResponseHandler) {
signalR.off('ExchangeResponse', globalExchangeResponseHandler)
}
if (globalReceiveMessageHandler) {
signalR.off('ReceiveMessage', globalReceiveMessageHandler)
}
//
globalExchangeResponseHandler = (message) => {
console.log('[App] 全局收到交换响应:', message)
//
if (message.sessionId && message.sessionId === chatStore.currentSessionId) {
console.log('[App] 当前在聊天页面,跳过全局处理')
return
}
// chatStore
if (message.extraData) {
try {
const extraData = typeof message.extraData === 'string'
? JSON.parse(message.extraData)
: message.extraData
const requestMessageId = extraData.RequestMessageId || extraData.requestMessageId
const status = extraData.Status ?? extraData.status ?? ExchangeStatus.PENDING
const sessionId = message.sessionId
console.log('[App] 交换响应详情:', { requestMessageId, status, sessionId })
// chatStore
if (sessionId && chatStore.messagesBySession[sessionId]) {
const messages = chatStore.messagesBySession[sessionId]
const requestMsg = messages.find(m => m.id === requestMessageId)
if (requestMsg) {
requestMsg.status = status
console.log('[App] 已更新消息状态:', requestMessageId, status)
//
if (status === ExchangeStatus.ACCEPTED) {
if (requestMsg.messageType === MessageType.EXCHANGE_WECHAT) {
requestMsg.exchangedContent = extraData.senderWeChat || extraData.receiverWeChat ||
extraData.SenderWeChat || extraData.ReceiverWeChat
} else if (requestMsg.messageType === MessageType.EXCHANGE_PHOTO) {
requestMsg.photos = extraData.senderPhotos || extraData.receiverPhotos ||
extraData.SenderPhotos || extraData.ReceiverPhotos
requestMsg.photoCount = requestMsg.photos?.length || 0
}
}
}
}
//
const isAccepted = status === ExchangeStatus.ACCEPTED
const typeText = message.messageType === 5 ? '微信' : '照片'
uni.showToast({
title: isAccepted ? `对方已同意交换${typeText}` : `对方已拒绝交换${typeText}`,
icon: 'none',
duration: 2000
})
} catch (e) {
console.error('[App] 解析交换响应数据失败:', e)
}
}
}
//
globalReceiveMessageHandler = (message) => {
console.log('[App] 全局收到新消息:', message)
//
if (message.sessionId && message.sessionId === chatStore.currentSessionId) {
console.log('[App] 当前在聊天页面,跳过全局处理')
return
}
//
if (message.sessionId) {
chatStore.incrementUnreadCount(message.sessionId)
}
}
signalR.on('ExchangeResponse', globalExchangeResponseHandler)
signalR.on('ReceiveMessage', globalReceiveMessageHandler)
console.log('[App] 全局 SignalR 事件监听已注册')
} catch (err) {
console.error('[App] 全局 SignalR 连接失败:', err)
}
}
export default {
onLaunch: function() {
@ -12,9 +135,14 @@
//
const configStore = useConfigStore()
configStore.loadAppConfig()
// SignalR
initGlobalSignalR()
},
onShow: function() {
console.log('App Show')
// SignalR
initGlobalSignalR()
},
onHide: function() {
console.log('App Hide')

View File

@ -79,24 +79,24 @@
</view>
</template>
<!-- 服务号关注弹窗 -->
<!-- 资料审核中弹窗 -->
<template v-else-if="type === 'auditing'">
<view class="popup-title">资料审核中</view>
<view class="auditing-info">
<text class="auditing-desc">正在加急审核您的相亲资料请耐心等待</text>
</view>
<button class="popup-btn auditing-btn" @click="handleClose">确定</button>
</template>
<!-- 服务号关注弹窗 - 样式与每日弹窗一致只显示图片 -->
<template v-else-if="type === 'serviceAccount'">
<image
v-if="imageUrl"
class="popup-image service-account-image"
class="popup-image"
:src="imageUrl"
mode="widthFix"
@click="handleImageClick"
/>
<view class="popup-title">{{ title || '关注服务号' }}</view>
<view class="popup-content">{{ content || '关注服务号,及时接收消息通知' }}</view>
<view class="service-account-tips">
<text> 有人解锁/收藏您时第一时间通知</text>
<text> 有人给您发消息时及时提醒</text>
<text> 每日推荐更新不错过优质对象</text>
</view>
<button class="popup-btn" @click="handleButtonClick">
{{ buttonText || '去关注' }}
</button>
</template>
<!-- 通用弹窗 -->
@ -132,7 +132,7 @@ export default {
type: {
type: String,
default: 'default',
validator: (value) => ['gender', 'daily', 'member', 'unlock', 'profile', 'serviceAccount', 'default'].includes(value)
validator: (value) => ['gender', 'daily', 'member', 'unlock', 'profile', 'auditing', 'serviceAccount', 'default'].includes(value)
},
imageUrl: {
type: String,
@ -184,6 +184,10 @@ export default {
if (this.linkUrl) {
this.navigateToLink()
}
//
if (this.type === 'serviceAccount') {
this.$emit('confirm')
}
},
handleButtonClick() {
//
@ -208,12 +212,9 @@ export default {
if (this.linkUrl.startsWith('/pages/')) {
uni.navigateTo({ url: this.linkUrl })
} else if (this.linkUrl.startsWith('http')) {
// 使webview
uni.setClipboardData({
data: this.linkUrl,
success: () => {
uni.showToast({ title: '链接已复制', icon: 'success' })
}
// 使webview
uni.navigateTo({
url: `/pages/webview/index?url=${encodeURIComponent(this.linkUrl)}`
})
}
}
@ -324,9 +325,10 @@ export default {
}
}
// /广 -
// /广/ -
.popup-daily,
.popup-member {
.popup-member,
.popup-serviceAccount {
background: transparent;
padding: 0;
width: auto;
@ -443,28 +445,24 @@ export default {
}
}
//
.popup-serviceAccount {
.service-account-image {
width: 200rpx;
height: 200rpx;
margin: 0 auto 20rpx;
display: block;
border-radius: 16rpx;
}
.service-account-tips {
background: #f8f8f8;
border-radius: 12rpx;
padding: 24rpx;
//
.popup-auditing {
.auditing-info {
text-align: center;
margin-bottom: 30rpx;
text {
display: block;
font-size: 24rpx;
.auditing-desc {
font-size: 28rpx;
color: #666;
line-height: 1.8;
line-height: 1.6;
}
}
.auditing-btn {
background: #e0e0e0;
color: #666;
}
}
</style>

View File

@ -50,9 +50,10 @@
<view class="user-details">
<view class="user-name-row">
<text class="nickname">{{ targetNickname }}</text>
<text class="relationship" v-if="targetRelationship">({{ targetRelationship }})</text>
<text class="relationship" v-if="targetRelationship && !targetNickname.includes('')">({{ targetRelationship }})</text>
<view class="user-tags">
<text v-if="targetIsMember" class="tag tag-vip">VIP会员</text>
<image v-if="targetIsMember && memberIconUrl" class="member-icon" :src="memberIconUrl" mode="heightFix" />
<text v-else-if="targetIsMember" class="tag tag-member">会员</text>
<text v-if="targetIsRealName" class="tag tag-realname">已实名</text>
</view>
</view>
@ -341,6 +342,7 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { useUserStore } from '@/store/user.js'
import { useConfigStore } from '@/store/config.js'
import { useChatStore, MessageType, MessageStatus } from '@/store/chat.js'
import { getMessages, sendMessage, exchangeWeChat, exchangePhoto, respondExchange, uploadVoice, getOrCreateSession } from '@/api/chat.js'
import { getUserDetail } from '@/api/user.js'
@ -348,9 +350,11 @@ import Loading from '@/components/Loading/index.vue'
import EmojiPicker from '@/components/EmojiPicker/index.vue'
import VoiceRecorder from '@/components/VoiceRecorder/index.vue'
import { formatTimestamp } from '@/utils/format.js'
import { getFullImageUrl } from '@/utils/image.js'
import signalR from '@/utils/signalr.js'
const userStore = useUserStore()
const configStore = useConfigStore()
const chatStore = useChatStore()
//
@ -370,6 +374,7 @@ const targetNickname = ref('')
const targetAvatar = ref('')
const targetRelationship = ref('')
const targetIsMember = ref(false)
const targetMemberLevel = ref(0)
const targetIsRealName = ref(false)
const targetXiangQinNo = ref('')
const targetUserDetail = ref(null)
@ -406,6 +411,13 @@ const innerAudioContext = ref(null)
const myAvatar = computed(() => userStore.avatar)
const myUserId = computed(() => userStore.userId)
// URL
const memberIconUrl = computed(() => {
if (!targetIsMember.value || !targetMemberLevel.value) return ''
const iconUrl = configStore.getMemberIcon(targetMemberLevel.value)
return iconUrl ? getFullImageUrl(iconUrl) : ''
})
//
const educationMap = {
1: '高中',
@ -460,6 +472,7 @@ const loadTargetUserDetail = async () => {
targetNickname.value = res.data.nickname || targetNickname.value
targetAvatar.value = res.data.avatar || targetAvatar.value
targetIsMember.value = res.data.isMember || false
targetMemberLevel.value = res.data.memberLevel || 0
targetIsRealName.value = res.data.isRealName || false
targetXiangQinNo.value = res.data.xiangQinNo || ''
targetRelationship.value = relationshipMap[res.data.relationship] || ''
@ -1309,18 +1322,31 @@ const handleExchangeResponse = (message) => {
//
if (message.extraData) {
try {
const extraData = JSON.parse(message.extraData)
if (extraData.requestMessageId) {
const requestMsg = messages.value.find(m => m.id === extraData.requestMessageId)
const extraData = typeof message.extraData === 'string' ? JSON.parse(message.extraData) : message.extraData
//
const requestMessageId = extraData.RequestMessageId || extraData.requestMessageId
const status = extraData.Status ?? extraData.status
if (requestMessageId) {
const requestMsg = messages.value.find(m => m.id === requestMessageId)
if (requestMsg) {
requestMsg.status = extraData.status
if (extraData.status === ExchangeStatus.ACCEPTED) {
requestMsg.status = status
console.log('[Chat] handleExchangeResponse 更新消息状态:', requestMessageId, status)
if (status === ExchangeStatus.ACCEPTED) {
//
if (requestMsg.messageType === MessageType.EXCHANGE_WECHAT) {
requestMsg.exchangedContent = extraData.senderWeChat || extraData.receiverWeChat
//
requestMsg.exchangedContent = extraData.ReceiverWeChat || extraData.receiverWeChat
hasExchangedWeChat.value = true
exchangedWeChat.value = requestMsg.exchangedContent
} else if (requestMsg.messageType === MessageType.EXCHANGE_PHOTO) {
requestMsg.photos = extraData.senderPhotos || extraData.receiverPhotos
requestMsg.photoCount = requestMsg.photos?.length || 0
//
const photos = extraData.ReceiverPhotos || extraData.receiverPhotos || []
requestMsg.photos = photos
requestMsg.photoCount = photos.length
hasExchangedPhoto.value = true
exchangedPhotos.value = photos
}
}
}
@ -1479,21 +1505,33 @@ onUnmounted(() => {
.user-tags {
display: flex;
align-items: center;
gap: 8rpx;
.tag {
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 16rpx;
.member-icon {
width: auto;
height: 42rpx;
vertical-align: middle;
}
&.tag-vip {
.tag {
display: inline-block;
font-size: 22rpx;
padding: 6rpx 16rpx;
border-radius: 8rpx;
height: 36rpx;
line-height: 24rpx;
box-sizing: border-box;
vertical-align: middle;
&.tag-member {
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
color: #ff9800;
}
&.tag-realname {
background: #e8f5e9;
color: #4caf50;
background: #3d4a4a;
color: #fff;
}
}
}

View File

@ -41,14 +41,18 @@
:visible="showServiceAccountPopup"
type="serviceAccount"
:imageUrl="serviceAccountPopup?.imageUrl"
:title="serviceAccountPopup?.title || '关注服务号'"
:content="serviceAccountPopup?.content || '关注服务号,及时接收消息通知'"
:buttonText="serviceAccountPopup?.buttonText || '去关注'"
:linkUrl="serviceAccountPopup?.linkUrl"
@close="handleCloseServiceAccountPopup"
@confirm="handleServiceAccountPopupConfirm"
/>
<!-- 资料审核中弹窗 -->
<Popup
:visible="showAuditingPopup"
type="auditing"
@close="handleCloseAuditingPopup"
/>
<!-- ==================== 固定底部条 ==================== -->
<!-- 会员广告条 - 固定在底部优先级最高 -->
@ -682,12 +686,24 @@ export default {
return true
}
// ==================== ====================
/** 是否显示审核中弹窗 */
const showAuditingPopup = ref(false)
/**
* 检查用户资料完善状态
*
* @returns {boolean} 资料是否已完善
*/
const checkProfileStatus = () => {
//
if (userStore.isProfileAuditing) {
showAuditingPopup.value = true
return false
}
//
if (!userStore.isProfileCompleted) {
uni.showModal({
title: '提示',
@ -704,6 +720,13 @@ export default {
return true
}
/**
* 关闭审核中弹窗
*/
const handleCloseAuditingPopup = () => {
showAuditingPopup.value = false
}
/**
* 检查用户解锁状态并处理
*
@ -883,6 +906,7 @@ export default {
dailyPopup,
showUnlockPopup,
remainingUnlockQuota,
showAuditingPopup,
//
initPage,
@ -905,7 +929,8 @@ export default {
handleConfirmUnlock,
handleGoMember,
handleScrollToLower,
handleRefresh
handleRefresh,
handleCloseAuditingPopup
}
},

View File

@ -291,6 +291,17 @@ export default {
//
const handleContact = (userId) => {
//
if (userStore.isProfileAuditing) {
uni.showModal({
title: '资料审核中',
content: '正在加急审核您的相亲资料,请耐心等待',
showCancel: false,
confirmText: '确定'
})
return
}
//
if (!userStore.isProfileCompleted) {
uni.showModal({

View File

@ -189,6 +189,17 @@ export default {
//
const handleContact = (userId) => {
//
if (userStore.isProfileAuditing) {
uni.showModal({
title: '资料审核中',
content: '正在加急审核您的相亲资料,请耐心等待',
showCancel: false,
confirmText: '确定'
})
return
}
//
if (!userStore.isProfileCompleted) {
uni.showModal({

View File

@ -304,20 +304,27 @@
let interactType = ''
if (url.includes('viewedMe')) {
interactType = 'viewedMe'
interactCounts.value.viewedMe = 0
} else if (url.includes('favoritedMe')) {
interactType = 'favoritedMe'
interactCounts.value.favoritedMe = 0
} else if (url.includes('unlockedMe')) {
interactType = 'unlockedMe'
interactCounts.value.unlockedMe = 0
}
//
//
if (interactType) {
markInteractAsRead(interactType).catch(err => {
try {
await markInteractAsRead(interactType)
//
if (interactType === 'viewedMe') {
interactCounts.value.viewedMe = 0
} else if (interactType === 'favoritedMe') {
interactCounts.value.favoritedMe = 0
} else if (interactType === 'unlockedMe') {
interactCounts.value.unlockedMe = 0
}
} catch (err) {
console.error('标记已读失败:', err)
})
}
}
uni.navigateTo({ url })

View File

@ -67,7 +67,7 @@
<view class="header-right">
<view class="name-row">
<text class="nickname">{{ userDetail.nickname }}</text>
<text class="relationship">({{ relationshipText }})</text>
<text class="relationship" v-if="!userDetail.nickname?.includes('')">({{ relationshipText }})</text>
<view class="header-tags">
<image v-if="userDetail.isMember && memberIconUrl" class="member-icon" :src="memberIconUrl" mode="aspectFit" />
<text v-else-if="userDetail.isMember" class="tag tag-member">会员</text>
@ -577,6 +577,14 @@ const handleFavorite = async () => {
//
const handleContact = async () => {
console.log('[Detail] handleContact - 检查实名状态:', {
targetIsRealName: userDetail.value?.isRealName,
myIsRealName: userStore.isRealName,
isLoggedIn: userStore.isLoggedIn,
isProfileCompleted: userStore.isProfileCompleted,
isProfileAuditing: userStore.isProfileAuditing
})
//
if (!userStore.isLoggedIn) {
uni.showModal({
@ -592,6 +600,17 @@ const handleContact = async () => {
return
}
//
if (userStore.isProfileAuditing) {
uni.showModal({
title: '资料审核中',
content: '正在加急审核您的相亲资料,请耐心等待',
showCancel: false,
confirmText: '确定'
})
return
}
if (!userStore.isProfileCompleted) {
showProfilePopup.value = true
return
@ -599,6 +618,7 @@ const handleContact = async () => {
//
if (userDetail.value?.isRealName && !userStore.isRealName) {
console.log('[Detail] 对方已实名但我未实名,跳转实名认证页')
uni.showModal({
title: '提示',
content: '对方已开启实名相亲,请先完成实名认证才能联系',

View File

@ -762,7 +762,7 @@ const formData = reactive({
houseStatus: 5, // ""
carStatus: 2, // ""
marriageStatus: 1, // ""
expectMarryTime: 0,
expectMarryTime: 1, // ""
introduction: '',
weChatNo: '',
phone: '', //

View File

@ -399,6 +399,17 @@ const handleContact = (userId) => {
return
}
//
if (userStore.isProfileAuditing) {
uni.showModal({
title: '资料审核中',
content: '正在加急审核您的相亲资料,请耐心等待',
showCancel: false,
confirmText: '确定'
})
return
}
//
if (!userStore.isProfileCompleted) {
uni.showModal({

View File

@ -232,15 +232,22 @@ export const useConfigStore = defineStore('config', {
/**
* 检查是否应该显示服务号关注弹窗
* 条件
* 1. 用户未关注服务号从后端获取真实状态
* 2. 弹窗已启用
* 3. 弹出后5分钟内不再弹出
* 4. 一天最多弹出3次
* 1. 用户已登录
* 2. 用户未关注服务号从后端获取真实状态
* 3. 弹窗已启用
* 4. 弹出后5分钟内不再弹出
* 5. 一天最多弹出3次
*/
checkServiceAccountPopup() {
// 从 userStore 获取真实的关注状态
const userStore = useUserStore()
// 如果未登录,不显示
if (!userStore.isLoggedIn) {
this.showServiceAccountPopup = false
return
}
// 如果已关注服务号,不显示
if (userStore.isFollowServiceAccount) {
this.showServiceAccountPopup = false

View File

@ -34,6 +34,7 @@ export const useUserStore = defineStore('user', {
avatar: '',
xiangQinNo: '',
isProfileCompleted: false,
auditStatus: 0, // 审核状态0-未提交资料 1-已通过审核 2-审核中 3-已拒绝
isMember: false,
memberLevel: 0,
isRealName: false,
@ -56,6 +57,11 @@ export const useUserStore = defineStore('user', {
*/
needCompleteProfile: (state) => !state.isProfileCompleted,
/**
* 是否资料审核中auditStatus = 2
*/
isProfileAuditing: (state) => state.auditStatus === 2,
/**
* 是否需要选择性别偏好
*/
@ -96,6 +102,7 @@ export const useUserStore = defineStore('user', {
this.avatar = ''
this.xiangQinNo = ''
this.isProfileCompleted = false
this.auditStatus = 0
this.isMember = false
this.memberLevel = 0
this.isRealName = false
@ -115,6 +122,7 @@ export const useUserStore = defineStore('user', {
if (userInfo.avatar !== undefined) this.avatar = userInfo.avatar
if (userInfo.xiangQinNo !== undefined) this.xiangQinNo = userInfo.xiangQinNo
if (userInfo.isProfileCompleted !== undefined) this.isProfileCompleted = userInfo.isProfileCompleted
if (userInfo.auditStatus !== undefined) this.auditStatus = userInfo.auditStatus
if (userInfo.isMember !== undefined) this.isMember = userInfo.isMember
if (userInfo.memberLevel !== undefined) this.memberLevel = userInfo.memberLevel
if (userInfo.isRealName !== undefined) this.isRealName = userInfo.isRealName
@ -127,6 +135,7 @@ export const useUserStore = defineStore('user', {
avatar: this.avatar,
xiangQinNo: this.xiangQinNo,
isProfileCompleted: this.isProfileCompleted,
auditStatus: this.auditStatus,
isMember: this.isMember,
memberLevel: this.memberLevel,
isRealName: this.isRealName,
@ -161,6 +170,7 @@ export const useUserStore = defineStore('user', {
this.avatar = userInfo.avatar || ''
this.xiangQinNo = userInfo.xiangQinNo || ''
this.isProfileCompleted = userInfo.isProfileCompleted || false
this.auditStatus = userInfo.auditStatus !== undefined ? userInfo.auditStatus : 0
this.isMember = userInfo.isMember || false
this.memberLevel = userInfo.memberLevel || 0
this.isRealName = userInfo.isRealName || false
@ -218,6 +228,7 @@ export const useUserStore = defineStore('user', {
avatar: data.avatar,
xiangQinNo: data.xiangQinNo,
isProfileCompleted: data.auditStatus === 1,
auditStatus: data.auditStatus,
isMember: data.isMember,
memberLevel: data.memberLevel,
isRealName: data.isRealName,

View File

@ -131,10 +131,14 @@ public class ChatController : ControllerBase
IsSelf = false // 接收者收到时不是自己发的
};
// 推送给会话组(双方都能收到)
// 推送到会话组(双方都在聊天页面时能收到)
await _hubContext.SendMessageToSessionAsync(request.SessionId, messageResponse);
_logger.LogInformation("消息已通过SignalR推送到会话组: MessageId={MessageId}, SessionId={SessionId}",
result.MessageId, request.SessionId);
// 同时推送到接收者的用户组(即使不在聊天页面也能收到)
await _hubContext.SendNewMessageAsync(request.ReceiverId, messageResponse);
_logger.LogInformation("消息已通过SignalR推送: MessageId={MessageId}, SessionId={SessionId}, ReceiverId={ReceiverId}",
result.MessageId, request.SessionId, request.ReceiverId);
return ApiResponse<SendMessageResponse>.Success(result);
}
@ -182,9 +186,14 @@ public class ChatController : ControllerBase
IsSelf = false
};
// 推送到会话组(双方都在聊天页面时能收到)
await _hubContext.SendMessageToSessionAsync(request.SessionId, messageResponse);
_logger.LogInformation("交换微信请求已通过SignalR推送到会话组: MessageId={MessageId}, SessionId={SessionId}",
result.RequestMessageId, request.SessionId);
// 同时推送到接收者的用户组(即使不在聊天页面也能收到)
await _hubContext.SendNewMessageAsync(request.ReceiverId, messageResponse);
_logger.LogInformation("交换微信请求已通过SignalR推送: MessageId={MessageId}, SessionId={SessionId}, ReceiverId={ReceiverId}",
result.RequestMessageId, request.SessionId, request.ReceiverId);
return ApiResponse<ExchangeRequestResponse>.Success(result);
}
@ -232,9 +241,14 @@ public class ChatController : ControllerBase
IsSelf = false
};
// 推送到会话组(双方都在聊天页面时能收到)
await _hubContext.SendMessageToSessionAsync(request.SessionId, messageResponse);
_logger.LogInformation("交换照片请求已通过SignalR推送到会话组: MessageId={MessageId}, SessionId={SessionId}",
result.RequestMessageId, request.SessionId);
// 同时推送到接收者的用户组(即使不在聊天页面也能收到)
await _hubContext.SendNewMessageAsync(request.ReceiverId, messageResponse);
_logger.LogInformation("交换照片请求已通过SignalR推送: MessageId={MessageId}, SessionId={SessionId}, ReceiverId={ReceiverId}",
result.RequestMessageId, request.SessionId, request.ReceiverId);
return ApiResponse<ExchangeRequestResponse>.Success(result);
}
@ -284,9 +298,14 @@ public class ChatController : ControllerBase
IsSelf = false
};
// 推送到会话组(在聊天页面的用户能收到)
await _hubContext.SendMessageToSessionAsync(result.SessionId, messageResponse);
_logger.LogInformation("交换响应已通过SignalR推送到会话组: ResultMessageId={ResultMessageId}, SessionId={SessionId}, IsAgreed={IsAgreed}",
result.ResultMessageId, result.SessionId, result.IsAgreed);
// 同时推送到请求发起者的用户组(即使不在聊天页面也能收到)
await _hubContext.SendExchangeResponseAsync(result.RequesterId, messageResponse);
_logger.LogInformation("交换响应已通过SignalR推送: ResultMessageId={ResultMessageId}, SessionId={SessionId}, RequesterId={RequesterId}, IsAgreed={IsAgreed}",
result.ResultMessageId, result.SessionId, result.RequesterId, result.IsAgreed);
return ApiResponse<ExchangeRespondResponse>.Success(result);
}

View File

@ -87,8 +87,12 @@ builder.Services.AddSwaggerGen(c =>
c.DocInclusionPredicate((docName, api) => true);
});
// Add SignalR
builder.Services.AddSignalR();
// Add SignalR with camelCase JSON serialization
builder.Services.AddSignalR()
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
});
// Add Hangfire
builder.Services.AddHangfireServices(builder.Configuration);

View File

@ -16,7 +16,7 @@ public class AdminProfileAuditQueryRequest
public int PageSize { get; set; } = 20;
/// <summary>
/// 审核状态0待审核 1已通过 2已拒绝,不传则查询全部
/// 审核状态0未提交 1已通过 2审核中 3已拒绝,不传则查询全部
/// </summary>
public int? AuditStatus { get; set; }

View File

@ -46,7 +46,7 @@ public class AdminUserQueryRequest
public bool? IsProfileCompleted { get; set; }
/// <summary>
/// 资料审核状态0待审核 1已通过 2已拒绝
/// 资料审核状态0未提交 1已通过 2审核中 3已拒绝
/// </summary>
public int? AuditStatus { get; set; }

View File

@ -66,7 +66,7 @@ public class AdminPendingProfileDto
public string WorkCity { get; set; } = string.Empty;
/// <summary>
/// 审核状态0待审核 1已通过 2已拒绝
/// 审核状态0未提交 1已通过 2审核中 3已拒绝
/// </summary>
public int AuditStatus { get; set; }
@ -282,7 +282,7 @@ public class AdminProfileAuditDetailDto
public string WeChatNo { get; set; } = string.Empty;
/// <summary>
/// 审核状态0待审核 1已通过 2已拒绝
/// 审核状态0未提交 1已通过 2审核中 3已拒绝
/// </summary>
public int AuditStatus { get; set; }

View File

@ -81,7 +81,7 @@ public class AdminUserListDto
public string MemberLevelText { get; set; } = string.Empty;
/// <summary>
/// 资料审核状态0待审核 1已通过 2已拒绝
/// 资料审核状态0未提交 1已通过 2审核中 3已拒绝
/// </summary>
public int? AuditStatus { get; set; }
@ -394,7 +394,7 @@ public class AdminUserProfileDto
public string WeChatNo { get; set; } = string.Empty;
/// <summary>
/// 审核状态0待审核 1已通过 2已拒绝
/// 审核状态0未提交 1已通过 2审核中 3已拒绝
/// </summary>
public int AuditStatus { get; set; }

View File

@ -136,7 +136,7 @@ public class ProfileResponse
public string? Phone { get; set; }
/// <summary>
/// 审核状态0待审核 1已通过 2已拒绝
/// 审核状态0未提交 1已通过 2审核中 3已拒绝
/// </summary>
public int AuditStatus { get; set; }

View File

@ -212,14 +212,26 @@ public class AdminProfileAuditService : IAdminProfileAuditService
// Pending (0) -> Rejected (2): 待审核 -> 已拒绝
// Rejected (2) -> Approved (1): 已拒绝 -> 已通过 (重新审核通过)
// Rejected (2) -> Rejected (2): 已拒绝 -> 已拒绝 (更新拒绝原因)
// 状态说明:
// NotSubmitted (0): 未提交资料
// Approved (1): 已通过审核
// Pending (2): 审核中
// Rejected (3): 已拒绝
//
// 有效的状态转换:
// Pending (2) -> Approved (1): 审核通过
// Pending (2) -> Rejected (3): 审核拒绝
// Rejected (3) -> Approved (1): 重新审核通过
// Rejected (3) -> Rejected (3): 更新拒绝原因
//
// Invalid transitions:
// Approved (1) -> Pending (0): 已通过不能回到待审核
// Approved (1) -> Rejected (2): 已通过不能直接拒绝(需要用户重新提交)
// Any -> Pending (0): 不能手动设置为待审核(只有用户提交/更新资料时才会设为待审核)
// 无效的状态转换:
// Approved (1) -> Pending (2): 已通过不能回到审核中
// Approved (1) -> Rejected (3): 已通过不能直接拒绝(需要用户重新提交)
// Any -> Pending (2): 不能手动设置为审核中(只有用户提交/更新资料时才会设为审核中)
// Any -> NotSubmitted (0): 不能手动设置为未提交
// Cannot transition to Pending status manually
if (targetStatus == (int)AuditStatus.Pending)
// Cannot transition to Pending or NotSubmitted status manually
if (targetStatus == (int)AuditStatus.Pending || targetStatus == (int)AuditStatus.NotSubmitted)
{
return false;
}
@ -363,9 +375,10 @@ public class AdminProfileAuditService : IAdminProfileAuditService
{
return auditStatus switch
{
0 => "待审核",
0 => "未提交",
1 => "已通过",
2 => "已拒绝",
2 => "审核中",
3 => "已拒绝",
_ => "未知"
};
}

View File

@ -735,9 +735,10 @@ public class AdminUserService : IAdminUserService
{
return auditStatus switch
{
0 => "待审核",
0 => "未提交",
1 => "已通过",
2 => "已拒绝",
2 => "审核中",
3 => "已拒绝",
_ => "未知"
};
}

View File

@ -557,10 +557,11 @@ public class ChatService : IChatService
extraData.SenderWeChat = senderProfile?.WeChatNo;
extraData.ReceiverWeChat = receiverProfile?.WeChatNo;
// 包含 RequestMessageId 以便前端精确匹配
// 包含 RequestMessageId 和 Status 以便前端精确匹配和状态更新
exchangedData = JsonSerializer.Serialize(new
{
RequestMessageId = request.RequestMessageId,
Status = 1, // 已同意
SenderWeChat = extraData.SenderWeChat,
ReceiverWeChat = extraData.ReceiverWeChat
});
@ -574,10 +575,11 @@ public class ChatService : IChatService
extraData.SenderPhotos = senderPhotos.OrderBy(p => p.Sort).Select(p => p.PhotoUrl).ToList();
extraData.ReceiverPhotos = receiverPhotos.OrderBy(p => p.Sort).Select(p => p.PhotoUrl).ToList();
// 包含 RequestMessageId 以便前端精确匹配
// 包含 RequestMessageId 和 Status 以便前端精确匹配和状态更新
exchangedData = JsonSerializer.Serialize(new
{
RequestMessageId = request.RequestMessageId,
Status = 1, // 已同意
SenderPhotos = extraData.SenderPhotos,
ReceiverPhotos = extraData.ReceiverPhotos
});

View File

@ -121,6 +121,12 @@ public class InteractService : IInteractService
foreach (var item in items)
{
var user = await _userRepository.GetByIdAsync(item.UserId);
// 跳过已删除的用户
if (user == null || user.IsDeleted)
{
continue;
}
var profile = (await _profileRepository.GetListAsync(p => p.UserId == item.UserId)).FirstOrDefault();
var photos = await _photoRepository.GetListAsync(p => p.UserId == item.UserId);
var firstPhoto = photos.OrderBy(p => p.Sort).FirstOrDefault();
@ -173,6 +179,12 @@ public class InteractService : IInteractService
foreach (var item in items)
{
var user = await _userRepository.GetByIdAsync(item.TargetUserId);
// 跳过已删除的用户
if (user == null || user.IsDeleted)
{
continue;
}
var profile = (await _profileRepository.GetListAsync(p => p.UserId == item.TargetUserId)).FirstOrDefault();
var photos = await _photoRepository.GetListAsync(p => p.UserId == item.TargetUserId);
var firstPhoto = photos.OrderBy(p => p.Sort).FirstOrDefault();
@ -320,6 +332,12 @@ public class InteractService : IInteractService
foreach (var item in items)
{
var user = await _userRepository.GetByIdAsync(item.UserId);
// 跳过已删除的用户
if (user == null || user.IsDeleted)
{
continue;
}
var profile = (await _profileRepository.GetListAsync(p => p.UserId == item.UserId)).FirstOrDefault();
var photos = await _photoRepository.GetListAsync(p => p.UserId == item.UserId);
var firstPhoto = photos.OrderBy(p => p.Sort).FirstOrDefault();
@ -371,6 +389,12 @@ public class InteractService : IInteractService
foreach (var item in items)
{
var user = await _userRepository.GetByIdAsync(item.TargetUserId);
// 跳过已删除的用户
if (user == null || user.IsDeleted)
{
continue;
}
var profile = (await _profileRepository.GetListAsync(p => p.UserId == item.TargetUserId)).FirstOrDefault();
var photos = await _photoRepository.GetListAsync(p => p.UserId == item.TargetUserId);
var firstPhoto = photos.OrderBy(p => p.Sort).FirstOrDefault();
@ -522,6 +546,12 @@ public class InteractService : IInteractService
foreach (var item in items)
{
var user = await _userRepository.GetByIdAsync(item.UserId);
// 跳过已删除的用户
if (user == null || user.IsDeleted)
{
continue;
}
var profile = (await _profileRepository.GetListAsync(p => p.UserId == item.UserId)).FirstOrDefault();
var photos = await _photoRepository.GetListAsync(p => p.UserId == item.UserId);
var firstPhoto = photos.OrderBy(p => p.Sort).FirstOrDefault();
@ -573,6 +603,12 @@ public class InteractService : IInteractService
foreach (var item in items)
{
var user = await _userRepository.GetByIdAsync(item.TargetUserId);
// 跳过已删除的用户
if (user == null || user.IsDeleted)
{
continue;
}
var profile = (await _profileRepository.GetListAsync(p => p.UserId == item.TargetUserId)).FirstOrDefault();
var photos = await _photoRepository.GetListAsync(p => p.UserId == item.TargetUserId);
var firstPhoto = photos.OrderBy(p => p.Sort).FirstOrDefault();

View File

@ -104,7 +104,7 @@ public class ProfileService : IProfileService
ParentHousingStatus = request.ParentHousingStatus,
ParentPensionStatus = request.ParentPensionStatus,
ParentMedicalStatus = request.ParentMedicalStatus,
AuditStatus = (int)AuditStatus.Pending, // 新资料设为审核
AuditStatus = (int)AuditStatus.Pending, // 新资料设为审核
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
@ -147,7 +147,7 @@ public class ProfileService : IProfileService
existingProfile.ParentHousingStatus = request.ParentHousingStatus;
existingProfile.ParentPensionStatus = request.ParentPensionStatus;
existingProfile.ParentMedicalStatus = request.ParentMedicalStatus;
existingProfile.AuditStatus = (int)AuditStatus.Pending; // 更新资料重置为审核
existingProfile.AuditStatus = (int)AuditStatus.Pending; // 更新资料重置为审核
existingProfile.RejectReason = null; // 清除之前的拒绝原因
existingProfile.UpdateTime = DateTime.Now;

View File

@ -128,7 +128,7 @@ public class UserProfile : BaseEntity
public string WeChatNo { get; set; } = string.Empty;
/// <summary>
/// 审核状态0待审核 1已通过 2已拒绝
/// 审核状态0未提交 1已通过 2审核中 3已拒绝
/// </summary>
public int AuditStatus { get; set; } = 0;

View File

@ -146,17 +146,22 @@ public enum ExpectMarryTime
public enum AuditStatus
{
/// <summary>
/// 待审核
/// 未提交资料
/// </summary>
Pending = 0,
NotSubmitted = 0,
/// <summary>
/// 已通过
/// 已通过审核
/// </summary>
Approved = 1,
/// <summary>
/// 审核中
/// </summary>
Pending = 2,
/// <summary>
/// 已拒绝
/// </summary>
Rejected = 2
Rejected = 3
}

View File

@ -37,6 +37,22 @@ public class AliyunRealNameProvider : IRealNameProvider
{
try
{
// 验证配置
if (string.IsNullOrEmpty(_options.AccessKeyId))
{
_logger.LogError("阿里云实名认证配置错误: AccessKeyId 为空");
return RealNameResult.Fail("CONFIG_ERROR", "实名认证服务配置错误,请联系管理员");
}
if (string.IsNullOrEmpty(_options.AccessKeySecret))
{
_logger.LogError("阿里云实名认证配置错误: AccessKeySecret 为空");
return RealNameResult.Fail("CONFIG_ERROR", "实名认证服务配置错误,请联系管理员");
}
_logger.LogInformation("阿里云实名认证配置: AccessKeyId={AccessKeyId}, SecretLength={SecretLength}",
_options.AccessKeyId[..8] + "***",
_options.AccessKeySecret?.Length ?? 0);
var parameters = new SortedDictionary<string, string>
{
{ "Action", "Id2MetaVerify" },
@ -129,7 +145,9 @@ public class AliyunRealNameProvider : IRealNameProvider
var stringToSign = $"GET&%2F&{PercentEncode(canonicalizedQueryString)}";
using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(_options.AccessKeySecret + "&"));
// 确保 AccessKeySecret 不为空
var secretKey = _options.AccessKeySecret ?? string.Empty;
using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(secretKey + "&"));
var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
return Convert.ToBase64String(hashBytes);
}