Преглед изворни кода

Merge remote-tracking branch 'origin/test_dev' into test_dev

# Conflicts:
#	LiangZhiYUMao/pages/plaza/detail.vue
yuxy пре 1 месец
родитељ
комит
bd38bccf3c
20 измењених фајлова са 1677 додато и 272 уклоњено
  1. 137 4
      LiangZhiYUMao/pages/message/chat.vue
  2. 339 67
      LiangZhiYUMao/pages/mine/my-dynamics.vue
  3. 144 17
      LiangZhiYUMao/pages/plaza/detail.vue
  4. 75 24
      LiangZhiYUMao/pages/plaza/index.vue
  5. 98 39
      LiangZhiYUMao/pages/plaza/publish.vue
  6. 107 25
      LiangZhiYUMao/utils/api.js
  7. 10 82
      service/admin/src/main/java/com/zhentao/controller/UserController.java
  8. 70 0
      service/dynamic/src/main/java/com/zhentao/controller/DynamicController.java
  9. 25 0
      service/dynamic/src/main/java/com/zhentao/service/UserDynamicsService.java
  10. 32 0
      service/dynamic/src/main/java/com/zhentao/service/impl/BrowseHistoryServiceImpl.java
  11. 81 14
      service/dynamic/src/main/java/com/zhentao/service/impl/InteractionServiceImpl.java
  12. 180 0
      service/dynamic/src/main/java/com/zhentao/service/impl/UserDynamicsServiceImpl.java
  13. 15 0
      service/websocket/src/main/java/com/zhentao/controller/ChatController.java
  14. 153 0
      service/websocket/src/main/java/com/zhentao/entity/UserVip.java
  15. 18 0
      service/websocket/src/main/java/com/zhentao/mapper/UserVipMapper.java
  16. 18 0
      service/websocket/src/main/java/com/zhentao/service/UserVipService.java
  17. 128 0
      service/websocket/src/main/java/com/zhentao/service/impl/UserVipServiceImpl.java
  18. 1 0
      service/websocket/src/main/java/com/zhentao/vo/ResultVo.java
  19. 18 0
      service/websocket/src/main/java/com/zhentao/vo/UserVipVo.java
  20. 28 0
      service/websocket/src/main/resources/com/zhentao/mapper/UserVipMapper.xml

+ 137 - 4
LiangZhiYUMao/pages/message/chat.vue

@@ -149,7 +149,15 @@
         </view>
       </view>
     </scroll-view>
-
+		<!-- 新增:消息发送限制提示 -->
+		<view class="message-limit-tip">
+		  <!-- 同时判断isVip和hasMessageLimit,增强可靠性     -->
+		  <text v-if="isVip || !hasMessageLimit" class="vip-tip">✨ VIP特权:无发送次数限制</text>
+		  <text v-else class="limit-tip">
+		    今日剩余可发送消息:{{ Math.max(remainingCount, 0) }} 条<text class="vip-link" @click="goToVipPage">(非VIP每日限5条)</text>
+		  </text>
+		</view>
+		
     <!-- 输入框 -->
     <view class="input-bar">
       <!-- 语音按钮 -->
@@ -276,8 +284,10 @@ export default {
       
       emojis: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳'],
 		showMoreOptionsModal: false, // 控制更多选项弹窗显示
-	    showBlockConfirmModal: false // 控制拉黑确认弹窗显示
-	
+	    showBlockConfirmModal: false, // 控制拉黑确认弹窗显示
+		isVip: false, // 是否VIP用户
+	    remainingCount: 5, // 剩余可发送消息数(非VIP默认5)
+	    hasMessageLimit: true // 是否有发送限制(VIP为false)
 	};
   },
   
@@ -355,7 +365,8 @@ export default {
     
     // 初始化 TIM
     await this.initTIM();
-    
+	// 获取用户消息发送限制(VIP状态+剩余次数)
+    await this.getUserMessageLimit();
     // 等待 SDK Ready 后再加载消息
     await this.waitForSDKReady();
     // 先检查拉黑状态
@@ -410,6 +421,26 @@ export default {
         });
       }
     },
+	/**
+	   * 跳转到VIP页面
+	   */
+	  goToVipPage() {
+	    console.log('点击跳转VIP页面');
+	    // 替换为你的实际VIP页面路径(例如会员开通页面)
+	    uni.navigateTo({
+	      url: '/pages/vip/index', // 请根据项目实际路径修改
+	      success: () => {
+	        console.log('跳转VIP页面成功');
+	      },
+	      fail: (err) => {
+	        console.error('跳转VIP页面失败:', err);
+	        uni.showToast({
+	          title: 'VIP页面不存在',
+	          icon: 'none'
+	        });
+	      }
+	    });
+	  },
     more(userid) {
       console.log('点击了更多按钮,开始跳转...', userid);
       // 如果目标页面是普通页面,用navigateTo(保留当前页面);如果是tabbar页面,用switchTab
@@ -725,6 +756,16 @@ export default {
      * 发送文本消息
      */
     async sendTextMessage() {
+		
+		if (this.hasMessageLimit && this.remainingCount <= 0) {
+		    uni.showToast({
+		      title: '今日消息发送次数已用完,开通VIP无限制',
+		      icon: 'none',
+		      duration: 2000
+		    });
+		    return;
+		  }
+		  
       if (!this.inputText.trim()) {
         return;
       }
@@ -794,6 +835,9 @@ export default {
         
         console.log('✅ 消息发送成功');
         this.syncMessageToMySQL(message);
+		if (!this.isVip) {
+		    await this.updateMessageCount();
+		  }
       } catch (error) {
         console.error('❌ 消息发送失败:', error);
         
@@ -1315,7 +1359,69 @@ export default {
 		    closeBlockConfirm() {
 		      this.showBlockConfirmModal = false;
 		    },
+		    /**
+		     * 从后端获取VIP状态和今日剩余发送次数
+		     */
+		    async getUserMessageLimit() {
+		      try {
+		        const [err, res] = await uni.request({
+		          url: 'http://localhost:1004/api/chat/getUserMessageLimit',
+		          method: 'GET',
+		          data: {
+		            userId: this.userId ,// 已在onLoad中初始化的当前用户ID
+					targetUserId: this.targetUserId
+		          },
+		          header: {
+		            'Content-Type': 'application/json'
+		          }
+		        });
+		    
+		        if (err) throw new Error('网络请求失败');
+		        if (res.data.code !== 200) throw new Error(res.data.message || '获取限制信息失败');
+		    
+		        const { isVip, remainingCount, hasMessageLimit } = res.data.data;
+		        this.isVip = isVip;
+		        this.remainingCount = remainingCount;
+		        this.hasMessageLimit = hasMessageLimit;
 		    
+		        console.log('✅ 用户消息限制信息:', { isVip, remainingCount, hasMessageLimit });
+		      } catch (error) {
+		        console.error('❌ 获取消息限制失败:', error);
+		        // 异常降级:默认非VIP,剩余5次
+		        this.isVip = false;
+		        this.remainingCount = 5;
+		        this.hasMessageLimit = true;
+		      }
+		    },
+			/**
+			 * 发送成功后,更新后端计数并同步前端剩余次数
+			 */
+			async updateMessageCount() {
+			  try {
+			    const [err, res] = await uni.request({
+			      url: 'http://localhost:8083/api/chat/updateMessageCount',
+			      method: 'get',
+			      data: {
+			        userId: this.userId ,// 已在onLoad中初始化的当前用户ID
+			        targetUserId: this.targetUserId
+			      },
+			      header: {
+			        'Content-Type': 'application/json'
+			      }
+			    });
+			
+			    if (err) throw new Error('更新计数失败');
+			    if (res.data.code === 200) {
+			      this.remainingCount = res.data.data.remainingCount; // 同步后端返回的剩余次数
+			    }
+			  } catch (error) {
+			    console.error('❌ 更新发送次数失败:', error);
+			    // 前端本地降级:避免影响用户体验,本地暂减1(刷新页面后同步真实数据)
+			    if (this.remainingCount > 0) {
+			      this.remainingCount--;
+			    }
+			  }
+			},
 		    /**
 		     * 确认拉黑好友
 		     */
@@ -1828,6 +1934,33 @@ export default {
 .message-action-menu .menu-icon {
   font-size: 36rpx;
 }
+
+/* 消息发送限制提示 */
+.message-limit-tip {
+  padding: 10rpx 20rpx;
+  font-size: 24rpx;
+  text-align: center;
+  background-color: #fff;
+  border-bottom: 1px solid #f5f5f5;
+}
+.vip-tip {
+  color: #ff9500; /* VIP橙色提示 */
+}
+.limit-tip {
+  color: #666; /* 普通灰色提示 */
+}
+
+.vip-link {
+  color: #2c9fff; /* 蓝色文字 */
+  text-decoration: underline; /* 下划线 */
+  cursor: pointer; /* 鼠标悬浮时显示手型 */
+  margin-left: 5rpx;
+}
+
+/* 点击时添加轻微反馈 */
+.vip-link:active {
+  opacity: 0.7; /* 点击时透明度降低 */
+}
 </style>
 
 

+ 339 - 67
LiangZhiYUMao/pages/mine/my-dynamics.vue

@@ -68,28 +68,34 @@
         <view class="empty-tip" v-if="dynamicList.length === 0">
           <text>暂无动态</text>
         </view>
-        <view class="dynamic-item" v-for="item in dynamicList" :key="item.dynamicId" @click="goToDetail(item.dynamicId)">
-          <!-- 动态内容 -->
-          <view class="dynamic-content">
-            <text class="dynamic-text">{{ item.content }}</text>
+        <view class="dynamic-item" v-for="item in dynamicList" :key="item.dynamicId">
+          <!-- 操作菜单按钮 -->
+          <view class="dynamic-menu" @click.stop="showActionMenu(item)">
+            <text class="menu-icon">⋯</text>
           </view>
-          <!-- 动态图片 -->
-          <view class="dynamic-images" v-if="item.mediaUrls">
-            <image
-                class="dynamic-image"
-                v-for="(img, index) in getMediaUrls(item.mediaUrls)"
-                :key="index"
-                :src="img"
-                mode="aspectFill"
-                @click.stop="previewImage(getMediaUrls(item.mediaUrls), index)"
-            ></image>
-          </view>
-          <!-- 动态信息 -->
-          <view class="dynamic-info">
-            <text class="dynamic-time">{{ formatTime(item.createdAt) }}</text>
-            <view class="dynamic-stats">
-              <text class="stat-item">{{ item.likeCount || 0 }} 赞</text>
-              <text class="stat-item">{{ item.commentCount || 0 }} 评论</text>
+          <view @click="goToDetail(item.dynamicId)">
+            <!-- 动态内容 -->
+            <view class="dynamic-content">
+              <text class="dynamic-text">{{ item.content }}</text>
+            </view>
+            <!-- 动态图片 -->
+            <view class="dynamic-images" v-if="item.mediaUrls">
+              <image
+                  class="dynamic-image"
+                  v-for="(img, index) in getMediaUrls(item.mediaUrls)"
+                  :key="index"
+                  :src="img"
+                  mode="aspectFill"
+                  @click.stop="previewImage(getMediaUrls(item.mediaUrls), index)"
+              ></image>
+            </view>
+            <!-- 动态信息 -->
+            <view class="dynamic-info">
+              <text class="dynamic-time">{{ formatTime(item.createdAt) }}</text>
+              <view class="dynamic-stats">
+                <text class="stat-item">{{ item.likeCount || 0 }} 赞</text>
+                <text class="stat-item">{{ item.commentCount || 0 }} 评论</text>
+              </view>
             </view>
           </view>
         </view>
@@ -100,35 +106,41 @@
         <view class="empty-tip" v-if="interactionList.length === 0">
           <text>暂无互动</text>
         </view>
-        <view class="interaction-item" v-for="item in interactionList" :key="item.dynamicId" @click="goToDetail(item.dynamicId)">
-          <!-- 用户信息 -->
-          <view class="user-info">
-            <image class="avatar" :src="getAvatar(item)" mode="aspectFill"></image>
-            <view class="user-details">
-              <text class="nickname">{{ getNickname(item) }}</text>
-            </view>
-          </view>
-          <!-- 互动内容 -->
-          <view class="interaction-content">
-            <text class="interaction-text">{{ item.content }}</text>
-          </view>
-          <!-- 互动图片 -->
-          <view class="interaction-images" v-if="item.mediaUrls">
-            <image
-                class="interaction-image"
-                v-for="(img, index) in getMediaUrls(item.mediaUrls)"
-                :key="index"
-                :src="img"
-                mode="aspectFill"
-                @click.stop="previewImage(getMediaUrls(item.mediaUrls), index)"
-            ></image>
+        <view class="interaction-item" v-for="item in interactionList" :key="item.dynamicId">
+          <!-- 操作菜单按钮 -->
+          <view class="interaction-menu" @click.stop="showInteractionMenu(item)">
+            <text class="menu-icon">⋯</text>
           </view>
-          <!-- 互动信息 -->
-          <view class="interaction-info">
-            <text class="interaction-time">{{ formatTime(item.createdAt) }}</text>
-            <view class="interaction-stats">
-              <text class="stat-item">{{ item.likeCount || 0 }} 赞</text>
-              <text class="stat-item">{{ item.commentCount || 0 }} 评论</text>
+          <view @click="goToDetail(item.dynamicId)">
+            <!-- 用户信息 -->
+            <view class="user-info">
+              <image class="avatar" :src="getAvatar(item)" mode="aspectFill"></image>
+              <view class="user-details">
+                <text class="nickname">{{ getNickname(item) }}</text>
+              </view>
+            </view>
+            <!-- 互动内容 -->
+            <view class="interaction-content">
+              <text class="interaction-text">{{ item.content }}</text>
+            </view>
+            <!-- 互动图片 -->
+            <view class="interaction-images" v-if="item.mediaUrls">
+              <image
+                  class="interaction-image"
+                  v-for="(img, index) in getMediaUrls(item.mediaUrls)"
+                  :key="index"
+                  :src="img"
+                  mode="aspectFill"
+                  @click.stop="previewImage(getMediaUrls(item.mediaUrls), index)"
+              ></image>
+            </view>
+            <!-- 互动信息 -->
+            <view class="interaction-info">
+              <text class="interaction-time">{{ formatTime(item.createdAt) }}</text>
+              <view class="interaction-stats">
+                <text class="stat-item">{{ item.likeCount || 0 }} 赞</text>
+                <text class="stat-item">{{ item.commentCount || 0 }} 评论</text>
+              </view>
             </view>
           </view>
         </view>
@@ -245,7 +257,12 @@ export default {
     },
     // 切换互动子标签
     switchSubTab(subTab) {
+      if (this.activeSubTab === subTab) {
+        return; // 如果点击的是当前标签,不重复加载
+      }
       this.activeSubTab = subTab;
+      // 先清空列表
+      this.interactionList = [];
       // 根据子标签加载对应数据
       this.loadInteractionData();
     },
@@ -297,26 +314,59 @@ export default {
     // 加载互动数据
     loadInteractionData() {
       const userInfo = uni.getStorageSync('userInfo');
-      if (userInfo && userInfo.userId) {
-        if (this.activeSubTab === 'like') {
-          // 获取用户点赞的动态列表
-          api.dynamic.getLikedList(userInfo.userId, 1, 10).then(res => {
-            if (res && res.records) {
-              this.interactionList = res.records;
-            }
-          }).catch(err => {
-            console.error('获取用户点赞列表失败:', err);
+      if (!userInfo || !userInfo.userId) {
+        console.warn('用户信息不存在,无法加载互动数据');
+        this.interactionList = [];
+        return;
+      }
+      
+      // 先清空列表,避免显示旧数据
+      this.interactionList = [];
+      
+      if (this.activeSubTab === 'like') {
+        // 获取用户点赞的动态列表
+        console.log('开始加载点赞列表,userId:', userInfo.userId);
+        api.dynamic.getLikedList(userInfo.userId, 1, 10).then(res => {
+          console.log('点赞列表API返回数据:', res);
+          if (res) {
+            // 处理返回数据,支持多种可能的数据结构
+            const records = res.records || res.list || (Array.isArray(res) ? res : []);
+            this.interactionList = records || [];
+            console.log('设置点赞列表,数量:', this.interactionList.length);
+          } else {
+            this.interactionList = [];
+            console.log('点赞列表返回数据为空');
+          }
+        }).catch(err => {
+          console.error('获取用户点赞列表失败:', err);
+          this.interactionList = [];
+          uni.showToast({
+            title: '加载点赞列表失败',
+            icon: 'none'
           });
-        } else if (this.activeSubTab === 'collect') {
-          // 获取用户收藏的动态列表
-          api.dynamic.getFavoritesList(userInfo.userId, 1, 10).then(res => {
-            if (res && res.records) {
-              this.interactionList = res.records;
-            }
-          }).catch(err => {
-            console.error('获取用户收藏列表失败:', err);
+        });
+      } else if (this.activeSubTab === 'collect') {
+        // 获取用户收藏的动态列表
+        console.log('开始加载收藏列表,userId:', userInfo.userId);
+        api.dynamic.getFavoritesList(userInfo.userId, 1, 10).then(res => {
+          console.log('收藏列表API返回数据:', res);
+          if (res) {
+            // 处理返回数据,支持多种可能的数据结构
+            const records = res.records || res.list || (Array.isArray(res) ? res : []);
+            this.interactionList = records || [];
+            console.log('设置收藏列表,数量:', this.interactionList.length);
+          } else {
+            this.interactionList = [];
+            console.log('收藏列表返回数据为空');
+          }
+        }).catch(err => {
+          console.error('获取用户收藏列表失败:', err);
+          this.interactionList = [];
+          uni.showToast({
+            title: '加载收藏列表失败',
+            icon: 'none'
           });
-        }
+        });
       }
     },
     // 加载浏览记录数据
@@ -420,6 +470,187 @@ export default {
           }
         }
       });
+    },
+    // 显示操作菜单
+    showActionMenu(item) {
+      uni.showActionSheet({
+        itemList: ['编辑动态', '删除动态'],
+        success: (res) => {
+          if (res.tapIndex === 0) {
+            // 编辑动态
+            this.editDynamic(item);
+          } else if (res.tapIndex === 1) {
+            // 删除动态
+            this.deleteDynamic(item);
+          }
+        }
+      });
+    },
+    // 编辑动态
+    editDynamic(item) {
+      // 跳转到编辑页面,传递动态信息
+      uni.navigateTo({
+        url: `/pages/plaza/publish?edit=true&dynamicId=${item.dynamicId}&content=${encodeURIComponent(item.content || '')}&mediaUrls=${encodeURIComponent(JSON.stringify(item.mediaUrls || []))}&mediaType=${item.mediaType || 0}&visibility=${item.visibility || 1}`
+      });
+    },
+    // 删除动态
+    deleteDynamic(item) {
+      uni.showModal({
+        title: '确认删除',
+        content: '确定要删除这条动态吗?删除后无法恢复。',
+        confirmText: '删除',
+        confirmColor: '#E91E63',
+        success: async (res) => {
+          if (res.confirm) {
+            try {
+              const userInfo = uni.getStorageSync('userInfo');
+              if (!userInfo || !userInfo.userId) {
+                uni.showToast({
+                  title: '请先登录',
+                  icon: 'none'
+                });
+                return;
+              }
+              
+              uni.showLoading({
+                title: '删除中...'
+              });
+              
+              await api.dynamic.deleteUserDynamic(item.dynamicId, userInfo.userId);
+              
+              uni.hideLoading();
+              uni.showToast({
+                title: '删除成功',
+                icon: 'success'
+              });
+              
+              // 重新加载动态列表
+              this.loadDynamicData();
+            } catch (error) {
+              uni.hideLoading();
+              console.error('删除动态失败:', error);
+              uni.showToast({
+                title: error.message || '删除失败',
+                icon: 'none'
+              });
+            }
+          }
+        }
+      });
+    },
+    // 显示互动操作菜单
+    showInteractionMenu(item) {
+      const menuItems = [];
+      if (this.activeSubTab === 'like') {
+        menuItems.push('取消点赞');
+      } else if (this.activeSubTab === 'collect') {
+        menuItems.push('取消收藏');
+      }
+      
+      if (menuItems.length === 0) return;
+      
+      uni.showActionSheet({
+        itemList: menuItems,
+        success: (res) => {
+          if (res.tapIndex === 0) {
+            if (this.activeSubTab === 'like') {
+              this.cancelLike(item);
+            } else if (this.activeSubTab === 'collect') {
+              this.cancelFavorite(item);
+            }
+          }
+        }
+      });
+    },
+    // 取消点赞
+    async cancelLike(item) {
+      try {
+        const userInfo = uni.getStorageSync('userInfo');
+        if (!userInfo || !userInfo.userId) {
+          uni.showToast({
+            title: '请先登录',
+            icon: 'none'
+          });
+          return;
+        }
+        
+        uni.showLoading({
+          title: '处理中...'
+        });
+        
+        await api.dynamic.unlike(item.dynamicId, userInfo.userId);
+        
+        uni.hideLoading();
+        uni.showToast({
+          title: '已取消点赞',
+          icon: 'success'
+        });
+        
+        // 通知其他页面更新状态(如首页、详情页)
+        uni.$emit('dynamic-updated', {
+          dynamicId: item.dynamicId,
+          isLiked: false,
+          likeCount: Math.max(0, (item.likeCount || 0) - 1)
+        });
+        
+        // 从列表中移除该项
+        const index = this.interactionList.findIndex(i => i.dynamicId === item.dynamicId);
+        if (index !== -1) {
+          this.interactionList.splice(index, 1);
+        }
+      } catch (error) {
+        uni.hideLoading();
+        console.error('取消点赞失败:', error);
+        uni.showToast({
+          title: error.message || '操作失败',
+          icon: 'none'
+        });
+      }
+    },
+    // 取消收藏
+    async cancelFavorite(item) {
+      try {
+        const userInfo = uni.getStorageSync('userInfo');
+        if (!userInfo || !userInfo.userId) {
+          uni.showToast({
+            title: '请先登录',
+            icon: 'none'
+          });
+          return;
+        }
+        
+        uni.showLoading({
+          title: '处理中...'
+        });
+        
+        await api.dynamic.unfavorite(item.dynamicId, userInfo.userId);
+        
+        uni.hideLoading();
+        uni.showToast({
+          title: '已取消收藏',
+          icon: 'success'
+        });
+        
+        // 通知其他页面更新状态(如首页、详情页)
+        uni.$emit('dynamic-updated', {
+          dynamicId: item.dynamicId,
+          isFavorited: false,
+          favoriteCount: Math.max(0, (item.favoriteCount || 0) - 1)
+        });
+        
+        // 从列表中移除该项
+        const index = this.interactionList.findIndex(i => i.dynamicId === item.dynamicId);
+        if (index !== -1) {
+          this.interactionList.splice(index, 1);
+        }
+      } catch (error) {
+        uni.hideLoading();
+        console.error('取消收藏失败:', error);
+        uni.showToast({
+          title: error.message || '操作失败',
+          icon: 'none'
+        });
+      }
     }
   }
 }
@@ -585,12 +816,53 @@ export default {
 .browse-item {
   padding: 30rpx;
   border-bottom: 2rpx solid #F5F5F5;
+  position: relative;
 
   &:last-child {
     border-bottom: none;
   }
 }
 
+/* 动态操作菜单 */
+.dynamic-menu {
+  position: absolute;
+  top: 30rpx;
+  right: 30rpx;
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 10;
+
+  .menu-icon {
+    font-size: 40rpx;
+    color: #999999;
+    font-weight: bold;
+    line-height: 1;
+  }
+}
+
+/* 互动操作菜单 */
+.interaction-menu {
+  position: absolute;
+  top: 30rpx;
+  right: 30rpx;
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 10;
+
+  .menu-icon {
+    font-size: 40rpx;
+    color: #999999;
+    font-weight: bold;
+    line-height: 1;
+  }
+}
+
 /* 用户信息 */
 .user-info {
   display: flex;

+ 144 - 17
LiangZhiYUMao/pages/plaza/detail.vue

@@ -162,10 +162,24 @@
 		<view v-if="showMenu" class="menu-popup">
 			<view class="menu-mask" @click="hideMoreMenu"></view>
 			<view class="menu-content">
-				<view class="menu-item" @click="handleReport">
-					<text class="menu-icon">🚩</text>
-					<text class="menu-text">举报</text>
-				</view>
+				<!-- 如果是自己的动态,显示编辑和删除 -->
+				<template v-if="isMyDynamic">
+					<view class="menu-item" @click="handleEdit">
+						<text class="menu-icon">✏️</text>
+						<text class="menu-text">编辑动态</text>
+					</view>
+					<view class="menu-item" @click="handleDelete">
+						<text class="menu-icon">🗑️</text>
+						<text class="menu-text">删除动态</text>
+					</view>
+				</template>
+				<!-- 如果不是自己的动态,显示举报 -->
+				<template v-else>
+					<view class="menu-item" @click="handleReport">
+						<text class="menu-icon">🚩</text>
+						<text class="menu-text">举报</text>
+					</view>
+				</template>
 				<view class="menu-item cancel" @click="hideMoreMenu">
 					<text class="menu-text">取消</text>
 				</view>
@@ -229,18 +243,42 @@ export default {
 		}
 	},
 	
+	computed: {
+		// 判断是否是自己的动态
+		isMyDynamic() {
+			if (!this.dynamic || !this.currentUserId) return false
+			return this.dynamic.userId === this.currentUserId ||
+			       (this.dynamic.user && this.dynamic.user.userId === this.currentUserId)
+		}
+	},
+
 	onLoad(options) {
-		// 兼容 id 和 dynamicId 两种参数名
 		if (options.id) {
 			this.dynamicId = options.id
+			// 从存储中获取当前用户ID
+			const storedUserId = uni.getStorageSync('userId')
+			if (storedUserId !== null && storedUserId !== undefined && storedUserId !== '') {
+				this.currentUserId = parseInt(storedUserId)
+			} else {
+				// 尝试从 userInfo 中获取
+				const userInfo = uni.getStorageSync('userInfo')
+				if (userInfo && (userInfo.userId || userInfo.id || userInfo.user_id)) {
+					this.currentUserId = parseInt(userInfo.userId || userInfo.id || userInfo.user_id)
+				} else {
+					// 未登录,设置为 null
+					this.currentUserId = null
+				}
+			}
+            this.loadDynamicDetail()
+            this.loadComments()
 		} else if (options.dynamicId) {
 			this.dynamicId = options.dynamicId
 		}
-		
+
 		if (this.dynamicId) {
 			this.loadDynamicDetail()
 			this.loadComments()
-			
+
 			// 如果需要滚动到评论区域
 			if (options.scrollToComment === 'true') {
 				this.$nextTick(() => {
@@ -344,7 +382,13 @@ export default {
 			try {
 				const res = await api.dynamic.getDetail(this.dynamicId, this.currentUserId)
 				if (res) {
-					this.dynamic = res
+					// 处理数据,确保布尔值正确转换(与列表页保持一致)
+					// 根据 dynamic_likes 和 dynamic_favorites 表判断:有记录显示 ❤️/⭐,无记录显示 🤍/☆
+					this.dynamic = {
+						...res,
+						isLiked: res.isLiked === true || res.isLiked === 1 || res.isLiked === 'true' || res.isLiked === '1',
+						isFavorited: res.isFavorited === true || res.isFavorited === 1 || res.isFavorited === 'true' || res.isFavorited === '1'
+					}
 				}
 			} catch (error) {
 				console.error('加载详情失败:', error)
@@ -366,7 +410,8 @@ export default {
 			try {
 				this.isLiking = true
 				
-				const isLiked = this.dynamic.isLiked
+				// 根据 dynamic_likes 表判断是否点赞(有记录显示 ❤️,无记录显示 🤍)
+				const isLiked = this.dynamic.isLiked === true || this.dynamic.isLiked === 1 || this.dynamic.isLiked === 'true' || this.dynamic.isLiked === '1'
 				const originalCount = this.dynamic.likeCount || 0
 				
 				// 立即更新UI(乐观更新)
@@ -380,17 +425,20 @@ export default {
 					await api.dynamic.like(this.dynamic.dynamicId)
 				}
 				
-                // 通知列表页同步状态
+                // 通知列表页同步状态(同时发送两个字段,确保状态完整同步)
                 uni.$emit('dynamic-updated', {
                     dynamicId: this.dynamic.dynamicId,
                     isLiked: this.dynamic.isLiked,
-                    likeCount: this.dynamic.likeCount
+                    likeCount: this.dynamic.likeCount,
+                    isFavorited: this.dynamic.isFavorited,
+                    favoriteCount: this.dynamic.favoriteCount
                 })
 			} catch (error) {
 				console.error('点赞失败:', error)
 				// 请求失败,恢复原状态
-				this.dynamic.isLiked = !this.dynamic.isLiked
-				this.dynamic.likeCount = this.dynamic.isLiked ? (this.dynamic.likeCount || 0) + 1 : Math.max(0, (this.dynamic.likeCount || 0) - 1)
+				const currentIsLiked = Boolean(this.dynamic.isLiked === true || this.dynamic.isLiked === 1 || this.dynamic.isLiked === 'true')
+				this.dynamic.isLiked = !currentIsLiked
+				this.dynamic.likeCount = !currentIsLiked ? (this.dynamic.likeCount || 0) + 1 : Math.max(0, (this.dynamic.likeCount || 0) - 1)
 				uni.showToast({
 					title: '操作失败,请重试',
 					icon: 'none'
@@ -412,7 +460,8 @@ export default {
 			try {
 				this.isFavoriting = true
 				
-				const isFavorited = this.dynamic.isFavorited
+				// 根据 dynamic_favorites 表判断是否收藏(有记录显示 ⭐,无记录显示 ☆)
+				const isFavorited = this.dynamic.isFavorited === true || this.dynamic.isFavorited === 1 || this.dynamic.isFavorited === 'true' || this.dynamic.isFavorited === '1'
 				const originalCount = this.dynamic.favoriteCount || 0
 				
 				// 立即更新UI(乐观更新)
@@ -429,17 +478,20 @@ export default {
 					})
 				}
 				
-                // 通知列表页同步状态
+                // 通知列表页同步状态(同时发送两个字段,确保状态完整同步)
                 uni.$emit('dynamic-updated', {
                     dynamicId: this.dynamic.dynamicId,
+                    isLiked: this.dynamic.isLiked,
+                    likeCount: this.dynamic.likeCount,
                     isFavorited: this.dynamic.isFavorited,
                     favoriteCount: this.dynamic.favoriteCount
                 })
 			} catch (error) {
 				console.error('收藏失败:', error)
 				// 请求失败,恢复原状态
-				this.dynamic.isFavorited = !this.dynamic.isFavorited
-				this.dynamic.favoriteCount = this.dynamic.isFavorited ? (this.dynamic.favoriteCount || 0) + 1 : Math.max(0, (this.dynamic.favoriteCount || 0) - 1)
+				const currentIsFavorited = this.dynamic.isFavorited === true || this.dynamic.isFavorited === 1 || this.dynamic.isFavorited === 'true' || this.dynamic.isFavorited === '1'
+				this.dynamic.isFavorited = !currentIsFavorited
+				this.dynamic.favoriteCount = !currentIsFavorited ? (this.dynamic.favoriteCount || 0) + 1 : Math.max(0, (this.dynamic.favoriteCount || 0) - 1)
 				uni.showToast({
 					title: '操作失败,请重试',
 					icon: 'none'
@@ -630,6 +682,81 @@ export default {
 			this.showMenu = false
 		},
 		
+		// 处理编辑
+		handleEdit() {
+			this.hideMoreMenu()
+			if (!this.dynamic || !this.dynamicId) {
+				uni.showToast({
+					title: '动态信息不存在',
+					icon: 'none'
+				})
+				return
+			}
+			// 跳转到编辑页面
+			const mediaUrls = Array.isArray(this.dynamic.mediaUrls)
+				? this.dynamic.mediaUrls
+				: (this.dynamic.mediaUrls ? JSON.parse(this.dynamic.mediaUrls) : [])
+			uni.navigateTo({
+				url: `/pages/plaza/publish?edit=true&dynamicId=${this.dynamicId}&content=${encodeURIComponent(this.dynamic.content || '')}&mediaUrls=${encodeURIComponent(JSON.stringify(mediaUrls))}&mediaType=${this.dynamic.mediaType || 0}&visibility=${this.dynamic.visibility || 1}`
+			})
+		},
+
+		// 处理删除
+		handleDelete() {
+			this.hideMoreMenu()
+			if (!this.dynamicId) {
+				uni.showToast({
+					title: '动态ID不存在',
+					icon: 'none'
+				})
+				return
+			}
+
+			uni.showModal({
+				title: '确认删除',
+				content: '确定要删除这条动态吗?删除后无法恢复。',
+				confirmText: '删除',
+				confirmColor: '#E91E63',
+				success: async (res) => {
+					if (res.confirm) {
+						try {
+							if (!this.currentUserId) {
+								uni.showToast({
+									title: '请先登录',
+									icon: 'none'
+								})
+								return
+							}
+
+							uni.showLoading({
+								title: '删除中...'
+							})
+
+							await api.dynamic.deleteUserDynamic(this.dynamicId, this.currentUserId)
+
+							uni.hideLoading()
+							uni.showToast({
+								title: '删除成功',
+								icon: 'success'
+							})
+
+							// 延迟返回上一页
+							setTimeout(() => {
+								uni.navigateBack()
+							}, 1500)
+						} catch (error) {
+							uni.hideLoading()
+							console.error('删除动态失败:', error)
+							uni.showToast({
+								title: error.message || '删除失败',
+								icon: 'none'
+							})
+						}
+					}
+				}
+			})
+		},
+
 		// 处理举报
 		handleReport() {
 			this.hideMoreMenu()

+ 75 - 24
LiangZhiYUMao/pages/plaza/index.vue

@@ -69,8 +69,8 @@
 					<!-- 互动栏 -->
 					<view class="action-bar">
 						<view class="action-item" @click.stop="handleLike(item)">
-							<text :class="['icon', item.isLiked ? 'icon-liked' : '']">
-								{{ item.isLiked ? '❤️' : '🤍' }}
+							<text :class="['icon', !!item.isLiked ? 'icon-liked' : '']">
+								{{ !!item.isLiked ? '❤️' : '🤍' }}
 							</text>
 							<text class="action-text">{{ item.likeCount || 0 }}</text>
 						</view>
@@ -81,8 +81,8 @@
 						</view>
 						
 						<view class="action-item" @click.stop="handleFavorite(item)">
-							<text :class="['icon', item.isFavorited ? 'icon-favorited' : '']">
-								{{ item.isFavorited ? '⭐' : '☆' }}
+							<text :class="['icon', !!item.isFavorited ? 'icon-favorited' : '']">
+								{{ !!item.isFavorited ? '⭐' : '☆' }}
 							</text>
 							<text class="action-text">{{ item.favoriteCount || 0 }}</text>
 						</view>
@@ -189,6 +189,20 @@ export default {
     }
   },
 	onLoad() {
+		// 从存储中获取当前用户ID
+		const storedUserId = uni.getStorageSync('userId')
+		if (storedUserId !== null && storedUserId !== undefined && storedUserId !== '') {
+			this.currentUserId = parseInt(storedUserId)
+		} else {
+			// 尝试从 userInfo 中获取
+			const userInfo = uni.getStorageSync('userInfo')
+			if (userInfo && (userInfo.userId || userInfo.id || userInfo.user_id)) {
+				this.currentUserId = parseInt(userInfo.userId || userInfo.id || userInfo.user_id)
+			} else {
+				// 未登录,设置为 null
+				this.currentUserId = null
+			}
+		}
 		this.loadDynamicList()
 		// 监听详情页更新事件,实时同步点赞/收藏状态
 		uni.$on('dynamic-updated', (payload) => {
@@ -196,11 +210,23 @@ export default {
 			const idx = this.dynamicList.findIndex(x => x.dynamicId === payload.dynamicId)
 			if (idx !== -1) {
 				const item = this.dynamicList[idx]
-				if (payload.hasOwnProperty('isLiked')) item.isLiked = payload.isLiked
-				if (payload.hasOwnProperty('likeCount')) item.likeCount = payload.likeCount
-				if (payload.hasOwnProperty('isFavorited')) item.isFavorited = payload.isFavorited
-				if (payload.hasOwnProperty('favoriteCount')) item.favoriteCount = payload.favoriteCount
-				this.$set(this.dynamicList, idx, item)
+				// 使用 hasOwnProperty 严格检查属性是否存在,只更新事件中明确包含的字段
+				// 这样可以避免误更新不相关的字段(如点赞时误更新收藏状态)
+				// 根据 dynamic_likes 表判断:有记录显示 ❤️,无记录显示 🤍
+				if (payload.hasOwnProperty('isLiked') && payload.isLiked !== undefined && payload.isLiked !== null) {
+					this.$set(item, 'isLiked', payload.isLiked === true || payload.isLiked === 1 || payload.isLiked === 'true' || payload.isLiked === '1')
+				}
+				if (payload.hasOwnProperty('likeCount') && payload.likeCount !== undefined && payload.likeCount !== null) {
+					this.$set(item, 'likeCount', Number(payload.likeCount) || 0)
+				}
+				// 只有当 isFavorited 明确存在于 payload 中时才更新(避免误更新)
+				// 根据 dynamic_favorites 表判断:有记录显示 ⭐,无记录显示 ☆
+				if (payload.hasOwnProperty('isFavorited') && payload.isFavorited !== undefined && payload.isFavorited !== null) {
+					this.$set(item, 'isFavorited', payload.isFavorited === true || payload.isFavorited === 1 || payload.isFavorited === 'true' || payload.isFavorited === '1')
+				}
+				if (payload.hasOwnProperty('favoriteCount') && payload.favoriteCount !== undefined && payload.favoriteCount !== null) {
+					this.$set(item, 'favoriteCount', Number(payload.favoriteCount) || 0)
+				}
 			}
 		})
 		// 监听发布页插入新动态
@@ -213,6 +239,20 @@ export default {
 	
 	onShow() {
 		// 返回列表页时自动刷新,确保状态一致
+		// 重新获取当前用户ID(可能在其他页面登录/登出)
+		const storedUserId = uni.getStorageSync('userId')
+		if (storedUserId !== null && storedUserId !== undefined && storedUserId !== '') {
+			this.currentUserId = parseInt(storedUserId)
+		} else {
+			// 尝试从 userInfo 中获取
+			const userInfo = uni.getStorageSync('userInfo')
+			if (userInfo && (userInfo.userId || userInfo.id || userInfo.user_id)) {
+				this.currentUserId = parseInt(userInfo.userId || userInfo.id || userInfo.user_id)
+			} else {
+				// 未登录,设置为 null
+				this.currentUserId = null
+			}
+		}
 		this.pageNum = 1
 		this.noMore = false
 		this.dynamicList = []
@@ -254,11 +294,14 @@ export default {
 				})
 				
 				if (res && res.records) {
-					// 处理数据
+					// 处理数据,确保布尔值正确转换(根据 dynamic_likes 和 dynamic_favorites 表判断)
+					// 后端返回的 isLiked 和 isFavorited 应该已经是 Boolean,但为了兼容性,确保正确转换
 					const newData = res.records.map(item => ({
 						...item,
-						isLiked: item.isLiked || false,
-						isFavorited: item.isFavorited || false
+						// 如果 dynamic_likes 表中有记录,显示 ❤️,否则显示 🤍
+						isLiked: item.isLiked === true || item.isLiked === 1 || item.isLiked === 'true' || item.isLiked === '1',
+						// 如果 dynamic_favorites 表中有记录,显示 ⭐,否则显示 ☆
+						isFavorited: item.isFavorited === true || item.isFavorited === 1 || item.isFavorited === 'true' || item.isFavorited === '1'
 					}))
 					
 					if (this.pageNum === 1) {
@@ -313,12 +356,15 @@ export default {
 				// 标记为正在处理
 				this.$set(this.likingMap, item.dynamicId, true)
 				
-				const isLiked = item.isLiked
+				// 根据 dynamic_likes 表判断是否点赞(有记录显示 ❤️,无记录显示 🤍)
+				const isLiked = item.isLiked === true || item.isLiked === 1 || item.isLiked === 'true' || item.isLiked === '1'
 				const originalCount = item.likeCount || 0
+				const newIsLiked = !isLiked
+				const newLikeCount = isLiked ? Math.max(0, originalCount - 1) : originalCount + 1
 				
-				// 立即更新UI(乐观更新)
-				item.isLiked = !isLiked
-				item.likeCount = isLiked ? Math.max(0, originalCount - 1) : originalCount + 1
+				// 立即更新UI(乐观更新),使用 $set 确保响应式更新
+				this.$set(item, 'isLiked', newIsLiked)
+				this.$set(item, 'likeCount', newLikeCount)
 				
 				// 发送请求
                 if (isLiked) {
@@ -331,8 +377,9 @@ export default {
 			} catch (error) {
 				console.error('点赞操作失败:', error)
 				// 请求失败,恢复原状态
-				item.isLiked = !item.isLiked
-				item.likeCount = item.isLiked ? (item.likeCount || 0) + 1 : Math.max(0, (item.likeCount || 0) - 1)
+				const currentIsLiked = item.isLiked === true || item.isLiked === 1 || item.isLiked === 'true' || item.isLiked === '1'
+				this.$set(item, 'isLiked', !currentIsLiked)
+				this.$set(item, 'likeCount', !currentIsLiked ? (item.likeCount || 0) + 1 : Math.max(0, (item.likeCount || 0) - 1))
 				uni.showToast({
 					title: '操作失败,请重试',
 					icon: 'none'
@@ -356,12 +403,15 @@ export default {
 				// 标记为正在处理
 				this.$set(this.favoritingMap, item.dynamicId, true)
 				
-				const isFavorited = item.isFavorited
+				// 根据 dynamic_favorites 表判断是否收藏(有记录显示 ⭐,无记录显示 ☆)
+				const isFavorited = item.isFavorited === true || item.isFavorited === 1 || item.isFavorited === 'true' || item.isFavorited === '1'
 				const originalCount = item.favoriteCount || 0
+				const newIsFavorited = !isFavorited
+				const newFavoriteCount = isFavorited ? Math.max(0, originalCount - 1) : originalCount + 1
 				
-				// 立即更新UI(乐观更新)
-				item.isFavorited = !isFavorited
-				item.favoriteCount = isFavorited ? Math.max(0, originalCount - 1) : originalCount + 1
+				// 立即更新UI(乐观更新),使用 $set 确保响应式更新
+				this.$set(item, 'isFavorited', newIsFavorited)
+				this.$set(item, 'favoriteCount', newFavoriteCount)
 				
                 if (isFavorited) {
 					// 取消收藏
@@ -377,8 +427,9 @@ export default {
 			} catch (error) {
 				console.error('收藏操作失败:', error)
 				// 请求失败,恢复原状态
-				item.isFavorited = !item.isFavorited
-				item.favoriteCount = item.isFavorited ? (item.favoriteCount || 0) + 1 : Math.max(0, (item.favoriteCount || 0) - 1)
+				const currentIsFavorited = item.isFavorited === true || item.isFavorited === 1 || item.isFavorited === 'true' || item.isFavorited === '1'
+				this.$set(item, 'isFavorited', !currentIsFavorited)
+				this.$set(item, 'favoriteCount', !currentIsFavorited ? (item.favoriteCount || 0) + 1 : Math.max(0, (item.favoriteCount || 0) - 1))
 				uni.showToast({
 					title: '操作失败,请重试',
 					icon: 'none'

+ 98 - 39
LiangZhiYUMao/pages/plaza/publish.vue

@@ -5,7 +5,7 @@
 			<view class="navbar-left" @click="goBack">
 				<text class="back-icon">← 返回</text>
 			</view>
-			<view class="navbar-title">发布动态</view>
+			<view class="navbar-title">{{ isEdit ? '编辑动态' : '发布动态' }}</view>
 			<view class="navbar-right"></view>
 		</view>
 		
@@ -50,7 +50,7 @@
 				:class="{ disabled: uploading || !content.trim() }"
 				@click="submit">
 				<text class="btn-icon">💕</text>
-				<text class="btn-text">发布</text>
+				<text class="btn-text">{{ isEdit ? '保存' : '发布' }}</text>
 			</button>
 		</view>
 	</view>
@@ -64,9 +64,40 @@ export default {
         return {
             content: '',
             mediaList: [],
-            uploading: false
+            uploading: false,
+            isEdit: false,
+            dynamicId: null,
+            visibility: 1,
+            mediaType: 1
         }
 	},
+	
+	onLoad(options) {
+		// 检查是否是编辑模式
+		if (options.edit === 'true' && options.dynamicId) {
+			this.isEdit = true
+			this.dynamicId = parseInt(options.dynamicId)
+			
+			// 加载动态数据
+			if (options.content) {
+				this.content = decodeURIComponent(options.content)
+			}
+			if (options.mediaUrls) {
+				try {
+					this.mediaList = JSON.parse(decodeURIComponent(options.mediaUrls))
+				} catch (e) {
+					console.error('解析媒体URL失败:', e)
+					this.mediaList = []
+				}
+			}
+			if (options.mediaType) {
+				this.mediaType = parseInt(options.mediaType)
+			}
+			if (options.visibility) {
+				this.visibility = parseInt(options.visibility)
+			}
+		}
+	},
 	methods: {
 		// 返回上一页
 		goBack() {
@@ -163,53 +194,81 @@ export default {
 				return
 			}
 			
-			uni.showLoading({ title: '正在发布...' })
+			uni.showLoading({ title: this.isEdit ? '正在保存...' : '正在发布...' })
 			
 			try {
+				const userId = uni.getStorageSync("userId")
+				if (!userId) {
+					uni.hideLoading()
+					uni.showToast({ title: '请先登录', icon: 'none' })
+					return
+				}
+				
 				const payload = {
-					userId: uni.getStorageSync("userId"),
+					userId: userId,
 					content: this.content,
 					mediaList: this.mediaList,
 					mediaType: this.mediaList.length > 0 ? 2 : 1, // 自动检测:有图片为2,纯文本为1
-					visibility: 1 // 默认公开
+					visibility: this.visibility || 1 // 默认公开
 				}
-				console.log(uni.getStorageSync("userId"))
-				const dynamicId = await api.dynamic.publish(payload)
-				uni.hideLoading()
-				uni.showToast({ title: '发布成功!开始你的缘分旅程~', icon: 'success' })
 				
-				// 直接插入到广场页数据头部,避免全量刷新
-				uni.$emit('dynamic-insert', {
-					dynamicId,
-					userId: uni.getStorageSync("userId"),
-					content: this.content,
-					mediaUrls: this.mediaList,
-					mediaType: this.mediaList.length > 0 ? 2 : 1,
-					visibility: 1,
-					likeCount: 0,
-					commentCount: 0,
-					favoriteCount: 0,
-					shareCount: 0,
-					viewCount: 0,
-					isLiked: false,
-					isFavorited: false,
-					createdAt: new Date().toISOString(),
-					user: { userId: uni.getStorageSync("userId"), nickname: '我', avatarUrl: '' }
-				})
-				
-				setTimeout(() => {
-					// 发布成功后回到动态列表页面
-					uni.switchTab({
-						url: '/pages/plaza/index',
-						fail: () => {
-							uni.redirectTo({ url: '/pages/plaza/index' })
-						}
+				if (this.isEdit && this.dynamicId) {
+					// 编辑模式:调用更新接口
+					await api.dynamic.updateUserDynamic(this.dynamicId, userId, payload)
+					uni.hideLoading()
+					uni.showToast({ title: '保存成功!', icon: 'success' })
+					
+					// 触发动态更新事件
+					uni.$emit('dynamic-update', {
+						dynamicId: this.dynamicId,
+						content: this.content,
+						mediaUrls: this.mediaList,
+						mediaType: payload.mediaType,
+						visibility: payload.visibility
 					})
-				}, 1500)
+					
+					setTimeout(() => {
+						uni.navigateBack()
+					}, 1500)
+				} else {
+					// 发布模式:调用创建接口
+					const dynamicId = await api.dynamic.publish(payload)
+					uni.hideLoading()
+					uni.showToast({ title: '发布成功!开始你的缘分旅程~', icon: 'success' })
+					
+					// 直接插入到广场页数据头部,避免全量刷新
+					uni.$emit('dynamic-insert', {
+						dynamicId,
+						userId: userId,
+						content: this.content,
+						mediaUrls: this.mediaList,
+						mediaType: payload.mediaType,
+						visibility: payload.visibility,
+						likeCount: 0,
+						commentCount: 0,
+						favoriteCount: 0,
+						shareCount: 0,
+						viewCount: 0,
+						isLiked: false,
+						isFavorited: false,
+						createdAt: new Date().toISOString(),
+						user: { userId: userId, nickname: '我', avatarUrl: '' }
+					})
+					
+					setTimeout(() => {
+						// 发布成功后回到动态列表页面
+						uni.switchTab({
+							url: '/pages/plaza/index',
+							fail: () => {
+								uni.redirectTo({ url: '/pages/plaza/index' })
+							}
+						})
+					}, 1500)
+				}
 			} catch (e) {
 				uni.hideLoading()
-				uni.showToast({ title: '发布失败,请重试', icon: 'none' })
-				console.error('发布失败:', e)
+				uni.showToast({ title: this.isEdit ? '保存失败,请重试' : '发布失败,请重试', icon: 'none' })
+				console.error(this.isEdit ? '保存失败:' : '发布失败:', e)
 			}
 		}
 	}

+ 107 - 25
LiangZhiYUMao/utils/api.js

@@ -365,14 +365,34 @@ export default {
     }),
 
     // 评论点赞/取消
-    likeComment: (commentId) => request({
-      url: `/dynamic/comment/like?commentId=${commentId}&userId=1`,
-      method: 'POST'
-    }),
-    unlikeComment: (commentId) => request({
-      url: `/dynamic/comment/like/${commentId}?userId=1`,
-      method: 'DELETE'
-    }),
+    likeComment: (commentId, userId) => {
+      // 如果没有传入userId,从本地存储获取
+      if (!userId) {
+        const userInfo = uni.getStorageSync('userInfo');
+        userId = userInfo ? (userInfo.userId || userInfo.id) : null;
+      }
+      if (!userId) {
+        return Promise.reject(new Error('用户未登录'));
+      }
+      return request({
+        url: `/dynamic/comment/like?commentId=${commentId}&userId=${userId}`,
+        method: 'POST'
+      });
+    },
+    unlikeComment: (commentId, userId) => {
+      // 如果没有传入userId,从本地存储获取
+      if (!userId) {
+        const userInfo = uni.getStorageSync('userInfo');
+        userId = userInfo ? (userInfo.userId || userInfo.id) : null;
+      }
+      if (!userId) {
+        return Promise.reject(new Error('用户未登录'));
+      }
+      return request({
+        url: `/dynamic/comment/like/${commentId}?userId=${userId}`,
+        method: 'DELETE'
+      });
+    },
 
 
     // 获取用户动态列表
@@ -389,28 +409,68 @@ export default {
     },
 
     // 点赞动态
-    like: (dynamicId) => request({
-      url: `/dynamic/like?dynamicId=${dynamicId}&userId=1`,
-      method: 'POST'
-    }),
+    like: (dynamicId, userId) => {
+      // 如果没有传入userId,从本地存储获取
+      if (!userId) {
+        const userInfo = uni.getStorageSync('userInfo');
+        userId = userInfo ? (userInfo.userId || userInfo.id) : null;
+      }
+      if (!userId) {
+        return Promise.reject(new Error('用户未登录'));
+      }
+      return request({
+        url: `/dynamic/like?dynamicId=${dynamicId}&userId=${userId}`,
+        method: 'POST'
+      });
+    },
 
     // 取消点赞
-    unlike: (dynamicId) => request({
-      url: `/dynamic/like/${dynamicId}`,
-      method: 'DELETE'
-    }),
+    unlike: (dynamicId, userId) => {
+      // 如果没有传入userId,从本地存储获取
+      if (!userId) {
+        const userInfo = uni.getStorageSync('userInfo');
+        userId = userInfo ? (userInfo.userId || userInfo.id) : null;
+      }
+      if (!userId) {
+        return Promise.reject(new Error('用户未登录'));
+      }
+      return request({
+        url: `/dynamic/like/${dynamicId}?userId=${userId}`,
+        method: 'DELETE'
+      });
+    },
 
     // 收藏动态
-    favorite: (dynamicId) => request({
-      url: `/dynamic/favorite?dynamicId=${dynamicId}&userId=1`,
-      method: 'POST'
-    }),
+    favorite: (dynamicId, userId) => {
+      // 如果没有传入userId,从本地存储获取
+      if (!userId) {
+        const userInfo = uni.getStorageSync('userInfo');
+        userId = userInfo ? (userInfo.userId || userInfo.id) : null;
+      }
+      if (!userId) {
+        return Promise.reject(new Error('用户未登录'));
+      }
+      return request({
+        url: `/dynamic/favorite?dynamicId=${dynamicId}&userId=${userId}`,
+        method: 'POST'
+      });
+    },
 
     // 取消收藏
-    unfavorite: (dynamicId) => request({
-      url: `/dynamic/favorite/${dynamicId}`,
-      method: 'DELETE'
-    }),
+    unfavorite: (dynamicId, userId) => {
+      // 如果没有传入userId,从本地存储获取
+      if (!userId) {
+        const userInfo = uni.getStorageSync('userInfo');
+        userId = userInfo ? (userInfo.userId || userInfo.id) : null;
+      }
+      if (!userId) {
+        return Promise.reject(new Error('用户未登录'));
+      }
+      return request({
+        url: `/dynamic/favorite/${dynamicId}?userId=${userId}`,
+        method: 'DELETE'
+      });
+    },
 
     // 发布动态
     // publish: (data) => request({
@@ -419,7 +479,29 @@ export default {
     //   data
     // }),
 
-    // 删除动态
+    // 创建个人动态
+    createUserDynamic: (payload) => request({
+      url: '/dynamic/user',
+      method: 'POST',
+      data: payload,
+      header: { 'Content-Type': 'application/json' }
+    }),
+
+    // 更新个人动态
+    updateUserDynamic: (dynamicId, userId, payload) => request({
+      url: `/dynamic/user/${dynamicId}?userId=${userId}`,
+      method: 'PUT',
+      data: payload,
+      header: { 'Content-Type': 'application/json' }
+    }),
+
+    // 删除个人动态
+    deleteUserDynamic: (dynamicId, userId) => request({
+      url: `/dynamic/user/${dynamicId}?userId=${userId}`,
+      method: 'DELETE'
+    }),
+
+    // 删除动态(旧接口,保留兼容)
     delete: (dynamicId) => request({
       url: `/dynamic/${dynamicId}`,
       method: 'DELETE'

+ 10 - 82
service/admin/src/main/java/com/zhentao/controller/UserController.java

@@ -6,7 +6,6 @@ import com.zhentao.common.Result;
 import com.zhentao.entity.*;
 import com.zhentao.mapper.*;
 import com.zhentao.vo.UserVO;
-import com.zhentao.vo.UserVipListVO;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -40,9 +39,6 @@ public class UserController {
     @Autowired
     private MatchmakerMapper matchmakerMapper;
     
-    @Autowired
-    private VipPackageMapper vipPackageMapper;
-    
     /**
      * 用户列表(分页)
      */
@@ -184,7 +180,7 @@ public class UserController {
     }
     
     /**
-     * VIP用户列表(查询有生效中VIP记录的用户)
+     * 认证用户列表(资料完整的用户)
      */
     @GetMapping("/vip/list")
     public Result<Map<String, Object>> vipList(
@@ -192,86 +188,18 @@ public class UserController {
             @RequestParam(defaultValue = "10") Integer pageSize) {
         
         try {
-            // 先查询所有生效中的VIP记录
-            QueryWrapper<UserVip> vipWrapper = new QueryWrapper<>();
-            vipWrapper.eq("status", 1)  // 状态:生效中
-                    .gt("end_time", LocalDateTime.now())  // 未过期
-                    .orderByDesc("end_time");
-            
-            List<UserVip> vipList = userVipMapper.selectList(vipWrapper);
-            
-            if (vipList == null || vipList.isEmpty()) {
-                Map<String, Object> data = new HashMap<>();
-                data.put("list", new ArrayList<>());
-                data.put("total", 0);
-                data.put("page", page);
-                data.put("pageSize", pageSize);
-                return Result.success(data);
-            }
-            
-            // 获取所有用户ID(去重)
-            List<Long> userIds = vipList.stream()
-                    .map(UserVip::getUserId)
-                    .distinct()
-                    .collect(Collectors.toList());
-            
-            // 计算总数
-            int total = userIds.size();
-            
-            // 分页处理
-            int start = (page - 1) * pageSize;
-            int end = Math.min(start + pageSize, userIds.size());
-            List<Long> pageUserIds = userIds.subList(start, end);
+            Page<Users> pageInfo = new Page<>(page, pageSize);
+            QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
             
-            // 查询用户信息
-            List<Users> users = new ArrayList<>();
-            if (!pageUserIds.isEmpty()) {
-                QueryWrapper<Users> userWrapper = new QueryWrapper<>();
-                userWrapper.in("user_id", pageUserIds);
-                users = usersMapper.selectList(userWrapper);
-            }
+            // 只查询资料完整的用户
+            queryWrapper.eq("is_profile_complete", 1);
+            queryWrapper.orderByDesc("created_at");
             
-            // 转换为UserVipListVO
-            List<UserVipListVO> voList = new ArrayList<>();
-            for (Users user : users) {
-                // 查找该用户最新的VIP记录(按结束时间排序,取最新的)
-                UserVip userVip = vipList.stream()
-                        .filter(vip -> vip.getUserId().equals(Long.valueOf(user.getUserId())))
-                        .max((v1, v2) -> v1.getEndTime().compareTo(v2.getEndTime()))
-                        .orElse(null);
-                
-                if (userVip != null) {
-                    UserVipListVO vo = new UserVipListVO();
-                    vo.setUserId(user.getUserId());
-                    vo.setNickname(user.getNickname());
-                    
-                    // 查询VIP套餐信息
-                    VipPackage vipPackage = vipPackageMapper.selectById(userVip.getPackageId());
-                    if (vipPackage != null) {
-                        vo.setVipLevel(vipPackage.getPackageName());
-                    } else {
-                        vo.setVipLevel("VIP会员");
-                    }
-                    
-                    vo.setVipStartTime(userVip.getStartTime());
-                    vo.setVipEndTime(userVip.getEndTime());
-                    
-                    // 计算剩余天数
-                    LocalDateTime now = LocalDateTime.now();
-                    if (userVip.getEndTime().isAfter(now)) {
-                        long days = Duration.between(now, userVip.getEndTime()).toDays();
-                        vo.setRemainingDays((int) Math.max(0, days));
-                    } else {
-                        vo.setRemainingDays(0);
-                    }
-                    
-                    voList.add(vo);
-                }
-            }
+            Page<Users> result = usersMapper.selectPage(pageInfo, queryWrapper);
             
             Map<String, Object> data = new HashMap<>();
-            data.put("list", voList);
-            data.put("total", total);
+            data.put("list", result.getRecords());
+            data.put("total", result.getTotal());
             data.put("page", page);
             data.put("pageSize", pageSize);
             
@@ -348,7 +276,7 @@ public class UserController {
             
             // 活跃用户数(最近7天登录)
             QueryWrapper<Users> activeWrapper = new QueryWrapper<>();
-            activeWrapper.gt("last_login_at", java.time.LocalDateTime.now().minusDays(7));
+            activeWrapper.gt("last_login_at", LocalDateTime.now().minusDays(7));
             Long activeUsers = usersMapper.selectCount(activeWrapper);
             
             Map<String, Object> stats = new HashMap<>();

+ 70 - 0
service/dynamic/src/main/java/com/zhentao/controller/DynamicController.java

@@ -170,6 +170,76 @@ public class DynamicController {
         }
     }
 
+    /**
+     * 创建个人动态
+     * @param dto 动态信息
+     * @return 动态ID
+     */
+    @PostMapping("/user")
+    public Result<Integer> createUserDynamic(@RequestBody @Valid PublishDynamicDTO dto) {
+        try {
+            if (dto.getUserId() == null) {
+                return Result.error("用户ID不能为空");
+            }
+            Integer dynamicId = userDynamicsService.createUserDynamic(dto);
+            return Result.success(dynamicId);
+        } catch (IllegalArgumentException e) {
+            return Result.error(e.getMessage());
+        } catch (Exception e) {
+            return Result.error("创建动态失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新个人动态
+     * @param dynamicId 动态ID
+     * @param userId 用户ID
+     * @param dto 动态信息
+     * @return 操作结果
+     */
+    @PutMapping("/user/{dynamicId}")
+    public Result<String> updateUserDynamic(
+            @PathVariable Integer dynamicId,
+            @RequestParam Integer userId,
+            @RequestBody @Valid PublishDynamicDTO dto) {
+        try {
+            boolean success = userDynamicsService.updateUserDynamic(dynamicId, userId, dto);
+            if (success) {
+                return Result.success("更新成功", "success");
+            } else {
+                return Result.error("更新失败");
+            }
+        } catch (IllegalArgumentException e) {
+            return Result.error(e.getMessage());
+        } catch (Exception e) {
+            return Result.error("更新动态失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 删除个人动态
+     * @param dynamicId 动态ID
+     * @param userId 用户ID
+     * @return 操作结果
+     */
+    @DeleteMapping("/user/{dynamicId}")
+    public Result<String> deleteUserDynamic(
+            @PathVariable Integer dynamicId,
+            @RequestParam Integer userId) {
+        try {
+            boolean success = userDynamicsService.deleteUserDynamic(dynamicId, userId);
+            if (success) {
+                return Result.success("删除成功", "success");
+            } else {
+                return Result.error("删除失败");
+            }
+        } catch (IllegalArgumentException e) {
+            return Result.error(e.getMessage());
+        } catch (Exception e) {
+            return Result.error("删除动态失败:" + e.getMessage());
+        }
+    }
+
     /**
      * 获取推荐动态列表(审核通过且公开)
      * @param pageNum 页码

+ 25 - 0
service/dynamic/src/main/java/com/zhentao/service/UserDynamicsService.java

@@ -2,6 +2,7 @@ package com.zhentao.service;
 
 import com.zhentao.common.PageResult;
 import com.zhentao.dto.DynamicQueryDTO;
+import com.zhentao.dto.PublishDynamicDTO;
 import com.zhentao.vo.DynamicVO;
 
 /**
@@ -44,6 +45,30 @@ public interface UserDynamicsService {
      * @param reason    处理原因(必填)
      */
     boolean adminModerate(Integer dynamicId, Integer action, String reason);
+
+    /**
+     * 创建个人动态
+     * @param dto 动态信息
+     * @return 动态ID
+     */
+    Integer createUserDynamic(PublishDynamicDTO dto);
+
+    /**
+     * 更新个人动态
+     * @param dynamicId 动态ID
+     * @param userId 用户ID(用于权限验证)
+     * @param dto 动态信息
+     * @return 是否成功
+     */
+    boolean updateUserDynamic(Integer dynamicId, Integer userId, PublishDynamicDTO dto);
+
+    /**
+     * 删除个人动态
+     * @param dynamicId 动态ID
+     * @param userId 用户ID(用于权限验证)
+     * @return 是否成功
+     */
+    boolean deleteUserDynamic(Integer dynamicId, Integer userId);
 }
 
 

+ 32 - 0
service/dynamic/src/main/java/com/zhentao/service/impl/BrowseHistoryServiceImpl.java

@@ -113,6 +113,38 @@ public class BrowseHistoryServiceImpl implements BrowseHistoryService {
             vo.setMediaUrls(new ArrayList<>());
         }
 
+        // 确保 isLiked 和 isFavorited 正确转换为 Boolean(根据 dynamic_likes 和 dynamic_favorites 表判断)
+        // SQL 返回 1/0,MyBatis 会映射为 Boolean,但为了安全起见,确保正确转换
+        Object isLikedObj = dynamic.getIsLiked();
+        if (isLikedObj != null) {
+            if (isLikedObj instanceof Boolean) {
+                vo.setIsLiked((Boolean) isLikedObj);
+            } else if (isLikedObj instanceof Integer) {
+                vo.setIsLiked(((Integer) isLikedObj) == 1);
+            } else if (isLikedObj instanceof Number) {
+                vo.setIsLiked(((Number) isLikedObj).intValue() == 1);
+            } else {
+                vo.setIsLiked(Boolean.TRUE.equals(isLikedObj));
+            }
+        } else {
+            vo.setIsLiked(false);
+        }
+
+        Object isFavoritedObj = dynamic.getIsFavorited();
+        if (isFavoritedObj != null) {
+            if (isFavoritedObj instanceof Boolean) {
+                vo.setIsFavorited((Boolean) isFavoritedObj);
+            } else if (isFavoritedObj instanceof Integer) {
+                vo.setIsFavorited(((Integer) isFavoritedObj) == 1);
+            } else if (isFavoritedObj instanceof Number) {
+                vo.setIsFavorited(((Number) isFavoritedObj).intValue() == 1);
+            } else {
+                vo.setIsFavorited(Boolean.TRUE.equals(isFavoritedObj));
+            }
+        } else {
+            vo.setIsFavorited(false);
+        }
+
         // 转换用户信息
         if (dynamic.getUser() != null) {
             UserSimpleVO userVO = new UserSimpleVO();

+ 81 - 14
service/dynamic/src/main/java/com/zhentao/service/impl/InteractionServiceImpl.java

@@ -23,9 +23,8 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.util.ArrayList;
 import java.util.List;
-
+import java.util.Map;
 import java.time.LocalDateTime;
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -145,7 +144,7 @@ public class InteractionServiceImpl implements InteractionService {
 
     @Override
     public PageResult<DynamicVO> listFavorites(Integer userId, Integer pageNum, Integer pageSize) {
-        // 查询用户收藏的动态ID列表
+        // 查询用户收藏的动态ID列表(按收藏时间倒序)
         List<Integer> favoriteDynamicIds = favoritesMapper.selectList(new QueryWrapper<DynamicFavorites>()
                 .eq("user_id", userId)
                 .orderByDesc("created_at"))
@@ -157,20 +156,38 @@ public class InteractionServiceImpl implements InteractionService {
             return new PageResult<DynamicVO>(new java.util.ArrayList<>(), 0L, Long.valueOf(pageNum), Long.valueOf(pageSize));
         }
         
-        // 查询动态详情(关联用户信息)
-        Page<UserDynamics> page = new Page<>(pageNum, pageSize);
-        IPage<UserDynamics> pg = userDynamicsMapper.selectDynamicsByIds(page, favoriteDynamicIds, userId);
+        // 手动分页:计算起始索引和结束索引
+        int total = favoriteDynamicIds.size();
+        int startIndex = (pageNum - 1) * pageSize;
+        int endIndex = Math.min(startIndex + pageSize, total);
+        
+        if (startIndex >= total) {
+            return new PageResult<DynamicVO>(new java.util.ArrayList<>(), (long)total, Long.valueOf(pageNum), Long.valueOf(pageSize));
+        }
+        
+        // 获取当前页的动态ID列表
+        List<Integer> pageDynamicIds = favoriteDynamicIds.subList(startIndex, endIndex);
         
-        List<DynamicVO> list = pg.getRecords().stream()
+        // 查询动态详情(关联用户信息)- 不进行分页,因为我们已经手动分页了
+        Page<UserDynamics> page = new Page<>(1, pageDynamicIds.size());
+        IPage<UserDynamics> pg = userDynamicsMapper.selectDynamicsByIds(page, pageDynamicIds, userId);
+        
+        // 按照原始ID列表的顺序重新排序(保持收藏时间倒序)
+        Map<Integer, DynamicVO> dynamicMap = pg.getRecords().stream()
                 .map(this::convertToVO)
+                .collect(Collectors.toMap(DynamicVO::getDynamicId, vo -> vo, (v1, v2) -> v1));
+        
+        List<DynamicVO> list = pageDynamicIds.stream()
+                .map(dynamicMap::get)
+                .filter(vo -> vo != null)
                 .collect(Collectors.toList());
         
-        return new PageResult<>(list, pg.getTotal(), pg.getCurrent(), pg.getSize());
+        return new PageResult<>(list, (long)total, Long.valueOf(pageNum), Long.valueOf(pageSize));
     }
 
     @Override
     public PageResult<DynamicVO> listLikedDynamics(Integer userId, Integer pageNum, Integer pageSize) {
-        // 查询用户点赞的动态ID列表
+        // 查询用户点赞的动态ID列表(按点赞时间倒序)
         List<Integer> likedDynamicIds = likesMapper.selectList(new QueryWrapper<DynamicLikes>()
                 .eq("user_id", userId)
                 .orderByDesc("created_at"))
@@ -182,15 +199,33 @@ public class InteractionServiceImpl implements InteractionService {
             return new PageResult<DynamicVO>(new java.util.ArrayList<>(), 0L, Long.valueOf(pageNum), Long.valueOf(pageSize));
         }
         
-        // 查询动态详情(关联用户信息)
-        Page<UserDynamics> page = new Page<>(pageNum, pageSize);
-        IPage<UserDynamics> pg = userDynamicsMapper.selectDynamicsByIds(page, likedDynamicIds, userId);
+        // 手动分页:计算起始索引和结束索引
+        int total = likedDynamicIds.size();
+        int startIndex = (pageNum - 1) * pageSize;
+        int endIndex = Math.min(startIndex + pageSize, total);
+        
+        if (startIndex >= total) {
+            return new PageResult<DynamicVO>(new java.util.ArrayList<>(), (long)total, Long.valueOf(pageNum), Long.valueOf(pageSize));
+        }
+        
+        // 获取当前页的动态ID列表
+        List<Integer> pageDynamicIds = likedDynamicIds.subList(startIndex, endIndex);
         
-        List<DynamicVO> list = pg.getRecords().stream()
+        // 查询动态详情(关联用户信息)- 不进行分页,因为我们已经手动分页了
+        Page<UserDynamics> page = new Page<>(1, pageDynamicIds.size());
+        IPage<UserDynamics> pg = userDynamicsMapper.selectDynamicsByIds(page, pageDynamicIds, userId);
+        
+        // 按照原始ID列表的顺序重新排序(保持点赞时间倒序)
+        Map<Integer, DynamicVO> dynamicMap = pg.getRecords().stream()
                 .map(this::convertToVO)
+                .collect(Collectors.toMap(DynamicVO::getDynamicId, vo -> vo, (v1, v2) -> v1));
+        
+        List<DynamicVO> list = pageDynamicIds.stream()
+                .map(dynamicMap::get)
+                .filter(vo -> vo != null)
                 .collect(Collectors.toList());
         
-        return new PageResult<>(list, pg.getTotal(), pg.getCurrent(), pg.getSize());
+        return new PageResult<>(list, (long)total, Long.valueOf(pageNum), Long.valueOf(pageSize));
     }
     
     /**
@@ -215,6 +250,38 @@ public class InteractionServiceImpl implements InteractionService {
             vo.setMediaUrls(new ArrayList<>());
         }
 
+        // 确保 isLiked 和 isFavorited 正确转换为 Boolean(根据 dynamic_likes 和 dynamic_favorites 表判断)
+        // SQL 返回 1/0,MyBatis 会映射为 Boolean,但为了安全起见,确保正确转换
+        Object isLikedObj = dynamic.getIsLiked();
+        if (isLikedObj != null) {
+            if (isLikedObj instanceof Boolean) {
+                vo.setIsLiked((Boolean) isLikedObj);
+            } else if (isLikedObj instanceof Integer) {
+                vo.setIsLiked(((Integer) isLikedObj) == 1);
+            } else if (isLikedObj instanceof Number) {
+                vo.setIsLiked(((Number) isLikedObj).intValue() == 1);
+            } else {
+                vo.setIsLiked(Boolean.TRUE.equals(isLikedObj));
+            }
+        } else {
+            vo.setIsLiked(false);
+        }
+
+        Object isFavoritedObj = dynamic.getIsFavorited();
+        if (isFavoritedObj != null) {
+            if (isFavoritedObj instanceof Boolean) {
+                vo.setIsFavorited((Boolean) isFavoritedObj);
+            } else if (isFavoritedObj instanceof Integer) {
+                vo.setIsFavorited(((Integer) isFavoritedObj) == 1);
+            } else if (isFavoritedObj instanceof Number) {
+                vo.setIsFavorited(((Number) isFavoritedObj).intValue() == 1);
+            } else {
+                vo.setIsFavorited(Boolean.TRUE.equals(isFavoritedObj));
+            }
+        } else {
+            vo.setIsFavorited(false);
+        }
+
         // 转换用户信息
         if (dynamic.getUser() != null) {
             UserSimpleVO userVO = new UserSimpleVO();

+ 180 - 0
service/dynamic/src/main/java/com/zhentao/service/impl/UserDynamicsServiceImpl.java

@@ -4,8 +4,13 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.zhentao.common.PageResult;
 import com.zhentao.dto.DynamicQueryDTO;
+import com.zhentao.dto.PublishDynamicDTO;
 import com.zhentao.entity.UserDynamics;
 import com.zhentao.mapper.UserDynamicsMapper;
 import com.zhentao.service.UserDynamicsService;
@@ -17,6 +22,7 @@ import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -221,6 +227,38 @@ public class UserDynamicsServiceImpl implements UserDynamicsService {
             vo.setMediaUrls(new ArrayList<>());
         }
 
+        // 确保 isLiked 和 isFavorited 正确转换为 Boolean(根据 dynamic_likes 和 dynamic_favorites 表判断)
+        // SQL 返回 1/0,MyBatis 会映射为 Boolean,但为了安全起见,确保正确转换
+        Object isLikedObj = dynamic.getIsLiked();
+        if (isLikedObj != null) {
+            if (isLikedObj instanceof Boolean) {
+                vo.setIsLiked((Boolean) isLikedObj);
+            } else if (isLikedObj instanceof Integer) {
+                vo.setIsLiked(((Integer) isLikedObj) == 1);
+            } else if (isLikedObj instanceof Number) {
+                vo.setIsLiked(((Number) isLikedObj).intValue() == 1);
+            } else {
+                vo.setIsLiked(Boolean.TRUE.equals(isLikedObj));
+            }
+        } else {
+            vo.setIsLiked(false);
+        }
+
+        Object isFavoritedObj = dynamic.getIsFavorited();
+        if (isFavoritedObj != null) {
+            if (isFavoritedObj instanceof Boolean) {
+                vo.setIsFavorited((Boolean) isFavoritedObj);
+            } else if (isFavoritedObj instanceof Integer) {
+                vo.setIsFavorited(((Integer) isFavoritedObj) == 1);
+            } else if (isFavoritedObj instanceof Number) {
+                vo.setIsFavorited(((Number) isFavoritedObj).intValue() == 1);
+            } else {
+                vo.setIsFavorited(Boolean.TRUE.equals(isFavoritedObj));
+            }
+        } else {
+            vo.setIsFavorited(false);
+        }
+
         // 转换用户信息
         if (dynamic.getUser() != null) {
             UserSimpleVO userVO = new UserSimpleVO();
@@ -233,6 +271,148 @@ public class UserDynamicsServiceImpl implements UserDynamicsService {
 
         return vo;
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Integer createUserDynamic(PublishDynamicDTO dto) {
+        if (dto.getUserId() == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        if (dto.getContent() == null || dto.getContent().trim().isEmpty()) {
+            throw new IllegalArgumentException("动态内容不能为空");
+        }
+
+        UserDynamics dynamic = new UserDynamics();
+        dynamic.setUserId(dto.getUserId());
+        dynamic.setContent(dto.getContent());
+        dynamic.setMediaType(dto.getMediaType() == null ? 1 : dto.getMediaType());
+        dynamic.setVisibility(dto.getVisibility() == null ? 1 : dto.getVisibility());
+        dynamic.setAuditStatus(1); // 默认审核通过
+        dynamic.setStatus(1); // 正常状态
+        dynamic.setLikeCount(0);
+        dynamic.setCommentCount(0);
+        dynamic.setFavoriteCount(0);
+        dynamic.setShareCount(0);
+        dynamic.setViewCount(0);
+        dynamic.setCreatedAt(LocalDateTime.now());
+        dynamic.setUpdatedAt(LocalDateTime.now());
+
+        // 处理媒体URL列表
+        if (CollectionUtils.isNotEmpty(dto.getMediaList())) {
+            try {
+                dynamic.setMediaUrls(objectMapper.writeValueAsString(dto.getMediaList()));
+            } catch (JsonProcessingException e) {
+                throw new RuntimeException("媒体URL序列化失败", e);
+            }
+        }
+
+        userDynamicsMapper.insert(dynamic);
+        return dynamic.getDynamicId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateUserDynamic(Integer dynamicId, Integer userId, PublishDynamicDTO dto) {
+        if (dynamicId == null || userId == null) {
+            throw new IllegalArgumentException("动态ID和用户ID不能为空");
+        }
+
+        // 验证动态是否存在且属于该用户
+        UserDynamics existing = userDynamicsMapper.selectById(dynamicId);
+        if (existing == null) {
+            throw new IllegalArgumentException("动态不存在");
+        }
+        if (!existing.getUserId().equals(userId)) {
+            throw new IllegalArgumentException("无权修改该动态");
+        }
+        if (existing.getStatus() != 1) {
+            throw new IllegalArgumentException("该动态已被删除或封禁,无法修改");
+        }
+
+        // 更新动态信息
+        UpdateWrapper<UserDynamics> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.eq("dynamic_id", dynamicId)
+                .eq("user_id", userId); // 确保只能修改自己的动态
+
+        if (dto.getContent() != null) {
+            updateWrapper.set("content", dto.getContent());
+        }
+        if (dto.getMediaType() != null) {
+            updateWrapper.set("media_type", dto.getMediaType());
+        }
+        if (dto.getVisibility() != null) {
+            updateWrapper.set("visibility", dto.getVisibility());
+        }
+        if (dto.getMediaList() != null) {
+            try {
+                String mediaUrlsJson = CollectionUtils.isNotEmpty(dto.getMediaList()) 
+                    ? objectMapper.writeValueAsString(dto.getMediaList()) 
+                    : null;
+                updateWrapper.set("media_urls", mediaUrlsJson);
+            } catch (JsonProcessingException e) {
+                throw new RuntimeException("媒体URL序列化失败", e);
+            }
+        }
+        updateWrapper.set("updated_at", LocalDateTime.now());
+
+        int updated = userDynamicsMapper.update(null, updateWrapper);
+        
+        // 清除缓存(清除所有可能的用户缓存)
+        if (updated > 0) {
+            try {
+                String pattern = DYNAMIC_CACHE_KEY + dynamicId + ":*";
+                java.util.Set<String> keys = redisTemplate.keys(pattern);
+                if (keys != null && !keys.isEmpty()) {
+                    redisTemplate.delete(keys);
+                }
+            } catch (Exception ignore) {
+                // 忽略缓存清除失败
+            }
+        }
+
+        return updated > 0;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteUserDynamic(Integer dynamicId, Integer userId) {
+        if (dynamicId == null || userId == null) {
+            throw new IllegalArgumentException("动态ID和用户ID不能为空");
+        }
+
+        // 验证动态是否存在且属于该用户
+        UserDynamics existing = userDynamicsMapper.selectById(dynamicId);
+        if (existing == null) {
+            throw new IllegalArgumentException("动态不存在");
+        }
+        if (!existing.getUserId().equals(userId)) {
+            throw new IllegalArgumentException("无权删除该动态");
+        }
+
+        // 逻辑删除(使用MyBatis-Plus的逻辑删除)
+        UpdateWrapper<UserDynamics> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.eq("dynamic_id", dynamicId)
+                .eq("user_id", userId) // 确保只能删除自己的动态
+                .set("status", 0) // 0表示删除
+                .set("updated_at", LocalDateTime.now());
+
+        int deleted = userDynamicsMapper.update(null, updateWrapper);
+        
+        // 清除缓存(清除所有可能的用户缓存)
+        if (deleted > 0) {
+            try {
+                String pattern = DYNAMIC_CACHE_KEY + dynamicId + ":*";
+                java.util.Set<String> keys = redisTemplate.keys(pattern);
+                if (keys != null && !keys.isEmpty()) {
+                    redisTemplate.delete(keys);
+                }
+            } catch (Exception ignore) {
+                // 忽略缓存清除失败
+            }
+        }
+
+        return deleted > 0;
+    }
 }
 
 

+ 15 - 0
service/websocket/src/main/java/com/zhentao/controller/ChatController.java

@@ -5,6 +5,8 @@ import com.zhentao.entity.ChatConversation;
 import com.zhentao.entity.ChatMessage;
 import com.zhentao.service.ChatMessageService;
 import com.zhentao.service.OnlineUserService;
+import com.zhentao.service.UserVipService;
+import com.zhentao.vo.ResultVo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -29,6 +31,8 @@ public class ChatController {
     @Autowired
     private com.zhentao.repository.ChatConversationMapper conversationMapper;
 
+    @Autowired
+    private UserVipService userVipService;
     /**
      * 获取会话消息列表(优化版:添加更多参数支持)
      * GET /api/chat/messages?userId=10001&targetUserId=10002&page=0&size=20&lastMessageId=xxx
@@ -358,4 +362,15 @@ public class ChatController {
         result.put("timestamp", System.currentTimeMillis());
         return result;
     }
+
+//    获取用户消息限制
+    @GetMapping("/getUserMessageLimit")
+    public ResultVo getUserMessageLimit(@RequestParam("userId")Long userId,@RequestParam("targetUserId")Long targetUserId){
+        return userVipService.getUserMessageLimit(userId,targetUserId);
+    }
+
+    @GetMapping("/updateMessageCount")
+    public ResultVo updateMessageCount(@RequestParam("userId")Long userId,@RequestParam("targetUserId")Long targetUserId){
+        return userVipService.updateMessageCount(userId,targetUserId);
+    }
 }

+ 153 - 0
service/websocket/src/main/java/com/zhentao/entity/UserVip.java

@@ -0,0 +1,153 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.math.BigDecimal;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 用户VIP记录表
+ * @TableName user_vip
+ */
+@TableName(value ="user_vip")
+@Data
+public class UserVip {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 套餐ID
+     */
+    private Long packageId;
+
+    /**
+     * 开始时间
+     */
+    private Date startTime;
+
+    /**
+     * 结束时间
+     */
+    private Date endTime;
+
+    /**
+     * 购买天数
+     */
+    private Integer durationDays;
+
+    /**
+     * 支付金额
+     */
+    private BigDecimal paymentAmount;
+
+    /**
+     * 支付方式
+     */
+    private String paymentMethod;
+
+    /**
+     * 订单号
+     */
+    private String orderNo;
+
+    /**
+     * 状态 0-已过期 1-生效中 2-已取消
+     */
+    private Integer status;
+
+    /**
+     * 来源(签到奖励/购买)
+     */
+    private String source;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 支付完成时间
+     */
+    private Date paymentTime;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        UserVip other = (UserVip) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getPackageId() == null ? other.getPackageId() == null : this.getPackageId().equals(other.getPackageId()))
+            && (this.getStartTime() == null ? other.getStartTime() == null : this.getStartTime().equals(other.getStartTime()))
+            && (this.getEndTime() == null ? other.getEndTime() == null : this.getEndTime().equals(other.getEndTime()))
+            && (this.getDurationDays() == null ? other.getDurationDays() == null : this.getDurationDays().equals(other.getDurationDays()))
+            && (this.getPaymentAmount() == null ? other.getPaymentAmount() == null : this.getPaymentAmount().equals(other.getPaymentAmount()))
+            && (this.getPaymentMethod() == null ? other.getPaymentMethod() == null : this.getPaymentMethod().equals(other.getPaymentMethod()))
+            && (this.getOrderNo() == null ? other.getOrderNo() == null : this.getOrderNo().equals(other.getOrderNo()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getSource() == null ? other.getSource() == null : this.getSource().equals(other.getSource()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getPaymentTime() == null ? other.getPaymentTime() == null : this.getPaymentTime().equals(other.getPaymentTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getPackageId() == null) ? 0 : getPackageId().hashCode());
+        result = prime * result + ((getStartTime() == null) ? 0 : getStartTime().hashCode());
+        result = prime * result + ((getEndTime() == null) ? 0 : getEndTime().hashCode());
+        result = prime * result + ((getDurationDays() == null) ? 0 : getDurationDays().hashCode());
+        result = prime * result + ((getPaymentAmount() == null) ? 0 : getPaymentAmount().hashCode());
+        result = prime * result + ((getPaymentMethod() == null) ? 0 : getPaymentMethod().hashCode());
+        result = prime * result + ((getOrderNo() == null) ? 0 : getOrderNo().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getSource() == null) ? 0 : getSource().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getPaymentTime() == null) ? 0 : getPaymentTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", userId=").append(userId);
+        sb.append(", packageId=").append(packageId);
+        sb.append(", startTime=").append(startTime);
+        sb.append(", endTime=").append(endTime);
+        sb.append(", durationDays=").append(durationDays);
+        sb.append(", paymentAmount=").append(paymentAmount);
+        sb.append(", paymentMethod=").append(paymentMethod);
+        sb.append(", orderNo=").append(orderNo);
+        sb.append(", status=").append(status);
+        sb.append(", source=").append(source);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", paymentTime=").append(paymentTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 18 - 0
service/websocket/src/main/java/com/zhentao/mapper/UserVipMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.mapper;
+
+import com.zhentao.entity.UserVip;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 26621
+* @description 针对表【user_vip(用户VIP记录表)】的数据库操作Mapper
+* @createDate 2025-12-01 19:18:32
+* @Entity com.zhentao.entity.UserVip
+*/
+public interface UserVipMapper extends BaseMapper<UserVip> {
+
+}
+
+
+
+

+ 18 - 0
service/websocket/src/main/java/com/zhentao/service/UserVipService.java

@@ -0,0 +1,18 @@
+package com.zhentao.service;
+
+import com.zhentao.entity.UserVip;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.vo.ResultVo;
+
+/**
+* @author 26621
+* @description 针对表【user_vip(用户VIP记录表)】的数据库操作Service
+* @createDate 2025-12-01 19:18:32
+*/
+public interface UserVipService extends IService<UserVip> {
+
+    ResultVo getUserMessageLimit(Long userId, Long targetUserId);
+
+    ResultVo updateMessageCount(Long userId, Long targetUserId);
+
+}

+ 128 - 0
service/websocket/src/main/java/com/zhentao/service/impl/UserVipServiceImpl.java

@@ -0,0 +1,128 @@
+package com.zhentao.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.UserVip;
+import com.zhentao.service.UserVipService;
+import com.zhentao.mapper.UserVipMapper;
+import com.zhentao.vo.ResultVo;
+import com.zhentao.vo.UserVipVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class UserVipServiceImpl extends ServiceImpl<UserVipMapper, UserVip>
+        implements UserVipService {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Override
+    public ResultVo getUserMessageLimit(Long userId, Long targetUserId) {
+        // 1. 先查Redis:判断用户是否有有效VIP(缓存中存在即有效)
+        String vipRedisKey = "user:vip:" + userId;
+        if (redisTemplate.hasKey(vipRedisKey)) {
+            UserVipVo vipVo = new UserVipVo();
+            vipVo.setVip(true);
+            vipVo.setRemainingCount(-1);
+            vipVo.setHasMessageLimit(false);
+            return ResultVo.success("success", vipVo);
+        }
+
+        // 2. Redis无缓存,查数据库:查询用户未过期的VIP
+        QueryWrapper<UserVip> queryWrapper = new QueryWrapper<>();
+        Date nowDate = new Date(); // 与endTime(Date类型)匹配
+        queryWrapper.lambda()
+                .eq(UserVip::getUserId, userId)
+                .gt(UserVip::getEndTime, nowDate); // 类型匹配,避免SQL错误
+
+        List<UserVip> userVips = baseMapper.selectList(queryWrapper);
+        if (!CollUtil.isEmpty(userVips)) {
+            for (UserVip vip : userVips) {
+                Date endTime = vip.getEndTime();
+                if (endTime == null) continue; // 跳过无效数据
+
+                // 计算过期时间(毫秒差)
+                long expireMillis = endTime.getTime() - nowDate.getTime();
+                if (expireMillis > 0) {
+                    // 缓存VIP标记(无需存整个对象),到期自动删除
+                    redisTemplate.opsForValue().set(vipRedisKey, Boolean.TRUE, expireMillis, TimeUnit.MILLISECONDS);
+                    // 返回VIP权限
+                    UserVipVo vipVo = new UserVipVo();
+                    vipVo.setVip(true);
+                    vipVo.setRemainingCount(-1);
+                    vipVo.setHasMessageLimit(false);
+                    return ResultVo.success("success", vipVo);
+                }
+            }
+        }
+
+        // 3. 非VIP用户:处理消息次数限制
+        UserVipVo normalVo = new UserVipVo();
+        normalVo.setVip(false);
+        normalVo.setHasMessageLimit(true);
+
+        String msgCountRedisKey = "msg:count:" + userId + ":" + targetUserId;
+        // 安全获取缓存(避免类型转换异常)
+//        Integer remainingCount = redisTemplate.opsForValue().get(msgCountRedisKey, Integer.class);
+        Object value = redisTemplate.opsForValue().get(msgCountRedisKey);
+        Integer remainingCount = (value != null) ? (Integer) value : null;
+
+        if (remainingCount == null) {
+            // 初始化缓存:当前时间到明天凌晨的秒数
+            LocalDateTime now = LocalDateTime.now();
+            LocalDateTime midnight = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.MIN);
+            long expireSeconds = ChronoUnit.SECONDS.between(now, midnight);
+
+            // 时间单位改为SECONDS,与计算结果一致
+            redisTemplate.opsForValue().set(msgCountRedisKey, 5, expireSeconds, TimeUnit.SECONDS);
+            remainingCount = 5;
+        }
+
+        normalVo.setRemainingCount(remainingCount);
+        return ResultVo.success("success", normalVo);
+    }
+
+    @Override
+    public ResultVo updateMessageCount(Long userId, Long targetUserId) {
+        // 1. 先判断用户是否为VIP(从Redis缓存获取,与getUserMessageLimit逻辑一致)
+        Boolean isVip = (Boolean) redisTemplate.opsForValue().get("user:vip:" + userId);
+        if (isVip != null && isVip) {
+            // VIP用户无限制,直接返回
+            return ResultVo.success("VIP用户无次数限制",
+                    new UserVipVo( true,-1,false));
+        }
+
+        // 2. 非VIP用户:构造Redis键(与前端一致:msg:count:userId:targetUserId)
+        String redisKey = "msg:count:" + userId + ":" + targetUserId;
+
+        // 3. 获取当前剩余次数(不存在则默认5次,防止缓存穿透)
+        Integer remainingCount = (Integer) redisTemplate.opsForValue().get(redisKey);
+        if (remainingCount == null) {
+            remainingCount = 5;
+        }
+
+        // 4. 递减次数(最小为0,不能为负数)
+        remainingCount = Math.max(remainingCount - 1, 0);
+
+        // 5. 更新Redis缓存(保持与前端一致的过期时间:到次日凌晨)
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime midnight = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.MIN);
+        long expireSeconds = ChronoUnit.SECONDS.between(now, midnight);
+        redisTemplate.opsForValue().set(redisKey, remainingCount, expireSeconds, TimeUnit.SECONDS);
+
+        // 6. 返回更新后的结果
+        return ResultVo.success("更新成功",
+                new UserVipVo( false,remainingCount, true));
+}
+}

+ 1 - 0
service/websocket/src/main/java/com/zhentao/vo/ResultVo.java

@@ -70,6 +70,7 @@ public class ResultVo<T> {
         return fail(500, message);
     }
 
+
     /**
      * 参数错误返回(默认状态码400)
      * @param message 提示消息

+ 18 - 0
service/websocket/src/main/java/com/zhentao/vo/UserVipVo.java

@@ -0,0 +1,18 @@
+package com.zhentao.vo;
+
+import afu.org.checkerframework.checker.igj.qual.I;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+
+public class UserVipVo {
+    private boolean isVip;
+    private Integer remainingCount;
+    private boolean hasMessageLimit;
+
+
+}

+ 28 - 0
service/websocket/src/main/resources/com/zhentao/mapper/UserVipMapper.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.mapper.UserVipMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.entity.UserVip">
+            <id property="id" column="id" />
+            <result property="userId" column="user_id" />
+            <result property="packageId" column="package_id" />
+            <result property="startTime" column="start_time" />
+            <result property="endTime" column="end_time" />
+            <result property="durationDays" column="duration_days" />
+            <result property="paymentAmount" column="payment_amount" />
+            <result property="paymentMethod" column="payment_method" />
+            <result property="orderNo" column="order_no" />
+            <result property="status" column="status" />
+            <result property="source" column="source" />
+            <result property="createTime" column="create_time" />
+            <result property="paymentTime" column="payment_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,user_id,package_id,start_time,end_time,duration_days,
+        payment_amount,payment_method,order_no,status,source,
+        create_time,payment_time
+    </sql>
+</mapper>