Sfoglia il codice sorgente

上线测试更改BUG

wangwenju 10 ore fa
parent
commit
4b2e90e162

+ 48 - 8
LiangZhiYUMao/pages/matchmaker-workbench/edit-profile.vue

@@ -11,12 +11,15 @@
     </view>
 
     <scroll-view scroll-y class="content">
-      <!-- 头像展示区域(不可修改) -->
+      <!-- 头像上传区域 -->
       <view class="avatar-section">
-        <view class="avatar-container">
+        <view class="avatar-container" @click="chooseAvatar">
           <image class="avatar" :src="userInfo.avatar || defaultAvatar" mode="aspectFill"></image>
+          <view class="avatar-upload-btn">
+            <text class="upload-icon">📷</text>
+          </view>
         </view>
-        <text class="avatar-text">头像由平台统一设置,暂不支持修改</text>
+        <text class="avatar-text">点击更换头像</text>
       </view>
 
       <!-- 表单区域 -->
@@ -367,6 +370,44 @@ export default {
       this.userInfo.birth_date = e.detail.value
     },
 
+    // 选择头像
+    chooseAvatar() {
+      uni.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType: ['album', 'camera'],
+        success: (res) => {
+          const tempFilePath = res.tempFilePaths[0]
+          this.uploadAvatar(tempFilePath)
+        },
+        fail: (err) => {
+          if (err.errMsg && !err.errMsg.includes('cancel')) {
+            uni.showToast({ title: '选择图片失败', icon: 'none' })
+          }
+        }
+      })
+    },
+
+    // 上传头像
+    async uploadAvatar(filePath) {
+      uni.showLoading({ title: '上传中...' })
+      try {
+        const uploadRes = await api.matchmaker.uploadAvatar(filePath)
+        if (uploadRes) {
+          this.userInfo.avatar = uploadRes
+          uni.showToast({ title: '头像上传成功', icon: 'success' })
+        } else {
+          uni.showToast({ title: '上传失败', icon: 'none' })
+        }
+      } catch (e) {
+        console.error('上传头像失败:', e)
+        const errMsg = e.message || (typeof e === 'string' ? e : '上传失败,请重试')
+        uni.showToast({ title: errMsg, icon: 'none', duration: 3000 })
+      } finally {
+        uni.hideLoading()
+      }
+    },
+
     // 保存资料
     async handleSave() {
       // 验证表单
@@ -404,6 +445,7 @@ export default {
         const payload = {
           // 注意:后端 Jackson 使用了 SNAKE_CASE 策略
           // email、gender、profile 名字不变,其他字段用下划线命名
+          avatar_url: this.userInfo.avatar,
           email: this.userInfo.email,
           real_name: this.userInfo.real_name,
           gender: this.userInfo.gender,
@@ -511,14 +553,12 @@ export default {
       display: flex;
       align-items: center;
       justify-content: center;
-      background: #FFFFFF;
-      border: 2rpx solid #E91E63;
+      background: #E91E63;
       border-radius: 50%;
-      box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+      box-shadow: 0 2rpx 8rpx rgba(233, 30, 99, 0.3);
 
       .upload-icon {
-        font-size: 28rpx;
-        color: #E91E63;
+        font-size: 24rpx;
       }
     }
   }

+ 2 - 2
LiangZhiYUMao/pages/matchmaker-workbench/index.vue

@@ -20,7 +20,7 @@
           <text class="heart-icon">❤️</text>
           <text class="matchmaker-name">{{ matchmakerInfo.realName || makerName }}</text>
         </view>
-        <image class="avatar" :src="matchmakerInfo.avatarUrl || '/static/default-avatar.svg'" mode="aspectFill"></image>
+        <image class="avatar" :src="matchmakerInfo.avatarUrl" mode="aspectFill"></image>
       </view>
 
       <!-- 统计卡片 -->
@@ -192,7 +192,7 @@ export default {
 							realName: matchmaker.realName || matchmaker.real_name || userInfo.nickname,
 							avatarUrl: matchmaker.avatarUrl || matchmaker.avatar_url || userInfo.avatarUrl,
 							successCouples: matchmaker.successCouples || matchmaker.success_couples || 0,
-							points: matchmaker.points || 0
+							points: matchmaker.points || 0,
 						}
 						
 						// 将matchmakerId保存到userInfo中,供其他页面使用

+ 77 - 85
LiangZhiYUMao/pages/mine/index.vue

@@ -215,13 +215,13 @@
         <text class="menu-arrow">›</text>
       </view>
 
-      <view class="menu-item" @click="goToPage('blacklist')">
+     <!-- <view class="menu-item" @click="goToPage('blacklist')">
         <view class="menu-left">
           <text class="menu-icon">📋</text>
           <text class="menu-text">黑名单</text>
         </view>
         <text class="menu-arrow">›</text>
-      </view>
+      </view> -->
 
       <view class="menu-item" @click="goToPage('settings')">
         <view class="menu-left">
@@ -307,7 +307,9 @@ export default {
       weekDays: ['日', '一', '二', '三', '四', '五', '六'],
       currentMonth: '',
       calendarDays: [],
-      checkinRewards: []
+      checkinRewards: [],
+      // 是否为红娘(用于区分签到接口)
+      isMatchmaker: false
     }
   },
   computed: {
@@ -698,55 +700,54 @@ export default {
       }
 
       try {
+        // 获取当前年月
+        const today = new Date()
+        const year = today.getFullYear()
+        const month = today.getMonth() + 1
+        
         // 检查用户是否为红娘
         const matchmakerStatusRes = await api.user.getMatchmakerStatus(this.userInfo.userId)
-     
-	 console.log(matchmakerStatusRes)
+        const isMatchmaker = matchmakerStatusRes && matchmakerStatusRes.isMatchmaker == 1
         
-        if (matchmakerStatusRes && matchmakerStatusRes.isMatchmaker == 1) {
+        // 保存红娘状态供签到时使用
+        this.isMatchmaker = isMatchmaker
         
-        
-          
-          // 获取当前年月
-          const today = new Date()
-          const year = today.getFullYear()
-          const month = today.getMonth() + 1
-          
-          // 调用红娘签到信息接口
-          const checkinInfoRes = await api.matchmaker.checkinInfo(
-            this.userInfo.userId, 
-            year, 
-            month
-          )
-          
-       
-          
-          // 更新签到数据
-          this.checkinData = {
-            continuousDays: checkinInfoRes.continuousDays,
-            totalDays: checkinInfoRes.totalDays,
-            todayChecked: checkinInfoRes.todayChecked,
-            checkedDates: checkinInfoRes.checkedDates
-          }
-          
-          // 生成日历
-          this.generateCalendarFromBackend(checkinInfoRes.checkedDates)
-          
-          // 简单的奖励列表
-          this.checkinRewards = [
-            { day: 7, icon: '👑', reward: '积分+10', received: checkinInfoRes.continuousDays >= 7, isCurrent: checkinInfoRes.continuousDays < 7 },
-            { day: 14, icon: '💎', reward: '积分+20', received: checkinInfoRes.continuousDays >= 14, isCurrent: checkinInfoRes.continuousDays >= 7 && checkinInfoRes.continuousDays < 14 },
-            { day: 30, icon: '🏆', reward: '积分+50', received: checkinInfoRes.continuousDays >= 30, isCurrent: checkinInfoRes.continuousDays >= 14 && checkinInfoRes.continuousDays < 30 }
-          ]
+        let checkinInfoRes
+        if (isMatchmaker) {
+          // 红娘用户,调用红娘签到信息接口
+          checkinInfoRes = await api.matchmaker.checkinInfo(this.userInfo.userId, year, month)
         } else {
-       
+          // 普通用户,调用用户签到信息接口
+          checkinInfoRes = await api.user.checkinInfo(this.userInfo.userId, year, month)
+        }
+        
+        // 更新签到数据
+        this.checkinData = {
+          continuousDays: checkinInfoRes.continuousDays || 0,
+          totalDays: checkinInfoRes.totalDays || 0,
+          todayChecked: checkinInfoRes.todayChecked || false,
+          checkedDates: checkinInfoRes.checkedDates || []
         }
+        
+        // 生成日历
+        this.generateCalendarFromBackend(this.checkinData.checkedDates)
+        
+        // 简单的奖励列表(签到7天给1天会员,14天给3天会员,30天给7天会员)
+        this.checkinRewards = [
+          { day: 7, icon: '👑', reward: '会员+1天', received: this.checkinData.continuousDays >= 7, isCurrent: this.checkinData.continuousDays < 7 },
+          { day: 14, icon: '💎', reward: '会员+3天', received: this.checkinData.continuousDays >= 14, isCurrent: this.checkinData.continuousDays >= 7 && this.checkinData.continuousDays < 14 },
+          { day: 30, icon: '🏆', reward: '会员+7天', received: this.checkinData.continuousDays >= 30, isCurrent: this.checkinData.continuousDays >= 14 && this.checkinData.continuousDays < 30 }
+        ]
       } catch (err) {
         console.error('加载签到信息失败:', err)
-        uni.showToast({
-          title: '加载签到信息失败',
-          icon: 'none'
-        })
+        // 设置默认值
+        this.checkinData = {
+          continuousDays: 0,
+          totalDays: 0,
+          todayChecked: false,
+          checkedDates: []
+        }
+        this.generateCalendarFromBackend([])
       }
     },
 
@@ -809,7 +810,7 @@ export default {
     closeCheckinPopup() {
       this.$refs.checkinPopup.close()
     },
-    // 处理签到(通过网关
+    // 处理签到(用户和红娘使用不同接口
     async handleCheckin() {
       if (this.checkinData.todayChecked) {
         uni.showToast({
@@ -826,49 +827,40 @@ export default {
       }
 
       try {
-        // 检查用户是否为红娘
-        const matchmakerStatusRes = await api.user.getMatchmakerStatus(this.userInfo.userId)
+        let signRes
+        if (this.isMatchmaker) {
+          // 红娘用户,使用红娘签到接口
+          signRes = await api.matchmaker.doCheckin(this.userInfo.userId)
+        } else {
+          // 普通用户,使用用户签到接口
+          signRes = await api.user.doCheckin(this.userInfo.userId)
+        }
         
+        // 签到成功后更新状态
+        this.checkinData.todayChecked = true
+        this.checkinData.totalDays++
+        this.checkinData.continuousDays++
         
-        if (matchmakerStatusRes && matchmakerStatusRes.isMatchmaker) {
-          // 是红娘,使用红娘签到接口
-         
-          
-          // 调用红娘签到接口,直接使用userId作为makerId
-          const signRes = await api.matchmaker.doCheckin(this.userInfo.userId)
-          
-          // 签到成功后更新状态
-          this.checkinData.todayChecked = true
-          this.checkinData.totalDays++
-          this.checkinData.continuousDays++
-          
-          // 获取当前日期字符串
-          const today = new Date()
-          const todayStr = this.formatDate(today)
-          
-          // 更新签到日期列表
-          if (!this.checkinData.checkedDates.includes(todayStr)) {
-            this.checkinData.checkedDates.push(todayStr)
-          }
-          
-          // 重新生成日历
-          this.generateCalendarFromBackend(this.checkinData.checkedDates)
-          
-          uni.showToast({
-            title: '签到成功 +5积分',
-            icon: 'success',
-            duration: 2500
-          })
-          
-          // 重新加载签到信息,确保数据最新
-          await this.loadCheckinInfo()
-        } else {
-         
-          uni.showToast({
-            title: '功能开发中,敬请期待',
-            icon: 'none'
-          })
+        // 获取当前日期字符串
+        const today = new Date()
+        const todayStr = this.formatDate(today)
+        
+        // 更新签到日期列表
+        if (!this.checkinData.checkedDates.includes(todayStr)) {
+          this.checkinData.checkedDates.push(todayStr)
         }
+        
+        // 重新生成日历
+        this.generateCalendarFromBackend(this.checkinData.checkedDates)
+        
+        uni.showToast({
+          title: '签到成功 +5积分',
+          icon: 'success',
+          duration: 2500
+        })
+        
+        // 重新加载签到信息,确保数据最新
+        await this.loadCheckinInfo()
       } catch (err) {
         console.error('签到失败:', err)
         uni.showToast({

+ 51 - 0
LiangZhiYUMao/utils/api.js

@@ -129,6 +129,24 @@ export default {
     // 获取今日匹配数
     getMatchCount: () => request({ url: '/user/match-count' }),
 
+    // 用户签到相关(独立于红娘签到)
+    checkinStatus: (userId) => request({
+      url: `/user/checkin/status?userId=${userId}`
+    }),
+
+    checkinStats: (userId) => request({
+      url: `/user/checkin/stats?userId=${userId}`
+    }),
+
+    doCheckin: (userId) => request({
+      url: `/user/checkin/do?userId=${userId}`,
+      method: 'POST'
+    }),
+
+    checkinInfo: (userId, year, month) => request({
+      url: `/user/checkin/info?userId=${userId}&year=${year}&month=${month}`
+    }),
+
     // 更新用户基本信息(昵称、头像等)
     updateInfo: (data) => request({
       url: '/user/basic',
@@ -500,6 +518,39 @@ export default {
       data
     }),
 
+    // 上传红娘头像
+    uploadAvatar: (filePath) => {
+      return new Promise((resolve, reject) => {
+        uni.uploadFile({
+          url: BASE_URL + '/matchmaker/upload/avatar',
+          filePath: filePath,
+          name: 'file',
+          header: {
+            'Content-Type': 'multipart/form-data'
+          },
+          success: (res) => {
+            console.log('上传响应:', res)
+            try {
+              const data = JSON.parse(res.data)
+              if (data.code === 200 || data.code === 0 || data.success) {
+                resolve(data.data)
+              } else {
+                console.error('上传失败:', data)
+                reject(new Error(data.message || data.msg || '上传失败'))
+              }
+            } catch (e) {
+              console.error('解析响应失败:', e, res.data)
+              reject(new Error('解析响应数据失败'))
+            }
+          },
+          fail: (error) => {
+            console.error('上传请求失败:', error)
+            reject(new Error('上传请求失败: ' + (error.errMsg || '未知错误')))
+          }
+        })
+      })
+    },
+
     // 获取本月签到记录
     checkinList: (makerId, year, month) => request({
       url: `/matchmaker/checkin/list?makerId=${makerId}&year=${year}&month=${month}`

+ 2 - 0
gateway/src/main/resources/application.yml

@@ -5,6 +5,8 @@ server:
 spring:
   application:
     name: gateway
+  codec:
+    max-in-memory-size: 20MB
   cloud:
     gateway:
       discovery:

+ 97 - 0
service/Essential/src/main/java/com/zhentao/controller/UserCheckinController.java

@@ -0,0 +1,97 @@
+package com.zhentao.controller;
+
+import com.zhentao.common.Result;
+import com.zhentao.service.CheckinService;
+import com.zhentao.vo.CheckinInfoVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 用户签到控制器
+ * 提供用户端签到接口(独立于红娘签到)
+ */
+@RestController
+@RequestMapping("/api/user/checkin")
+public class UserCheckinController {
+    
+    @Autowired
+    private CheckinService checkinService;
+    
+    /**
+     * 用户签到
+     * @param userId 用户ID
+     * @return 签到结果
+     */
+    @PostMapping("/do")
+    public Result<CheckinInfoVO> doCheckin(@RequestParam Long userId) {
+        try {
+            CheckinInfoVO vo = checkinService.checkin(userId);
+            
+            // 判断是否获得奖励(签到7天给1天会员,14天给3天会员,30天给7天会员)
+            String message = "签到成功!";
+            if (vo.getContinuousDays() == 7) {
+                message = "🎉 恭喜!连续签到7天,获得1天会员奖励!";
+            } else if (vo.getContinuousDays() == 14) {
+                message = "🎉 恭喜!连续签到14天,获得3天会员奖励!";
+            } else if (vo.getContinuousDays() == 30) {
+                message = "🎊 恭喜!连续签到30天,获得7天会员大礼包!";
+            }
+            
+            return Result.success(message, vo);
+        } catch (RuntimeException e) {
+            return Result.error(e.getMessage());
+        } catch (Exception e) {
+            return Result.error("签到失败");
+        }
+    }
+    
+    /**
+     * 获取签到状态
+     * @param userId 用户ID
+     * @return 是否已签到
+     */
+    @GetMapping("/status")
+    public Result<Boolean> getCheckinStatus(@RequestParam Long userId) {
+        try {
+            CheckinInfoVO vo = checkinService.getCheckinInfo(userId);
+            return Result.success(vo.getTodayChecked());
+        } catch (Exception e) {
+            return Result.error("获取签到状态失败");
+        }
+    }
+    
+    /**
+     * 获取签到统计
+     * @param userId 用户ID
+     * @return 签到统计信息
+     */
+    @GetMapping("/stats")
+    public Result<CheckinInfoVO> getCheckinStats(@RequestParam Long userId) {
+        try {
+            CheckinInfoVO vo = checkinService.getCheckinInfo(userId);
+            return Result.success(vo);
+        } catch (Exception e) {
+            return Result.error("获取签到统计失败");
+        }
+    }
+    
+    /**
+     * 获取签到信息(包含当月已签到日期)
+     * @param userId 用户ID
+     * @param year 年份
+     * @param month 月份
+     * @return 签到信息
+     */
+    @GetMapping("/info")
+    public Result<CheckinInfoVO> getCheckinInfo(
+            @RequestParam Long userId,
+            @RequestParam(required = false) Integer year,
+            @RequestParam(required = false) Integer month) {
+        try {
+            CheckinInfoVO vo = checkinService.getCheckinInfo(userId);
+            return Result.success(vo);
+        } catch (Exception e) {
+            return Result.error("获取签到信息失败");
+        }
+    }
+}

+ 11 - 0
service/Essential/src/main/java/com/zhentao/entity/CheckinRecord.java

@@ -27,6 +27,11 @@ public class CheckinRecord implements Serializable {
      */
     private Long userId;
     
+    /**
+     * 用户类型 1-普通用户 2-红娘
+     */
+    private Integer userType;
+    
     /**
      * 签到日期
      */
@@ -56,5 +61,11 @@ public class CheckinRecord implements Serializable {
      * 创建时间
      */
     private LocalDateTime createTime;
+    
+    /**
+     * 用户类型常量
+     */
+    public static final int USER_TYPE_NORMAL = 1;  // 普通用户
+    public static final int USER_TYPE_MATCHMAKER = 2;  // 红娘
 }
 

+ 30 - 1
service/Essential/src/main/java/com/zhentao/mapper/CheckinRecordMapper.java

@@ -16,23 +16,41 @@ import java.util.List;
 public interface CheckinRecordMapper extends BaseMapper<CheckinRecord> {
     
     /**
-     * 查询用户在指定日期的签到记录
+     * 查询用户在指定日期的签到记录(不区分用户类型,兼容旧数据)
      */
     @Select("SELECT * FROM checkin_record WHERE user_id = #{userId} AND checkin_date = #{date} LIMIT 1")
     CheckinRecord selectByUserIdAndDate(@Param("userId") Long userId, @Param("date") LocalDate date);
     
+    /**
+     * 查询用户在指定日期的签到记录(区分用户类型)
+     */
+    @Select("SELECT * FROM checkin_record WHERE user_id = #{userId} AND user_type = #{userType} AND checkin_date = #{date} LIMIT 1")
+    CheckinRecord selectByUserIdAndTypeAndDate(@Param("userId") Long userId, @Param("userType") Integer userType, @Param("date") LocalDate date);
+    
     /**
      * 查询用户最近一次签到记录
      */
     @Select("SELECT * FROM checkin_record WHERE user_id = #{userId} ORDER BY checkin_date DESC LIMIT 1")
     CheckinRecord selectLatestByUserId(@Param("userId") Long userId);
     
+    /**
+     * 查询用户最近一次签到记录(区分用户类型)
+     */
+    @Select("SELECT * FROM checkin_record WHERE user_id = #{userId} AND user_type = #{userType} ORDER BY checkin_date DESC LIMIT 1")
+    CheckinRecord selectLatestByUserIdAndType(@Param("userId") Long userId, @Param("userType") Integer userType);
+    
     /**
      * 查询用户累计签到天数
      */
     @Select("SELECT COUNT(DISTINCT checkin_date) FROM checkin_record WHERE user_id = #{userId}")
     Integer countTotalDays(@Param("userId") Long userId);
     
+    /**
+     * 查询用户累计签到天数(区分用户类型)
+     */
+    @Select("SELECT COUNT(DISTINCT checkin_date) FROM checkin_record WHERE user_id = #{userId} AND user_type = #{userType}")
+    Integer countTotalDaysByType(@Param("userId") Long userId, @Param("userType") Integer userType);
+    
     /**
      * 查询用户在指定月份的所有签到日期
      */
@@ -42,5 +60,16 @@ public interface CheckinRecordMapper extends BaseMapper<CheckinRecord> {
     List<LocalDate> selectMonthCheckinDates(@Param("userId") Long userId, 
                                              @Param("year") int year, 
                                              @Param("month") int month);
+    
+    /**
+     * 查询用户在指定月份的所有签到日期(区分用户类型)
+     */
+    @Select("SELECT checkin_date FROM checkin_record WHERE user_id = #{userId} AND user_type = #{userType} " +
+            "AND YEAR(checkin_date) = #{year} AND MONTH(checkin_date) = #{month} " +
+            "ORDER BY checkin_date")
+    List<LocalDate> selectMonthCheckinDatesByType(@Param("userId") Long userId, 
+                                                   @Param("userType") Integer userType,
+                                                   @Param("year") int year, 
+                                                   @Param("month") int month);
 }
 

+ 18 - 2
service/Essential/src/main/java/com/zhentao/service/CheckinService.java

@@ -8,17 +8,33 @@ import com.zhentao.vo.CheckinInfoVO;
 public interface CheckinService {
     
     /**
-     * 用户签到
+     * 用户签到(默认普通用户)
      * @param userId 用户ID
      * @return 签到信息
      */
     CheckinInfoVO checkin(Long userId);
     
     /**
-     * 获取用户签到信息
+     * 用户签到(指定用户类型)
+     * @param userId 用户ID
+     * @param userType 用户类型 1-普通用户 2-红娘
+     * @return 签到信息
+     */
+    CheckinInfoVO checkin(Long userId, Integer userType);
+    
+    /**
+     * 获取用户签到信息(默认普通用户)
      * @param userId 用户ID
      * @return 签到信息
      */
     CheckinInfoVO getCheckinInfo(Long userId);
+    
+    /**
+     * 获取用户签到信息(指定用户类型)
+     * @param userId 用户ID
+     * @param userType 用户类型 1-普通用户 2-红娘
+     * @return 签到信息
+     */
+    CheckinInfoVO getCheckinInfo(Long userId, Integer userType);
 }
 

+ 57 - 46
service/Essential/src/main/java/com/zhentao/service/impl/CheckinServiceImpl.java

@@ -13,14 +13,13 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.time.YearMonth;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * 签到服务实现类
- * 使用Redis的Bitmap实现签到功能
+ * 支持普通用户和红娘分开签到统计
  */
 @Service
 public class CheckinServiceImpl implements CheckinService {
@@ -37,34 +36,40 @@ public class CheckinServiceImpl implements CheckinService {
     private static final String CHECKIN_KEY_PREFIX = "checkin:";
     
     /**
-     * 生成Redis Key: checkin:userId:yyyyMM
+     * 生成Redis Key: checkin:userType:userId:yyyyMM
      */
-    private String getCheckinKey(Long userId, LocalDate date) {
+    private String getCheckinKey(Long userId, Integer userType, LocalDate date) {
         String yearMonth = date.format(DateTimeFormatter.ofPattern("yyyyMM"));
-        return CHECKIN_KEY_PREFIX + userId + ":" + yearMonth;
+        return CHECKIN_KEY_PREFIX + userType + ":" + userId + ":" + yearMonth;
     }
     
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public CheckinInfoVO checkin(Long userId) {
+        // 默认普通用户签到
+        return checkin(userId, CheckinRecord.USER_TYPE_NORMAL);
+    }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public CheckinInfoVO checkin(Long userId, Integer userType) {
         LocalDate today = LocalDate.now();
         
-        // 检查今天是否已签到(从数据库查询,更准确)
-        CheckinRecord existRecord = checkinRecordMapper.selectByUserIdAndDate(userId, today);
+        // 检查今天是否已签到(区分用户类型
+        CheckinRecord existRecord = checkinRecordMapper.selectByUserIdAndTypeAndDate(userId, userType, today);
         if (existRecord != null) {
             throw new RuntimeException("今日已签到");
         }
         
         // 使用Redis Bitmap记录签到
-        String key = getCheckinKey(userId, today);
+        String key = getCheckinKey(userId, userType, today);
         int dayOfMonth = today.getDayOfMonth();
         redisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
         
-        // 计算连续签到天数
-        Integer continuousDays = calculateContinuousDays(userId);
+        // 计算连续签到天数(区分用户类型)
+        Integer continuousDays = calculateContinuousDays(userId, userType);
         
-        // 计算累计签到天数
-        Integer totalDays = calculateTotalDays(userId);
+        // 计算累计签到天数(区分用户类型)
+        Integer totalDays = calculateTotalDays(userId, userType);
         
         // 判断是否获得奖励
         Integer rewardType = getRewardType(continuousDays);
@@ -73,6 +78,7 @@ public class CheckinServiceImpl implements CheckinService {
         // 保存签到记录到数据库
         CheckinRecord record = new CheckinRecord();
         record.setUserId(userId);
+        record.setUserType(userType);
         record.setCheckinDate(today);
         record.setContinuousDays(continuousDays);
         record.setTotalDays(totalDays);
@@ -81,8 +87,8 @@ public class CheckinServiceImpl implements CheckinService {
         record.setCreateTime(LocalDateTime.now());
         checkinRecordMapper.insert(record);
         
-        // 如果获得VIP奖励,自动发放
-        if (rewardType > 0) {
+        // 如果获得VIP奖励,自动发放(仅普通用户)
+        if (rewardType > 0 && userType == CheckinRecord.USER_TYPE_NORMAL) {
             Integer vipDays = getVipDays(rewardType);
             if (vipDays > 0) {
                 vipService.grantVip(userId, vipDays, "签到奖励-连续" + continuousDays + "天");
@@ -90,29 +96,35 @@ public class CheckinServiceImpl implements CheckinService {
         }
         
         // 返回签到信息
-        return getCheckinInfo(userId);
+        return getCheckinInfo(userId, userType);
     }
     
     @Override
     public CheckinInfoVO getCheckinInfo(Long userId) {
+        // 默认普通用户
+        return getCheckinInfo(userId, CheckinRecord.USER_TYPE_NORMAL);
+    }
+    
+    @Override
+    public CheckinInfoVO getCheckinInfo(Long userId, Integer userType) {
         LocalDate today = LocalDate.now();
         
         CheckinInfoVO vo = new CheckinInfoVO();
         
-        // 计算连续签到天数
-        Integer continuousDays = calculateContinuousDays(userId);
+        // 计算连续签到天数(区分用户类型)
+        Integer continuousDays = calculateContinuousDays(userId, userType);
         vo.setContinuousDays(continuousDays);
         
-        // 计算累计签到天数
-        Integer totalDays = calculateTotalDays(userId);
+        // 计算累计签到天数(区分用户类型)
+        Integer totalDays = calculateTotalDays(userId, userType);
         vo.setTotalDays(totalDays);
         
-        // 检查今天是否已签到(从数据库查询
-        CheckinRecord todayRecord = checkinRecordMapper.selectByUserIdAndDate(userId, today);
+        // 检查今天是否已签到(区分用户类型
+        CheckinRecord todayRecord = checkinRecordMapper.selectByUserIdAndTypeAndDate(userId, userType, today);
         vo.setTodayChecked(todayRecord != null);
         
-        // 获取本月已签到日期
-        List<String> checkedDates = getMonthCheckedDates(userId, today);
+        // 获取本月已签到日期(区分用户类型)
+        List<String> checkedDates = getMonthCheckedDates(userId, userType, today);
         vo.setCheckedDates(checkedDates);
         
         // 设置奖励信息
@@ -123,16 +135,16 @@ public class CheckinServiceImpl implements CheckinService {
     }
     
     /**
-     * 计算连续签到天数(从数据库查询
+     * 计算连续签到天数(区分用户类型
      */
-    private Integer calculateContinuousDays(Long userId) {
+    private Integer calculateContinuousDays(Long userId, Integer userType) {
         LocalDate today = LocalDate.now();
         int continuousDays = 0;
         LocalDate checkDate = today;
         
         // 从今天往前推,检查连续签到
         for (int i = 0; i < 365; i++) {
-            CheckinRecord record = checkinRecordMapper.selectByUserIdAndDate(userId, checkDate);
+            CheckinRecord record = checkinRecordMapper.selectByUserIdAndTypeAndDate(userId, userType, checkDate);
             if (record != null) {
                 continuousDays++;
                 checkDate = checkDate.minusDays(1);
@@ -145,24 +157,24 @@ public class CheckinServiceImpl implements CheckinService {
     }
     
     /**
-     * 计算累计签到天数
+     * 计算累计签到天数(区分用户类型)
      */
-    private Integer calculateTotalDays(Long userId) {
-        // 从数据库查询累计天数(更准确)
-        Integer totalDays = checkinRecordMapper.countTotalDays(userId);
+    private Integer calculateTotalDays(Long userId, Integer userType) {
+        Integer totalDays = checkinRecordMapper.countTotalDaysByType(userId, userType);
         return totalDays == null ? 0 : totalDays;
     }
     
     /**
-     * 获取本月已签到的日期列表(从数据库查询,确保数据一致性
+     * 获取本月已签到的日期列表(区分用户类型
      */
-    private List<String> getMonthCheckedDates(Long userId, LocalDate date) {
+    private List<String> getMonthCheckedDates(Long userId, Integer userType, LocalDate date) {
         List<String> dates = new ArrayList<>();
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
         
-        // 从数据库查询本月的签到记录
-        List<LocalDate> checkinDates = checkinRecordMapper.selectMonthCheckinDates(
+        // 从数据库查询本月的签到记录(区分用户类型)
+        List<LocalDate> checkinDates = checkinRecordMapper.selectMonthCheckinDatesByType(
             userId, 
+            userType,
             date.getYear(), 
             date.getMonthValue()
         );
@@ -178,7 +190,7 @@ public class CheckinServiceImpl implements CheckinService {
     
     /**
      * 根据连续签到天数判断奖励类型
-     * 0: 无奖励, 1: 1天VIP, 2: 3天VIP, 3: 7天VIP
+     * 0: 无奖励, 1: 1天会员, 2: 3天会员, 3: 7天会员
      */
     private Integer getRewardType(Integer continuousDays) {
         if (continuousDays == 7) {
@@ -196,9 +208,9 @@ public class CheckinServiceImpl implements CheckinService {
      */
     private Integer getVipDays(Integer rewardType) {
         switch (rewardType) {
-            case 1: return 1;
-            case 2: return 3;
-            case 3: return 7;
+            case 1: return 1;  // 签到7天给1天会员
+            case 2: return 3;  // 签到14天给3天会员
+            case 3: return 7;  // 签到30天给7天会员
             default: return 0;
         }
     }
@@ -209,22 +221,21 @@ public class CheckinServiceImpl implements CheckinService {
     private List<CheckinRewardVO> buildRewards(Integer continuousDays) {
         List<CheckinRewardVO> rewards = new ArrayList<>();
         
-        // 7天VIP奖励
+        // 7天会员奖励
         boolean received7 = continuousDays >= 7;
         boolean isCurrent7 = continuousDays < 7;
-        rewards.add(new CheckinRewardVO(7, "👑", "VIP+1天", received7, isCurrent7));
+        rewards.add(new CheckinRewardVO(7, "👑", "会员+1天", received7, isCurrent7));
         
-        // 14天VIP奖励
+        // 14天会员奖励
         boolean received14 = continuousDays >= 14;
         boolean isCurrent14 = continuousDays >= 7 && continuousDays < 14;
-        rewards.add(new CheckinRewardVO(14, "💎", "VIP+3天", received14, isCurrent14));
+        rewards.add(new CheckinRewardVO(14, "💎", "会员+3天", received14, isCurrent14));
         
-        // 30天VIP奖励
+        // 30天会员奖励
         boolean received30 = continuousDays >= 30;
         boolean isCurrent30 = continuousDays >= 14 && continuousDays < 30;
-        rewards.add(new CheckinRewardVO(30, "🏆", "VIP+7天", received30, isCurrent30));
+        rewards.add(new CheckinRewardVO(30, "🏆", "会员+7天", received30, isCurrent30));
         
         return rewards;
     }
 }
-

+ 11 - 3
service/Essential/src/main/resources/sql/checkin.sql

@@ -2,6 +2,7 @@
 CREATE TABLE IF NOT EXISTS `checkin_record` (
   `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
   `user_id` BIGINT NOT NULL COMMENT '用户ID',
+  `user_type` TINYINT NOT NULL DEFAULT 1 COMMENT '用户类型 1-普通用户 2-红娘',
   `checkin_date` DATE NOT NULL COMMENT '签到日期',
   `continuous_days` INT DEFAULT 0 COMMENT '当前连续签到天数',
   `total_days` INT DEFAULT 0 COMMENT '累计签到天数',
@@ -9,12 +10,19 @@ CREATE TABLE IF NOT EXISTS `checkin_record` (
   `reward_type` INT DEFAULT 0 COMMENT '奖励类型 0-无 1-1天VIP 2-3天VIP 3-7天VIP',
   `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   PRIMARY KEY (`id`),
-  UNIQUE KEY `uk_user_date` (`user_id`, `checkin_date`),
+  UNIQUE KEY `uk_user_type_date` (`user_id`, `user_type`, `checkin_date`),
   KEY `idx_user_id` (`user_id`),
+  KEY `idx_user_type` (`user_type`),
   KEY `idx_checkin_date` (`checkin_date`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='签到记录表';
 
+-- 如果表已存在,添加user_type字段
+-- ALTER TABLE `checkin_record` ADD COLUMN `user_type` TINYINT NOT NULL DEFAULT 1 COMMENT '用户类型 1-普通用户 2-红娘' AFTER `user_id`;
+-- ALTER TABLE `checkin_record` DROP INDEX `uk_user_date`;
+-- ALTER TABLE `checkin_record` ADD UNIQUE KEY `uk_user_type_date` (`user_id`, `user_type`, `checkin_date`);
+
 -- 插入测试数据(可选)
--- INSERT INTO `checkin_record` (`user_id`, `checkin_date`, `continuous_days`, `total_days`, `reward_received`, `reward_type`, `create_time`)
--- VALUES (1, '2025-10-10', 1, 1, 0, 0, '2025-10-10 09:00:00');
+-- INSERT INTO `checkin_record` (`user_id`, `user_type`, `checkin_date`, `continuous_days`, `total_days`, `reward_received`, `reward_type`, `create_time`)
+-- VALUES (1, 1, '2025-10-10', 1, 1, 0, 0, '2025-10-10 09:00:00');
 
+    

+ 90 - 0
service/homePage/src/main/java/com/zhentao/controller/MatchmakerController.java

@@ -6,17 +6,25 @@ import com.zhentao.dto.MatchmakerQueryDTO;
 import com.zhentao.entity.Matchmaker;
 import com.zhentao.service.MatchmakerService;
 import com.zhentao.vo.MatchmakerVO;
+import io.minio.BucketExistsArgs;
+import io.minio.MakeBucketArgs;
+import io.minio.MinioClient;
+import io.minio.PutObjectArgs;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.InputStream;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 /**
  * 红娘控制器
@@ -29,6 +37,15 @@ public class MatchmakerController {
     @Autowired
     private MatchmakerService matchmakerService;
     
+    @Autowired
+    private MinioClient minioClient;
+    
+    @Value("${minio.bucketName:matchmakers}")
+    private String minioBucketName;
+    
+    @Value("https://api.zhongruanke.cn/minio")
+    private String minioEndpoint;
+    
     private final RestTemplate restTemplate = new RestTemplate();
     
     // websocket 服务的 IM 接口地址
@@ -238,6 +255,79 @@ public class MatchmakerController {
         }
     }
     
+    /**
+     * 上传红娘头像
+     * 
+     * @param file 头像文件
+     * @return 头像URL
+     */
+    @PostMapping("/upload/avatar")
+    public Result<String> uploadAvatar(@RequestParam("file") MultipartFile file) {
+        try {
+            // 1. 验证文件
+            if (file == null || file.isEmpty()) {
+                return Result.error("请选择要上传的图片");
+            }
+            
+            // 2. 验证文件类型
+            String contentType = file.getContentType();
+            if (contentType == null || !contentType.startsWith("image/")) {
+                return Result.error("只能上传图片文件");
+            }
+            
+            // 3. 验证文件大小(最大5MB)
+            if (file.getSize() > 5 * 1024 * 1024) {
+                return Result.error("图片大小不能超过5MB");
+            }
+            
+            // 4. 生成唯一文件名
+            String originalFilename = file.getOriginalFilename();
+            String extension = "";
+            if (originalFilename != null && originalFilename.contains(".")) {
+                extension = originalFilename.substring(originalFilename.lastIndexOf("."));
+            } else {
+                // 根据contentType推断扩展名
+                if (contentType.contains("jpeg") || contentType.contains("jpg")) {
+                    extension = ".jpg";
+                } else if (contentType.contains("png")) {
+                    extension = ".png";
+                } else if (contentType.contains("gif")) {
+                    extension = ".gif";
+                } else {
+                    extension = ".jpg";
+                }
+            }
+            String fileName = "matchmaker-avatar/" + UUID.randomUUID().toString() + extension;
+            
+            // 5. 检查并创建 MinIO 桶
+            if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioBucketName).build())) {
+                minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioBucketName).build());
+                System.out.println("创建 MinIO 桶: " + minioBucketName);
+            }
+            
+            // 6. 上传文件到 MinIO
+            try (InputStream inputStream = file.getInputStream()) {
+                minioClient.putObject(
+                        PutObjectArgs.builder()
+                                .bucket(minioBucketName)
+                                .object(fileName)
+                                .stream(inputStream, file.getSize(), -1)
+                                .contentType(contentType)
+                                .build()
+                );
+            }
+            
+            // 7. 构建访问URL
+            String avatarUrl = minioEndpoint + "/" + minioBucketName + "/" + fileName;
+            System.out.println("✅ 红娘头像上传成功: " + avatarUrl);
+            
+            return Result.success(avatarUrl);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("上传头像失败:" + e.getMessage());
+        }
+    }
+    
     /**
      * 删除红娘
      * 

+ 7 - 1
service/homePage/src/main/resources/application.yml

@@ -3,6 +3,12 @@ server:
 spring:
   application:
     name: homePage
+  # 文件上传配置
+  servlet:
+    multipart:
+      enabled: true
+      max-file-size: 10MB
+      max-request-size: 10MB
   # config:
   #   import: optional:nacos:mysql-dev?group=zk-parent&namespace=dev&refresh=true  # 暂时禁用 Nacos 配置导入
   cloud:
@@ -70,7 +76,7 @@ mybatis-plus:
 minio:
   endpoint: http://minio.zhongruanke.cn  # MinIO API地址(9000是API,9001是控制台)
   accessKey: minioadmin                   # MinIO访问密钥
-  secretKey: minioadmin                   # MinIO密钥
+  secretKey: minioadmin123                # MinIO密钥
   bucketName: matchmakers                 # 红娘资源桶名
   bannerBucket: banners                   # 轮播图专用桶名
   avatarFolder: 红娘                      # 头像文件夹路径