Browse Source

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

dujian 1 day ago
parent
commit
5d60f97443

+ 20 - 4
alien-entity/src/main/java/shop/alien/entity/store/excelVo/util/ExcelGenerator.java

@@ -3,22 +3,38 @@ package shop.alien.entity.store.excelVo.util;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.*;
 import java.lang.reflect.Field;
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.List;
 
 public class ExcelGenerator {
 
+    private static final Logger log = LoggerFactory.getLogger(ExcelGenerator.class);
+
+    /**
+     * 生成 Excel 到本地路径。会在写入前确保父目录存在(含 Linux 生产环境;旧实现仅在 Windows 下 mkdirs,
+     * 导致如 /usr/local/alien/file/excel/generate/ 未预先创建时出现 FileNotFoundException)。
+     */
     public static <T> String generateExcel(String filePath, List<T> dataList, Class<T> clazz) throws IOException {
         String osName = System.getProperty("os.name").toLowerCase();
         if (osName.contains("win")) {
             filePath = "c:/" + filePath;
-            String substring = filePath.substring(0, filePath.lastIndexOf("/"));
-            File dir = new File(substring);
-            if (!dir.exists()) {
-                dir.mkdirs();
+        }
+        Path outputPath = Paths.get(filePath);
+        Path parentDirectory = outputPath.getParent();
+        if (parentDirectory != null) {
+            try {
+                Files.createDirectories(parentDirectory);
+            } catch (IOException e) {
+                log.error("创建 Excel 输出目录失败, filePath={}, parentDirectory={}", filePath, parentDirectory, e);
+                throw e;
             }
         }
         // 创建一个新的工作簿

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeAppealManageVo.java

@@ -36,4 +36,12 @@ public class LifeAppealManageVo extends LifeAppealManage {
     @ApiModelProperty(value = "评论图片")
     private String commentImage;
 
+    @ApiModelProperty(value = "评论来源名称")
+    private String sourceTypeName;
+
+    @ApiModelProperty(value = "评论来源类型")
+    private Integer sourceType;
+
+    @ApiModelProperty(value = "申诉时间")
+    private String auditTime;
 }

+ 5 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFansVo.java

@@ -6,6 +6,8 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.util.Date;
+
 @Data
 @JsonInclude
 @NoArgsConstructor
@@ -59,4 +61,7 @@ public class LifeFansVo {
 
     @ApiModelProperty(value = "是否是已入驻成功的商户 0-不是 1-是(只有 store_application_status = 1 时才是商户)")
     public String isMerchant;
+
+    @ApiModelProperty(value = "时间:我的关注为本人发出关注记录;我的粉丝为对方关注本人(followed_id=fansId)记录;互关为双方较大值。用于排序与回显")
+    private Date createdTime;
 }

+ 8 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeAppealManageMapper.java

@@ -41,4 +41,12 @@ public interface LifeAppealManageMapper extends BaseMapper<LifeAppealManage> {
             @Param("appealType") String appealType,
             @Param("storeContact") String storeContact);
 
+    /**
+     * 平台申诉管理:根据评论申诉主键 id 查询单条详情(与列表查询字段一致)
+     *
+     * @param id store_comment_appeal.id
+     * @return 申诉详情,不存在时返回 null
+     */
+    LifeAppealManageVo getAppealDetailById(@Param("id") Integer id);
+
 }

+ 13 - 12
alien-entity/src/main/java/shop/alien/mapper/LifeFansMapper.java

@@ -16,17 +16,17 @@ import java.util.List;
 public interface LifeFansMapper extends BaseMapper<LifeFans> {
 
     @Select("<script>" +
-            "select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, MAX(foll.blockedType) blockedType, MAX(foll.blockedId) blockedId, MAX(foll.username) username, MAX(foll.accountBlurb) accountBlurb, MAX(foll.isMerchant) isMerchant, " +
+            "select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, MAX(foll.blockedType) blockedType, MAX(foll.blockedId) blockedId, MAX(foll.username) username, MAX(foll.accountBlurb) accountBlurb, MAX(foll.isMerchant) isMerchant, MAX(foll.created_time) created_time, " +
             "  MAX(lb.id) blackListid, MAX(if(isnull(fans.id), 0, 1)) isFollowMe, MAX(if(isnull(fans_this.id), 0, 1)) isFollowThis, MAX(if(isnull(lb.id), '0', '1')) isBlocked,  " +
             "  (select count(1) from life_fans where fans_id= foll.phoneId and delete_flag =0) followNum, " +
             "  (select count(1) from life_fans where followed_id= foll.phoneId and delete_flag =0) fansNum from ( " +
             "    with follow as (   " +
-            "        select substring_index(followed_id, '_', 1) as flag, substring_index(followed_id, '_', -1) as phone   " +
+            "        select substring_index(followed_id, '_', 1) as flag, substring_index(followed_id, '_', -1) as phone, created_time   " +
             "        from life_fans   " +
             "        where delete_flag = 0 and fans_id = #{fansId} " +
             "    )   " +
             "    select info.id, IF(info.store_application_status = 0, user.nick_name, info.store_name) AS NAME," +
-            "    user.head_img image, concat('store_', user.phone) phoneId, IF(info.store_application_status = 0, user.account_blurb, info.store_blurb) AS blurb, 1 blockedType,user.id blockedId ,IFNULL(user.nick_name, user.name) username, user.account_blurb accountBlurb, IF(info.store_application_status = 1, '1', '0') AS isMerchant " +
+            "    user.head_img image, concat('store_', user.phone) phoneId, IF(info.store_application_status = 0, user.account_blurb, info.store_blurb) AS blurb, 1 blockedType,user.id blockedId ,IFNULL(user.nick_name, user.name) username, user.account_blurb accountBlurb, IF(info.store_application_status = 1, '1', '0') AS isMerchant, foll.created_time " +
             "    from follow foll " +
             "    join store_user user on foll.phone = user.phone " +
             "    join store_info info on info.id = user.store_id " +
@@ -34,7 +34,7 @@ public interface LifeFansMapper extends BaseMapper<LifeFans> {
             "    where foll.flag = 'store' and user.delete_flag = 0 and info.delete_flag = 0 " +
             "<if test=\"onlyStoreFollowed == false\">" +
             "    union " +
-            "    select user.id, user.user_name name, user.user_image image, concat('user_', user.user_phone) phoneId, user.jianjie blurb, 2 blockedType,user.id blockedId,'' username, '' accountBlurb, '0' AS isMerchant " +
+            "    select user.id, user.user_name name, user.user_image image, concat('user_', user.user_phone) phoneId, user.jianjie blurb, 2 blockedType,user.id blockedId,'' username, '' accountBlurb, '0' AS isMerchant, foll.created_time " +
             "    from follow foll " +
             "    join life_user user on foll.phone = user.user_phone   " +
             "    where foll.flag = 'user' and user.delete_flag = 0   " +
@@ -70,17 +70,17 @@ public interface LifeFansMapper extends BaseMapper<LifeFans> {
     List<LifeFansVo> getMyFollowedAll(@Param("fansId") String fansId);
 
     @Select("<script>" +
-            "select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, MAX(foll.blockedType) blockedType, MAX(foll.blockedId) blockedId, MAX(foll.isMerchant) isMerchant, " +
+            "select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, MAX(foll.blockedType) blockedType, MAX(foll.blockedId) blockedId, MAX(foll.isMerchant) isMerchant, MAX(foll.created_time) created_time, " +
             "  MAX(lb.id) blackListid, MAX(if(isnull(fans.id), 0, 1)) isFollowThis, MAX(if(isnull(fans_me.id), 0, 1)) isFollowMe, MAX(if(isnull(lb.id), '0', '1')) isBlocked, " +
             "    (select count(1) from life_fans fans2 where fans2.followed_id = foll.phoneId and fans2.delete_flag = 0) fansNum, " +
             "    (select count(1) from life_fans fans3 where fans3.fans_id = foll.phoneId and fans3.delete_flag = 0) followNum " +
             "from ( " +
             "    with follow as ( " +
-            "        select substring_index(fans_id, '_', 1) as flag, substring_index(fans_id, '_', -1) as phone " +
+            "        select substring_index(fans_id, '_', 1) as flag, substring_index(fans_id, '_', -1) as phone, created_time " +
             "        from life_fans " +
             "        where delete_flag = 0 and followed_id = #{fansId} " +
             "    ) " +
-            "    select user.id, IF(info.store_application_status = 0, user.nick_name, info.store_name) AS name, user.head_img image, concat('store_', user.phone) phoneId, IF(info.store_application_status = 0, user.account_blurb, info.store_blurb) AS blurb, 1 blockedType,user.id blockedId, IF(info.store_application_status = 1, '1', '0') AS isMerchant" +
+            "    select user.id, IF(info.store_application_status = 0, user.nick_name, info.store_name) AS name, user.head_img image, concat('store_', user.phone) phoneId, IF(info.store_application_status = 0, user.account_blurb, info.store_blurb) AS blurb, 1 blockedType,user.id blockedId, IF(info.store_application_status = 1, '1', '0') AS isMerchant, foll.created_time " +
             "    from follow foll " +
             "    join store_user user on foll.phone = user.phone " +
             "    join store_info info on info.id = user.store_id " +
@@ -88,7 +88,7 @@ public interface LifeFansMapper extends BaseMapper<LifeFans> {
             "    where foll.flag = 'store' and user.delete_flag = 0 and info.delete_flag = 0" +
             "<if test=\"onlyStoreFans == false\">" +
             "    union " +
-            "    select user.id, user.user_name name, user.user_image image, concat('user_', user.user_phone) phoneId, user.jianjie blurb, 2 blockedType,user.id blockedId, '0' AS isMerchant" +
+            "    select user.id, user.user_name name, user.user_image image, concat('user_', user.user_phone) phoneId, user.jianjie blurb, 2 blockedType,user.id blockedId, '0' AS isMerchant, foll.created_time " +
             "    from follow foll " +
             "    join life_user user on foll.phone = user.user_phone " +
             "    where foll.flag = 'user' and user.delete_flag = 0 " +
@@ -209,25 +209,26 @@ public interface LifeFansMapper extends BaseMapper<LifeFans> {
             "${ew.customSqlSegment} ")
     IPage<LifeFansVo> getMyUserFans(IPage<LifeFansVo> iPage, @Param("fansId") String fansId, @Param(Constants.WRAPPER) QueryWrapper<LifeFansVo> wrapper);
 
-    @Select("select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, MAX(foll.blockedType) blockedType, MAX(foll.blockedId) blockedId, MAX(foll.username) username, MAX(foll.accountBlurb) accountBlurb, MAX(foll.isMerchant) isMerchant, " +
+    @Select("select MAX(foll.id) id, MAX(foll.name) name, MAX(foll.image) image, foll.phoneId, MAX(foll.blurb) blurb, MAX(foll.blockedType) blockedType, MAX(foll.blockedId) blockedId, MAX(foll.username) username, MAX(foll.accountBlurb) accountBlurb, MAX(foll.isMerchant) isMerchant, MAX(foll.created_time) created_time, " +
             "  MAX(lb.id) blackListid, 1 as isFollowThis, 1 as isFollowMe, " +
             "       (select count(1) from life_fans fans2 where fans2.followed_id = foll.phoneId and fans2.delete_flag = 0) fansNum, " +
             "       (select count(1) from life_fans fans3 where fans3.fans_id = foll.phoneId and fans3.delete_flag = 0) followNum " +
             "from ( " +
             "    with follow as ( " +
-            "        select substring_index(fans1.followed_id, '_', 1) as flag, substring_index(fans1.followed_id, '_', -1) as phone " +
+            "        select substring_index(fans1.followed_id, '_', 1) as flag, substring_index(fans1.followed_id, '_', -1) as phone, " +
+            "               GREATEST(fans1.created_time, fans2.created_time) as created_time " +
             "        from life_fans fans1 " +
             "        join life_fans fans2 on fans1.followed_id = fans2.fans_id and fans1.fans_id = fans2.followed_id " +
             "        where fans1.delete_flag = 0 and fans2.delete_flag = 0 and fans1.fans_id = #{fansId} " +
             "    ) " +
-            "    select info.id, IF(info.store_application_status = 0, user.nick_name, info.store_name) AS name, user.head_img image, concat('store_', user.phone) phoneId, IF(info.store_application_status = 0, user.account_blurb, info.store_blurb) AS blurb, 1 blockedType,user.id blockedId ,IFNULL(user.nick_name, user.name) username, user.account_blurb accountBlurb, IF(info.store_application_status = 1, '1', '0') AS isMerchant " +
+            "    select info.id, IF(info.store_application_status = 0, user.nick_name, info.store_name) AS name, user.head_img image, concat('store_', user.phone) phoneId, IF(info.store_application_status = 0, user.account_blurb, info.store_blurb) AS blurb, 1 blockedType,user.id blockedId ,IFNULL(user.nick_name, user.name) username, user.account_blurb accountBlurb, IF(info.store_application_status = 1, '1', '0') AS isMerchant, foll.created_time " +
             "    from follow foll " +
             "    join store_user user on foll.phone = user.phone " +
             "    join store_info info on info.id = user.store_id " +
             "    left join store_img img on img.store_id = user.store_id and img.img_type = '10' and img.delete_flag = 0 " +
             "    where foll.flag = 'store' and user.delete_flag = 0 and info.delete_flag = 0 " +
             "    union " +
-            "    select user.id, user.user_name name, user.user_image image, concat('user_', user.user_phone) phoneId, user.jianjie blurb, 2 blockedType,user.id blockedId,'' username, '' accountBlurb, '0' AS isMerchant " +
+            "    select user.id, user.user_name name, user.user_image image, concat('user_', user.user_phone) phoneId, user.jianjie blurb, 2 blockedType,user.id blockedId,'' username, '' accountBlurb, '0' AS isMerchant, foll.created_time " +
             "    from follow foll " +
             "    join life_user user on foll.phone = user.user_phone " +
             "    where foll.flag = 'user' and user.delete_flag = 0 " +

+ 8 - 5
alien-entity/src/main/java/shop/alien/mapper/LifeUserDynamicsMapper.java

@@ -53,15 +53,17 @@ public interface LifeUserDynamicsMapper extends BaseMapper<LifeUserDynamics> {
     List<LifeUserDynamicsVo> getStoreDynamicslistWithWrapper(@Param("userId") String userId, @Param(Constants.WRAPPER) QueryWrapper<LifeUserDynamics> dynamicsWrapper);
 
     @Select("with middle_lud as (\n" +
-            "  select distinct  \n" +
+            "  select \n" +
             "    lud.*,\n" +
-            "    '1' as isLike  \n" +
+            "    '1' as isLike,\n" +
+            "    llr_sub.like_created_time\n" +
             "  from life_user_dynamics lud\n" +
             "  inner join (\n" +
-            "    select distinct huifu_id  \n" +
+            "    select huifu_id, max(created_time) as like_created_time\n" +
             "    from life_like_record llr\n" +
             "    where llr.dianzan_id = #{phoneId}\n" +
             "      and llr.delete_flag = 0\n" +
+            "    group by huifu_id\n" +
             "  ) llr_sub on lud.id = llr_sub.huifu_id\n" +
             "  where lud.delete_flag = 0\n" +
             "    and not exists (  -- 替代NOT IN,避免NULL问题\n" +
@@ -83,10 +85,11 @@ public interface LifeUserDynamicsMapper extends BaseMapper<LifeUserDynamics> {
             "from middle_lud\n" +
             "left join life_fans lf on lf.fans_id = #{phoneId} \n" +
             "                      and lf.followed_id = middle_lud.phone_id\n" +
-            "                      and lf.delete_flag = '0'" +
+            "                      and lf.delete_flag = '0'\n" +
             "left join life_fans lf1 on lf1.fans_id = middle_lud.phone_id \n" +
             "                      and lf1.followed_id = #{phoneId}\n" +
-            "                      and lf1.delete_flag = '0'")
+            "                      and lf1.delete_flag = '0'\n" +
+            "order by middle_lud.like_created_time desc")
     List<LifeUserDynamicsVo> selectDianZanList(String phoneId);
 
     List<LifeUserDynamicsVo> getDynamicsList(@Param("nickName") String nickName, @Param("userType") String userType, @Param("dynamicsType") Integer dynamicsType, @Param("releaseStartTime") String releaseStartTime, @Param("releaseEndTime") String releaseEndTime, @Param("storeName") String storeName);

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

@@ -115,7 +115,7 @@
         user.delete_flag,
         user.created_time,
         user.is_recommended,
-        firm.firm_name AS firmName,
+        user.law_firm AS firmName,
         firmTwo.payment_account AS paymentNum,
         firmTwo.address AS address,
         GROUP_CONCAT(DISTINCT a.name SEPARATOR ',') AS scenarioNames,

+ 63 - 11
alien-entity/src/main/resources/mapper/LifeAppealManageMapper.xml

@@ -15,6 +15,7 @@
         id, festival_name, festival_date, particular_year
     </sql>
 
+    <!-- 业务类型取自 store_comment.business_type;store_info 无 business_type 列(经营种类为 business_types / business_type_name 等) -->
     <select id="getAppealManagement" resultType="shop.alien.entity.store.vo.LifeAppealManageVo">
         SELECT
         appeal.id,
@@ -23,11 +24,10 @@
         su.`name` AS store_contact,
         lu.user_phone AS user_phone,
         store.store_tel AS store_phone,
-        `comment`.comment_content AS customer_report,
+        `comment`.content AS customer_report,
         appeal.appeal_reason,
         appeal.appeal_status AS appeal_type,
         si.img_url as appeal_image,
-        sg.img_url AS commentImage,
         CASE
         appeal.appeal_status
         WHEN 0 THEN
@@ -37,32 +37,35 @@
         WHEN 2 THEN
         "失败"
         END AS appeal_type_name,
-        `comment`.business_type,
+        `comment`.source_type AS sourceType,
         CASE
-        `comment`.business_type
+        `comment`.source_type
         WHEN 1 THEN
-        "订单评论"
+        "动态的评论"
         WHEN 2 THEN
         "动态社区评论"
         WHEN 3 THEN
-        "活动评论"
-        END AS business_type_name,
-        appeal.created_time AS appeal_time
+        "打卡评论"
+        END AS sourceTypeName,
+        lu.user_phone AS userPhone,
+        appeal.created_time AS appealTime,
+        appeal.updated_time AS auditTime,
+        store.business_section AS businessType,
+        store.business_type_name AS businessTypeName
         FROM
         store_comment_appeal appeal
         LEFT JOIN store_info store ON appeal.store_id = store.id
         LEFT JOIN store_user su ON su.store_id = store.id and su.delete_flag = 0
-        LEFT JOIN store_comment `comment` ON `comment`.id = appeal.comment_id
+        LEFT JOIN common_comment `comment` ON `comment`.id = appeal.comment_id
         LEFT JOIN life_user lu ON `comment`.user_id = lu.id
         LEFT JOIN store_img si ON appeal.img_id = si.id
-        LEFT JOIN store_img sg ON sg.id = `comment`.img_id
         WHERE
         1 = 1
         <if test="storeName != null and storeName != ''">
             AND store.store_name LIKE CONCAT('%', #{storeName}, '%')
         </if>
         <if test="storeContact != null and storeContact != ''">
-            AND `comment`.comment_content LIKE CONCAT('%', #{storeContact}, '%')
+            AND `comment`.content LIKE CONCAT('%', #{storeContact}, '%')
         </if>
         <if test="storePhone != null and storePhone != ''">
             AND lu.user_phone LIKE CONCAT('%', #{storePhone}, '%')
@@ -74,4 +77,53 @@
         appeal.created_time DESC
     </select>
 
+    <!-- 与 getAppealManagement 同字段,按评论申诉主键查单条,供平台端详情 -->
+    <select id="getAppealDetailById" resultType="shop.alien.entity.store.vo.LifeAppealManageVo">
+        SELECT
+        appeal.id,
+        appeal.store_id,
+        store.store_name,
+        su.`name` AS store_contact,
+        lu.user_phone AS user_phone,
+        store.store_tel AS store_phone,
+        `comment`.content AS customer_report,
+        appeal.appeal_reason,
+        appeal.appeal_status AS appeal_type,
+        si.img_url as appeal_image,
+        CASE
+        appeal.appeal_status
+        WHEN 0 THEN
+        "待处理"
+        WHEN 1 THEN
+        "成功"
+        WHEN 2 THEN
+        "失败"
+        END AS appeal_type_name,
+        `comment`.source_type,
+        CASE
+        `comment`.source_type
+        WHEN 1 THEN
+            "动态的评论"
+        WHEN 2 THEN
+            "动态社区评论"
+        WHEN 3 THEN
+            "打卡评论"
+        END AS source_type_name,
+        appeal.created_time AS appeal_time,
+        appeal.updated_time AS auditTime,
+        lu.user_phone AS userPhone,
+        store.business_section AS businessType,
+        store.business_type_name AS businessTypeName
+        FROM
+        store_comment_appeal appeal
+        LEFT JOIN store_info store ON appeal.store_id = store.id
+        LEFT JOIN store_user su ON su.store_id = store.id and su.delete_flag = 0
+        LEFT JOIN common_comment `comment` ON `comment`.id = appeal.comment_id
+        LEFT JOIN life_user lu ON `comment`.user_id = lu.id
+        LEFT JOIN store_img si ON appeal.img_id = si.id
+        WHERE
+        appeal.id = #{id}
+        LIMIT 1
+    </select>
+
 </mapper>

+ 3 - 3
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerUserServiceImpl.java

@@ -687,9 +687,9 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
             }
 
         }
+
         if (lawyerUserVo.getFirmId() != null) {
             lawyerUser.setFirmId(lawyerUserVo.getFirmId());
-            lawyerUser.setLawFirm(lawyerUserVo.getFirmName());
             Integer result = lawyerUserMapper.updateLawyerUser(lawyerUser);
             if (result <= 0) {
                 log.warn("更新律所信息失败:更新数据库失败,律师ID={}", lawyerUserVo.getId());
@@ -794,8 +794,8 @@ public class LawyerUserServiceImpl extends ServiceImpl<LawyerUserMapper, LawyerU
 
 // 只有当有字段需要更新时才执行更新操作
         if (hasUpdate) {
-
-            Integer result = lawyerUserMapper.updateLawyerUser(lawyerUser);
+            lawyerUser.setLawFirm(lawyerUserVo.getLawFirm());
+            Integer result = lawyerUserMapper.updateById(lawyerUser);
             if (result <= 0) {
                 log.warn("更新律师用户信息失败:更新数据库失败,律师ID={}", lawyerUserVo.getId());
                 return R.fail("修改律师信息失败");

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

@@ -85,7 +85,7 @@ public class CommonRatingController {
             storeId = "#{#commonRating.businessId}",
             targetType = "STORE"
     )
-    @ApiOperation(value = "新增评价", notes = "0:成功(包括审核通过和审核不通过但允许创建的情况), 1:失败, 2:内容审核不通过(直接拒绝)。注意:小票审核和打卡审核不通过时,允许创建评论但审核状态设为不通过并记录审核原因")
+    @ApiOperation(value = "新增评价", notes = "0:成功(包括审核通过和审核不通过但允许创建的情况), 1:失败, 2:内容审核不通过(直接拒绝)。注意:小票审核和打卡审核不通过时,允许创建评论但审核状态设为不通过并记录审核原因;若打卡未通过且小票未通过(含未上传有效消费凭证),将推送「评价审核失败通知」并含店铺名、失败原因与参与指引")
     @PostMapping("/addRating")
     public R<Integer> add(@RequestBody CommonRating commonRating) {
         log.info("CommonRatingController.add?commonRating={}", commonRating);

+ 13 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeAppealManageController.java

@@ -124,4 +124,17 @@ public class LifeAppealManageController {
         String s = lifeAppealManageService.appealsExport(appealType, storeName, storePhone);
         return R.data(s);
     }
+
+    /**
+     * 根据申诉主键查询详情(平台申诉管理列表同维度的单条数据)。
+     *
+     * @param id 评论申诉主键,对应列表中的记录 id
+     */
+    @ApiOperation("根据id查询申诉详情")
+    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "申诉id(store_comment_appeal 主键)", dataType = "Integer", paramType = "query", required = true)})
+    @GetMapping("/getAppealDetail")
+    public R<LifeAppealManageVo> getAppealDetail(@RequestParam("id") Integer id) {
+        log.info("LifeAppealManageController.getAppealDetail?id={}", id);
+        return lifeAppealManageService.getAppealDetailById(id);
+    }
 }

+ 23 - 0
alien-store/src/main/java/shop/alien/store/service/LifeAppealManageService.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
@@ -18,6 +19,7 @@ import shop.alien.entity.store.dto.LifeAppealManageDto;
 import shop.alien.entity.store.excelVo.LifeAppealManageExcelVo;
 import shop.alien.entity.store.excelVo.StoreInfoExcelVo;
 import shop.alien.entity.store.excelVo.util.ExcelGenerator;
+import shop.alien.entity.result.R;
 import shop.alien.entity.store.vo.LifeAppealManageVo;
 import shop.alien.mapper.LifeAppealManageMapper;
 import shop.alien.mapper.LifeNoticeMapper;
@@ -36,6 +38,7 @@ import java.util.UUID;
 /**
  * 申诉管理
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class LifeAppealManageService {
@@ -160,4 +163,24 @@ public class LifeAppealManageService {
         String url = aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
         return url;
     }
+
+    /**
+     * 平台端:根据申诉主键查询详情(与「获取申诉列表」同一数据源 store_comment_appeal)。
+     *
+     * @param id 评论申诉表主键 {@code store_comment_appeal.id}
+     * @return 成功返回详情;id 为空或记录不存在返回失败信息
+     */
+    public R<LifeAppealManageVo> getAppealDetailById(Integer id) {
+        if (id == null) {
+            log.warn("LifeAppealManageService.getAppealDetailById: 申诉ID为空");
+            return R.fail("申诉ID不能为空");
+        }
+        LifeAppealManageVo appealDetail = lifeAppealManageMapper.getAppealDetailById(id);
+        if (appealDetail == null) {
+            log.info("LifeAppealManageService.getAppealDetailById: 未找到记录, id={}", id);
+            return R.fail("申诉记录不存在");
+        }
+        log.info("LifeAppealManageService.getAppealDetailById: 查询成功, id={}, storeId={}", id, appealDetail.getStoreId());
+        return R.data(appealDetail);
+    }
 }

+ 3 - 0
alien-store/src/main/java/shop/alien/store/service/LifeStoreService.java

@@ -80,6 +80,7 @@ public class LifeStoreService {
                 .or(StringUtils.isNotEmpty(name))
                 .like(StringUtils.isNotEmpty(name), "foll.phoneId", name);
         wrapper.groupBy("foll.phoneId");
+        wrapper.last(" ORDER BY MAX(foll.created_time) DESC");
         String trimmedMyFansId = StringUtils.isNotEmpty(myFansId) ? myFansId.trim() : null;
         String relationFansId = StringUtils.isNotEmpty(trimmedMyFansId) ? trimmedMyFansId : fansId;
         String blockerSourceId = StringUtils.isNotEmpty(trimmedMyFansId) ? trimmedMyFansId : fansId;
@@ -122,6 +123,7 @@ public class LifeStoreService {
         QueryWrapper<LifeFansVo> wrapper = new QueryWrapper<>();
         wrapper.like(StringUtils.isNotEmpty(name), "foll.name", name);
         wrapper.groupBy("foll.phoneId");
+        wrapper.last(" ORDER BY MAX(foll.created_time) DESC");
         String trimmedMyFansId = StringUtils.isNotEmpty(myFansId) ? myFansId.trim() : null;
         String relationFansId = StringUtils.isNotEmpty(trimmedMyFansId) ? trimmedMyFansId : fansId;
         String blockerSourceId = StringUtils.isNotEmpty(trimmedMyFansId) ? trimmedMyFansId : fansId;
@@ -183,6 +185,7 @@ public class LifeStoreService {
         QueryWrapper<LifeFansVo> wrapper = new QueryWrapper<>();
         wrapper.like(StringUtils.isNotEmpty(name), "foll.name", name);
         wrapper.groupBy("foll.phoneId");
+        wrapper.last(" ORDER BY MAX(foll.created_time) DESC");
         String blockerType = "";
         String blockerId = "";
         if ("user".equals(fansId.split("_")[0])) {

+ 17 - 6
alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java

@@ -293,7 +293,8 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
             }
             
             // 判断审核结果:如果两个条件都不满足,判定为审核不通过;至少满足一个条件,算审核通过
-            if (!condition1Passed && !condition2Passed) {
+            boolean bothConditionFailed = !condition1Passed && !condition2Passed;
+            if (bothConditionFailed) {
                 // 两个条件都不满足,判定为审核不通过
                 if (auditReason == null) {
                     if (imageUrls.isEmpty()) {
@@ -329,19 +330,29 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                 return 1;
             }
             
-            // 6. 如果审核不通过(小票审核或打卡审核不通过),发送websocket通知给用户
-            if (commonRating.getAuditStatus() != null && commonRating.getAuditStatus() == 2 && StringUtils.isNotEmpty(auditReason)) {
+            // 6. 打卡与小票均未通过(含未上传有效消费凭证)时发送通知:评价审核失败通知 + 引导文案
+            if (commonRating.getAuditStatus() != null && commonRating.getAuditStatus() == 2 && StringUtils.isNotEmpty(auditReason) && bothConditionFailed) {
                 try {
                     // 获取用户信息
                     LifeUser lifeUser = lifeUserMapper.selectById(commonRating.getUserId());
                     if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone())) {
                         String receiverId = "user_" + lifeUser.getUserPhone();
-                        
+
+                        String storeName = "该";
+                        StoreInfo storeForNotice = storeInfoMapper.selectById(commonRating.getBusinessId());
+                        if (storeForNotice != null && StringUtils.isNotEmpty(storeForNotice.getStoreName())) {
+                            storeName = storeForNotice.getStoreName();
+                        }
+                        String noticeTitle = "评价审核通知";
+                        String fullMessage = "您对" + storeName + "店铺的评价未通过审核。失败原因:" + auditReason
+                                + "。您可通过成功发布一条打卡内容,或在评价中上传消费记录两种方式参与店铺评价,审核成功后可在\"我的评价及店铺评价\"中查看。";
+
                         // 构建通知内容
                         JSONObject contextJson = new JSONObject();
                         contextJson.put("ratingId", commonRating.getId());
                         contextJson.put("storeId", commonRating.getBusinessId());
-                        contextJson.put("message", "您的评价审核未通过:" + auditReason);
+                        contextJson.put("noticeName", noticeTitle);
+                        contextJson.put("message", fullMessage);
                         contextJson.put("auditReason", auditReason);
                         
                         // 保存通知到数据库
@@ -349,7 +360,7 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                         lifeNotice.setSenderId("system");
                         lifeNotice.setReceiverId(receiverId);
                         lifeNotice.setBusinessId(commonRating.getBusinessId());
-                        lifeNotice.setTitle("评价审核通知");
+                        lifeNotice.setTitle(noticeTitle);
                         lifeNotice.setContext(contextJson.toJSONString());
                         lifeNotice.setNoticeType(Constants.Notice.SYSTEM_NOTICE); // 系统通知
                         lifeNotice.setIsRead(0);

+ 1 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeBlacklistServiceImpl.java

@@ -93,6 +93,7 @@ public class LifeBlacklistServiceImpl extends ServiceImpl<LifeBlacklistMapper, L
         } else if (2 == type) {
             wrapper.eq(LifeBlacklist::getBlockedType, "2");
         }
+        wrapper.orderByDesc(LifeBlacklist::getCreatedTime);
         List<LifeBlacklist> lifeBlacklists = lifeBlacklistMapper.selectList(wrapper);
 
         for (LifeBlacklist lifeBlacklist : lifeBlacklists) {

+ 2 - 0
alien-store/src/main/java/shop/alien/store/service/impl/OperationalActivityServiceImpl.java

@@ -177,6 +177,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                 LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
                 if (coupon != null) {
                     vo.setCouponName(coupon.getName());
+                    vo.setCouponType(coupon.getType());
                 }
             }
 
@@ -221,6 +222,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
             LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
             if (coupon != null) {
                 vo.setCouponName(coupon.getName());
+                vo.setCouponType(coupon.getType());
             }
         }