浏览代码

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

李亚非 2 月之前
父节点
当前提交
349e0f7e57

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/StoreClockIn.java

@@ -80,7 +80,7 @@ public class StoreClockIn extends Model<StoreClockIn> {
     @TableField(value = "updated_user_id", fill = FieldFill.INSERT_UPDATE)
     private Integer updatedUserId;
 
-    @ApiModelProperty(value = "是否审核(未审核:0,审核中:1,审核完成:2)")
+    @ApiModelProperty(value = "是否审核(未审核:0,审核中:1,审核通过:2,审核拒绝:3)")
     @TableField("check_flag")
     private Integer checkFlag;
 

+ 5 - 0
alien-entity/src/main/java/shop/alien/mapper/CommonRatingMapper.java

@@ -1,8 +1,11 @@
 package shop.alien.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -67,5 +70,7 @@ public interface CommonRatingMapper extends BaseMapper<CommonRating> {
      */
     @Update("UPDATE common_rating SET delete_flag = 1, updated_time = NOW() WHERE id = #{id}")
     int logicDeleteById(@Param("id") Integer id);
+
+    IPage<CommonRating> getMyRatingList(Page<CommonRating> page, @Param(Constants.WRAPPER) QueryWrapper<CommonRating> wrapper);
 }
 

+ 1 - 1
alien-entity/src/main/java/shop/alien/mapper/StoreClockInMapper.java

@@ -48,7 +48,7 @@ public interface StoreClockInMapper extends BaseMapper<StoreClockIn> {
      *
      * @return list
      */
-    @Select("SELECT store_id as storeId, count(store_id) as count FROM `store_clock_in` where delete_flag = 0 GROUP BY store_id ")
+    @Select("SELECT store_id as storeId, count(store_id) as count FROM `store_clock_in` where delete_flag = 0 and check_flag != 3 GROUP BY store_id ")
     List<Map<Integer, Integer>> getStoreClockInCount();
      /**
       * 获取所有店铺打卡次数(有图片并且设置为可见的)

+ 6 - 0
alien-entity/src/main/resources/mapper/CommonRatingMapper.xml

@@ -31,6 +31,12 @@
         is_anonymous, is_show, audit_status, like_count, score_one, score_two, score_three,
         delete_flag, created_user_id, updated_time, updated_user_id
     </sql>
+    <select id="getMyRatingList" resultType="shop.alien.entity.store.vo.CommonRatingVo">
+        select cr.*,si.store_name storeName
+        from common_rating cr
+        left join store_info si on cr.business_id = si.id
+        ${ew.customSqlSegment}
+    </select>
 
 </mapper>
 

+ 110 - 31
alien-entity/src/main/resources/mapper/LifeUserDynamicsMapper.xml

@@ -3,60 +3,139 @@
 <mapper namespace="shop.alien.mapper.LifeUserDynamicsMapper">
 
     <select id="getDynamicsList" resultType="shop.alien.entity.store.vo.LifeUserDynamicsVo">
-        select dyna1.*, COUNT(dyna1.id) AS fansCount from (
-        select dyna.*,COUNT(sc.id) AS commentCount,  COUNT(lm.id) AS transferNum from (
-        with dynamice as(
-        select
+        SELECT
+        -- 1. 用ANY_VALUE()包裹非聚合列,符合only_full_group_by规则
+        ANY_VALUE(dyna1.id) AS id,
+        ANY_VALUE(dyna1.dynamicsType) AS dynamicsType,
+        ANY_VALUE(dyna1.title) AS title,
+        ANY_VALUE(dyna1.phoneId) AS phoneId,
+        ANY_VALUE(dyna1.context) AS context,
+        ANY_VALUE(dyna1.image_path) AS image_path,
+        ANY_VALUE(dyna1.address) AS address,
+        ANY_VALUE(dyna1.address_name) AS address_name,
+        ANY_VALUE(dyna1.address_context) AS address_context,
+        ANY_VALUE(dyna1.liulan_count) AS liulan_count,
+        ANY_VALUE(dyna1.dianzan_count) AS dianzan_count,
+        ANY_VALUE(dyna1.type) AS type,
+        ANY_VALUE(dyna1.created_time) AS created_time,
+        ANY_VALUE(dyna1.userType) AS userType,
+        ANY_VALUE(dyna1.phone) AS phone,
+        ANY_VALUE(dyna1.draft) AS draft,
+        ANY_VALUE(dyna1.address_province) AS address_province,
+        ANY_VALUE(dyna1.top_status) AS top_status,
+        ANY_VALUE(dyna1.top_time) AS top_time,
+        ANY_VALUE(dyna1.enable_status) AS enable_status,
+        ANY_VALUE(dyna1.userName) AS userName,
+        ANY_VALUE(dyna1.userImage) AS userImage,
+        ANY_VALUE(dyna1.storeUserId) AS storeUserId,
+        ANY_VALUE(dyna1.storeOrUserId) AS storeOrUserId,
+        ANY_VALUE(dyna1.isExpert) AS isExpert,
+        ANY_VALUE(dyna1.store_name) AS store_name,
+        -- 2. 修正聚合计数逻辑:用COUNT(DISTINCT)避免重复计数,粉丝数统计lf1.id而非dyna1.id
+        COUNT(DISTINCT lf1.id) AS fansCount,
+        -- 保留内层的评论数、转发数(从dyna1中取聚合后的值)
+        ANY_VALUE(dyna1.commentCount) AS commentCount,
+        ANY_VALUE(dyna1.transferNum) AS transferNum
+        FROM (
+        SELECT
+        -- 内层同样用ANY_VALUE()包裹非聚合列
+        ANY_VALUE(dyna.id) AS id,
+        ANY_VALUE(dyna.dynamicsType) AS dynamicsType,
+        ANY_VALUE(dyna.title) AS title,
+        ANY_VALUE(dyna.phoneId) AS phoneId,
+        ANY_VALUE(dyna.context) AS context,
+        ANY_VALUE(dyna.image_path) AS image_path,
+        ANY_VALUE(dyna.address) AS address,
+        ANY_VALUE(dyna.address_name) AS address_name,
+        ANY_VALUE(dyna.address_context) AS address_context,
+        ANY_VALUE(dyna.liulan_count) AS liulan_count,
+        ANY_VALUE(dyna.dianzan_count) AS dianzan_count,
+        ANY_VALUE(dyna.type) AS type,
+        ANY_VALUE(dyna.created_time) AS created_time,
+        ANY_VALUE(dyna.userType) AS userType,
+        ANY_VALUE(dyna.phone) AS phone,
+        ANY_VALUE(dyna.draft) AS draft,
+        ANY_VALUE(dyna.address_province) AS address_province,
+        ANY_VALUE(dyna.top_status) AS top_status,
+        ANY_VALUE(dyna.top_time) AS top_time,
+        ANY_VALUE(dyna.enable_status) AS enable_status,
+        ANY_VALUE(dyna.userName) AS userName,
+        ANY_VALUE(dyna.userImage) AS userImage,
+        ANY_VALUE(dyna.storeUserId) AS storeUserId,
+        ANY_VALUE(dyna.storeOrUserId) AS storeOrUserId,
+        ANY_VALUE(dyna.isExpert) AS isExpert,
+        ANY_VALUE(dyna.store_name) AS store_name,
+        -- 3. 修正评论数/转发数:COUNT(DISTINCT)避免LEFT JOIN导致的重复计数
+        COUNT(DISTINCT sc.id) AS commentCount,
+        COUNT(DISTINCT lm.id) AS transferNum
+        FROM (
+        WITH dynamice AS (
+        SELECT
         CASE
         WHEN image_path REGEXP '.mp4|.avi|.flv|.mkv|.rmvb|.wmv|.3gp|.mov' THEN 2
         WHEN image_path REGEXP '.jpg|.jpeg|.png|.bmp|.webp|.gif|.svg' THEN 1
         ELSE 0
-        END AS dynamicsType,id, title, phone_id phoneId, context, image_path, address,address_name,address_context, liulan_count, dianzan_count, type, created_time, substring_index(phone_id, '_', 1) userType, substring_index(phone_id, '_', -1) phone, draft , address_province, top_status, top_time, enable_status
-        from life_user_dynamics
-        where delete_flag = 0 and draft = 0 order by created_time desc
+        END AS dynamicsType,
+        id, title, phone_id phoneId, context, image_path, address,address_name,address_context,
+        liulan_count, dianzan_count, type, created_time,
+        SUBSTRING_INDEX(phone_id, '_', 1) userType,
+        SUBSTRING_INDEX(phone_id, '_', -1) phone,
+        draft , address_province, top_status, top_time, enable_status
+        FROM life_user_dynamics
+        WHERE delete_flag = 0 AND draft = 0
         )
-        select dynamice.*, user.nick_name userName, user.head_img userImage, info.id storeUserId, user.id storeOrUserId, 0 isExpert, info.store_name
-        from dynamice
-        left join store_user user on dynamice.phone = user.phone and user.delete_flag = 0
-        left join store_info info on info.id = user.store_id and info.delete_flag = 0
-        left join store_img img on img.store_id = user.store_id and img.img_type = '10' and img.delete_flag = 0
-        where dynamice.userType = 'store'
-        union
-        select dynamice.*, user.user_name userName, user.user_image userImage, user.id storeUserId, user.id storeOrUserId, IF(lue.expert_code IS NOT NULL , 1, 0) AS isExpert, '' store_name
-        from dynamice
-        join life_user user on dynamice.phone = user.user_phone and user.delete_flag = 0
-        left join life_user_expert  lue on lue.user_id = user.id and lue.delete_flag = 0
-        where dynamice.userType = 'user') dyna
-        left join life_comment lc on lc.dongtai_shequ_id = dyna.id
-        left join store_comment sc on sc.business_id = dyna.id and sc.business_type = 2 and sc.delete_flag = 0
-        left join life_message lm on lm.business_id = dyna.id
-        GROUP BY dyna.id order by  dyna.created_time desc) dyna1
-        left join life_fans lf1 on lf1.followed_id =  dyna1.phoneId
-        where 1=1
+        SELECT
+        dynamice.*,
+        user.nick_name userName, user.head_img userImage,
+        info.id storeUserId, user.id storeOrUserId,
+        0 isExpert, info.store_name
+        FROM dynamice
+        LEFT JOIN store_user user ON dynamice.phone = user.phone AND user.delete_flag = 0
+        LEFT JOIN store_info info ON info.id = user.store_id AND info.delete_flag = 0
+        LEFT JOIN store_img img ON img.store_id = user.store_id AND img.img_type = '10' AND img.delete_flag = 0
+        WHERE dynamice.userType = 'store'
+        UNION
+        SELECT
+        dynamice.*,
+        user.user_name userName, user.user_image userImage,
+        user.id storeUserId, user.id storeOrUserId,
+        IF(lue.expert_code IS NOT NULL , 1, 0) AS isExpert,
+        '' store_name
+        FROM dynamice
+        JOIN life_user user ON dynamice.phone = user.user_phone AND user.delete_flag = 0
+        LEFT JOIN life_user_expert  lue ON lue.user_id = user.id AND lue.delete_flag = 0
+        WHERE dynamice.userType = 'user'
+        ) dyna
+        LEFT JOIN life_comment lc ON lc.dongtai_shequ_id = dyna.id
+        LEFT JOIN store_comment sc ON sc.business_id = dyna.id AND sc.business_type = 2 AND sc.delete_flag = 0
+        LEFT JOIN life_message lm ON lm.business_id = dyna.id
+        -- 4. GROUP BY仅保留主键id(因dyna.id是唯一主键,ANY_VALUE()包裹的列在同一id下值唯一)
+        GROUP BY dyna.id
+        ) dyna1
+        LEFT JOIN life_fans lf1 ON lf1.followed_id = dyna1.phoneId
+        WHERE 1=1
         <if test="nickName != null and nickName != ''">
             AND dyna1.userName LIKE CONCAT('%', #{nickName}, '%')
         </if>
-
         <if test="userType != null and userType != ''">
             AND dyna1.userType = #{userType}
         </if>
-
         <if test="dynamicsType != null and dynamicsType != ''">
             AND dyna1.dynamicsType = #{dynamicsType}
         </if>
-
         <if test="releaseStartTime != null and releaseStartTime != ''">
             AND dyna1.created_time >= #{releaseStartTime}
         </if>
-
         <if test="storeName != null and storeName != ''">
             AND dyna1.store_name LIKE CONCAT('%', #{storeName}, '%')
         </if>
-
         <if test="releaseEndTime != null and releaseEndTime != ''">
             AND dyna1.created_time &lt;= #{releaseEndTime}
         </if>
-        GROUP by dyna1.id order by dyna1.top_status desc, dyna1.top_time desc
+        -- 5. 外层GROUP BY仅保留主键id,保证分组逻辑正确
+        GROUP BY dyna1.id
+        -- 6. 最终排序移到外层,子查询的ORDER BY无意义且影响性能
+        ORDER BY dyna1.top_status DESC, dyna1.top_time DESC;
     </select>
 
     <select id="getDynamicsDetail" resultType="shop.alien.entity.store.vo.LifeUserDynamicsVo">

+ 1 - 0
alien-entity/src/main/resources/mapper/StoreStaffReviewMapper.xml

@@ -65,6 +65,7 @@
             AND CONVERT(llr.dianzan_id, CHAR) = CONVERT(#{currentUserId}, CHAR)
             AND llr.delete_flag = 0
         WHERE ssr.delete_flag = 0
+          AND ssr.audit_status = 1
         <if test="staffUserId != null">
             AND ssr.staff_user_id = #{staffUserId}
         </if>

+ 35 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRoleServiceImpl.java

@@ -11,9 +11,12 @@ import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
+import shop.alien.config.redis.BaseRedisService;
 import shop.alien.entity.store.StorePlatformRole;
+import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StorePlatformRoleVo;
 import shop.alien.mapper.StorePlatformRoleMapper;
+import shop.alien.mapper.StoreUserMapper;
 import shop.alien.storeplatform.service.StorePlatformRoleMenuService;
 import shop.alien.storeplatform.service.StorePlatformRoleService;
 import shop.alien.storeplatform.service.StorePlatformUserRoleService;
@@ -36,6 +39,8 @@ public class StorePlatformRoleServiceImpl extends ServiceImpl<StorePlatformRoleM
     private final StorePlatformRoleMapper storePlatformRoleMapper;
     private final StorePlatformRoleMenuService storePlatformRoleMenuService;
     private final StorePlatformUserRoleService storePlatformUserRoleService;
+    private final StoreUserMapper storeUserMapper;
+    private final BaseRedisService baseRedisService;
 
     @Override
     public IPage<StorePlatformRole> getRolePage(int page, int size, Integer storeId, String roleName, String description, String status) {
@@ -273,6 +278,36 @@ public class StorePlatformRoleServiceImpl extends ServiceImpl<StorePlatformRoleM
                     return false;
                 }
             }
+            //角色被编辑 清除当前被编辑角色的账号的token
+            try {
+                // 获取该角色下的所有用户ID
+                Integer storeId = role.getStoreId() != null ? role.getStoreId() : originalRole.getStoreId();
+                List<Integer> userIds = storePlatformUserRoleService.getUserIdsByRoleId(role.getRoleId(), storeId);
+                if (userIds != null && !userIds.isEmpty()) {
+                    // 根据用户ID查询用户信息,获取手机号
+                    for (Integer userId : userIds) {
+                        StoreUser storeUser = storeUserMapper.selectById(userId);
+                        if (storeUser != null && storeUser.getPhone() != null) {
+                            // 删除Redis中的token,key格式:storePlatform_手机号
+                            String tokenKey = "store_" + storeUser.getPhone();
+                            String existingToken = baseRedisService.getString(tokenKey);
+                            if (existingToken != null) {
+                                baseRedisService.delete(tokenKey);
+                                log.info("清除角色编辑后的用户token成功,roleId={}, userId={}, phone={}, tokenKey={}", 
+                                        role.getRoleId(), userId, storeUser.getPhone(), tokenKey);
+                            } else {
+                                log.warn("用户token不存在或已过期,roleId={}, userId={}, phone={}, tokenKey={}", 
+                                        role.getRoleId(), userId, storeUser.getPhone(), tokenKey);
+                            }
+                        }
+                    }
+                } else {
+                    log.info("该角色下没有关联用户,无需清除token,roleId={}", role.getRoleId());
+                }
+            } catch (Exception e) {
+                log.error("清除角色编辑后的用户token失败,roleId={}, error={}", role.getRoleId(), e.getMessage(), e);
+                // 不清除token不影响角色更新,只记录错误日志
+            }
             log.info("成功更新角色权限,roleId={}, menuIds={}", role.getRoleId(), menuIds);
         }
 

+ 17 - 42
alien-store/src/main/java/shop/alien/store/controller/CommonRatingController.java

@@ -147,47 +147,22 @@ public class CommonRatingController {
         return R.fail("删除评价失败");
     }
 
-//
-//    @ApiOperation("更新评价")
-//    @PutMapping("/update")
-//    public R<Boolean> update(@RequestBody CommonRating commonRating) {
-//        log.info("CommonRatingController.update?commonRating={}", commonRating);
-//        return R.data(commonRatingService.updateById(commonRating));
-//    }
-//
-//    @ApiOperation("删除评价")
-//    @ApiImplicitParam(name = "id", value = "评价ID", dataType = "Long", paramType = "path", required = true)
-//    @DeleteMapping("/delete/{id}")
-//    public R<Boolean> delete(@PathVariable Long id) {
-//        log.info("CommonRatingController.delete?id={}", id);
-//        return R.data(commonRatingService.removeById(id));
-//    }
-//
-//    @ApiOperation("获取平均评分")
-//    @ApiImplicitParams({
-//            @ApiImplicitParam(name = "businessType", value = "业务类型", dataType = "Integer", paramType = "query", required = true),
-//            @ApiImplicitParam(name = "businessId", value = "业务ID", dataType = "Long", paramType = "query", required = true)
-//    })
-//    @GetMapping("/getAverageScore")
-//    public R<Double> getAverageScore(
-//            @RequestParam Integer businessType,
-//            @RequestParam Long businessId) {
-//        log.info("CommonRatingController.getAverageScore?businessType={}&businessId={}", businessType, businessId);
-//        return R.data(commonRatingService.getAverageScore(businessType, businessId));
-//    }
-//
-//    @ApiOperation("获取评价数量")
-//    @ApiImplicitParams({
-//            @ApiImplicitParam(name = "businessType", value = "业务类型", dataType = "Integer", paramType = "query", required = true),
-//            @ApiImplicitParam(name = "businessId", value = "业务ID", dataType = "Long", paramType = "query", required = true)
-//    })
-//    @GetMapping("/getRatingCount")
-//    public R<Long> getRatingCount(
-//            @RequestParam Integer businessType,
-//            @RequestParam Long businessId) {
-//        log.info("CommonRatingController.getRatingCount?businessType={}&businessId={}", businessType, businessId);
-//        return R.data(commonRatingService.getRatingCount(businessType, businessId));
-//    }
+    // 查询我自己的评价
+    @ApiOperation("查询我的评价")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "当前页码", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "每页数量", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "businessType", value = "业务类型", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "auditStatus", value = "审核状态", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/getMyRatingList")
+    public R getMyRatingList(@RequestParam(defaultValue = "1") Integer pageNum,
+                             @RequestParam(defaultValue = "10") Integer pageSize,
+                             @RequestParam Integer businessType,
+                             @RequestParam Long userId,
+                             @RequestParam(required = false) Integer auditStatus) {
+        log.info("CommonRatingController.getMyRatingList?pageNum={}&pageSize={}&businessType={}&userId={}&auditStatus={}", pageNum, pageSize, businessType, userId, auditStatus);
+        return commonRatingService.getMyRatingList(pageNum, pageSize, businessType, userId, auditStatus);
+    }
 
 }
-

+ 27 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreOperationalStatisticsController.java

@@ -165,6 +165,33 @@ public class StoreOperationalStatisticsController {
         }
     }
 
+    @ApiOperation("根据ID查询历史统计记录详情")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "id",
+                    value = "历史记录ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @GetMapping("/history/detail")
+    public R<StoreOperationalStatisticsHistory> getHistoryById(@RequestParam("id") Integer id) {
+        log.info("StoreOperationalStatisticsController.getHistoryById - id={}", id);
+        try {
+            if (id == null || id <= 0) {
+                return R.fail("ID不能为空且必须大于0");
+            }
+            
+            StoreOperationalStatisticsHistory history = storeOperationalStatisticsService.getHistoryById(id);
+            return R.data(history);
+        } catch (Exception e) {
+            log.error("查询历史统计记录详情失败 - id={}, error={}", id, e.getMessage(), e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+
     @ApiOperation("删除历史统计记录")
     @ApiOperationSupport(order = 4)
     @ApiImplicitParams({

+ 11 - 0
alien-store/src/main/java/shop/alien/store/service/CommonRatingService.java

@@ -61,6 +61,17 @@ public interface CommonRatingService extends IService<CommonRating> {
      */
     void updateStoreScoreAfterDelete(Integer businessId);
 
+    /**
+     * 查询我的评价
+     * @param pageNum
+     * @param pageSize
+     * @param businessType
+     * @param userId
+     * @param auditStatus
+     * @return
+     */
+    R getMyRatingList(Integer pageNum, Integer pageSize, Integer businessType, Long userId, Integer auditStatus);
+
 
   /*  /**
      * 根据业务类型和业务ID获取平均评分

+ 8 - 0
alien-store/src/main/java/shop/alien/store/service/StoreOperationalStatisticsService.java

@@ -50,6 +50,14 @@ public interface StoreOperationalStatisticsService {
     IPage<StoreOperationalStatisticsHistory> getHistoryList(Integer storeId, Integer page, Integer size);
 
     /**
+     * 根据ID查询历史统计记录详情
+     *
+     * @param id 历史记录ID
+     * @return 历史统计记录详情
+     */
+    StoreOperationalStatisticsHistory getHistoryById(Integer id);
+
+    /**
      * 删除历史统计记录(逻辑删除)
      *
      * @param id 历史记录ID

+ 18 - 1
alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java

@@ -323,7 +323,8 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                 wrapper.eq(CommonRating::getId, -1);
             }
         }
-
+        // 只查询审核通过的评价
+        wrapper.eq(CommonRating::getAuditStatus, 1);
         wrapper.eq(CommonRating::getIsShow, 1);
         wrapper.orderByDesc(CommonRating::getId);
         IPage<CommonRating> page1 = this.page(page, wrapper);
@@ -778,6 +779,22 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
         }
     }
 
+    @Override
+    public R getMyRatingList(Integer pageNum, Integer pageSize, Integer businessType, Long userId, Integer auditStatus) {
+        Page<CommonRating> page = new Page<>(pageNum, pageSize);
+        // 改成queryWrapper
+        QueryWrapper<CommonRating> wrapper = new QueryWrapper<>();
+        wrapper.eq(businessType!=null,"cr.business_type", businessType)
+                .eq(userId != null, "cr.user_id", userId)
+                .eq("cr.delete_flag", 0)
+                .eq(auditStatus != null,"cr.audit_status", auditStatus);
+        wrapper.eq("cr.is_show", 1);
+        wrapper.orderByDesc("cr.created_time");
+        IPage<CommonRating> page1 = commonRatingMapper.getMyRatingList(page, wrapper);
+        R<IPage<CommonRatingVo>> iPageR = doListBusinessWithType(page1, businessType, userId, null);
+        return iPageR;
+    }
+
 /*
     @Override
     public Double getAverageScore(Integer businessType, Long businessId) {

+ 194 - 5
alien-store/src/main/java/shop/alien/store/service/impl/StoreClockInServiceImpl.java

@@ -8,17 +8,25 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import com.alibaba.fastjson2.JSONObject;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.StoreClockInVo;
+import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
+import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.StoreClockInService;
 import shop.alien.store.service.StoreCommentService;
+import shop.alien.store.util.ai.AiContentModerationUtil;
+import shop.alien.store.util.ai.AiVideoModerationUtil;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.*;
+import java.util.concurrent.*;
 import java.util.stream.Collectors;
 
 /**
@@ -29,10 +37,15 @@ import java.util.stream.Collectors;
  * @author ssk
  * @since 2025-05-15
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
 public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, StoreClockIn> implements StoreClockInService {
 
+    // 1. 自定义图片审核线程池(全局复用,避免频繁创建线程)
+    private ExecutorService imgAuditExecutor;
+
     private final StoreClockInMapper storeClockInMapper;
 
     private final LifeUserMapper lifeUserMapper;
@@ -49,6 +62,62 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
 
      private final StoreImgMapper storeImgMapper;
 
+    private final AiContentModerationUtil aiContentModerationUtil;
+
+    private final LifeNoticeMapper lifeNoticeMapper;
+
+    private final WebSocketProcess webSocketProcess;
+
+    // 初始化线程池
+    @PostConstruct
+    public void initExecutor() {
+        // 核心参数根据业务调整,IO密集型任务线程数可设为CPU核心数*2~4
+        int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
+        int maxPoolSize = Runtime.getRuntime().availableProcessors() * 4;
+        imgAuditExecutor = new ThreadPoolExecutor(
+                corePoolSize,
+                maxPoolSize,
+                60L,
+                TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(500), // 任务队列,避免无界队列溢出
+                new ThreadFactory() {
+                    private int count = 0;
+                    @Override
+                    public Thread newThread(Runnable r) {
+                        Thread t = new Thread(r);
+                        t.setName("img-audit-" + count++); // 自定义线程名,便于排查
+                        return t;
+                    }
+                },
+                // 任务拒绝策略:记录日志+调用者线程兜底(避免任务丢失)
+                new ThreadPoolExecutor.CallerRunsPolicy() {
+                    @Override
+                    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
+                        log.error("图片审核任务被拒绝,队列已满!当前队列大小:{},活跃线程数:{}",
+                                e.getQueue().size(), e.getActiveCount());
+                        super.rejectedExecution(r, e);
+                    }
+                }
+        );
+
+    }
+
+    // 优雅关闭线程池
+    @PreDestroy
+    public void destroyExecutor() {
+        if (Objects.nonNull(imgAuditExecutor)) {
+            imgAuditExecutor.shutdown();
+            try {
+                if (!imgAuditExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
+                    imgAuditExecutor.shutdownNow();
+                }
+            } catch (InterruptedException e) {
+                imgAuditExecutor.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
     @Override
     public StoreClockIn addStoreClockIn(StoreClockIn storeClockIn) {
         LifeUser user = lifeUserMapper.selectById(storeClockIn.getUserId());
@@ -72,6 +141,7 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
             IPage<StoreClockInVo> storeClockInIPage1 = storeClockInMapper.getStoreClockInList(iPage, new QueryWrapper<StoreClockIn>()
                     .eq("clock.store_id", storeId)
                     .eq("clock.delete_flag", 0)
+                    .ne("clock.check_flag", 3)
                     .eq("user.delete_flag", 0)
                     .eq("store.delete_flag", 0)
                     .isNotNull(0 == mySelf, "clock.img_url")
@@ -95,6 +165,7 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
         wrapper.eq(1 == mySelf, "clock.user_id", userId);
         wrapper.isNotNull(0 == mySelf, "clock.img_url");
         wrapper.eq("clock.delete_flag", 0);
+        wrapper.ne("clock.check_flag", 3);
         wrapper.eq("user.delete_flag", 0);
         wrapper.eq("store.delete_flag", 0);
         wrapper.and(wrapper1 ->
@@ -212,11 +283,129 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
 
     @Override
     public int setImgAndAiContent(int id, String img,String aiContent) {
+        // 1. 先更新打卡状态为"审核中",然后返回
         LambdaUpdateWrapper<StoreClockIn> wrapper = new LambdaUpdateWrapper<>();
         wrapper.set(StoreClockIn::getImgUrl, img);
-        wrapper.set(StoreClockIn::getMaybeAiContent,aiContent);
+        wrapper.set(StoreClockIn::getMaybeAiContent, aiContent);
+        wrapper.set(StoreClockIn::getCheckFlag, 1); // 1-审核中
         wrapper.eq(StoreClockIn::getId, id);
-        return storeClockInMapper.update(null, wrapper);
+        int updateResult = storeClockInMapper.update(null, wrapper);
+        
+        // 2. 异步调用AI接口审核图片
+        StoreClockIn storeClockIn = storeClockInMapper.selectById(id);
+        if (storeClockIn == null) {
+            log.warn("打卡记录不存在,id={}", id);
+            return updateResult;
+        }
+        
+        LifeUser lifeUser = lifeUserMapper.selectById(storeClockIn.getUserId());
+        if (lifeUser == null) {
+            log.warn("用户不存在,userId={}", storeClockIn.getUserId());
+            return updateResult;
+        }
+        
+        final String phoneId = "user_" + lifeUser.getUserPhone();
+        final Integer clockInId = id;
+        final Integer userId = storeClockIn.getUserId();
+        
+        List<String> imgList = new ArrayList<>();
+        if (StringUtils.isNotBlank(img)) {
+            String[] imgArray = img.split(",");
+            for (String imgUrl : imgArray) {
+                String trimmed = imgUrl.trim();
+                if (StringUtils.isNotBlank(trimmed)) {
+                    imgList.add(trimmed);
+                }
+            }
+        }
+        
+        // 异步执行AI审核任务
+        CompletableFuture.runAsync(() -> {
+            AiContentModerationUtil.AuditResult imgAuditResult = null;
+            try {
+                imgAuditResult = CompletableFuture.supplyAsync(
+                        () -> aiContentModerationUtil.auditContent(null, imgList),
+                        imgAuditExecutor
+                ).get();
+
+                // 3. 审核后按照注释修改状态
+                LambdaUpdateWrapper<StoreClockIn> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(StoreClockIn::getId, clockInId);
+                
+                if (imgAuditResult != null && imgAuditResult.isPassed()) {
+                    // 审核通过,修改状态,打卡可显示在列表中
+                    updateWrapper.set(StoreClockIn::getCheckFlag, 2); // 2-审核通过
+                    updateWrapper.set(StoreClockIn::getReason, null); // 清除拒绝原因
+                    storeClockInMapper.update(null, updateWrapper);
+                    log.info("打卡图片审核通过,打卡ID:{}", clockInId);
+                } else {
+                    // 审核拒绝,修改状态,打卡不可显示在列表中,通知用户,打卡审核不通过
+                    String rejectReason = (imgAuditResult != null && StringUtils.isNotEmpty(imgAuditResult.getFailureReason()))
+                            ? imgAuditResult.getFailureReason()
+                            : "图片内容不符合规范";
+                    updateWrapper.set(StoreClockIn::getCheckFlag, 3); // 2-审核完成(但审核未通过)
+                    updateWrapper.set(StoreClockIn::getReason, rejectReason); // 记录拒绝原因
+                    storeClockInMapper.update(null, updateWrapper);
+                    log.warn("打卡图片审核拒绝,打卡ID:{},原因:{}", clockInId, rejectReason);
+                    
+                    // 通知用户打卡审核不通过
+                    sendAuditRejectNotification(clockInId, userId, phoneId, rejectReason);
+                }
+            } catch (Exception e) {
+                log.error("图片审核接口调用异常,打卡ID:{}", clockInId, e);
+                // 审核异常时,保持审核中状态,记录错误原因
+                LambdaUpdateWrapper<StoreClockIn> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(StoreClockIn::getId, clockInId);
+                updateWrapper.set(StoreClockIn::getCheckFlag, 1); // 保持审核中状态
+                updateWrapper.set(StoreClockIn::getReason, "审核异常,请稍后重试");
+                storeClockInMapper.update(null, updateWrapper);
+            }
+        }, imgAuditExecutor);
+        
+        return updateResult;
+    }
+
+    /**
+     * 发送审核拒绝通知
+     *
+     * @param clockInId 打卡ID
+     * @param userId    用户ID
+     * @param phoneId   用户phoneId
+     * @param reason    拒绝原因
+     */
+    private void sendAuditRejectNotification(Integer clockInId, Integer userId, String phoneId, String reason) {
+        try {
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setSenderId("system");
+            lifeNotice.setBusinessId(clockInId);
+            lifeNotice.setReceiverId(phoneId);
+            lifeNotice.setTitle("打卡审核通知");
+            lifeNotice.setNoticeType(1); // 1-系统通知
+            lifeNotice.setIsRead(0);
+
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("message", "您的打卡审核未通过,原因:" + reason);
+            jsonObject.put("clockInId", clockInId);
+            jsonObject.put("status", "rejected");
+            lifeNotice.setContext(jsonObject.toJSONString());
+
+            // 保存通知
+            lifeNoticeMapper.insert(lifeNotice);
+
+            // 发送WebSocket消息
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId("system");
+            webSocketVo.setReceiverId(phoneId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
+            webSocketProcess.sendMessage(phoneId, JSONObject.from(webSocketVo).toJSONString());
+
+            log.info("打卡审核拒绝通知发送成功,打卡ID:{},接收人:{}", clockInId, phoneId);
+        } catch (Exception e) {
+            log.error("发送打卡审核拒绝通知失败,打卡ID:{},异常信息:{}", clockInId, e.getMessage(), e);
+        }
     }
 
     /**

+ 134 - 1
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalStatisticsServiceImpl.java

@@ -7,13 +7,19 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.http.*;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
 import shop.alien.entity.store.vo.StoreOperationalStatisticsVo;
 import shop.alien.mapper.*;
 import shop.alien.store.service.StoreOperationalStatisticsService;
 import shop.alien.store.util.StatisticsComparisonImageUtil;
+import shop.alien.store.util.ai.AiAuthTokenUtil;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.RandomCreateUtil;
 import shop.alien.util.pdf.ImageToPdfUtil;
@@ -34,6 +40,7 @@ import java.util.*;
 @Slf4j
 @Service
 @RequiredArgsConstructor
+@RefreshScope
 public class StoreOperationalStatisticsServiceImpl implements StoreOperationalStatisticsService {
 
     private final LifeBrowseRecordMapper lifeBrowseRecordMapper;
@@ -56,6 +63,11 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     private final StoreOperationalStatisticsHistoryMapper statisticsHistoryMapper;
     private final StoreTrackStatisticsMapper storeTrackStatisticsMapper;
     private final AliOSSUtil aliOSSUtil;
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+    private final RestTemplate restTemplate;
+
+    @Value("${third-party-ai-store-summary-suggestion.base-url:}")
+    private String aiStatisticsAnalysisUrl;
 
     private static final String DATE_FORMAT = "yyyy-MM-dd";
     private static final String STAT_TYPE_DAILY = "DAILY";
@@ -221,9 +233,19 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         comparison.setPriceListRanking(buildPriceListRankingComparison(currentStatistics.getPriceListRanking(), previousStatistics.getPriceListRanking()));
 
         // 保存历史记录(不包含PDF URL,PDF URL只在 generateStatisticsComparisonPdf 接口中保存)
-        saveStatisticsHistory(storeId, currentStartTime, currentEndTime, 
+        Integer historyId = saveStatisticsHistory(storeId, currentStartTime, currentEndTime, 
                 previousStartTime, previousEndTime, comparison, null);
 
+        // 异步调用AI接口进行数据分析
+        if (historyId != null) {
+            try {
+                callAiAnalysisAsync(historyId, storeId, comparison);
+            } catch (Exception e) {
+                log.error("调用AI分析接口失败 - historyId={}, error={}", historyId, e.getMessage(), e);
+                // AI分析失败不影响主流程,只记录日志
+            }
+        }
+
         return comparison;
     }
 
@@ -1035,6 +1057,31 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     }
 
     @Override
+    public StoreOperationalStatisticsHistory getHistoryById(Integer id) {
+        log.info("StoreOperationalStatisticsServiceImpl.getHistoryById - id={}", id);
+        
+        // 参数校验
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("历史记录ID不能为空且必须大于0");
+        }
+        
+        // 查询历史记录
+        LambdaQueryWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreOperationalStatisticsHistory::getId, id)
+               .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0);
+        
+        StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(wrapper);
+        
+        if (history == null) {
+            log.warn("查询历史统计记录详情失败,记录不存在或已删除 - id={}", id);
+            throw new RuntimeException("历史记录不存在或已删除");
+        }
+        
+        log.info("查询历史统计记录详情成功 - id={}", id);
+        return history;
+    }
+
+    @Override
     public boolean deleteHistory(Integer id) {
         log.info("StoreOperationalStatisticsServiceImpl.deleteHistory - id={}", id);
         
@@ -1738,4 +1785,90 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         return comparison;
     }
 
+    /**
+     * 异步调用AI接口进行数据分析
+     * 
+     * @param historyId 历史记录ID
+     * @param storeId 店铺ID
+     * @param comparison 统计数据对比
+     */
+    private void callAiAnalysisAsync(Integer historyId, Integer storeId, StoreOperationalStatisticsComparisonVo comparison) {
+        // 如果AI接口URL未配置,跳过调用
+        if (!StringUtils.hasText(aiStatisticsAnalysisUrl)) {
+            log.warn("AI统计分析接口URL未配置,跳过AI分析 - historyId={}", historyId);
+            return;
+        }
+
+        try {
+            // 获取访问令牌
+            String accessToken = aiAuthTokenUtil.getAccessToken();
+            if (!StringUtils.hasText(accessToken)) {
+                log.error("调用AI分析接口失败,获取accessToken失败 - historyId={}", historyId);
+                return;
+            }
+
+            // 构建请求体,只发送id
+            Map<String, Object> requestBody = new HashMap<>();
+            requestBody.put("id", historyId);
+
+            // 构建请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            log.info(requestBody.toString());
+
+            HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
+
+            log.info("开始调用AI统计分析接口 - historyId={}, storeId={}", historyId, storeId);
+            ResponseEntity<String> response = restTemplate.postForEntity(
+                    aiStatisticsAnalysisUrl != null ? aiStatisticsAnalysisUrl : "", request, String.class);
+
+            if (response != null && response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI统计分析接口调用成功 - historyId={}, response={}", historyId, responseBody);
+                
+                // 解析AI返回的结果
+                if (StringUtils.hasText(responseBody)) {
+                    try {
+                        @SuppressWarnings("unchecked")
+                        Map<String, Object> responseMap = (Map<String, Object>) JSON.parseObject(responseBody, Map.class);
+                        if (responseMap != null) {
+                            String summary = extractStringValue(responseMap, "summary");
+                            String optimizationSuggestions = extractStringValue(responseMap, "optimizationSuggestions");
+                            
+                            // 更新历史记录的AI分析结果
+                            if (StringUtils.hasText(summary) || StringUtils.hasText(optimizationSuggestions)) {
+                                updateHistoryAiAnalysis(historyId, 1, summary, optimizationSuggestions);
+                                log.info("更新AI分析结果成功 - historyId={}", historyId);
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.warn("解析AI分析结果失败 - historyId={}, responseBody={}, error={}", 
+                                historyId, responseBody, e.getMessage());
+                    }
+                }
+            } else {
+                log.warn("AI统计分析接口调用失败 - historyId={}, statusCode={}", 
+                        historyId, response != null ? response.getStatusCode() : null);
+            }
+        } catch (Exception e) {
+            log.error("调用AI统计分析接口异常 - historyId={}, error={}", historyId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 从Map中提取String值
+     */
+    private String extractStringValue(Map<String, Object> map, String key) {
+        if (map == null) {
+            return null;
+        }
+        Object value = map.get(key);
+        if (value == null) {
+            return null;
+        }
+        return value.toString();
+    }
+
 }