Browse Source

Merge branch 'sit' into release_lutong_bug

# Conflicts:
#	alien-store/src/main/java/shop/alien/store/service/impl/StoreClockInServiceImpl.java
lutong 2 tháng trước cách đây
mục cha
commit
90e4cfd860
100 tập tin đã thay đổi với 3974 bổ sung610 xóa
  1. 15 74
      alien-api/src/main/resources/logback-spring.xml
  2. 3 3
      alien-entity/src/main/java/shop/alien/entity/store/CommentAppeal.java
  3. 2 2
      alien-entity/src/main/java/shop/alien/entity/store/CommonComment.java
  4. 1 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeNotice.java
  5. 24 4
      alien-entity/src/main/java/shop/alien/entity/store/StoreCuisine.java
  6. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreCuisineCategory.java
  7. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreImg.java
  8. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java
  9. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreOfficialAlbum.java
  10. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StorePlatformUserRole.java
  11. 5 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffConfig.java
  12. 74 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreTable.java
  13. 78 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreTableLog.java
  14. 21 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/BatchQueryTableStatusDTO.java
  15. 20 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineComboDto.java
  16. 18 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineTypeResponseDto.java
  17. 32 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreCuisineCategoryDTO.java
  18. 24 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreCuisineCategorySortDTO.java
  19. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreRenovationRequirementDto.java
  20. 8 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffConfigListQueryDto.java
  21. 0 36
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffFitnessCourseGroup.java
  22. 0 37
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffFitnessCourseItem.java
  23. 28 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreTableChangeDTO.java
  24. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreTableDTO.java
  25. 33 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreVideoSaveDto.java
  26. 22 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/TablewareFeeDto.java
  27. 0 4
      alien-entity/src/main/java/shop/alien/entity/store/excelVo/StoreUserExcelVo.java
  28. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackListVo.java
  29. 1 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderRevenueVO.java
  30. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PerformerVo.java
  31. 6 4
      alien-entity/src/main/java/shop/alien/entity/store/vo/PriceListVo.java
  32. 7 3
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreSubExcelVo.java
  33. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreTableStatusVO.java
  34. 7 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java
  35. 55 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountListVo.java
  36. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountStoreListVo.java
  37. 43 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java
  38. 75 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivityAchievement.java
  39. 93 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivitySignup.java
  40. 64 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseDetailVo.java
  41. 42 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseItemVo.java
  42. 75 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseVo.java
  43. 46 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementVo.java
  44. 26 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java
  45. 132 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDetailVo.java
  46. 79 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityMySignupVo.java
  47. 126 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityPlatformDetailVo.java
  48. 30 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupCheckVo.java
  49. 23 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupIdDTO.java
  50. 32 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupQueryDTO.java
  51. 76 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupVO.java
  52. 9 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java
  53. 1 1
      alien-entity/src/main/java/shop/alien/mapper/LifeBrowseRecordMapper.java
  54. 13 0
      alien-entity/src/main/java/shop/alien/mapper/StoreCuisineCategoryMapper.java
  55. 13 6
      alien-entity/src/main/java/shop/alien/mapper/StoreOfficialAlbumMapper.java
  56. 17 0
      alien-entity/src/main/java/shop/alien/mapper/StorePlatformUserRoleMapper.java
  57. 13 0
      alien-entity/src/main/java/shop/alien/mapper/StoreTableLogMapper.java
  58. 13 0
      alien-entity/src/main/java/shop/alien/mapper/StoreTableMapper.java
  59. 10 0
      alien-entity/src/main/java/shop/alien/mapper/StoreUserMapper.java
  60. 68 0
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivityAchievementMapper.java
  61. 81 0
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivitySignupMapper.java
  62. 3 0
      alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml
  63. 10 3
      alien-entity/src/main/resources/mapper/StoreCuisineMapper.xml
  64. 35 0
      alien-entity/src/main/resources/mapper/StorePlatformUserRoleMapper.xml
  65. 11 0
      alien-entity/src/main/resources/mapper/StoreUserMapper.xml
  66. 9 5
      alien-entity/src/main/resources/mapper/SubAccountStoreMapper.xml
  67. 161 0
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityAchievementMapper.xml
  68. 124 0
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivitySignupMapper.xml
  69. 6 1
      alien-gateway/src/main/java/shop/alien/gateway/service/impl/StoreUserServiceImpl.java
  70. 16 78
      alien-gateway/src/main/resources/logback-spring.xml
  71. 24 11
      alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java
  72. 145 0
      alien-job/src/main/java/shop/alien/job/store/StoreOperationalActivityJob.java
  73. 14 73
      alien-job/src/main/resources/logback-spring.xml
  74. 1 1
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/AiAutoReview.java
  75. 15 74
      alien-lawyer/src/main/resources/logback-spring.xml
  76. 15 75
      alien-second/src/main/resources/logback-spring.xml
  77. 18 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java
  78. 111 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivitySignupController.java
  79. 56 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformUserRoleController.java
  80. 9 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/OperationalActivityService.java
  81. 51 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/OperationalActivitySignupService.java
  82. 28 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformUserRoleService.java
  83. 174 7
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java
  84. 220 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivitySignupServiceImpl.java
  85. 9 9
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreManageServiceImpl.java
  86. 89 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformUserRoleServiceImpl.java
  87. 15 75
      alien-store-platform/src/main/resources/logback-spring.xml
  88. 7 0
      alien-store/pom.xml
  89. 62 0
      alien-store/src/main/java/shop/alien/store/config/WeChatMiniProgramConfig.java
  90. 13 2
      alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java
  91. 218 0
      alien-store/src/main/java/shop/alien/store/controller/AiTagsController.java
  92. 1 1
      alien-store/src/main/java/shop/alien/store/controller/CommonCommentController.java
  93. 9 5
      alien-store/src/main/java/shop/alien/store/controller/CommonRatingController.java
  94. 106 0
      alien-store/src/main/java/shop/alien/store/controller/FeedbackEmailController.java
  95. 2 1
      alien-store/src/main/java/shop/alien/store/controller/OperationalActivityController.java
  96. 147 0
      alien-store/src/main/java/shop/alien/store/controller/StoreCuisineCategoryController.java
  97. 45 0
      alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java
  98. 15 1
      alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java
  99. 128 8
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  100. 13 2
      alien-store/src/main/java/shop/alien/store/controller/StoreOfficialAlbumController.java

+ 15 - 74
alien-api/src/main/resources/logback-spring.xml

@@ -1,40 +1,28 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
-<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
-<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
-<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
-<!-- 该信息是由于设置了当配置文件变化时重新加载,所以每当达到扫描时间的时候就会检查配置文件是否错误。但是由于一般配置文件都放在了JAR包中,
-    而扫描的时候无法扫描JAR包内,因此会提示没有可以检查的文件,所以每隔一段时间就输出一次-->
-<configuration scan="false" scanPeriod="60 seconds" debug="true">
-    <contextName>logback-spring</contextName>
+<configuration scan="false" scanPeriod="60 seconds" debug="false">
 
-    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
     <!-- 定义全局参数常量 -->
     <property name="log.level" value="debug"/>
-    <property name="log.maxHistory" value="30"/><!-- 30表示30 -->
+    <property name="log.maxHistory" value="30"/><!-- 30表示30天 -->
     <springProperty scope="context" name="logging.path" source="logging.path" defaultValue="C:/project/ext/log"/>
     <!--输出文件前缀-->
-    <property name="FILENAME" value="xiaokuihua_api"/>
-
-    <!--0. 日志格式和颜色渲染 -->
-    <!-- 彩色日志依赖的渲染类 -->
-    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
-    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
-    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+    <property name="FILENAME" value="alien"/>
 
     <!-- 文件输出格式 -->
     <property name="FILE_LOG_PATTERN" value="[%d{MM/dd HH:mm:ss.SSS}][%-10.10thread][%-5level][%-40.40c{1}:%5line]:[%15method] || %m%n"/>
-    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+    
+    <!-- 控制台输出格式:纯文本,无颜色,适合 Docker/EFK -->
+    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n"/>
 
     <!--1. 输出到控制台-->
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <!-- 【关键】控制台只输出 INFO 及以上,防止 SQL 刷屏 ES -->
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-            <level>${log.level}</level>
+            <level>INFO</level>
         </filter>
         <encoder>
             <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
-            <!-- 设置字符集 -->
             <charset>UTF-8</charset>
         </encoder>
     </appender>
@@ -42,27 +30,17 @@
     <!--2. 输出到文档-->
     <!-- DEBUG 日志 -->
     <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <!-- 当前的日志文件存放路径 -->
         <file>${logging.path}/DEBUG.log</file>
-        <!-- 日志滚动策略 -->
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 历史日志文件的存放路径和名称 -->
             <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_DEBUG.log.gz</fileNamePattern>
-            <!-- 日志文件最大的保存历史 数量-->
             <maxHistory>${log.maxHistory}</maxHistory>
         </rollingPolicy>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
             <pattern>${FILE_LOG_PATTERN}</pattern>
         </encoder>
-        <!--日志文件最大的大小-->
-        <!--        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
-        <!--            <MaxFileSize>10MB</MaxFileSize>-->
-        <!--        </triggeringPolicy>-->
-        <!-- 此日志文档只记录debug级别的 -->
         <filter class="ch.qos.logback.classic.filter.LevelFilter">
             <level>DEBUG</level>
-            <onMatch>ACCEPT</onMatch>  <!-- 用过滤器,只接受DEBUG级别的日志信息,其余全部过滤掉 -->
+            <onMatch>ACCEPT</onMatch>
             <onMismatch>DENY</onMismatch>
         </filter>
     </appender>
@@ -117,41 +95,14 @@
         </filter>
     </appender>
 
-    <!--
-      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
-      以及指定<appender>。<logger>仅有一个name属性,
-      一个可选的level和一个可选的addtivity属性。
-      name:用来指定受此logger约束的某一个包或者具体的某一个类。
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-         还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
-         如果未设置此属性,那么当前logger将会继承上级的级别。
-      addtivity:是否向上级logger传递打印信息。默认是true。
-      <logger name="org.springframework.web" level="info"/>
-      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-    -->
-
-    <!--
-      使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
-      第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
-      第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-      【logging.level.org.mybatis=debug logging.level.dao=debug】
-     -->
-    <!-- mybatis显示sql,修改此处扫描包名 -->
-
-
-    <!--
-      root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-      不能设置为INHERITED或者同义词NULL。默认是DEBUG
-      可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-    -->
+    <!-- 降噪配置 -->
+    <logger name="springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator" level="WARN"/>
+    <logger name="org.springframework.security.web.DefaultSecurityFilterChain " level="WARN"/>
+    <logger name="com.netflix.config.sources.URLConfigurationSource " level="WARN"/>
+    <logger name="com.netflix.discovery" level="WARN"/>
+    <logger name="org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]" level="WARN"/>
 
     <!-- 4. 最终的策略 -->
-    <!-- 4.1 开发环境:打印控制台-->
-    <!--打印sql-->
-    <!--    <logger name="com.veryhappy.music.dao" level="debug"/>-->
-
-    <!--打印log-->
     <root level="info">
         <appender-ref ref="CONSOLE"/>
         <appender-ref ref="DEBUG_FILE"/>
@@ -160,14 +111,4 @@
         <appender-ref ref="ERROR_FILE"/>
     </root>
 
-    <!--   4.2 生产环境:输出到文档-->
-    <springProfile name="pro">
-        <root level="info">
-            <appender-ref ref="CONSOLE"/>
-            <appender-ref ref="DEBUG_FILE"/>
-            <appender-ref ref="INFO_FILE"/>
-            <appender-ref ref="ERROR_FILE"/>
-            <appender-ref ref="WARN_FILE"/>
-        </root>
-    </springProfile>
 </configuration>

+ 3 - 3
alien-entity/src/main/java/shop/alien/entity/store/CommentAppeal.java

@@ -97,8 +97,8 @@ public class CommentAppeal extends Model<CommentAppeal> {
     @TableField("order_id")
     private Integer orderId;
 
-//    @ApiModelProperty(value = "评价图片")
-//    @TableField("review_images")
-//    private String reviewImages;
+    @ApiModelProperty(value = "评价图片")
+    @TableField("review_images")
+    private String reviewImages;
 }
 

+ 2 - 2
alien-entity/src/main/java/shop/alien/entity/store/CommonComment.java

@@ -28,11 +28,11 @@ public class CommonComment implements Serializable {
     @TableId(value = "id", type = IdType.AUTO)
     private Long id;
 
-    @ApiModelProperty(value = "评论来源类型:1-评价的评论 2-动态评论")
+    @ApiModelProperty(value = "评论来源类型:1-评价的评论 2-动态评论 3-打卡评论")
     @TableField("source_type")
     private Integer sourceType;
 
-    @ApiModelProperty(value = "来源关联ID:source_type=1时=rating.id,=2时=动态ID")
+    @ApiModelProperty(value = "来源关联ID:source_type=1时=rating.id,=2时=动态ID,=3打卡id")
     @TableField("source_id")
     private Long sourceId;
 

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

@@ -30,6 +30,7 @@ public class LifeNotice {
     private Integer businessId;
 
     @ApiModelProperty(value = "标题")
+    @TableField(value = "title", insertStrategy = FieldStrategy.IGNORED)
     private String title;
 
     @ApiModelProperty(value = "公告内容")

+ 24 - 4
alien-entity/src/main/java/shop/alien/entity/store/StoreCuisine.java

@@ -30,6 +30,14 @@ public class StoreCuisine {
     @TableField("store_id")
     private Integer storeId;
 
+    @ApiModelProperty(value = "美食类型: 1-单品,2-套餐")
+    @TableField("cuisine_type")
+    private Integer cuisineType;
+
+    @ApiModelProperty(value = "菜品分类ids(JSON数组,如:[1,2,3])")
+    @TableField("category_ids")
+    private String categoryIds;
+
     @ApiModelProperty(value = "菜名")
     @TableField("name")
     private String name;
@@ -38,6 +46,18 @@ public class StoreCuisine {
     @TableField("total_price")
     private BigDecimal totalPrice;
 
+    @ApiModelProperty(value = "首页展示(0:否, 1:是)")
+    @TableField("is_homepage_display")
+    private Integer isHomepageDisplay;
+
+    @ApiModelProperty(value = "菜品标签(JSON数组,如:[\"招牌菜\",\"推荐\"])")
+    @TableField("tags")
+    private String tags;
+
+    @ApiModelProperty(value = "菜品短评")
+    @TableField("dish_review")
+    private String dishReview;
+
     @ApiModelProperty(value = "图片列表,最多 9 张 URL")
     @TableField("images")
     private String images;
@@ -54,6 +74,10 @@ public class StoreCuisine {
     @TableField("detail_content")
     private String detailContent;
 
+    @ApiModelProperty(value = "菜品描述")
+    @TableField("description")
+    private String description;
+
     @ApiModelProperty(value = "补充说明")
     @TableField("extra_note")
     private String extraNote;
@@ -86,10 +110,6 @@ public class StoreCuisine {
     @TableField("rejection_reason")
     private String rejectionReason;
 
-    @ApiModelProperty(value = "美食类型: 1-单品,2-套餐")
-    @TableField("cuisine_type")
-    private Integer cuisineType;
-
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

+ 66 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreCuisineCategory.java

@@ -0,0 +1,66 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 菜品分类表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_cuisine_category")
+@ApiModel(value = "StoreCuisineCategory对象", description = "菜品分类表")
+public class StoreCuisineCategory {
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "分类名称")
+    @TableField("category_name")
+    private String categoryName;
+
+    @ApiModelProperty(value = "状态(0:禁用, 1:启用)")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "排序")
+    @TableField("sort")
+    private Integer sort;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreImg.java

@@ -84,4 +84,8 @@ public class StoreImg extends Model<StoreImg> {
     @ApiModelProperty(value = "相册名称")
     @TableField(exist = false)
     private String albumName;
+
+    @ApiModelProperty(value = "活动类型:COMMENT-评论有礼,MARKETING-营销活动")
+    @TableField(exist = false)
+    private String activityType;
 }

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java

@@ -355,4 +355,8 @@ public class StoreInfo {
     @TableField("store_tickets")
     private Integer storeTickets;
 
+    @ApiModelProperty(value = "餐具费(限2位正整数)")
+    @TableField("tableware_fee")
+    private Integer tablewareFee;
+
 }

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreOfficialAlbum.java

@@ -32,6 +32,10 @@ public class StoreOfficialAlbum extends Model<StoreOfficialAlbum> {
     @ApiModelProperty(value = "相册名称")
     @TableField("album_name")
     private String albumName;
+    
+    @ApiModelProperty(value = "是否固定 (0-否, 1-是)")
+    @TableField("is_fixed")
+    private Integer isFixed;
 
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/StorePlatformUserRole.java

@@ -55,4 +55,8 @@ public class StorePlatformUserRole implements Serializable {
     @TableField("account_name")
     private String accountName;
 
+    @ApiModelProperty(value = "禁用标志(0启用 1 禁用)")
+    @TableField("status")
+    private Integer status;
+
 }

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

@@ -144,4 +144,9 @@ public class StoreStaffConfig extends Model<StoreStaffConfig> {
     @ApiModelProperty(value = "平均评分(1-5星,支持0.5星)")
     @TableField("service_score")
     private java.math.BigDecimal serviceScore;
+
+    @ApiModelProperty(value = "审核时间")
+    @TableField("audit_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
 }

+ 74 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreTable.java

@@ -0,0 +1,74 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 桌号表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_table")
+@ApiModel(value = "StoreTable对象", description = "桌号表")
+public class StoreTable {
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "桌号")
+    @TableField("table_number")
+    private String tableNumber;
+
+    @ApiModelProperty(value = "当前进行中的订单ID(订单结账或取消后清空)")
+    @TableField("current_order_id")
+    private Integer currentOrderId;
+
+    @ApiModelProperty(value = "二维码URL")
+    @TableField("qrcode_url")
+    private String qrcodeUrl;
+
+    @ApiModelProperty(value = "状态(0:空闲, 1:就餐中, 2:其他)")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    @TableField("remark")
+    private String remark;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}

+ 78 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreTableLog.java

@@ -0,0 +1,78 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 桌号换桌记录表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_table_log")
+@ApiModel(value = "StoreTableLog对象", description = "桌号换桌记录表")
+public class StoreTableLog {
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "订单ID")
+    @TableField("order_id")
+    private Integer orderId;
+
+    @ApiModelProperty(value = "原桌号ID")
+    @TableField("from_table_id")
+    private Integer fromTableId;
+
+    @ApiModelProperty(value = "原桌号")
+    @TableField("from_table_number")
+    private String fromTableNumber;
+
+    @ApiModelProperty(value = "目标桌号ID")
+    @TableField("to_table_id")
+    private Integer toTableId;
+
+    @ApiModelProperty(value = "目标桌号")
+    @TableField("to_table_number")
+    private String toTableNumber;
+
+    @ApiModelProperty(value = "换桌原因")
+    @TableField("change_reason")
+    private String changeReason;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}

+ 21 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/BatchQueryTableStatusDTO.java

@@ -0,0 +1,21 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 批量查询桌号状态DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "BatchQueryTableStatusDTO对象", description = "批量查询桌号状态")
+public class BatchQueryTableStatusDTO {
+
+    @ApiModelProperty(value = "桌号ID列表", required = true)
+    private List<Integer> tableIds;
+}

+ 20 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineComboDto.java

@@ -28,6 +28,10 @@ public class CuisineComboDto {
     @TableField("cuisine_type")
     private Integer cuisineType;
 
+    @ApiModelProperty(value = "菜品分类ids(JSON数组,如:[1,2,3])")
+    @TableField("category_ids")
+    private String categoryIds;
+
     @ApiModelProperty(value = "商户id")
     @TableField("store_id")
     private Integer storeId;
@@ -40,6 +44,18 @@ public class CuisineComboDto {
     @TableField("total_price")
     private BigDecimal totalPrice;
 
+    @ApiModelProperty(value = "首页展示(0:否, 1:是)")
+    @TableField("is_homepage_display")
+    private Integer isHomepageDisplay;
+
+    @ApiModelProperty(value = "菜品标签(JSON数组,如:[\"招牌菜\",\"推荐\"])")
+    @TableField("tags")
+    private String tags;
+
+    @ApiModelProperty(value = "菜品短评")
+    @TableField("dish_review")
+    private String dishReview;
+
     @ApiModelProperty(value = "图片列表,最多 9 张 URL")
     @TableField("images")
     private String images;
@@ -52,6 +68,10 @@ public class CuisineComboDto {
     @TableField("detail_content")
     private String detailContent;
 
+    @ApiModelProperty(value = "菜品描述")
+    @TableField("description")
+    private String description;
+
     @ApiModelProperty(value = "补充说明")
     @TableField("extra_note")
     private String extraNote;

+ 18 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineTypeResponseDto.java

@@ -38,6 +38,21 @@ public class CuisineTypeResponseDto {
         @ApiModelProperty(value = "总价")
         private BigDecimal totalPrice;
 
+        @ApiModelProperty(value = "菜品分类ids(JSON数组,如:[1,2,3])")
+        private String categoryIds;
+
+        @ApiModelProperty(value = "菜品分类名称(JSON数组,如:[\"热菜\",\"凉菜\"])")
+        private String categoryNames;
+
+        @ApiModelProperty(value = "首页展示(0:否, 1:是)")
+        private Integer isHomepageDisplay;
+
+        @ApiModelProperty(value = "菜品标签(JSON数组,如:[\"招牌菜\",\"推荐\"])")
+        private String tags;
+
+        @ApiModelProperty(value = "菜品短评")
+        private String dishReview;
+
         @ApiModelProperty(value = "图片列表,最多 9 张 URL,逗号分隔")
         private String images;
 
@@ -50,6 +65,9 @@ public class CuisineTypeResponseDto {
         @ApiModelProperty(value = "图文详情-文字")
         private String detailContent;
 
+        @ApiModelProperty(value = "菜品描述")
+        private String description;
+
         @ApiModelProperty(value = "补充说明")
         private String extraNote;
 

+ 32 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreCuisineCategoryDTO.java

@@ -0,0 +1,32 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 菜品分类管理DTO
+ * 用于批量创建和编辑菜品分类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreCuisineCategoryDTO对象", description = "菜品分类管理DTO")
+public class StoreCuisineCategoryDTO {
+
+    @ApiModelProperty(value = "分类ID", notes = "编辑分类时必填")
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID", notes = "批量创建分类时必填")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "分类名称", notes = "编辑分类时必填")
+    private String categoryName;
+
+    @ApiModelProperty(value = "分类名称列表", notes = "批量创建分类时必填,多个分类名称用英文逗号分隔,如:热菜,水果,甜品")
+    private String categoryNames;
+
+}

+ 24 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreCuisineCategorySortDTO.java

@@ -0,0 +1,24 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 菜品分类排序DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreCuisineCategorySortDTO对象", description = "菜品分类排序DTO")
+public class StoreCuisineCategorySortDTO {
+
+    @ApiModelProperty(value = "门店ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "分类排序列表", required = true, notes = "按顺序排列的分类ID列表")
+    private List<Integer> categoryIds;
+}

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreRenovationRequirementDto.java

@@ -71,6 +71,9 @@ public class StoreRenovationRequirementDto {
     @ApiModelProperty(value = "动态状态(0:草稿, 1:已发布, 2:已下架)")
     private Integer status;
 
+    @ApiModelProperty(value = "审核状态(0:待审核, 1:审核通过, 2:审核失败)")
+    private Integer auditStatus;
+
     @ApiModelProperty(value = "浏览数")
     private Integer viewCount;
 

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffConfigListQueryDto.java

@@ -5,6 +5,8 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import java.util.Date;
+
 /**
  * 商家端-员工列表查询 DTO(QueryString 绑定)
  */
@@ -33,6 +35,12 @@ public class StoreStaffConfigListQueryDto {
 
     @ApiModelProperty(value = "员工名称")
     private String staffName;
+
+    @ApiModelProperty(value = "创建时间开始(yyyy-MM-dd HH:mm:ss)")
+    private Date startCreatedTime;
+
+    @ApiModelProperty(value = "创建时间结束(yyyy-MM-dd HH:mm:ss)")
+    private Date endCreatedTime;
 }
 
 

+ 0 - 36
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffFitnessCourseGroup.java

@@ -1,36 +0,0 @@
-package shop.alien.entity.store.dto;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * 运动健身-课程类型分组(用于前端“课程类型下多项目”的绑定;不直接映射数据库表)
- */
-@Data
-@JsonInclude
-@ApiModel(value = "StoreStaffFitnessCourseGroup对象", description = "运动健身-课程类型分组(分组结构用)")
-public class StoreStaffFitnessCourseGroup implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * 课程类型编码:
-     * - 推荐传字典 dictId(更稳定)
-     * - 兼容传字典 dictDetail(名称)
-     */
-    @ApiModelProperty(value = "课程类型(推荐 dictId;兼容 dictDetail)")
-    private String courseType;
-
-    @ApiModelProperty(value = "课程类型名称(可选,便于回填展示)")
-    private String courseTypeName;
-
-    @ApiModelProperty(value = "项目列表")
-    private List<StoreStaffFitnessCourseItem> items;
-}
-
-

+ 0 - 37
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffFitnessCourseItem.java

@@ -1,37 +0,0 @@
-package shop.alien.entity.store.dto;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-
-/**
- * 运动健身-课程项目(用于前端分组绑定;不直接映射数据库表)
- */
-@Data
-@JsonInclude
-@ApiModel(value = "StoreStaffFitnessCourseItem对象", description = "运动健身-课程项目(分组结构用)")
-public class StoreStaffFitnessCourseItem implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    @ApiModelProperty(value = "项目名称")
-    private String courseName;
-
-    @ApiModelProperty(value = "价格类型(0-固定价 1-价格区间)")
-    private Integer coursePriceType;
-
-    @ApiModelProperty(value = "固定价(coursePriceType=0)")
-    private BigDecimal coursePrice;
-
-    @ApiModelProperty(value = "最低价(coursePriceType=1)")
-    private BigDecimal courseMinPrice;
-
-    @ApiModelProperty(value = "最高价(coursePriceType=1)")
-    private BigDecimal courseMaxPrice;
-}
-
-

+ 28 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreTableChangeDTO.java

@@ -0,0 +1,28 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 换桌DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreTableChangeDTO对象", description = "换桌")
+public class StoreTableChangeDTO {
+
+    @ApiModelProperty(value = "订单ID", required = true)
+    private Integer orderId;
+
+    @ApiModelProperty(value = "原桌号ID", required = true)
+    private Integer fromTableId;
+
+    @ApiModelProperty(value = "目标桌号ID", required = true)
+    private Integer toTableId;
+
+    @ApiModelProperty(value = "换桌原因")
+    private String changeReason;
+}

+ 29 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreTableDTO.java

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 桌号管理DTO
+ * 用于批量创建桌号和编辑桌号
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreTableDTO对象", description = "桌号管理DTO")
+public class StoreTableDTO {
+
+    @ApiModelProperty(value = "桌号ID", notes = "编辑桌号时必填")
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID", notes = "批量创建桌号时必填")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "桌号名称", notes = "编辑桌号时必填")
+    private String tableNumber;
+
+    @ApiModelProperty(value = "桌号列表", notes = "批量创建桌号时必填,多个桌号用英文逗号分隔,如:A01,A02,A03")
+    private String tableNumbers;
+}

+ 33 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreVideoSaveDto.java

@@ -0,0 +1,33 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 视频保存DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreVideoSaveDto对象", description = "视频保存DTO")
+public class StoreVideoSaveDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "门店ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "相册ID(业务ID)")
+    private Integer businessId;
+
+    @ApiModelProperty(value = "视频URL列表", required = true)
+    private List<String> videoUrls;
+
+    @ApiModelProperty(value = "视频ID列表")
+    private List<Integer> videoIds;
+
+}

+ 22 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/TablewareFeeDto.java

@@ -0,0 +1,22 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 餐具费DTO
+ *
+ * @author auto-generated
+ * @since 2025-01-27
+ */
+@Data
+@ApiModel(value = "TablewareFeeDto", description = "餐具费DTO")
+public class TablewareFeeDto {
+
+    @ApiModelProperty(value = "门店ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "餐具费(限2位正整数)")
+    private Integer tablewareFee;
+}

+ 0 - 4
alien-entity/src/main/java/shop/alien/entity/store/excelVo/StoreUserExcelVo.java

@@ -23,10 +23,6 @@ public class StoreUserExcelVo {
     @ExcelHeader(value = "账号ID")
     private Integer id;
 
-//    @ApiModelProperty(value = "主账号手机号")
-//    @ExcelHeader(value = "主账号手机号码")
-//    @JsonInclude(JsonInclude.Include.NON_NULL)
-//    private String parentAccountPhone;
 
     @ApiModelProperty(value = "手机号码")
     @ExcelHeader(value = "手机号码")

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackListVo.java

@@ -22,9 +22,15 @@ public class LifeFeedbackListVo implements Serializable {
     @ApiModelProperty(value = "用户昵称")
     private String nickName;
 
+    @ApiModelProperty(value = "用户昵称")
+    private String userNickName;
+
     @ApiModelProperty(value = "账号(手机号)")
     private String phone;
 
+    @ApiModelProperty(value = "用户手机号)")
+    private String userPhone;
+
     @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
     private Integer feedbackType;
 

+ 1 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/OrderRevenueVO.java

@@ -47,3 +47,4 @@ public class OrderRevenueVO implements Serializable {
 
 
 
+

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/PerformerVo.java

@@ -43,5 +43,11 @@ public class PerformerVo implements Serializable {
      */
     @ApiModelProperty(value = "职位")
     private String style;
+
+    /**
+     * 标签(对应 store_staff_config.tag)
+     */
+    @ApiModelProperty(value = "标签")
+    private String tag;
 }
 

+ 6 - 4
alien-entity/src/main/java/shop/alien/entity/store/vo/PriceListVo.java

@@ -1,18 +1,14 @@
 package shop.alien.entity.store.vo;
 
 import com.baomidou.mybatisplus.annotation.*;
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
-import shop.alien.entity.store.StoreCuisine;
-import shop.alien.entity.store.StorePrice;
 
 import java.math.BigDecimal;
 import java.util.Date;
-import java.util.List;
 
 /**
  * 价目表通用VO
@@ -74,6 +70,12 @@ public class PriceListVo {
     @ApiModelProperty(value = "美食类型: 1-单品,2-套餐")
     private Integer cuisineType;
 
+    @ApiModelProperty(value = "菜品分类ids(JSON数组,如:[1,2,3])")
+    private String categoryIds;
+
+    @ApiModelProperty(value = "菜品分类名称(JSON数组,如:[\"热菜\",\"凉菜\"])")
+    private String categoryNames;
+
     @ApiModelProperty(value = "拒绝原因")
     private String rejectionReason;
 

+ 7 - 3
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreSubExcelVo.java

@@ -15,14 +15,18 @@ public class StoreSubExcelVo {
     @ApiModelProperty(value = "序号")
     private Integer serialNumber;
 
-    @ApiModelProperty(value = "主")
-    @ExcelHeader(value = "账号ID")
-    private Integer id;
+    @ApiModelProperty(value = "主账号ID(主账号联系方式所对应的id)")
+    @ExcelHeader(value = "账号ID")
+    private Integer parentAccountId;
 
     @ApiModelProperty(value = "主账号手机号")
     @ExcelHeader(value = "主账号手机号码")
     private String parentAccountPhone;
 
+    @ApiModelProperty(value = "子账号ID")
+    @ExcelHeader(value = "子账号ID")
+    private Integer subAccountId;
+
     @ApiModelProperty(value = "手机号码")
     @ExcelHeader(value = "手机号码")
     private String phone;

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreTableStatusVO.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 桌号状态VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreTableStatusVO对象", description = "桌号状态")
+public class StoreTableStatusVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "桌号ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "状态(0:空闲, 1:就餐中, 2:其他)")
+    private Integer status;
+}

+ 7 - 1
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java

@@ -20,9 +20,12 @@ import java.util.List;
 @JsonInclude
 @ApiModel(value = "StoreUserVo对象", description = "门店用户扩展")
 public class StoreUserVo extends StoreUser {
-     @ApiModelProperty(value = "父账号Id")
+     @ApiModelProperty(value = "父账号Id(主账号联系方式所对应的id)")
     private Integer parentAccountId;
 
+    @ApiModelProperty(value = "子账号ID(用于删除/禁用等操作,子账号列表时必填)")
+    private Integer subAccountId;
+
     @ApiModelProperty(value = "登录Token")
     private String token;
 
@@ -35,6 +38,9 @@ public class StoreUserVo extends StoreUser {
     @ApiModelProperty(value = "账号是否启用")
     private boolean switchStatus;
 
+    @ApiModelProperty(value = "状态展示文案:启用/禁用,供前端直接展示")
+    private String statusName;
+
     @ApiModelProperty(value = "验证码")
     private String verificationCode;
 

+ 55 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountListVo.java

@@ -0,0 +1,55 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 子账号列表信息VO(包含主账号电话)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "SubAccountListVo对象", description = "子账号列表信息VO")
+public class SubAccountListVo {
+
+    @ApiModelProperty(value = "主账号ID")
+    private Integer mainAccountId;
+
+    @ApiModelProperty(value = "主账号联系电话")
+    private String mainAccountPhone;
+
+    @ApiModelProperty(value = "子账号中间表ID")
+    private Integer subAccountId;
+
+    @ApiModelProperty(value = "子账号用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "联系电话(子账号电话)")
+    private String phone;
+
+    @ApiModelProperty(value = "账号名称")
+    private String accountName;
+
+    @ApiModelProperty(value = "店铺ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "角色ID")
+    private Long roleId;
+
+    @ApiModelProperty(value = "角色名称")
+    private String roleName;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "状态(0:启用 1:禁用)")
+    private Integer status;
+}

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountStoreListVo.java

@@ -49,4 +49,7 @@ public class SubAccountStoreListVo implements Serializable {
 
     @ApiModelProperty(value = "手机号")
     private String phone;
+
+    @ApiModelProperty(value = "禁用标志(0启用 1禁用)")
+    private Integer status;
 }

+ 43 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java

@@ -97,5 +97,48 @@ public class StoreOperationalActivity {
     @ApiModelProperty(value = "拒绝原因")
     @TableField("approval_comments")
     private String approvalComments;
+
+    @ApiModelProperty(value = "活动类型:COMMENT-评论有礼, MARKETING-营销活动")
+    @TableField("activity_type")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名开始时间")
+    @TableField("signup_start_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupStartTime;
+
+    @ApiModelProperty(value = "报名结束时间")
+    @TableField("signup_end_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupEndTime;
+
+    @ApiModelProperty(value = "活动限制人数")
+    @TableField("activity_limit_people")
+    private Integer activityLimitPeople;
+
+    @ApiModelProperty(value = "活动详情")
+    @TableField("activity_details")
+    private String activityDetails;
+
+    @ApiModelProperty(value = "结果类型:0-文字, 1-图片")
+    @TableField("result_type")
+    private Integer resultType;
+
+    @ApiModelProperty(value = "结果文字")
+    @TableField("result_text")
+    private String resultText;
+
+    @ApiModelProperty(value = "结果图片地址")
+    @TableField("result_media_url")
+    private String resultMediaUrl;
+
+    @ApiModelProperty(value = "审核时间")
+    @TableField("audit_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
+
+    @ApiModelProperty(value = "上传图片类型:0-本地上传, 1-AI生成")
+    @TableField("upload_img_type")
+    private Integer uploadImgType;
 }
 

+ 75 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivityAchievement.java

@@ -0,0 +1,75 @@
+package shop.alien.entity.storePlatform;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 运营活动成果表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_operational_activity_achievement")
+@ApiModel(value = "StoreOperationalActivityAchievement对象", description = "运营活动成果表")
+public class StoreOperationalActivityAchievement {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    @TableField("activity_id")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名ID")
+    @TableField("signup_id")
+    private Integer signupId;
+
+    @ApiModelProperty(value = "成果描述")
+    @TableField("achievement_desc")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    @TableField("media_urls")
+    private String mediaUrls;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}

+ 93 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivitySignup.java

@@ -0,0 +1,93 @@
+package shop.alien.entity.storePlatform;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 运营活动报名表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_operational_activity_signup")
+@ApiModel(value = "StoreOperationalActivitySignup对象", description = "运营活动报名表")
+public class StoreOperationalActivitySignup {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    @TableField("activity_id")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "商户ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名人姓名")
+    @TableField("user_name")
+    private String userName;
+
+    @ApiModelProperty(value = "手机号")
+    @TableField("phone")
+    private String phone;
+
+    @ApiModelProperty(value = "报名状态:0-待审核,1-拒绝,2-通过")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "审核拒绝原因")
+    @TableField("reject_reason")
+    private String rejectReason;
+
+    @ApiModelProperty(value = "审核时间")
+    @TableField("audit_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
+
+    @ApiModelProperty(value = "报名时间")
+    @TableField("signup_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupTime;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}

+ 64 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseDetailVo.java

@@ -0,0 +1,64 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 运营活动案例详情
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityAchievementCaseDetailVo对象", description = "运营活动案例详情")
+public class StoreOperationalActivityAchievementCaseDetailVo {
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "用户姓名")
+    private String userName;
+
+    @ApiModelProperty(value = "联系方式(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "用户手机号")
+    private String userPhone;
+
+    @ApiModelProperty(value = "用户头像")
+    private String userImage;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "上传情况:0-未上传, 1-已上传")
+    private Integer hasResult;
+
+    @ApiModelProperty(value = "成果列表")
+    private List<StoreOperationalActivityAchievementCaseItemVo> achievementList;
+}

+ 42 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseItemVo.java

@@ -0,0 +1,42 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 运营活动案例详情-成果条目
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityAchievementCaseItemVo对象", description = "运营活动案例详情-成果条目")
+public class StoreOperationalActivityAchievementCaseItemVo {
+
+    @ApiModelProperty(value = "成果ID")
+    private Integer achievementId;
+
+    @ApiModelProperty(value = "成果描述")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    private String mediaUrls;
+
+    @ApiModelProperty(value = "图片/视频URL列表")
+    private List<String> mediaUrlList;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+}

+ 75 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseVo.java

@@ -0,0 +1,75 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 运营活动成果案例列表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreOperationalActivityAchievementCaseVo对象", description = "运营活动成果案例列表")
+public class StoreOperationalActivityAchievementCaseVo {
+
+    @ApiModelProperty(value = "成果ID")
+    private Integer achievementId;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "活动状态")
+    private Integer activityStatus;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "店铺用户头像")
+    private String storeUserHeadImg;
+
+    @ApiModelProperty(value = "店铺用户昵称")
+    private String storeUserNickName;
+
+    @ApiModelProperty(value = "联系方式(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "上传情况:0-未上传, 1-已上传")
+    private Integer hasResult;
+
+    @ApiModelProperty(value = "成果首图")
+    private String firstMediaUrl;
+
+    @ApiModelProperty(value = "更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "用户手机号")
+    private String userPhone;
+
+    @ApiModelProperty(value = "用户名称")
+    private String userName;
+
+    @ApiModelProperty(value = "用户头像")
+    private String userImage;
+}

+ 46 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementVo.java

@@ -0,0 +1,46 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+import java.util.Date;
+
+/**
+ * 运营活动成果返回对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityAchievementVo", description = "运营活动成果返回对象")
+public class StoreOperationalActivityAchievementVo {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名ID")
+    private Integer signupId;
+
+    @ApiModelProperty(value = "成果描述")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    private String mediaUrls;
+
+    @ApiModelProperty(value = "图片/视频URL列表")
+    private List<String> mediaUrlList;
+
+    @ApiModelProperty(value = "创建时间")
+    private Date createdTime;
+}

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java

@@ -82,5 +82,31 @@ public class StoreOperationalActivityDTO {
 
     @ApiModelProperty(value = "AI审核的输入参数")
     private JsonNode auditParam;
+
+    @ApiModelProperty(value = "活动类型:COMMENT-评论有礼, MARKETING-营销活动")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date signupStartTime;
+
+    @ApiModelProperty(value = "报名结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date signupEndTime;
+
+    @ApiModelProperty(value = "活动限制人数")
+    private Integer activityLimitPeople;
+
+    @ApiModelProperty(value = "活动详情")
+    private String activityDetails;
+
+    @ApiModelProperty(value = "结果类型:0-文字, 1-图片")
+    private Integer resultType;
+
+    @ApiModelProperty(value = "结果文字")
+    private String resultText;
+
+    @ApiModelProperty(value = "结果图片地址")
+    private String resultMediaUrl;
 }
 

+ 132 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDetailVo.java

@@ -0,0 +1,132 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 运营活动详情返回对象(用户端)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreOperationalActivityDetailVo", description = "运营活动详情返回对象")
+public class StoreOperationalActivityDetailVo {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "商户ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动宣传图URL")
+    private String promotionalImage;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "用户可参与次数,0表示不限制")
+    private Integer participationLimit;
+
+    @ApiModelProperty(value = "活动规则")
+    private String activityRule;
+
+    @ApiModelProperty(value = "奖励类型")
+    private String rewardType;
+
+    @ApiModelProperty(value = "优惠券ID")
+    private Integer couponId;
+
+    @ApiModelProperty(value = "优惠券发放数量")
+    private Integer couponQuantity;
+
+    @ApiModelProperty(value = "状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "拒绝原因")
+    private String approvalComments;
+
+    @ApiModelProperty(value = "活动类型")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupStartTime;
+
+    @ApiModelProperty(value = "报名结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupEndTime;
+
+    @ApiModelProperty(value = "活动限制人数")
+    private Integer activityLimitPeople;
+
+    @ApiModelProperty(value = "活动详情")
+    private String activityDetails;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "活动状态名称")
+    private String statusName;
+
+    @ApiModelProperty(value = "优惠券名称")
+    private String couponName;
+
+    @ApiModelProperty(value = "商户名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "活动标题图片URL")
+    private String activityTitleImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL")
+    private String activityDetailImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL列表")
+    private List<String> activityDetailImgUrlList;
+
+    @ApiModelProperty(value = "当前报名人数")
+    private Integer currentSignupCount;
+
+    @ApiModelProperty(value = "当前通过人数")
+    private Integer currentApprovedCount;
+
+    @ApiModelProperty(value = "是否已报名")
+    private Boolean signedUp;
+
+    @ApiModelProperty(value = "是否在报名时间内")
+    private Boolean inSignupTime;
+
+    @ApiModelProperty(value = "详情按钮状态:0-活动已结束,1-报名已截止,2-等待活动开始,3-报名成功,4-已报名,5-立即报名")
+    private Integer detailStatus;
+
+    @ApiModelProperty(value = "结果类型:0-文字, 1-图片")
+    private Integer resultType;
+
+    @ApiModelProperty(value = "结果文字")
+    private String resultText;
+
+    @ApiModelProperty(value = "结果图片地址")
+    private String resultMediaUrl;
+
+    @ApiModelProperty(value = "审核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
+
+}

+ 79 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityMySignupVo.java

@@ -0,0 +1,79 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 我的报名列表返回对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityMySignupVo", description = "我的报名列表返回对象")
+public class StoreOperationalActivityMySignupVo {
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "报名ID")
+    private Integer signupId;
+
+    @ApiModelProperty(value = "商户ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动宣传图URL")
+    private String promotionalImage;
+
+    @ApiModelProperty(value = "活动标题图URL")
+    private String activityTitleImgUrl;
+
+    @ApiModelProperty(value = "店铺用户头像")
+    private String storeUserHeadImg;
+
+    @ApiModelProperty(value = "店铺用户昵称")
+    private String storeUserNickName;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "活动状态")
+    private Integer activityStatus;
+
+    @ApiModelProperty(value = "报名状态")
+    private Integer signupStatus;
+
+    @JsonIgnore
+    private Integer signupAuditStatus;
+
+    @ApiModelProperty(value = "活动开始状态(0-已开始,1-已结束)")
+    private Integer activityStartStatus;
+
+    @ApiModelProperty(value = "是否有成果")
+    private Integer hasAchievement;
+
+    @ApiModelProperty(value = "列表状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "报名时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupTime;
+
+    @ApiModelProperty(value = "状态文案")
+    private String statusLabel;
+}

+ 126 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityPlatformDetailVo.java

@@ -0,0 +1,126 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 运营活动详情返回对象(用户端)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityDetailVo", description = "运营活动详情返回对象")
+public class StoreOperationalActivityPlatformDetailVo {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "商户ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动宣传图URL")
+    private String promotionalImage;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "用户可参与次数,0表示不限制")
+    private Integer participationLimit;
+
+    @ApiModelProperty(value = "活动规则")
+    private String activityRule;
+
+    @ApiModelProperty(value = "奖励类型")
+    private String rewardType;
+
+    @ApiModelProperty(value = "优惠券ID")
+    private Integer couponId;
+
+    @ApiModelProperty(value = "优惠券发放数量")
+    private Integer couponQuantity;
+
+    @ApiModelProperty(value = "状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "拒绝原因")
+    private String approvalComments;
+
+    @ApiModelProperty(value = "活动类型")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupStartTime;
+
+    @ApiModelProperty(value = "报名结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupEndTime;
+
+    @ApiModelProperty(value = "活动限制人数")
+    private Integer activityLimitPeople;
+
+    @ApiModelProperty(value = "活动详情")
+    private String activityDetails;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "活动状态名称")
+    private String statusName;
+
+    @ApiModelProperty(value = "优惠券名称")
+    private String couponName;
+
+    @ApiModelProperty(value = "商户名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "活动标题图片URL")
+    private String activityTitleImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL")
+    private String activityDetailImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL列表")
+    private List<String> activityDetailImgUrlList;
+
+    @ApiModelProperty(value = "当前报名人数")
+    private Integer currentSignupCount;
+
+    @ApiModelProperty(value = "当前通过人数")
+    private Integer currentApprovedCount;
+
+    @ApiModelProperty(value = "是否已报名")
+    private Boolean signedUp;
+
+    @ApiModelProperty(value = "是否在报名时间内")
+    private Boolean inSignupTime;
+
+    @ApiModelProperty(value = "详情按钮状态:0-活动已结束,1-报名已截止,2-等待活动开始,3-报名成功,4-已报名,5-立即报名")
+    private Integer detailStatus;
+
+    @ApiModelProperty(value = "结果类型:0-文字, 1-图片")
+    private Integer resultType;
+
+    @ApiModelProperty(value = "结果文字")
+    private String resultText;
+
+    @ApiModelProperty(value = "结果图片地址")
+    private String resultMediaUrl;
+}

+ 30 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupCheckVo.java

@@ -0,0 +1,30 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动报名校验结果
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivitySignupCheckVo", description = "运营活动报名校验结果")
+public class StoreOperationalActivitySignupCheckVo {
+
+    @ApiModelProperty(value = "是否已满")
+    private boolean full;
+
+    @ApiModelProperty(value = "是否已成功报名")
+    private boolean signedUp;
+
+    @ApiModelProperty(value = "当前报名人数")
+    private Integer currentSignupCount;
+
+    @ApiModelProperty(value = "活动限制人数")
+    private Integer activityLimitPeople;
+}

+ 23 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupIdDTO.java

@@ -0,0 +1,23 @@
+package shop.alien.entity.storePlatform.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动报名ID DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivitySignupIdDTO", description = "运营活动报名ID DTO")
+public class StoreOperationalActivitySignupIdDTO {
+
+    @ApiModelProperty(value = "报名ID", required = true)
+    private Integer id;
+
+    @ApiModelProperty(value = "审核拒绝原因(审核拒绝时必填)")
+    private String rejectReason;
+}
+

+ 32 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupQueryDTO.java

@@ -0,0 +1,32 @@
+package shop.alien.entity.storePlatform.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动报名查询DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivitySignupQueryDTO", description = "运营活动报名查询DTO")
+public class StoreOperationalActivitySignupQueryDTO {
+
+    @ApiModelProperty(value = "商户ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "报名状态:0-待审核,1-拒绝,2-通过")
+    private Integer status;
+
+    @ApiModelProperty(value = "活动名称(模糊查询)")
+    private String activityName;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer pageNum;
+
+    @ApiModelProperty(value = "每页大小", example = "10")
+    private Integer pageSize;
+}
+

+ 76 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupVO.java

@@ -0,0 +1,76 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 运营活动报名列表返回对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivitySignupVO", description = "运营活动报名列表返回对象")
+public class StoreOperationalActivitySignupVO {
+
+    @ApiModelProperty(value = "主键ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "商户ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名人姓名")
+    private String userName;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "用户头像")
+    private String userImage;
+
+    @ApiModelProperty(value = "用户手机号")
+    private String userPhone;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+
+    @ApiModelProperty(value = "所属活动")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动类型:COMMENT-评论有礼, MARKETING-营销活动")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名状态:0-待审核,1-拒绝,2-通过")
+    private Integer status;
+
+    @ApiModelProperty(value = "报名状态文字:待审核、拒绝、通过")
+    private String statusText;
+
+    @ApiModelProperty(value = "审核拒绝原因")
+    private String rejectReason;
+
+    @ApiModelProperty(value = "审核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
+
+    @ApiModelProperty(value = "报名时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+}
+

+ 9 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java

@@ -54,5 +54,14 @@ public class StoreOperationalActivityVO extends StoreOperationalActivity {
 
     @ApiModelProperty(value = "活动详情图片")
     private String activityDetailImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL列表")
+    private java.util.List<String> activityDetailImgUrlList;
+
+    @ApiModelProperty(value = "活动类型名称")
+    private String activityTypeName;
+
+    @ApiModelProperty(value = "上传图片类型:0-本地上传, 1-AI生成")
+    private Integer uploadImgType;
 }
 

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

@@ -12,7 +12,7 @@ import java.util.Map;
 public interface LifeBrowseRecordMapper extends BaseMapper<LifeBrowseRecord> {
 
     @Select("SELECT a.id,a.liulan_date liulanDate, b.store_type storeType, b.store_position storePosition, a.liulan_time liulanTime, " +
-            "c.img_url image, b.store_name storeName, b.store_address storeDetailAddress, b.id storeId, a.created_time, d.img_url entranceImage, dict.dict_detail storeTypeStr,ROUND(b.score_avg, 2) scoreAvg,b.business_types_name businessTypesName " +
+            "c.img_url image, b.store_name storeName, b.store_address storeDetailAddress, b.id storeId, a.created_time, d.img_url entranceImage, dict.dict_detail storeTypeStr,ROUND(b.score_avg, 2) scoreAvg,b.business_types_name businessTypesName,b.business_section businessSection " +
             "FROM life_browse_record a " +
             "LEFT JOIN store_info b ON a.store_id = b.id " +
             "LEFT JOIN store_img c on b.id = c.store_id and c.img_type = 11 and c.delete_flag = 0 " +

+ 13 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreCuisineCategoryMapper.java

@@ -0,0 +1,13 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.entity.store.StoreCuisineCategory;
+
+/**
+ * 菜品分类表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreCuisineCategoryMapper extends BaseMapper<StoreCuisineCategory> {
+}

+ 13 - 6
alien-entity/src/main/java/shop/alien/mapper/StoreOfficialAlbumMapper.java

@@ -29,14 +29,21 @@ public interface StoreOfficialAlbumMapper extends BaseMapper<StoreOfficialAlbum>
 
     @Select("select " +
             " soa.id," +
-            " (" +
-            "        SELECT COUNT(*) " +
-            "        FROM store_img b2 " +
-            "        WHERE b2.business_id = soa.id " +
+            " CASE " +
+            "     WHEN soa.album_name = '视频' THEN " +
+            "         (SELECT COUNT(*) " +
+            "          FROM store_video sv " +
+            "          WHERE sv.business_id = soa.id " +
+            "            AND sv.delete_flag = 0 " +
+            "            AND sv.store_id = #{storeId}) " +
+            "     ELSE " +
+            "         (SELECT COUNT(*) " +
+            "          FROM store_img b2 " +
+            "          WHERE b2.business_id = soa.id " +
             "            AND b2.img_type IN ('2', '4') " +
             "            AND b2.delete_flag = 0 " +
-            "            AND b2.store_id = #{storeId} " +
-            "    ) as imgCount " +
+            "            AND b2.store_id = #{storeId}) " +
+            " END as imgCount " +
             " from" +
             " store_official_album soa\n" +
             " where " +

+ 17 - 0
alien-entity/src/main/java/shop/alien/mapper/StorePlatformUserRoleMapper.java

@@ -1,9 +1,12 @@
 package shop.alien.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import shop.alien.entity.store.StorePlatformUserRole;
+import shop.alien.entity.store.vo.SubAccountListVo;
 import shop.alien.entity.store.vo.SubAccountVo;
 
 import java.util.List;
@@ -30,5 +33,19 @@ public interface StorePlatformUserRoleMapper extends BaseMapper<StorePlatformUse
                                         @Param("accountName") String accountName,
                                         @Param("phone") String phone,
                                         @Param("roleName") String roleName);
+
+    /**
+     * 查询所有子账号信息(包含主账号电话)- 分页查询
+     *
+     * @param page      分页对象
+     * @param accountId 账号ID(主账号ID或子账号用户ID,模糊查询)
+     * @param phone      联系电话(子账号电话,模糊查询)
+     * @param status    状态(精确查询)
+     * @return 子账号分页列表
+     */
+    IPage<SubAccountListVo> queryAllSubAccounts(Page<SubAccountListVo> page,
+                                                @Param("accountId") String accountId,
+                                                @Param("phone") String phone,
+                                                @Param("status") Integer status);
 }
 

+ 13 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreTableLogMapper.java

@@ -0,0 +1,13 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.entity.store.StoreTableLog;
+
+/**
+ * 桌号换桌记录表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreTableLogMapper extends BaseMapper<StoreTableLog> {
+}

+ 13 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreTableMapper.java

@@ -0,0 +1,13 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.entity.store.StoreTable;
+
+/**
+ * 桌号表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreTableMapper extends BaseMapper<StoreTable> {
+}

+ 10 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreUserMapper.java

@@ -36,4 +36,14 @@ public interface StoreUserMapper extends BaseMapper<StoreUser> {
     StoreUser getRemoveUser(@Param("id") String id);
 
     StoreUser getUserIncludeDeleted(@Param("id") Integer id);
+
+    /**
+     * 通过 store_user 与 store_platform_user_role 联表查询主账号下的子账号 id 列表(store_user.id)。
+     * 用于删除/禁用时判断主账号是否有关联子账号、主账号下子账号数量等。
+     *
+     * @param mainAccountId 主账号 id(store_user.id)
+     * @return 子账号的 store_user.id 列表
+     */
+    List<Integer> selectSubAccountUserIdsByMainAccountIdWithRole(@Param("mainAccountId") Integer mainAccountId);
+
 }

+ 68 - 0
alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivityAchievementMapper.java

@@ -0,0 +1,68 @@
+package shop.alien.mapper.storePlantform;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.storePlatform.StoreOperationalActivityAchievement;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseDetailVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseItemVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo;
+
+import java.util.List;
+
+/**
+ * 运营活动成果 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StoreOperationalActivityAchievementMapper extends BaseMapper<StoreOperationalActivityAchievement> {
+
+    /**
+     * 分页查询案例列表(同一用户同一活动最新成果)。
+     *
+     * @param page           分页
+     * @param activityStatus 活动状态
+     * @return 分页列表
+     */
+    IPage<StoreOperationalActivityAchievementCaseVo> selectCasePage(
+            IPage<?> page,
+            @Param("activityStatus") Integer activityStatus);
+
+    /**
+     * 案例详情头部信息
+     *
+     * @param activityId 活动ID
+     * @param userId 用户ID
+     * @return 详情
+     */
+    StoreOperationalActivityAchievementCaseDetailVo selectCaseHeader(@Param("activityId") Integer activityId,
+                                                                     @Param("userId") Integer userId);
+
+    /**
+     * 案例详情成果列表
+     *
+     * @param activityId 活动ID
+     * @param userId 用户ID
+     * @return 成果列表
+     */
+    List<StoreOperationalActivityAchievementCaseItemVo> selectCaseItems(@Param("activityId") Integer activityId,
+                                                                        @Param("userId") Integer userId);
+
+    /**
+     * 商家端分页查询案例列表(按商户ID、上传情况、活动名称筛选)
+     *
+     * @param page 分页
+     * @param storeId 商户ID
+     * @param hasResult 上传情况:0-未上传, 1-已上传
+     * @param activityName 活动名称(模糊查询)
+     * @return 分页列表
+     */
+    IPage<StoreOperationalActivityAchievementCaseVo> selectStoreCasePage(
+            IPage<?> page,
+            @Param("storeId") Integer storeId,
+            @Param("hasResult") Integer hasResult,
+            @Param("activityName") String activityName);
+}

+ 81 - 0
alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivitySignupMapper.java

@@ -0,0 +1,81 @@
+package shop.alien.mapper.storePlantform;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityMySignupVo;
+
+import java.util.List;
+
+/**
+ * 运营活动报名 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StoreOperationalActivitySignupMapper extends BaseMapper<StoreOperationalActivitySignup> {
+
+    /**
+     * 根据活动ID查询报名列表(仅未删除)。
+     *
+     * @param activityId 活动ID
+     * @return 报名列表
+     */
+    List<StoreOperationalActivitySignup> selectByActivityId(@Param("activityId") Integer activityId);
+
+    /**
+     * 统计活动当前报名人数(待审核+通过,未删除)。
+     *
+     * @param activityId 活动ID
+     * @return 报名人数
+     */
+    Integer countSignupByActivityId(@Param("activityId") Integer activityId);
+
+    /**
+     * 统计活动当前通过人数(通过状态,未删除)。
+     *
+     * @param activityId 活动ID
+     * @return 通过人数
+     */
+    Integer countApprovedByActivityId(@Param("activityId") Integer activityId);
+
+    /**
+     * 统计用户是否已通过报名。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 报名数量
+     */
+    Integer countApprovedByActivityAndUser(@Param("activityId") Integer activityId,
+                                           @Param("userId") Integer userId);
+
+    /**
+     * 统计用户是否已报名(待审核/通过)。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 报名数量
+     */
+    Integer countSignedUpByActivityAndUser(@Param("activityId") Integer activityId,
+                                           @Param("userId") Integer userId);
+
+    /**
+     * 获取用户最新报名状态。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 报名状态
+     */
+    Integer selectLatestSignupStatus(@Param("activityId") Integer activityId,
+                                     @Param("userId") Integer userId);
+
+    /**
+     * 查询我的报名列表。
+     *
+     * @param userId 用户ID
+     * @return 报名列表
+     */
+    List<StoreOperationalActivityMySignupVo> selectMySignups(@Param("userId") Integer userId);
+}

+ 3 - 0
alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml

@@ -121,7 +121,9 @@
         SELECT
             f.id,
             u.nick_name AS nickName,
+            lu.user_name AS userNickName,
             u.phone AS phone,
+            lu.user_phone AS userPhone,
             f.feedback_type AS feedbackType,
             CASE f.feedback_type
                 WHEN 0 THEN 'bug反馈'
@@ -149,6 +151,7 @@
         FROM life_feedback f
         LEFT JOIN store_user u ON f.user_id = u.id
         LEFT JOIN life_sys s ON f.staff_id = s.id
+        LEFT JOIN life_user lu ON f.user_id = lu.id
         WHERE 1=1
         <if test="feedbackType != null">
             AND f.feedback_type = #{feedbackType}

+ 10 - 3
alien-entity/src/main/resources/mapper/StoreCuisineMapper.xml

@@ -8,12 +8,18 @@
     <resultMap id="BaseResultMap" type="shop.alien.entity.store.StoreCuisine">
         <id column="id" property="id" />
         <result column="store_id" property="storeId" />
+        <result column="cuisine_type" property="cuisineType" />
+        <result column="category_ids" property="categoryIds" />
         <result column="name" property="name" />
         <result column="total_price" property="totalPrice" />
+        <result column="is_homepage_display" property="isHomepageDisplay" />
+        <result column="tags" property="tags" />
+        <result column="dish_review" property="dishReview" />
         <result column="images" property="images" />
         <result column="image_content" property="imageContent" />
         <result column="raw_json" property="rawJson" />
         <result column="detail_content" property="detailContent" />
+        <result column="description" property="description" />
         <result column="extra_note" property="extraNote" />
         <result column="need_reserve" property="needReserve" />
         <result column="reserve_rule" property="reserveRule" />
@@ -31,9 +37,10 @@
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, store_id, name, total_price, images, image_content, raw_json, detail_content,
-        extra_note, need_reserve, reserve_rule, people_limit, usage_rule, status, shelf_status,
-        rejection_reason, delete_flag, created_user_id, updated_user_id, created_time, updated_time
+        id, store_id, cuisine_type, category_ids, name, total_price, is_homepage_display, tags, dish_review,
+        images, image_content, raw_json, detail_content, description, extra_note, need_reserve, reserve_rule,
+        people_limit, usage_rule, status, shelf_status, rejection_reason, delete_flag, created_user_id,
+        updated_user_id, created_time, updated_time
     </sql>
 
 </mapper>

+ 35 - 0
alien-entity/src/main/resources/mapper/StorePlatformUserRoleMapper.xml

@@ -32,5 +32,40 @@
         ORDER BY spur.created_time DESC
     </select>
 
+    <!-- 查询所有子账号信息(包含主账号电话) -->
+    <select id="queryAllSubAccounts" resultType="shop.alien.entity.store.vo.SubAccountListVo">
+        SELECT
+            su_main.id AS mainAccountId,
+            su_main.phone AS mainAccountPhone,
+            sur.id AS userId,
+            sur.phone AS phone,
+            spur.id AS subAccountId,
+            spur.account_name AS accountName,
+            spur.store_id AS storeId,
+            spr.role_id AS roleId,
+            spr.role_name AS roleName,
+            spur.created_time AS createdTime,
+            spur.status AS status
+        FROM store_platform_user_role spur
+        INNER JOIN store_user sur ON spur.user_id = sur.id
+        LEFT JOIN store_user su_main ON spur.store_id = su_main.store_id 
+            AND su_main.account_type = 1 
+            AND su_main.delete_flag = 0
+        LEFT JOIN store_platform_role spr ON spur.role_id = spr.role_id
+        WHERE spur.delete_flag = 0
+          AND sur.delete_flag = 0
+        <if test="accountId != null and accountId != ''">
+            AND (sur.id LIKE CONCAT('%', #{accountId}, '%') 
+                 OR su_main.id LIKE CONCAT('%', #{accountId}, '%'))
+        </if>
+        <if test="phone != null and phone != ''">
+            AND sur.phone LIKE CONCAT('%', #{phone}, '%')
+        </if>
+        <if test="status != null">
+            AND spur.status = #{status}
+        </if>
+        ORDER BY spur.created_time DESC
+    </select>
+
 </mapper>
 

+ 11 - 0
alien-entity/src/main/resources/mapper/StoreUserMapper.xml

@@ -16,4 +16,15 @@
         FROM store_user
         where id = #{id}
     </select>
+
+    <!-- 通过 store_user 与 store_platform_user_role 联表查主账号下的子账号 id(store_user.id),用于删除/禁用时用子账号 id 判断 -->
+    <select id="selectSubAccountUserIdsByMainAccountIdWithRole" resultType="java.lang.Integer">
+        SELECT DISTINCT u.id
+        FROM store_user u
+        INNER JOIN store_platform_user_role r ON u.id = r.user_id AND r.delete_flag = 0
+        WHERE u.sub_account_id = #{mainAccountId}
+          AND u.account_type = 2
+          AND u.delete_flag = 0
+    </select>
+
 </mapper>

+ 9 - 5
alien-entity/src/main/resources/mapper/SubAccountStoreMapper.xml

@@ -17,7 +17,8 @@
             spr.role_name COLLATE utf8mb4_unicode_ci AS roleName,
             spur.user_id AS userId,
             spur.account_name COLLATE utf8mb4_unicode_ci AS accountName,
-            su.phone COLLATE utf8mb4_unicode_ci AS phone
+            su.phone COLLATE utf8mb4_unicode_ci AS phone,
+            spur.status AS status
         FROM
             store_platform_user_role spur
         INNER JOIN store_user su ON spur.user_id = su.id
@@ -44,7 +45,8 @@
             NULL AS roleName,
             su_main.id AS userId,
             su_main.name COLLATE utf8mb4_unicode_ci AS accountName,
-            su_main.phone COLLATE utf8mb4_unicode_ci AS phone
+            su_main.phone COLLATE utf8mb4_unicode_ci AS phone,
+            NULL AS status
         FROM
             store_user su_main
         LEFT JOIN store_info si_main ON su_main.store_id = si_main.id 
@@ -85,7 +87,8 @@
             spr.role_name COLLATE utf8mb4_unicode_ci AS roleName,
             spur.user_id AS userId,
             spur.account_name COLLATE utf8mb4_unicode_ci AS accountName,
-            su.phone COLLATE utf8mb4_unicode_ci AS phone
+            su.phone COLLATE utf8mb4_unicode_ci AS phone,
+            spur.status AS status
         FROM
             store_platform_user_role spur
                 INNER JOIN store_user su ON spur.user_id = su.id
@@ -111,14 +114,15 @@
             NULL AS roleName,
             su_main.id AS userId,
             su_main.name COLLATE utf8mb4_unicode_ci AS accountName,
-            su_main.phone COLLATE utf8mb4_unicode_ci AS phone
+            su_main.phone COLLATE utf8mb4_unicode_ci AS phone,
+            NULL AS status
         FROM
             store_user su_main
                 INNER JOIN store_info si_main ON su_main.store_id = si_main.id
         WHERE
             (
                 -- 如果传入的是子账号,查询其主账号的门店
-                (su_main.id = (SELECT sub_account_id FROM store_user WHERE id = #{userId} AND account_type = 2 AND delete_flag = 0 LIMIT 1))
+                (su_main.id = (SELECT id FROM store_user WHERE id = #{userId} AND account_type = 2 AND delete_flag = 0 LIMIT 1))
            OR
            -- 如果传入的是主账号,查询自己的门店
             (su_main.id = #{userId} AND su_main.account_type = 1)

+ 161 - 0
alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityAchievementMapper.xml

@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.storePlantform.StoreOperationalActivityAchievementMapper">
+
+    <select id="selectCasePage" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo">
+        SELECT
+            ach.id AS achievementId,
+            ach.user_id AS userId,
+            ach.activity_id AS activityId,
+            act.activity_name AS activityName,
+            act.start_time AS startTime,
+            act.end_time AS endTime,
+            CASE
+                WHEN act.status = 7 THEN 7
+                WHEN act.status IN (4, 5) AND act.end_time IS NOT NULL AND NOW() > act.end_time THEN 7
+                WHEN act.status IN (4, 5) AND (act.start_time IS NULL OR (NOW() >= act.start_time AND act.end_time > NOW())) THEN 5
+                ELSE act.status
+            END AS activityStatus,
+            u.user_name AS nickName,
+            su.head_img AS storeUserHeadImg,
+            su.nick_name AS storeUserNickName,
+            SUBSTRING_INDEX(ach.media_urls, ',', 1) AS firstMediaUrl,
+            COALESCE(ach.updated_time, ach.created_time) AS updatedTime
+        FROM store_operational_activity_achievement ach
+        INNER JOIN (
+            SELECT t.activity_id, t.user_id, MAX(t.id) AS max_id
+            FROM store_operational_activity_achievement t
+            INNER JOIN (
+                SELECT activity_id, user_id, MAX(COALESCE(updated_time, created_time)) AS max_time
+                FROM store_operational_activity_achievement
+                WHERE delete_flag = 0
+                GROUP BY activity_id, user_id
+            ) latest_time
+                ON t.activity_id = latest_time.activity_id
+                AND t.user_id = latest_time.user_id
+                AND COALESCE(t.updated_time, t.created_time) = latest_time.max_time
+            WHERE t.delete_flag = 0
+            GROUP BY t.activity_id, t.user_id
+        ) latest ON latest.max_id = ach.id
+        INNER JOIN store_operational_activity act ON act.id = ach.activity_id
+        LEFT JOIN life_user u ON u.id = ach.user_id
+        LEFT JOIN store_user su ON su.store_id = act.store_id
+            AND su.account_type = 1
+            AND su.delete_flag = 0
+        WHERE ach.delete_flag = 0
+          AND act.delete_flag = 0
+        <if test="activityStatus != null">
+            <if test="activityStatus == 5">
+                AND act.status IN (4, 5)
+            </if>
+            <if test="activityStatus != 5">
+                AND act.status = #{activityStatus}
+            </if>
+        </if>
+        ORDER BY COALESCE(ach.updated_time, ach.created_time) DESC, ach.id DESC
+    </select>
+
+    <select id="selectCaseHeader" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseDetailVo">
+        SELECT
+            act.id AS activityId,
+            act.activity_name AS activityName,
+            act.start_time AS startTime,
+            act.end_time AS endTime,
+            u.id AS userId,
+            u.user_name AS nickName,
+            signup.user_name AS userName,
+            signup.phone AS phone,
+            signup.created_time AS createdTime,
+            u.user_image AS userImage,
+            u.user_phone AS userPhone
+        FROM store_operational_activity act
+        LEFT JOIN life_user u ON u.id = #{userId}
+        LEFT JOIN store_operational_activity_signup signup ON signup.activity_id = #{activityId}
+            AND signup.user_id = #{userId}
+            AND signup.delete_flag = 0
+        WHERE act.id = #{activityId}
+          AND act.delete_flag = 0
+          AND u.delete_flag = 0
+        LIMIT 1
+    </select>
+
+    <select id="selectCaseItems" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseItemVo">
+        SELECT
+            ach.id AS achievementId,
+            ach.achievement_desc AS achievementDesc,
+            ach.media_urls AS mediaUrls,
+            ach.created_time AS createdTime,
+            COALESCE(ach.updated_time, ach.created_time) AS updatedTime
+        FROM store_operational_activity_achievement ach
+        WHERE ach.activity_id = #{activityId}
+          AND ach.user_id = #{userId}
+          AND ach.delete_flag = 0
+        ORDER BY COALESCE(ach.updated_time, ach.created_time) DESC, ach.id DESC
+    </select>
+
+    <select id="selectStoreCasePage" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo">
+        SELECT
+            ach.id AS achievementId,
+            ach.user_id AS userId,
+            ach.activity_id AS activityId,
+            act.activity_name AS activityName,
+            CASE
+                WHEN act.status = 7 THEN 7
+                WHEN act.status IN (4, 5) AND act.end_time IS NOT NULL AND NOW() > act.end_time THEN 7
+                WHEN act.status IN (4, 5) AND (act.start_time IS NULL OR NOW() >= act.start_time) THEN 5
+                ELSE act.status
+            END AS activityStatus,
+            u.user_name AS nickName,
+            signup.phone AS phone,
+            u.user_name AS userName,
+            u.user_image AS userImage,
+            u.user_phone AS userPhone,
+            CASE
+                WHEN (act.result_media_url IS NOT NULL AND act.result_media_url != '')
+                    OR (act.result_text IS NOT NULL AND act.result_text != '') THEN 1
+                ELSE 0
+            END AS hasResult,
+            SUBSTRING_INDEX(ach.media_urls, ',', 1) AS firstMediaUrl,
+            COALESCE(ach.updated_time, ach.created_time) AS updatedTime
+        FROM store_operational_activity_achievement ach
+        INNER JOIN (
+            SELECT t.activity_id, t.user_id, MAX(t.id) AS max_id
+            FROM store_operational_activity_achievement t
+            INNER JOIN (
+                SELECT activity_id, user_id, MAX(COALESCE(updated_time, created_time)) AS max_time
+                FROM store_operational_activity_achievement
+                WHERE delete_flag = 0
+                GROUP BY activity_id, user_id
+            ) latest_time
+                ON t.activity_id = latest_time.activity_id
+                AND t.user_id = latest_time.user_id
+                AND COALESCE(t.updated_time, t.created_time) = latest_time.max_time
+            WHERE t.delete_flag = 0
+            GROUP BY t.activity_id, t.user_id
+        ) latest ON latest.max_id = ach.id
+        INNER JOIN store_operational_activity act ON act.id = ach.activity_id
+        LEFT JOIN life_user u ON u.id = ach.user_id
+        LEFT JOIN store_operational_activity_signup signup ON signup.activity_id = ach.activity_id
+            AND signup.user_id = ach.user_id
+            AND signup.delete_flag = 0
+        WHERE ach.delete_flag = 0
+          AND act.delete_flag = 0
+          AND act.store_id = #{storeId}
+        <if test="hasResult != null">
+            <choose>
+                <when test="hasResult == 0">
+                    AND ((act.result_media_url IS NULL OR act.result_media_url = '')
+                    AND (act.result_text IS NULL OR act.result_text = ''))
+                </when>
+                <when test="hasResult == 1">
+                    AND ((act.result_media_url IS NOT NULL AND act.result_media_url != '')
+                    OR (act.result_text IS NOT NULL AND act.result_text != ''))
+                </when>
+            </choose>
+        </if>
+        <if test="activityName != null and activityName != ''">
+            AND act.activity_name LIKE CONCAT('%', #{activityName}, '%')
+        </if>
+        ORDER BY COALESCE(ach.updated_time, ach.created_time) DESC, ach.id DESC
+    </select>
+</mapper>

+ 124 - 0
alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivitySignupMapper.xml

@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.storePlatform.StoreOperationalActivitySignup">
+        <id column="id" property="id" />
+        <result column="activity_id" property="activityId" />
+        <result column="store_id" property="storeId" />
+        <result column="user_id" property="userId" />
+        <result column="user_name" property="userName" />
+        <result column="phone" property="phone" />
+        <result column="status" property="status" />
+        <result column="signup_time" property="signupTime" />
+        <result column="delete_flag" property="deleteFlag" />
+        <result column="created_time" property="createdTime" />
+        <result column="created_user_id" property="createdUserId" />
+        <result column="updated_time" property="updatedTime" />
+        <result column="updated_user_id" property="updatedUserId" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, activity_id, store_id, user_id, user_name, phone, status, signup_time,
+        delete_flag, created_time, created_user_id, updated_time, updated_user_id
+    </sql>
+
+    <select id="selectByActivityId" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List" />
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND delete_flag = 0
+        ORDER BY signup_time DESC
+    </select>
+
+    <select id="countSignupByActivityId" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND delete_flag = 0
+          AND status IN (0, 2)
+    </select>
+
+    <select id="countApprovedByActivityId" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND delete_flag = 0
+          AND status = 2
+    </select>
+
+    <select id="countApprovedByActivityAndUser" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND user_id = #{userId}
+          AND delete_flag = 0
+          AND status in (0,2)
+        LIMIT 1
+    </select>
+
+    <select id="countSignedUpByActivityAndUser" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND user_id = #{userId}
+          AND delete_flag = 0
+          AND status IN (0, 2)
+        LIMIT 1
+    </select>
+
+    <select id="selectLatestSignupStatus" resultType="java.lang.Integer">
+        SELECT status
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND user_id = #{userId}
+          AND delete_flag = 0
+        ORDER BY signup_time DESC, id DESC
+        LIMIT 1
+    </select>
+
+    <select id="selectMySignups" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityMySignupVo">
+        SELECT
+            s.id AS signupId,
+            s.activity_id AS activityId,
+            a.store_id AS storeId,
+            a.activity_name AS activityName,
+            a.promotional_image AS promotionalImage,
+            img.img_url AS activityTitleImgUrl,
+            su.head_img AS storeUserHeadImg,
+            su.nick_name AS storeUserNickName,
+            a.start_time AS startTime,
+            a.end_time AS endTime,
+            a.status AS activityStatus,
+            CASE
+                WHEN a.signup_start_time IS NOT NULL AND NOW() &lt; a.signup_start_time THEN 0
+                WHEN a.signup_end_time IS NOT NULL AND NOW() &gt; a.signup_end_time THEN 2
+                ELSE 1
+            END AS signupStatus,
+            s.status AS signupAuditStatus,
+            CASE WHEN a.status = 7 THEN 1 ELSE 0 END AS activityStartStatus,
+            CASE WHEN ach.signup_id IS NULL THEN 0 ELSE 1 END AS hasAchievement,
+            s.signup_time AS signupTime
+        FROM store_operational_activity_signup s
+        INNER JOIN store_operational_activity a ON a.id = s.activity_id
+        LEFT JOIN store_img img ON img.store_id = a.store_id
+            AND img.business_id = a.id
+            AND img.img_type = 26
+            AND img.delete_flag = 0
+        LEFT JOIN store_user su ON su.store_id = a.store_id
+            AND su.account_type = 1
+            AND su.delete_flag = 0
+        LEFT JOIN (
+            SELECT DISTINCT signup_id
+            FROM store_operational_activity_achievement
+            WHERE delete_flag = 0
+        ) ach ON ach.signup_id = s.id
+        WHERE s.user_id = #{userId}
+          AND s.delete_flag = 0
+          AND a.delete_flag = 0
+        ORDER BY s.signup_time DESC
+    </select>
+</mapper>

+ 6 - 1
alien-gateway/src/main/java/shop/alien/gateway/service/impl/StoreUserServiceImpl.java

@@ -146,6 +146,7 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
                 // 查询子账号关联的所有门店和角色
                 LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
                 roleWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId())
+                        .eq(StorePlatformUserRole::getStatus, 0)
                         .eq(StorePlatformUserRole::getDeleteFlag, 0);
                 List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
 
@@ -173,6 +174,9 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
                     }
 
                     if (maxPermissionRole != null) {
+                        if(maxPermissionRole.getStatus() == 1){
+                            return R.fail("子账号已被禁用无法登录");
+                        }
                         // 设置门店ID和角色ID
                         storeUserVo.setStoreId(maxPermissionRole.getStoreId());
                         storeUserVo.setRoleId(maxPermissionRole.getRoleId());
@@ -193,7 +197,8 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
                         log.warn("子账号关联的门店都没有角色ID - userId: {}", storeUser.getId());
                     }
                 } else {
-                    log.warn("子账号未关联任何门店 - userId: {}", storeUser.getId());
+                    log.warn("子账号未关联任何门店或子账号已被禁用 - userId: {}", storeUser.getId());
+                    return R.fail("子账号已被禁用无法登录");
                 }
             }
         }

+ 16 - 78
alien-gateway/src/main/resources/logback-spring.xml

@@ -1,40 +1,28 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
-<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
-<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
-<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
-<!-- 该信息是由于设置了当配置文件变化时重新加载,所以每当达到扫描时间的时候就会检查配置文件是否错误。但是由于一般配置文件都放在了JAR包中,
-    而扫描的时候无法扫描JAR包内,因此会提示没有可以检查的文件,所以每隔一段时间就输出一次-->
-<configuration scan="false" scanPeriod="60 seconds" debug="true">
-    <contextName>logback-spring</contextName>
+<configuration scan="false" scanPeriod="60 seconds" debug="false">
 
-    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
     <!-- 定义全局参数常量 -->
     <property name="log.level" value="debug"/>
-    <property name="log.maxHistory" value="30"/><!-- 30表示30 -->
-    <springProperty scope="context" name="logging.path" source="logging.path"   defaultValue="C:/project/ext/log"/>
+    <property name="log.maxHistory" value="30"/><!-- 30表示30天 -->
+    <springProperty scope="context" name="logging.path" source="logging.path" defaultValue="C:/project/ext/log"/>
     <!--输出文件前缀-->
-    <property name="FILENAME" value="xiaokuihua_gateway"/>
-
-    <!--0. 日志格式和颜色渲染 -->
-    <!-- 彩色日志依赖的渲染类 -->
-    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
-    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
-    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+    <property name="FILENAME" value="alien"/>
 
     <!-- 文件输出格式 -->
     <property name="FILE_LOG_PATTERN" value="[%d{MM/dd HH:mm:ss.SSS}][%-10.10thread][%-5level][%-40.40c{1}:%5line]:[%15method] || %m%n"/>
-    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+    
+    <!-- 控制台输出格式:纯文本,无颜色,适合 Docker/EFK -->
+    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n"/>
 
     <!--1. 输出到控制台-->
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <!-- 【关键】控制台只输出 INFO 及以上,防止 SQL 刷屏 ES -->
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-            <level>${log.level}</level>
+            <level>INFO</level>
         </filter>
         <encoder>
             <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
-            <!-- 设置字符集 -->
             <charset>UTF-8</charset>
         </encoder>
     </appender>
@@ -42,27 +30,17 @@
     <!--2. 输出到文档-->
     <!-- DEBUG 日志 -->
     <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <!-- 当前的日志文件存放路径 -->
         <file>${logging.path}/DEBUG.log</file>
-        <!-- 日志滚动策略 -->
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 历史日志文件的存放路径和名称 -->
             <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_DEBUG.log.gz</fileNamePattern>
-            <!-- 日志文件最大的保存历史 数量-->
             <maxHistory>${log.maxHistory}</maxHistory>
         </rollingPolicy>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
             <pattern>${FILE_LOG_PATTERN}</pattern>
         </encoder>
-        <!--日志文件最大的大小-->
-        <!--        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
-        <!--            <MaxFileSize>10MB</MaxFileSize>-->
-        <!--        </triggeringPolicy>-->
-        <!-- 此日志文档只记录debug级别的 -->
         <filter class="ch.qos.logback.classic.filter.LevelFilter">
             <level>DEBUG</level>
-            <onMatch>ACCEPT</onMatch>  <!-- 用过滤器,只接受DEBUG级别的日志信息,其余全部过滤掉 -->
+            <onMatch>ACCEPT</onMatch>
             <onMismatch>DENY</onMismatch>
         </filter>
     </appender>
@@ -117,44 +95,14 @@
         </filter>
     </appender>
 
-    <!--
-      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
-      以及指定<appender>。<logger>仅有一个name属性,
-      一个可选的level和一个可选的addtivity属性。
-      name:用来指定受此logger约束的某一个包或者具体的某一个类。
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-         还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
-         如果未设置此属性,那么当前logger将会继承上级的级别。
-      addtivity:是否向上级logger传递打印信息。默认是true。
-      <logger name="org.springframework.web" level="info"/>
-      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-    -->
-
-    <!--
-      使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
-      第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
-      第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-      【logging.level.org.mybatis=debug logging.level.dao=debug】
-     -->
-    <!-- mybatis显示sql,修改此处扫描包名 -->
-
-
-    <!--
-      root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-      不能设置为INHERITED或者同义词NULL。默认是DEBUG
-      可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-    -->
-
-    <!-- 关闭 Swagger/SpringDoc CachingOperationNameGenerator 的 INFO 日志 -->
-    <logger name="d.s.w.r.o.CachingOperationNameGenerator" level="WARN"/>
+    <!-- 降噪配置 -->
+    <logger name="springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator" level="WARN"/>
+    <logger name="org.springframework.security.web.DefaultSecurityFilterChain " level="WARN"/>
+    <logger name="com.netflix.config.sources.URLConfigurationSource " level="WARN"/>
+    <logger name="com.netflix.discovery" level="WARN"/>
+    <logger name="org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]" level="WARN"/>
 
     <!-- 4. 最终的策略 -->
-    <!-- 4.1 开发环境:打印控制台-->
-    <!--打印sql-->
-    <!--    <logger name="com.veryhappy.music.dao" level="debug"/>-->
-
-    <!--打印log-->
     <root level="info">
         <appender-ref ref="CONSOLE"/>
         <appender-ref ref="DEBUG_FILE"/>
@@ -163,14 +111,4 @@
         <appender-ref ref="ERROR_FILE"/>
     </root>
 
-    <!--   4.2 生产环境:输出到文档-->
-    <springProfile name="pro">
-        <root level="info">
-            <appender-ref ref="CONSOLE"/>
-            <appender-ref ref="DEBUG_FILE"/>
-            <appender-ref ref="INFO_FILE"/>
-            <appender-ref ref="ERROR_FILE"/>
-            <appender-ref ref="WARN_FILE"/>
-        </root>
-    </springProfile>
 </configuration>

+ 24 - 11
alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java

@@ -189,20 +189,21 @@ public class BadReviewAppealJob {
                         continue;
                     }
 
-                    // 计算差值:差值>=10则同意,差值<10则驳回
-                    double confidenceDiff = Math.abs(userConfidence - merchantConfidence);
-                    log.info("差评申述置信度分析结果,申诉ID: {},user_confidence: {},merchant_confidence: {},差值: {}", 
-                        appeal.getId(), userConfidence, merchantConfidence, confidenceDiff);
+                    // 谁分数多算谁赢,平分的话算用户赢
+                    log.info("差评申述置信度分析结果,申诉ID: {},user_confidence: {},merchant_confidence: {}", 
+                        appeal.getId(), userConfidence, merchantConfidence);
 
                     StoreCommentAppeal sCommentAppeal = new StoreCommentAppeal();
                     sCommentAppeal.setRecordId(appeal.getRecordId());
                     sCommentAppeal.setAppealAiApproval(dataJsonObj.toJSONString());
 
-                    // 差值>=10则同意,差值<10则驳回
-                    if (confidenceDiff >= 10) {
+                    // 谁分数多算谁赢,平分的话算用户赢
+                    if (merchantConfidence > userConfidence) {
+                        //  商家分数 > 用户分数,商家赢(同意)
                         sCommentAppeal.setAppealStatus(2);
                         sCommentAppeal.setFinalResult("已同意");
-                        log.info("差评申述置信度差值>=10,申诉通过,申诉ID: {},差值: {}", appeal.getId(), confidenceDiff);
+                        log.info("差评申述置信度判断:商家赢(user_confidence: {} >= merchant_confidence: {}),申诉通过,申诉ID: {}",
+                            userConfidence, merchantConfidence, appeal.getId());
                         
                         // 删除评价和评论(与 StoreCommentAppealServiceImpl 逻辑一致)
                         Integer ratingId = appeal.getCommentId(); // commentId 存储的是评价ID(common_rating.id)
@@ -210,9 +211,11 @@ public class BadReviewAppealJob {
                             deleteRatingAndComments(ratingId);
                         }
                     } else {
+                        //  用户分数 >= 商家分数,用户赢(驳回)
                         sCommentAppeal.setAppealStatus(1);
                         sCommentAppeal.setFinalResult("已驳回");
-                        log.info("差评申述置信度差值<10,申诉驳回,申诉ID: {},差值: {}", appeal.getId(), confidenceDiff);
+                        log.info("差评申述置信度判断:用户赢(user_confidence: {} < merchant_confidence: {}),申诉驳回,申诉ID: {}",
+                            userConfidence, merchantConfidence, appeal.getId());
                     }
 
                     storeCommentAppealMapper.updateByRecordId(appeal.getRecordId(),
@@ -222,14 +225,24 @@ public class BadReviewAppealJob {
                     // 发送通知
                     LifeNotice lifeMessage = new LifeNotice();
                     StoreUser storeUser = storeUserMapper.selectOne(new QueryWrapper<StoreUser>().eq("store_id", appeal.getStoreId()));
+                    if (storeUser == null) {
+                        log.warn("发送申诉通知失败,未找到店铺用户,申诉ID: {},店铺ID: {}", appeal.getId(), appeal.getStoreId());
+                        continue;
+                    }
+                    String title = "申诉通知";
+                    lifeMessage.setTitle(title);
                     lifeMessage.setReceiverId("store_" + storeUser.getPhone());
                     String text = "您的评论申诉结果为" + sCommentAppeal.getFinalResult();
                     lifeMessage.setContext(text);
                     lifeMessage.setSenderId("system");
-                    lifeMessage.setTitle("申诉通知");
                     lifeMessage.setIsRead(0);
                     lifeMessage.setNoticeType(1);
-                    lifeNoticeMapper.insert(lifeMessage);
+                    int insertResult = lifeNoticeMapper.insert(lifeMessage);
+                    if (insertResult > 0) {
+                        log.info("申诉通知发送成功,申诉ID: {},通知标题: {}", appeal.getId(), title);
+                    } else {
+                        log.error("申诉通知发送失败,申诉ID: {},通知标题: {}", appeal.getId(), title);
+                    }
 
                 } else {
                     if (analyzeResp != null) {
@@ -574,7 +587,7 @@ public class BadReviewAppealJob {
                 // 没有评价,设置默认评分为0
                 StoreInfo storeInfo = new StoreInfo();
                 storeInfo.setId(businessId);
-                storeInfo.setScoreAvg(0.0);
+                storeInfo.setScoreAvg(5.0);
                 storeInfo.setScoreOne(0.0);
                 storeInfo.setScoreTwo(0.0);
                 storeInfo.setScoreThree(0.0);

+ 145 - 0
alien-job/src/main/java/shop/alien/job/store/StoreOperationalActivityJob.java

@@ -0,0 +1,145 @@
+package shop.alien.job.store;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.storePlatform.StoreOperationalActivity;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 营销活动状态定时任务
+ * 根据活动的开始时间和结束时间自动更新活动状态
+ *
+ * @author system
+ * @date 2025/01/XX
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class StoreOperationalActivityJob {
+
+    private final StoreOperationalActivityMapper activityMapper;
+
+    /**
+     * 营销活动状态更新任务
+     * 定时更新活动状态:未开始 -> 进行中 -> 已结束
+     */
+    @XxlJob("operationalActivityStatusUpdateTask")
+    public void operationalActivityStatusUpdateTask() {
+        log.info("【定时任务】开始执行营销活动状态更新任务...");
+        
+        // 获取当前时间(零点)
+        Date now = new Date();
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(now);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        now = calendar.getTime();
+
+        try {
+            // 查询需要更新状态的活动:2-未开始, 5-进行中, 8-审核成功
+            List<Integer> statusList = new ArrayList<>();
+            statusList.add(2); // 未开始
+            statusList.add(5); // 进行中
+            statusList.add(8); // 审核成功
+
+            LambdaQueryWrapper<StoreOperationalActivity> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreOperationalActivity::getStatus, statusList)
+                    .isNotNull(StoreOperationalActivity::getStartTime)
+                    .isNotNull(StoreOperationalActivity::getEndTime);
+            
+            List<StoreOperationalActivity> activities = activityMapper.selectList(queryWrapper);
+            log.info("【定时任务】查询到需要更新状态的活动数量: {}", activities.size());
+
+            if (activities.isEmpty()) {
+                log.info("【定时任务】没有需要更新状态的活动");
+                return;
+            }
+
+            // 需要设置为"已结束"的活动ID列表
+            List<Integer> endActivityIds = new ArrayList<>();
+            // 需要设置为"进行中"的活动ID列表
+            List<Integer> ongoingActivityIds = new ArrayList<>();
+            // 需要设置为"未开始"的活动ID列表
+            List<Integer> notStartActivityIds = new ArrayList<>();
+
+            // 遍历活动,判断状态
+            for (StoreOperationalActivity activity : activities) {
+                Date startTime = activity.getSignupStartTime() != null ? activity.getSignupStartTime() : activity.getStartTime();
+                Date endTime = activity.getEndTime();
+                Integer currentStatus = activity.getStatus();
+
+                if (startTime == null || endTime == null) {
+                    log.warn("【定时任务】活动ID: {} 的开始时间或结束时间为空,跳过处理", activity.getId());
+                    continue;
+                }
+
+                // 判断当前时间与活动时间的关系
+                if (now.compareTo(endTime) > 0) {
+                    // 当前时间 > 结束时间:应该设置为"已结束"(7)
+                    // 只处理状态为"未开始"(2)或"进行中"(5)的活动
+                    if (currentStatus == 2 || currentStatus == 5) {
+                        endActivityIds.add(activity.getId());
+                        log.debug("【定时任务】活动ID: {} 已过期,需要设置为已结束", activity.getId());
+                    }
+                } else if (now.compareTo(startTime) >= 0 && now.compareTo(endTime) <= 0) {
+                    // 当前时间在活动时间范围内:应该设置为"进行中"(5)
+                    // 只处理状态为"未开始"(2)或"审核成功"(8)的活动
+                    if (currentStatus == 2 || currentStatus == 8) {
+                        ongoingActivityIds.add(activity.getId());
+                        log.debug("【定时任务】活动ID: {} 正在进行中,需要设置为进行中", activity.getId());
+                    }
+                } else if (now.compareTo(startTime) < 0) {
+                    // 当前时间 < 开始时间:应该设置为"未开始"(2)
+                    // 只处理状态为"审核成功"(8)的活动
+                    if (currentStatus == 8) {
+                        notStartActivityIds.add(activity.getId());
+                        log.debug("【定时任务】活动ID: {} 未开始,需要设置为未开始", activity.getId());
+                    }
+                }
+            }
+
+            // 批量更新状态为"已结束"
+            if (!endActivityIds.isEmpty()) {
+                LambdaUpdateWrapper<StoreOperationalActivity> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.in(StoreOperationalActivity::getId, endActivityIds)
+                        .set(StoreOperationalActivity::getStatus, 7); // 7-已结束
+                int updateCount = activityMapper.update(null, updateWrapper);
+                log.info("【定时任务】已结束活动更新完成,活动ID列表: {},更新数量: {}", endActivityIds, updateCount);
+            }
+
+            // 批量更新状态为"进行中"
+            if (!ongoingActivityIds.isEmpty()) {
+                LambdaUpdateWrapper<StoreOperationalActivity> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.in(StoreOperationalActivity::getId, ongoingActivityIds)
+                        .set(StoreOperationalActivity::getStatus, 5); // 5-进行中
+                int updateCount = activityMapper.update(null, updateWrapper);
+                log.info("【定时任务】进行中活动更新完成,活动ID列表: {},更新数量: {}", ongoingActivityIds, updateCount);
+            }
+
+            // 批量更新状态为"未开始"
+            if (!notStartActivityIds.isEmpty()) {
+                LambdaUpdateWrapper<StoreOperationalActivity> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.in(StoreOperationalActivity::getId, notStartActivityIds)
+                        .set(StoreOperationalActivity::getStatus, 2); // 2-未开始
+                int updateCount = activityMapper.update(null, updateWrapper);
+                log.info("【定时任务】未开始活动更新完成,活动ID列表: {},更新数量: {}", notStartActivityIds, updateCount);
+            }
+
+            log.info("【定时任务】营销活动状态更新任务执行完成");
+        } catch (Exception e) {
+            log.error("【定时任务】营销活动状态更新任务执行异常", e);
+        }
+    }
+}
+

+ 14 - 73
alien-job/src/main/resources/logback-spring.xml

@@ -1,40 +1,28 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
-<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
-<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
-<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
-<!-- 该信息是由于设置了当配置文件变化时重新加载,所以每当达到扫描时间的时候就会检查配置文件是否错误。但是由于一般配置文件都放在了JAR包中,
-    而扫描的时候无法扫描JAR包内,因此会提示没有可以检查的文件,所以每隔一段时间就输出一次-->
-<configuration scan="false" scanPeriod="60 seconds" debug="true">
-    <contextName>logback-spring</contextName>
+<configuration scan="false" scanPeriod="60 seconds" debug="false">
 
-    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
     <!-- 定义全局参数常量 -->
     <property name="log.level" value="debug"/>
-    <property name="log.maxHistory" value="30"/><!-- 30表示30 -->
+    <property name="log.maxHistory" value="30"/><!-- 30表示30天 -->
     <springProperty scope="context" name="logging.path" source="logging.path" defaultValue="C:/project/ext/log"/>
     <!--输出文件前缀-->
     <property name="FILENAME" value="alien"/>
 
-    <!--0. 日志格式和颜色渲染 -->
-    <!-- 彩色日志依赖的渲染类 -->
-    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
-    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
-    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
-
     <!-- 文件输出格式 -->
     <property name="FILE_LOG_PATTERN" value="[%d{MM/dd HH:mm:ss.SSS}][%-10.10thread][%-5level][%-40.40c{1}:%5line]:[%15method] || %m%n"/>
-    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+    
+    <!-- 控制台输出格式:纯文本,无颜色,适合 Docker/EFK -->
+    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n"/>
 
     <!--1. 输出到控制台-->
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <!-- 【关键】控制台只输出 INFO 及以上,防止 SQL 刷屏 ES -->
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-            <level>${log.level}</level>
+            <level>INFO</level>
         </filter>
         <encoder>
             <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
-            <!-- 设置字符集 -->
             <charset>UTF-8</charset>
         </encoder>
     </appender>
@@ -42,27 +30,17 @@
     <!--2. 输出到文档-->
     <!-- DEBUG 日志 -->
     <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <!-- 当前的日志文件存放路径 -->
         <file>${logging.path}/DEBUG.log</file>
-        <!-- 日志滚动策略 -->
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 历史日志文件的存放路径和名称 -->
             <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_DEBUG.log.gz</fileNamePattern>
-            <!-- 日志文件最大的保存历史 数量-->
             <maxHistory>${log.maxHistory}</maxHistory>
         </rollingPolicy>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
             <pattern>${FILE_LOG_PATTERN}</pattern>
         </encoder>
-        <!--日志文件最大的大小-->
-    <!--        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
-    <!--            <MaxFileSize>10MB</MaxFileSize>-->
-    <!--        </triggeringPolicy>-->
-        <!-- 此日志文档只记录debug级别的 -->
         <filter class="ch.qos.logback.classic.filter.LevelFilter">
             <level>DEBUG</level>
-            <onMatch>ACCEPT</onMatch>  <!-- 用过滤器,只接受DEBUG级别的日志信息,其余全部过滤掉 -->
+            <onMatch>ACCEPT</onMatch>
             <onMismatch>DENY</onMismatch>
         </filter>
     </appender>
@@ -117,41 +95,14 @@
         </filter>
     </appender>
 
-    <!--
-      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
-      以及指定<appender>。<logger>仅有一个name属性,
-      一个可选的level和一个可选的addtivity属性。
-      name:用来指定受此logger约束的某一个包或者具体的某一个类。
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-         还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
-         如果未设置此属性,那么当前logger将会继承上级的级别。
-      addtivity:是否向上级logger传递打印信息。默认是true。
-      <logger name="org.springframework.web" level="info"/>
-      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-    -->
-
-    <!--
-      使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
-      第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
-      第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-      【logging.level.org.mybatis=debug logging.level.dao=debug】
-     -->
-    <!-- mybatis显示sql,修改此处扫描包名 -->
-
-
-    <!--
-      root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-      不能设置为INHERITED或者同义词NULL。默认是DEBUG
-      可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-    -->
+    <!-- 降噪配置 -->
+    <logger name="springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator" level="WARN"/>
+    <logger name="org.springframework.security.web.DefaultSecurityFilterChain " level="WARN"/>
+    <logger name="com.netflix.config.sources.URLConfigurationSource " level="WARN"/>
+    <logger name="com.netflix.discovery" level="WARN"/>
+    <logger name="org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]" level="WARN"/>
 
     <!-- 4. 最终的策略 -->
-    <!-- 4.1 开发环境:打印控制台-->
-    <!--打印sql-->
-    <!--    <logger name="com.veryhappy.music.dao" level="debug"/>-->
-
-    <!--打印log-->
     <root level="info">
         <appender-ref ref="CONSOLE"/>
         <appender-ref ref="DEBUG_FILE"/>
@@ -160,14 +111,4 @@
         <appender-ref ref="ERROR_FILE"/>
     </root>
 
-    <!--   4.2 生产环境:输出到文档-->
-    <springProfile name="pro">
-        <root level="info">
-            <appender-ref ref="CONSOLE"/>
-            <appender-ref ref="DEBUG_FILE"/>
-            <appender-ref ref="INFO_FILE"/>
-            <appender-ref ref="ERROR_FILE"/>
-            <appender-ref ref="WARN_FILE"/>
-        </root>
-    </springProfile>
 </configuration>

+ 1 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/controller/AiAutoReview.java

@@ -88,7 +88,7 @@ public class AiAutoReview {
         //申诉图片
         commentAppeal.setImgUrl(requestBody.get("appeal_images").toString());
         //评价图片
-//        commentAppeal.setReviewImages(requestBody.get("review_images").toString());
+        commentAppeal.setReviewImages(requestBody.get("review_images").toString());
         //律师id
         commentAppeal.setLawyerUserId(requestBody.get("lawyer_user_id").toString());
         commentAppealService.submitAppeal(commentAppeal);

+ 15 - 74
alien-lawyer/src/main/resources/logback-spring.xml

@@ -1,40 +1,28 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
-<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
-<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
-<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
-<!-- 该信息是由于设置了当配置文件变化时重新加载,所以每当达到扫描时间的时候就会检查配置文件是否错误。但是由于一般配置文件都放在了JAR包中,
-    而扫描的时候无法扫描JAR包内,因此会提示没有可以检查的文件,所以每隔一段时间就输出一次-->
-<configuration scan="false" scanPeriod="60 seconds" debug="true">
-<!--    <contextName>logback-spring</contextName>-->
+<configuration scan="false" scanPeriod="60 seconds" debug="false">
 
-    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
     <!-- 定义全局参数常量 -->
     <property name="log.level" value="debug"/>
-    <property name="log.maxHistory" value="30"/><!-- 30表示30 -->
-    <springProperty scope="context" name="logging.path" source="logging.path"  defaultValue="C:/project/ext/log"/>
+    <property name="log.maxHistory" value="30"/><!-- 30表示30天 -->
+    <springProperty scope="context" name="logging.path" source="logging.path" defaultValue="C:/project/ext/log"/>
     <!--输出文件前缀-->
     <property name="FILENAME" value="alien"/>
 
-    <!--0. 日志格式和颜色渲染 -->
-    <!-- 彩色日志依赖的渲染类 -->
-    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
-    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
-    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
-
     <!-- 文件输出格式 -->
     <property name="FILE_LOG_PATTERN" value="[%d{MM/dd HH:mm:ss.SSS}][%-10.10thread][%-5level][%-40.40c{1}:%5line]:[%15method] || %m%n"/>
-    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+    
+    <!-- 控制台输出格式:纯文本,无颜色,适合 Docker/EFK -->
+    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n"/>
 
     <!--1. 输出到控制台-->
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <!-- 【关键】控制台只输出 INFO 及以上,防止 SQL 刷屏 ES -->
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-            <level>${log.level}</level>
+            <level>INFO</level>
         </filter>
         <encoder>
             <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
-            <!-- 设置字符集 -->
             <charset>UTF-8</charset>
         </encoder>
     </appender>
@@ -42,27 +30,17 @@
     <!--2. 输出到文档-->
     <!-- DEBUG 日志 -->
     <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <!-- 当前的日志文件存放路径 -->
         <file>${logging.path}/DEBUG.log</file>
-        <!-- 日志滚动策略 -->
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 历史日志文件的存放路径和名称 -->
             <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_DEBUG.log.gz</fileNamePattern>
-            <!-- 日志文件最大的保存历史 数量-->
             <maxHistory>${log.maxHistory}</maxHistory>
         </rollingPolicy>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
             <pattern>${FILE_LOG_PATTERN}</pattern>
         </encoder>
-        <!--日志文件最大的大小-->
-        <!--        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
-        <!--            <MaxFileSize>10MB</MaxFileSize>-->
-        <!--        </triggeringPolicy>-->
-        <!-- 此日志文档只记录debug级别的 -->
         <filter class="ch.qos.logback.classic.filter.LevelFilter">
             <level>DEBUG</level>
-            <onMatch>ACCEPT</onMatch>  <!-- 用过滤器,只接受DEBUG级别的日志信息,其余全部过滤掉 -->
+            <onMatch>ACCEPT</onMatch>
             <onMismatch>DENY</onMismatch>
         </filter>
     </appender>
@@ -117,41 +95,14 @@
         </filter>
     </appender>
 
-    <!--
-      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
-      以及指定<appender>。<logger>仅有一个name属性,
-      一个可选的level和一个可选的addtivity属性。
-      name:用来指定受此logger约束的某一个包或者具体的某一个类。
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-         还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
-         如果未设置此属性,那么当前logger将会继承上级的级别。
-      addtivity:是否向上级logger传递打印信息。默认是true。
-      <logger name="org.springframework.web" level="info"/>
-      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-    -->
-
-    <!--
-      使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
-      第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
-      第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-      【logging.level.org.mybatis=debug logging.level.dao=debug】
-     -->
-    <!-- mybatis显示sql,修改此处扫描包名 -->
-
-
-    <!--
-      root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-      不能设置为INHERITED或者同义词NULL。默认是DEBUG
-      可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-    -->
+    <!-- 降噪配置 -->
+    <logger name="springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator" level="WARN"/>
+    <logger name="org.springframework.security.web.DefaultSecurityFilterChain " level="WARN"/>
+    <logger name="com.netflix.config.sources.URLConfigurationSource " level="WARN"/>
+    <logger name="com.netflix.discovery" level="WARN"/>
+    <logger name="org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]" level="WARN"/>
 
     <!-- 4. 最终的策略 -->
-    <!-- 4.1 开发环境:打印控制台-->
-    <!--打印sql-->
-    <!--    <logger name="com.veryhappy.music.dao" level="debug"/>-->
-
-    <!--打印log-->
     <root level="info">
         <appender-ref ref="CONSOLE"/>
         <appender-ref ref="DEBUG_FILE"/>
@@ -160,14 +111,4 @@
         <appender-ref ref="ERROR_FILE"/>
     </root>
 
-    <!--   4.2 生产环境:输出到文档-->
-    <springProfile name="pro">
-        <root level="info">
-            <appender-ref ref="CONSOLE"/>
-            <appender-ref ref="DEBUG_FILE"/>
-            <appender-ref ref="INFO_FILE"/>
-            <appender-ref ref="ERROR_FILE"/>
-            <appender-ref ref="WARN_FILE"/>
-        </root>
-    </springProfile>
 </configuration>

+ 15 - 75
alien-second/src/main/resources/logback-spring.xml

@@ -1,41 +1,28 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
-<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
-<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
-<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
-<!-- 该信息是由于设置了当配置文件变化时重新加载,所以每当达到扫描时间的时候就会检查配置文件是否错误。但是由于一般配置文件都放在了JAR包中,
-    而扫描的时候无法扫描JAR包内,因此会提示没有可以检查的文件,所以每隔一段时间就输出一次-->
-<configuration scan="false" scanPeriod="60 seconds" debug="true">
-    <contextName>logback-spring</contextName>
+<configuration scan="false" scanPeriod="60 seconds" debug="false">
 
-    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
     <!-- 定义全局参数常量 -->
     <property name="log.level" value="debug"/>
-    <property name="log.maxHistory" value="30"/><!-- 30表示30个 -->
-    <springProperty scope="context" name="logging.path" source="logging.path"  defaultValue="C:/project/ext/log"/>
-
+    <property name="log.maxHistory" value="30"/><!-- 30表示30天 -->
+    <springProperty scope="context" name="logging.path" source="logging.path" defaultValue="C:/project/ext/log"/>
     <!--输出文件前缀-->
     <property name="FILENAME" value="alien"/>
 
-    <!--0. 日志格式和颜色渲染 -->
-    <!-- 彩色日志依赖的渲染类 -->
-    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
-    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
-    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
-
     <!-- 文件输出格式 -->
     <property name="FILE_LOG_PATTERN" value="[%d{MM/dd HH:mm:ss.SSS}][%-10.10thread][%-5level][%-40.40c{1}:%5line]:[%15method] || %m%n"/>
-    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+    
+    <!-- 控制台输出格式:纯文本,无颜色,适合 Docker/EFK -->
+    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n"/>
 
     <!--1. 输出到控制台-->
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <!-- 【关键】控制台只输出 INFO 及以上,防止 SQL 刷屏 ES -->
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-            <level>${log.level}</level>
+            <level>INFO</level>
         </filter>
         <encoder>
             <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
-            <!-- 设置字符集 -->
             <charset>UTF-8</charset>
         </encoder>
     </appender>
@@ -43,27 +30,17 @@
     <!--2. 输出到文档-->
     <!-- DEBUG 日志 -->
     <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <!-- 当前的日志文件存放路径 -->
         <file>${logging.path}/DEBUG.log</file>
-        <!-- 日志滚动策略 -->
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 历史日志文件的存放路径和名称 -->
             <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_DEBUG.log.gz</fileNamePattern>
-            <!-- 日志文件最大的保存历史 数量-->
             <maxHistory>${log.maxHistory}</maxHistory>
         </rollingPolicy>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
             <pattern>${FILE_LOG_PATTERN}</pattern>
         </encoder>
-        <!--日志文件最大的大小-->
-        <!--        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
-        <!--            <MaxFileSize>10MB</MaxFileSize>-->
-        <!--        </triggeringPolicy>-->
-        <!-- 此日志文档只记录debug级别的 -->
         <filter class="ch.qos.logback.classic.filter.LevelFilter">
             <level>DEBUG</level>
-            <onMatch>ACCEPT</onMatch>  <!-- 用过滤器,只接受DEBUG级别的日志信息,其余全部过滤掉 -->
+            <onMatch>ACCEPT</onMatch>
             <onMismatch>DENY</onMismatch>
         </filter>
     </appender>
@@ -118,41 +95,14 @@
         </filter>
     </appender>
 
-    <!--
-      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
-      以及指定<appender>。<logger>仅有一个name属性,
-      一个可选的level和一个可选的addtivity属性。
-      name:用来指定受此logger约束的某一个包或者具体的某一个类。
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-         还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
-         如果未设置此属性,那么当前logger将会继承上级的级别。
-      addtivity:是否向上级logger传递打印信息。默认是true。
-      <logger name="org.springframework.web" level="info"/>
-      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-    -->
-
-    <!--
-      使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
-      第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
-      第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-      【logging.level.org.mybatis=debug logging.level.dao=debug】
-     -->
-    <!-- mybatis显示sql,修改此处扫描包名 -->
-
-
-    <!--
-      root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-      不能设置为INHERITED或者同义词NULL。默认是DEBUG
-      可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-    -->
+    <!-- 降噪配置 -->
+    <logger name="springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator" level="WARN"/>
+    <logger name="org.springframework.security.web.DefaultSecurityFilterChain " level="WARN"/>
+    <logger name="com.netflix.config.sources.URLConfigurationSource " level="WARN"/>
+    <logger name="com.netflix.discovery" level="WARN"/>
+    <logger name="org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]" level="WARN"/>
 
     <!-- 4. 最终的策略 -->
-    <!-- 4.1 开发环境:打印控制台-->
-    <!--打印sql-->
-    <!--    <logger name="com.veryhappy.music.dao" level="debug"/>-->
-
-    <!--打印log-->
     <root level="info">
         <appender-ref ref="CONSOLE"/>
         <appender-ref ref="DEBUG_FILE"/>
@@ -161,14 +111,4 @@
         <appender-ref ref="ERROR_FILE"/>
     </root>
 
-    <!--   4.2 生产环境:输出到文档-->
-    <springProfile name="pro">
-        <root level="info">
-            <appender-ref ref="CONSOLE"/>
-            <appender-ref ref="DEBUG_FILE"/>
-            <appender-ref ref="INFO_FILE"/>
-            <appender-ref ref="ERROR_FILE"/>
-            <appender-ref ref="WARN_FILE"/>
-        </root>
-    </springProfile>
 </configuration>

+ 18 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java

@@ -125,7 +125,7 @@ public class OperationalActivityController {
             if (dto.getStoreId() == null || dto.getStoreId() <= 0) {
                  return R.data(null, "暂无承载数据!!!");
             }
-            IPage<StoreOperationalActivityVO> result = activityService.queryActivityList( dto.getStoreId(), dto.getStatus(), dto.getActivityName(), dto.getPageNum(), dto.getPageSize());
+            IPage<StoreOperationalActivityVO> result = activityService.queryActivityList( dto.getStoreId(), dto.getStatus(), dto.getActivityType(), dto.getActivityName(), dto.getPageNum(), dto.getPageSize());
             return R.data(result);
         } catch (Exception e) {
             log.error("OperationalActivityController.qeryActivityList ERROR: {}", e.getMessage(), e);
@@ -156,5 +156,22 @@ public class OperationalActivityController {
             return R.fail(e.getMessage());
         }
     }
+
+    @ApiOperation("上传活动结果")
+    @ApiOperationSupport(order = 8)
+    @PostMapping("/uploadResult")
+    public R<String> uploadActivityResult(@RequestBody StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityController.uploadActivityResult: dto={}", dto);
+        try {
+            int result = activityService.uploadActivityResult(dto);
+            if (result > 0) {
+                return R.success("活动结果上传成功");
+            }
+            return R.fail("活动结果上传失败");
+        } catch (Exception e) {
+            log.error("OperationalActivityController.uploadActivityResult ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
 }
 

+ 111 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivitySignupController.java

@@ -0,0 +1,111 @@
+package shop.alien.storeplatform.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupVO;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupQueryDTO;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupIdDTO;
+import shop.alien.storeplatform.service.OperationalActivitySignupService;
+
+/**
+ * 运营活动报名管理控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"商家端-运营活动报名管理"})
+@ApiSort(11)
+@CrossOrigin
+@RestController
+@RequestMapping("/operationalActivitySignup")
+@RequiredArgsConstructor
+public class OperationalActivitySignupController {
+
+    private final OperationalActivitySignupService signupService;
+
+    @ApiOperation("根据商户ID查询报名列表")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/queryList")
+    public R<IPage<StoreOperationalActivitySignupVO>> querySignupList(@RequestBody StoreOperationalActivitySignupQueryDTO dto) {
+        log.info("OperationalActivitySignupController.querySignupList: dto={}", dto);
+        try {
+            if (dto.getStoreId() == null || dto.getStoreId() <= 0) {
+                return R.fail("商户ID不能为空");
+            }
+            Integer pageNum = dto.getPageNum() != null ? dto.getPageNum() : 1;
+            Integer pageSize = dto.getPageSize() != null ? dto.getPageSize() : 10;
+            IPage<StoreOperationalActivitySignupVO> result = signupService.querySignupList(
+                    dto.getStoreId(), dto.getStatus(), dto.getActivityName(), pageNum, pageSize);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("OperationalActivitySignupController.querySignupList ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据ID查询报名详情")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/queryById")
+    public R<StoreOperationalActivitySignupVO> querySignupById(@RequestBody StoreOperationalActivitySignupIdDTO dto) {
+        log.info("OperationalActivitySignupController.querySignupById: id={}", dto.getId());
+        try {
+            if (dto.getId() == null || dto.getId() <= 0) {
+                return R.fail("报名ID不能为空");
+            }
+            StoreOperationalActivitySignupVO result = signupService.querySignupById(dto.getId());
+            if (result == null) {
+                return R.fail("报名记录不存在");
+            }
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("OperationalActivitySignupController.querySignupById ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("审核通过")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/approve")
+    public R<String> approveSignup(@RequestBody StoreOperationalActivitySignupIdDTO dto) {
+        log.info("OperationalActivitySignupController.approveSignup: id={}", dto.getId());
+        try {
+            if (dto.getId() == null || dto.getId() <= 0) {
+                return R.fail("报名ID不能为空");
+            }
+            int result = signupService.approveSignup(dto.getId());
+            if (result > 0) {
+                return R.success("审核通过成功");
+            }
+            return R.fail("审核通过失败");
+        } catch (Exception e) {
+            log.error("OperationalActivitySignupController.approveSignup ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("审核拒绝")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/reject")
+    public R<String> rejectSignup(@RequestBody StoreOperationalActivitySignupIdDTO dto) {
+        log.info("OperationalActivitySignupController.rejectSignup: id={}, rejectReason={}", dto.getId(), dto.getRejectReason());
+        try {
+            if (dto.getId() == null || dto.getId() <= 0) {
+                return R.fail("报名ID不能为空");
+            }
+            int result = signupService.rejectSignup(dto.getId(), dto.getRejectReason());
+            if (result > 0) {
+                return R.success("审核拒绝成功");
+            }
+            return R.fail("审核拒绝失败");
+        } catch (Exception e) {
+            log.error("OperationalActivitySignupController.rejectSignup ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}
+

+ 56 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformUserRoleController.java

@@ -1,11 +1,13 @@
 package shop.alien.storeplatform.controller;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.vo.SubAccountDetailVo;
+import shop.alien.entity.store.vo.SubAccountListVo;
 import shop.alien.entity.store.vo.SubAccountVo;
 import shop.alien.storeplatform.dto.AssignRolesDto;
 import shop.alien.storeplatform.dto.BatchDeleteSubAccountDto;
@@ -241,5 +243,59 @@ public class StorePlatformUserRoleController {
         return R.fail("更新子账号信息失败");
     }
 
+    @ApiOperation("查询所有子账号信息(分页)")
+    @ApiOperationSupport(order = 11)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", required = false, defaultValue = "1"),
+            @ApiImplicitParam(name = "pageSize", value = "每页大小", dataType = "int", paramType = "query", required = false, defaultValue = "10"),
+            @ApiImplicitParam(name = "mainAccountId", value = "账号ID(主账号ID或子账号用户ID,模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "phone", value = "联系电话(子账号电话,模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "status", value = "状态(0:启用 1:禁用)", dataType = "int", paramType = "query", required = false)
+    })
+    @GetMapping("/queryAllSubAccounts")
+    public R<IPage<SubAccountListVo>> queryAllSubAccounts(
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "mainAccountId", required = false) String mainAccountId,
+            @RequestParam(value = "phone", required = false) String phone,
+            @RequestParam(value = "status", required = false) Integer status) {
+        log.info("StorePlatformUserRoleController.queryAllSubAccounts?pageNum={}, pageSize={}, mainAccountId={}, phone={}, status={}",
+                pageNum, pageSize, mainAccountId, phone, status);
+        IPage<SubAccountListVo> subAccountPage = storePlatformUserRoleService.queryAllSubAccounts(pageNum, pageSize, mainAccountId, phone, status);
+        return R.data(subAccountPage);
+    }
+
+    @ApiOperation("禁用子账号")
+    @ApiOperationSupport(order = 12)
+    @PlatformOperationLog(
+            module = "账号操作记录",
+            type = "禁用子账号",
+            content = "禁用子账号(关联记录ID=#{#id})"
+    )
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "子账号关联记录的主键ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "status", value = "状态标志(0启用 1禁用)", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/disableSubAccount")
+    public R<String> disableSubAccount(@RequestParam("id") Long id, @RequestParam("status") Integer status) {
+        log.info("StorePlatformUserRoleController.disableSubAccount?id={}, status={}", id, status);
+        
+        if (id == null) {
+            return R.fail("子账号关联记录ID不能为空");
+        }
+        
+        if (status == null || (status != 0 && status != 1)) {
+            return R.fail("状态参数必须为0(启用)或1(禁用)");
+        }
+        
+        boolean result = storePlatformUserRoleService.disableSubAccount(id, status);
+        if (result) {
+            String message = status == 0 ? "启用子账号成功" : "禁用子账号成功";
+            return R.success(message);
+        }
+        String message = status == 0 ? "启用子账号失败" : "禁用子账号失败";
+        return R.fail(message);
+    }
+
 }
 

+ 9 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/service/OperationalActivityService.java

@@ -56,7 +56,7 @@ public interface OperationalActivityService {
      * @param pageSize     每页大小
      * @return 分页结果
      */
-    IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize);
+    IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status,String activityType, String activityName,  Integer pageNum, Integer pageSize);
 
     /**
      * 启用/禁用活动
@@ -67,5 +67,13 @@ public interface OperationalActivityService {
      */
     int updateActivityStatus(Integer id, Integer status);
 
+    /**
+     * 上传活动结果
+     *
+     * @param dto 活动结果信息(包含活动ID和结果内容)
+     * @return 更新结果
+     */
+    int uploadActivityResult(StoreOperationalActivityDTO dto);
+
 }
 

+ 51 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/OperationalActivitySignupService.java

@@ -0,0 +1,51 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupVO;
+
+/**
+ * 运营活动报名服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface OperationalActivitySignupService {
+
+    /**
+     * 根据商户ID分页查询报名列表
+     *
+     * @param storeId   商户ID
+     * @param status    报名状态(可选,0-待审核,1-拒绝,2-通过)
+     * @param activityName 活动名称(可选,模糊查询)
+     * @param pageNum   页码
+     * @param pageSize  每页大小
+     * @return 分页结果
+     */
+    IPage<StoreOperationalActivitySignupVO> querySignupList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize);
+
+    /**
+     * 根据ID查询报名详情
+     *
+     * @param id 报名ID
+     * @return 报名详情
+     */
+    StoreOperationalActivitySignupVO querySignupById(Integer id);
+
+    /**
+     * 审核通过
+     *
+     * @param id 报名ID
+     * @return 更新结果
+     */
+    int approveSignup(Integer id);
+
+    /**
+     * 审核拒绝
+     *
+     * @param id 报名ID
+     * @param rejectReason 拒绝原因
+     * @return 更新结果
+     */
+    int rejectSignup(Integer id, String rejectReason);
+}
+

+ 28 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformUserRoleService.java

@@ -1,9 +1,11 @@
 package shop.alien.storeplatform.service;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StorePlatformUserRole;
 import shop.alien.entity.store.vo.SubAccountDetailVo;
+import shop.alien.entity.store.vo.SubAccountListVo;
 import shop.alien.entity.store.vo.SubAccountVo;
 
 import java.util.List;
@@ -120,5 +122,31 @@ public interface StorePlatformUserRoleService extends IService<StorePlatformUser
      * @return 是否成功
      */
     boolean updateSubAccount(Integer userId, String phone, String accountName, Integer storeId, Long roleId);
+
+    /**
+     * 查询所有子账号信息(包含主账号电话)- 分页查询
+     * 查询 store_platform_user_role 表中所有的数据
+     * 主账号电话通过 store_id 关联 store_user 表查询
+     * 联系电话通过 user_id 关联 store_user 表查询
+     *
+     * @param pageNum   页码
+     * @param pageSize  每页大小
+     * @param mainAccountId 账号ID(主账号ID或子账号用户ID,模糊查询)
+     * @param phone     联系电话(子账号电话,模糊查询)
+     * @param status    状态(精确查询)
+     * @return 子账号分页列表
+     */
+    IPage<SubAccountListVo> queryAllSubAccounts(Integer pageNum, Integer pageSize, String mainAccountId, String phone, Integer status);
+
+    /**
+     * 更新子账号状态
+     * 根据传入的 status 值更新 store_platform_user_role 表中指定记录的 status 字段
+     * status 为 0 表示启用,status 为 1 表示禁用
+     *
+     * @param id 子账号关联记录的主键ID
+     * @param status 状态值(0启用 1禁用)
+     * @return 是否成功
+     */
+    boolean disableSubAccount(Long id, Integer status);
 }
 

+ 174 - 7
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -115,6 +115,16 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     String authorization = "Bearer " + accessToken;
                     
                     JsonNode auditParam = dto.getAuditParam();
+                    // 如果 auditParam 是字符串,先解析为 JsonNode
+                    if (auditParam != null && auditParam.isTextual()) {
+                        try {
+                            auditParam = objectMapper.readTree(auditParam.asText());
+                        } catch (Exception e) {
+                            log.error("解析 auditParam JSON 字符串失败: {}", auditParam.asText(), e);
+                            auditParam = null;
+                        }
+                    }
+                    
                     String auditText = (auditParam != null && auditParam.has("text")) ? auditParam.get("text").asText() : "";
                     JsonNode imagesNode = (auditParam != null) ? auditParam.get("image_urls") : null;
                     
@@ -127,15 +137,37 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     // 调用同步审核工具类
                     AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(auditText, imageUrls);
                     
+                    // 审核结束后,设置审核时间
+                    Date auditTime = new Date();
+                    activity.setAuditTime(auditTime);
+                    
+                    // 审核通过,根据活动时间自动设置状态
+                    Date currentTime = new Date();
+                    Date startTime = activity.getStartTime();
+                    Date endTime = activity.getEndTime();
+                    
+                    int status;
+                    if (currentTime.before(startTime)) {
+                        // 当前时间在活动开始时间之前,设置为未开始
+                        status = 2;
+                    } else if (currentTime.compareTo(startTime) >= 0 && currentTime.compareTo(endTime) <= 0) {
+                        // 当前时间在活动时间之间,设置为进行中
+                        status = 5;
+                    } else {
+                        // 当前时间在活动结束时间之后,设置为已结束
+                        status = 7;
+                    }
+                    activity.setStatus(status);
+
                     // 如果审核不通过,记录原因并提前结束
                     if (!auditResult.isPassed()) {
                         log.warn("AI内容审核未通过: {}", auditResult.getFailureReason());
                         failureReasonHolder.set(auditResult.getFailureReason());
-                        return 2; // 返回2表示审核失败
+                        activity.setApprovalComments(auditResult.getFailureReason());
+                        activity.setStatus(3);
+//                        return 2; // 返回2表示审核失败
                     }
-                    
-                    // 审核通过,执行入库操作(状态设为8:待生成海报)
-                    activity.setStatus(8);
+
                     result = activityMapper.insert(activity);
                     // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
                     if (dto.getUploadImgType() == 2) {
@@ -231,7 +263,114 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
         StoreOperationalActivity activity = new StoreOperationalActivity();
         BeanUtils.copyProperties(dto, activity);
-        Integer result = activityMapper.updateById(activity);
+        Integer result =0;
+        try {
+            String accessToken = getToken();
+            if (accessToken == null || accessToken.isEmpty()) {
+                log.error("获取AI服务access_token失败,无法生成促销图片");
+            } else {
+                // AI登录成功
+                // 先调用AI进行运营名称和图片的审核,同步调用
+                String authorization = "Bearer " + accessToken;
+
+                JsonNode auditParam = dto.getAuditParam();
+                // 如果 auditParam 是字符串,先解析为 JsonNode
+                if (auditParam != null && auditParam.isTextual()) {
+                    try {
+                        auditParam = objectMapper.readTree(auditParam.asText());
+                    } catch (Exception e) {
+                        log.error("解析 auditParam JSON 字符串失败: {}", auditParam.asText(), e);
+                        auditParam = null;
+                    }
+                }
+
+                String auditText = (auditParam != null && auditParam.has("text")) ? auditParam.get("text").asText() : "";
+                JsonNode imagesNode = (auditParam != null) ? auditParam.get("image_urls") : null;
+
+                List<String> imageUrls = (imagesNode != null && imagesNode.isArray())
+                        ? StreamSupport.stream(imagesNode.spliterator(), false)
+                        .map(JsonNode::asText)
+                        .collect(Collectors.toList())
+                        : new ArrayList<>();
+
+                // 调用同步审核工具类
+                AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(auditText, imageUrls);
+
+                // 审核结束后,设置审核时间
+                Date auditTime = new Date();
+                activity.setAuditTime(auditTime);
+
+                // 审核通过,根据活动时间自动设置状态
+                Date currentTime = new Date();
+                Date startTime = activity.getStartTime();
+                Date endTime = activity.getEndTime();
+
+                int status;
+                if (currentTime.before(startTime)) {
+                    // 当前时间在活动开始时间之前,设置为未开始
+                    status = 2;
+                } else if (currentTime.compareTo(startTime) >= 0 && currentTime.compareTo(endTime) <= 0) {
+                    // 当前时间在活动时间之间,设置为进行中
+                    status = 5;
+                } else {
+                    // 当前时间在活动结束时间之后,设置为已结束
+                    status = 7;
+                }
+                activity.setStatus(status);
+
+                // 如果审核不通过,记录原因并提前结束
+                if (!auditResult.isPassed()) {
+                    log.warn("AI内容审核未通过: {}", auditResult.getFailureReason());
+                    failureReasonHolder.set(auditResult.getFailureReason());
+                    activity.setApprovalComments(auditResult.getFailureReason());
+                    activity.setStatus(3);
+//                        return 2; // 返回2表示审核失败
+                }
+
+                result = activityMapper.updateById(activity);
+                // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
+                if (dto.getUploadImgType() == 2) {
+                    // 格式化输入AI参数
+                    ObjectNode requestBody = objectMapper.createObjectNode();
+                    String filled = String.format(
+                            tpl,
+                            dto.getActivityName(),
+                            dto.getStartTime(),
+                            dto.getEndTime(),
+                            dto.getParticipationLimit(),
+                            dto.getActivityRule(),
+                            dto.getCouponQuantity(),
+                            dto.getImgDescribe()
+                    );
+                    requestBody.put("text", filled);
+                    JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, requestBody);
+                    // 解析响应
+                    if (imgResponse.has("data")) {
+                        JsonNode data = imgResponse.get("data");
+                        // 提取横向图(banner_image)的图片URL
+                        if (data.has("banner_image")) {
+                            JsonNode bannerImage = data.get("banner_image");
+                            if (bannerImage.has("image_url") && !bannerImage.get("image_url").isNull()) {
+                                String bannerImageUrl = bannerImage.get("image_url").asText();
+                                dto.getActivityTitleImg().setImgUrl(bannerImageUrl);
+                            }
+                        }
+                        // 提取竖向图(vertical_image)的图片URL
+                        if (data.has("vertical_image")) {
+                            JsonNode verticalImage = data.get("vertical_image");
+                            if (verticalImage.has("image_url") && !verticalImage.get("image_url").isNull()) {
+                                String verticalImageUrl = verticalImage.get("image_url").asText();
+                                dto.getActivityDetailImg().setImgUrl(verticalImageUrl);
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // AI调用失败,也可以添加数据
+            log.error("调用AI服务生成促销图片失败", e);
+        }
+//         result = activityMapper.updateById(activity);
 
         // 添加
         if (result > 0) {
@@ -342,7 +481,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
 
     @Override
-    public IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize) {
+    public IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status,String activityType, String activityName, Integer pageNum, Integer pageSize) {
         log.info("OperationalActivityServiceImpl.queryActivityList: storeId={}, status={}, activityName={}, pageNum={}, pageSize={}",
                 storeId, status, activityName, pageNum, pageSize);
 
@@ -350,6 +489,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         wrapper.eq(storeId != null, StoreOperationalActivity::getStoreId, storeId);
         wrapper.like(activityName != null && activityName != "", StoreOperationalActivity::getActivityName, activityName);
         wrapper.eq(status != null, StoreOperationalActivity::getStatus, status);
+        wrapper.eq(activityType != null, StoreOperationalActivity::getActivityType, activityType);
 
         IPage<StoreOperationalActivity> list = activityMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
 
@@ -377,7 +517,13 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                 vo.setStatusName("已结束");
             }
 
-            vo.setCouponName(lifeDiscountCouponMapper.selectById(activity.getCouponId()).getName());
+            // 设置优惠券名称(判空处理)
+            if (activity.getCouponId() != null) {
+                LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
+                if (coupon != null) {
+                    vo.setCouponName(coupon.getName());
+                }
+            }
 
             voRecords.add(vo);
         }
@@ -433,5 +579,26 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
         return activityMapper.updateById(activity);
     }
+
+    @Override
+    public int uploadActivityResult(StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityServiceImpl.uploadActivityResult: dto={}", dto);
+
+        if (dto.getId() == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+
+        if (dto.getResultType() == null) {
+            throw new IllegalArgumentException("结果类型不能为空");
+        }
+
+        StoreOperationalActivity activity = new StoreOperationalActivity();
+        activity.setId(dto.getId());
+        activity.setResultType(dto.getResultType());
+        activity.setResultText(dto.getResultText());
+        activity.setResultMediaUrl(dto.getResultMediaUrl());
+
+        return activityMapper.updateById(activity);
+    }
 }
 

+ 220 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivitySignupServiceImpl.java

@@ -0,0 +1,220 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+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.BeanUtils;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeUser;
+import shop.alien.entity.storePlatform.StoreOperationalActivity;
+import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupVO;
+import shop.alien.mapper.LifeUserMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper;
+import shop.alien.storeplatform.service.OperationalActivitySignupService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 运营活动报名服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class OperationalActivitySignupServiceImpl implements OperationalActivitySignupService {
+
+    private final StoreOperationalActivitySignupMapper signupMapper;
+    private final StoreOperationalActivityMapper activityMapper;
+    private final LifeUserMapper lifeUserMapper;
+
+    @Override
+    public IPage<StoreOperationalActivitySignupVO> querySignupList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize) {
+        log.info("OperationalActivitySignupServiceImpl.querySignupList: storeId={}, status={}, activityName={}, pageNum={}, pageSize={}", 
+                storeId, status, activityName, pageNum, pageSize);
+
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("商户ID不能为空");
+        }
+
+        // 构建查询条件
+        LambdaQueryWrapper<StoreOperationalActivitySignup> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreOperationalActivitySignup::getStoreId, storeId);
+        
+        if (status != null) {
+            wrapper.eq(StoreOperationalActivitySignup::getStatus, status);
+        }
+
+        // 如果指定了活动名称,先查询活动ID列表
+        if (activityName != null && !activityName.trim().isEmpty()) {
+            LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
+            activityWrapper.eq(StoreOperationalActivity::getStoreId, storeId)
+                    .like(StoreOperationalActivity::getActivityName, activityName.trim())
+                    .eq(StoreOperationalActivity::getDeleteFlag, 0);
+            List<StoreOperationalActivity> activities = activityMapper.selectList(activityWrapper);
+            if (activities.isEmpty()) {
+                // 如果没有匹配的活动,返回空结果
+                return new Page<>(pageNum, pageSize);
+            }
+            List<Integer> activityIds = activities.stream()
+                    .map(StoreOperationalActivity::getId)
+                    .collect(Collectors.toList());
+            wrapper.in(StoreOperationalActivitySignup::getActivityId, activityIds);
+        }
+
+        wrapper.orderByDesc(StoreOperationalActivitySignup::getCreatedTime);
+
+        // 分页查询
+        IPage<StoreOperationalActivitySignup> page = new Page<>(pageNum, pageSize);
+        IPage<StoreOperationalActivitySignup> signupPage = signupMapper.selectPage(page, wrapper);
+
+        // 转换为VO
+        IPage<StoreOperationalActivitySignupVO> voPage = new Page<>(pageNum, pageSize);
+        voPage.setTotal(signupPage.getTotal());
+        voPage.setPages(signupPage.getPages());
+
+        List<StoreOperationalActivitySignupVO> voList = signupPage.getRecords().stream().map(signup -> {
+            StoreOperationalActivitySignupVO vo = new StoreOperationalActivitySignupVO();
+            BeanUtils.copyProperties(signup, vo);
+
+            // 查询活动名称和活动类型
+            LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
+            activityWrapper.eq(StoreOperationalActivity::getId, signup.getActivityId())
+                    .eq(StoreOperationalActivity::getDeleteFlag, 0);
+            StoreOperationalActivity activity = activityMapper.selectOne(activityWrapper);
+            if (activity != null) {
+                vo.setActivityName(activity.getActivityName());
+                vo.setActivityType(activity.getActivityType());
+            }
+
+            // 查询用户昵称
+            if (signup.getUserId() != null) {
+                LifeUser lifeUser = lifeUserMapper.selectById(signup.getUserId());
+                if (lifeUser != null && lifeUser.getUserName() != null) {
+                    vo.setNickName(lifeUser.getUserName());
+                    vo.setUserImage(lifeUser.getUserImage());
+                    vo.setUserPhone(lifeUser.getUserPhone());
+                    vo.setUserName(lifeUser.getUserName());
+                }
+            }
+
+            // 设置状态文字
+            vo.setStatusText(getStatusText(signup.getStatus()));
+
+            return vo;
+        }).collect(Collectors.toList());
+
+        voPage.setRecords(voList);
+        return voPage;
+    }
+
+    @Override
+    public StoreOperationalActivitySignupVO querySignupById(Integer id) {
+        log.info("OperationalActivitySignupServiceImpl.querySignupById: id={}", id);
+
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("报名ID不能为空");
+        }
+
+        StoreOperationalActivitySignup signup = signupMapper.selectById(id);
+        if (signup == null) {
+            return null;
+        }
+
+        StoreOperationalActivitySignupVO vo = new StoreOperationalActivitySignupVO();
+        BeanUtils.copyProperties(signup, vo);
+
+        // 查询活动名称和活动类型
+        LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
+        activityWrapper.eq(StoreOperationalActivity::getId, signup.getActivityId())
+                .eq(StoreOperationalActivity::getDeleteFlag, 0);
+        StoreOperationalActivity activity = activityMapper.selectOne(activityWrapper);
+        if (activity != null) {
+            vo.setActivityName(activity.getActivityName());
+            vo.setActivityType(activity.getActivityType());
+        }
+
+        // 查询用户昵称
+        if (signup.getUserId() != null) {
+            LifeUser lifeUser = lifeUserMapper.selectById(signup.getUserId());
+            if (lifeUser != null && lifeUser.getUserName() != null) {
+                vo.setNickName(lifeUser.getUserName());
+            }
+        }
+
+        // 设置状态文字
+        vo.setStatusText(getStatusText(signup.getStatus()));
+
+        return vo;
+    }
+
+    @Override
+    public int approveSignup(Integer id) {
+        log.info("OperationalActivitySignupServiceImpl.approveSignup: id={}", id);
+
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("报名ID不能为空");
+        }
+
+        LambdaUpdateWrapper<StoreOperationalActivitySignup> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreOperationalActivitySignup::getId, id)
+                .set(StoreOperationalActivitySignup::getStatus, 2) // 2-通过
+                .set(StoreOperationalActivitySignup::getAuditTime, new Date()); // 设置审核时间为当前时间
+
+        return signupMapper.update(null, wrapper);
+    }
+
+    @Override
+    public int rejectSignup(Integer id, String rejectReason) {
+        log.info("OperationalActivitySignupServiceImpl.rejectSignup: id={}, rejectReason={}", id, rejectReason);
+
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("报名ID不能为空");
+        }
+
+        LambdaUpdateWrapper<StoreOperationalActivitySignup> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreOperationalActivitySignup::getId, id)
+                .set(StoreOperationalActivitySignup::getStatus, 1) // 1-拒绝
+                .set(StoreOperationalActivitySignup::getAuditTime, new Date()); // 设置审核时间为当前时间
+        
+        // 如果提供了拒绝原因,则保存;否则使用默认值
+        if (rejectReason != null && !rejectReason.trim().isEmpty()) {
+            wrapper.set(StoreOperationalActivitySignup::getRejectReason, rejectReason.trim());
+        } else {
+            wrapper.set(StoreOperationalActivitySignup::getRejectReason, "审核未通过");
+        }
+
+        return signupMapper.update(null, wrapper);
+    }
+
+    /**
+     * 获取状态文字
+     *
+     * @param status 状态值
+     * @return 状态文字
+     */
+    private String getStatusText(Integer status) {
+        if (status == null) {
+            return "未知";
+        }
+        switch (status) {
+            case 0:
+                return "待审核";
+            case 1:
+                return "拒绝";
+            case 2:
+                return "通过";
+            default:
+                return "未知";
+        }
+    }
+}
+

+ 9 - 9
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreManageServiceImpl.java

@@ -98,14 +98,14 @@ public class StoreManageServiceImpl implements StoreManageService {
 
         // 1. 获取经营板块信息
         Integer businessSection = storeInfoDto.getBusinessSection();
-        StoreDictionary businessSectionDict = storeDictionaryMapper.selectOne(
-                new LambdaQueryWrapper<StoreDictionary>()
-                        .eq(StoreDictionary::getDictId, businessSection)
-                        .eq(StoreDictionary::getTypeName, "business_section")
-        );
-        if (businessSectionDict == null) {
-            throw new IllegalArgumentException("经营板块不存在:" + businessSection);
-        }
+//        StoreDictionary businessSectionDict = storeDictionaryMapper.selectOne(
+//                new LambdaQueryWrapper<StoreDictionary>()
+//                        .eq(StoreDictionary::getDictId, businessSection)
+//                        .eq(StoreDictionary::getTypeName, "business_section")
+//        );
+//        if (businessSectionDict == null) {
+//            throw new IllegalArgumentException("经营板块不存在:" + businessSection);
+//        }
 
         // 2. 获取经营种类信息
         List<String> businessTypes = storeInfoDto.getBusinessTypes();
@@ -162,7 +162,7 @@ public class StoreManageServiceImpl implements StoreManageService {
 
         // 7. 设置经营板块及类型
         storeInfo.setBusinessSection(businessSection);
-        storeInfo.setBusinessSectionName(businessSectionDict.getDictDetail());
+        storeInfo.setBusinessSectionName(storeInfoDto.getBusinessSectionName());
         storeInfo.setBusinessTypes(String.join(",", businessTypes));
         storeInfo.setBusinessTypesName(String.join(",", businessTypeNames));
 

+ 89 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformUserRoleServiceImpl.java

@@ -2,6 +2,8 @@ package shop.alien.storeplatform.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+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;
@@ -15,6 +17,7 @@ import shop.alien.entity.store.StorePlatformUserRole;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.PermissionItemVo;
 import shop.alien.entity.store.vo.SubAccountDetailVo;
+import shop.alien.entity.store.vo.SubAccountListVo;
 import shop.alien.entity.store.vo.SubAccountVo;
 import shop.alien.mapper.StorePlatformMenuMapper;
 import shop.alien.mapper.StorePlatformRoleMapper;
@@ -201,7 +204,7 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
 
                 if (storeUser != null) {
                     // 如果用户有 storeId 字段值,说明是主账号,不应删除
-                    if (storeUser.getStoreId() != null && storeUser.getStoreId() > 0) {
+                    if (storeUser.getStoreId() != null && storeUser.getStoreId() > 0 || storeUser.getAccountType()==1) {
                         log.info("用户是主账号,不删除 store_user 记录: userId={}, storeId={}", userId, storeUser.getStoreId());
                     } else {
                         // 不是主账号,可以安全删除
@@ -775,5 +778,90 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
             return false;
         }
     }
+
+    @Override
+    public IPage<SubAccountListVo> queryAllSubAccounts(Integer pageNum, Integer pageSize, String accountId, String phone, Integer status) {
+        try {
+            // 创建分页对象
+            Page<SubAccountListVo> page = new Page<>(pageNum, pageSize);
+            // 执行分页查询
+            IPage<SubAccountListVo> result = storePlatformUserRoleMapper.queryAllSubAccounts(page, accountId, phone, status);
+            log.info("查询所有子账号信息成功,共{}条记录,当前页{}条,查询条件:accountId={}, phone={}, status={}", 
+                    result.getTotal(), result.getRecords() != null ? result.getRecords().size() : 0, accountId, phone, status);
+            return result;
+        } catch (Exception e) {
+            log.error("查询所有子账号信息失败,查询条件:accountId={}, phone={}, status={}", accountId, phone, status, e);
+            // 返回空分页对象
+            Page<SubAccountListVo> emptyPage = new Page<>(pageNum, pageSize);
+            emptyPage.setTotal(0);
+            emptyPage.setRecords(new ArrayList<>());
+            return emptyPage;
+        }
+    }
+
+    @Override
+    public boolean disableSubAccount(Long id, Integer status) {
+        if (id == null) {
+            log.error("子账号关联记录ID不能为空");
+            return false;
+        }
+
+        if (status == null || (status != 0 && status != 1)) {
+            log.error("状态参数必须为0(启用)或1(禁用): status={}", status);
+            return false;
+        }
+
+        try {
+            // 1. 先查询记录是否存在
+            StorePlatformUserRole userRole = storePlatformUserRoleMapper.selectById(id);
+            if (userRole == null) {
+                log.error("子账号关联记录不存在: id={}", id);
+                return false;
+            }
+
+            // 2. 检查当前状态是否已经是目标状态
+            if (userRole.getStatus() != null && userRole.getStatus().equals(status)) {
+                String statusText = status == 0 ? "启用" : "禁用";
+                log.warn("子账号已经是{}状态: id={}", statusText, id);
+                return true; // 已经是目标状态,返回成功
+            }
+
+            // 3. 根据传入的 status 值更新状态
+            LambdaUpdateWrapper<StorePlatformUserRole> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StorePlatformUserRole::getId, id)
+                    .eq(StorePlatformUserRole::getDeleteFlag, 0) // 只更新未删除的记录
+                    .set(StorePlatformUserRole::getStatus, status);
+            int updateResult = storePlatformUserRoleMapper.update(null, updateWrapper);
+            // 根据传入的 status 值更新
+            StoreUser storeUser = storeUserMapper.selectById(userRole.getUserId());
+            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成功 userId={}, phone={}, tokenKey={}",
+                            userRole.getUserId(), storeUser.getPhone(), tokenKey);
+                } else {
+                    log.warn("用户token不存在或已过期,userId={}, phone={}, tokenKey={}",
+                            userRole.getUserId(), storeUser.getPhone(), tokenKey);
+                }
+            }
+
+            if (updateResult > 0) {
+                String statusText = status == 0 ? "启用" : "禁用";
+                log.info("成功{}子账号: id={}, userId={}, storeId={}, roleId={}, status={}", 
+                        statusText, id, userRole.getUserId(), userRole.getStoreId(), userRole.getRoleId(), status);
+                return true;
+            } else {
+                String statusText = status == 0 ? "启用" : "禁用";
+                log.error("{}子账号失败: id={}", statusText, id);
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("更新子账号状态异常: id={}, status={}", id, status, e);
+            return false;
+        }
+    }
 }
 

+ 15 - 75
alien-store-platform/src/main/resources/logback-spring.xml

@@ -1,41 +1,28 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
-<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
-<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
-<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
-<!-- 该信息是由于设置了当配置文件变化时重新加载,所以每当达到扫描时间的时候就会检查配置文件是否错误。但是由于一般配置文件都放在了JAR包中,
-    而扫描的时候无法扫描JAR包内,因此会提示没有可以检查的文件,所以每隔一段时间就输出一次-->
-<configuration scan="false" scanPeriod="60 seconds" debug="true">
-    <contextName>logback-spring</contextName>
+<configuration scan="false" scanPeriod="60 seconds" debug="false">
 
-    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
     <!-- 定义全局参数常量 -->
     <property name="log.level" value="debug"/>
-    <property name="log.maxHistory" value="30"/><!-- 30表示30个 -->
-    <springProperty scope="context" name="logging.path" source="logging.path"  defaultValue="C:/project/ext/log"/>
-
+    <property name="log.maxHistory" value="30"/><!-- 30表示30天 -->
+    <springProperty scope="context" name="logging.path" source="logging.path" defaultValue="C:/project/ext/log"/>
     <!--输出文件前缀-->
     <property name="FILENAME" value="alien"/>
 
-    <!--0. 日志格式和颜色渲染 -->
-    <!-- 彩色日志依赖的渲染类 -->
-    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
-    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
-    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
-
     <!-- 文件输出格式 -->
     <property name="FILE_LOG_PATTERN" value="[%d{MM/dd HH:mm:ss.SSS}][%-10.10thread][%-5level][%-40.40c{1}:%5line]:[%15method] || %m%n"/>
-    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+    
+    <!-- 控制台输出格式:纯文本,无颜色,适合 Docker/EFK -->
+    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n"/>
 
     <!--1. 输出到控制台-->
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <!-- 【关键】控制台只输出 INFO 及以上,防止 SQL 刷屏 ES -->
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-            <level>${log.level}</level>
+            <level>INFO</level>
         </filter>
         <encoder>
             <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
-            <!-- 设置字符集 -->
             <charset>UTF-8</charset>
         </encoder>
     </appender>
@@ -43,27 +30,17 @@
     <!--2. 输出到文档-->
     <!-- DEBUG 日志 -->
     <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <!-- 当前的日志文件存放路径 -->
         <file>${logging.path}/DEBUG.log</file>
-        <!-- 日志滚动策略 -->
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <!-- 历史日志文件的存放路径和名称 -->
             <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_DEBUG.log.gz</fileNamePattern>
-            <!-- 日志文件最大的保存历史 数量-->
             <maxHistory>${log.maxHistory}</maxHistory>
         </rollingPolicy>
         <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
             <pattern>${FILE_LOG_PATTERN}</pattern>
         </encoder>
-        <!--日志文件最大的大小-->
-        <!--        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
-        <!--            <MaxFileSize>10MB</MaxFileSize>-->
-        <!--        </triggeringPolicy>-->
-        <!-- 此日志文档只记录debug级别的 -->
         <filter class="ch.qos.logback.classic.filter.LevelFilter">
             <level>DEBUG</level>
-            <onMatch>ACCEPT</onMatch>  <!-- 用过滤器,只接受DEBUG级别的日志信息,其余全部过滤掉 -->
+            <onMatch>ACCEPT</onMatch>
             <onMismatch>DENY</onMismatch>
         </filter>
     </appender>
@@ -118,41 +95,14 @@
         </filter>
     </appender>
 
-    <!--
-      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
-      以及指定<appender>。<logger>仅有一个name属性,
-      一个可选的level和一个可选的addtivity属性。
-      name:用来指定受此logger约束的某一个包或者具体的某一个类。
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-         还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
-         如果未设置此属性,那么当前logger将会继承上级的级别。
-      addtivity:是否向上级logger传递打印信息。默认是true。
-      <logger name="org.springframework.web" level="info"/>
-      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-    -->
-
-    <!--
-      使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
-      第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
-      第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-      【logging.level.org.mybatis=debug logging.level.dao=debug】
-     -->
-    <!-- mybatis显示sql,修改此处扫描包名 -->
-
-
-    <!--
-      root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
-      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
-      不能设置为INHERITED或者同义词NULL。默认是DEBUG
-      可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-    -->
+    <!-- 降噪配置 -->
+    <logger name="springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator" level="WARN"/>
+    <logger name="org.springframework.security.web.DefaultSecurityFilterChain " level="WARN"/>
+    <logger name="com.netflix.config.sources.URLConfigurationSource " level="WARN"/>
+    <logger name="com.netflix.discovery" level="WARN"/>
+    <logger name="org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]" level="WARN"/>
 
     <!-- 4. 最终的策略 -->
-    <!-- 4.1 开发环境:打印控制台-->
-    <!--打印sql-->
-    <!--    <logger name="com.veryhappy.music.dao" level="debug"/>-->
-
-    <!--打印log-->
     <root level="info">
         <appender-ref ref="CONSOLE"/>
         <appender-ref ref="DEBUG_FILE"/>
@@ -161,14 +111,4 @@
         <appender-ref ref="ERROR_FILE"/>
     </root>
 
-    <!--   4.2 生产环境:输出到文档-->
-    <springProfile name="pro">
-        <root level="info">
-            <appender-ref ref="CONSOLE"/>
-            <appender-ref ref="DEBUG_FILE"/>
-            <appender-ref ref="INFO_FILE"/>
-            <appender-ref ref="ERROR_FILE"/>
-            <appender-ref ref="WARN_FILE"/>
-        </root>
-    </springProfile>
 </configuration>

+ 7 - 0
alien-store/pom.xml

@@ -304,6 +304,13 @@
     </dependencies>
 
     <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes><include>**/*</include></includes>
+                <filtering>false</filtering>
+            </resource>
+        </resources>
         <plugins>
             <plugin>
                 <!-- 指定项目编译时的java版本和编码方式 -->

+ 62 - 0
alien-store/src/main/java/shop/alien/store/config/WeChatMiniProgramConfig.java

@@ -0,0 +1,62 @@
+package shop.alien.store.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Component;
+
+/**
+ * 微信小程序配置
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@Component
+@RefreshScope
+@ConfigurationProperties(prefix = "wechat.miniprogram")
+public class WeChatMiniProgramConfig {
+
+    /**
+     * 小程序AppID
+     */
+    private String appId;
+
+    /**
+     * 小程序AppSecret
+     */
+    private String appSecret;
+
+    /**
+     * 小程序页面路径
+     */
+    private String pagePath;
+
+    /**
+     * 环境版本:release-正式版,trial-体验版,develop-开发版
+     */
+    private String envVersion;
+
+    /**
+     * 二维码配置
+     */
+    private QrCodeConfig qrcode = new QrCodeConfig();
+
+    @Data
+    public static class QrCodeConfig {
+        /**
+         * 是否使用OSS存储
+         */
+        private boolean useOss = true;
+
+        /**
+         * OSS Bucket名称
+         */
+        private String ossBucket;
+
+        /**
+         * 本地存储路径(不使用OSS时)
+         */
+        private String localPath;
+    }
+}

+ 13 - 2
alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import io.swagger.annotations.Api;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cloud.context.config.annotation.RefreshScope;
 import org.springframework.http.HttpEntity;
@@ -21,9 +22,11 @@ import org.springframework.web.client.RestTemplate;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeBlacklist;
 import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreInfoVo;
 import shop.alien.mapper.LifeBlacklistMapper;
 import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.StoreUserMapper;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.CommonRatingService;
 import shop.alien.store.service.StoreImgService;
@@ -42,7 +45,8 @@ import java.util.stream.Collectors;
 public class AiSearchController {
 
     private final StoreImgMapper storeImgMapper;
-    
+    private final StoreUserMapper storeUserMapper;
+
     @Value("${third-party-ai-search.exact.base-url:http://124.93.18.180:7870/api/v1/search}")
     private String aiSearchExactUrl;
     
@@ -162,7 +166,14 @@ public class AiSearchController {
         queryWrapper.eq("blocker_type",2);
         queryWrapper.eq("blocked_type",1);
         List<LifeBlacklist> lifeBlacklists = lifeBlacklistMapper.selectList(queryWrapper);
+        // 坑:查询出来的是拉黑的商户id,不是商铺id 😊彻底疯狂
         List<String> blockedIds = lifeBlacklists.stream().map(x -> x.getBlockedId()).collect(Collectors.toList());
+        List<Integer> collect = new ArrayList<>();
+        if(blockedIds.size()>0){
+            List<StoreUser> storeUsers = storeUserMapper.selectBatchIds(blockedIds);
+            collect = storeUsers.stream().filter(x -> StringUtils.isNotBlank(x.getStoreId().toString())).map(x -> x.getStoreId()).collect(Collectors.toList());
+        }
+
         List<StoreInfoVo> storeInfoList = new ArrayList<>();
 
         if (results != null) {
@@ -187,7 +198,7 @@ public class AiSearchController {
 
                 // 使用JSON.parseObject方法进行转换
                 StoreInfoVo storeInfo = JSON.parseObject(camelCaseItem.toJSONString(), StoreInfoVo.class);
-                if(blockedIds.contains(storeInfo.getId().toString())){
+                if(collect.contains(storeInfo.getId())){
                     continue;
                 }
                 Integer totalCount = 0;

+ 218 - 0
alien-store/src/main/java/shop/alien/store/controller/AiTagsController.java

@@ -0,0 +1,218 @@
+package shop.alien.store.controller;
+
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+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.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.CommonRating;
+import shop.alien.store.service.CommonRatingService;
+import shop.alien.store.util.ai.AiAuthTokenUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+@Api(tags = {"ai搜索标签"})
+@CrossOrigin
+@RestController
+@RequestMapping("/aiTags")
+@RequiredArgsConstructor
+@RefreshScope
+public class AiTagsController {
+
+    @Value("${third-part-ai-tags.tags-url:http://124.93.18.180:9000/ai/intelligent-analysis/api/v1/tag/get_store_level_tag}")
+    private String aiTagsUrl;
+
+    @Value("${third-part-ai-tags.tags-rating-url:http://124.93.18.180:9000/ai/intelligent-analysis/api/v1/tag/get_ratings_by_tag}")
+    private String tagsRatingUrl;
+
+    private static final Pattern PATTERN = Pattern.compile("([^()]+)\\((\\d+)\\)");
+
+    private final RestTemplate restTemplate;
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+    private final CommonRatingService commonRatingService;
+    /**
+     * 获取店铺标签
+     *
+     * @param storeId 店铺ID
+     * @param page    页码
+     * @param pageNum 每页数量
+     * @return 店铺标签列表
+     */
+    @ApiOperation("获取店铺标签")
+    @RequestMapping("/storeTags")
+    public R storeTags(@RequestParam("storeId") Long storeId,
+                       @RequestParam(value = "page", defaultValue = "1") Integer page,
+                       @RequestParam(value = "pageNum", defaultValue = "10") Integer pageNum) {
+        try {
+            // 1. 获取访问令牌(如果外部接口需要,可添加到请求头)
+            String accessToken = aiAuthTokenUtil.getAccessToken();
+
+            // 2. 构建请求头
+            HttpHeaders aiHeaders = new HttpHeaders();
+            aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+            // 如果外部接口需要 token 认证,添加到请求头(根据实际需求调整)
+            if (accessToken != null && !accessToken.isEmpty()) {
+                aiHeaders.set("Authorization", "Bearer " + accessToken);
+            }
+
+            // 3. 拼接请求 URL 和参数(重点:按外部接口格式拼接)
+            // 使用 String.format 拼接参数,注意参数名对应:page_size 而非 pageNum
+            String requestUrl = String.format("%s?store_id=%d&page=%d&page_size=%d",
+                    aiTagsUrl, storeId, page, pageNum);
+
+            HttpEntity<Void> requestEntity = new HttpEntity<>(aiHeaders);
+            ResponseEntity<String> exchange = restTemplate.exchange(requestUrl, HttpMethod.GET, requestEntity, String.class);
+//            ResponseEntity<String> responseEntity = restTemplate.exchange(requestUrl,"GET",requestEntity,String.class);
+            if(exchange.getStatusCode().is2xxSuccessful()){
+                // 4. 解析响应体(根据外部接口返回格式调整)
+                String responseBody = exchange.getBody();
+                JSONObject jsonObject = JSONObject.parseObject(responseBody);
+                JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list");
+                if(jsonArray.isEmpty()){
+                    return R.data(new ArrayList<>());
+                }
+                String negativeComment = jsonArray.getJSONObject(0).getString("negative_comment");
+                // 这里简单返回原始字符串(根据实际情况调整)
+                return R.data(parseComment(negativeComment));
+            } else {
+                return R.fail("获取店铺标签失败:" + exchange.getStatusCode());
+            }
+        } catch (Exception e) {
+            // 补充异常处理:捕获调用外部接口的异常,返回友好提示
+            e.printStackTrace(); // 生产环境建议用日志框架记录
+            return R.fail("获取店铺标签失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据标签获取店铺评分
+     *
+     * @param storeId 店铺ID
+     * @param tag     标签(如:商品质量差)
+     * @param page    页码
+     * @param pageNum 每页数量(对应外部接口的page_size)
+     * @return 店铺评分列表
+     */
+    @ApiOperation("根据标签获取店铺评价")
+    @RequestMapping("/storeRatingsByTag")
+    public R storeRatingsByTag(@RequestParam("storeId") Long storeId,
+                               @RequestParam("tag") String tag,  // 新增标签参数,必填
+                               @RequestParam(value = "page", defaultValue = "1") Integer page,
+                               @RequestParam(value = "pageNum", defaultValue = "10") Integer pageNum) {
+        try {
+            // 1. 获取访问令牌(复用原有逻辑)
+            String accessToken = aiAuthTokenUtil.getAccessToken();
+
+            // 2. 构建请求头
+            HttpHeaders aiHeaders = new HttpHeaders();
+            aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+            // 添加token认证(如有需要)
+            if (accessToken != null && !accessToken.isEmpty()) {
+                aiHeaders.set("Authorization", "Bearer " + accessToken);
+            }
+
+            // 3. 拼接请求URL和参数(核心调整:新增tag参数,匹配外部接口参数名)
+            String requestUrl = String.format("%s?store_id=%d&tag=%s&page=%d&page_size=%d",
+                    tagsRatingUrl, storeId, tag, page, pageNum);
+
+            // 4. 发送GET请求
+            HttpEntity<Void> requestEntity = new HttpEntity<>(aiHeaders);
+            ResponseEntity<String> exchange = restTemplate.exchange(requestUrl, HttpMethod.GET, requestEntity, String.class);
+
+            // 5. 处理响应结果
+            if (exchange.getStatusCode().is2xxSuccessful()) {
+                String responseBody = exchange.getBody();
+                JSONObject jsonObject = JSONObject.parseObject(responseBody);
+                JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list");
+                if(jsonArray.isEmpty()){
+                    return R.data(new ArrayList<>());
+                }
+                // 2. 提取list中的数值(这里list是数值数组,而非对象数组)
+                List<Long> idList = new ArrayList<>();
+                for (int i = 0; i < jsonArray.size(); i++) {
+                    // 直接获取数值并转为Long类型
+                    Object value = jsonArray.get(i);
+                    if (value instanceof Number) {
+                        idList.add(((Number) value).longValue());
+                    }
+                }
+                Page<CommonRating> page1 = new Page<>(page, pageNum);
+                QueryWrapper<CommonRating> wrapper = new QueryWrapper<>();
+                wrapper.in("id",idList);
+                IPage<CommonRating> page2 = commonRatingService.page(page1, wrapper);
+                // 保持和原有接口一致的返回格式:将data节点重新封装(可根据实际需求调整)
+                return commonRatingService.doListBusinessWithType(page2, 1, null, null);
+            } else {
+                return R.fail("获取店铺评价失败:" + exchange.getStatusCode());
+            }
+        } catch (Exception e) {
+            // 异常日志记录(生产环境建议替换为日志框架,如logback/log4j2)
+            e.printStackTrace();
+            return R.fail("获取店铺评分失败:" + e.getMessage());
+        }
+    }
+
+    public static List<JSONObject> parseComment(String negativeComment) {
+        // 最终返回的结果列表
+        List<JSONObject> resultList = new ArrayList<>();
+        // 临时存储标签-数值的映射,用于累加相同标签的数值
+        Map<String, Integer> tagCountMap = new HashMap<>();
+
+        // 1. 空值校验:避免空字符串/Null导致后续处理异常
+        if (negativeComment == null || negativeComment.trim().isEmpty()) {
+            return resultList;
+        }
+
+        // 2. 按分号;分割成多个片段(如:["差评(1)", "商品质量差(1),商品质量差(1)"])
+        String[] semicolonParts = negativeComment.split(";");
+        String trimPart = semicolonParts[1].trim();
+
+            // 3. 按逗号,分割当前片段内的多个标签(如:["商品质量差(1)", "商品质量差(1)"])
+            String[] commaParts = trimPart.split(",");
+            for (String tagWithNum : commaParts) {
+                String trimTag = tagWithNum.trim();
+                if (trimTag.isEmpty()) {
+                    continue;
+                }
+
+                // 4. 正则匹配标签和数值(如:"差评(1)" → 标签=差评,数值=1)
+                Matcher matcher = PATTERN.matcher(trimTag);
+                if (matcher.find()) {
+                    String label = matcher.group(1).trim(); // 标签(如:差评)
+                    int value = Integer.parseInt(matcher.group(2)); // 数值(如:1)
+
+                    // 5. 累加相同标签的数值
+                    tagCountMap.put(label, tagCountMap.getOrDefault(label, 0) + value);
+                }
+            }
+
+        // 6. 将Map转换为指定的List<JSONObject>格式
+        for (Map.Entry<String, Integer> entry : tagCountMap.entrySet()) {
+            JSONObject jsonObj = new JSONObject();
+            jsonObj.put("label", entry.getKey());
+            jsonObj.put("value", String.valueOf(entry.getValue())); // value转为字符串,匹配示例格式
+            resultList.add(jsonObj);
+        }
+
+        return resultList;
+    }
+}

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

@@ -140,7 +140,7 @@ public class CommonCommentController {
     public R getCommitCount(@RequestParam Integer sourceId,
                             @RequestParam Integer sourceType,
                             @RequestParam String userId,
-                            @RequestParam String userType){
+                            @RequestParam(required = false) String userType){
         return R.data(commonCommentService.getCommitCount(sourceId, sourceType, userId, userType));
     }
 }

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

@@ -87,7 +87,11 @@ public class CommonRatingController {
     @PostMapping("/addRating")
     public R<Integer> add(@RequestBody CommonRating commonRating) {
         log.info("CommonRatingController.add?commonRating={}", commonRating);
-        return R.data(commonRatingService.saveCommonRating(commonRating));
+        try {
+            return R.data(commonRatingService.saveCommonRating(commonRating));
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        }
     }
 
     @ApiOperation("获取评价详情,和所有回复")
@@ -113,7 +117,7 @@ public class CommonRatingController {
 
     /**
      * 删除评价(并重新统计店铺评分)
-     * 
+     *
      * @param ratingId 评价id
      * @return 0:成功, 1:失败
      */
@@ -121,16 +125,16 @@ public class CommonRatingController {
     @GetMapping("/deleteRating")
     public R deleteRating(@RequestParam Long ratingId) {
         log.info("删除评价,ratingId={}", ratingId);
-        
+
         // 先获取评价信息(用于后续更新评分)
         CommonRating rating = commonRatingService.getById(ratingId);
         if (rating == null) {
             return R.fail("评价不存在");
         }
-        
+
         // 删除评价
         boolean b = commonRatingService.removeById(ratingId);
-        
+
         if (b) {
             // 删除成功后,重新统计店铺评分(仅店铺评价类型)
             if (rating.getBusinessType() != null && rating.getBusinessType() == 1) {

+ 106 - 0
alien-store/src/main/java/shop/alien/store/controller/FeedbackEmailController.java

@@ -0,0 +1,106 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.*;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.store.util.ai.AiFeedbackEmailUtil;
+
+/**
+ * 邮件分发Controller
+ * 提供反馈邮件发送接口
+ *
+ * @author system
+ * @date 2025-01-20
+ */
+@Slf4j
+@Api(tags = {"邮件分发接口"})
+@CrossOrigin
+@RestController
+@RequestMapping("/feedback-email")
+@RequiredArgsConstructor
+public class FeedbackEmailController {
+
+    private final AiFeedbackEmailUtil aiFeedbackEmailUtil;
+
+    @ApiOperation(value = "发送反馈邮件", httpMethod = "POST")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/send")
+    public R<EmailSendResponse> sendFeedbackEmail(@RequestBody FeedbackEmailRequest request) {
+        log.info("FeedbackEmailController.sendFeedbackEmail, request={}", request);
+
+        // 参数校验
+        if (request == null || !StringUtils.hasText(request.getFeedbackContent())) {
+            log.error("反馈内容不能为空");
+            return R.fail("反馈内容不能为空");
+        }
+
+        try {
+            // 调用邮件发送工具类
+            AiFeedbackEmailUtil.EmailSendResult result = aiFeedbackEmailUtil.sendFeedbackEmail(
+                    request.getFeedbackType(),
+                    request.getFeedbackContent(),
+                    request.getUserId(),
+                    request.getFeedbackSource(),
+                    request.getContactWay()
+            );
+
+            if (result.isSuccess()) {
+                EmailSendResponse response = new EmailSendResponse();
+                response.setSuccess(true);
+                response.setMessage(result.getMessage());
+                response.setTicketId(result.getTicketId());
+                log.info("邮件发送成功:ticketId={}", result.getTicketId());
+                return R.data(response, "邮件发送成功");
+            } else {
+                log.warn("邮件发送失败:{}", result.getMessage());
+                return R.fail(result.getMessage());
+            }
+        } catch (Exception e) {
+            log.error("发送反馈邮件异常", e);
+            return R.fail("发送邮件异常:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 反馈邮件请求DTO
+     */
+    @Data
+    @ApiModel(value = "FeedbackEmailRequest", description = "反馈邮件请求参数")
+    public static class FeedbackEmailRequest {
+        @ApiModelProperty(value = "问题反馈类型(0-bug反馈,1-优化反馈,2-新增功能反馈)", example = "0")
+        private String feedbackType;
+
+        @ApiModelProperty(value = "反馈内容(必填)", required = true, example = "测试bug反馈邮件功能")
+        private String feedbackContent;
+
+        @ApiModelProperty(value = "用户ID(可选)", example = "1001")
+        private Integer userId;
+
+        @ApiModelProperty(value = "反馈来源(可选,0,1)", example = "0")
+        private String feedbackSource;
+
+        @ApiModelProperty(value = "联系方式(可选)", example = "test@example.com")
+        private String contactWay;
+    }
+
+    /**
+     * 邮件发送响应DTO
+     */
+    @Data
+    @ApiModel(value = "EmailSendResponse", description = "邮件发送响应结果")
+    public static class EmailSendResponse {
+        @ApiModelProperty(value = "是否成功")
+        private Boolean success;
+
+        @ApiModelProperty(value = "响应消息")
+        private String message;
+
+        @ApiModelProperty(value = "工单ID")
+        private Long ticketId;
+    }
+}
+

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

@@ -60,10 +60,11 @@ public class OperationalActivityController {
             @RequestParam(value = "pageNum", required = false) Integer pageNum,
             @RequestParam(value = "pageSize", required = false) Integer pageSize,
             @RequestParam(value = "status", required = false) Integer status,
+            @RequestParam(value = "activityType", required = false) String activityType,
             @RequestParam(value = "activityName", required = false) String activityName) {
         log.info("OperationalActivityController.pageActivityDetail storeId={}, storeName={}, pageNum={}, pageSize={}, status={}, activityName={}", storeId, storeName, pageNum, pageSize, status, activityName);
         try {
-            IPage<StoreOperationalActivityVO> result = activityService.pageActivityDetail(storeId, storeName, pageNum, pageSize, status, activityName);
+            IPage<StoreOperationalActivityVO> result = activityService.pageActivityDetail(storeId, storeName, pageNum, pageSize, status, activityName, activityType);
             return R.data(result);
         } catch (IllegalArgumentException e) {
             return R.fail(e.getMessage());

+ 147 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreCuisineCategoryController.java

@@ -0,0 +1,147 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreCuisineCategory;
+import shop.alien.entity.store.dto.StoreCuisineCategoryDTO;
+import shop.alien.entity.store.dto.StoreCuisineCategorySortDTO;
+import shop.alien.store.service.StoreCuisineCategoryService;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 菜品分类管理 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"商家端点餐管理-菜品分类管理"})
+@ApiSort(14)
+@CrossOrigin
+@RestController
+@RequestMapping("/storeCuisineCategory")
+@RequiredArgsConstructor
+public class StoreCuisineCategoryController {
+
+    private final StoreCuisineCategoryService storeCuisineCategoryService;
+
+    @ApiOperationSupport(order = 1)
+    @ApiOperation("查询菜品分类列表")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getCategoryList")
+    public R<List<StoreCuisineCategory>> getCategoryList(@RequestParam Integer storeId) {
+        log.info("StoreCuisineCategoryController.getCategoryList?storeId={}", storeId);
+        return R.data(storeCuisineCategoryService.getCategoryList(storeId));
+    }
+
+    @ApiOperationSupport(order = 3)
+    @ApiOperation("批量创建菜品分类")
+    @PostMapping("/batchCreateCategories")
+    public R<Boolean> batchCreateCategories(@RequestBody StoreCuisineCategoryDTO dto) {
+        log.info("StoreCuisineCategoryController.batchCreateCategories?dto={}", dto);
+        
+        if (dto.getStoreId() == null || !StringUtils.hasText(dto.getCategoryNames())) {
+            return R.fail("门店ID和分类名称列表不能为空");
+        }
+
+        // 解析分类名称列表
+        List<String> categoryNameList = Arrays.stream(dto.getCategoryNames().split(","))
+                .map(String::trim)
+                .filter(StringUtils::hasText)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (categoryNameList.isEmpty()) {
+            return R.fail("分类名称列表不能为空");
+        }
+
+        try {
+            boolean result = storeCuisineCategoryService.batchCreateCategories(dto.getStoreId(), categoryNameList);
+            if (result) {
+                return R.success("批量创建菜品分类成功");
+            } else {
+                return R.fail("批量创建菜品分类失败");
+            }
+        } catch (Exception e) {
+            log.error("批量创建菜品分类失败", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperationSupport(order = 4)
+    @ApiOperation("编辑菜品分类")
+    @PostMapping("/updateCategory")
+    public R<Boolean> updateCategory(@RequestBody StoreCuisineCategoryDTO dto) {
+        log.info("StoreCuisineCategoryController.updateCategory?dto={}", dto);
+        
+        if (dto.getId() == null || !StringUtils.hasText(dto.getCategoryName())) {
+            return R.fail("分类ID和分类名称不能为空");
+        }
+        
+        try {
+            boolean result = storeCuisineCategoryService.updateCategory(dto.getId(), dto.getCategoryName());
+            if (result) {
+                return R.success("更新菜品分类成功");
+            } else {
+                return R.fail("更新菜品分类失败");
+            }
+        } catch (Exception e) {
+            log.error("更新菜品分类失败", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperationSupport(order = 5)
+    @ApiOperation("删除菜品分类")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "分类ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/deleteCategory")
+    public R<Boolean> deleteCategory(@RequestParam Integer id) {
+        log.info("StoreCuisineCategoryController.deleteCategory?id={}", id);
+        
+        try {
+            boolean result = storeCuisineCategoryService.deleteCategory(id);
+            if (result) {
+                return R.success("删除菜品分类成功");
+            } else {
+                return R.fail("删除菜品分类失败");
+            }
+        } catch (Exception e) {
+            log.error("删除菜品分类失败", e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperationSupport(order = 6)
+    @ApiOperation("更新菜品分类排序")
+    @PostMapping("/updateCategorySort")
+    public R<Boolean> updateCategorySort(@RequestBody StoreCuisineCategorySortDTO dto) {
+        log.info("StoreCuisineCategoryController.updateCategorySort?dto={}", dto);
+        
+        if (dto.getStoreId() == null || dto.getCategoryIds() == null || dto.getCategoryIds().isEmpty()) {
+            return R.fail("门店ID和分类ID列表不能为空");
+        }
+        
+        try {
+            boolean result = storeCuisineCategoryService.updateCategorySort(dto.getStoreId(), dto.getCategoryIds());
+            if (result) {
+                return R.success("更新菜品分类排序成功");
+            } else {
+                return R.fail("更新菜品分类排序失败");
+            }
+        } catch (Exception e) {
+            log.error("更新菜品分类排序失败", e);
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 45 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java

@@ -11,12 +11,15 @@ import org.springframework.beans.BeanUtils;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreCuisine;
+import shop.alien.entity.store.StoreInfo;
 import shop.alien.entity.store.StorePrice;
 import shop.alien.entity.store.dto.CuisineComboDto;
 import shop.alien.entity.store.dto.CuisineTypeResponseDto;
+import shop.alien.entity.store.dto.TablewareFeeDto;
 import shop.alien.entity.store.vo.PriceListVo;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.StoreCuisineService;
+import shop.alien.store.service.StoreInfoService;
 import shop.alien.store.service.StorePriceService;
 import shop.alien.store.util.ai.AiGetPriceUtil;
 
@@ -44,6 +47,8 @@ public class StoreCuisineController {
 
     private final StorePriceService storePriceService;
 
+    private final StoreInfoService storeInfoService;
+
     @ApiOperation("新增美食套餐或单品")
     @ApiOperationSupport(order = 1)
     @PostMapping("/addCuisineCombo")
@@ -181,6 +186,8 @@ public class StoreCuisineController {
                     PriceListVo vo = new PriceListVo();
 
                     BeanUtils.copyProperties(cuisine, vo);
+                    // 设置分类名称
+                    vo.setCategoryNames(storeCuisineService.getCategoryNames(cuisine.getCategoryIds()));
                     priceListVo.add(vo);
                 }
             }
@@ -246,6 +253,44 @@ public class StoreCuisineController {
         String price = priceObj.toString();
         return R.data(price);
     }
+
+    @ApiOperation("保存或更新餐具费")
+    @ApiOperationSupport(order = 8)
+    @PostMapping("/saveTablewareFee")
+    public R<String> saveTablewareFee(@RequestBody TablewareFeeDto tablewareFeeDto) {
+        log.info("StoreCuisineController.saveTablewareFee?storeId={},tablewareFee={}", 
+                tablewareFeeDto.getStoreId(), tablewareFeeDto.getTablewareFee());
+        
+        if (tablewareFeeDto.getStoreId() == null) {
+            return R.fail("门店ID不能为空");
+        }
+        
+        if (storeInfoService.saveOrUpdateTablewareFee(tablewareFeeDto.getStoreId(), tablewareFeeDto.getTablewareFee())) {
+            return R.success("保存成功");
+        }
+        return R.fail("保存失败");
+    }
+
+    @ApiOperation("查询餐具费")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getTablewareFee")
+    public R<Integer> getTablewareFee(@RequestParam("storeId") Integer storeId) {
+        log.info("StoreCuisineController.getTablewareFee?storeId={}", storeId);
+        
+        if (storeId == null) {
+            return R.fail("门店ID不能为空");
+        }
+        
+        StoreInfo storeInfo = storeInfoService.getById(storeId);
+        if (storeInfo == null) {
+            return R.fail("门店不存在");
+        }
+        
+        return R.data(storeInfo.getTablewareFee());
+    }
 }
 
 

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

@@ -16,6 +16,7 @@ import shop.alien.store.service.StoreOfficialAlbumService;
 import shop.alien.store.util.GroupConstant;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 二期-门店图片Controller
@@ -238,7 +239,20 @@ public class StoreImgController {
     @PostMapping("/delete")
     public R<String> delete(@RequestBody List<Integer> ids) {
         log.info("StoreImgController.delete?ids={}", ids);
-        if (storeImgService.removeByIds(ids)) {
+        // 参数校验:ID列表不能为空
+        if (ids == null || ids.isEmpty()) {
+            log.warn("删除图片失败,ID列表为空");
+            return R.fail("删除失败:ID列表不能为空");
+        }
+        // 过滤掉null值和无效ID
+        List<Integer> validIds = ids.stream()
+                .filter(id -> id != null && id > 0)
+                .collect(Collectors.toList());
+        if (validIds.isEmpty()) {
+            log.warn("删除图片失败,没有有效的ID");
+            return R.fail("删除失败:没有有效的ID");
+        }
+        if (storeImgService.removeByIds(validIds)) {
             return R.success("删除成功");
         }
         return R.fail("删除失败");

+ 128 - 8
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.http.ResponseEntity;
 import org.springframework.transaction.annotation.Transactional;
@@ -72,6 +73,7 @@ public class StoreInfoController {
     private final StoreInfoMapper storeInfoMapper;
     private final LifeBlacklistMapper lifeBlacklistMapper;
     private final StoreUserMapper storeUserMapper;
+    private final LifeFansMapper lifeFansMapper;
 
     @ApiOperation("获取所有门店")
     @ApiOperationSupport(order = 1)
@@ -1516,7 +1518,7 @@ public class StoreInfoController {
         }
     }
 
-    @ApiOperation("分页随机查询店铺(排除已删除和已拉黑的店铺)")
+    @ApiOperation("分页随机查询店铺(排除已删除、已拉黑和已关注的店铺)")
     @ApiOperationSupport(order = 100)
     @ApiImplicitParams({
             @ApiImplicitParam(
@@ -1573,13 +1575,69 @@ public class StoreInfoController {
                 }
             }
             
+            // 查询当前登录用户已关注的店铺ID(仅普通用户)
+            List<Integer> followedStoreIds = new ArrayList<>();
+            if (userLoginInfo != null && userLoginInfo.getUserId() > 0 && "user".equals(userLoginInfo.getType())) {
+                try {
+                    // 查询用户信息获取手机号
+                    Integer userId = userLoginInfo.getUserId();
+                    LifeUser lifeUser = lifeUserMapper.selectById(userId);
+                    
+                    if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone())) {
+                        // 构造粉丝ID:fans_id = "user_" + 用户手机号
+                        String fansId = "user_" + lifeUser.getUserPhone();
+                        
+                        // 查询该用户关注的所有门店(followed_id 以 "store_" 开头)
+                        LambdaQueryWrapper<LifeFans> fansWrapper = new LambdaQueryWrapper<>();
+                        fansWrapper.eq(LifeFans::getFansId, fansId)
+                                   .likeRight(LifeFans::getFollowedId, "store_")
+                                   .eq(LifeFans::getDeleteFlag, 0);
+                        List<LifeFans> lifeFansList = lifeFansMapper.selectList(fansWrapper);
+                        
+                        if (!CollectionUtils.isEmpty(lifeFansList)) {
+                            // 从 followed_id 中提取门店手机号(去掉 "store_" 前缀)
+                            Set<String> storePhones = new HashSet<>();
+                            for (LifeFans fans : lifeFansList) {
+                                if (fans.getFollowedId() != null && fans.getFollowedId().startsWith("store_")) {
+                                    String storePhone = fans.getFollowedId().substring(6); // 去掉 "store_" 前缀
+                                    if (StringUtils.isNotEmpty(storePhone)) {
+                                        storePhones.add(storePhone);
+                                    }
+                                }
+                            }
+                            
+                            // 通过门店手机号查询门店ID
+                            if (!storePhones.isEmpty()) {
+                                LambdaQueryWrapper<StoreInfo> storeWrapper = new LambdaQueryWrapper<>();
+                                storeWrapper.in(StoreInfo::getStoreTel, storePhones)
+                                          .eq(StoreInfo::getDeleteFlag, 0);
+                                List<StoreInfo> followedStores = storeInfoMapper.selectList(storeWrapper);
+                                
+                                for (StoreInfo store : followedStores) {
+                                    if (store.getId() != null) {
+                                        followedStoreIds.add(store.getId());
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("查询用户已关注门店失败 - userId: {}, error: {}", 
+                            userLoginInfo.getUserId(), e.getMessage(), e);
+                }
+            }
+            
+            // 合并拉黑和已关注的店铺ID列表
+            Set<Integer> excludedStoreIds = new HashSet<>(blacklistedStoreIds);
+            excludedStoreIds.addAll(followedStoreIds);
+            
             // 构建查询条件
             LambdaQueryWrapper<StoreInfo> queryWrapper = new LambdaQueryWrapper<>();
             queryWrapper.eq(StoreInfo::getDeleteFlag, 0); // 未删除
             
-            // 排除被拉黑的店铺
-            if (!blacklistedStoreIds.isEmpty()) {
-                queryWrapper.notIn(StoreInfo::getId, blacklistedStoreIds);
+            // 排除被拉黑和已关注的店铺
+            if (!excludedStoreIds.isEmpty()) {
+                queryWrapper.notIn(StoreInfo::getId, excludedStoreIds);
             }
             
             // 随机排序
@@ -1591,7 +1649,7 @@ public class StoreInfoController {
             // 执行分页查询
             IPage<StoreInfo> result = storeInfoMapper.selectPage(pageObj, queryWrapper);
             
-            // 批量查询商户头像
+            // 批量查询商户头像和手机号
             List<Integer> storeIds = new ArrayList<>();
             for (StoreInfo store : result.getRecords()) {
                 if (store.getId() != null) {
@@ -1600,20 +1658,31 @@ public class StoreInfoController {
             }
             
             Map<Integer, String> headImgMap = new HashMap<>();
+            Map<Integer, String> storePhoneMap = new HashMap<>(); // 店铺ID -> 门店手机号
             if (!storeIds.isEmpty()) {
+                // 批量查询店铺信息,获取门店手机号
+                List<StoreInfo> storeInfoList = storeInfoMapper.selectBatchIds(storeIds);
+                for (StoreInfo storeInfo : storeInfoList) {
+                    if (storeInfo.getId() != null && StringUtils.isNotEmpty(storeInfo.getStoreTel())) {
+                        storePhoneMap.put(storeInfo.getId(), storeInfo.getStoreTel());
+                    }
+                }
+                
                 LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
                 userWrapper.in(StoreUser::getStoreId, storeIds)
                           .eq(StoreUser::getDeleteFlag, 0);
                 List<StoreUser> storeUsers = storeUserMapper.selectList(userWrapper);
                 for (StoreUser user : storeUsers) {
-                    if (user.getStoreId() != null && user.getHeadImg() != null) {
+                    if (user.getStoreId() != null) {
                         // 如果同一个店铺有多个用户,取第一个(通常是主账号)
-                        headImgMap.putIfAbsent(user.getStoreId(), user.getHeadImg());
+                        if (user.getHeadImg() != null) {
+                            headImgMap.putIfAbsent(user.getStoreId(), user.getHeadImg());
+                        }
                     }
                 }
             }
             
-            // 转换为包含头像的VO对象
+            // 转换为包含头像和关注状态的VO对象
             List<StoreInfoWithHeadImg> voList = new ArrayList<>();
             for (StoreInfo store : result.getRecords()) {
                 StoreInfoWithHeadImg vo = new StoreInfoWithHeadImg();
@@ -1621,6 +1690,53 @@ public class StoreInfoController {
                 // 获取商户头像,如果没有头像则设置为null
                 String headImg = headImgMap.get(store.getId());
                 vo.setHeadImg(headImg != null && !headImg.trim().isEmpty() ? headImg : null);
+                
+                // 当前登录用户是否关注该店铺
+                Integer isFollowed = 0; // 默认未关注
+                if (userLoginInfo != null && userLoginInfo.getUserId() > 0 && "user".equals(userLoginInfo.getType())) {
+                    try {
+                        // userId是用户ID(数字),查询用户信息获取手机号
+                        Integer userId = userLoginInfo.getUserId();
+                        LifeUser lifeUser = lifeUserMapper.selectById(userId);
+
+                        // 获取门店手机号
+                        String storePhone = storePhoneMap.get(store.getId());
+
+                        if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone())
+                                && StringUtils.isNotEmpty(storePhone)) {
+                            // 构造关注关系:followed_id = "store_" + 门店手机号,fans_id = "user_" + 用户手机号
+                            String followedId = "store_" + storePhone;
+                            String fansId = "user_" + lifeUser.getUserPhone();
+
+                            // 查询关注关系
+                            LambdaQueryWrapper<LifeFans> fansWrapper = new LambdaQueryWrapper<>();
+                            fansWrapper.eq(LifeFans::getFollowedId, followedId)
+                                    .eq(LifeFans::getFansId, fansId)
+                                    .eq(LifeFans::getDeleteFlag, 0);
+                            LifeFans lifeFans = lifeFansMapper.selectOne(fansWrapper);
+
+                            if (lifeFans != null) {
+                                isFollowed = 1; // 已关注
+                            } else {
+                                isFollowed = 0; // 未关注
+                            }
+                        } else {
+                            isFollowed = 0; // 无法获取用户手机号或店铺手机号,默认未关注
+                        }
+                    } catch (NumberFormatException e) {
+                        log.error("用户ID格式错误 - userId: {}, storeId: {}, error: {}",
+                                userLoginInfo.getUserId(), store.getId(), e.getMessage());
+                        isFollowed = 0; // 用户ID格式错误,默认未关注
+                    } catch (Exception e) {
+                        log.error("查询用户关注状态失败 - userId: {}, storeId: {}, error: {}",
+                                userLoginInfo.getUserId(), store.getId(), e.getMessage(), e);
+                        isFollowed = 0; // 查询失败,默认未关注
+                    }
+                } else {
+                    isFollowed = 0; // 用户未登录或不是普通用户,默认未关注
+                }
+                
+                vo.setIsFollowed(isFollowed);
                 voList.add(vo);
             }
             
@@ -1647,6 +1763,10 @@ public class StoreInfoController {
         @ApiModelProperty(value = "商户头像(来自store_user表的head_img字段)")
         @TableField(exist = false)
         private String headImg;
+        
+        @ApiModelProperty(value = "是否已关注(1-已关注,0-未关注)")
+        @TableField(exist = false)
+        private Integer isFollowed;
     }
 
 

+ 13 - 2
alien-store/src/main/java/shop/alien/store/controller/StoreOfficialAlbumController.java

@@ -34,8 +34,19 @@ public class StoreOfficialAlbumController {
     @PostMapping("/createOrUpdateOfficialAlbum")
     public R<StoreOfficialAlbum> createOrUpdateOfficialAlbum(@RequestBody StoreOfficialAlbum storeOfficialAlbum) {
         log.info("StoreOfficialAlbumController.createOfficialAlbum?storeOfficialAlbum={}", storeOfficialAlbum);
-        StoreOfficialAlbum storeOfficial = storeOfficialAlbumService.createOrUpdateOfficialAlbum(storeOfficialAlbum);
-        return R.data(storeOfficial);
+        try {
+            StoreOfficialAlbum storeOfficial = storeOfficialAlbumService.createOrUpdateOfficialAlbum(storeOfficialAlbum);
+            return R.data(storeOfficial);
+        } catch (IllegalArgumentException e) {
+            log.warn("创建/更新官方相册失败,参数错误:{}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (RuntimeException e) {
+            log.error("创建/更新官方相册失败,业务错误:{}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("创建/更新官方相册失败,系统异常:{}", e.getMessage(), e);
+            return R.fail("操作失败:" + e.getMessage());
+        }
     }
 
     @ApiOperation("获取官方相册列表")

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác