Эх сурвалжийг харах

Merge branch 'sit' into release_buried-point_ph

lutong 2 сар өмнө
parent
commit
7c82229cd7
100 өөрчлөгдсөн 8097 нэмэгдсэн , 437 устгасан
  1. 5 0
      alien-entity/src/main/java/shop/alien/entity/second/vo/SecondUserViolationDetailVo.java
  2. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/StoreClockIn.java
  3. 12 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreOperationalStatisticsHistory.java
  4. 125 0
      alien-entity/src/main/java/shop/alien/entity/store/StorePlatformMenu.java
  5. 82 0
      alien-entity/src/main/java/shop/alien/entity/store/StorePlatformRole.java
  6. 44 0
      alien-entity/src/main/java/shop/alien/entity/store/StorePlatformRoleMenu.java
  7. 58 0
      alien-entity/src/main/java/shop/alien/entity/store/StorePlatformUserRole.java
  8. 20 2
      alien-entity/src/main/java/shop/alien/entity/store/StoreUser.java
  9. 5 1
      alien-entity/src/main/java/shop/alien/entity/store/StoreVideo.java
  10. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackDto.java
  11. 56 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/deserializer/JsonStringDeserializer.java
  12. 16 1
      alien-entity/src/main/java/shop/alien/entity/store/excelVo/StoreUserExcelVo.java
  13. 1 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/OrderRevenueVO.java
  14. 25 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/PermissionItemVo.java
  15. 28 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/RolePermissionTableVo.java
  16. 45 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/RolePermissionVo.java
  17. 23 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StorePlatformMenuVo.java
  18. 33 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StorePlatformRoleVo.java
  19. 37 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreSubExcelVo.java
  20. 20 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java
  21. 39 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountDetailVo.java
  22. 52 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountStoreListVo.java
  23. 37 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountVo.java
  24. 82 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StorePlatformOperationLog.java
  25. 1 1
      alien-entity/src/main/java/shop/alien/mapper/StoreClockInMapper.java
  26. 32 0
      alien-entity/src/main/java/shop/alien/mapper/StorePlatformMenuMapper.java
  27. 15 0
      alien-entity/src/main/java/shop/alien/mapper/StorePlatformOperationLogMapper.java
  28. 21 0
      alien-entity/src/main/java/shop/alien/mapper/StorePlatformRoleMapper.java
  29. 17 0
      alien-entity/src/main/java/shop/alien/mapper/StorePlatformRoleMenuMapper.java
  30. 34 0
      alien-entity/src/main/java/shop/alien/mapper/StorePlatformUserRoleMapper.java
  31. 2 0
      alien-entity/src/main/java/shop/alien/mapper/StoreUserMapper.java
  32. 24 0
      alien-entity/src/main/java/shop/alien/mapper/SubAccountStoreMapper.java
  33. 41 0
      alien-entity/src/main/resources/mapper/StorePlatformMenuMapper.xml
  34. 36 0
      alien-entity/src/main/resources/mapper/StorePlatformUserRoleMapper.xml
  35. 6 0
      alien-entity/src/main/resources/mapper/StoreUserMapper.xml
  36. 65 0
      alien-entity/src/main/resources/mapper/SubAccountStoreMapper.xml
  37. 15 0
      alien-gateway/src/main/java/shop/alien/gateway/controller/StoreUserController.java
  38. 15 0
      alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformRoleGatewayMapper.java
  39. 26 0
      alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformRoleMenuGatewayMapper.java
  40. 15 0
      alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformUserRoleGatewayMapper.java
  41. 8 4
      alien-gateway/src/main/java/shop/alien/gateway/service/StoreUserService.java
  42. 167 1
      alien-gateway/src/main/java/shop/alien/gateway/service/impl/StoreUserServiceImpl.java
  43. 10 4
      alien-lawyer/src/main/java/shop/alien/lawyer/config/WebSocketProcess.java
  44. 4 0
      alien-store-platform/pom.xml
  45. 31 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/annotation/PlatformOperationLog.java
  46. 598 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/aspect/PlatformOperationLogAspect.java
  47. 72 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/PlatformOperationLogController.java
  48. 105 67
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformMenuController.java
  49. 254 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformRoleController.java
  50. 137 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformRoleMenuController.java
  51. 248 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformUserRoleController.java
  52. 28 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/dto/AssignRolesDto.java
  53. 25 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/dto/BatchDeleteSubAccountDto.java
  54. 29 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/dto/CreateAccountDto.java
  55. 46 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/dto/CreateRoleDto.java
  56. 29 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/dto/QuerySubAccountDto.java
  57. 32 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/dto/UpdateAccountDto.java
  58. 49 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/dto/UpdateRoleDto.java
  59. 40 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/enums/PlatformOperationModule.java
  60. 35 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/PlatformOperationLogService.java
  61. 49 36
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformMenuService.java
  62. 86 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformRoleMenuService.java
  63. 15 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformRoleQueryService.java
  64. 123 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformRoleService.java
  65. 123 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformUserRoleService.java
  66. 66 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/PlatformOperationLogServiceImpl.java
  67. 4 4
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreBusinessServiceImpl.java
  68. 162 230
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformMenuServiceImpl.java
  69. 319 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRoleMenuServiceImpl.java
  70. 26 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRoleQueryServiceImpl.java
  71. 410 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRoleServiceImpl.java
  72. 745 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformUserRoleServiceImpl.java
  73. 12 4
      alien-store/src/main/java/shop/alien/store/config/WebSocketProcess.java
  74. 4 4
      alien-store/src/main/java/shop/alien/store/controller/LifeCollectController.java
  75. 11 5
      alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java
  76. 27 0
      alien-store/src/main/java/shop/alien/store/controller/StoreOperationalStatisticsController.java
  77. 143 0
      alien-store/src/main/java/shop/alien/store/controller/StorePlatformMenuController.java
  78. 66 9
      alien-store/src/main/java/shop/alien/store/controller/StoreUserController.java
  79. 6 8
      alien-store/src/main/java/shop/alien/store/controller/UserViolationController.java
  80. 1 1
      alien-store/src/main/java/shop/alien/store/service/LifeUserViolationService.java
  81. 21 0
      alien-store/src/main/java/shop/alien/store/service/RoleMenuService.java
  82. 19 0
      alien-store/src/main/java/shop/alien/store/service/StoreOperationalStatisticsService.java
  83. 38 0
      alien-store/src/main/java/shop/alien/store/service/StorePlatformMenuService.java
  84. 8 2
      alien-store/src/main/java/shop/alien/store/service/StoreUserService.java
  85. 20 0
      alien-store/src/main/java/shop/alien/store/service/SubAccountStoreService.java
  86. 1 1
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java
  87. 8 4
      alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java
  88. 189 0
      alien-store/src/main/java/shop/alien/store/service/impl/RoleMenuServiceImpl.java
  89. 194 5
      alien-store/src/main/java/shop/alien/store/service/impl/StoreClockInServiceImpl.java
  90. 37 1
      alien-store/src/main/java/shop/alien/store/service/impl/StoreImgServiceImpl.java
  91. 4 7
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  92. 283 15
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalStatisticsServiceImpl.java
  93. 1001 0
      alien-store/src/main/java/shop/alien/store/service/impl/StorePlatformMenuServiceImpl.java
  94. 239 9
      alien-store/src/main/java/shop/alien/store/service/impl/StoreUserServiceImpl.java
  95. 334 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreVideoServiceImpl.java
  96. 37 0
      alien-store/src/main/java/shop/alien/store/service/impl/SubAccountStoreServiceImpl.java
  97. 55 1
      alien-util/src/main/java/shop/alien/util/common/VideoUtils.java
  98. 2 2
      alien-util/src/main/java/shop/alien/util/encryption/EncryptTypeHandler.java
  99. 7 1
      alien-util/src/main/java/shop/alien/util/encryption/StandardAesUtil.java
  100. 43 5
      alien-util/src/main/java/shop/alien/util/encryption/advice/DecryptRequestBodyAdvice.java

+ 5 - 0
alien-entity/src/main/java/shop/alien/entity/second/vo/SecondUserViolationDetailVo.java

@@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import shop.alien.entity.store.CommonComment;
 import shop.alien.entity.store.LifeUserDynamics;
 import shop.alien.entity.store.StoreComment;
 
@@ -42,4 +43,8 @@ public class SecondUserViolationDetailVo extends SecondUserViolationVo{
     @ApiModelProperty(value = "评论信息")
     StoreComment commentInfo;
 
+    // 评论信息
+    @ApiModelProperty(value = "评论信息")
+    CommonComment commonCommentInfo;
+
 }

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

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

+ 12 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreOperationalStatisticsHistory.java

@@ -62,6 +62,18 @@ public class StoreOperationalStatisticsHistory {
     @TableField("pdf_url")
     private String pdfUrl;
 
+    @ApiModelProperty(value = "AI分析是否完成, 0:未完成, 1:已完成")
+    @TableField("ai_analysis_completed")
+    private Integer aiAnalysisCompleted;
+
+    @ApiModelProperty(value = "总结")
+    @TableField("summary")
+    private String summary;
+
+    @ApiModelProperty(value = "优化建议")
+    @TableField("optimization_suggestions")
+    private String optimizationSuggestions;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

+ 125 - 0
alien-entity/src/main/java/shop/alien/entity/store/StorePlatformMenu.java

@@ -0,0 +1,125 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+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.Date;
+import java.util.List;
+
+/**
+ * 商家PC菜单权限表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@JsonIgnoreProperties(ignoreUnknown = true)
+@TableName("store_platform_menu")
+@ApiModel(value = "StorePlatformMenu对象", description = "商家PC菜单权限表")
+public class StorePlatformMenu implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "菜单ID")
+    @TableId(value = "menu_id", type = IdType.AUTO)
+    private Long menuId;
+
+    @ApiModelProperty(value = "菜单名称")
+    @TableField("menu_name")
+    private String menuName;
+
+    @ApiModelProperty(value = "父菜单ID(0表示根菜单)")
+    @TableField("parent_id")
+    private Long parentId;
+
+    @ApiModelProperty(value = "菜单类型(M目录 C菜单 F按钮)")
+    @TableField("menu_type")
+    private String menuType;
+
+    @ApiModelProperty(value = "显示顺序")
+    @TableField("menu_sort")
+    private Integer menuSort;
+
+    @ApiModelProperty(value = "菜单层级(1一级菜单 2二级菜单 3三级菜单)")
+    @TableField("level")
+    private Integer level;
+
+    @ApiModelProperty(value = "路由地址")
+    @TableField("path")
+    private String path;
+
+    @ApiModelProperty(value = "组件路径")
+    @TableField("component")
+    private String component;
+
+    @ApiModelProperty(value = "权限标识(如:sys:user:list)")
+    @TableField("perms")
+    private String perms;
+
+    @ApiModelProperty(value = "菜单图标")
+    @TableField("icon")
+    private String icon;
+
+    @ApiModelProperty(value = "菜单状态(0正常 1停用)")
+    @TableField("status")
+    private String status;
+
+    @ApiModelProperty(value = "是否显示(0显示 1隐藏)")
+    @TableField("visible")
+    private String visible;
+
+    @ApiModelProperty(value = "是否为外链(0是 1否)")
+    @TableField("is_frame")
+    private String isFrame;
+
+    @ApiModelProperty(value = "是否缓存(0缓存 1不缓存)")
+    @TableField("is_cache")
+    private String isCache;
+
+    @ApiModelProperty(value = "删除标志(0存在 2删除)")
+    @TableField("del_flag")
+    @TableLogic(value = "0", delval = "2")
+    private String delFlag;
+
+    @ApiModelProperty(value = "创建者")
+    @TableField("create_by")
+    private String createBy;
+
+    @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 = "更新者")
+    @TableField("update_by")
+    private String updateBy;
+
+    @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 = "备注")
+    @TableField("remark")
+    private String remark;
+
+    @ApiModelProperty(value = "子菜单列表(用于树形结构)")
+    @TableField(exist = false)
+    private List<StorePlatformMenu> children;
+
+    @ApiModelProperty(value = "一级分类名称(用于展示,只读)")
+    @TableField(exist = false)
+    private String firstLevelMenuName;
+
+    @ApiModelProperty(value = "二级分类名称(用于展示,只读)")
+    @TableField(exist = false)
+    private String secondLevelMenuName;
+}
+

+ 82 - 0
alien-entity/src/main/java/shop/alien/entity/store/StorePlatformRole.java

@@ -0,0 +1,82 @@
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 平台角色信息表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_platform_role")
+@ApiModel(value = "StorePlatformRole对象", description = "平台角色信息表")
+public class StorePlatformRole implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "角色ID")
+    @TableId(value = "role_id", type = IdType.AUTO)
+    private Long roleId;
+
+    @ApiModelProperty(value = "店铺ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "角色名称")
+    @TableField("role_name")
+    private String roleName;
+
+    @ApiModelProperty(value = "显示顺序")
+    @TableField("role_sort")
+    private Integer roleSort;
+
+    @ApiModelProperty(value = "角色状态(0正常 1停用)")
+    @TableField("status")
+    private String status;
+
+    @ApiModelProperty(value = "删除标志(0代表存在 2代表删除)")
+    @TableField("del_flag")
+    @TableLogic(value = "0", delval = "2")
+    private String delFlag;
+
+    @ApiModelProperty(value = "创建者")
+    @TableField("create_by")
+    private String createBy;
+
+    @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 = "更新者")
+    @TableField("update_by")
+    private String updateBy;
+
+    @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 = "备注")
+    @TableField("remark")
+    private String remark;
+
+    @ApiModelProperty(value = "描述")
+    @TableField("description")
+    private String description;
+
+    @ApiModelProperty(value = "英文角色名称")
+    @TableField("role_name_en")
+    private String roleNameEn;
+}
+

+ 44 - 0
alien-entity/src/main/java/shop/alien/entity/store/StorePlatformRoleMenu.java

@@ -0,0 +1,44 @@
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 角色菜单关联表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_platform_role_menu")
+@ApiModel(value = "StorePlatformRoleMenu对象", description = "角色菜单关联表")
+public class StorePlatformRoleMenu implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "角色ID")
+    @TableField("role_id")
+    private Long roleId;
+
+    @ApiModelProperty(value = "菜单ID")
+    @TableField("menu_id")
+    private Long menuId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+}
+

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

@@ -0,0 +1,58 @@
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户角色关联表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_platform_user_role")
+@ApiModel(value = "StorePlatformUserRole对象", description = "用户角色关联表")
+public class StorePlatformUserRole implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "用户ID(关联store_user.id)")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "角色ID(关联store_platform_role.role_id)")
+    @TableField("role_id")
+    private Long roleId;
+
+    @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("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "删除标志(0未删除 1已删除)")
+    @TableField("delete_flag")
+    @TableLogic(value = "0", delval = "1")
+    private Integer deleteFlag;
+
+
+    @ApiModelProperty(value = "删除标志(0未删除 1已删除)")
+    @TableField("account_name")
+    private String accountName;
+
+}

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

@@ -10,6 +10,7 @@ import lombok.Data;
 import shop.alien.util.encryption.EncryptTypeHandler;
 
 import java.util.Date;
+import java.util.List;
 
 /**
  * 二期-门店用户
@@ -31,7 +32,7 @@ public class StoreUser extends Model<StoreUser> {
         this.money = money;
     }
 
-    @ApiModelProperty(value = "主键")
+    @ApiModelProperty(value = "主键(主账号Id)")
     @TableId(value = "id", type = IdType.AUTO)
     private Integer id;
 
@@ -64,10 +65,11 @@ public class StoreUser extends Model<StoreUser> {
     @TableField("money")
     private Integer money;
 
-    @ApiModelProperty(value = "用户状态")
+    @ApiModelProperty(value = "用户状态,0:启用 1:禁用")
     @TableField("status")
     private Integer status;
 
+
     @ApiModelProperty(value = "密码状态, 0:初始密码, 1:已修改, 2:手机号已验证")
     @TableField("pass_type")
     private Integer passType;
@@ -136,4 +138,20 @@ public class StoreUser extends Model<StoreUser> {
     private String jianjie;
     @TableField(exist = false)
     private String moneyStr;
+    @ApiModelProperty(value = "账号类型,1:主账号,2:子账号")
+    @TableField("account_type")
+    private  Integer accountType;
+    @ApiModelProperty(value = "子账号Id")
+    @TableField("sub_account_id")
+    private Integer subAccountId;
+    @TableField(exist = false)
+    private Integer storeUserParentAccount;
+    @TableField(exist = false)
+    @ApiModelProperty(value = "子账号数量")
+    private Integer childAccountCount;
+    @ApiModelProperty(value = "子账号联系方式列表")
+    @TableField(exist = false)
+    private List<String> childPhoneNumbers;
+
+
 }

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

@@ -4,10 +4,13 @@ import com.baomidou.mybatisplus.annotation.*;
 import com.baomidou.mybatisplus.extension.activerecord.Model;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import shop.alien.entity.store.dto.deserializer.JsonStringDeserializer;
 
 import java.util.Date;
 
@@ -40,8 +43,9 @@ public class StoreVideo extends Model<StoreVideo> {
     @TableField("img_sort")
     private Integer imgSort;
 
-    @ApiModelProperty(value = "视频链接")
+    @ApiModelProperty(value = "视频链接和封面地址")
     @TableField("img_url")
+    @JsonDeserialize(using = JsonStringDeserializer.class)
     private String imgUrl;
 
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")

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

@@ -24,7 +24,7 @@ public class LifeFeedbackDto implements Serializable {
     @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
     private Integer feedbackWay;
 
-    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-功能反馈")
     private Integer feedbackType;
 
     @ApiModelProperty(value = "反馈内容")

+ 56 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/deserializer/JsonStringDeserializer.java

@@ -0,0 +1,56 @@
+package shop.alien.entity.store.dto.deserializer;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+
+/**
+ * 自定义反序列化器:将字符串或JSON数组/对象转换为字符串
+ * 支持以下格式:
+ * - 字符串:"xxx" 或 "[{\"video\":\"...\",\"cover\":\"...\"}]"
+ * - JSON数组:[{"video":"...","cover":"..."}]
+ * - JSON对象:{"video":"...","cover":"..."}
+ * - null:返回null
+ * 
+ * @author system
+ * @since 2025-01-16
+ */
+public class JsonStringDeserializer extends JsonDeserializer<String> {
+
+    @Override
+    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+        JsonNode node = p.getCodec().readTree(p);
+        
+        // 如果为null,返回null
+        if (node == null || node.isNull()) {
+            return null;
+        }
+        
+        // 如果是字符串,直接返回
+        if (node.isTextual()) {
+            return node.asText();
+        }
+        
+        // 如果是数组或对象,转换为JSON字符串
+        if (node.isArray() || node.isObject()) {
+            try {
+                // 使用ObjectMapper将JsonNode转换为JSON字符串
+                ObjectMapper mapper = (ObjectMapper) p.getCodec();
+                return mapper.writeValueAsString(node);
+            } catch (JsonProcessingException e) {
+                // 如果转换失败,使用toString()方法(注意:这可能不会生成标准的JSON格式)
+                // 但作为兜底方案,仍然返回
+                return node.toString();
+            }
+        }
+        
+        // 其他类型(数字、布尔值等),转换为字符串
+        return node.asText();
+    }
+}
+

+ 16 - 1
alien-entity/src/main/java/shop/alien/entity/store/excelVo/StoreUserExcelVo.java

@@ -12,7 +12,7 @@ import shop.alien.entity.store.excelVo.util.ExcelHeader;
  */
 @Data
 @JsonInclude
-@ApiModel(value = "StoreUser表Excel导出对象", description = "商家端账号信息")
+@ApiModel(value = "StoreUser表Excel导出对象", description = "商家端账号信息")
 public class StoreUserExcelVo {
 
     @ExcelHeader("序号")
@@ -23,6 +23,11 @@ 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 = "手机号码")
     private String phone;
@@ -31,7 +36,17 @@ public class StoreUserExcelVo {
     @ExcelHeader(value = "创建时间")
     private String createdTime;
 
+    @ApiModelProperty(value = "子账号数量")
+    @ExcelHeader(value = "子账号数量")
+    private Integer childAccountCount;
+
     @ApiModelProperty(value = "状态")
     @ExcelHeader(value = "状态")
     private String status;
+
+
+
+
+
+
 }

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

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

+ 25 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/PermissionItemVo.java

@@ -0,0 +1,25 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 权限项VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "PermissionItemVo对象", description = "权限项VO")
+public class PermissionItemVo {
+
+    @ApiModelProperty(value = "权限ID(菜单ID)")
+    private Long permissionId;
+
+    @ApiModelProperty(value = "权限名称(菜单名称)")
+    private String permissionName;
+}
+

+ 28 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/RolePermissionTableVo.java

@@ -0,0 +1,28 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 角色权限表格VO(用于表格形式展示权限层级)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "RolePermissionTableVo对象", description = "角色权限表格VO(用于表格形式展示权限层级)")
+public class RolePermissionTableVo {
+
+    @ApiModelProperty(value = "一级权限名称")
+    private String level1Permission;
+
+    @ApiModelProperty(value = "二级权限名称")
+    private String level2Permission;
+
+    @ApiModelProperty(value = "三级权限名称")
+    private String level3Permission;
+}
+

+ 45 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/RolePermissionVo.java

@@ -0,0 +1,45 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 角色权限VO(用于展示权限层级结构)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "RolePermissionVo对象", description = "角色权限VO(用于展示权限层级结构)")
+public class RolePermissionVo {
+
+    @ApiModelProperty(value = "菜单ID")
+    private Long menuId;
+
+    @ApiModelProperty(value = "菜单名称")
+    private String menuName;
+
+    @ApiModelProperty(value = "父菜单ID")
+    private Long parentId;
+
+    @ApiModelProperty(value = "菜单层级(1一级菜单 2二级菜单 3三级菜单)")
+    private Integer level;
+
+    @ApiModelProperty(value = "菜单类型(M目录 C菜单 F按钮)")
+    private String menuType;
+
+    @ApiModelProperty(value = "显示顺序")
+    private Integer menuSort;
+
+    @ApiModelProperty(value = "权限标识")
+    private String perms;
+
+    @ApiModelProperty(value = "子权限列表")
+    private List<RolePermissionVo> children;
+}
+

+ 23 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StorePlatformMenuVo.java

@@ -0,0 +1,23 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.StorePlatformMenu;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ *  菜单Vo
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StorePlatformMenuVo对象", description = "菜单Vo")
+public class StorePlatformMenuVo extends StorePlatformMenu implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "子菜单列表(用于树形结构)")
+    private List<StorePlatformMenu> children;
+}

+ 33 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StorePlatformRoleVo.java

@@ -0,0 +1,33 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import shop.alien.entity.store.StorePlatformRole;
+
+import java.util.List;
+
+/**
+ * 平台角色信息VO(包含关联统计)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@JsonInclude
+@ApiModel(value = "StorePlatformRoleVo对象", description = "平台角色信息VO(包含关联统计)")
+public class StorePlatformRoleVo extends StorePlatformRole {
+
+    @ApiModelProperty(value = "关联子账号数量")
+    private Integer subAccountCount;
+
+    @ApiModelProperty(value = "关联权限数量")
+    private Integer permissionCount;
+
+    @ApiModelProperty(value = "权限列表(树形结构)")
+    private List<RolePermissionVo> permissions;
+}
+

+ 37 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreSubExcelVo.java

@@ -0,0 +1,37 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+
+@Data
+@JsonInclude
+@ApiModel(value = "StoreUser表Excel导出对象", description = "商家端子账号信息")
+public class StoreSubExcelVo {
+
+    @ExcelHeader("序号")
+    @ApiModelProperty(value = "序号")
+    private Integer serialNumber;
+
+    @ApiModelProperty(value = "主键")
+    @ExcelHeader(value = "账号ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "主账号手机号")
+    @ExcelHeader(value = "主账号手机号码")
+    private String parentAccountPhone;
+
+    @ApiModelProperty(value = "手机号码")
+    @ExcelHeader(value = "手机号码")
+    private String phone;
+
+    @ApiModelProperty(value = "创建时间")
+    @ExcelHeader(value = "创建时间")
+    private String createdTime;
+
+    @ApiModelProperty(value = "状态")
+    @ExcelHeader(value = "状态")
+    private String status;
+}

+ 20 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java

@@ -7,6 +7,8 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import shop.alien.entity.store.StoreUser;
 
+import java.util.List;
+
 /**
  * 二期-门店用户扩展
  *
@@ -18,6 +20,8 @@ import shop.alien.entity.store.StoreUser;
 @JsonInclude
 @ApiModel(value = "StoreUserVo对象", description = "门店用户扩展")
 public class StoreUserVo extends StoreUser {
+     @ApiModelProperty(value = "父账号Id")
+    private Integer parentAccountId;
 
     @ApiModelProperty(value = "登录Token")
     private String token;
@@ -42,4 +46,20 @@ public class StoreUserVo extends StoreUser {
 
     @ApiModelProperty(value = "是否提供餐食")
     private Integer mealsFlag;
+
+    @ApiModelProperty(value = "子账号列表")
+    private List<StoreUser> childAccounts;
+
+    @ApiModelProperty(value = "主账号姓名")
+    private String parentAccountName;
+
+    @ApiModelProperty(value = "主账号手机号")
+    private String parentAccountPhone;
+
+    @ApiModelProperty(value = "角色ID(当前账号在目标门店的角色)")
+    private Long roleId;
+
+    @ApiModelProperty(value = "角色名称(当前账号在目标门店的角色名称)")
+    private String roleName;
+
 }

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountDetailVo.java

@@ -0,0 +1,39 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 子账号详情VO(用于编辑账号页面)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "SubAccountDetailVo对象", description = "子账号详情VO(用于编辑账号页面)")
+public class SubAccountDetailVo {
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "账号名称")
+    private String accountName;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+
+    @ApiModelProperty(value = "角色ID")
+    private Long roleId;
+
+    @ApiModelProperty(value = "角色名称")
+    private String roleName;
+
+    @ApiModelProperty(value = "权限列表(权限名称列表)")
+    private List<PermissionItemVo> permissions;
+}
+

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

@@ -0,0 +1,52 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 子账号关联门店列表 VO
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Data
+@JsonInclude(JsonInclude.Include.ALWAYS)
+@ApiModel(value = "SubAccountStoreListVo对象", description = "子账号关联门店列表")
+public class SubAccountStoreListVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "门店ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "门店名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "门店地址")
+    private String storeAddress;
+
+    @ApiModelProperty(value = "门店电话")
+    private String storeTel;
+
+    @ApiModelProperty(value = "营业状态(-1:注销中, 0:正常营业, 1:暂停营业, 2:筹建中, 99:永久关门)")
+    private Integer businessStatus;
+
+    @ApiModelProperty(value = "角色ID")
+    private Long roleId;
+
+    @ApiModelProperty(value = "角色名称")
+    private String roleName;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "账号名称")
+    private String accountName;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+}

+ 37 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/SubAccountVo.java

@@ -0,0 +1,37 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 子账号信息VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "SubAccountVo对象", description = "子账号信息VO")
+public class SubAccountVo {
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "账号名称")
+    private String accountName;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+
+    @ApiModelProperty(value = "角色ID")
+    private Long roleId;
+
+    @ApiModelProperty(value = "角色名称")
+    private String roleName;
+
+    @ApiModelProperty(value = "权限数量")
+    private Integer permissionCount;
+}
+

+ 82 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/StorePlatformOperationLog.java

@@ -0,0 +1,82 @@
+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.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 平台操作记录表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@TableName("store_platform_operation_log")
+@ApiModel(value = "StorePlatformOperationLog对象", description = "平台操作记录")
+public class StorePlatformOperationLog {
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "操作模块")
+    @TableField("operation_module")
+    private String operationModule;
+
+    @ApiModelProperty(value = "操作类型")
+    @TableField("operation_type")
+    private String operationType;
+
+    @ApiModelProperty(value = "操作内容")
+    @TableField("operation_content")
+    private String operationContent;
+
+    @ApiModelProperty(value = "操作入参(JSON)")
+    @TableField("operation_params")
+    private String operationParams;
+
+    @ApiModelProperty(value = "操作者ID")
+    @TableField("operator_id")
+    private String operatorId;
+
+    @ApiModelProperty(value = "操作者账号")
+    @TableField("operator_account")
+    private String operatorAccount;
+
+    @ApiModelProperty(value = "操作者姓名")
+    @TableField("operator_name")
+    private String operatorName;
+
+    @ApiModelProperty(value = "操作者店铺ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "用户类型")
+    @TableField("user_type")
+    private String userType;
+
+    @ApiModelProperty(value = "请求方法")
+    @TableField("request_method")
+    private String requestMethod;
+
+    @ApiModelProperty(value = "请求路径")
+    @TableField("request_path")
+    private String requestPath;
+
+    @ApiModelProperty(value = "IP地址")
+    @TableField("ip_address")
+    private String ipAddress;
+
+    @ApiModelProperty(value = "操作时间")
+    @TableField(value = "operation_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date operationTime;
+}

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

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

+ 32 - 0
alien-entity/src/main/java/shop/alien/mapper/StorePlatformMenuMapper.java

@@ -0,0 +1,32 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.store.StorePlatformMenu;
+import shop.alien.entity.store.vo.StorePlatformMenuVo;
+
+import java.util.List;
+
+/**
+ * 商家PC菜单权限表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StorePlatformMenuMapper extends BaseMapper<StorePlatformMenu> {
+
+    IPage<StorePlatformMenuVo> selectList(IPage<StorePlatformMenuVo> store, LambdaQueryWrapper<StorePlatformMenuVo> queryWrapper);
+
+    /**
+     * 根据角色ID查询权限菜单列表
+     *
+     * @param roleId 角色ID
+     * @return 菜单列表
+     */
+    List<StorePlatformMenu> selectMenusByRoleId(@Param("roleId") Long roleId);
+}
+

+ 15 - 0
alien-entity/src/main/java/shop/alien/mapper/StorePlatformOperationLogMapper.java

@@ -0,0 +1,15 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.storePlatform.StorePlatformOperationLog;
+
+/**
+ * 平台操作记录 Mapper接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StorePlatformOperationLogMapper extends BaseMapper<StorePlatformOperationLog> {
+}

+ 21 - 0
alien-entity/src/main/java/shop/alien/mapper/StorePlatformRoleMapper.java

@@ -0,0 +1,21 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import shop.alien.entity.store.StorePlatformRole;
+
+/**
+ * 平台角色信息表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StorePlatformRoleMapper extends BaseMapper<StorePlatformRole> {
+
+    @Select("SELECT role_name FROM store_platform_role WHERE role_id = #{roleId} LIMIT 1")
+    String selectRoleNameById(@Param("roleId") Long roleId);
+}
+

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

@@ -0,0 +1,17 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StorePlatformRoleMenu;
+
+/**
+ * 角色菜单关联表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StorePlatformRoleMenuMapper extends BaseMapper<StorePlatformRoleMenu> {
+
+}
+

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

@@ -0,0 +1,34 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.StorePlatformUserRole;
+import shop.alien.entity.store.vo.SubAccountVo;
+
+import java.util.List;
+
+/**
+ * 用户角色关联表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StorePlatformUserRoleMapper extends BaseMapper<StorePlatformUserRole> {
+
+    /**
+     * 根据店铺ID查询子账号列表(连表查询)
+     *
+     * @param storeId    店铺ID
+     * @param accountName 账号名称(模糊查询)
+     * @param phone       手机号(模糊查询)
+     * @param roleName    角色名称(模糊查询)
+     * @return 子账号列表
+     */
+    List<SubAccountVo> querySubAccounts(@Param("storeId") Integer storeId,
+                                        @Param("accountName") String accountName,
+                                        @Param("phone") String phone,
+                                        @Param("roleName") String roleName);
+}
+

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

@@ -34,4 +34,6 @@ public interface StoreUserMapper extends BaseMapper<StoreUser> {
     List<Map<String,Object>> selectStore(@Param("userPhones") String userPhones);
 
     StoreUser getRemoveUser(@Param("id") String id);
+
+    StoreUser getUserIncludeDeleted(@Param("id") Integer id);
 }

+ 24 - 0
alien-entity/src/main/java/shop/alien/mapper/SubAccountStoreMapper.java

@@ -0,0 +1,24 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.vo.SubAccountStoreListVo;
+
+import java.util.List;
+
+/**
+ * 子账号关联门店 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Mapper
+public interface SubAccountStoreMapper extends BaseMapper<Object> {
+    /**
+     * 根据用户ID查询子账号关联的门店列表
+     * @param userId 用户ID(store_user.id)
+     * @return 门店列表
+     */
+    List<SubAccountStoreListVo> selectSubAccountStoreListByUserId(@Param("userId") Integer userId);
+}

+ 41 - 0
alien-entity/src/main/resources/mapper/StorePlatformMenuMapper.xml

@@ -0,0 +1,41 @@
+<?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.StorePlatformMenuMapper">
+
+    <!-- 根据角色ID查询权限菜单列表 -->
+    <select id="selectMenusByRoleId" resultType="shop.alien.entity.store.StorePlatformMenu">
+        SELECT
+            m.menu_id AS menuId,
+            m.menu_name AS menuName,
+            m.parent_id AS parentId,
+            m.menu_type AS menuType,
+            m.menu_sort AS menuSort,
+            m.level AS level,
+            m.path AS path,
+            m.component AS component,
+            m.perms AS perms,
+            m.icon AS icon,
+            m.status AS status,
+            m.visible AS visible,
+            m.is_frame AS isFrame,
+            m.is_cache AS isCache,
+            m.del_flag AS delFlag,
+            m.create_by AS createBy,
+            m.created_time AS createdTime,
+            m.update_by AS updateBy,
+            m.updated_time AS updatedTime,
+            m.remark AS remark
+        FROM
+            store_platform_menu m
+        INNER JOIN store_platform_role_menu rm ON m.menu_id = rm.menu_id
+        WHERE
+            rm.role_id = #{roleId}
+            AND m.del_flag = '0'
+            AND m.status = '0'
+        ORDER BY
+            m.menu_sort ASC
+    </select>
+
+</mapper>

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

@@ -0,0 +1,36 @@
+<?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.StorePlatformUserRoleMapper">
+
+    <!-- 根据店铺ID查询子账号列表(连表查询) -->
+    <select id="querySubAccounts" resultType="shop.alien.entity.store.vo.SubAccountVo">
+        SELECT
+            sur.id AS userId,
+            spur.account_name AS accountName,
+            sur.phone AS phone,
+            spr.role_id AS roleId,
+            spr.role_name AS roleName,
+            spur.store_id AS storeId,
+            0 AS permissionCount
+        FROM store_platform_user_role spur
+        INNER JOIN store_user sur ON spur.user_id = sur.id
+        LEFT JOIN store_platform_role spr ON spur.role_id = spr.role_id
+        WHERE spur.store_id = #{storeId}
+          AND sur.delete_flag = 0
+          AND spur.delete_flag = 0
+        <if test="accountName != null and accountName != ''">
+            AND (spur.account_name LIKE CONCAT('%', #{accountName}, '%') )
+        </if>
+        <if test="phone != null and phone != ''">
+            AND sur.phone LIKE CONCAT('%', #{phone}, '%')
+        </if>
+        <if test="roleName != null and roleName != ''">
+            AND spr.role_name LIKE CONCAT('%', #{roleName}, '%')
+        </if>
+        ORDER BY spur.created_time DESC
+    </select>
+
+</mapper>
+

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

@@ -10,4 +10,10 @@
         FROM store_user
         where id = #{id}
     </select>
+
+    <select id="getUserIncludeDeleted" resultType="shop.alien.entity.store.StoreUser">
+        select id, phone, name, nick_name
+        FROM store_user
+        where id = #{id}
+    </select>
 </mapper>

+ 65 - 0
alien-entity/src/main/resources/mapper/SubAccountStoreMapper.xml

@@ -0,0 +1,65 @@
+<?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.SubAccountStoreMapper">
+
+    <!-- 根据用户ID查询子账号关联的门店列表(包含主账号的门店) -->
+    <select id="selectSubAccountStoreListByUserId" resultType="shop.alien.entity.store.vo.SubAccountStoreListVo">
+        -- 查询子账号通过store_platform_user_role关联的门店
+        SELECT
+            si.id AS storeId,
+            si.store_name COLLATE utf8mb4_unicode_ci AS storeName,
+            si.store_address COLLATE utf8mb4_unicode_ci AS storeAddress,
+            si.store_tel COLLATE utf8mb4_unicode_ci AS storeTel,
+            si.business_status AS businessStatus,
+            spur.role_id AS roleId,
+            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
+        FROM
+            store_platform_user_role spur
+        INNER JOIN store_user su ON spur.user_id = su.id
+        INNER JOIN store_info si ON spur.store_id = si.id
+        LEFT JOIN store_platform_role spr ON spur.role_id = spr.role_id 
+            AND (spr.del_flag = '0' OR spr.del_flag IS NULL)
+        WHERE
+            spur.user_id = #{userId}
+            AND spur.delete_flag = 0
+            AND su.delete_flag = 0
+            AND si.delete_flag = 0
+        
+        UNION
+        
+        -- 查询主账号的门店(通过store_user.store_id关联)
+        SELECT
+            si_main.id AS storeId,
+            si_main.store_name COLLATE utf8mb4_unicode_ci AS storeName,
+            si_main.store_address COLLATE utf8mb4_unicode_ci AS storeAddress,
+            si_main.store_tel COLLATE utf8mb4_unicode_ci AS storeTel,
+            si_main.business_status AS businessStatus,
+            NULL AS roleId,
+            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
+        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))
+                OR
+                -- 如果传入的是主账号,查询自己的门店
+                (su_main.id = #{userId} AND su_main.account_type = 1)
+            )
+            AND su_main.delete_flag = 0
+            AND si_main.delete_flag = 0
+            AND su_main.store_id IS NOT NULL
+        
+        ORDER BY storeId DESC
+    </select>
+
+</mapper>

+ 15 - 0
alien-gateway/src/main/java/shop/alien/gateway/controller/StoreUserController.java

@@ -87,4 +87,19 @@ public class StoreUserController {
                 : R.fail("密码错误");
     }
 
+    /**
+     * 切换子账号门店(类似登录接口,无需密码和验证码)
+     */
+    @ApiOperation(value = "切换子账号门店", notes = "切换子账号当前操作的门店,相当于无密码无验证码的登录接口,返回用户信息和token,包含角色id")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID(store_user.id)", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "目标门店ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/switchSubAccountStore")
+    public R<StoreUserVo> switchSubAccountStore(@RequestParam("userId") Integer userId, @RequestParam("storeId") Integer storeId) {
+        log.info("StoreUserController.switchSubAccountStore?userId={}, storeId={}", userId, storeId);
+        return storeUserService.switchSubAccountStore(userId, storeId);
+    }
+
 }

+ 15 - 0
alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformRoleGatewayMapper.java

@@ -0,0 +1,15 @@
+package shop.alien.gateway.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StorePlatformRole;
+
+/**
+ * 平台角色信息 Mapper 接口(Gateway)
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Mapper
+public interface StorePlatformRoleGatewayMapper extends BaseMapper<StorePlatformRole> {
+}

+ 26 - 0
alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformRoleMenuGatewayMapper.java

@@ -0,0 +1,26 @@
+package shop.alien.gateway.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import shop.alien.entity.store.StorePlatformRoleMenu;
+
+/**
+ * 角色菜单关联表 Mapper 接口(Gateway)
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Mapper
+public interface StorePlatformRoleMenuGatewayMapper extends BaseMapper<StorePlatformRoleMenu> {
+    
+    /**
+     * 根据角色ID查询菜单权限数量
+     *
+     * @param roleId 角色ID
+     * @return 菜单权限数量
+     */
+    @Select("SELECT COUNT(*) FROM store_platform_role_menu WHERE role_id = #{roleId}")
+    Integer countMenuByRoleId(@Param("roleId") Long roleId);
+}

+ 15 - 0
alien-gateway/src/main/java/shop/alien/gateway/mapper/StorePlatformUserRoleGatewayMapper.java

@@ -0,0 +1,15 @@
+package shop.alien.gateway.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StorePlatformUserRole;
+
+/**
+ * 用户角色关联表 Mapper 接口(Gateway)
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Mapper
+public interface StorePlatformUserRoleGatewayMapper extends BaseMapper<StorePlatformUserRole> {
+}

+ 8 - 4
alien-gateway/src/main/java/shop/alien/gateway/service/StoreUserService.java

@@ -1,14 +1,10 @@
 package shop.alien.gateway.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.StoreUser;
 import shop.alien.entity.store.vo.StoreUserVo;
 
-import java.io.IOException;
-import java.util.List;
-
 /**
  * 二期-门店用户 服务类
  *
@@ -23,4 +19,12 @@ public interface StoreUserService extends IService<StoreUser> {
      */
     R<StoreUserVo> createToKen(StoreUser storeUser);
 
+    /**
+     * 切换子账号门店(类似登录接口,无需密码和验证码)
+     * @param userId 用户ID(store_user.id)
+     * @param storeId 目标门店ID
+     * @return 用户信息和token
+     */
+    R<StoreUserVo> switchSubAccountStore(Integer userId, Integer storeId);
+
 }

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

@@ -1,23 +1,31 @@
 package shop.alien.gateway.service.impl;
 
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.store.StorePlatformRole;
+import shop.alien.entity.store.StorePlatformUserRole;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreUserVo;
 import shop.alien.gateway.config.BaseRedisService;
 import shop.alien.gateway.mapper.StoreInfoGatewayMapper;
+import shop.alien.gateway.mapper.StorePlatformRoleGatewayMapper;
+import shop.alien.gateway.mapper.StorePlatformRoleMenuGatewayMapper;
+import shop.alien.gateway.mapper.StorePlatformUserRoleGatewayMapper;
 import shop.alien.gateway.mapper.StoreUserGatewayMapper;
 import shop.alien.gateway.service.StoreUserService;
 import shop.alien.util.common.JwtUtil;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -26,6 +34,7 @@ import java.util.Map;
  * @author ssk
  * @since 2024-12-11
  */
+@Slf4j
 @Transactional
 @Service
 @RequiredArgsConstructor
@@ -42,6 +51,12 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
 
     private final BaseRedisService baseRedisService;
 
+    private final StorePlatformUserRoleGatewayMapper storePlatformUserRoleMapper;
+    
+    private final StorePlatformRoleMenuGatewayMapper storePlatformRoleMenuMapper;
+    
+    private final StorePlatformRoleGatewayMapper storePlatformRoleMapper;
+
     /**
      * token
      *
@@ -81,7 +96,67 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
         tokenMap.put("userType", "store");
         storeUserVo.setToken(JwtUtil.createJWT("store_" + storeUser.getPhone(), storeUser.getName(), JSONObject.toJSONString(tokenMap), effectiveTimeIntLong));
         baseRedisService.setString("store_" + storeUser.getPhone(), storeUserVo.getToken());
-        StoreInfo storeInfo = storeInfoMapper.selectById(storeUser.getStoreId());
+        if(storeUserVo.getStoreId() == null){
+            // 子账号登录,查询当前子账号权限最多的那个门店和角色id
+            log.info("子账号登录,storeId为空,查询权限最多的门店 - userId: {}", storeUser.getId());
+            
+            // 查询子账号关联的所有门店和角色
+            LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
+            roleWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId())
+                    .eq(StorePlatformUserRole::getDeleteFlag, 0);
+            List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
+            
+            if (userRoles != null && !userRoles.isEmpty()) {
+                // 计算每个角色的菜单权限数量,选择权限最多的门店
+                StorePlatformUserRole maxPermissionRole = null;
+                int maxPermissionCount = -1;
+                
+                for (StorePlatformUserRole userRole : userRoles) {
+                    if (userRole.getRoleId() != null) {
+                        // 查询该角色的菜单权限数量
+                        Integer permissionCount = storePlatformRoleMenuMapper.countMenuByRoleId(userRole.getRoleId());
+                        if (permissionCount == null) {
+                            permissionCount = 0;
+                        }
+                        
+                        // 如果权限数量更多,或者权限数量相同但角色ID更大,则更新
+                        if (permissionCount > maxPermissionCount || 
+                            (permissionCount == maxPermissionCount && 
+                             (maxPermissionRole == null || userRole.getRoleId() > maxPermissionRole.getRoleId()))) {
+                            maxPermissionCount = permissionCount;
+                            maxPermissionRole = userRole;
+                        }
+                    }
+                }
+                
+                if (maxPermissionRole != null) {
+                    // 设置门店ID和角色ID
+                    storeUserVo.setStoreId(maxPermissionRole.getStoreId());
+                    storeUserVo.setRoleId(maxPermissionRole.getRoleId());
+                    
+                    // 查询角色名称
+                    if (maxPermissionRole.getRoleId() != null) {
+                        StorePlatformRole role = storePlatformRoleMapper.selectById(maxPermissionRole.getRoleId());
+                        if (role != null) {
+                            storeUserVo.setRoleName(role.getRoleName());
+                        }
+                    }
+                    
+                    log.info("子账号权限最多的门店查询成功 - userId: {}, storeId: {}, roleId: {}, roleName: {}, 权限数量: {}", 
+                            storeUser.getId(), maxPermissionRole.getStoreId(), maxPermissionRole.getRoleId(), 
+                            storeUserVo.getRoleName(), maxPermissionCount);
+                } else {
+                    log.warn("子账号关联的门店都没有角色ID - userId: {}", storeUser.getId());
+                }
+            } else {
+                log.warn("子账号未关联任何门店 - userId: {}", storeUser.getId());
+            }
+        }
+        // 查询门店信息(如果storeId不为空)
+        StoreInfo storeInfo = null;
+        if (storeUserVo.getStoreId() != null) {
+            storeInfo = storeInfoMapper.selectById(storeUserVo.getStoreId());
+        }
         if (storeInfo != null) {
             storeUserVo.setBusinessSection(storeInfo.getBusinessSection());
             storeUserVo.setBusinessTypesName(storeInfo.getBusinessTypesName());
@@ -89,4 +164,95 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
         return R.data(storeUserVo);
     }
 
+    @Override
+    public R<StoreUserVo> switchSubAccountStore(Integer userId, Integer storeId) {
+        log.info("StoreUserServiceImpl.switchSubAccountStore?userId={}, storeId={}", userId, storeId);
+
+        if (userId == null || storeId == null) {
+            log.warn("用户ID或门店ID为空 - userId: {}, storeId: {}", userId, storeId);
+            return R.fail("用户ID或门店ID不能为空");
+        }
+
+        // 1. 查询用户信息(类似登录接口,验证用户是否存在)
+        StoreUser storeUser = this.getById(userId);
+        if (storeUser == null) {
+            log.warn("用户不存在 - userId: {}", userId);
+            return R.fail("当前账号不存在,请先去注册账号!");
+        }
+
+        // 2. 验证账号状态
+        if (storeUser.getStatus() == 1) {
+            log.warn("账号被禁用 - userId: {}", userId);
+            return R.fail("账号被禁用");
+        }
+
+        // 3. 判断是主账号还是子账号,并验证门店关联
+        Long roleId = null;
+        Integer finalStoreId = null; // 最终要回显的门店ID
+        StoreInfo storeInfo = new StoreInfo();
+        if (storeUser.getStoreId() != null && storeUser.getStoreId().equals(storeId)) {
+            // 主账号:直接回显 store_user 的门店 id
+//            if (storeUser.getStoreId() == null || !storeUser.getStoreId().equals(storeId)) {
+//                log.warn("主账号门店ID不匹配 - userId: {}, 传入storeId: {}, 实际storeId: {}",
+//                        userId, storeId, storeUser.getStoreId());
+//                return R.fail("切换失败,门店ID不匹配");
+//            }
+            finalStoreId = storeUser.getStoreId();
+            roleId = null; // 主账号没有角色id
+        } else {
+            // 子账号:查询角色关联表,回显子账号关联的门店 id
+            LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
+            roleWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                    .eq(StorePlatformUserRole::getStoreId, storeId)
+                    .eq(StorePlatformUserRole::getDeleteFlag, 0);
+            StorePlatformUserRole userRole = storePlatformUserRoleMapper.selectOne(roleWrapper);
+            
+            if (userRole == null) {
+                log.warn("子账号未关联目标门店 - userId: {}, storeId: {}", userId, storeId);
+                return R.fail("切换失败,请确认子账号是否关联了该门店");
+            }
+            
+            finalStoreId = userRole.getStoreId(); // 从关联表获取门店ID
+            storeInfo = storeInfoMapper.selectById(finalStoreId);
+            roleId = userRole.getRoleId() != null ? userRole.getRoleId() : null;
+        }
+
+        // 4. 删除旧的token(参考switchingStates逻辑)
+        baseRedisService.delete("store_" + storeUser.getPhone());
+        baseRedisService.delete("storePlatform_" + storeUser.getPhone());
+        log.info("删除用户token - phone: {}", storeUser.getPhone());
+
+        // 5. 创建临时用户对象用于生成token(设置正确的门店ID)
+        StoreUser tempUser = new StoreUser();
+        BeanUtils.copyProperties(storeUser, tempUser);
+        tempUser.setStoreId(finalStoreId);
+
+        // 6. 生成token(类似登录接口)
+        R<StoreUserVo> tokenResult = createToKen(tempUser);
+        if (tokenResult.getCode() != 200 || tokenResult.getData() == null) {
+            log.error("生成token失败 - userId: {}", userId);
+            return R.fail("切换失败,token生成失败");
+        }
+
+        // 7. 设置角色ID和门店ID(如果没有角色id,该字段为null)
+        StoreUserVo storeUserVo = tokenResult.getData();
+        storeUserVo.setRoleId(roleId);
+        storeUserVo.setStoreId(finalStoreId); // 确保回显正确的门店ID
+        
+        // 查询并设置角色名称
+        if (roleId != null) {
+            StorePlatformRole role = storePlatformRoleMapper.selectById(roleId);
+            if (role != null) {
+                storeUserVo.setRoleName(role.getRoleName());
+            }
+        }
+        
+        if(storeInfo != null){
+            storeUserVo.setName(storeInfo.getStoreName());
+        }
+
+        log.info("切换门店成功 - userId: {}, storeId: {}, roleId: {}", userId, finalStoreId, roleId);
+        return R.data(storeUserVo);
+    }
+
 }

+ 10 - 4
alien-lawyer/src/main/java/shop/alien/lawyer/config/WebSocketProcess.java

@@ -157,6 +157,8 @@ public class WebSocketProcess implements ApplicationContextAware {
         try {
             //每新建立一个连接,就把当前客户id为key,this为value存储到map中
             this.session = session;
+            // 设置 5 分钟没有任何消息往来则自动关闭连接,防止长连接堆积
+            this.session.setMaxIdleTimeout(5 * 60 * 1000);
             concurrentHashMap.put(id, this);
             log.info("WebSocketProcess.onOpen() Open a websocket. id={}", id);
 
@@ -177,10 +179,12 @@ public class WebSocketProcess implements ApplicationContextAware {
     @OnClose
     public void onClose(Session session, @PathParam("sendId") String id) {
         try {
-            //客户端连接关闭时,移除map中存储的键值对
-            concurrentHashMap.remove(id);
-            log.info("WebSocketProcess.onClose() close a websocket, concurrentHashMap remove sessionId= {}", id);
-            if (baseRedisService.hasKey("blackList_" + id)) baseRedisService.delete("blackList_" + id);
+            //客户端连接关闭时,安全移除map中存储的键值对
+            boolean removed = concurrentHashMap.remove(id, this);
+            if (removed) {
+                log.info("WebSocketProcess.onClose() close a websocket, concurrentHashMap remove sessionId= {}", id);
+                if (baseRedisService.hasKey("blackList_" + id)) baseRedisService.delete("blackList_" + id);
+            }
         } catch (Exception e) {
             log.error("WebSocketProcess.onClose()----Exception----Message={}", e.getMessage());
         }
@@ -193,6 +197,8 @@ public class WebSocketProcess implements ApplicationContextAware {
     public void onError(@PathParam("sendId") String id, Throwable error) {
         try {
             log.error("WebSocketProcess.onError() Error,id={}, Msg=", id, error);
+            // 发生错误时主动移除,防止僵尸连接
+            concurrentHashMap.remove(id, this);
         } catch (Exception e) {
             log.error("WebSocketProcess.onError()----Exception----Message={}", e.getMessage());
         }

+ 4 - 0
alien-store-platform/pom.xml

@@ -56,6 +56,10 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-websocket</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.elasticsearch.client</groupId>

+ 31 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/annotation/PlatformOperationLog.java

@@ -0,0 +1,31 @@
+package shop.alien.storeplatform.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 平台操作记录注解
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface PlatformOperationLog {
+
+    /**
+     * 操作模块
+     */
+    String module();
+
+    /**
+     * 操作类型
+     */
+    String type();
+
+    /**
+     * 操作内容(支持SpEL表达式)
+     * 示例:"新增角色 #{#roleName}(店铺ID=#{#storeId})"
+     */
+    String content() default "";
+}

+ 598 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/aspect/PlatformOperationLogAspect.java

@@ -0,0 +1,598 @@
+package shop.alien.storeplatform.aspect;
+
+import com.alibaba.fastjson.JSON;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.annotation.Order;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.common.TemplateParserContext;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StorePlatformMenu;
+import shop.alien.entity.store.StorePlatformRole;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.storePlatform.StorePlatformOperationLog;
+import shop.alien.mapper.StorePlatformMenuMapper;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.storeplatform.annotation.PlatformOperationLog;
+import shop.alien.storeplatform.dto.CreateAccountDto;
+import shop.alien.storeplatform.dto.CreateRoleDto;
+import shop.alien.storeplatform.dto.UpdateRoleDto;
+import shop.alien.storeplatform.service.PlatformOperationLogService;
+import com.alibaba.fastjson.JSONObject;
+import shop.alien.storeplatform.service.StorePlatformRoleMenuService;
+import shop.alien.storeplatform.service.StorePlatformRoleQueryService;
+import shop.alien.storeplatform.util.LoginUserUtil;
+import shop.alien.storeplatform.dto.UpdateAccountDto;
+import shop.alien.entity.store.StorePlatformUserRole;
+import shop.alien.mapper.StorePlatformUserRoleMapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 平台操作记录切面。
+ * <p>
+ * 通过 {@link PlatformOperationLog} 注解拦截业务方法,记录操作内容、入参、操作者与请求信息。
+ * </p>
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Aspect
+@Component
+@Order(10)
+@RequiredArgsConstructor
+public class PlatformOperationLogAspect {
+
+    private final PlatformOperationLogService platformOperationLogService;
+    private final StorePlatformRoleMenuService storePlatformRoleMenuService;
+    private final StorePlatformMenuMapper storePlatformMenuMapper;
+    private final StoreUserMapper storeUserMapper;
+    private final StorePlatformUserRoleMapper storePlatformUserRoleMapper;
+    private final StorePlatformRoleQueryService storePlatformRoleQueryService;
+    private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
+    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
+    private final TemplateParserContext templateParserContext = new TemplateParserContext("#{", "}");
+
+    /**
+     * 操作记录切点:拦截带 {@link PlatformOperationLog} 注解的方法。
+     */
+    @Pointcut("@annotation(shop.alien.storeplatform.annotation.PlatformOperationLog)")
+    public void operationLogPointcut() {
+    }
+
+    /**
+     * 环绕通知:先执行业务逻辑,再生成并保存操作日志。
+     *
+     * @param joinPoint 连接点
+     * @return 业务方法返回值
+     * @throws Throwable 业务方法执行异常
+     */
+    @Around("operationLogPointcut()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        PlatformOperationLog annotation = method.getAnnotation(PlatformOperationLog.class);
+        // 变更前快照:用于“修改子账号”差异日志
+        SubAccountSnapshot beforeSnapshot = buildSubAccountSnapshot(annotation, joinPoint.getArgs());
+
+        Object result;
+        try {
+            result = joinPoint.proceed();
+        } catch (Exception e) {
+            throw e;
+        }
+
+        if (annotation != null && shouldRecord(result)) {
+            try {
+                StorePlatformOperationLog logRecord = buildLogRecord(joinPoint, annotation, result, beforeSnapshot);
+                platformOperationLogService.save(logRecord);
+            } catch (Exception e) {
+                log.warn("平台操作记录失败: {}", e.getMessage());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 判断是否需要记录操作日志。
+     * <p>
+     * 返回值为 {@link R} 时,只有成功结果才记录。
+     * </p>
+     *
+     * @param result 业务方法返回值
+     * @return 是否记录日志
+     */
+    private boolean shouldRecord(Object result) {
+        if (result instanceof R) {
+            return R.isSuccess((R<?>) result);
+        }
+        return true;
+    }
+
+    /**
+     * 构建操作日志实体。
+     *
+     * @param joinPoint     连接点
+     * @param annotation    注解信息
+     * @param result        业务方法返回值
+     * @param beforeSnapshot 子账号变更前快照
+     * @return 操作日志实体
+     */
+    private StorePlatformOperationLog buildLogRecord(ProceedingJoinPoint joinPoint, PlatformOperationLog annotation, Object result,
+                                                     SubAccountSnapshot beforeSnapshot) {
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        Object[] args = joinPoint.getArgs();
+        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
+
+        // SpEL 上下文:支持 #p0/#arg0/参数名/返回值
+        EvaluationContext context = new StandardEvaluationContext();
+        if (args != null) {
+            for (int i = 0; i < args.length; i++) {
+                context.setVariable("p" + i, args[i]);
+                context.setVariable("arg" + i, args[i]);
+                if (parameterNames != null && parameterNames.length > i) {
+                    context.setVariable(parameterNames[i], args[i]);
+                }
+            }
+        }
+        context.setVariable("result", result);
+
+        StorePlatformOperationLog logRecord = new StorePlatformOperationLog();
+        logRecord.setOperationModule(annotation.module());
+        logRecord.setOperationType(annotation.type());
+        logRecord.setOperationTime(new Date());
+
+        String content = resolveContent(annotation, context, args, beforeSnapshot);
+        logRecord.setOperationContent(content);
+        logRecord.setOperationParams(buildParamsJson(parameterNames, args));
+
+        fillOperatorInfo(logRecord);
+        fillRequestInfo(logRecord);
+        return logRecord;
+    }
+
+    /**
+     * 填充操作者信息。
+     *
+     * @param logRecord 操作日志实体
+     */
+    private void fillOperatorInfo(StorePlatformOperationLog logRecord) {
+        try {
+            JSONObject userInfo = LoginUserUtil.getCurrentUserInfo();
+            if (userInfo != null) {
+                logRecord.setOperatorId(userInfo.getString("userId"));
+                String account = userInfo.getString("phone");
+                if (!StringUtils.hasText(account)) {
+                    account = userInfo.getString("account");
+                }
+                logRecord.setOperatorAccount(account);
+                String name = userInfo.getString("userName");
+                if (!StringUtils.hasText(name)) {
+                    name = userInfo.getString("name");
+                }
+                logRecord.setOperatorName(name);
+                Integer storeId = LoginUserUtil.getCurrentStoreId();
+                if (storeId != null) {
+                    logRecord.setStoreId(storeId);
+                }
+                String userType = userInfo.getString("userType");
+                logRecord.setUserType(StringUtils.hasText(userType) ? userType : "platform");
+            }
+        } catch (Exception e) {
+            log.debug("填充操作人信息失败", e);
+        }
+    }
+
+    /**
+     * 填充请求信息。
+     *
+     * @param logRecord 操作日志实体
+     */
+    private void fillRequestInfo(StorePlatformOperationLog logRecord) {
+        try {
+            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            if (attributes == null) {
+                return;
+            }
+            HttpServletRequest request = attributes.getRequest();
+            // 记录请求方法/路径/IP
+            logRecord.setRequestMethod(request.getMethod());
+            logRecord.setRequestPath(request.getRequestURI());
+            logRecord.setIpAddress(getIpAddress(request));
+        } catch (Exception e) {
+            log.debug("填充请求信息失败", e);
+        }
+    }
+
+    /**
+     * 获取请求 IP 地址,优先从代理头读取。
+     *
+     * @param request HttpServletRequest
+     * @return IP 地址
+     */
+    private String getIpAddress(HttpServletRequest request) {
+        String ip = request.getHeader("X-Forwarded-For");
+        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_CLIENT_IP");
+        }
+        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+        }
+        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        return ip;
+    }
+
+    /**
+     * 解析日志内容。
+     * <p>
+     * 优先处理账号相关自定义内容,其次为角色统一模板,最后回退到注解 SpEL。
+     * </p>
+     *
+     * @param annotation    注解信息
+     * @param context       SpEL 上下文
+     * @param args          方法参数
+     * @param beforeSnapshot 子账号变更前快照
+     * @return 日志内容
+     */
+    private String resolveContent(PlatformOperationLog annotation, EvaluationContext context, Object[] args,
+                                  SubAccountSnapshot beforeSnapshot) {
+        // 先处理自定义的账号相关日志模板
+        if ("账号操作记录".equals(annotation.module()) && annotation.type().contains("移除角色")) {
+            String content = buildAccountRoleRemoveContent(args);
+            if (StringUtils.hasText(content)) {
+                return content;
+            }
+        }
+        if ("账号操作记录".equals(annotation.module()) && annotation.type().contains("新增子账号")) {
+            String content = buildCreateSubAccountContent(args);
+            if (StringUtils.hasText(content)) {
+                return content;
+            }
+        }
+        if ("账号操作记录".equals(annotation.module()) && annotation.type().contains("修改子账号")) {
+            String content = buildUpdateSubAccountContent(args, beforeSnapshot);
+            if (StringUtils.hasText(content)) {
+                return content;
+            }
+        }
+        // 角色相关日志的统一格式
+        if ("角色操作记录".equals(annotation.module())) {
+            return buildRoleOperationContent(annotation.type(), args);
+        }
+        String content = annotation.content();
+        if (StringUtils.hasText(content)) {
+            Expression exp = spelExpressionParser.parseExpression(content, templateParserContext);
+            String parsed = exp.getValue(context, String.class);
+            return parsed != null ? parsed : annotation.type();
+        }
+        return annotation.type();
+    }
+
+    /**
+     * 构建子账号变更前快照,仅用于“修改子账号”日志差异对比。
+     *
+     * @param annotation 注解信息
+     * @param args       方法参数
+     * @return 变更前快照
+     */
+    private SubAccountSnapshot buildSubAccountSnapshot(PlatformOperationLog annotation, Object[] args) {
+        if (annotation == null || args == null || args.length == 0) {
+            return null;
+        }
+        if (!"账号操作记录".equals(annotation.module()) || !annotation.type().contains("修改子账号")) {
+            return null;
+        }
+        if (!(args[0] instanceof UpdateAccountDto)) {
+            return null;
+        }
+        UpdateAccountDto dto = (UpdateAccountDto) args[0];
+        if (dto.getUserId() == null || dto.getStoreId() == null) {
+            return null;
+        }
+        StoreUser storeUser = storeUserMapper.selectById(dto.getUserId());
+        String phone = storeUser != null ? storeUser.getPhone() : null;
+        String accountName = null;
+        Long roleId = null;
+
+        LambdaQueryWrapper<StorePlatformUserRole> userRoleQuery = new LambdaQueryWrapper<>();
+        userRoleQuery.eq(StorePlatformUserRole::getUserId, dto.getUserId())
+                .eq(StorePlatformUserRole::getStoreId, dto.getStoreId())
+                .eq(StorePlatformUserRole::getDeleteFlag, 0)
+                .last("LIMIT 1");
+        StorePlatformUserRole userRole = storePlatformUserRoleMapper.selectOne(userRoleQuery);
+        if (userRole != null) {
+            accountName = userRole.getAccountName();
+            roleId = userRole.getRoleId();
+        }
+
+        if (!StringUtils.hasText(accountName) && storeUser != null) {
+            accountName = StringUtils.hasText(storeUser.getName())
+                    ? storeUser.getName()
+                    : (StringUtils.hasText(storeUser.getNickName()) ? storeUser.getNickName() : storeUser.getPhone());
+        }
+        return new SubAccountSnapshot(phone, accountName, roleId);
+    }
+
+    /**
+     * 构建“修改子账号”日志内容,仅记录变更字段。
+     *
+     * @param args           方法参数
+     * @param beforeSnapshot 变更前快照
+     * @return 日志内容
+     */
+    private String buildUpdateSubAccountContent(Object[] args, SubAccountSnapshot beforeSnapshot) {
+        if (beforeSnapshot == null || args == null || args.length == 0) {
+            return null;
+        }
+        if (!(args[0] instanceof UpdateAccountDto)) {
+            return null;
+        }
+        UpdateAccountDto dto = (UpdateAccountDto) args[0];
+        StringBuilder changed = new StringBuilder();
+
+        if (StringUtils.hasText(dto.getPhone()) && !dto.getPhone().equals(beforeSnapshot.phone)) {
+            appendChange(changed, "手机号", beforeSnapshot.phone, dto.getPhone());
+        }
+        if (StringUtils.hasText(dto.getAccountName()) && !dto.getAccountName().equals(beforeSnapshot.accountName)) {
+            appendChange(changed, "账号名", beforeSnapshot.accountName, dto.getAccountName());
+        }
+        if (dto.getRoleId() != null && !dto.getRoleId().equals(beforeSnapshot.roleId)) {
+            String oldRoleName = resolveRoleName(beforeSnapshot.roleId);
+            String newRoleName = resolveRoleName(dto.getRoleId());
+            appendChange(changed, "角色", oldRoleName, newRoleName);
+        }
+
+        if (changed.length() == 0) {
+            return "修改子账号(无变更)";
+        }
+        return "修改子账号," + changed;
+    }
+
+    /**
+     * 构建“新增子账号”日志内容。
+     *
+     * @param args 方法参数
+     * @return 日志内容
+     */
+    private String buildCreateSubAccountContent(Object[] args) {
+        if (args == null || args.length == 0) {
+            return null;
+        }
+        if (!(args[0] instanceof CreateAccountDto)) {
+            return null;
+        }
+        CreateAccountDto dto = (CreateAccountDto) args[0];
+        StringBuilder content = new StringBuilder();
+        content.append("新增子账号");
+        if (StringUtils.hasText(dto.getPhone())) {
+            content.append(",手机号:").append(dto.getPhone());
+        }
+        if (StringUtils.hasText(dto.getAccountName())) {
+            content.append(",账号名:").append(dto.getAccountName());
+        }
+        if (dto.getRoleId() != null) {
+            String roleName = resolveRoleName(dto.getRoleId());
+            content.append(",角色:").append(roleName);
+        }
+        return content.toString();
+    }
+
+    /**
+     * 追加字段变更描述。
+     *
+     * @param builder  内容构建器
+     * @param label    字段中文名
+     * @param oldValue 旧值
+     * @param newValue 新值
+     */
+    private void appendChange(StringBuilder builder, String label, String oldValue, String newValue) {
+        if (builder.length() > 0) {
+            builder.append(";");
+        }
+        builder.append(label)
+                .append(":")
+                .append(StringUtils.hasText(oldValue) ? oldValue : "空")
+                .append("修改为")
+                .append(StringUtils.hasText(newValue) ? newValue : "空");
+    }
+
+    /**
+     * 子账号变更前快照。
+     */
+    private static class SubAccountSnapshot {
+        private final String phone;
+        private final String accountName;
+        private final Long roleId;
+
+        private SubAccountSnapshot(String phone, String accountName, Long roleId) {
+            this.phone = phone;
+            this.accountName = accountName;
+            this.roleId = roleId;
+        }
+    }
+
+    /**
+     * 构建“移除角色”日志内容。
+     *
+     * @param args 方法参数
+     * @return 日志内容
+     */
+    private String buildAccountRoleRemoveContent(Object[] args) {
+        if (args == null || args.length < 2) {
+            return null;
+        }
+        Integer userId = null;
+        Long roleId = null;
+        if (args[0] instanceof Integer) {
+            userId = (Integer) args[0];
+        }
+        if (args[1] instanceof Long) {
+            roleId = (Long) args[1];
+        }
+        if (userId == null || roleId == null) {
+            return null;
+        }
+
+        String accountName = resolveAccountName(userId);
+        String roleName = resolveRoleName(roleId);
+        return String.format("移除用户%s的%s角色", accountName, roleName);
+    }
+
+    /**
+     * 解析账号名称,允许读取已删除用户。
+     *
+     * @param userId 用户ID
+     * @return 账号名称
+     */
+    private String resolveAccountName(Integer userId) {
+        StoreUser storeUser = storeUserMapper.getUserIncludeDeleted(userId);
+        if (storeUser == null) {
+            return "未知";
+        }
+        String name = storeUser.getName();
+        if (!StringUtils.hasText(name)) {
+            name = storeUser.getNickName();
+        }
+        if (!StringUtils.hasText(name)) {
+            name = storeUser.getPhone();
+        }
+        return StringUtils.hasText(name) ? name : "未知";
+    }
+
+    /**
+     * 解析角色名称。
+     *
+     * @param roleId 角色ID
+     * @return 角色名称
+     */
+    private String resolveRoleName(Long roleId) {
+        String roleName = storePlatformRoleQueryService.getRoleNameById(roleId);
+        if (!StringUtils.hasText(roleName)) {
+            return "未知";
+        }
+        return roleName;
+    }
+
+    /**
+     * 构建操作入参 JSON 字符串。
+     *
+     * @param parameterNames 参数名列表
+     * @param args           参数值列表
+     * @return JSON 字符串
+     */
+    private String buildParamsJson(String[] parameterNames, Object[] args) {
+        try {
+            if (args == null || args.length == 0) {
+                return null;
+            }
+            if (parameterNames == null || parameterNames.length == 0) {
+                return JSON.toJSONString(args);
+            }
+            java.util.Map<String, Object> params = new java.util.LinkedHashMap<>();
+            for (int i = 0; i < args.length; i++) {
+                String name = parameterNames.length > i ? parameterNames[i] : "arg" + i;
+                params.put(name, args[i]);
+            }
+            return JSON.toJSONString(params);
+        } catch (Exception e) {
+            log.debug("序列化操作入参失败", e);
+            return null;
+        }
+    }
+
+    private String buildRoleOperationContent(String operationType, Object[] args) {
+        String roleName = null;
+        List<Long> menuIds = Collections.emptyList();
+        Long roleId = null;
+
+        if (args != null && args.length > 0) {
+            Object firstArg = args[0];
+            if (firstArg instanceof StorePlatformRole) {
+                StorePlatformRole role = (StorePlatformRole) firstArg;
+                roleName = role.getRoleName();
+            } else if (firstArg instanceof CreateRoleDto) {
+                CreateRoleDto dto = (CreateRoleDto) firstArg;
+                roleName = dto.getRoleName();
+                menuIds = dto.getMenuIds() != null ? dto.getMenuIds() : Collections.emptyList();
+            } else if (firstArg instanceof UpdateRoleDto) {
+                UpdateRoleDto dto = (UpdateRoleDto) firstArg;
+                roleName = dto.getRoleName();
+                roleId = dto.getRoleId();
+                menuIds = dto.getMenuIds() != null ? dto.getMenuIds() : Collections.emptyList();
+            } else if (firstArg instanceof Long) {
+                roleId = (Long) firstArg;
+            }
+        }
+
+        if ((roleName == null || roleName.trim().isEmpty()) && roleId != null) {
+             roleName = resolveRoleName(roleId);
+        }
+
+        if ((menuIds == null || menuIds.isEmpty()) && roleId != null) {
+            menuIds = storePlatformRoleMenuService.getMenuIdsByRoleId(roleId);
+        }
+
+        List<String> menuNames = resolveMenuNames(menuIds);
+        int count = menuNames.size();
+        String menuSummary = menuNames.isEmpty() ? "无权限" : menuNames.get(0);
+        String roleLabel = StringUtils.hasText(roleName) ? roleName : "未知";
+        String type = normalizeRoleOperationType(operationType);
+        return String.format("%s%s角色标签,包含%s等%d个权限", type, roleLabel, menuSummary, count);
+    }
+
+    private List<String> resolveMenuNames(List<Long> menuIds) {
+        if (menuIds == null || menuIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return storePlatformMenuMapper.selectBatchIds(menuIds).stream()
+                .map(StorePlatformMenu::getMenuName)
+                .filter(StringUtils::hasText)
+                .collect(Collectors.toList());
+    }
+
+    private String normalizeRoleOperationType(String operationType) {
+        if (operationType == null) {
+            return "操作";
+        }
+        if (operationType.contains("新增")) {
+            return "新增";
+        }
+        if (operationType.contains("修改")) {
+            return "修改";
+        }
+        if (operationType.contains("删除")) {
+            return "删除";
+        }
+        return operationType;
+    }
+}

+ 72 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/PlatformOperationLogController.java

@@ -0,0 +1,72 @@
+package shop.alien.storeplatform.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiSort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import shop.alien.entity.result.R;
+import shop.alien.entity.storePlatform.StorePlatformOperationLog;
+import shop.alien.storeplatform.enums.PlatformOperationModule;
+import shop.alien.storeplatform.service.PlatformOperationLogService;
+
+import java.util.Date;
+
+/**
+ * 平台操作记录查询接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"平台-操作记录"})
+@ApiSort(99)
+@RestController
+@RequestMapping("/platform/operationLog")
+@RequiredArgsConstructor
+public class PlatformOperationLogController {
+
+    private final PlatformOperationLogService platformOperationLogService;
+
+    @ApiOperation("分页查询操作记录")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "页容", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "module", value = "操作模块(ACCOUNT/ROLE)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "account", value = "操作账号(模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "startTime", value = "开始时间", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endTime", value = "结束时间", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/page")
+    public R<IPage<StorePlatformOperationLog>> getPage(
+            @RequestParam(name = "page", defaultValue = "1") int page,
+            @RequestParam(name = "size", defaultValue = "10") int size,
+            @RequestParam(name = "module", required = false) String module,
+            @RequestParam(name = "account", required = false) String account,
+            @RequestParam(name = "startTime", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
+            @RequestParam(name = "endTime", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime) {
+        String moduleName = resolveModuleName(module);
+        log.info("PlatformOperationLogController.getPage?page={}, size={}, module={}, account={}, startTime={}, endTime={}",
+                page, size, moduleName, account, startTime, endTime);
+        return R.data(platformOperationLogService.getOperationLogPage(page, size, moduleName, account, startTime, endTime));
+    }
+
+    private String resolveModuleName(String module) {
+        if (!StringUtils.hasText(module)) {
+            return null;
+        }
+        PlatformOperationModule operationModule = PlatformOperationModule.fromCode(module);
+        return operationModule != null ? operationModule.getModuleName() : null;
+    }
+}

+ 105 - 67
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformMenuController.java

@@ -1,119 +1,157 @@
 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.StoreMenu;
-import shop.alien.entity.store.vo.StoreMenuVo;
+import shop.alien.entity.store.StorePlatformMenu;
 import shop.alien.storeplatform.service.StorePlatformMenuService;
 
 import java.util.List;
 
 /**
- * 二期-门店菜单Controller
+ * 商家PC菜单权限表 前端控制器
  *
- * @author ssk
- * @since 2024-12-05
+ * @author system
+ * @since 2025-01-XX
  */
 @Slf4j
-@Api(tags = {"商户平台-门店菜单"})
-@ApiSort(4)
+@Api(tags = {"平台-菜单管理"})
+@ApiSort(1)
 @CrossOrigin
 @RestController
-@RequestMapping("/menu")
+@RequestMapping("/platform/menu")
 @RequiredArgsConstructor
 public class StorePlatformMenuController {
 
-    private final StorePlatformMenuService storeMenuService;
+    private final StorePlatformMenuService storePlatformMenuService;
 
-    @ApiOperation("获取门店菜单")
+    @ApiOperation("分页查询菜单列表")
     @ApiOperationSupport(order = 1)
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "dishType", value = "菜品类型, 0:菜单, 1:推荐", dataType = "Integer", paramType = "query"),
-            @ApiImplicitParam(name = "phoneId", value = "消息标识", dataType = "Integer", paramType = "query")
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "页容", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "menuName", value = "菜单名称(支持模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "menuType", value = "菜单类型(M目录 C菜单 F按钮)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "菜单状态(0正常 1停用)", dataType = "String", paramType = "query")
     })
-    @GetMapping("/getMenuByStoreId")
-    public R<List<StoreMenuVo>> getMenuByStoreId(Integer storeId, Integer dishType, String phoneId) {
-        log.info("StoreRecommendController.getByStoreId?id={}&dishType={}&phoneId={}", storeId, dishType, phoneId);
-        return R.data(storeMenuService.getStoreMenu(storeId, dishType, phoneId));
+    @GetMapping("/getMenuPage")
+    public R<IPage<StorePlatformMenu>> getMenuPage(
+            @RequestParam(name = "page", defaultValue = "1") int page,
+            @RequestParam(name = "size", defaultValue = "10") int size,
+            @RequestParam(value = "menuName", required = false) String menuName,
+            @RequestParam(value = "menuType", required = false) String menuType,
+            @RequestParam(value = "status", required = false) String status) {
+        log.info("StorePlatformMenuController.getMenuPage?page={}, size={}, menuName={}, menuType={}, status={}", 
+                page, size, menuName, menuType, status);
+        IPage<StorePlatformMenu> menuPage = storePlatformMenuService.getMenuPage(page, size, menuName, menuType, status);
+        return R.data(menuPage);
     }
 
-    @ApiOperation("新增或修改门店菜单")
+    @ApiOperation("根据ID查询菜单详情")
     @ApiOperationSupport(order = 2)
-    @PostMapping("/saveOrUpdate")
-    public R<String> saveOrUpdate(@RequestBody StoreMenuVo storeMenuVo) {
-        log.info("StoreRecommendController.saveOrUpdateMenu?storeMenu={}", storeMenuVo);
-        return storeMenuService.saveOrUpdateMenus(storeMenuVo);
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @GetMapping("/getMenuById")
+    public R<StorePlatformMenu> getMenuById(@RequestParam("menuId") Long menuId) {
+        log.info("StorePlatformMenuController.getMenuById?menuId={}", menuId);
+        StorePlatformMenu menu = storePlatformMenuService.getMenuById(menuId);
+        if (menu == null) {
+            return R.fail("未找到该菜单信息");
+        }
+        return R.data(menu);
     }
 
-    @ApiOperation(value = "删除门店菜单")
+    @ApiOperation("新增菜单")
     @ApiOperationSupport(order = 3)
-    @GetMapping("/delete")
-    public R<String> delete(@RequestParam(value = "ids") List<Integer> ids, @RequestParam(value = "dishType") int dishType) {
-        log.info("StoreRecommendController.delete?ids={}", ids);
-        return storeMenuService.deleteMenu(ids, dishType);
+    @PostMapping("/saveMenu")
+    public R<String> saveMenu(@RequestBody StorePlatformMenu menu) {
+        log.info("StorePlatformMenuController.saveMenu?menu={}", menu);
+        boolean result = storePlatformMenuService.saveMenu(menu);
+        if (result) {
+            return R.success("新增成功");
+        }
+        return R.fail("新增失败");
     }
 
-    @ApiOperation("获取菜品详情")
+    @ApiOperation("修改菜单")
     @ApiOperationSupport(order = 4)
-    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "菜品id", dataType = "Integer", paramType = "query")})
-    @GetMapping("/getMenuInfo")
-    public R<StoreMenuVo> getMenuInfo(Integer id) {
-        log.info("StoreRecommendController.getMenuInfo?id={}", id);
-        return R.data(storeMenuService.getMenuInfo(id));
+    @PostMapping("/updateMenu")
+    public R<String> updateMenu(@RequestBody StorePlatformMenu menu) {
+        log.info("StorePlatformMenuController.updateMenu?menu={}", menu);
+        boolean result = storePlatformMenuService.updateMenu(menu);
+        if (result) {
+            return R.success("修改成功");
+        }
+        return R.fail("修改失败");
     }
 
-    @ApiOperation("菜品排序")
+    @ApiOperation("删除菜单")
     @ApiOperationSupport(order = 5)
-    @PostMapping("/getSortMenuInfo")
-    public R<Boolean> getSortMenuInfo(@RequestBody StoreMenuVo storeMenuVo) {
-        log.info("StoreRecommendController.getSortMenuInfo?storeMenuVo={}", storeMenuVo);
-        Boolean flag = false;
-        flag = storeMenuService.getSortMenuInfo(storeMenuVo);
-        if (flag) {
-            return R.success("已更新排序");
-        } else {
-            return R.fail("排序失败");
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @DeleteMapping("/deleteMenu")
+    public R<String> deleteMenu(@RequestParam("menuId") Long menuId) {
+        log.info("StorePlatformMenuController.deleteMenu?menuId={}", menuId);
+        boolean result = storePlatformMenuService.deleteMenu(menuId);
+        if (result) {
+            return R.success("删除成功");
         }
+        return R.fail("删除失败");
     }
 
-    @ApiOperation("保存菜品排序")
+    @ApiOperation("修改菜单状态")
     @ApiOperationSupport(order = 6)
-    @PostMapping("/saveMenuSort")
-    public R<Boolean> saveMenuSort(@RequestBody List<StoreMenu> storeMenuList) {
-        log.info("StoreRecommendController.saveMenuSort?storeMenuList={}", storeMenuList);
-        Boolean flag = false;
-        flag = storeMenuService.saveMenuSort(storeMenuList);
-        if (flag) {
-            return R.success("已更新排序");
-        } else {
-            return R.fail("排序失败");
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "status", value = "菜单状态(0正常 1停用)", dataType = "String", paramType = "query", required = true)
+    })
+    @PutMapping("/updateStatus")
+    public R<String> updateStatus(
+            @RequestParam("menuId") Long menuId,
+            @RequestParam("status") String status) {
+        log.info("StorePlatformMenuController.updateStatus?menuId={}, status={}", menuId, status);
+        boolean result = storePlatformMenuService.updateStatus(menuId, status);
+        if (result) {
+            return R.success("操作成功");
         }
+        return R.fail("操作失败");
     }
 
-    @ApiOperation("获取门店菜品推荐数量")
+    @ApiOperation("查询所有正常状态的菜单列表(树形结构)")
     @ApiOperationSupport(order = 7)
+    @GetMapping("/getMenuTree")
+    public R<List<StorePlatformMenu>> getMenuTree() {
+        log.info("StorePlatformMenuController.getMenuTree");
+        List<StorePlatformMenu> menus = storePlatformMenuService.getMenuTree();
+        return R.data(menus);
+    }
+
+    @ApiOperation("根据父菜单ID查询子菜单列表")
+    @ApiOperationSupport(order = 8)
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true)
+            @ApiImplicitParam(name = "parentId", value = "父菜单ID", dataType = "Long", paramType = "query", required = true)
     })
-    @GetMapping("/getMenuCountByStoreId")
-    public R<StoreMenuVo> getMenuCountByStoreId(Integer storeId) {
-        log.info("StoreRecommendController.getMenuCountByStoreId?id={}", storeId);
-        return R.data(storeMenuService.getMenuCountByStoreId(storeId));
+    @GetMapping("/getMenusByParentId")
+    public R<List<StorePlatformMenu>> getMenusByParentId(@RequestParam("parentId") Long parentId) {
+        log.info("StorePlatformMenuController.getMenusByParentId?parentId={}", parentId);
+        List<StorePlatformMenu> menus = storePlatformMenuService.getMenusByParentId(parentId);
+        return R.data(menus);
     }
 
-    @ApiOperation("获取用户对该菜品是否点赞")
-    @ApiOperationSupport(order = 8)
+    @ApiOperation("根据层级查询菜单列表")
+    @ApiOperationSupport(order = 9)
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "menuId", value = "菜品id", dataType = "Integer", paramType = "query", required = true)
+            @ApiImplicitParam(name = "level", value = "菜单层级(1一级菜单 2二级菜单 3三级菜单)", dataType = "int", paramType = "query", required = true)
     })
-    @GetMapping("/getMenuLikeStatus")
-    public R<Boolean> getMenuLikeStatus(String userId, Integer menuId) {
-        log.info("StoreRecommendController.getMenuLikeStatus?userId={}&menuId={}", userId, menuId);
-        return R.data(storeMenuService.getMenuLikeStatus(userId, menuId));
+    @GetMapping("/getMenusByLevel")
+    public R<List<StorePlatformMenu>> getMenusByLevel(@RequestParam("level") Integer level) {
+        log.info("StorePlatformMenuController.getMenusByLevel?level={}", level);
+        List<StorePlatformMenu> menus = storePlatformMenuService.getMenusByLevel(level);
+        return R.data(menus);
     }
 }

+ 254 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformRoleController.java

@@ -0,0 +1,254 @@
+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.StorePlatformRole;
+import shop.alien.entity.store.vo.RolePermissionTableVo;
+import shop.alien.entity.store.vo.StorePlatformRoleVo;
+import shop.alien.storeplatform.annotation.PlatformOperationLog;
+import shop.alien.storeplatform.dto.CreateRoleDto;
+import shop.alien.storeplatform.dto.UpdateRoleDto;
+import shop.alien.storeplatform.service.StorePlatformRoleMenuService;
+import shop.alien.storeplatform.service.StorePlatformRoleService;
+
+import java.util.List;
+
+/**
+ * 平台角色信息表 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"平台-角色管理"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/platform/role")
+@RequiredArgsConstructor
+public class StorePlatformRoleController {
+
+    private final StorePlatformRoleService storePlatformRoleService;
+    private final StorePlatformRoleMenuService storePlatformRoleMenuService;
+
+    @ApiOperation("分页查询角色列表(包含关联统计)")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "页容", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = false),
+            @ApiImplicitParam(name = "roleName", value = "角色名称(支持模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "description", value = "角色描述(支持模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "角色状态(0正常 1停用)", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getRolePage")
+    public R<IPage<StorePlatformRoleVo>> getRolePage(
+            @RequestParam(name = "page", defaultValue = "1") int page,
+            @RequestParam(name = "size", defaultValue = "10") int size,
+            @RequestParam(value = "storeId", required = true) Integer storeId,
+            @RequestParam(value = "roleName", required = false) String roleName,
+            @RequestParam(value = "description", required = false) String description,
+            @RequestParam(value = "status", required = false) String status) {
+        log.info("StorePlatformRoleController.getRolePage?page={}, size={}, storeId={}, roleName={}, description={}, status={}", 
+                page, size, storeId, roleName, description, status);
+        IPage<StorePlatformRoleVo> rolePage = storePlatformRoleService.getRolePageWithStats(page, size, storeId, roleName, description, status);
+        return R.data(rolePage);
+    }
+
+    @ApiOperation("根据ID查询角色详情")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @GetMapping("/getRoleById")
+    public R<StorePlatformRole> getRoleById(@RequestParam("roleId") Long roleId) {
+        log.info("StorePlatformRoleController.getRoleById?roleId={}", roleId);
+        StorePlatformRole role = storePlatformRoleService.getRoleById(roleId);
+        if (role == null) {
+            return R.fail("未找到该角色信息");
+        }
+        return R.data(role);
+    }
+
+    @ApiOperation("新增角色")
+    @ApiOperationSupport(order = 3)
+    @PlatformOperationLog(
+            module = "角色操作记录",
+            type = "新增角色",
+            content = "新增角色 #{#p0.roleName}(店铺ID=#{#p0.storeId})"
+    )
+    @PostMapping("/saveRole")
+    public R<String> saveRole(@RequestBody StorePlatformRole role) {
+        log.info("StorePlatformRoleController.saveRole?role={}", role);
+        // 检查角色名称是否已存在(同一店铺内不能重复)
+        if (storePlatformRoleService.checkRoleNameExists(role.getStoreId(), role.getRoleName(), null)) {
+            return R.fail("已有名称不允许重复添加");
+        }
+        boolean result = storePlatformRoleService.saveRole(role);
+        if (result) {
+            return R.success("新增成功");
+        }
+        return R.fail("新增失败");
+    }
+
+    @ApiOperation("创建角色(包含权限分配)")
+    @ApiOperationSupport(order = 3)
+    @PlatformOperationLog(
+            module = "角色操作记录",
+            type = "新增角色",
+            content = "新增角色 #{#p0.roleName}(店铺ID=#{#p0.storeId},权限数=#{#p0.menuIds != null ? #p0.menuIds.size() : 0})"
+    )
+    @PostMapping("/createRole")
+    public R<String> createRole(@RequestBody CreateRoleDto createRoleDto) {
+        log.info("StorePlatformRoleController.createRole?storeId={}, roleName={}, menuIds={}", 
+                createRoleDto.getStoreId(), createRoleDto.getRoleName(), createRoleDto.getMenuIds());
+        
+        // 检查角色名称是否已存在(同一店铺内不能重复)
+        if (storePlatformRoleService.checkRoleNameExists(createRoleDto.getStoreId(), createRoleDto.getRoleName(), null)) {
+            return R.fail("已有名称不允许重复添加");
+        }
+        
+        // 构建角色对象
+        StorePlatformRole role = new StorePlatformRole();
+        role.setStoreId(createRoleDto.getStoreId());
+        role.setRoleName(createRoleDto.getRoleName());
+        role.setRoleSort(createRoleDto.getRoleSort());
+        role.setStatus(createRoleDto.getStatus());
+        role.setDescription(createRoleDto.getDescription());
+        role.setRoleNameEn(createRoleDto.getRoleNameEn());
+        role.setRemark(createRoleDto.getRemark());
+        
+        // 创建角色并分配权限
+        boolean result = storePlatformRoleService.createRoleWithMenus(role, createRoleDto.getMenuIds());
+        if (result) {
+            return R.success("创建成功");
+        }
+        return R.fail("创建失败");
+    }
+
+    @ApiOperation("修改角色")
+    @ApiOperationSupport(order = 4)
+    @PlatformOperationLog(
+            module = "角色操作记录",
+            type = "修改角色",
+            content = "修改角色ID=#{#p0.roleId},名称=#{#p0.roleName}(店铺ID=#{#p0.storeId})"
+    )
+    @PostMapping("/updateRole")
+    public R<String> updateRole(@RequestBody UpdateRoleDto updateRoleDto) {
+        log.info("StorePlatformRoleController.updateRole?roleId={}, roleName={}, menuIds={}", 
+                updateRoleDto.getRoleId(), updateRoleDto.getRoleName(), updateRoleDto.getMenuIds());
+        
+        // 校验必填参数
+        if (updateRoleDto.getRoleId() == null) {
+            return R.fail("角色ID不能为空");
+        }
+        if (updateRoleDto.getStoreId() == null) {
+            return R.fail("店铺ID不能为空");
+        }
+        
+        // 构建角色对象
+        StorePlatformRole role = new StorePlatformRole();
+        role.setRoleId(updateRoleDto.getRoleId());
+        role.setStoreId(updateRoleDto.getStoreId());
+        role.setRoleName(updateRoleDto.getRoleName());
+        role.setRoleSort(updateRoleDto.getRoleSort());
+        role.setStatus(updateRoleDto.getStatus());
+        role.setDescription(updateRoleDto.getDescription());
+        role.setRoleNameEn(updateRoleDto.getRoleNameEn());
+        role.setRemark(updateRoleDto.getRemark());
+        
+        // 更新角色和权限
+        boolean result = storePlatformRoleService.updateRoleWithMenus(role, updateRoleDto.getMenuIds());
+        if (result) {
+            return R.success("修改成功");
+        }
+        return R.fail("修改失败");
+    }
+
+    @ApiOperation("删除角色")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @PlatformOperationLog(
+            module = "角色操作记录",
+            type = "删除角色",
+            content = "删除角色ID=#{#roleId}"
+    )
+    @DeleteMapping("/deleteRole")
+    public R<String> deleteRole(@RequestParam("roleId") Long roleId) {
+        log.info("StorePlatformRoleController.deleteRole?roleId={}", roleId);
+        boolean result = storePlatformRoleService.deleteRole(roleId);
+        if (result) {
+            return R.success("删除成功");
+        }
+        return R.fail("删除失败");
+    }
+
+    @ApiOperation("逻辑删除角色")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @PlatformOperationLog(
+            module = "角色操作记录",
+            type = "删除角色",
+            content = "删除角色ID=#{#roleId}"
+    )
+    @DeleteMapping("/deleteRoleWithCheck")
+    public R<String> deleteRoleWithCheck(@RequestParam("roleId") Long roleId) {
+        log.info("StorePlatformRoleController.deleteRoleWithCheck?roleId={}", roleId);
+        String errorMsg = storePlatformRoleService.deleteRoleWithCheck(roleId);
+        if (errorMsg == null) {
+            return R.success("删除成功");
+        }
+        return R.fail(errorMsg);
+    }
+
+    @ApiOperation("查询所有正常状态的角色列表")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = false)
+    })
+    @GetMapping("/getAllNormalRoles")
+    public R<List<StorePlatformRole>> getAllNormalRoles(
+            @RequestParam(value = "storeId", required = false) Integer storeId) {
+        log.info("StorePlatformRoleController.getAllNormalRoles?storeId={}", storeId);
+        List<StorePlatformRole> roles = storePlatformRoleService.getAllNormalRoles(storeId);
+        return R.data(roles);
+    }
+
+    @ApiOperation("检查角色名称是否已存在")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = false),
+            @ApiImplicitParam(name = "roleName", value = "角色名称", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "excludeRoleId", value = "排除的角色ID(用于更新时排除自己)", dataType = "Long", paramType = "query", required = false)
+    })
+    @GetMapping("/checkRoleNameExists")
+    public R<Boolean> checkRoleNameExists(
+            @RequestParam(value = "storeId", required = false) Integer storeId,
+            @RequestParam("roleName") String roleName,
+            @RequestParam(value = "excludeRoleId", required = false) Long excludeRoleId) {
+        log.info("StorePlatformRoleController.checkRoleNameExists?storeId={}, roleName={}, excludeRoleId={}", storeId, roleName, excludeRoleId);
+        boolean exists = storePlatformRoleService.checkRoleNameExists(storeId, roleName, excludeRoleId);
+        return R.data(exists);
+    }
+
+    @ApiOperation("查看角色权限(表格形式)")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @GetMapping("/getRolePermissionTable")
+    public R<List<RolePermissionTableVo>> getRolePermissionTable(@RequestParam("roleId") Long roleId) {
+        log.info("StorePlatformRoleController.getRolePermissionTable?roleId={}", roleId);
+        List<RolePermissionTableVo> tableData = storePlatformRoleMenuService.getRolePermissionTable(roleId);
+        return R.data(tableData);
+    }
+}
+

+ 137 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformRoleMenuController.java

@@ -0,0 +1,137 @@
+package shop.alien.storeplatform.controller;
+
+import io.swagger.annotations.*;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.storeplatform.service.StorePlatformRoleMenuService;
+
+import java.util.List;
+
+/**
+ * 角色菜单关联表 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"平台-角色菜单关联管理"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/platform/role-menu")
+@RequiredArgsConstructor
+public class StorePlatformRoleMenuController {
+
+    private final StorePlatformRoleMenuService storePlatformRoleMenuService;
+
+    @ApiOperation("根据角色ID查询菜单ID列表")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @GetMapping("/getMenuIdsByRoleId")
+    public R<List<Long>> getMenuIdsByRoleId(@RequestParam("roleId") Long roleId) {
+        log.info("StorePlatformRoleMenuController.getMenuIdsByRoleId?roleId={}", roleId);
+        List<Long> menuIds = storePlatformRoleMenuService.getMenuIdsByRoleId(roleId);
+        return R.data(menuIds);
+    }
+
+    @ApiOperation("根据菜单ID查询角色ID列表")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @GetMapping("/getRoleIdsByMenuId")
+    public R<List<Long>> getRoleIdsByMenuId(@RequestParam("menuId") Long menuId) {
+        log.info("StorePlatformRoleMenuController.getRoleIdsByMenuId?menuId={}", menuId);
+        List<Long> roleIds = storePlatformRoleMenuService.getRoleIdsByMenuId(menuId);
+        return R.data(roleIds);
+    }
+
+    @ApiOperation("为角色分配菜单")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/assignMenus")
+    public R<String> assignMenus(@RequestBody AssignMenusDto assignMenusDto) {
+        log.info("StorePlatformRoleMenuController.assignMenus?roleId={}, menuIds={}", 
+                assignMenusDto.getRoleId(), assignMenusDto.getMenuIds());
+        boolean result = storePlatformRoleMenuService.assignMenus(assignMenusDto.getRoleId(), assignMenusDto.getMenuIds());
+        if (result) {
+            return R.success("分配成功");
+        }
+        return R.fail("分配失败");
+    }
+
+    @ApiOperation("移除角色的所有菜单")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @DeleteMapping("/removeAllMenus")
+    public R<String> removeAllMenus(@RequestParam("roleId") Long roleId) {
+        log.info("StorePlatformRoleMenuController.removeAllMenus?roleId={}", roleId);
+        boolean result = storePlatformRoleMenuService.removeAllMenus(roleId);
+        if (result) {
+            return R.success("移除成功");
+        }
+        return R.fail("移除失败");
+    }
+
+    @ApiOperation("为角色添加单个菜单")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/addMenu")
+    public R<String> addMenu(@RequestBody AddMenuDto addMenuDto) {
+        log.info("StorePlatformRoleMenuController.addMenu?roleId={}, menuId={}", 
+                addMenuDto.getRoleId(), addMenuDto.getMenuId());
+        boolean result = storePlatformRoleMenuService.addMenu(addMenuDto.getRoleId(), addMenuDto.getMenuId());
+        if (result) {
+            return R.success("添加成功");
+        }
+        return R.fail("添加失败");
+    }
+
+    @ApiOperation("移除角色的单个菜单")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @DeleteMapping("/removeMenu")
+    public R<String> removeMenu(@RequestParam("roleId") Long roleId, @RequestParam("menuId") Long menuId) {
+        log.info("StorePlatformRoleMenuController.removeMenu?roleId={}, menuId={}", roleId, menuId);
+        boolean result = storePlatformRoleMenuService.removeMenu(roleId, menuId);
+        if (result) {
+            return R.success("移除成功");
+        }
+        return R.fail("移除失败");
+    }
+
+    /**
+     * 分配菜单请求DTO
+     */
+    @Data
+    @ApiModel(value = "AssignMenusDto", description = "分配菜单请求参数")
+    static class AssignMenusDto {
+        @ApiModelProperty(value = "角色ID", required = true)
+        private Long roleId;
+
+        @ApiModelProperty(value = "菜单ID列表", required = true)
+        private List<Long> menuIds;
+    }
+
+    /**
+     * 添加菜单请求DTO
+     */
+    @Data
+    @ApiModel(value = "AddMenuDto", description = "添加菜单请求参数")
+    static class AddMenuDto {
+        @ApiModelProperty(value = "角色ID", required = true)
+        private Long roleId;
+
+        @ApiModelProperty(value = "菜单ID", required = true)
+        private Long menuId;
+    }
+}
+

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

@@ -0,0 +1,248 @@
+package shop.alien.storeplatform.controller;
+
+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.SubAccountVo;
+import shop.alien.storeplatform.dto.AssignRolesDto;
+import shop.alien.storeplatform.dto.BatchDeleteSubAccountDto;
+import shop.alien.storeplatform.dto.CreateAccountDto;
+import shop.alien.storeplatform.dto.UpdateAccountDto;
+import shop.alien.storeplatform.service.StorePlatformUserRoleService;
+import shop.alien.storeplatform.annotation.PlatformOperationLog;
+
+
+import java.util.List;
+
+/**
+ * 用户角色关联表 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"平台-用户角色关联管理"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/platform/user-role")
+@RequiredArgsConstructor
+public class StorePlatformUserRoleController {
+
+    private final StorePlatformUserRoleService storePlatformUserRoleService;
+
+    @ApiOperation("根据用户ID查询角色ID列表")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/getRoleIdsByUserId")
+    public R<List<Long>> getRoleIdsByUserId(@RequestParam("userId") Integer userId, @RequestParam("storeId") Integer storeId) {
+        log.info("StorePlatformUserRoleController.getRoleIdsByUserId?userId={}, storeId={}", userId, storeId);
+        List<Long> roleIds = storePlatformUserRoleService.getRoleIdsByUserId(userId, storeId);
+        return R.data(roleIds);
+    }
+
+    @ApiOperation("根据角色ID查询用户ID列表")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/getUserIdsByRoleId")
+    public R<List<Integer>> getUserIdsByRoleId(@RequestParam("roleId") Long roleId, @RequestParam("storeId") Integer storeId) {
+        log.info("StorePlatformUserRoleController.getUserIdsByRoleId?roleId={}, storeId={}", roleId, storeId);
+        List<Integer> userIds = storePlatformUserRoleService.getUserIdsByRoleId(roleId, storeId);
+        return R.data(userIds);
+    }
+
+    @ApiOperation("为用户分配角色")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/assignRoles")
+    public R<String> assignRoles(@RequestBody AssignRolesDto assignRolesDto) {
+        log.info("StorePlatformUserRoleController.assignRoles?userId={}, roleIds={}, storeId={}", 
+                assignRolesDto.getUserId(), assignRolesDto.getRoleIds(), assignRolesDto.getStoreId());
+        boolean result = storePlatformUserRoleService.assignRoles(assignRolesDto.getUserId(), assignRolesDto.getRoleIds(), assignRolesDto.getStoreId());
+        if (result) {
+            return R.success("分配成功");
+        }
+        return R.fail("分配失败");
+    }
+
+    @ApiOperation("移除用户的所有角色")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PlatformOperationLog(
+            module = "账号操作记录",
+            type = "移除角色",
+            content = "移除用户ID=#{#userId}的全部角色(店铺ID=#{#storeId})"
+    )
+    @DeleteMapping("/removeAllRoles")
+    public R<String> removeAllRoles(@RequestParam("userId") Integer userId, @RequestParam("storeId") Integer storeId) {
+        log.info("StorePlatformUserRoleController.removeAllRoles?userId={}, storeId={}", userId, storeId);
+        boolean result = storePlatformUserRoleService.removeAllRoles(userId, storeId);
+        if (result) {
+            return R.success("移除成功");
+        }
+        return R.fail("移除失败");
+    }
+
+    @ApiOperation("移除用户的指定角色")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PlatformOperationLog(
+            module = "账号操作记录",
+            type = "移除角色",
+            content = "移除用户ID=#{#userId}的角色ID=#{#roleId}(店铺ID=#{#storeId})"
+    )
+    @GetMapping("/removeRole")
+    public R<String> removeRole(
+            @RequestParam("userId") Integer userId,
+            @RequestParam("roleId") Long roleId,
+            @RequestParam("storeId") Integer storeId
+    ) {
+        log.info("StorePlatformUserRoleController.removeRole?userId={}, roleId={}, storeId={}", userId, roleId, storeId);
+        boolean result = storePlatformUserRoleService.removeRole(userId, roleId, storeId);
+        if (result) {
+            return R.success("移除成功");
+        }
+        return R.fail("移除失败");
+    }
+
+    @ApiOperation("创建账号并分配角色")
+    @ApiOperationSupport(order = 6)
+    @PlatformOperationLog(
+            module = "账号操作记录",
+            type = "新增子账号",
+            content = "新增账号手机号:#{#p0.phone},账号名:#{#p0.accountName}"
+    )
+    @PostMapping("/createAccountAndAssignRole")
+    public R<String> createAccountAndAssignRole(@RequestBody CreateAccountDto createAccountDto) {
+        log.info("StorePlatformUserRoleController.createAccountAndAssignRole?phone={}, accountName={}, storeId={}, roleId={}", 
+                createAccountDto.getPhone(), createAccountDto.getAccountName(), createAccountDto.getStoreId(), createAccountDto.getRoleId());
+        boolean result = storePlatformUserRoleService.createAccountAndAssignRole(
+                createAccountDto.getPhone(), 
+                createAccountDto.getAccountName(), 
+                createAccountDto.getStoreId(), 
+                createAccountDto.getRoleId());
+        if (result) {
+            return R.success("创建账号并分配角色成功");
+        }
+        return R.fail("创建账号并分配角色失败");
+    }
+
+
+    @ApiOperation("查询当前店铺下的子账号列表")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "accountName", value = "账号名称(模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "phone", value = "手机号(模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "roleName", value = "角色名称(模糊查询)", dataType = "String", paramType = "query", required = false)
+    })
+    @GetMapping("/querySubAccounts")
+    public R<List<SubAccountVo>> querySubAccounts(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam(value = "accountName", required = false) String accountName,
+            @RequestParam(value = "phone", required = false) String phone,
+            @RequestParam(value = "roleName", required = false) String roleName) {
+        log.info("StorePlatformUserRoleController.querySubAccounts?storeId={}, accountName={}, phone={}, roleName={}",
+                storeId, accountName, phone, roleName);
+        List<SubAccountVo> subAccountList = storePlatformUserRoleService.querySubAccounts(
+                storeId, accountName, phone, roleName);
+        return R.data(subAccountList);
+    }
+
+    @ApiOperation("查询子账号详情")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "roleIdNew", value = "角色ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @GetMapping("/getSubAccountDetail")
+    public R<SubAccountDetailVo> getSubAccountDetail(
+            @RequestParam("userId") Integer userId,
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("roleIdNew") Long roleIdNew
+            ) {
+        log.info("StorePlatformUserRoleController.getSubAccountDetail?userId={}, storeId={},roleIdNew={}", userId, storeId,roleIdNew);
+        SubAccountDetailVo detail = storePlatformUserRoleService.getSubAccountDetail(userId, storeId, roleIdNew);
+        if (detail == null) {
+            return R.fail("子账号不存在");
+        }
+        return R.data(detail);
+    }
+
+    @ApiOperation("批量删除子账号")
+    @ApiOperationSupport(order = 9)
+    @PlatformOperationLog(
+            module = "账号操作记录",
+            type = "批量删除子账号",
+            content = "批量删除子账号(店铺ID=#{#p0.storeId},用户数量=#{#p0.userIds != null ? #p0.userIds.size() : 0})"
+    )
+    @PostMapping("/batchDeleteSubAccounts")
+    public R<String> batchDeleteSubAccounts(@RequestBody BatchDeleteSubAccountDto batchDeleteDto) {
+        log.info("StorePlatformUserRoleController.batchDeleteSubAccounts?storeId={}, userIds={}", 
+                batchDeleteDto.getStoreId(), batchDeleteDto.getUserIds());
+        
+        if (batchDeleteDto.getStoreId() == null) {
+            return R.fail("店铺ID不能为空");
+        }
+        if (batchDeleteDto.getUserIds() == null || batchDeleteDto.getUserIds().isEmpty()) {
+            return R.fail("用户ID列表不能为空");
+        }
+        
+        boolean result = storePlatformUserRoleService.batchDeleteSubAccounts(
+                batchDeleteDto.getUserIds(), batchDeleteDto.getStoreId());
+        if (result) {
+            return R.success("批量删除成功");
+        }
+        return R.fail("批量删除失败");
+    }
+
+    @ApiOperation("更新子账号信息")
+    @ApiOperationSupport(order = 10)
+    @PlatformOperationLog(
+            module = "账号操作记录",
+            type = "修改子账号",
+            content = "修改子账号,用户ID:#{#p0.userId},手机号:#{#p0.phone},账号名:#{#p0.accountName}"
+    )
+    @PostMapping("/updateSubAccount")
+    public R<String> updateSubAccount(@RequestBody UpdateAccountDto updateAccountDto) {
+        log.info("StorePlatformUserRoleController.updateSubAccount?userId={}, phone={}, accountName={}, storeId={}, roleId={}", 
+                updateAccountDto.getUserId(), updateAccountDto.getPhone(), updateAccountDto.getAccountName(), 
+                updateAccountDto.getStoreId(), updateAccountDto.getRoleId());
+        
+        if (updateAccountDto.getUserId() == null) {
+            return R.fail("用户ID不能为空");
+        }
+        if (updateAccountDto.getStoreId() == null) {
+            return R.fail("店铺ID不能为空");
+        }
+        
+        boolean result = storePlatformUserRoleService.updateSubAccount(
+                updateAccountDto.getUserId(),
+                updateAccountDto.getPhone(),
+                updateAccountDto.getAccountName(),
+                updateAccountDto.getStoreId(),
+                updateAccountDto.getRoleId());
+        if (result) {
+            return R.success("更新子账号信息成功");
+        }
+        return R.fail("更新子账号信息失败");
+    }
+
+}
+

+ 28 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/AssignRolesDto.java

@@ -0,0 +1,28 @@
+package shop.alien.storeplatform.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 = "AssignRolesDto", description = "分配角色请求参数")
+public class AssignRolesDto {
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "角色ID列表", required = true)
+    private List<Long> roleIds;
+
+    @ApiModelProperty(value = "店铺ID", required = true)
+    private Integer storeId;
+}
+

+ 25 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/BatchDeleteSubAccountDto.java

@@ -0,0 +1,25 @@
+package shop.alien.storeplatform.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 = "BatchDeleteSubAccountDto", description = "批量删除子账号请求参数")
+public class BatchDeleteSubAccountDto {
+
+    @ApiModelProperty(value = "店铺ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "用户ID列表", required = true)
+    private List<Integer> userIds;
+}
+

+ 29 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/CreateAccountDto.java

@@ -0,0 +1,29 @@
+package shop.alien.storeplatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 创建账号请求DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "CreateAccountDto", description = "创建账号请求参数")
+public class CreateAccountDto {
+
+    @ApiModelProperty(value = "手机号", required = true)
+    private String phone;
+
+    @ApiModelProperty(value = "账号名称")
+    private String accountName;
+
+    @ApiModelProperty(value = "店铺ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "角色ID", required = true)
+    private Long roleId;
+}
+

+ 46 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/CreateRoleDto.java

@@ -0,0 +1,46 @@
+package shop.alien.storeplatform.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 = "CreateRoleDto", description = "创建角色请求参数")
+public class CreateRoleDto {
+
+    @ApiModelProperty(value = "店铺ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "角色名称", required = true)
+    private String roleName;
+
+    @ApiModelProperty(value = "角色类型")
+    private String roleType;
+
+    @ApiModelProperty(value = "显示顺序")
+    private Integer roleSort;
+
+    @ApiModelProperty(value = "角色状态(0正常 1停用)")
+    private String status;
+
+    @ApiModelProperty(value = "角色描述")
+    private String description;
+
+    @ApiModelProperty(value = "英文角色名称")
+    private String roleNameEn;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "菜单权限ID列表(子账号权限)", required = true)
+    private List<Long> menuIds;
+}
+

+ 29 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/QuerySubAccountDto.java

@@ -0,0 +1,29 @@
+package shop.alien.storeplatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 查询子账号请求DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "QuerySubAccountDto", description = "查询子账号请求参数")
+public class QuerySubAccountDto {
+
+    @ApiModelProperty(value = "店铺ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "账号名称(模糊查询)")
+    private String accountName;
+
+    @ApiModelProperty(value = "手机号(模糊查询)")
+    private String phone;
+
+    @ApiModelProperty(value = "角色名称(模糊查询)")
+    private String roleName;
+}
+

+ 32 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/UpdateAccountDto.java

@@ -0,0 +1,32 @@
+package shop.alien.storeplatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 更新子账号请求DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "UpdateAccountDto", description = "更新子账号请求参数")
+public class UpdateAccountDto {
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+
+    @ApiModelProperty(value = "账号名称")
+    private String accountName;
+
+    @ApiModelProperty(value = "店铺ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "角色ID")
+    private Long roleId;
+}
+

+ 49 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/UpdateRoleDto.java

@@ -0,0 +1,49 @@
+package shop.alien.storeplatform.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 = "UpdateRoleDto", description = "更新角色请求参数")
+public class UpdateRoleDto {
+
+    @ApiModelProperty(value = "角色ID", required = true)
+    private Long roleId;
+
+    @ApiModelProperty(value = "店铺ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "角色名称", required = true)
+    private String roleName;
+
+    @ApiModelProperty(value = "角色类型")
+    private String roleType;
+
+    @ApiModelProperty(value = "显示顺序")
+    private Integer roleSort;
+
+    @ApiModelProperty(value = "角色状态(0正常 1停用)")
+    private String status;
+
+    @ApiModelProperty(value = "角色描述")
+    private String description;
+
+    @ApiModelProperty(value = "英文角色名称")
+    private String roleNameEn;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "菜单权限ID列表(子账号权限)")
+    private List<Long> menuIds;
+}
+

+ 40 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/enums/PlatformOperationModule.java

@@ -0,0 +1,40 @@
+package shop.alien.storeplatform.enums;
+
+/**
+ * 平台操作记录模块枚举
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public enum PlatformOperationModule {
+    ACCOUNT("ACCOUNT", "账号操作记录"),
+    ROLE("ROLE", "角色操作记录");
+
+    private final String code;
+    private final String moduleName;
+
+    PlatformOperationModule(String code, String moduleName) {
+        this.code = code;
+        this.moduleName = moduleName;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getModuleName() {
+        return moduleName;
+    }
+
+    public static PlatformOperationModule fromCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (PlatformOperationModule module : values()) {
+            if (module.code.equalsIgnoreCase(code)) {
+                return module;
+            }
+        }
+        return null;
+    }
+}

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

@@ -0,0 +1,35 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.storePlatform.StorePlatformOperationLog;
+
+import java.util.Date;
+
+/**
+ * 平台操作记录服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface PlatformOperationLogService {
+
+    /**
+     * 保存操作记录
+     *
+     * @param logRecord 操作记录
+     */
+    void save(StorePlatformOperationLog logRecord);
+
+    /**
+     * 分页查询操作记录
+     *
+     * @param page      页码
+     * @param size      页容
+     * @param module    操作模块
+     * @param account   操作账号(模糊查询)
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @return IPage<StorePlatformOperationLog>
+     */
+    IPage<StorePlatformOperationLog> getOperationLogPage(int page, int size, String module, String account, Date startTime, Date endTime);
+}

+ 49 - 36
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformMenuService.java

@@ -1,79 +1,92 @@
 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.StoreMenu;
-import shop.alien.entity.store.vo.StoreMenuVo;
+import shop.alien.entity.store.StorePlatformMenu;
 
 import java.util.List;
 
 /**
- * 二期-门店推荐 服务类
+ * 商家PC菜单权限表 服务类
  *
- * @author ssk
- * @since 2024-12-05
+ * @author system
+ * @since 2025-01-XX
  */
-public interface StorePlatformMenuService extends IService<StoreMenu> {
+public interface StorePlatformMenuService extends IService<StorePlatformMenu> {
 
     /**
-     * 获取门店菜单
+     * 分页查询菜单列表
      *
-     * @param storeId  门店id
-     * @param dishType 菜品类型, 0:菜单, 1:推荐
-     * @param phoneId  消息标识
-     * @return list
+     * @param page     页码
+     * @param size     页容
+     * @param menuName 菜单名称(支持模糊查询)
+     * @param menuType 菜单类型(M目录 C菜单 F按钮)
+     * @param status   菜单状态(0正常 1停用)
+     * @return IPage<StorePlatformMenu>
      */
-    List<StoreMenuVo> getStoreMenu(Integer storeId, Integer dishType, String phoneId);
+    IPage<StorePlatformMenu> getMenuPage(int page, int size, String menuName, String menuType, String status);
 
     /**
-     * 获取菜品详情
+     * 根据ID查询菜单详情
      *
-     * @param id 菜品id
-     * @return StoreMenuVo
+     * @param menuId 菜单ID
+     * @return StorePlatformMenu
      */
-    StoreMenuVo getMenuInfo(Integer id);
+    StorePlatformMenu getMenuById(Long menuId);
 
     /**
-     * 新增或修改门店菜单
+     * 新增菜单
      *
+     * @param menu 菜单信息
+     * @return 是否成功
      */
-    R<String> saveOrUpdateMenu(StoreMenu storeMenu);
+    boolean saveMenu(StorePlatformMenu menu);
 
     /**
-     * 新增或修改门店菜单
+     * 修改菜单
      *
+     * @param menu 菜单信息
+     * @return 是否成功
      */
-    R<String> saveOrUpdateMenus(StoreMenuVo storeMenuVo);
+    boolean updateMenu(StorePlatformMenu menu);
 
     /**
+     * 删除菜单(逻辑删除)
      *
-     * 菜品排序
+     * @param menuId 菜单ID
+     * @return 是否成功
      */
-    Boolean getSortMenuInfo(StoreMenuVo storeMenuVo);
+    boolean deleteMenu(Long menuId);
 
     /**
+     * 修改菜单状态
      *
-     * 保存菜品排序
+     * @param menuId 菜单ID
+     * @param status 菜单状态(0正常 1停用)
+     * @return 是否成功
      */
-    Boolean saveMenuSort(List<StoreMenu> storeMenuList);
+    boolean updateStatus(Long menuId, String status);
 
     /**
+     * 查询所有正常状态的菜单列表(树形结构)
      *
-     * 删除门店菜单
+     * @return List<StorePlatformMenu>
      */
-    R deleteMenu(List<Integer> ids, int dishType);
+    List<StorePlatformMenu> getMenuTree();
 
-
-
-    StoreMenuVo getMenuCountByStoreId(int storeId);
+    /**
+     * 根据父菜单ID查询子菜单列表
+     *
+     * @param parentId 父菜单ID
+     * @return List<StorePlatformMenu>
+     */
+    List<StorePlatformMenu> getMenusByParentId(Long parentId);
 
     /**
-     * 获取用户对该菜品是否点赞
+     * 根据层级查询菜单列表
      *
-     * @param userId  用户id
-     * @param menuId 菜品id
-     * @return boolean
+     * @param level 菜单层级(1一级菜单 2二级菜单 3三级菜单)
+     * @return List<StorePlatformMenu>
      */
-    boolean getMenuLikeStatus(String userId, Integer menuId);
+    List<StorePlatformMenu> getMenusByLevel(Integer level);
 }
-

+ 86 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformRoleMenuService.java

@@ -0,0 +1,86 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StorePlatformRoleMenu;
+
+import java.util.List;
+
+/**
+ * 角色菜单关联表 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StorePlatformRoleMenuService extends IService<StorePlatformRoleMenu> {
+
+    /**
+     * 根据角色ID查询菜单ID列表
+     *
+     * @param roleId 角色ID
+     * @return 菜单ID列表
+     */
+    List<Long> getMenuIdsByRoleId(Long roleId);
+
+    /**
+     * 根据菜单ID查询角色ID列表
+     *
+     * @param menuId 菜单ID
+     * @return 角色ID列表
+     */
+    List<Long> getRoleIdsByMenuId(Long menuId);
+
+    /**
+     * 为角色分配菜单
+     *
+     * @param roleId  角色ID
+     * @param menuIds 菜单ID列表
+     * @return 是否成功
+     */
+    boolean assignMenus(Long roleId, List<Long> menuIds);
+
+    /**
+     * 移除角色的所有菜单
+     *
+     * @param roleId 角色ID
+     * @return 是否成功
+     */
+    boolean removeAllMenus(Long roleId);
+
+    /**
+     * 为角色添加单个菜单
+     *
+     * @param roleId 角色ID
+     * @param menuId 菜单ID
+     * @return 是否成功
+     */
+    boolean addMenu(Long roleId, Long menuId);
+
+    /**
+     * 移除角色的单个菜单
+     *
+     * @param roleId 角色ID
+     * @param menuId 菜单ID
+     * @return 是否成功
+     */
+    boolean removeMenu(Long roleId, Long menuId);
+
+    /**
+     * 根据角色ID获取权限树形结构
+     *
+     * @param roleId 角色ID
+     * @return 权限树形结构列表
+     */
+    List<shop.alien.entity.store.vo.RolePermissionVo> getRolePermissionTree(Long roleId);
+
+    /**
+     * 根据角色ID获取权限表格数据(按层级展示)
+     * 如果角色有三级权限,则显示一级、二级、三级权限
+     * 如果只有一级权限,则只显示一级权限
+     * 如果只有二级权限,则显示一级和二级权限
+     *
+     * @param roleId 角色ID
+     * @return 权限表格数据列表
+     */
+    List<shop.alien.entity.store.vo.RolePermissionTableVo> getRolePermissionTable(Long roleId);
+}
+

+ 15 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformRoleQueryService.java

@@ -0,0 +1,15 @@
+package shop.alien.storeplatform.service;
+
+/**
+ * 角色查询服务(SQL实现)
+ */
+public interface StorePlatformRoleQueryService {
+
+    /**
+     * 根据角色ID查询角色名
+     *
+     * @param roleId 角色ID
+     * @return 角色名
+     */
+    String getRoleNameById(Long roleId);
+}

+ 123 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformRoleService.java

@@ -0,0 +1,123 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StorePlatformRole;
+import shop.alien.entity.store.vo.StorePlatformRoleVo;
+
+import java.util.List;
+
+/**
+ * 平台角色信息表 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StorePlatformRoleService extends IService<StorePlatformRole> {
+
+    /**
+     * 分页查询角色列表
+     *
+     * @param page     页码
+     * @param size     页容
+     * @param storeId  店铺ID
+     * @param roleName 角色名称(支持模糊查询)
+     * @param description 角色描述(支持模糊查询)
+     * @param status   角色状态(0正常 1停用)
+     * @return IPage<StorePlatformRole>
+     */
+    IPage<StorePlatformRole> getRolePage(int page, int size, Integer storeId, String roleName, String description, String status);
+
+    /**
+     * 分页查询角色列表(包含关联统计)
+     *
+     * @param page     页码
+     * @param size     页容
+     * @param storeId  店铺ID
+     * @param roleName 角色名称(支持模糊查询)
+     * @param description 角色描述(支持模糊查询)
+     * @param status   角色状态(0正常 1停用)
+     * @return IPage<StorePlatformRoleVo>
+     */
+    IPage<StorePlatformRoleVo> getRolePageWithStats(int page, int size, Integer storeId, String roleName, String description, String status);
+
+    /**
+     * 根据ID查询角色详情
+     *
+     * @param roleId 角色ID
+     * @return StorePlatformRole
+     */
+    StorePlatformRole getRoleById(Long roleId);
+
+    /**
+     * 新增角色
+     *
+     * @param role 角色信息
+     * @return 是否成功
+     */
+    boolean saveRole(StorePlatformRole role);
+
+    /**
+     * 创建角色(包含权限分配)
+     *
+     * @param role 角色信息
+     * @param menuIds 菜单权限ID列表
+     * @return 是否成功
+     */
+    boolean createRoleWithMenus(StorePlatformRole role, List<Long> menuIds);
+
+    /**
+     * 检查角色名称是否已存在
+     *
+     * @param storeId 店铺ID
+     * @param roleName 角色名称
+     * @param excludeRoleId 排除的角色ID(用于更新时排除自己)
+     * @return 是否存在
+     */
+    boolean checkRoleNameExists(Integer storeId, String roleName, Long excludeRoleId);
+
+    /**
+     * 修改角色
+     *
+     * @param role 角色信息
+     * @return 是否成功
+     */
+    boolean updateRole(StorePlatformRole role);
+
+    /**
+     * 更新角色(包含权限分配)
+     * 如果角色类型(roleName)有变化,则更新 store_platform_role 表
+     * 如果权限有变化,则更新 store_platform_role_menu 表
+     *
+     * @param role 角色信息
+     * @param menuIds 菜单权限ID列表
+     * @return 是否成功
+     */
+    boolean updateRoleWithMenus(StorePlatformRole role, List<Long> menuIds);
+
+    /**
+     * 删除角色(逻辑删除)
+     *
+     * @param roleId 角色ID
+     * @return 是否成功
+     */
+    boolean deleteRole(Long roleId);
+
+    /**
+     * 逻辑删除角色
+     * 如果角色有关联的子账号,则不允许删除
+     *
+     * @param roleId 角色ID
+     * @return 删除结果信息,成功返回null,失败返回错误信息
+     */
+    String deleteRoleWithCheck(Long roleId);
+
+    /**
+     * 查询所有正常状态的角色列表
+     *
+     * @param storeId 店铺ID
+     * @return List<StorePlatformRole>
+     */
+    List<StorePlatformRole> getAllNormalRoles(Integer storeId);
+}
+

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

@@ -0,0 +1,123 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StorePlatformUserRole;
+import shop.alien.entity.store.vo.SubAccountDetailVo;
+import shop.alien.entity.store.vo.SubAccountVo;
+
+import java.util.List;
+
+/**
+ * 用户角色关联表 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StorePlatformUserRoleService extends IService<StorePlatformUserRole> {
+
+    /**
+     * 根据用户ID查询角色ID列表
+     *
+     * @param userId  用户ID
+     * @param storeId 店铺ID
+     * @return 角色ID列表
+     */
+    List<Long> getRoleIdsByUserId(Integer userId, Integer storeId);
+
+    /**
+     * 根据角色ID查询用户ID列表
+     *
+     * @param roleId  角色ID
+     * @param storeId 店铺ID
+     * @return 用户ID列表
+     */
+    List<Integer> getUserIdsByRoleId(Long roleId, Integer storeId);
+
+    /**
+     * 为用户分配角色
+     *
+     * @param userId  用户ID
+     * @param roleIds 角色ID列表
+     * @param storeId 店铺ID
+     * @return 是否成功
+     */
+    boolean assignRoles(Integer userId, List<Long> roleIds, Integer storeId);
+
+    /**
+     * 移除用户的所有角色
+     *
+     * @param userId  用户ID
+     * @param storeId 店铺ID
+     * @return 是否成功
+     */
+    boolean removeAllRoles(Integer userId, Integer storeId);
+
+    /**
+     * 移除用户的指定角色
+     *
+     * @param userId  用户ID
+     * @param roleId  角色ID
+     * @param storeId 店铺ID
+     * @return 是否成功
+     */
+    boolean removeRole(Integer userId, Long roleId, Integer storeId);
+
+    /**
+     * 根据手机号创建账号并分配角色
+     * 如果 store_user 表中没有该手机号的记录,则插入一条数据
+     * 如果 store_platform_user_role 表中没有该用户和店铺的角色记录,则插入数据
+     *
+     * @param phone       手机号
+     * @param accountName 账号名称
+     * @param storeId     店铺ID
+     * @param roleId      角色ID
+     * @return 是否成功
+     */
+    boolean createAccountAndAssignRole(String phone, String accountName, Integer storeId, Long roleId);
+
+    /**
+     * 查询当前店铺下的子账号列表
+     * 根据店铺ID查询 store_platform_user_role 表连表 store_user 和 store_platform_role
+     * 支持根据账号名称、手机号、角色名称进行模糊查询
+     * 并计算每个子账号的权限数量
+     *
+     * @param storeId    店铺ID
+     * @param accountName 账号名称(模糊查询)
+     * @param phone       手机号(模糊查询)
+     * @param roleName    角色名称(模糊查询)
+     * @return 子账号列表
+     */
+    List<SubAccountVo> querySubAccounts(Integer storeId, String accountName, String phone, String roleName);
+
+    /**
+     * 根据用户ID和店铺ID查询子账号详情(用于编辑账号页面)
+     * 包含账号基本信息、角色信息和权限列表
+     *
+     * @param userId  用户ID
+     * @param storeId 店铺ID
+     * @return 子账号详情
+     */
+    SubAccountDetailVo getSubAccountDetail(Integer userId, Integer storeId, Long roleIdNew);
+
+    /**
+     * 批量逻辑删除子账号(删除用户角色关联关系)
+     *
+     * @param userIds 用户ID列表
+     * @param storeId 店铺ID
+     * @return 是否成功
+     */
+    boolean batchDeleteSubAccounts(List<Integer> userIds, Integer storeId);
+
+    /**
+     * 更新子账号信息(账号名、手机号和角色信息)
+     *
+     * @param userId      用户ID
+     * @param phone       手机号(可选)
+     * @param accountName 账号名称(可选)
+     * @param storeId     店铺ID
+     * @param roleId      角色ID(可选)
+     * @return 是否成功
+     */
+    boolean updateSubAccount(Integer userId, String phone, String accountName, Integer storeId, Long roleId);
+}
+

+ 66 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/PlatformOperationLogServiceImpl.java

@@ -0,0 +1,66 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.stereotype.Service;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.storePlatform.StorePlatformOperationLog;
+import shop.alien.mapper.StorePlatformOperationLogMapper;
+import shop.alien.storeplatform.service.PlatformOperationLogService;
+import shop.alien.storeplatform.util.LoginUserUtil;
+
+import java.util.Date;
+import java.util.Collections;
+
+/**
+ * 平台操作记录服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class PlatformOperationLogServiceImpl implements PlatformOperationLogService {
+
+    private final StorePlatformOperationLogMapper storePlatformOperationLogMapper;
+
+    @Override
+    public void save(StorePlatformOperationLog logRecord) {
+        try {
+            storePlatformOperationLogMapper.insert(logRecord);
+        } catch (Exception e) {
+            log.warn("保存平台操作记录失败: {}", e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public IPage<StorePlatformOperationLog> getOperationLogPage(int page, int size, String module, String account, Date startTime, Date endTime) {
+        Page<StorePlatformOperationLog> pageInfo = new Page<>(page, size);
+        Integer storeId = LoginUserUtil.getCurrentStoreId();
+        if (storeId == null) {
+            pageInfo.setRecords(Collections.emptyList());
+            pageInfo.setTotal(0);
+            return pageInfo;
+        }
+        LambdaQueryWrapper<StorePlatformOperationLog> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StorePlatformOperationLog::getStoreId, storeId);
+        if (StringUtils.hasText(module)) {
+            wrapper.eq(StorePlatformOperationLog::getOperationModule, module);
+        }
+        if (StringUtils.hasText(account)) {
+            wrapper.like(StorePlatformOperationLog::getOperatorAccount, account);
+        }
+        if (startTime != null) {
+            wrapper.ge(StorePlatformOperationLog::getOperationTime, startTime);
+        }
+        if (endTime != null) {
+            wrapper.le(StorePlatformOperationLog::getOperationTime, endTime);
+        }
+        wrapper.orderByDesc(StorePlatformOperationLog::getOperationTime);
+        return storePlatformOperationLogMapper.selectPage(pageInfo, wrapper);
+    }
+}

+ 4 - 4
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreBusinessServiceImpl.java

@@ -1836,7 +1836,7 @@ public class StoreBusinessServiceImpl extends ServiceImpl<StoreInfoMapper, Store
     private static LifeNotice getLifeNotice(StoreInfoVo storeInfo) {
         LifeNotice lifeMessage = new LifeNotice();
         lifeMessage.setReceiverId("store_" + storeInfo.getStorePhone());
-        String text = "您提交的店铺注销申请已成功接收,系统将按流程进行处理。注销申请提交后,将进入7天的冷静期,期间您可随在【我的设置】-【店铺信息】-【操作】-【注销店铺】中撤回申请,冷静期结束后,系统将正式开始注销操作,店铺内所数据将被永久清除,且无法恢复。如有疑问,可联系客服咨询,感谢您使用我们的服务。";
+        String text = "您提交的店铺注销申请已成功接收,系统将按流程进行处理。\n\n注销申请提交后,将进入7天的冷静期,期间您可随时在【我的设置】-【注销店铺】中撤回申请。冷静期结束后,系统将正式开始注销操作,店铺内所有数据将被永久清除,且无法恢复。\n\n如有疑问,可联系客服咨询,感谢您使用我们的服务。";
         com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
         jsonObject.put("message", text);
         lifeMessage.setContext(jsonObject.toJSONString());
@@ -1868,13 +1868,13 @@ public class StoreBusinessServiceImpl extends ServiceImpl<StoreInfoMapper, Store
             // 发送通知
             LifeNotice lifeMessage = new LifeNotice();
             lifeMessage.setReceiverId("store_" + storeInfo.getStorePhone());
-            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm");
             String storeDate = simpleDateFormat.format(new Date());
-            String text = "您在"+storeDate+"撤销了注销账号,所有数据均已保留,您可继续在平台使用。";
+            String text = "当前店铺状态已恢复正常。您可继续使用该店铺登录并享受各项服务,所有店铺数据均已妥善保留。\n\n若您后续仍有注销需求,可随时通过【我的设置】-【注销店铺】中重新提交申请。感谢您的理解与支持!";
             com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
             jsonObject.put("message", text);
             lifeMessage.setContext(jsonObject.toJSONString());
-            lifeMessage.setTitle("撤销注销店铺通知");
+            lifeMessage.setTitle("处理结果: 已撤回注销申请");
             lifeMessage.setSenderId("system");
             lifeMessage.setIsRead(0);
             lifeMessage.setNoticeType(1);

+ 162 - 230
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformMenuServiceImpl.java

@@ -1,284 +1,216 @@
 package shop.alien.storeplatform.service.impl;
 
-import cn.hutool.core.collection.CollectionUtil;
-import com.alibaba.nacos.common.utils.CollectionUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+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 org.springframework.beans.BeanUtils;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
-import shop.alien.entity.result.R;
-import shop.alien.entity.store.LifeGroupBuyThali;
-import shop.alien.entity.store.LifeLikeRecord;
-import shop.alien.entity.store.StoreImg;
-import shop.alien.entity.store.StoreMenu;
-import shop.alien.entity.store.vo.StoreMenuVo;
-import shop.alien.mapper.LifeGroupBuyThaliMapper;
-import shop.alien.mapper.LifeLikeRecordMapper;
-import shop.alien.mapper.StoreMenuMapper;
-import shop.alien.storeplatform.service.StorePlatformImgService;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.store.StorePlatformMenu;
+import shop.alien.mapper.StorePlatformMenuMapper;
 import shop.alien.storeplatform.service.StorePlatformMenuService;
 
-import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
- * 二期-门店推荐 服务实现类
+ * 商家PC菜单权限表 服务实现类
  *
- * @author ssk
- * @since 2024-12-05
+ * @author system
+ * @since 2025-01-XX
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
-public class StorePlatformMenuServiceImpl extends ServiceImpl<StoreMenuMapper, StoreMenu> implements StorePlatformMenuService {
+@Transactional
+public class StorePlatformMenuServiceImpl extends ServiceImpl<StorePlatformMenuMapper, StorePlatformMenu> implements StorePlatformMenuService {
 
-    private final StoreMenuMapper storeMenuMapper;
+    private final StorePlatformMenuMapper storePlatformMenuMapper;
 
-    private final LifeLikeRecordMapper lifeLikeRecordMapper;
-
-    private final StorePlatformImgService storeImgService;
-
-    private final LifeGroupBuyThaliMapper lifeGroupBuyThaliMapper;
-
-
-    /**
-     * 获取门店菜单
-     *
-     * @param storeId  门店id
-     * @param dishType 菜品类型, 0:菜单, 1:推荐
-     * @param phoneId  消息标识
-     * @return list
-     */
     @Override
-    public List<StoreMenuVo> getStoreMenu(Integer storeId, Integer dishType, String phoneId) {
-
-        if (dishType == 0) {
-            List<StoreMenuVo> collect = storeMenuMapper.getStoreMenuList(storeId, null);
-            return collect.stream().sorted(Comparator.comparing(StoreMenuVo::getSort)).collect(Collectors.toList());
-        } else {
-            List<StoreMenuVo> collect = storeMenuMapper.getStoreMenuList(storeId, dishType);
-            collect.forEach(item -> {
-                if (StringUtils.isNotEmpty(phoneId)) {
-                    LambdaQueryWrapper<LifeLikeRecord> query = new LambdaQueryWrapper<>();
-                    query.eq(LifeLikeRecord::getDianzanId, phoneId).eq(LifeLikeRecord::getHuifuId, item.getId());
-                    Integer i = lifeLikeRecordMapper.selectCount(query);
-                    if (i > 0) {
-                        item.setIsLike(1);
-                    } else {
-                        item.setIsLike(0);
-                    }
-                }
-            });
-            return collect.stream().sorted(Comparator.comparing(StoreMenuVo::getSort)).collect(Collectors.toList());
+    public IPage<StorePlatformMenu> getMenuPage(int page, int size, String menuName, String menuType, String status) {
+        LambdaQueryWrapper<StorePlatformMenu> queryWrapper = new LambdaQueryWrapper<>();
+        
+        // 菜单名称模糊查询
+        if (StringUtils.hasText(menuName)) {
+            queryWrapper.like(StorePlatformMenu::getMenuName, menuName);
+        }
+        // 菜单类型查询
+        if (StringUtils.hasText(menuType)) {
+            queryWrapper.eq(StorePlatformMenu::getMenuType, menuType);
+        }
+        // 菜单状态查询
+        if (StringUtils.hasText(status)) {
+            queryWrapper.eq(StorePlatformMenu::getStatus, status);
         }
+        
+        // 按显示顺序和创建时间排序
+        queryWrapper.orderByAsc(StorePlatformMenu::getMenuSort);
+        queryWrapper.orderByDesc(StorePlatformMenu::getCreatedTime);
+
+        return storePlatformMenuMapper.selectPage(new Page<>(page, size), queryWrapper);
     }
 
-    /**
-     * 获取菜品详情
-     *
-     * @param id 菜品id
-     * @return StoreMenuVo
-     */
     @Override
-    public StoreMenuVo getMenuInfo(Integer id) {
-        return storeMenuMapper.getMenuInfo(id);
+    public StorePlatformMenu getMenuById(Long menuId) {
+        return storePlatformMenuMapper.selectById(menuId);
     }
 
-    /**
-     * 新增或修改门店菜品
-     *
-     * @param storeMenu
-     * @return
-     */
     @Override
-    public R<String> saveOrUpdateMenu(StoreMenu storeMenu) {
-        boolean flag = false;
-        LambdaQueryWrapper<StoreMenu> storeMenuLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        //修改菜品
-        if (storeMenu.getId() != null) {
-            flag = this.updateById(storeMenu);
-            if (!flag) {
-                log.error("菜单修改失败");
-                return R.fail("菜单修改失败");
-            }
-            return R.success("菜单修改成功");
-        } else {//新增菜品
-            if (StringUtils.isEmpty(storeMenu.getDishName())) {
-                return R.fail("请输入菜品名称");
-            }
-            storeMenuLambdaQueryWrapper.eq(StoreMenu::getStoreId, storeMenu.getStoreId());
-            List<StoreMenu> menuList = this.list(storeMenuLambdaQueryWrapper);
-            if (CollectionUtil.isNotEmpty(menuList)) {
-                int maxSort = menuList.stream().map(StoreMenu::getSort).reduce(Integer::max).get();
-                storeMenu.setSort(maxSort + 1);
-            } else {
-                storeMenu.setSort(1);
-            }
-            //保存菜品
-            flag = this.save(storeMenu);
-            if (!flag) {
-                return R.fail("菜品新增失败");
-            }
+    public boolean saveMenu(StorePlatformMenu menu) {
+        // 校验必填字段
+        if (!StringUtils.hasText(menu.getMenuName())) {
+            log.error("菜单名称不能为空");
+            return false;
+        }
+        if (!StringUtils.hasText(menu.getMenuType())) {
+            log.error("菜单类型不能为空");
+            return false;
+        }
+        // 设置默认值
+        if (menu.getMenuSort() == null) {
+            menu.setMenuSort(0);
+        }
+        if (menu.getParentId() == null) {
+            menu.setParentId(0L);
+        }
+        // 自动计算菜单层级
+        if (menu.getLevel() == null) {
+            menu.setLevel(calculateMenuLevel(menu.getParentId()));
+        }
+        if (!StringUtils.hasText(menu.getStatus())) {
+            menu.setStatus("0"); // 默认正常
+        }
+        if (!StringUtils.hasText(menu.getDelFlag())) {
+            menu.setDelFlag("0"); // 默认未删除
+        }
+        if (!StringUtils.hasText(menu.getVisible())) {
+            menu.setVisible("0"); // 默认显示
+        }
+        if (!StringUtils.hasText(menu.getIsFrame())) {
+            menu.setIsFrame("1"); // 默认非外链
+        }
+        if (!StringUtils.hasText(menu.getIsCache())) {
+            menu.setIsCache("0"); // 默认缓存
         }
-        return R.success("新增菜品成功");
+        int result = storePlatformMenuMapper.insert(menu);
+        return result > 0;
     }
 
-    /**
-     * 新增或修改门店菜品new
-     *
-     * @param storeMenuVo
-     * @return
-     */
     @Override
-    public R<String> saveOrUpdateMenus(StoreMenuVo storeMenuVo) {
-        boolean flag = false;
-        LambdaQueryWrapper<StoreMenu> storeMenuLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        int imgId = 0;
-        if (storeMenuVo.getImgId() == null || storeMenuVo.getImgId() == 0) {
-            StoreImg storeImg = new StoreImg();
-            storeImg.setStoreId(storeMenuVo.getStoreId());
-            storeImg.setImgType(7);
-            storeImg.setImgUrl(storeMenuVo.getImgUrl());
-            storeImg.setImgDescription(storeMenuVo.getDishName());
-            storeImgService.saveOrUpdate(storeImg);
-            imgId = storeImg.getId();
-        } else {
-            imgId = storeMenuVo.getImgId();
+    public boolean updateMenu(StorePlatformMenu menu) {
+        if (menu.getMenuId() == null) {
+            log.error("菜单ID不能为空");
+            return false;
         }
 
-        // 封装storeMenu参数
-        StoreMenu storeMenu = new StoreMenu();
-        BeanUtils.copyProperties(storeMenuVo, storeMenu);
-        storeMenu.setImgId(imgId);
+        int result = storePlatformMenuMapper.updateById(menu);
+        return result > 0;
+    }
 
-        //修改菜品
-        if (storeMenu.getId() != null) {
-            flag = this.updateById(storeMenu);
-            if (!flag) {
-                log.error("菜单修改失败");
-                return R.fail("菜单修改失败");
-            }
-            return R.success("菜单修改成功");
-        } else {
-            //新增菜品
-            // 校验菜品参数
-            if (StringUtils.isEmpty(storeMenu.getDishName())) {
-                return R.fail("请输入菜品名称");
-            }
+    @Override
+    public boolean deleteMenu(Long menuId) {
+        // 逻辑删除
+        LambdaUpdateWrapper<StorePlatformMenu> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(StorePlatformMenu::getMenuId, menuId);
+        updateWrapper.set(StorePlatformMenu::getDelFlag, "2"); // 2代表删除
+        
+        int result = storePlatformMenuMapper.update(null, updateWrapper);
+        return result > 0;
+    }
 
-            storeMenuLambdaQueryWrapper.eq(StoreMenu::getStoreId, storeMenu.getStoreId());
-            List<StoreMenu> menuList = this.list(storeMenuLambdaQueryWrapper);
-            if (CollectionUtil.isNotEmpty(menuList)) {
-                int maxSort = menuList.stream().map(StoreMenu::getSort).reduce(Integer::max).get();
-                storeMenu.setSort(maxSort + 1);
-            } else {
-                storeMenu.setSort(1);
-            }
-            //保存菜品
-            flag = this.save(storeMenu);
-            if (!flag) {
-                return R.fail("菜品新增失败");
-            }
+    @Override
+    public boolean updateStatus(Long menuId, String status) {
+        if (menuId == null) {
+            log.error("菜单ID不能为空");
+            return false;
         }
-        return R.success("新增菜品成功");
+        if (!StringUtils.hasText(status)) {
+            log.error("菜单状态不能为空");
+            return false;
+        }
+        
+        LambdaUpdateWrapper<StorePlatformMenu> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(StorePlatformMenu::getMenuId, menuId);
+        updateWrapper.set(StorePlatformMenu::getStatus, status);
+        
+        int result = storePlatformMenuMapper.update(null, updateWrapper);
+        return result > 0;
     }
 
+    @Override
+    public List<StorePlatformMenu> getMenuTree() {
+        // 查询所有未删除的菜单
+        LambdaQueryWrapper<StorePlatformMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformMenu::getDelFlag, "0");
+        queryWrapper.eq(StorePlatformMenu::getStatus, "0"); // 正常状态
+        queryWrapper.orderByAsc(StorePlatformMenu::getMenuSort);
+        List<StorePlatformMenu> allMenus = storePlatformMenuMapper.selectList(queryWrapper);
+        
+
+        return buildMenuTree(allMenus, 0L);
+    }
 
-    /**
-     * 菜品排序信息
-     *
-     * @param storeMenuVo
-     * @return
-     */
     @Override
-    public Boolean getSortMenuInfo(StoreMenuVo storeMenuVo) {
-        boolean flag = false;
-        if (CollectionUtil.isNotEmpty(storeMenuVo.getSortList())) {
-            for (int i = 0; i < storeMenuVo.getSortList().size(); i++) {
-                LambdaQueryWrapper<StoreMenu> lambdaQueryWrapper = new LambdaQueryWrapper();
-                lambdaQueryWrapper.eq(StoreMenu::getId, storeMenuVo.getSortList().get(i).getId());
-                StoreMenu storeMenu = this.getOne(lambdaQueryWrapper);
-                storeMenu.setSort(i + 1);
-                flag = this.updateById(storeMenu);
-            }
-        }
-        return flag;
+    public List<StorePlatformMenu> getMenusByParentId(Long parentId) {
+        LambdaQueryWrapper<StorePlatformMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformMenu::getParentId, parentId);
+        queryWrapper.eq(StorePlatformMenu::getDelFlag, "0");
+        queryWrapper.eq(StorePlatformMenu::getStatus, "0");
+        queryWrapper.orderByAsc(StorePlatformMenu::getMenuSort);
+        
+        return storePlatformMenuMapper.selectList(queryWrapper);
     }
 
     @Override
-    public Boolean saveMenuSort(List<StoreMenu> storeMenuList) {
-        return this.updateBatchById(storeMenuList);
+    public List<StorePlatformMenu> getMenusByLevel(Integer level) {
+        LambdaQueryWrapper<StorePlatformMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformMenu::getLevel, level);
+        queryWrapper.eq(StorePlatformMenu::getDelFlag, "0");
+        queryWrapper.eq(StorePlatformMenu::getStatus, "0");
+        queryWrapper.orderByAsc(StorePlatformMenu::getMenuSort);
+        
+        return storePlatformMenuMapper.selectList(queryWrapper);
     }
 
     /**
-     * 删除菜品或推荐菜 根据类型区分
-     *
-     * @param ids
-     * @param dishType 0:菜品 1:推荐菜
-     * @return
+     * 构建菜单树
      */
-    @Override
-    public R deleteMenu(List<Integer> ids, int dishType) {
-        QueryWrapper<StoreMenu> queryWrapperMenu = new QueryWrapper<>();
-        boolean flag = false;
-        if (dishType == 0) {
-            List<LifeGroupBuyThali> lifeGroupBuyThaliList = lifeGroupBuyThaliMapper.selectList(new LambdaQueryWrapper<LifeGroupBuyThali>().in(LifeGroupBuyThali::getDetailId, ids));
-            if (CollectionUtil.isNotEmpty(lifeGroupBuyThaliList)) {
-                return R.fail("该菜品已被团购套餐引用,不能删除");
-            }
-            flag = this.removeByIds(ids);
-        } else {
-            queryWrapperMenu.in("id", ids);
-            List<StoreMenu> storeMenuList = baseMapper.selectList(queryWrapperMenu);
-            storeMenuList.forEach(item -> {
-                item.setDishType(0);
-                this.updateById(item);
-            });
-            flag = true;
-        }
-        if (!flag) {
-            return R.fail("删除失败");
-        }
-        return R.success("删除成功");
-    }
-    @Override
-    public StoreMenuVo getMenuCountByStoreId(int storeId) {
-        StoreMenuVo storeMenuVo = new StoreMenuVo();
-        LambdaUpdateWrapper<StoreMenu> updateWrapper = new LambdaUpdateWrapper<>();
-        updateWrapper.eq(StoreMenu::getStoreId, storeId);
-        updateWrapper.eq(StoreMenu::getDeleteFlag, 0);
-        List<StoreMenu> storeMenuList = storeMenuMapper.selectList(updateWrapper);
-        if (CollectionUtils.isNotEmpty(storeMenuList)) {
-            storeMenuVo.setMenuCount(storeMenuList.size());
-            Map<Integer, List<StoreMenu>> groupByDishType = storeMenuList.stream()
-                    .collect(Collectors.groupingBy(StoreMenu::getDishType));
-            List<StoreMenu> suggestList = groupByDishType.get(1);
-            if (CollectionUtils.isNotEmpty(suggestList)) {
-                storeMenuVo.setSuggestCount(suggestList.size());
-            } else {
-                storeMenuVo.setSuggestCount(0);
-            }
-        } else {
-            storeMenuVo.setMenuCount(0);
-            storeMenuVo.setSuggestCount(0);
-        }
-        return storeMenuVo;
+    private List<StorePlatformMenu> buildMenuTree(List<StorePlatformMenu> allMenus, Long parentId) {
+        return allMenus.stream()
+                .filter(menu -> parentId.equals(menu.getParentId()))
+                .map(menu -> {
+                    menu.setChildren(buildMenuTree(allMenus, menu.getMenuId()));
+                    return menu;
+                })
+                .collect(Collectors.toList());
     }
 
     /**
-     * 获取用户对该菜品是否点赞
+     * 计算菜单层级
+     * 根据父菜单ID计算当前菜单的层级
      *
-     * @param userId 用户id
-     * @param menuId 菜品id
-     * @return boolean
+     * @param parentId 父菜单ID
+     * @return 菜单层级(1一级菜单 2二级菜单 3三级菜单)
      */
-    @Override
-    public boolean getMenuLikeStatus(String userId, Integer menuId) {
-        return lifeLikeRecordMapper.selectCount(new QueryWrapper<LifeLikeRecord>().eq("dianzan_id", userId).eq("huifu_id", menuId).eq("delete_flag", 0)) > 0;
+    private Integer calculateMenuLevel(Long parentId) {
+        if (parentId == null || parentId == 0L) {
+            return 1; // 一级菜单
+        }
+        // 查询父菜单的层级
+        StorePlatformMenu parentMenu = storePlatformMenuMapper.selectById(parentId);
+        if (parentMenu == null) {
+            return 1; // 如果父菜单不存在,默认为一级菜单
+        }
+        // 父菜单层级 + 1
+        Integer parentLevel = parentMenu.getLevel();
+        if (parentLevel == null) {
+            return 2; // 如果父菜单没有层级信息,默认为二级菜单
+        }
+        return parentLevel + 1;
     }
 }

+ 319 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRoleMenuServiceImpl.java

@@ -0,0 +1,319 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.StorePlatformMenu;
+import shop.alien.entity.store.StorePlatformRoleMenu;
+import shop.alien.entity.store.vo.RolePermissionTableVo;
+import shop.alien.entity.store.vo.RolePermissionVo;
+import shop.alien.mapper.StorePlatformMenuMapper;
+import shop.alien.mapper.StorePlatformRoleMenuMapper;
+import shop.alien.storeplatform.service.StorePlatformRoleMenuService;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 角色菜单关联表 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class StorePlatformRoleMenuServiceImpl extends ServiceImpl<StorePlatformRoleMenuMapper, StorePlatformRoleMenu> implements StorePlatformRoleMenuService {
+
+    private final StorePlatformRoleMenuMapper storePlatformRoleMenuMapper;
+    private final StorePlatformMenuMapper storePlatformMenuMapper;
+
+    @Override
+    public List<Long> getMenuIdsByRoleId(Long roleId) {
+        LambdaQueryWrapper<StorePlatformRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformRoleMenu::getRoleId, roleId);
+        List<StorePlatformRoleMenu> list = storePlatformRoleMenuMapper.selectList(queryWrapper);
+        return list.stream().map(StorePlatformRoleMenu::getMenuId).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<Long> getRoleIdsByMenuId(Long menuId) {
+        LambdaQueryWrapper<StorePlatformRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformRoleMenu::getMenuId, menuId);
+        List<StorePlatformRoleMenu> list = storePlatformRoleMenuMapper.selectList(queryWrapper);
+        return list.stream().map(StorePlatformRoleMenu::getRoleId).collect(Collectors.toList());
+    }
+
+    @Override
+    public boolean assignMenus(Long roleId, List<Long> menuIds) {
+        if (roleId == null || menuIds == null || menuIds.isEmpty()) {
+            log.error("参数不能为空");
+            return false;
+        }
+        // 先删除角色的所有菜单
+        removeAllMenus(roleId);
+        // 批量插入新菜单
+        List<StorePlatformRoleMenu> roleMenus = menuIds.stream()
+                .map(menuId -> {
+                    StorePlatformRoleMenu roleMenu = new StorePlatformRoleMenu();
+                    roleMenu.setRoleId(roleId);
+                    roleMenu.setMenuId(menuId);
+                    return roleMenu;
+                })
+                .collect(Collectors.toList());
+        return this.saveBatch(roleMenus);
+    }
+
+    @Override
+    public boolean removeAllMenus(Long roleId) {
+        LambdaQueryWrapper<StorePlatformRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformRoleMenu::getRoleId, roleId);
+        return storePlatformRoleMenuMapper.delete(queryWrapper) >= 0;
+    }
+
+    @Override
+    public boolean addMenu(Long roleId, Long menuId) {
+        if (roleId == null || menuId == null) {
+            log.error("参数不能为空");
+            return false;
+        }
+        // 检查是否已存在
+        LambdaQueryWrapper<StorePlatformRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformRoleMenu::getRoleId, roleId)
+                .eq(StorePlatformRoleMenu::getMenuId, menuId);
+        StorePlatformRoleMenu exist = storePlatformRoleMenuMapper.selectOne(queryWrapper);
+        if (exist != null) {
+            log.warn("角色菜单关联已存在,roleId={}, menuId={}", roleId, menuId);
+            return true;
+        }
+        StorePlatformRoleMenu roleMenu = new StorePlatformRoleMenu();
+        roleMenu.setRoleId(roleId);
+        roleMenu.setMenuId(menuId);
+        return storePlatformRoleMenuMapper.insert(roleMenu) > 0;
+    }
+
+    @Override
+    public boolean removeMenu(Long roleId, Long menuId) {
+        LambdaQueryWrapper<StorePlatformRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformRoleMenu::getRoleId, roleId)
+                .eq(StorePlatformRoleMenu::getMenuId, menuId);
+        return storePlatformRoleMenuMapper.delete(queryWrapper) > 0;
+    }
+
+    @Override
+    public List<RolePermissionVo> getRolePermissionTree(Long roleId) {
+        if (roleId == null) {
+            return new ArrayList<>();
+        }
+        
+        // 1. 查询角色拥有的菜单ID列表
+        List<Long> menuIds = getMenuIdsByRoleId(roleId);
+        if (menuIds == null || menuIds.isEmpty()) {
+            return new ArrayList<>();
+        }
+        
+        // 2. 查询这些菜单的详细信息
+        LambdaQueryWrapper<StorePlatformMenu> menuQueryWrapper = new LambdaQueryWrapper<>();
+        menuQueryWrapper.in(StorePlatformMenu::getMenuId, menuIds);
+        menuQueryWrapper.eq(StorePlatformMenu::getDelFlag, "0");
+        menuQueryWrapper.eq(StorePlatformMenu::getStatus, "0");
+        menuQueryWrapper.orderByAsc(StorePlatformMenu::getMenuSort);
+        List<StorePlatformMenu> menus = storePlatformMenuMapper.selectList(menuQueryWrapper);
+        
+        if (menus == null || menus.isEmpty()) {
+            return new ArrayList<>();
+        }
+        
+        // 3. 转换为VO
+        List<RolePermissionVo> permissionVos = menus.stream()
+                .map(menu -> {
+                    RolePermissionVo vo = new RolePermissionVo();
+                    BeanUtils.copyProperties(menu, vo);
+                    return vo;
+                })
+                .collect(Collectors.toList());
+        
+        // 4. 构建树形结构
+        return buildPermissionTree(permissionVos);
+    }
+
+    /**
+     * 构建权限树形结构
+     */
+    private List<RolePermissionVo> buildPermissionTree(List<RolePermissionVo> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+        
+        // 创建映射:ID到节点的映射,父ID到子节点列表的映射
+        Map<Long, RolePermissionVo> nodeMap = new HashMap<>();
+        Map<Long, List<RolePermissionVo>> parentChildMap = new HashMap<>();
+        List<RolePermissionVo> result = new ArrayList<>();
+        
+        // 第一步:建立映射关系
+        for (RolePermissionVo vo : flatList) {
+            Long menuId = vo.getMenuId();
+            Long parentId = vo.getParentId() != null ? vo.getParentId() : 0L;
+            
+            // 存入节点映射
+            nodeMap.put(menuId, vo);
+            
+            // 如果是根节点(parentId为0或null),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(vo);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(vo);
+            }
+        }
+        
+        // 第二步:建立父子关系
+        for (RolePermissionVo vo : nodeMap.values()) {
+            Long menuId = vo.getMenuId();
+            if (parentChildMap.containsKey(menuId)) {
+                // 对子节点按排序值排序
+                List<RolePermissionVo> children = parentChildMap.get(menuId);
+                children.sort(Comparator.comparing(RolePermissionVo::getMenuSort, Comparator.nullsLast(Integer::compareTo)));
+                vo.setChildren(children);
+            }
+        }
+        
+        // 对根节点按排序值排序
+        result.sort(Comparator.comparing(RolePermissionVo::getMenuSort, Comparator.nullsLast(Integer::compareTo)));
+        
+        return result;
+    }
+
+    @Override
+    public List<RolePermissionTableVo> getRolePermissionTable(Long roleId) {
+        if (roleId == null) {
+            return new ArrayList<>();
+        }
+
+        // 1. 查询角色拥有的菜单ID列表
+        List<Long> menuIds = getMenuIdsByRoleId(roleId);
+        if (menuIds == null || menuIds.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 2. 查询这些菜单的详细信息
+        LambdaQueryWrapper<StorePlatformMenu> menuQueryWrapper = new LambdaQueryWrapper<>();
+        menuQueryWrapper.in(StorePlatformMenu::getMenuId, menuIds);
+        menuQueryWrapper.eq(StorePlatformMenu::getDelFlag, "0");
+        menuQueryWrapper.eq(StorePlatformMenu::getStatus, "0");
+        menuQueryWrapper.orderByAsc(StorePlatformMenu::getMenuSort);
+        List<StorePlatformMenu> menus = storePlatformMenuMapper.selectList(menuQueryWrapper);
+
+        if (menus == null || menus.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 3. 找出最高层级
+        Integer maxLevel = menus.stream()
+                .map(StorePlatformMenu::getLevel)
+                .filter(Objects::nonNull)
+                .max(Integer::compareTo)
+                .orElse(1);
+
+        // 4. 如果需要,补充父级权限
+        Set<Long> allMenuIds = new HashSet<>(menuIds);
+        if (maxLevel >= 3) {
+            // 如果有三级权限,需要补充一级和二级父权限
+            for (StorePlatformMenu menu : menus) {
+                if (menu.getLevel() != null && menu.getLevel() == 3) {
+                    // 补充二级父权限
+                    if (menu.getParentId() != null && menu.getParentId() != 0) {
+                        allMenuIds.add(menu.getParentId());
+                        // 补充一级父权限
+                        StorePlatformMenu level2Menu = storePlatformMenuMapper.selectById(menu.getParentId());
+                        if (level2Menu != null && level2Menu.getParentId() != null && level2Menu.getParentId() != 0) {
+                            allMenuIds.add(level2Menu.getParentId());
+                        }
+                    }
+                }
+            }
+        } else if (maxLevel == 2) {
+            // 如果只有二级权限,需要补充一级父权限
+            for (StorePlatformMenu menu : menus) {
+                if (menu.getLevel() != null && menu.getLevel() == 2) {
+                    if (menu.getParentId() != null && menu.getParentId() != 0) {
+                        allMenuIds.add(menu.getParentId());
+                    }
+                }
+            }
+        }
+
+        // 5. 查询所有需要的菜单(包括补充的父级权限)
+        if (allMenuIds.size() > menuIds.size()) {
+            LambdaQueryWrapper<StorePlatformMenu> allMenuQueryWrapper = new LambdaQueryWrapper<>();
+            allMenuQueryWrapper.in(StorePlatformMenu::getMenuId, allMenuIds);
+            allMenuQueryWrapper.eq(StorePlatformMenu::getDelFlag, "0");
+            allMenuQueryWrapper.eq(StorePlatformMenu::getStatus, "0");
+            allMenuQueryWrapper.orderByAsc(StorePlatformMenu::getMenuSort);
+            menus = storePlatformMenuMapper.selectList(allMenuQueryWrapper);
+        }
+
+        // 6. 按层级分组
+        Map<Integer, List<StorePlatformMenu>> menusByLevel = menus.stream()
+                .filter(menu -> menu.getLevel() != null)
+                .collect(Collectors.groupingBy(StorePlatformMenu::getLevel));
+
+        // 7. 构建表格数据
+        List<RolePermissionTableVo> tableData = new ArrayList<>();
+
+        // 获取一级权限
+        List<StorePlatformMenu> level1Menus = menusByLevel.getOrDefault(1, new ArrayList<>());
+        level1Menus.sort(Comparator.comparing(StorePlatformMenu::getMenuSort, Comparator.nullsLast(Integer::compareTo)));
+
+        for (StorePlatformMenu level1Menu : level1Menus) {
+            // 获取该一级权限下的二级权限
+            List<StorePlatformMenu> level2Menus = menusByLevel.getOrDefault(2, new ArrayList<>()).stream()
+                    .filter(menu -> level1Menu.getMenuId().equals(menu.getParentId()))
+                    .sorted(Comparator.comparing(StorePlatformMenu::getMenuSort, Comparator.nullsLast(Integer::compareTo)))
+                    .collect(Collectors.toList());
+
+            if (level2Menus.isEmpty()) {
+                // 如果只有一级权限,只显示一级权限
+                RolePermissionTableVo vo = new RolePermissionTableVo();
+                vo.setLevel1Permission(level1Menu.getMenuName());
+                tableData.add(vo);
+            } else {
+                // 如果有二级权限,遍历二级权限
+                for (StorePlatformMenu level2Menu : level2Menus) {
+                    // 获取该二级权限下的三级权限
+                    List<StorePlatformMenu> level3Menus = menusByLevel.getOrDefault(3, new ArrayList<>()).stream()
+                            .filter(menu -> level2Menu.getMenuId().equals(menu.getParentId()))
+                            .sorted(Comparator.comparing(StorePlatformMenu::getMenuSort, Comparator.nullsLast(Integer::compareTo)))
+                            .collect(Collectors.toList());
+
+                    if (level3Menus.isEmpty()) {
+                        // 如果只有二级权限,显示一级和二级权限
+                        RolePermissionTableVo vo = new RolePermissionTableVo();
+                        vo.setLevel1Permission(level1Menu.getMenuName());
+                        vo.setLevel2Permission(level2Menu.getMenuName());
+                        tableData.add(vo);
+                    } else {
+                        // 如果有三级权限,显示一级、二级、三级权限
+                        // 将三级权限名称用空格连接
+                        String level3PermissionNames = level3Menus.stream()
+                                .map(StorePlatformMenu::getMenuName)
+                                .collect(Collectors.joining(" "));
+                        RolePermissionTableVo vo = new RolePermissionTableVo();
+                        vo.setLevel1Permission(level1Menu.getMenuName());
+                        vo.setLevel2Permission(level2Menu.getMenuName());
+                        vo.setLevel3Permission(level3PermissionNames);
+                        tableData.add(vo);
+                    }
+                }
+            }
+        }
+
+        return tableData;
+    }
+}
+

+ 26 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformRoleQueryServiceImpl.java

@@ -0,0 +1,26 @@
+package shop.alien.storeplatform.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import shop.alien.mapper.StorePlatformRoleMapper;
+import shop.alien.storeplatform.service.StorePlatformRoleQueryService;
+
+/**
+ * 角色查询服务实现(SQL实现)
+ */
+@Service
+@RequiredArgsConstructor
+public class StorePlatformRoleQueryServiceImpl implements StorePlatformRoleQueryService {
+
+    private final StorePlatformRoleMapper storePlatformRoleMapper;
+
+    @Override
+    public String getRoleNameById(Long roleId) {
+        if (roleId == null) {
+            return null;
+        }
+        String roleName = storePlatformRoleMapper.selectRoleNameById(roleId);
+        return StringUtils.hasText(roleName) ? roleName : null;
+    }
+}

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

@@ -0,0 +1,410 @@
+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;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+import shop.alien.config.redis.BaseRedisService;
+import shop.alien.entity.store.StorePlatformRole;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.vo.StorePlatformRoleVo;
+import shop.alien.mapper.StorePlatformRoleMapper;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.storeplatform.service.StorePlatformRoleMenuService;
+import shop.alien.storeplatform.service.StorePlatformRoleService;
+import shop.alien.storeplatform.service.StorePlatformUserRoleService;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 平台角色信息表 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class StorePlatformRoleServiceImpl extends ServiceImpl<StorePlatformRoleMapper, StorePlatformRole> implements StorePlatformRoleService {
+
+    private final StorePlatformRoleMapper storePlatformRoleMapper;
+    private final StorePlatformRoleMenuService storePlatformRoleMenuService;
+    private final StorePlatformUserRoleService storePlatformUserRoleService;
+    private final StoreUserMapper storeUserMapper;
+    private final BaseRedisService baseRedisService;
+
+    @Override
+    public IPage<StorePlatformRole> getRolePage(int page, int size, Integer storeId, String roleName, String description, String status) {
+        LambdaQueryWrapper<StorePlatformRole> queryWrapper = new LambdaQueryWrapper<>();
+        
+        // 店铺ID查询
+        if (storeId != null) {
+            queryWrapper.eq(StorePlatformRole::getStoreId, storeId);
+        }
+        
+        // 角色名称模糊查询
+        if (StringUtils.hasText(roleName)) {
+            queryWrapper.like(StorePlatformRole::getRoleName, roleName);
+        }
+        // 角色描述模糊查询
+        if (StringUtils.hasText(description)) {
+            queryWrapper.like(StorePlatformRole::getDescription, description);
+        }
+        
+        // 角色状态查询
+        if (StringUtils.hasText(status)) {
+            queryWrapper.eq(StorePlatformRole::getStatus, status);
+        }
+        
+        // 按显示顺序和创建时间排序
+        queryWrapper.orderByAsc(StorePlatformRole::getRoleSort);
+        queryWrapper.orderByDesc(StorePlatformRole::getCreatedTime);
+
+        return storePlatformRoleMapper.selectPage(new Page<>(page, size), queryWrapper);
+    }
+
+    @Override
+    public StorePlatformRole getRoleById(Long roleId) {
+        return storePlatformRoleMapper.selectById(roleId);
+    }
+
+    @Override
+    public boolean saveRole(StorePlatformRole role) {
+        // 校验必填字段
+        if (!StringUtils.hasText(role.getRoleName())) {
+            log.error("角色名称不能为空");
+            return false;
+        }
+        // 检查角色名称是否已存在(同一店铺内不能重复)
+        if (checkRoleNameExists(role.getStoreId(), role.getRoleName(), null)) {
+            log.error("角色名称已存在:{}", role.getRoleName());
+            return false;
+        }
+        // 设置默认值
+        if (role.getRoleSort() == null) {
+            role.setRoleSort(0);
+        }
+        if (!StringUtils.hasText(role.getStatus())) {
+            role.setStatus("0"); // 默认正常
+        }
+        if (!StringUtils.hasText(role.getDelFlag())) {
+            role.setDelFlag("0"); // 默认未删除
+        }
+        int result = storePlatformRoleMapper.insert(role);
+        return result > 0;
+    }
+
+    @Override
+    public boolean createRoleWithMenus(StorePlatformRole role, List<Long> menuIds) {
+        // 校验必填字段
+        if (!StringUtils.hasText(role.getRoleName())) {
+            log.error("角色名称不能为空");
+            return false;
+        }
+        // 检查角色名称是否已存在(同一店铺内不能重复)
+        if (checkRoleNameExists(role.getStoreId(), role.getRoleName(), null)) {
+            log.error("角色名称已存在,不允许重复添加:{}", role.getRoleName());
+            return false;
+        }
+        // 设置默认值
+        if (role.getRoleSort() == null) {
+            role.setRoleSort(0);
+        }
+        if (!StringUtils.hasText(role.getStatus())) {
+            role.setStatus("0"); // 默认正常
+        }
+        if (!StringUtils.hasText(role.getDelFlag())) {
+            role.setDelFlag("0"); // 默认未删除
+        }
+        // 保存角色
+        int result = storePlatformRoleMapper.insert(role);
+        if (result <= 0) {
+            log.error("保存角色失败");
+            return false;
+        }
+        // 如果有菜单权限,保存到关联表
+        if (menuIds != null && !menuIds.isEmpty()) {
+            boolean assignResult = storePlatformRoleMenuService.assignMenus(role.getRoleId(), menuIds);
+            if (!assignResult) {
+                log.error("分配菜单权限失败,角色ID:{}", role.getRoleId());
+                // 如果分配权限失败,可以考虑回滚角色创建,这里先记录日志
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean checkRoleNameExists(Integer storeId, String roleName, Long excludeRoleId) {
+        if (!StringUtils.hasText(roleName)) {
+            return false;
+        }
+        LambdaQueryWrapper<StorePlatformRole> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformRole::getRoleName, roleName);
+        queryWrapper.eq(StorePlatformRole::getDelFlag, "0"); // 只查询未删除的
+        // 店铺ID查询(同一店铺内角色名称不能重复)
+        if (storeId != null) {
+            queryWrapper.eq(StorePlatformRole::getStoreId, storeId);
+        }
+        // 如果指定了排除的角色ID,则排除该角色
+        if (excludeRoleId != null) {
+            queryWrapper.ne(StorePlatformRole::getRoleId, excludeRoleId);
+        }
+        long count = storePlatformRoleMapper.selectCount(queryWrapper);
+        return count > 0;
+    }
+
+    @Override
+    public boolean updateRole(StorePlatformRole role) {
+        if (role.getRoleId() == null) {
+            log.error("角色ID不能为空");
+            return false;
+        }
+
+        int result = storePlatformRoleMapper.updateById(role);
+        return result > 0;
+    }
+
+    @Override
+    public boolean updateRoleWithMenus(StorePlatformRole role, List<Long> menuIds) {
+        if (role.getRoleId() == null) {
+            log.error("角色ID不能为空");
+            return false;
+        }
+
+        // 1. 查询原始角色信息
+        StorePlatformRole originalRole = storePlatformRoleMapper.selectById(role.getRoleId());
+        if (originalRole == null) {
+            log.error("角色不存在,roleId={}", role.getRoleId());
+            return false;
+        }
+
+        // 2. 校验角色类型(roleName)是否变化
+        boolean roleNameChanged = false;
+        if (StringUtils.hasText(role.getRoleName()) && 
+            !role.getRoleName().equals(originalRole.getRoleName())) {
+            // 角色名称变化,需要校验是否重复
+            // 使用传入的 storeId,如果没有则使用原始角色的 storeId
+            Integer storeId = role.getStoreId() != null ? role.getStoreId() : originalRole.getStoreId();
+            if (checkRoleNameExists(storeId, role.getRoleName(), role.getRoleId())) {
+                log.error("角色名称已存在,不允许重复:{}", role.getRoleName());
+                return false;
+            }
+            roleNameChanged = true;
+            log.info("角色名称发生变化:{} -> {}", originalRole.getRoleName(), role.getRoleName());
+        }
+
+        // 3. 如果角色类型有变化,更新 store_platform_role 表
+        if (roleNameChanged || role.getRoleSort() != null || 
+            StringUtils.hasText(role.getStatus()) || StringUtils.hasText(role.getDescription()) ||
+            StringUtils.hasText(role.getRoleNameEn()) || StringUtils.hasText(role.getRemark())) {
+            // 只更新传入的字段,保留原有字段
+            StorePlatformRole updateRole = new StorePlatformRole();
+            updateRole.setRoleId(role.getRoleId());
+            if (roleNameChanged && StringUtils.hasText(role.getRoleName())) {
+                updateRole.setRoleName(role.getRoleName());
+            }
+            if (role.getRoleSort() != null) {
+                updateRole.setRoleSort(role.getRoleSort());
+            }
+            if (StringUtils.hasText(role.getStatus())) {
+                updateRole.setStatus(role.getStatus());
+            }
+            if (StringUtils.hasText(role.getDescription())) {
+                updateRole.setDescription(role.getDescription());
+            }
+            if (StringUtils.hasText(role.getRoleNameEn())) {
+                updateRole.setRoleNameEn(role.getRoleNameEn());
+            }
+            if (StringUtils.hasText(role.getRemark())) {
+                updateRole.setRemark(role.getRemark());
+            }
+            
+            int updateResult = storePlatformRoleMapper.updateById(updateRole);
+            if (updateResult <= 0) {
+                log.error("更新角色信息失败,roleId={}", role.getRoleId());
+                return false;
+            }
+            log.info("成功更新角色信息,roleId={}", role.getRoleId());
+        }
+
+        // 4. 查询当前角色的权限列表
+        List<Long> currentMenuIds = storePlatformRoleMenuService.getMenuIdsByRoleId(role.getRoleId());
+        
+        // 5. 比较权限是否变化
+        boolean permissionsChanged = false;
+        if (menuIds != null) {
+            // 排序后比较,避免顺序不同导致的误判
+            List<Long> sortedCurrent = currentMenuIds != null ? 
+                currentMenuIds.stream().sorted().collect(Collectors.toList()) : 
+                java.util.Collections.emptyList();
+            List<Long> sortedNew = menuIds.stream().sorted().collect(Collectors.toList());
+            
+            if (!sortedCurrent.equals(sortedNew)) {
+                permissionsChanged = true;
+                log.info("权限发生变化,roleId={}, 原权限={}, 新权限={}", 
+                    role.getRoleId(), sortedCurrent, sortedNew);
+            }
+        } else {
+            // 如果传入的 menuIds 为 null,但当前有权限,也算变化
+            if (currentMenuIds != null && !currentMenuIds.isEmpty()) {
+                permissionsChanged = true;
+                log.info("权限发生变化(清空权限),roleId={}, 原权限={}", role.getRoleId(), currentMenuIds);
+            }
+        }
+
+        // 6. 如果权限有变化,更新 store_platform_role_menu 表
+        if (permissionsChanged) {
+            // 先删除所有现有权限
+            boolean removeResult = storePlatformRoleMenuService.removeAllMenus(role.getRoleId());
+            if (!removeResult) {
+                log.error("删除角色权限失败,roleId={}", role.getRoleId());
+                return false;
+            }
+            
+            // 如果有新权限,则添加
+            if (menuIds != null && !menuIds.isEmpty()) {
+                boolean assignResult = storePlatformRoleMenuService.assignMenus(role.getRoleId(), menuIds);
+                if (!assignResult) {
+                    log.error("分配角色权限失败,roleId={}", role.getRoleId());
+                    return false;
+                }
+            }
+            //角色被编辑 清除当前被编辑角色的账号的token
+            try {
+                // 获取该角色下的所有用户ID
+                Integer storeId = role.getStoreId() != null ? role.getStoreId() : originalRole.getStoreId();
+                List<Integer> userIds = storePlatformUserRoleService.getUserIdsByRoleId(role.getRoleId(), storeId);
+                if (userIds != null && !userIds.isEmpty()) {
+                    // 根据用户ID查询用户信息,获取手机号
+                    for (Integer userId : userIds) {
+                        StoreUser storeUser = storeUserMapper.selectById(userId);
+                        if (storeUser != null && storeUser.getPhone() != null) {
+                            // 删除Redis中的token,key格式:storePlatform_手机号
+                            String tokenKey = "store_" + storeUser.getPhone();
+                            String existingToken = baseRedisService.getString(tokenKey);
+                            if (existingToken != null) {
+                                baseRedisService.delete(tokenKey);
+                                log.info("清除角色编辑后的用户token成功,roleId={}, userId={}, phone={}, tokenKey={}", 
+                                        role.getRoleId(), userId, storeUser.getPhone(), tokenKey);
+                            } else {
+                                log.warn("用户token不存在或已过期,roleId={}, userId={}, phone={}, tokenKey={}", 
+                                        role.getRoleId(), userId, storeUser.getPhone(), tokenKey);
+                            }
+                        }
+                    }
+                } else {
+                    log.info("该角色下没有关联用户,无需清除token,roleId={}", role.getRoleId());
+                }
+            } catch (Exception e) {
+                log.error("清除角色编辑后的用户token失败,roleId={}, error={}", role.getRoleId(), e.getMessage(), e);
+                // 不清除token不影响角色更新,只记录错误日志
+            }
+            log.info("成功更新角色权限,roleId={}, menuIds={}", role.getRoleId(), menuIds);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean deleteRole(Long roleId) {
+        // 逻辑删除
+        LambdaUpdateWrapper<StorePlatformRole> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(StorePlatformRole::getRoleId, roleId);
+        updateWrapper.set(StorePlatformRole::getDelFlag, "2"); // 2代表删除
+        
+        int result = storePlatformRoleMapper.update(null, updateWrapper);
+        return result > 0;
+    }
+
+    @Override
+    public String deleteRoleWithCheck(Long roleId) {
+        if (roleId == null) {
+            log.error("角色ID不能为空");
+            return "角色ID不能为空";
+        }
+
+        // 查询角色信息,获取 storeId
+        StorePlatformRole role = storePlatformRoleMapper.selectById(roleId);
+        if (role == null) {
+            log.error("角色不存在: roleId={}", roleId);
+            return "角色不存在";
+        }
+
+        // 检查角色是否有关联的子账号
+        List<Integer> userIds = storePlatformUserRoleService.getUserIdsByRoleId(roleId, role.getStoreId());
+        if (userIds != null && !userIds.isEmpty()) {
+            log.warn("角色有关联的子账号,不允许删除: roleId={}, 关联子账号数量={}", roleId, userIds.size());
+            return "该角色下存在关联的子账号,不允许删除";
+        }
+
+        // 执行逻辑删除
+        LambdaUpdateWrapper<StorePlatformRole> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(StorePlatformRole::getRoleId, roleId);
+        updateWrapper.set(StorePlatformRole::getDelFlag, "2"); // 2代表删除
+
+        int result = storePlatformRoleMapper.update(null, updateWrapper);
+        if (result > 0) {
+            log.info("角色逻辑删除成功: roleId={}", roleId);
+            return null; // 成功返回null
+        } else {
+            log.error("角色逻辑删除失败: roleId={}", roleId);
+            return "删除失败";
+        }
+    }
+
+    @Override
+    public List<StorePlatformRole> getAllNormalRoles(Integer storeId) {
+        LambdaQueryWrapper<StorePlatformRole> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformRole::getStatus, "0"); // 正常状态
+        queryWrapper.eq(StorePlatformRole::getDelFlag, "0"); // 未删除
+        // 店铺ID查询
+        if (storeId != null) {
+            queryWrapper.eq(StorePlatformRole::getStoreId, storeId);
+        }
+        queryWrapper.orderByAsc(StorePlatformRole::getRoleSort);
+        
+        return storePlatformRoleMapper.selectList(queryWrapper);
+    }
+
+    @Override
+    public IPage<StorePlatformRoleVo> getRolePageWithStats(int page, int size, Integer storeId, String roleName, String description, String status) {
+        // 先查询角色分页数据
+        IPage<StorePlatformRole> rolePage = getRolePage(page, size, storeId, roleName, description, status);
+        
+        // 转换为VO并填充统计信息
+        IPage<StorePlatformRoleVo> voPage = new Page<>(rolePage.getCurrent(), rolePage.getSize(), rolePage.getTotal());
+        List<StorePlatformRoleVo> voList = rolePage.getRecords().stream()
+                .map(role -> {
+                    StorePlatformRoleVo vo = new StorePlatformRoleVo();
+                    BeanUtils.copyProperties(role, vo);
+                    
+                    // 统计关联子账号数量
+                    List<Integer> userIds = storePlatformUserRoleService.getUserIdsByRoleId(role.getRoleId(),storeId);
+                    vo.setSubAccountCount(userIds != null ? userIds.size() : 0);
+                    
+                    // 统计关联权限数量
+                    List<Long> menuIds = storePlatformRoleMenuService.getMenuIdsByRoleId(role.getRoleId());
+                    vo.setPermissionCount(menuIds != null ? menuIds.size() : 0);
+                    
+                    // 查询权限树形结构
+//                    List<RolePermissionVo> permissions = storePlatformRoleMenuService.getRolePermissionTree(role.getRoleId());
+//                    vo.setPermissions(permissions);
+                    
+                    return vo;
+                })
+                .collect(Collectors.toList());
+        
+        voPage.setRecords(voList);
+        return voPage;
+    }
+}
+

+ 745 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformUserRoleServiceImpl.java

@@ -0,0 +1,745 @@
+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.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.StorePlatformMenu;
+import shop.alien.entity.store.StorePlatformRole;
+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.SubAccountVo;
+import shop.alien.mapper.StorePlatformMenuMapper;
+import shop.alien.mapper.StorePlatformRoleMapper;
+import shop.alien.mapper.StorePlatformUserRoleMapper;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.storeplatform.service.StorePlatformRoleMenuService;
+import shop.alien.storeplatform.service.StorePlatformUserRoleService;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 用户角色关联表 服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformUserRoleMapper, StorePlatformUserRole> implements StorePlatformUserRoleService {
+
+    private final StorePlatformUserRoleMapper storePlatformUserRoleMapper;
+    private final StoreUserMapper storeUserMapper;
+    private final StorePlatformRoleMenuService storePlatformRoleMenuService;
+    private final StorePlatformRoleMapper storePlatformRoleMapper;
+    private final StorePlatformMenuMapper storePlatformMenuMapper;
+
+    @Override
+    public List<Long> getRoleIdsByUserId(Integer userId, Integer storeId) {
+        LambdaQueryWrapper<StorePlatformUserRole> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                .eq(StorePlatformUserRole::getStoreId, storeId);
+        List<StorePlatformUserRole> list = storePlatformUserRoleMapper.selectList(queryWrapper);
+        return list.stream().map(StorePlatformUserRole::getRoleId).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<Integer> getUserIdsByRoleId(Long roleId, Integer storeId) {
+        LambdaQueryWrapper<StorePlatformUserRole> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformUserRole::getRoleId, roleId)
+                .eq(StorePlatformUserRole::getStoreId, storeId);
+        List<StorePlatformUserRole> list = storePlatformUserRoleMapper.selectList(queryWrapper);
+        return list.stream().map(StorePlatformUserRole::getUserId).collect(Collectors.toList());
+    }
+
+    @Override
+    public boolean assignRoles(Integer userId, List<Long> roleIds, Integer storeId) {
+        if (userId == null || roleIds == null || roleIds.isEmpty() || storeId == null) {
+            log.error("参数不能为空");
+            return false;
+        }
+        // 先删除用户的所有角色
+        removeAllRoles(userId, storeId);
+        // 批量插入新角色
+        List<StorePlatformUserRole> userRoles = roleIds.stream()
+                .map(roleId -> {
+                    StorePlatformUserRole userRole = new StorePlatformUserRole();
+                    userRole.setUserId(userId);
+                    userRole.setRoleId(roleId);
+                    userRole.setStoreId(storeId);
+                    return userRole;
+                })
+                .collect(Collectors.toList());
+        return this.saveBatch(userRoles);
+    }
+
+    @Override
+    public boolean removeAllRoles(Integer userId, Integer storeId) {
+        LambdaQueryWrapper<StorePlatformUserRole> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                .eq(StorePlatformUserRole::getStoreId, storeId);
+        return storePlatformUserRoleMapper.delete(queryWrapper) >= 0;
+    }
+
+//    @Override
+//    public boolean removeRole(Integer userId, Long roleId, Integer storeId) {
+//        if (userId == null || roleId == null || storeId == null) {
+//            log.error("参数不能为空: userId={}, roleId={}, storeId={}", userId, roleId, storeId);
+//            return false;
+//        }
+//
+//        try {
+//            // 1. 先查询该用户在所有店铺下的子账号数量(未删除的)
+//            LambdaQueryWrapper<StorePlatformUserRole> countWrapper = new LambdaQueryWrapper<>();
+//            countWrapper.eq(StorePlatformUserRole::getUserId, userId)
+//                    .eq(StorePlatformUserRole::getDeleteFlag, 0);
+//            long subAccountCount = storePlatformUserRoleMapper.selectCount(countWrapper);
+//
+//            log.info("用户在所有店铺下的子账号数量: userId={}, count={}", userId, subAccountCount);
+//
+//            // 2. 逻辑删除 store_platform_user_role 表的记录
+//            LambdaUpdateWrapper<StorePlatformUserRole> updateWrapper = new LambdaUpdateWrapper<>();
+//            updateWrapper.eq(StorePlatformUserRole::getUserId, userId)
+//                    .eq(StorePlatformUserRole::getRoleId, roleId)
+//                    .eq(StorePlatformUserRole::getStoreId, storeId)
+//                    .eq(StorePlatformUserRole::getDeleteFlag, 0)
+//                    .set(StorePlatformUserRole::getDeleteFlag, 1); // 1代表删除
+//            int updateResult = storePlatformUserRoleMapper.update(null, updateWrapper);
+//
+//            if (updateResult <= 0) {
+//                log.error("逻辑删除 store_platform_user_role 记录失败: userId={}, roleId={}, storeId={}", userId, roleId, storeId);
+//                return false;
+//            }
+//
+//            // 3. 如果只是一个账号的子账号(删除前只有1个),则同时逻辑删除 store_user 表
+//            if (subAccountCount == 1) {
+//                LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
+//                userUpdateWrapper.eq(StoreUser::getId, userId)
+//                        .eq(StoreUser::getDeleteFlag, 0)
+//                        .set(StoreUser::getDeleteFlag, 1); // 1代表删除
+//                int userUpdateResult = storeUserMapper.update(null, userUpdateWrapper);
+//
+//                if (userUpdateResult > 0) {
+//                    log.info("用户只有一个子账号,已同时逻辑删除 store_user 记录: userId={}", userId);
+//                } else {
+//                    log.warn("逻辑删除 store_user 记录失败或记录不存在: userId={}", userId);
+//                }
+//            } else {
+//                log.info("用户有多个子账号(count={}),仅删除 store_platform_user_role 记录,不删除 store_user 记录: userId={}", subAccountCount, userId);
+//            }
+//
+//            return true;
+//        } catch (Exception e) {
+//            log.error("移除用户角色失败: userId={}, roleId={}, storeId={}", userId, roleId, storeId, e);
+//            return false;
+//        }
+//    }
+
+
+    @Override
+    public boolean removeRole(Integer userId, Long roleId, Integer storeId) {
+        if (userId == null || roleId == null || storeId == null) {
+            log.error("参数不能为空: userId={}, roleId={}, storeId={}", userId, roleId, storeId);
+            return false;
+        }
+
+        try {
+            // 1. 先查询该用户在所有店铺下的子账号数量(未删除的)
+            LambdaQueryWrapper<StorePlatformUserRole> countWrapper = new LambdaQueryWrapper<>();
+            countWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                    .eq(StorePlatformUserRole::getDeleteFlag, 0);
+            long subAccountCount = storePlatformUserRoleMapper.selectCount(countWrapper);
+
+            log.info("用户在所有店铺下的子账号数量: userId={}, count={}", userId, subAccountCount);
+
+            // 2. 逻辑删除 store_platform_user_role 表的记录
+            LambdaUpdateWrapper<StorePlatformUserRole> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                    .eq(StorePlatformUserRole::getRoleId, roleId)
+                    .eq(StorePlatformUserRole::getStoreId, storeId)
+                    .eq(StorePlatformUserRole::getDeleteFlag, 0)
+                    .set(StorePlatformUserRole::getDeleteFlag, 1); // 1代表删除
+            int updateResult = storePlatformUserRoleMapper.update(null, updateWrapper);
+
+            if (updateResult <= 0) {
+                log.error("逻辑删除 store_platform_user_role 记录失败: userId={}, roleId={}, storeId={}", userId, roleId, storeId);
+                return false;
+            }
+
+            // 3. 如果只是一个账号的子账号(删除前只有1个),则需要进一步判断是否为主账号,再决定是否同时逻辑删除 store_user 表
+            if (subAccountCount == 1) {
+                // 查询用户信息以判断是否为主账号
+                StoreUser user = storeUserMapper.selectById(userId);
+                if (user != null) {
+                    // 如果用户有 storeId 字段值,说明是主账号,不应删除
+                    if (user.getStoreId() != null && user.getStoreId() > 0) {
+                        log.info("用户是主账号,不删除 store_user 记录: userId={}, storeId={}", userId, user.getStoreId());
+                    } else {
+                        // 不是主账号,可以安全删除
+                        LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
+                        userUpdateWrapper.eq(StoreUser::getId, userId)
+                                .eq(StoreUser::getDeleteFlag, 0)
+                                .set(StoreUser::getDeleteFlag, 1); // 1代表删除
+                        int userUpdateResult = storeUserMapper.update(null, userUpdateWrapper);
+
+                        if (userUpdateResult > 0) {
+                            log.info("用户只有一个子账号且不是主账号,已同时逻辑删除 store_user 记录: userId={}", userId);
+                        } else {
+                            log.warn("逻辑删除 store_user 记录失败或记录不存在: userId={}", userId);
+                        }
+                    }
+                } else {
+                    log.warn("未找到用户信息: userId={}", userId);
+                }
+            } else {
+                log.info("用户有多个子账号(count={}),仅删除 store_platform_user_role 记录,不删除 store_user 记录: userId={}", subAccountCount, userId);
+            }
+
+            return true;
+        } catch (Exception e) {
+            log.error("移除用户角色失败: userId={}, roleId={}, storeId={}", userId, roleId, storeId, e);
+            return false;
+        }
+    }
+
+
+
+    @Override
+    public boolean createAccountAndAssignRole(String phone, String accountName, Integer storeId, Long roleId) {
+        if (phone == null || phone.isEmpty() || storeId == null || roleId == null) {
+            log.error("参数不能为空: phone={}, storeId={}, roleId={}", phone, storeId, roleId);
+            return false;
+        }
+
+        try {
+            // 0. 校验当前店铺中是否已存在相同手机号或用户名的子账号
+            // 先根据手机号查询用户
+            LambdaQueryWrapper<StoreUser> checkUserWrapper = new LambdaQueryWrapper<>();
+            checkUserWrapper.eq(StoreUser::getPhone, phone)
+                    .eq(StoreUser::getDeleteFlag, 0);
+            StoreUser checkUser = storeUserMapper.selectOne(checkUserWrapper);
+            
+            if (checkUser != null) {
+                // 如果用户存在,检查该用户在当前店铺是否已有子账号记录
+                LambdaQueryWrapper<StorePlatformUserRole> checkRoleWrapper = new LambdaQueryWrapper<>();
+                checkRoleWrapper.eq(StorePlatformUserRole::getUserId, checkUser.getId())
+                        .eq(StorePlatformUserRole::getStoreId, storeId)
+                        .eq(StorePlatformUserRole::getDeleteFlag, 0);
+                StorePlatformUserRole existingSubAccount = storePlatformUserRoleMapper.selectOne(checkRoleWrapper);
+                
+                if (existingSubAccount != null) {
+                    log.error("该手机号在当前店铺已存在子账号,不支持创建: phone={}, storeId={}", phone, storeId);
+                    return false;
+                }
+            }
+            
+            // 如果提供了账号名称,检查当前店铺中是否已存在相同账号名称的子账号
+            if (accountName != null && !accountName.trim().isEmpty()) {
+                LambdaQueryWrapper<StorePlatformUserRole> checkAccountNameWrapper = new LambdaQueryWrapper<>();
+                checkAccountNameWrapper.eq(StorePlatformUserRole::getAccountName, accountName)
+                        .eq(StorePlatformUserRole::getStoreId, storeId)
+                        .eq(StorePlatformUserRole::getDeleteFlag, 0);
+                StorePlatformUserRole existingAccountName = storePlatformUserRoleMapper.selectOne(checkAccountNameWrapper);
+                
+                if (existingAccountName != null) {
+                    log.error("该账号名称在当前店铺已存在子账号,不支持创建: accountName={}, storeId={}", accountName, storeId);
+                    return false;
+                }
+            }
+
+            // 1. 根据手机号查询 store_user 表
+            LambdaQueryWrapper<StoreUser> userQueryWrapper = new LambdaQueryWrapper<>();
+            userQueryWrapper.eq(StoreUser::getPhone, phone);
+            StoreUser storeUser = storeUserMapper.selectOne(userQueryWrapper);
+
+            //校验添加子账号的时候如果是当前主账号的手机号,阻断
+            if (storeUser != null && storeUser.getStoreId() != null) {
+                int storeIdNew = storeUser.getStoreId();
+                if (storeIdNew == storeId) {
+                    log.error("该手机号为当前当前店铺手机号,请勿重复创建: phone={}, storeId={}", phone, storeId);
+                    return false;
+                }
+            }
+
+            Integer userId;
+            if (storeUser == null) {
+                // 2. 如果没有数据,则插入一条数据
+                StoreUser newUser = new StoreUser();
+                newUser.setPhone(phone);
+                newUser.setName(accountName != null ? accountName : phone);
+//                newUser.setStoreId(storeId);
+                newUser.setStatus(0); // 0:启用
+                newUser.setDeleteFlag(0); // 0:未删除
+                newUser.setMoney(0);
+//                newUser.setPassType(0); // 0:初始密码
+                newUser.setLogoutFlag(0); // 0:未注销
+                newUser.setAccountType(2);
+                newUser.setPassType(0);
+                newUser.setPassword("");
+                newUser.setNickName(accountName != null ? accountName : phone);
+                newUser.setCreatedTime(new Date());
+                
+                int insertResult = storeUserMapper.insert(newUser);
+                if (insertResult <= 0) {
+                    log.error("插入 store_user 表失败: phone={}", phone);
+                    return false;
+                }
+                userId = newUser.getId();
+                log.info("成功创建新用户: phone={}, userId={}", phone, userId);
+            } else {
+                userId = storeUser.getId();
+                log.info("用户已存在: phone={}, userId={}", phone, userId);
+            }
+
+            // 3. 通过 store_id 和 user_id 查询 store_platform_user_role 表
+            LambdaQueryWrapper<StorePlatformUserRole> roleQueryWrapper = new LambdaQueryWrapper<>();
+            roleQueryWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                    .eq(StorePlatformUserRole::getStoreId, storeId)
+                    .eq(StorePlatformUserRole::getRoleId, roleId);
+            StorePlatformUserRole existingRole = storePlatformUserRoleMapper.selectOne(roleQueryWrapper);
+
+            if (existingRole == null) {
+                // 4. 如果没有数据,则插入数据
+                StorePlatformUserRole userRole = new StorePlatformUserRole();
+                userRole.setUserId(userId);
+                userRole.setRoleId(roleId);
+                userRole.setStoreId(storeId);
+                userRole.setDeleteFlag(0);
+                userRole.setAccountName(accountName);
+                userRole.setCreatedTime(new Date());
+                
+                boolean insertRoleResult = this.save(userRole);
+                if (!insertRoleResult) {
+                    log.error("插入 store_platform_user_role 表失败: userId={}, storeId={}, roleId={}", userId, storeId, roleId);
+                    return false;
+                }
+                log.info("成功分配角色: userId={}, storeId={}, roleId={}", userId, storeId, roleId);
+            } else {
+                log.info("用户角色关系已存在: userId={}, storeId={}, roleId={}", userId, storeId, roleId);
+            }
+
+            return true;
+        } catch (Exception e) {
+            log.error("创建账号并分配角色失败: phone={}, storeId={}, roleId={}", phone, storeId, roleId, e);
+            return false;
+        }
+    }
+
+    @Override
+    public List<SubAccountVo> querySubAccounts(Integer storeId, String accountName, String phone, String roleName) {
+        if (storeId == null) {
+            log.error("店铺ID不能为空");
+            return new ArrayList<>();
+        }
+
+        try {
+            // 查询子账号列表
+            List<SubAccountVo> subAccountList = storePlatformUserRoleMapper.querySubAccounts(
+                    storeId, accountName, phone, roleName);
+
+            // 计算每个子账号的权限数量
+            subAccountList.forEach(subAccount -> {
+                if (subAccount.getRoleId() != null) {
+                    List<Long> menuIds = storePlatformRoleMenuService.getMenuIdsByRoleId(subAccount.getRoleId());
+                    subAccount.setPermissionCount(menuIds != null ? menuIds.size() : 0);
+                } else {
+                    subAccount.setPermissionCount(0);
+                }
+            });
+
+            return subAccountList;
+        } catch (Exception e) {
+            log.error("查询子账号列表失败: storeId={}, accountName={}, phone={}, roleName={}", 
+                    storeId, accountName, phone, roleName, e);
+            return new ArrayList<>();
+        }
+    }
+
+    @Override
+    public SubAccountDetailVo getSubAccountDetail(Integer userId, Integer storeId , Long roleIdNew) {
+        if (userId == null || storeId == null) {
+            log.error("用户ID或店铺ID不能为空: userId={}, storeId={}", userId, storeId);
+            return null;
+        }
+
+        try {
+            SubAccountDetailVo detailVo = new SubAccountDetailVo();
+            detailVo.setUserId(userId);
+
+            // 1. 查询用户基本信息
+            StoreUser storeUser = storeUserMapper.selectById(userId);
+            if (storeUser == null) {
+                log.error("用户不存在: userId={}", userId);
+                return null;
+            }
+            
+            // 设置账号名称(优先使用 name,否则使用 nick_name)
+//            String accountName = storeUser.getName();
+//            if (accountName == null || accountName.isEmpty()) {
+//                accountName = storeUser.getNickName();
+//            }
+//            detailVo.setAccountName(accountName);
+            detailVo.setPhone(storeUser.getPhone());
+
+            // 2. 查询用户角色关联(根据userId和storeId查询)
+            LambdaQueryWrapper<StorePlatformUserRole> userRoleQueryWrapper = new LambdaQueryWrapper<>();
+            userRoleQueryWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                    .eq(StorePlatformUserRole::getRoleId, roleIdNew)
+                    .eq(StorePlatformUserRole::getStoreId, storeId);
+            StorePlatformUserRole userRole = storePlatformUserRoleMapper.selectOne(userRoleQueryWrapper);
+
+            if (userRole != null && userRole.getRoleId() != null) {
+                Long roleId = userRole.getRoleId();
+                detailVo.setRoleId(roleId);
+                detailVo.setAccountName(userRole.getAccountName());
+
+                // 3. 查询角色信息
+                StorePlatformRole role = storePlatformRoleMapper.selectById(roleId);
+                if (role != null) {
+                    detailVo.setRoleName(role.getRoleName());
+                }
+
+                // 4. 查询权限列表(菜单列表)
+                List<Long> menuIds = storePlatformRoleMenuService.getMenuIdsByRoleId(roleId);
+                if (menuIds != null && !menuIds.isEmpty()) {
+                    LambdaQueryWrapper<StorePlatformMenu> menuQueryWrapper = new LambdaQueryWrapper<>();
+                    menuQueryWrapper.in(StorePlatformMenu::getMenuId, menuIds);
+                    menuQueryWrapper.eq(StorePlatformMenu::getDelFlag, "0");
+                    menuQueryWrapper.eq(StorePlatformMenu::getStatus, "0");
+                    menuQueryWrapper.orderByAsc(StorePlatformMenu::getMenuSort);
+                    List<StorePlatformMenu> menus = storePlatformMenuMapper.selectList(menuQueryWrapper);
+
+                    // 转换为权限项VO列表
+                    List<PermissionItemVo> permissionList = menus.stream()
+                            .map(menu -> {
+                                PermissionItemVo permissionItem = new PermissionItemVo();
+                                permissionItem.setPermissionId(menu.getMenuId());
+                                permissionItem.setPermissionName(menu.getMenuName());
+                                return permissionItem;
+                            })
+                            .collect(Collectors.toList());
+                    detailVo.setPermissions(permissionList);
+                } else {
+                    detailVo.setPermissions(new ArrayList<>());
+                }
+            } else {
+                // 如果没有角色,设置为空
+                detailVo.setRoleId(null);
+                detailVo.setRoleName(null);
+                detailVo.setPermissions(new ArrayList<>());
+            }
+
+            return detailVo;
+        } catch (Exception e) {
+            log.error("查询子账号详情失败: userId={}, storeId={}", userId, storeId, e);
+            return null;
+        }
+    }
+
+//    @Override
+//    public boolean batchDeleteSubAccounts(List<Integer> userIds, Integer storeId) {
+//        if (userIds == null || userIds.isEmpty()) {
+//            log.error("用户ID列表不能为空");
+//            return false;
+//        }
+//        if (storeId == null) {
+//            log.error("店铺ID不能为空");
+//            return false;
+//        }
+//
+//        try {
+//            // 批量逻辑删除用户角色关联关系
+//            LambdaUpdateWrapper<StorePlatformUserRole> updateWrapper = new LambdaUpdateWrapper<>();
+//            updateWrapper.eq(StorePlatformUserRole::getStoreId, storeId)
+//                    .in(StorePlatformUserRole::getUserId, userIds)
+//                    .set(StorePlatformUserRole::getDeleteFlag, 1); // 1代表删除
+//
+//            int result = storePlatformUserRoleMapper.update(null, updateWrapper);
+//            log.info("批量删除子账号成功: storeId={}, userIds={}, 删除数量={}", storeId, userIds, result);
+//            return result > 0;
+//        } catch (Exception e) {
+//            log.error("批量删除子账号失败: storeId={}, userIds={}", storeId, userIds, e);
+//            return false;
+//        }
+//    }
+
+
+    @Override
+    public boolean batchDeleteSubAccounts(List<Integer> userIds, Integer storeId) {
+        if (userIds == null || userIds.isEmpty()) {
+            log.error("用户ID列表不能为空");
+            return false;
+        }
+        if (storeId == null) {
+            log.error("店铺ID不能为空");
+            return false;
+        }
+
+        try {
+            for (Integer userId : userIds) {
+                // 1. 先查询该用户在所有店铺下的子账号数量(未删除的)
+                LambdaQueryWrapper<StorePlatformUserRole> countWrapper = new LambdaQueryWrapper<>();
+                countWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                        .eq(StorePlatformUserRole::getDeleteFlag, 0);
+                long subAccountCount = storePlatformUserRoleMapper.selectCount(countWrapper);
+
+                log.info("用户在所有店铺下的子账号数量: userId={}, count={}", userId, subAccountCount);
+
+                // 2. 逻辑删除 store_platform_user_role 表的记录(只删除当前店铺的记录)
+                LambdaUpdateWrapper<StorePlatformUserRole> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                        .eq(StorePlatformUserRole::getStoreId, storeId)
+                        .eq(StorePlatformUserRole::getDeleteFlag, 0)
+                        .set(StorePlatformUserRole::getDeleteFlag, 1); // 1代表删除
+
+                int result = storePlatformUserRoleMapper.update(null, updateWrapper);
+
+                if (result <= 0) {
+                    log.warn("逻辑删除 store_platform_user_role 记录失败: userId={}, storeId={}", userId, storeId);
+                    continue; // 继续处理下一个用户
+                }
+
+                log.info("逻辑删除 store_platform_user_role 记录成功: userId={}, storeId={}, 删除数量={}", userId, storeId, result);
+
+                // 3. 判断是否只有一个子账号,如果是则同时逻辑删除 store_user 表
+                if (subAccountCount == 1) {
+
+                    StoreUser user = storeUserMapper.selectById(userId);
+
+                    if (user != null) {
+                        // 如果用户有 storeId 字段值,说明是主账号,不应删除
+                        if (user.getStoreId() != null && user.getStoreId() > 0) {
+                            log.info("用户是主账号,不删除 store_user 记录: userId={}, storeId={}", userId, user.getStoreId());
+                        } else {
+                            // 不是主账号,可以安全删除
+                            LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
+                            userUpdateWrapper.eq(StoreUser::getId, userId)
+                                    .eq(StoreUser::getDeleteFlag, 0)
+                                    .set(StoreUser::getDeleteFlag, 1); // 1代表删除
+                            int userUpdateResult = storeUserMapper.update(null, userUpdateWrapper);
+
+                            if (userUpdateResult > 0) {
+                                log.info("用户只有一个子账号且不是主账号,已同时逻辑删除 store_user 记录: userId={}", userId);
+                            } else {
+                                log.warn("逻辑删除 store_user 记录失败或记录不存在: userId={}", userId);
+                            }
+                        }
+                    } else {
+                        log.warn("未找到用户信息: userId={}", userId);
+                    }
+                } else {
+                    log.info("用户有多个子账号(count={}),仅删除 store_platform_user_role 记录,不删除 store_user 记录: userId={}", subAccountCount, userId);
+                }
+            }
+            log.info("批量删除子账号完成: storeId={}, userIds={}", storeId, userIds);
+            return true;
+        } catch (Exception e) {
+            log.error("批量删除子账号失败: storeId={}, userIds={}", storeId, userIds, e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean updateSubAccount(Integer userId, String phone, String accountName, Integer storeId, Long roleId) {
+        if (userId == null || storeId == null) {
+            log.error("用户ID或店铺ID不能为空: userId={}, storeId={}", userId, storeId);
+            return false;
+        }
+
+        try {
+            // 1. 验证用户是否存在
+            StoreUser storeUser = storeUserMapper.selectById(userId);
+            if (storeUser == null || storeUser.getDeleteFlag() != 0) {
+                log.error("用户不存在或已删除: userId={}", userId);
+                return false;
+            }
+
+            // 2. 如果提供了手机号,更新手机号
+            if (phone != null && !phone.trim().isEmpty()) {
+                // 检查新手机号是否与其他用户重复(排除当前用户)
+                LambdaQueryWrapper<StoreUser> phoneCheckWrapper = new LambdaQueryWrapper<>();
+                phoneCheckWrapper.eq(StoreUser::getPhone, phone)
+                        .ne(StoreUser::getId, userId)
+                        .eq(StoreUser::getDeleteFlag, 0);
+                StoreUser existingUser = storeUserMapper.selectOne(phoneCheckWrapper);
+                
+                if (existingUser != null) {
+                    // 检查该手机号对应的用户是否在当前店铺已有子账号
+                    LambdaQueryWrapper<StorePlatformUserRole> checkRoleWrapper = new LambdaQueryWrapper<>();
+                    checkRoleWrapper.eq(StorePlatformUserRole::getUserId, existingUser.getId())
+                            .eq(StorePlatformUserRole::getStoreId, storeId)
+                            .eq(StorePlatformUserRole::getDeleteFlag, 0);
+                    StorePlatformUserRole existingSubAccount = storePlatformUserRoleMapper.selectOne(checkRoleWrapper);
+                    
+                    if (existingSubAccount != null) {
+                        log.error("该手机号在当前店铺已存在子账号,不支持更新: phone={}, storeId={}", phone, storeId);
+                        return false;
+                    }
+                }
+
+                // 更新用户手机号、姓名和昵称
+                LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
+                userUpdateWrapper.eq(StoreUser::getId, userId)
+                        .set(StoreUser::getPhone, phone);
+                
+                // 如果提供了账号名称,同时更新姓名和昵称;否则使用手机号作为姓名和昵称
+                if (accountName != null && !accountName.trim().isEmpty()) {
+                    userUpdateWrapper.set(StoreUser::getName, accountName)
+                            .set(StoreUser::getNickName, accountName);
+                } else {
+                    userUpdateWrapper.set(StoreUser::getName, phone)
+                            .set(StoreUser::getNickName, phone);
+                }
+                
+                int userUpdateResult = storeUserMapper.update(null, userUpdateWrapper);
+                if (userUpdateResult <= 0) {
+                    log.error("更新用户手机号失败: userId={}, phone={}", userId, phone);
+                    return false;
+                }
+                log.info("成功更新用户手机号: userId={}, phone={}", userId, phone);
+            }
+
+            // 3. 查询当前用户的角色关联记录
+            // 如果提供了角色ID,通过userId + storeId + roleId查询;否则通过userId + storeId查询第一个
+            StorePlatformUserRole existingUserRole = null;
+            Long targetRoleId = roleId; // 用于确定要查询/更新的角色ID
+            
+            if (roleId != null) {
+                // 如果提供了角色ID,先查询是否存在该角色关联记录
+                LambdaQueryWrapper<StorePlatformUserRole> userRoleQueryWrapper = new LambdaQueryWrapper<>();
+                userRoleQueryWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                        .eq(StorePlatformUserRole::getStoreId, storeId)
+                        .eq(StorePlatformUserRole::getRoleId, roleId)
+                        .eq(StorePlatformUserRole::getDeleteFlag, 0);
+                existingUserRole = storePlatformUserRoleMapper.selectOne(userRoleQueryWrapper);
+            }
+            
+            // 如果通过roleId没找到,或者没有提供roleId,则查询第一个记录
+            if (existingUserRole == null) {
+                LambdaQueryWrapper<StorePlatformUserRole> userRoleQueryWrapper = new LambdaQueryWrapper<>();
+                userRoleQueryWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                        .eq(StorePlatformUserRole::getStoreId, storeId)
+                        .eq(StorePlatformUserRole::getDeleteFlag, 0)
+                        .last("LIMIT 1");
+                existingUserRole = storePlatformUserRoleMapper.selectOne(userRoleQueryWrapper);
+                
+                // 如果没有提供roleId,使用现有记录的roleId
+                if (targetRoleId == null && existingUserRole != null) {
+                    targetRoleId = existingUserRole.getRoleId();
+                }
+            }
+
+            // 4. 更新或创建角色关联记录
+            if (existingUserRole == null) {
+                // 如果不存在角色关联记录,需要创建一条(至少需要角色ID)
+                if (roleId == null) {
+                    log.error("用户在该店铺没有角色关联记录,且未提供角色ID,无法创建: userId={}, storeId={}", userId, storeId);
+                    return false;
+                }
+                
+                // 检查账号名称是否重复
+                if (accountName != null && !accountName.trim().isEmpty()) {
+                    LambdaQueryWrapper<StorePlatformUserRole> accountNameCheckWrapper = new LambdaQueryWrapper<>();
+                    accountNameCheckWrapper.eq(StorePlatformUserRole::getAccountName, accountName)
+                            .eq(StorePlatformUserRole::getStoreId, storeId)
+                            .eq(StorePlatformUserRole::getDeleteFlag, 0);
+                    StorePlatformUserRole existingAccountName = storePlatformUserRoleMapper.selectOne(accountNameCheckWrapper);
+                    
+                    if (existingAccountName != null) {
+                        log.error("该账号名称在当前店铺已存在,不支持创建: accountName={}, storeId={}", accountName, storeId);
+                        return false;
+                    }
+                }
+                
+                // 创建新的角色关联记录
+                StorePlatformUserRole newUserRole = new StorePlatformUserRole();
+                newUserRole.setUserId(userId);
+                newUserRole.setRoleId(roleId);
+                newUserRole.setStoreId(storeId);
+                newUserRole.setDeleteFlag(0);
+                newUserRole.setAccountName(accountName != null ? accountName : (storeUser.getNickName() != null ? storeUser.getNickName() : storeUser.getPhone()));
+                newUserRole.setCreatedTime(new Date());
+                
+                boolean insertResult = this.save(newUserRole);
+                if (!insertResult) {
+                    log.error("创建角色关联记录失败: userId={}, storeId={}, roleId={}", userId, storeId, roleId);
+                    return false;
+                }
+                log.info("成功创建角色关联记录: userId={}, storeId={}, roleId={}", userId, storeId, roleId);
+            } else {
+                // 如果提供了账号名称,更新账号名称(需要检查是否重复)
+                if (accountName != null && !accountName.trim().isEmpty()) {
+                    // 检查新账号名称是否在当前店铺已存在(排除当前记录)
+                    LambdaQueryWrapper<StorePlatformUserRole> accountNameCheckWrapper = new LambdaQueryWrapper<>();
+                    accountNameCheckWrapper.eq(StorePlatformUserRole::getAccountName, accountName)
+                            .eq(StorePlatformUserRole::getStoreId, storeId)
+                            .eq(StorePlatformUserRole::getDeleteFlag, 0)
+                            .ne(StorePlatformUserRole::getId, existingUserRole.getId());
+                    StorePlatformUserRole existingAccountName = storePlatformUserRoleMapper.selectOne(accountNameCheckWrapper);
+                    
+                    if (existingAccountName != null) {
+                        log.error("该账号名称在当前店铺已存在,不支持更新: accountName={}, storeId={}", accountName, storeId);
+                        return false;
+                    }
+
+                    // 更新账号名称(只更新当前记录)
+                    LambdaUpdateWrapper<StorePlatformUserRole> accountNameUpdateWrapper = new LambdaUpdateWrapper<>();
+                    accountNameUpdateWrapper.eq(StorePlatformUserRole::getId, existingUserRole.getId())
+                            .set(StorePlatformUserRole::getAccountName, accountName);
+                    
+                    int accountNameUpdateResult = storePlatformUserRoleMapper.update(null, accountNameUpdateWrapper);
+                    if (accountNameUpdateResult <= 0) {
+                        log.error("更新账号名称失败: userId={}, accountName={}", userId, accountName);
+                        return false;
+                    }
+                    log.info("成功更新账号名称: userId={}, accountName={}", userId, accountName);
+                }
+
+                // 如果提供了角色ID且与现有不同,更新角色信息
+                if (roleId != null && !roleId.equals(existingUserRole.getRoleId())) {
+                    // 检查新角色是否已存在相同的关联记录
+                    LambdaQueryWrapper<StorePlatformUserRole> roleCheckWrapper = new LambdaQueryWrapper<>();
+                    roleCheckWrapper.eq(StorePlatformUserRole::getUserId, userId)
+                            .eq(StorePlatformUserRole::getStoreId, storeId)
+                            .eq(StorePlatformUserRole::getRoleId, roleId)
+                            .eq(StorePlatformUserRole::getDeleteFlag, 0);
+                    StorePlatformUserRole existingRoleRecord = storePlatformUserRoleMapper.selectOne(roleCheckWrapper);
+                    
+                    if (existingRoleRecord != null && !existingRoleRecord.getId().equals(existingUserRole.getId())) {
+                        log.error("用户在该店铺已存在该角色关联记录: userId={}, storeId={}, roleId={}", userId, storeId, roleId);
+                        return false;
+                    }
+
+                    // 更新角色ID(只更新当前记录)
+                    LambdaUpdateWrapper<StorePlatformUserRole> roleUpdateWrapper = new LambdaUpdateWrapper<>();
+                    roleUpdateWrapper.eq(StorePlatformUserRole::getId, existingUserRole.getId())
+                            .set(StorePlatformUserRole::getRoleId, roleId);
+                    
+                    int roleUpdateResult = storePlatformUserRoleMapper.update(null, roleUpdateWrapper);
+                    if (roleUpdateResult <= 0) {
+                        log.error("更新角色ID失败: userId={}, roleId={}", userId, roleId);
+                        return false;
+                    }
+                    log.info("成功更新角色ID: userId={}, oldRoleId={}, newRoleId={}", userId, existingUserRole.getRoleId(), roleId);
+                }
+            }
+
+            return true;
+        } catch (Exception e) {
+            log.error("更新子账号信息失败: userId={}, phone={}, accountName={}, storeId={}, roleId={}", 
+                    userId, phone, accountName, storeId, roleId, e);
+            return false;
+        }
+    }
+}
+

+ 12 - 4
alien-store/src/main/java/shop/alien/store/config/WebSocketProcess.java

@@ -210,6 +210,8 @@ public class WebSocketProcess implements ApplicationContextAware {
         try {
             //每新建立一个连接,就把当前客户id为key,this为value存储到map中
             this.session = session;
+            // 设置 5 分钟没有任何消息往来则自动关闭连接,防止长连接堆积
+            this.session.setMaxIdleTimeout(5 * 60 * 1000);
             concurrentHashMap.put(id, this);
             
             // 从Session的UserProperties中获取IP地址和User-Agent(由WebSocketConfigurator在握手时设置)
@@ -242,10 +244,13 @@ public class WebSocketProcess implements ApplicationContextAware {
     @OnClose
     public void onClose(Session session, @PathParam("sendId") String id) {
         try {
-            //客户端连接关闭时,移除map中存储的键值对
-            concurrentHashMap.remove(id);
-            log.info("WebSocketProcess.onClose() close a websocket, concurrentHashMap remove sessionId= {}", id);
-            if (baseRedisService.hasKey("blackList_" + id)) baseRedisService.delete("blackList_" + id);
+            //客户端连接关闭时,安全移除map中存储的键值对
+            boolean removed = concurrentHashMap.remove(id, this);
+            if (removed) {
+                log.info("WebSocketProcess.onClose() close a websocket, concurrentHashMap remove sessionId= {}", id);
+                if (baseRedisService.hasKey("blackList_" + id)) baseRedisService.delete("blackList_" + id);
+                connectionInfoMap.remove(id);
+            }
         } catch (Exception e) {
             log.error("WebSocketProcess.onClose()----Exception----Message={}", e.getMessage());
         }
@@ -258,6 +263,9 @@ public class WebSocketProcess implements ApplicationContextAware {
     public void onError(@PathParam("sendId") String id, Throwable error) {
         try {
             log.error("WebSocketProcess.onError() Error,id={}, Msg=", id, error);
+            // 发生错误时主动移除,防止僵尸连接
+            concurrentHashMap.remove(id, this);
+            connectionInfoMap.remove(id);
         } catch (Exception e) {
             log.error("WebSocketProcess.onError()----Exception----Message={}", e.getMessage());
         }

+ 4 - 4
alien-store/src/main/java/shop/alien/store/controller/LifeCollectController.java

@@ -86,11 +86,11 @@ public class LifeCollectController {
             QueryWrapper<StoreInfoVo> queryWrapper = new QueryWrapper<>();
             queryWrapper.in("a.id", ids).eq("a.delete_flag", 0);
             // 查询店铺团购信息
-            LambdaUpdateWrapper<LifeCoupon> quanWrapper = new LambdaUpdateWrapper<>();
+/*            LambdaUpdateWrapper<LifeCoupon> quanWrapper = new LambdaUpdateWrapper<>();
             quanWrapper.in(LifeCoupon::getStoreId, ids)
                     .eq(LifeCoupon::getStatus, 1)
                     .orderByDesc(LifeCoupon::getCreatedTime);
-            List<LifeCoupon> quanList = lifeCouponMapper.selectList(quanWrapper);
+            List<LifeCoupon> quanList = lifeCouponMapper.selectList(quanWrapper);*/
             // 八大类分类
             if (!StringUtils.isEmpty(businessSection)) {
                 queryWrapper.eq("a.business_section", businessSection);
@@ -108,7 +108,7 @@ public class LifeCollectController {
                     if (a.getStoreId().equals(b.getId().toString())) {
                         // 团购券设置
                         List<Map<String, Object>> quanMapList = new ArrayList<>();
-                        if (!quanList.isEmpty()) {
+                        /*if (!quanList.isEmpty()) {
                             for (LifeCoupon quan : quanList) {
                                 if (b.getId().toString().equals(quan.getStoreId())) {
                                     Map<String, Object> quanMap = new HashMap<>();
@@ -121,7 +121,7 @@ public class LifeCollectController {
                                     quanMapList.add(quanMap);
                                 }
                             }
-                        }
+                        }*/
                         b.setQuanList(quanMapList);
                         // 得分,人均
                         b.setAvgScore("0");

+ 11 - 5
alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java

@@ -85,15 +85,21 @@ public class StoreImgController {
         boolean isHeadImage = (imgType == 20 || imgType == 21);
         if(imgType==4){
             Integer storeId = storeImgInfoVo.getStoreId();
-            StoreOfficialAlbum album = storeOfficialAlbumService.lambdaQuery()
-                    .eq(StoreOfficialAlbum::getStoreId, storeId).one();
-            if (null == album) {
+            // 查询名称为"环境"的相册,因为一个门店可能有多个相册
+            List<StoreOfficialAlbum> albumList = storeOfficialAlbumService.lambdaQuery()
+                    .eq(StoreOfficialAlbum::getStoreId, storeId)
+                    .eq(StoreOfficialAlbum::getAlbumName, "环境")
+                    .orderByAsc(StoreOfficialAlbum::getId)
+                    .list();
+            if (albumList == null || albumList.isEmpty()) {
                 return R.fail("没有默认环境相册");
             }
+            // 如果有多条记录,取第一条(按ID升序)
+            StoreOfficialAlbum album = albumList.get(0);
             storeImgList.forEach(storeImg -> storeImg.setBusinessId(album.getId()));
-            // 添加图片时 ,修改数量
+            // 添加图片时,修改数量(只更新当前使用的相册)
             storeOfficialAlbumService.lambdaUpdate()
-                    .eq(StoreOfficialAlbum::getStoreId, storeId)
+                    .eq(StoreOfficialAlbum::getId, album.getId())
                     .setSql("img_count = img_count + " + storeImgList.size())
                     .update();
         }

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

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

+ 143 - 0
alien-store/src/main/java/shop/alien/store/controller/StorePlatformMenuController.java

@@ -0,0 +1,143 @@
+package shop.alien.store.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.StorePlatformMenu;
+import shop.alien.entity.store.vo.StorePlatformMenuVo;
+import shop.alien.store.service.StorePlatformMenuService;
+
+/**
+ * 商家菜单管理
+ *
+ */
+@Slf4j
+@Api(tags = {"平台-菜单管理"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/platform")
+@RequiredArgsConstructor
+public class StorePlatformMenuController {
+    private final StorePlatformMenuService storePlatformMenuService;
+
+    @ApiOperation("分页查询菜单列表")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "页容", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "menuName", value = "菜单名称(支持模糊查询)", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getMenulist")
+    public R<IPage<StorePlatformMenuVo>> getMenuPage(
+            @RequestParam(name = "page", defaultValue = "1") int page,
+            @RequestParam(name = "size", defaultValue = "10") int size,
+            @RequestParam(value = "menuName", required = false) String menuName) {
+        log.info("StorePlatformMenuController.getMenuPage?page={}, size={}, menuName={}",
+                page, size, menuName);
+        IPage<StorePlatformMenuVo> store = storePlatformMenuService.getMenuPage(page, size, menuName);
+        return R.data(store);
+    }
+
+
+    @ApiOperation("查询/编辑当前菜单(JSON方式)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping(value = "/getMenuById", consumes = "application/json")
+    public R<StorePlatformMenu> getMenuById(@RequestBody StorePlatformMenu storePlatformMenu) {
+        log.info("StorePlatformMenuController.getMenuById(JSON)?menuId={}",
+                storePlatformMenu != null ? storePlatformMenu.getMenuId() : null);
+        if (storePlatformMenu == null || storePlatformMenu.getMenuId() == null) {
+            log.warn("查询/编辑当前菜单信息失败:菜单ID为空");
+            return R.fail("菜单ID不能为空");
+        }
+        
+        R<StorePlatformMenu> result = storePlatformMenuService.getByMenuId(storePlatformMenu);
+        if (result.isSuccess()) {
+            StorePlatformMenu menu = result.getData();
+            log.info("查询/编辑当前菜单信息成功,菜单ID={}, 菜单名称={}, 菜单路径={}, 菜单状态={}, 菜单层级={}, 一级菜单名称={}, 二级菜单名称={}",
+                    menu != null ? menu.getMenuId() : null,
+                    menu != null ? menu.getMenuName() : null,
+                    menu != null ? menu.getPath() : null,
+                    menu != null ? menu.getStatus() : null,
+                    menu != null ? menu.getLevel() : null,
+                    menu != null ? menu.getFirstLevelMenuName() : null,
+                    menu != null ? menu.getSecondLevelMenuName() : null);
+        } else {
+            log.warn("查询/编辑当前菜单信息失败,菜单ID={}", storePlatformMenu.getMenuId());
+        }
+        return result;
+    }
+
+
+    @ApiOperation("删除当前菜单(GET方式,URL参数)")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "long", paramType = "query", required = true)
+    })
+    @GetMapping("/deleteMenuDetails")
+    public R<StorePlatformMenu> deleteMenuDetailsByIdByGet(
+            @RequestParam Long menuId) {
+        log.info("StorePlatformMenuController.deleteMenuDetailsByIdByGet? menuId={}",  menuId);
+        
+        StorePlatformMenu storePlatformMenu = new StorePlatformMenu();
+        storePlatformMenu.setMenuId(menuId);
+        return storePlatformMenuService.deleteMenuDetails(storePlatformMenu);
+    }
+
+
+    @ApiOperation("启用禁用状态")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "status", value = "是否显示(0显示 1隐藏)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name ="level" , value = "菜单层级(1一级菜单 2二级菜单 3三级菜单)",dataType = "int", paramType = "query")
+    })
+    @GetMapping("/updateMenuStatus")
+    public R<StorePlatformMenu> updateMenuStatus(
+            @RequestParam Long menuId,
+            @RequestParam String status,
+            @RequestParam Integer level) {
+        log.info("StorePlatformMenuController.updateMenuStatus? menuId={},status={},level={}",  menuId,status,level);
+
+        StorePlatformMenu storePlatformMenu = new StorePlatformMenu();
+        storePlatformMenu.setMenuId(menuId);
+        storePlatformMenu.setStatus(status);
+        storePlatformMenu.setLevel(level);
+        return storePlatformMenuService.editUpdateMenuStatus(menuId,status,level);
+    }
+
+    @ApiOperation("新增菜单")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/addMenuStatus")
+    public R<StorePlatformMenu> addMenuStatus(@RequestBody StorePlatformMenu store) {
+        log.info("StorePlatformMenuController.addMenuStatus?菜单名称={}, 一级分类名称={}, 二级分类名称={}",
+                store != null ? store.getMenuName() : null,
+                store != null ? store.getFirstLevelMenuName() : null,
+                store != null ? store.getSecondLevelMenuName() : null);
+        
+        if (store == null) {
+            log.warn("新增菜单失败:菜单信息为空");
+            return R.fail("菜单信息不能为空");
+        }
+
+        R<StorePlatformMenu> result = storePlatformMenuService.addByMenu(store);
+        if (result.isSuccess()) {
+            StorePlatformMenu menu = result.getData();
+            log.info("新增菜单成功,菜单ID={}, 菜单名称={}, 菜单路径={}, 菜单状态={}, 菜单层级={}, 一级菜单名称={}, 二级菜单名称={}",
+                    menu != null ? menu.getMenuId() : null,
+                    menu != null ? menu.getMenuName() : null,
+                    menu != null ? menu.getPath() : null,
+                    menu != null ? menu.getStatus() : null,
+                    menu != null ? menu.getLevel() : null,
+                    menu != null ? menu.getFirstLevelMenuName() : null,
+                    menu != null ? menu.getSecondLevelMenuName() : null);
+        } else {
+            log.warn("新增菜单失败,菜单名称={}", store.getMenuName());
+        }
+        return result;
+    }
+
+}

+ 66 - 9
alien-store/src/main/java/shop/alien/store/controller/StoreUserController.java

@@ -10,16 +10,21 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StorePlatformMenu;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreInfoVo;
 import shop.alien.entity.store.vo.StoreUserVo;
+import shop.alien.entity.store.vo.SubAccountStoreListVo;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreUserMapper;
 import shop.alien.store.config.BaseRedisService;
+import shop.alien.store.service.RoleMenuService;
 import shop.alien.store.service.StoreInfoService;
 import shop.alien.store.service.StoreUserService;
+import shop.alien.store.service.SubAccountStoreService;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -47,6 +52,10 @@ public class StoreUserController {
 
     private final BaseRedisService baseRedisService;
 
+    private final SubAccountStoreService subAccountStoreService;
+    
+    private final RoleMenuService roleMenuService;
+
     /**
      * 校验商户端账号是否禁用
      *
@@ -208,15 +217,22 @@ public class StoreUserController {
     }
 
     /**
-     * web端查询用户列表
+     * web端查询用户列表(支持主账号和子账号查询)
      */
-    @ApiOperation("web端查询用户列表")
+    @ApiOperation("web端查询用户列表(支持主账号和子账号查询)")
     @ApiOperationSupport(order = 7)
     @GetMapping("/getStoreUserList")
-    @ApiImplicitParams({@ApiImplicitParam(name = "pageNum", value = "页数", dataType = "int", paramType = "query", required = true), @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = true), @ApiImplicitParam(name = "storeName", value = "门店名称", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "storeContact", value = "门店联系人", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "storePhone", value = "门店电话", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "storeType", value = "门店类型", dataType = "String", paramType = "query")})
-    public R<IPage<StoreUserVo>> getStoreUserList(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(required = false) String id, @RequestParam(required = false) String phone, @RequestParam(required = false) Integer status) {
-        log.info("StoreInfoController.getStoreUserList?pageNum={},pageSize={},id={},phone={},status={}", pageNum, pageSize, id, phone, status);
-        R<IPage<StoreUserVo>> storeUserVoR = storeUserService.getStoreUserList(pageNum, pageSize, id, phone, status);
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "int", paramType = "query", required = false),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = false),
+            @ApiImplicitParam(name = "id", value = "账号ID", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "phone", value = "联系电话", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "status", value = "状态", dataType = "int", paramType = "query", required = false),
+            @ApiImplicitParam(name = "accountType", value = "账号类型:1-主账号,2-子账号", dataType = "int", paramType = "query", required = true)
+    })
+    public R<IPage<StoreUserVo>> getStoreUserList(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(required = false) String id, @RequestParam(required = false) String phone, @RequestParam(required = false) Integer status, @RequestParam(required = true) Integer accountType) {
+        log.info("StoreUserController.getStoreUserList?pageNum={},pageSize={},id={},phone={},status={},accountType={}", pageNum, pageSize, id, phone, status, accountType);
+        R<IPage<StoreUserVo>> storeUserVoR = storeUserService.getStoreUserList(pageNum, pageSize, id, phone, status, accountType);
         return storeUserVoR;
     }
 
@@ -290,11 +306,11 @@ public class StoreUserController {
     @ApiOperation(value = "web端导出商家端账号相关信息")
     @ApiOperationSupport(order = 6)
     @GetMapping("/exportExcel")
-    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户id", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "phone", value = "联系电话", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "status", value = "状态", dataType = "String", paramType = "query"),})
+    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户id", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "phone", value = "联系电话", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "status", value = "状态", dataType = "String", paramType = "query"),@ApiImplicitParam(name = "accountType", value = "账号类型:1-主账号,2-子账号", dataType = "int", paramType = "query")})
     @ResponseBody
-    public R exportExcel(@RequestParam(value = "id", required = false) String id, @RequestParam(value = "phone", required = false) String phone, @RequestParam(value = "status", required = false) String status) throws IOException {
+    public R exportExcel(@RequestParam(value = "id", required = false) String id, @RequestParam(value = "phone", required = false) String phone, @RequestParam(value = "status", required = false) String status, @RequestParam(required = false) Integer accountType) throws IOException {
         log.info("StoreInfoController.exportExcel");
-        String excelPath = storeUserService.exportExcel(id, phone, status);
+        String excelPath = storeUserService.exportExcel(id, phone, status,accountType);
         return R.data(excelPath);
     }
 
@@ -434,4 +450,45 @@ public class StoreUserController {
         lambdaUpdateWrapper.eq(StoreUser::getId, storeUserVo.getId());
         return R.data(storeUserMapper.update(null, lambdaUpdateWrapper));
     }
+
+    /**
+     * 子账号关联门店列表
+     */
+    @ApiOperation(value = "子账号关联门店列表", notes = "根据用户ID查询子账号关联的门店列表")
+    @ApiOperationSupport(order = 10)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID(store_user.id)", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getSubAccountStoreList")
+    public R<List<SubAccountStoreListVo>> getSubAccountStoreList(@RequestParam("userId") Integer userId) {
+        log.info("StoreUserController.getSubAccountStoreList?userId={}", userId);
+        List<SubAccountStoreListVo> storeList = subAccountStoreService.getSubAccountStoreList(userId);
+        return R.data(storeList);
+    }
+
+    /**
+     * 通过角色ID查询权限菜单
+     */
+    @ApiOperation(value = "通过角色ID查询权限菜单", notes = "根据角色ID查询该角色拥有的所有权限菜单")
+    @ApiOperationSupport(order = 11)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true)
+    })
+    @GetMapping("/getMenusByRoleId")
+    public R<List<StorePlatformMenu>> getMenusByRoleId(@RequestParam("roleId") Long roleId) {
+        log.info("StoreUserController.getMenusByRoleId?roleId={}", roleId);
+        
+        if (roleId == null) {
+            log.warn("角色ID为空");
+            return R.fail("角色ID不能为空");
+        }
+        
+        try {
+            List<StorePlatformMenu> menus = roleMenuService.getMenusByRoleId(roleId);
+            return R.data(menus);
+        } catch (Exception e) {
+            log.error("查询角色权限菜单失败 - roleId: {}", roleId, e);
+            return R.fail("查询菜单失败:" + e.getMessage());
+        }
+    }
 }

+ 6 - 8
alien-store/src/main/java/shop/alien/store/controller/UserViolationController.java

@@ -1,22 +1,18 @@
 package shop.alien.store.controller;
 
-import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.fasterxml.jackson.core.JsonProcessingException;
 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.second.vo.SecondUserViolationDetailVo;
-import shop.alien.entity.store.LifeNotice;
 import shop.alien.entity.store.LifeUserViolation;
 import shop.alien.entity.store.UserLoginInfo;
 import shop.alien.entity.store.dto.LifeUserViolationDto;
 import shop.alien.mapper.LifeNoticeMapper;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.LifeUserViolationService;
-import shop.alien.util.common.JwtUtil;
 import shop.alien.util.common.TokenInfo;
 import springfox.documentation.annotations.ApiIgnore;
 
@@ -177,11 +173,13 @@ public class UserViolationController {
     @ApiOperationSupport(order = 9)
     @GetMapping("/queryViolationDetail")
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "id", value = "ID", dataType = "Integer", paramType = "query")
+            @ApiImplicitParam(name = "id", value = "ID", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "reportContextType", value = "举报类型", dataType = "String", paramType = "query"),
     })
     public R<SecondUserViolationDetailVo> queryViolationDetail(
-            @RequestParam(value = "id", required = false) Integer id) {
-        log.info("UserViolationController.queryViolationDetail?id={}", id);
-        return R.data(lifeUserViolationService.queryViolationDetail(id));
+            @RequestParam(value = "id", required = false) Integer id,
+            @RequestParam(value = "reportContextType", required = false) String reportContextType) {
+        log.info("UserViolationController.queryViolationDetail?id={}&reportContextType={}", id, reportContextType);
+        return R.data(lifeUserViolationService.queryViolationDetail(id, reportContextType));
     }
 }

+ 1 - 1
alien-store/src/main/java/shop/alien/store/service/LifeUserViolationService.java

@@ -40,5 +40,5 @@ public interface LifeUserViolationService extends IService<LifeUserViolation> {
 
     String level(UserLoginInfo userLoginInfo);
 
-    SecondUserViolationDetailVo queryViolationDetail(Integer id);
+    SecondUserViolationDetailVo queryViolationDetail(Integer id, String reportContextType);
 }

+ 21 - 0
alien-store/src/main/java/shop/alien/store/service/RoleMenuService.java

@@ -0,0 +1,21 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.store.StorePlatformMenu;
+
+import java.util.List;
+
+/**
+ * 角色菜单服务接口
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+public interface RoleMenuService {
+    /**
+     * 通过角色ID查询权限菜单
+     *
+     * @param roleId 角色ID
+     * @return 菜单列表
+     */
+    List<StorePlatformMenu> getMenusByRoleId(Long roleId);
+}

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

@@ -50,6 +50,14 @@ public interface StoreOperationalStatisticsService {
     IPage<StoreOperationalStatisticsHistory> getHistoryList(Integer storeId, Integer page, Integer size);
 
     /**
+     * 根据ID查询历史统计记录详情
+     *
+     * @param id 历史记录ID
+     * @return 历史统计记录详情
+     */
+    StoreOperationalStatisticsHistory getHistoryById(Integer id);
+
+    /**
      * 删除历史统计记录(逻辑删除)
      *
      * @param id 历史记录ID
@@ -85,6 +93,17 @@ public interface StoreOperationalStatisticsService {
     boolean updateHistoryPdfUrl(Integer historyId, String pdfUrl);
 
     /**
+     * 更新历史统计记录的AI分析结果
+     *
+     * @param historyId             历史记录ID
+     * @param aiAnalysisCompleted   AI分析是否完成, 0:未完成, 1:已完成
+     * @param summary               总结
+     * @param optimizationSuggestions 优化建议
+     * @return 是否成功
+     */
+    boolean updateHistoryAiAnalysis(Integer historyId, Integer aiAnalysisCompleted, String summary, String optimizationSuggestions);
+
+    /**
      * 生成统计数据对比PDF报告并上传到OSS
      *
      * @param storeId           店铺ID

+ 38 - 0
alien-store/src/main/java/shop/alien/store/service/StorePlatformMenuService.java

@@ -0,0 +1,38 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StorePlatformMenu;
+import shop.alien.entity.store.vo.StorePlatformMenuVo;
+
+public interface StorePlatformMenuService {
+    /**
+     * 分页查询
+     * @param page
+     * @param size
+     * @param menuName
+     * @return
+     */
+    IPage<StorePlatformMenuVo> getMenuPage(int page, int size, String menuName);
+
+    /**
+     * 编辑菜单ID
+     * @param storePlatformMenu
+     * @return
+     */
+    R<StorePlatformMenu> getByMenuId(StorePlatformMenu storePlatformMenu);
+
+    /**
+     *  删除
+     * @param storePlatformMenu
+     * @return
+     */
+
+    R<StorePlatformMenu> deleteMenuDetails(StorePlatformMenu storePlatformMenu);
+
+
+
+    R<StorePlatformMenu> editUpdateMenuStatus(Long menuId,String status,Integer level);
+
+    R<StorePlatformMenu> addByMenu(StorePlatformMenu store);
+}

+ 8 - 2
alien-store/src/main/java/shop/alien/store/service/StoreUserService.java

@@ -100,7 +100,8 @@ public interface StoreUserService extends IService<StoreUser> {
      *
      * @return boolean
      */
-    R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status);
+    R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status,Integer accountType);
+
 
     /**
      * web端编辑用户列表
@@ -134,7 +135,7 @@ public interface StoreUserService extends IService<StoreUser> {
     /**
      * web端导出商家端账号相关信息
      */
-    String exportExcel(String id, String phone, String status) throws IOException;
+    String exportExcel(String id, String phone, String status, Integer accountType) throws IOException;
 
 
     /**
@@ -191,4 +192,9 @@ public interface StoreUserService extends IService<StoreUser> {
     List<String> getIds(String name, String phone);
 
     R<Boolean> havePayPassword(String storeUserId, String password);
+
+    List<StoreUser> getSubAccountsByMainAccountId(Integer mainAccountId);
+
+
+    List<StoreUser> getChildAccountsByParentId(String Id);
 }

+ 20 - 0
alien-store/src/main/java/shop/alien/store/service/SubAccountStoreService.java

@@ -0,0 +1,20 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.store.vo.SubAccountStoreListVo;
+
+import java.util.List;
+
+/**
+ * 子账号关联门店服务接口
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+public interface SubAccountStoreService {
+    /**
+     * 根据用户ID查询子账号关联的门店列表
+     * @param userId 用户ID(store_user.id)
+     * @return 门店列表
+     */
+    List<SubAccountStoreListVo> getSubAccountStoreList(Integer userId);
+}

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

@@ -400,7 +400,7 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             case 1:
                 return "优化反馈";
             case 2:
-                return "新增功能反馈";
+                return "功能反馈";
             default:
                 return "";
         }

+ 8 - 4
alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java

@@ -86,6 +86,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
     private final StoreImgService storeImgService;
 
     private final AiUserViolationUtils aiUserViolationUtils;
+    private final CommonCommentMapper commonCommentMapper;
 
     @Autowired
     private SecondUserViolationMapper mapper;
@@ -760,7 +761,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
 
     @Override
-    public SecondUserViolationDetailVo queryViolationDetail(Integer id) {
+    public SecondUserViolationDetailVo queryViolationDetail(Integer id, String reportContextType) {
         // 查询举报信息
         SecondUserViolationDetailVo item = mapper.getUserViolationDetailInfo(id);
 
@@ -782,10 +783,13 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
         if ("3".equals(item.getReportContextType())) {
             // TODO: 处理评论类型
-            StoreComment commentInfo = storeCommentMapper.selectById(item.getBusinessId());
-            item.setCommentInfo(commentInfo);
+            CommonComment commonComment = commonCommentMapper.selectById(item.getBusinessId());
+            item.setCommonCommentInfo(commonComment);
 
-            LifeUserDynamics dynamicsInfo = lifeUserDynamicsMapper.selectById(commentInfo.getBusinessId());
+//            StoreComment commentInfo = storeCommentMapper.selectById(item.getBusinessId());
+//            item.setCommentInfo(commentInfo);
+
+            LifeUserDynamics dynamicsInfo = lifeUserDynamicsMapper.selectById(commonComment.getSourceId());
             item.setDynamicsInfo(dynamicsInfo);
             // 将逗号分隔的图片路径拆分成 List<String>
             if (dynamicsInfo != null && StringUtils.isNotEmpty(dynamicsInfo.getImagePath())) {

+ 189 - 0
alien-store/src/main/java/shop/alien/store/service/impl/RoleMenuServiceImpl.java

@@ -0,0 +1,189 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.StorePlatformMenu;
+import shop.alien.mapper.StorePlatformMenuMapper;
+import shop.alien.store.service.RoleMenuService;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 角色菜单服务实现类
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class RoleMenuServiceImpl implements RoleMenuService {
+
+    private final StorePlatformMenuMapper storePlatformMenuMapper;
+
+    @Override
+    public List<StorePlatformMenu> getMenusByRoleId(Long roleId) {
+        log.info("RoleMenuServiceImpl.getMenusByRoleId?roleId={}", roleId);
+        
+        if (roleId == null) {
+            log.warn("角色ID为空");
+            return new ArrayList<>();
+        }
+        
+        try {
+            // 1. 查询角色直接关联的菜单列表
+            List<StorePlatformMenu> directMenus = storePlatformMenuMapper.selectMenusByRoleId(roleId);
+            
+            if (directMenus == null || directMenus.isEmpty()) {
+                log.info("角色未分配任何菜单权限 - roleId: {}", roleId);
+                return new ArrayList<>();
+            }
+            
+            // 2. 找出最高层级
+            Integer maxLevel = directMenus.stream()
+                    .map(StorePlatformMenu::getLevel)
+                    .filter(Objects::nonNull)
+                    .max(Integer::compareTo)
+                    .orElse(1);
+            
+            // 3. 收集所有需要的菜单ID(包括父菜单)
+            Set<Long> allMenuIds = directMenus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            
+            // 4. 如果需要,补充父级权限
+            if (maxLevel >= 3) {
+                // 如果有三级权限,需要补充一级和二级父权限
+                for (StorePlatformMenu menu : directMenus) {
+                    if (menu.getLevel() != null && menu.getLevel() == 3) {
+                        // 补充二级父权限
+                        if (menu.getParentId() != null && menu.getParentId() != 0) {
+                            allMenuIds.add(menu.getParentId());
+                            // 补充一级父权限
+                            StorePlatformMenu level2Menu = storePlatformMenuMapper.selectById(menu.getParentId());
+                            if (level2Menu != null && level2Menu.getParentId() != null && level2Menu.getParentId() != 0) {
+                                allMenuIds.add(level2Menu.getParentId());
+                            }
+                        }
+                    }
+                }
+            } else if (maxLevel == 2) {
+                // 如果只有二级权限,需要补充一级父权限
+                for (StorePlatformMenu menu : directMenus) {
+                    if (menu.getLevel() != null && menu.getLevel() == 2) {
+                        if (menu.getParentId() != null && menu.getParentId() != 0) {
+                            allMenuIds.add(menu.getParentId());
+                        }
+                    }
+                }
+            }
+            
+            // 5. 查询所有需要的菜单(包括父菜单)
+            List<StorePlatformMenu> allMenus = new ArrayList<>();
+            if (!allMenuIds.isEmpty()) {
+                LambdaQueryWrapper<StorePlatformMenu> menuWrapper = new LambdaQueryWrapper<>();
+                menuWrapper.in(StorePlatformMenu::getMenuId, allMenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .eq(StorePlatformMenu::getStatus, "0")
+                        .orderByAsc(StorePlatformMenu::getMenuSort);
+                allMenus = storePlatformMenuMapper.selectList(menuWrapper);
+            }
+            
+            // 6. 构建树形结构
+            List<StorePlatformMenu> treeMenus = buildMenuTree(allMenus);
+            
+            log.info("查询角色权限菜单成功 - roleId: {}, 菜单数量: {}, 树形结构数量: {}", 
+                    roleId, allMenus.size(), treeMenus.size());
+            return treeMenus;
+            
+        } catch (Exception e) {
+            log.error("查询角色权限菜单失败 - roleId: {}", roleId, e);
+            return new ArrayList<>();
+        }
+    }
+    
+    /**
+     * 构建菜单树形结构
+     *
+     * @param menus 菜单列表
+     * @return 树形结构的菜单列表
+     */
+    private List<StorePlatformMenu> buildMenuTree(List<StorePlatformMenu> menus) {
+        if (menus == null || menus.isEmpty()) {
+            return new ArrayList<>();
+        }
+        
+        // 按层级和排序顺序排序
+        menus.sort(Comparator
+                .comparing(StorePlatformMenu::getLevel, Comparator.nullsLast(Integer::compareTo))
+                .thenComparing(StorePlatformMenu::getMenuSort, Comparator.nullsLast(Integer::compareTo)));
+        
+        // 创建菜单映射表(menuId -> menu)
+        Map<Long, StorePlatformMenu> menuMap = menus.stream()
+                .collect(Collectors.toMap(
+                        StorePlatformMenu::getMenuId,
+                        menu -> {
+                            // 初始化 children 列表
+                            if (menu.getChildren() == null) {
+                                menu.setChildren(new ArrayList<>());
+                            }
+                            return menu;
+                        },
+                        (existing, replacement) -> existing
+                ));
+        
+        // 构建树形结构
+        List<StorePlatformMenu> rootMenus = new ArrayList<>();
+        for (StorePlatformMenu menu : menus) {
+            Long parentId = menu.getParentId();
+            
+            if (parentId == null || parentId == 0) {
+                // 根菜单(一级菜单)
+                rootMenus.add(menu);
+            } else {
+                // 子菜单,添加到父菜单的 children 中
+                StorePlatformMenu parentMenu = menuMap.get(parentId);
+                if (parentMenu != null) {
+                    if (parentMenu.getChildren() == null) {
+                        parentMenu.setChildren(new ArrayList<>());
+                    }
+                    parentMenu.getChildren().add(menu);
+                } else {
+                    // 如果找不到父菜单,作为根菜单处理
+                    rootMenus.add(menu);
+                }
+            }
+        }
+        
+        // 对每个层级的 children 进行排序
+        sortMenuChildren(rootMenus);
+        
+        return rootMenus;
+    }
+    
+    /**
+     * 递归排序菜单的 children
+     *
+     * @param menus 菜单列表
+     */
+    private void sortMenuChildren(List<StorePlatformMenu> menus) {
+        if (menus == null || menus.isEmpty()) {
+            return;
+        }
+        
+        // 对当前层级排序
+        menus.sort(Comparator
+                .comparing(StorePlatformMenu::getMenuSort, Comparator.nullsLast(Integer::compareTo)));
+        
+        // 递归排序子菜单
+        for (StorePlatformMenu menu : menus) {
+            if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
+                sortMenuChildren(menu.getChildren());
+            }
+        }
+    }
+}

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

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

+ 37 - 1
alien-store/src/main/java/shop/alien/store/service/impl/StoreImgServiceImpl.java

@@ -15,11 +15,13 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeUser;
 import shop.alien.entity.store.StoreImg;
 import shop.alien.entity.store.StoreOfficialAlbum;
+import shop.alien.entity.store.StoreVideo;
 import shop.alien.entity.store.vo.StoreImgTypeVo;
 import shop.alien.mapper.LifeUserMapper;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreOfficialAlbumMapper;
 import shop.alien.store.service.StoreImgService;
+import shop.alien.store.service.StoreVideoService;
 import shop.alien.store.util.ai.AiImageColorExtractUtil;
 import shop.alien.util.common.Constants;
 
@@ -44,6 +46,7 @@ public class StoreImgServiceImpl extends ServiceImpl<StoreImgMapper, StoreImg> i
     private final StoreOfficialAlbumMapper storeOfficialAlbumMapper;
     private final LifeUserMapper lifeUserMapper;
     private final AiImageColorExtractUtil aiImageColorExtractUtil;
+    private final StoreVideoService storeVideoService;
 
     /**
      * 获取门店图片
@@ -181,7 +184,40 @@ public class StoreImgServiceImpl extends ServiceImpl<StoreImgMapper, StoreImg> i
         LambdaUpdateWrapper<StoreOfficialAlbum> updateWrapper = new LambdaUpdateWrapper<>();
         updateWrapper.eq(StoreOfficialAlbum::getId, businessId).set(StoreOfficialAlbum::getImgCount, imgCount);
         storeOfficialAlbumMapper.update(null, updateWrapper);
-        return this.list(lambdaQueryWrapper);
+        List<StoreImg> resList = this.list(lambdaQueryWrapper);
+        // 查询视频列表,只获取第一个视频的封面
+        List<StoreVideo> byStoreId = storeVideoService.getByStoreId(storeId);
+        if (!CollectionUtils.isEmpty(byStoreId)) {
+            StoreVideo storeVideo = byStoreId.get(0);
+            String imgUrl = storeVideo.getImgUrl();
+            if (imgUrl != null && !imgUrl.trim().isEmpty()) {
+                try {
+                    // 解析JSON数组格式的imgUrl
+                    com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(imgUrl);
+                    if (jsonArray != null && !jsonArray.isEmpty()) {
+                        com.alibaba.fastjson.JSONObject firstItem = jsonArray.getJSONObject(0);
+                        if (firstItem != null && firstItem.containsKey("cover")) {
+                            String coverUrl = firstItem.getString("cover");
+                            if (coverUrl != null && !coverUrl.trim().isEmpty()) {
+                                // 创建StoreImg对象,设置封面URL
+                                StoreImg storeImg = new StoreImg();
+                                storeImg.setImgUrl(coverUrl);
+                                storeImg.setStoreId(storeId);
+                                storeImg.setImgType(imgType);
+                                storeImg.setBusinessId(businessId);
+                                storeImg.setImgDescription("视频");
+                                storeImg.setImgSort(resList.size() + 1);
+                                resList.add(storeImg);
+                                log.info("从第一个视频中提取封面成功,coverUrl: {}", coverUrl);
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    log.warn("解析视频imgUrl失败,imgUrl: {}, error: {}", imgUrl, e.getMessage());
+                }
+            }
+        }
+        return resList;
     }
 
     @Override

+ 4 - 7
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -305,9 +305,6 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         Integer headImgStatus = hasHeadImg ? 1 : 2;
         // 获取数据库中的head_img_status状态
         Integer headImgStatusFromDb = storeInfo.getHeadImgStatus();
-        // 记录查询结果日志
-        log.info("门店头图审核状态判断,storeId={}, 查询到头图数量={}, 是否有有效头图={}, 审核状态={}, 数据库原状态={}", 
-                id, headImgList != null ? headImgList.size() : 0, hasHeadImg, headImgStatus, headImgStatusFromDb);
         // 实时更新数据库:如果数据库中的状态与实际情况不一致,或者为null,则更新数据库
         if (headImgStatusFromDb == null || !headImgStatus.equals(headImgStatusFromDb)) {
             try {
@@ -2699,7 +2696,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
     private static LifeNotice getLifeNotice(StoreInfoVo storeInfo) {
         LifeNotice lifeMessage = new LifeNotice();
         lifeMessage.setReceiverId("store_" + storeInfo.getStorePhone());
-        String text = "您提交的店铺注销申请已成功接收,系统将按流程进行处理。注销申请提交后,将进入7天的冷静期,期间您可随在【我的设置】-【店铺信息】-【操作】-【注销店铺】中撤回申请,冷静期结束后,系统将正式开始注销操作,店铺内所数据将被永久清除,且无法恢复。如有疑问,可联系客服咨询,感谢您使用我们的服务。";
+        String text = "您提交的店铺注销申请已成功接收,系统将按流程进行处理。\n\n注销申请提交后,将进入7天的冷静期,期间您可随时在【我的设置】-【注销店铺】中撤回申请。冷静期结束后,系统将正式开始注销操作,店铺内所有数据将被永久清除,且无法恢复。\n\n如有疑问,可联系客服咨询,感谢您使用我们的服务。";
         com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
         jsonObject.put("message", text);
         lifeMessage.setContext(jsonObject.toJSONString());
@@ -2731,13 +2728,13 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             // 发送通知
             LifeNotice lifeMessage = new LifeNotice();
             lifeMessage.setReceiverId("store_" + storeInfo.getStorePhone());
-            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm");
             String storeDate = simpleDateFormat.format(new Date());
-            String text = "您在" + storeDate + "撤销了注销账号,所有数据均已保留,您可继续在平台使用。";
+            String text = "当前店铺状态已恢复正常。您可继续使用该店铺登录并享受各项服务,所有店铺数据均已妥善保留。\n\n若您后续仍有注销需求,可随时通过【我的设置】-【注销店铺】中重新提交申请。感谢您的理解与支持!";
             com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
             jsonObject.put("message", text);
             lifeMessage.setContext(jsonObject.toJSONString());
-            lifeMessage.setTitle("撤销注销店铺通知");
+            lifeMessage.setTitle("处理结果: 已撤回注销申请");
             lifeMessage.setSenderId("system");
             lifeMessage.setIsRead(0);
             lifeMessage.setNoticeType(1);

+ 283 - 15
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalStatisticsServiceImpl.java

@@ -7,13 +7,19 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.http.*;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
 import shop.alien.entity.store.vo.StoreOperationalStatisticsVo;
 import shop.alien.mapper.*;
 import shop.alien.store.service.StoreOperationalStatisticsService;
 import shop.alien.store.util.StatisticsComparisonImageUtil;
+import shop.alien.store.util.ai.AiAuthTokenUtil;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.RandomCreateUtil;
 import shop.alien.util.pdf.ImageToPdfUtil;
@@ -34,6 +40,7 @@ import java.util.*;
 @Slf4j
 @Service
 @RequiredArgsConstructor
+@RefreshScope
 public class StoreOperationalStatisticsServiceImpl implements StoreOperationalStatisticsService {
 
     private final LifeBrowseRecordMapper lifeBrowseRecordMapper;
@@ -56,6 +63,11 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     private final StoreOperationalStatisticsHistoryMapper statisticsHistoryMapper;
     private final StoreTrackStatisticsMapper storeTrackStatisticsMapper;
     private final AliOSSUtil aliOSSUtil;
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+    private final RestTemplate restTemplate;
+
+    @Value("${third-party-ai-store-summary-suggestion.base-url:}")
+    private String aiStatisticsAnalysisUrl;
 
     private static final String DATE_FORMAT = "yyyy-MM-dd";
     private static final String STAT_TYPE_DAILY = "DAILY";
@@ -63,6 +75,18 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     @Override
     public StoreOperationalStatisticsVo getStatistics(Integer storeId, String startTime, String endTime) {
         log.info("StoreOperationalStatisticsServiceImpl.getStatistics - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
+        
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+        if (startTime == null || startTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("开始时间不能为空");
+        }
+        if (endTime == null || endTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("结束时间不能为空");
+        }
+        
         return calculateStatistics(storeId, startTime, endTime);
     }
 
@@ -173,6 +197,23 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         log.info("StoreOperationalStatisticsServiceImpl.getStatisticsComparison - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
                 storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
 
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+        if (currentStartTime == null || currentStartTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("当期开始时间不能为空");
+        }
+        if (currentEndTime == null || currentEndTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("当期结束时间不能为空");
+        }
+        if (previousStartTime == null || previousStartTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("上期开始时间不能为空");
+        }
+        if (previousEndTime == null || previousEndTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("上期结束时间不能为空");
+        }
+
         StoreOperationalStatisticsComparisonVo comparison = new StoreOperationalStatisticsComparisonVo();
         comparison.setCurrentStartTime(currentStartTime);
         comparison.setCurrentEndTime(currentEndTime);
@@ -192,9 +233,19 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         comparison.setPriceListRanking(buildPriceListRankingComparison(currentStatistics.getPriceListRanking(), previousStatistics.getPriceListRanking()));
 
         // 保存历史记录(不包含PDF URL,PDF URL只在 generateStatisticsComparisonPdf 接口中保存)
-        saveStatisticsHistory(storeId, currentStartTime, currentEndTime, 
+        Integer historyId = saveStatisticsHistory(storeId, currentStartTime, currentEndTime, 
                 previousStartTime, previousEndTime, comparison, null);
 
+        // 异步调用AI接口进行数据分析
+        if (historyId != null) {
+            try {
+                callAiAnalysisAsync(historyId, storeId, comparison);
+            } catch (Exception e) {
+                log.error("调用AI分析接口失败 - historyId={}, error={}", historyId, e.getMessage(), e);
+                // AI分析失败不影响主流程,只记录日志
+            }
+        }
+
         return comparison;
     }
 
@@ -347,8 +398,10 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
 
         // 浏览记录统计
         LambdaQueryWrapper<LifeBrowseRecord> browseWrapper = new LambdaQueryWrapper<>();
-        browseWrapper.eq(LifeBrowseRecord::getStoreId, String.valueOf(storeId))
-                .between(LifeBrowseRecord::getCreatedTime, startDate, endDate)
+        if (storeId != null) {
+            browseWrapper.eq(LifeBrowseRecord::getStoreId, String.valueOf(storeId));
+        }
+        browseWrapper.between(LifeBrowseRecord::getCreatedTime, startDate, endDate)
                 .eq(LifeBrowseRecord::getDeleteFlag, 0);
         List<LifeBrowseRecord> browseRecords = lifeBrowseRecordMapper.selectList(browseWrapper);
 
@@ -382,8 +435,10 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
 
         // 收藏次数
         LambdaQueryWrapper<LifeCollect> collectWrapper = new LambdaQueryWrapper<>();
-        collectWrapper.eq(LifeCollect::getStoreId, String.valueOf(storeId))
-                .between(LifeCollect::getCreatedTime, startDate, endDate)
+        if (storeId != null) {
+            collectWrapper.eq(LifeCollect::getStoreId, String.valueOf(storeId));
+        }
+        collectWrapper.between(LifeCollect::getCreatedTime, startDate, endDate)
                 .eq(LifeCollect::getDeleteFlag, 0);
         long collectionCount = lifeCollectMapper.selectCount(collectWrapper);
         interactionData.setStoreCollectionCount(collectionCount);
@@ -979,8 +1034,11 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         log.info("StoreOperationalStatisticsServiceImpl.getHistoryList - storeId={}, page={}, size={}", storeId, page, size);
         
         // 参数校验
-        int pageNum = page > 0 ? page : 1;
-        int pageSize = size > 0 ? size : 10;
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+        int pageNum = page != null && page > 0 ? page : 1;
+        int pageSize = size != null && size > 0 ? size : 10;
         
         // 构建分页对象
         IPage<StoreOperationalStatisticsHistory> pageObj = new Page<>(pageNum, pageSize);
@@ -999,6 +1057,31 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     }
 
     @Override
+    public StoreOperationalStatisticsHistory getHistoryById(Integer id) {
+        log.info("StoreOperationalStatisticsServiceImpl.getHistoryById - id={}", id);
+        
+        // 参数校验
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("历史记录ID不能为空且必须大于0");
+        }
+        
+        // 查询历史记录
+        LambdaQueryWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreOperationalStatisticsHistory::getId, id)
+               .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0);
+        
+        StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(wrapper);
+        
+        if (history == null) {
+            log.warn("查询历史统计记录详情失败,记录不存在或已删除 - id={}", id);
+            throw new RuntimeException("历史记录不存在或已删除");
+        }
+        
+        log.info("查询历史统计记录详情成功 - id={}", id);
+        return history;
+    }
+
+    @Override
     public boolean deleteHistory(Integer id) {
         log.info("StoreOperationalStatisticsServiceImpl.deleteHistory - id={}", id);
         
@@ -1086,6 +1169,56 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
             return false;
         }
     }
+
+    @Override
+    public boolean updateHistoryAiAnalysis(Integer historyId, Integer aiAnalysisCompleted, String summary, String optimizationSuggestions) {
+        log.info("StoreOperationalStatisticsServiceImpl.updateHistoryAiAnalysis - historyId={}, aiAnalysisCompleted={}", historyId, aiAnalysisCompleted);
+        
+        if (historyId == null || historyId <= 0) {
+            log.warn("更新历史统计记录AI分析结果失败,历史记录ID无效 - historyId={}", historyId);
+            return false;
+        }
+        
+        try {
+            // 查询历史记录是否存在且未删除
+            StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(
+                    new LambdaQueryWrapper<StoreOperationalStatisticsHistory>()
+                            .eq(StoreOperationalStatisticsHistory::getId, historyId)
+                            .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0));
+            
+            if (history == null) {
+                log.warn("更新历史统计记录AI分析结果失败,历史记录不存在或已删除 - historyId={}", historyId);
+                return false;
+            }
+            
+            // 更新AI分析相关字段
+            LambdaUpdateWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaUpdateWrapper<>();
+            wrapper.eq(StoreOperationalStatisticsHistory::getId, historyId)
+                   .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0);
+            
+            if (aiAnalysisCompleted != null) {
+                wrapper.set(StoreOperationalStatisticsHistory::getAiAnalysisCompleted, aiAnalysisCompleted);
+            }
+            if (summary != null) {
+                wrapper.set(StoreOperationalStatisticsHistory::getSummary, summary.trim());
+            }
+            if (optimizationSuggestions != null) {
+                wrapper.set(StoreOperationalStatisticsHistory::getOptimizationSuggestions, optimizationSuggestions.trim());
+            }
+            
+            int result = statisticsHistoryMapper.update(null, wrapper);
+            if (result > 0) {
+                log.info("更新历史统计记录AI分析结果成功 - historyId={}, aiAnalysisCompleted={}", historyId, aiAnalysisCompleted);
+                return true;
+            } else {
+                log.warn("更新历史统计记录AI分析结果失败,未更新任何记录 - historyId={}", historyId);
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("更新历史统计记录AI分析结果失败 - historyId={}, error={}", historyId, e.getMessage(), e);
+            return false;
+        }
+    }
     
     // ==================== 累加和转换方法 ====================
     
@@ -1297,14 +1430,17 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         
         // 访问时长转换为秒(原数据是毫秒)
         Long totalDuration = accumulator.getOrDefault("totalDuration", 0L);
-        vo.setVisitDuration(totalDuration / 1000);
+        vo.setVisitDuration(totalDuration > 0 ? totalDuration / 1000 : 0L);
         
-        // 平均访问时长:如果有多个统计记录,需要重新计算平均值
-        Long avgDuration = accumulator.getOrDefault("avgDuration", 0L);
-        if (count > 0 && avgDuration > 0) {
-            vo.setAvgVisitDuration(avgDuration / 1000); // 转换为秒
+        // 平均访问时长:总时长除以访问次数(转换为秒)
+        Long visitorCount = accumulator.getOrDefault("visitorCount", 0L);
+        if (count > 0 && totalDuration > 0 && visitorCount > 0) {
+            // 平均访问时长 = 总时长 / 访客数(转换为秒)
+            vo.setAvgVisitDuration((totalDuration / visitorCount) / 1000);
         } else {
-            vo.setAvgVisitDuration(0L);
+            // 如果没有访客数,使用原有的平均时长数据
+            Long avgDuration = accumulator.getOrDefault("avgDuration", 0L);
+            vo.setAvgVisitDuration(avgDuration > 0 ? avgDuration / 1000 : 0L);
         }
         
         return vo;
@@ -1569,9 +1705,26 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         log.info("StoreOperationalStatisticsServiceImpl.generateStatisticsComparisonPdf - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
                 storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
         
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+        if (currentStartTime == null || currentStartTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("当期开始时间不能为空");
+        }
+        if (currentEndTime == null || currentEndTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("当期结束时间不能为空");
+        }
+        if (previousStartTime == null || previousStartTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("上期开始时间不能为空");
+        }
+        if (previousEndTime == null || previousEndTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("上期结束时间不能为空");
+        }
+        
         try {
-            // 1. 获取统计数据对比
-            StoreOperationalStatisticsComparisonVo comparison = getStatisticsComparison(
+            // 1. 获取统计数据对比(不保存历史记录,因为后面会保存带PDF URL的记录)
+            StoreOperationalStatisticsComparisonVo comparison = getStatisticsComparisonWithoutSave(
                     storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
             
             // 2. 生成图片
@@ -1602,5 +1755,120 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
             throw new RuntimeException("生成图片并转PDF上传失败: " + e.getMessage(), e);
         }
     }
+    
+    /**
+     * 获取统计数据对比(不保存历史记录)
+     * 用于 generateStatisticsComparisonPdf 方法,避免重复保存历史记录
+     */
+    private StoreOperationalStatisticsComparisonVo getStatisticsComparisonWithoutSave(Integer storeId, String currentStartTime, String currentEndTime,
+                                                                                      String previousStartTime, String previousEndTime) {
+        StoreOperationalStatisticsComparisonVo comparison = new StoreOperationalStatisticsComparisonVo();
+        comparison.setCurrentStartTime(currentStartTime);
+        comparison.setCurrentEndTime(currentEndTime);
+        comparison.setPreviousStartTime(previousStartTime);
+        comparison.setPreviousEndTime(previousEndTime);
+
+        // 获取当期和上期的统计数据
+        StoreOperationalStatisticsVo currentStatistics = calculateStatistics(storeId, currentStartTime, currentEndTime);
+        StoreOperationalStatisticsVo previousStatistics = calculateStatistics(storeId, previousStartTime, previousEndTime);
+
+        // 构建对比数据
+        comparison.setTrafficData(buildTrafficDataComparison(currentStatistics.getTrafficData(), previousStatistics.getTrafficData()));
+        comparison.setInteractionData(buildInteractionDataComparison(currentStatistics.getInteractionData(), previousStatistics.getInteractionData()));
+        comparison.setCouponData(buildCouponDataComparison(currentStatistics.getCouponData(), previousStatistics.getCouponData()));
+        comparison.setVoucherData(buildVoucherDataComparison(currentStatistics.getVoucherData(), previousStatistics.getVoucherData()));
+        comparison.setServiceQualityData(buildServiceQualityDataComparison(currentStatistics.getServiceQualityData(), previousStatistics.getServiceQualityData()));
+        comparison.setPriceListRanking(buildPriceListRankingComparison(currentStatistics.getPriceListRanking(), previousStatistics.getPriceListRanking()));
+
+        // 不保存历史记录,由调用方决定是否保存
+
+        return comparison;
+    }
+
+    /**
+     * 异步调用AI接口进行数据分析
+     * 
+     * @param historyId 历史记录ID
+     * @param storeId 店铺ID
+     * @param comparison 统计数据对比
+     */
+    private void callAiAnalysisAsync(Integer historyId, Integer storeId, StoreOperationalStatisticsComparisonVo comparison) {
+        // 如果AI接口URL未配置,跳过调用
+        if (!StringUtils.hasText(aiStatisticsAnalysisUrl)) {
+            log.warn("AI统计分析接口URL未配置,跳过AI分析 - historyId={}", historyId);
+            return;
+        }
+
+        try {
+            // 获取访问令牌
+            String accessToken = aiAuthTokenUtil.getAccessToken();
+            if (!StringUtils.hasText(accessToken)) {
+                log.error("调用AI分析接口失败,获取accessToken失败 - historyId={}", historyId);
+                return;
+            }
+
+            // 构建请求体,只发送id
+            Map<String, Object> requestBody = new HashMap<>();
+            requestBody.put("id", historyId);
+
+            // 构建请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            log.info(requestBody.toString());
+
+            HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
+
+            log.info("开始调用AI统计分析接口 - historyId={}, storeId={}", historyId, storeId);
+            ResponseEntity<String> response = restTemplate.postForEntity(
+                    aiStatisticsAnalysisUrl != null ? aiStatisticsAnalysisUrl : "", request, String.class);
+
+            if (response != null && response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI统计分析接口调用成功 - historyId={}, response={}", historyId, responseBody);
+                
+                // 解析AI返回的结果
+                if (StringUtils.hasText(responseBody)) {
+                    try {
+                        @SuppressWarnings("unchecked")
+                        Map<String, Object> responseMap = (Map<String, Object>) JSON.parseObject(responseBody, Map.class);
+                        if (responseMap != null) {
+                            String summary = extractStringValue(responseMap, "summary");
+                            String optimizationSuggestions = extractStringValue(responseMap, "optimizationSuggestions");
+                            
+                            // 更新历史记录的AI分析结果
+                            if (StringUtils.hasText(summary) || StringUtils.hasText(optimizationSuggestions)) {
+                                updateHistoryAiAnalysis(historyId, 1, summary, optimizationSuggestions);
+                                log.info("更新AI分析结果成功 - historyId={}", historyId);
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.warn("解析AI分析结果失败 - historyId={}, responseBody={}, error={}", 
+                                historyId, responseBody, e.getMessage());
+                    }
+                }
+            } else {
+                log.warn("AI统计分析接口调用失败 - historyId={}, statusCode={}", 
+                        historyId, response != null ? response.getStatusCode() : null);
+            }
+        } catch (Exception e) {
+            log.error("调用AI统计分析接口异常 - historyId={}, error={}", historyId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 从Map中提取String值
+     */
+    private String extractStringValue(Map<String, Object> map, String key) {
+        if (map == null) {
+            return null;
+        }
+        Object value = map.get(key);
+        if (value == null) {
+            return null;
+        }
+        return value.toString();
+    }
 
 }

+ 1001 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StorePlatformMenuServiceImpl.java

@@ -0,0 +1,1001 @@
+package shop.alien.store.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;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StorePlatformMenu;
+import shop.alien.entity.store.vo.StorePlatformMenuVo;
+import shop.alien.mapper.StorePlatformMenuMapper;
+import shop.alien.store.service.StorePlatformMenuService;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 商家菜单管理 业务层实现
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class StorePlatformMenuServiceImpl extends ServiceImpl<StorePlatformMenuMapper, StorePlatformMenu> implements StorePlatformMenuService {
+
+    private final StorePlatformMenuMapper storePlatformMenuMapper;
+
+    @Override
+    public IPage<StorePlatformMenuVo> getMenuPage(int page, int size, String menuName) {
+        log.info("分页查询菜单列表,页码={}, 页大小={}, 菜单名称={}", page, size, menuName);
+        
+        // 1. 查询所有未删除的菜单(支持模糊查询)
+        LambdaQueryWrapper<StorePlatformMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StorePlatformMenu::getDelFlag, "0");
+        
+        // 支持菜单名称模糊查询
+        if (StringUtils.hasText(menuName)) {
+            queryWrapper.like(StorePlatformMenu::getMenuName, menuName);
+        }
+        
+        // 按显示顺序和创建时间排序
+        queryWrapper.orderByAsc(StorePlatformMenu::getMenuSort);
+        queryWrapper.orderByDesc(StorePlatformMenu::getCreatedTime);
+        
+        // 查询所有符合条件的菜单
+        List<StorePlatformMenu> allMenus = storePlatformMenuMapper.selectList(queryWrapper);
+        
+        // 转换为VO对象
+        List<StorePlatformMenuVo> allMenuVos = allMenus.stream()
+                .map(this::convertToVo)
+                .collect(Collectors.toList());
+        
+        // 2. 构建树形结构(根据parent_id构建)
+        List<StorePlatformMenuVo> treeMenus = buildMenuTreeByParentId(allMenuVos);
+        
+        // 3. 对一级菜单(parent_id = 0 或 null)进行分页
+        List<StorePlatformMenuVo> level1Menus = treeMenus.stream()
+                .filter(menu -> menu.getParentId() == null || menu.getParentId() == 0)
+                .collect(Collectors.toList());
+        
+        // 计算分页
+        int total = level1Menus.size();
+        int start = (page - 1) * size;
+        int end = Math.min(start + size, total);
+        
+        List<StorePlatformMenuVo> pageMenus = start < total ? level1Menus.subList(start, end) : new ArrayList<>();
+        
+        // 4. 构建分页结果
+        IPage<StorePlatformMenuVo> pageResult = new Page<>(page, size, total);
+        pageResult.setRecords(pageMenus);
+        
+        log.info("分页查询菜单列表完成,总记录数={}, 当前页记录数={}", total, pageMenus.size());
+        
+        return pageResult;
+    }
+    
+    /**
+     * 将StorePlatformMenu转换为StorePlatformMenuVo
+     */
+    private StorePlatformMenuVo convertToVo(StorePlatformMenu menu) {
+        StorePlatformMenuVo vo = new StorePlatformMenuVo();
+        vo.setMenuId(menu.getMenuId());
+        vo.setMenuName(menu.getMenuName());
+        vo.setParentId(menu.getParentId());
+        vo.setMenuType(menu.getMenuType());
+        vo.setMenuSort(menu.getMenuSort());
+        vo.setLevel(menu.getLevel());
+        vo.setPath(menu.getPath());
+        vo.setComponent(menu.getComponent());
+        vo.setPerms(menu.getPerms());
+        vo.setIcon(menu.getIcon());
+        vo.setStatus(menu.getStatus());
+        vo.setVisible(menu.getVisible());
+        vo.setIsFrame(menu.getIsFrame());
+        vo.setIsCache(menu.getIsCache());
+        vo.setDelFlag(menu.getDelFlag());
+        vo.setCreateBy(menu.getCreateBy());
+        vo.setCreatedTime(menu.getCreatedTime());
+        vo.setUpdateBy(menu.getUpdateBy());
+        vo.setUpdatedTime(menu.getUpdatedTime());
+        vo.setRemark(menu.getRemark());
+        vo.setChildren(new ArrayList<StorePlatformMenu>());
+        return vo;
+    }
+    
+    /**
+     * 根据parent_id构建菜单树形结构
+     * parent_id = 0 或 null 表示一级菜单
+     * 一级菜单包含二级菜单,二级菜单包含三级菜单
+     * 同时设置父菜单名称用于回显
+     */
+    private List<StorePlatformMenuVo> buildMenuTreeByParentId(List<StorePlatformMenuVo> allMenus) {
+        // 创建菜单ID到菜单对象的映射,便于快速查找
+        Map<Long, StorePlatformMenuVo> menuMap = allMenus.stream()
+                .collect(Collectors.toMap(StorePlatformMenuVo::getMenuId, menu -> menu));
+        
+        // 获取一级菜单(parent_id = 0 或 null)
+        List<StorePlatformMenuVo> rootMenus = allMenus.stream()
+                .filter(menu -> menu.getParentId() == null || menu.getParentId() == 0)
+                .sorted((a, b) -> {
+                    if (a.getMenuSort() != null && b.getMenuSort() != null) {
+                        return a.getMenuSort().compareTo(b.getMenuSort());
+                    }
+                    return 0;
+                })
+                .collect(Collectors.toList());
+        
+        // 递归构建树形结构
+        for (StorePlatformMenuVo rootMenu : rootMenus) {
+            buildChildrenRecursive(rootMenu, allMenus, menuMap);
+        }
+        
+        return rootMenus;
+    }
+    
+    /**
+     * 递归构建子菜单树
+     * 
+     * @param parentMenu 父菜单
+     * @param allMenus 所有菜单列表
+     * @param menuMap 菜单ID到菜单对象的映射
+     */
+    private void buildChildrenRecursive(StorePlatformMenuVo parentMenu, 
+                                       List<StorePlatformMenuVo> allMenus, 
+                                       Map<Long, StorePlatformMenuVo> menuMap) {
+        // 查找当前菜单的所有子菜单(parent_id = 当前菜单的menu_id)
+        List<StorePlatformMenuVo> children = allMenus.stream()
+                .filter(menu -> parentMenu.getMenuId().equals(menu.getParentId()))
+                .sorted((a, b) -> {
+                    if (a.getMenuSort() != null && b.getMenuSort() != null) {
+                        return a.getMenuSort().compareTo(b.getMenuSort());
+                    }
+                    return 0;
+                })
+                .collect(Collectors.toList());
+        
+        if (CollectionUtils.isEmpty(children)) {
+            parentMenu.setChildren(new ArrayList<StorePlatformMenu>());
+            return;
+        }
+        
+        // 设置父菜单名称用于回显
+        for (StorePlatformMenuVo childMenu : children) {
+            // 设置一级父菜单名称
+            if (parentMenu.getParentId() == null || parentMenu.getParentId() == 0) {
+                // 如果父菜单是一级菜单,子菜单设置一级父菜单名称
+                childMenu.setFirstLevelMenuName(parentMenu.getMenuName());
+            } else {
+                // 如果父菜单是二级菜单,子菜单设置二级父菜单名称
+                childMenu.setSecondLevelMenuName(parentMenu.getMenuName());
+                // 同时查找一级父菜单名称
+                StorePlatformMenuVo grandParentMenu = menuMap.get(parentMenu.getParentId());
+                if (grandParentMenu != null) {
+                    childMenu.setFirstLevelMenuName(grandParentMenu.getMenuName());
+                }
+            }
+            
+            // 递归构建子菜单的子菜单
+            buildChildrenRecursive(childMenu, allMenus, menuMap);
+        }
+        
+        // 转换为 List<StorePlatformMenu> 类型
+        List<StorePlatformMenu> menuChildren = new ArrayList<StorePlatformMenu>();
+        menuChildren.addAll(children);
+        parentMenu.setChildren(menuChildren);
+    }
+
+    /**
+     * 编辑菜单 可以更新
+     * @param storePlatformMenu
+     * @return
+     */
+    @Override
+    public R<StorePlatformMenu> getByMenuId(StorePlatformMenu storePlatformMenu) {
+        Long menuId = storePlatformMenu.getMenuId();
+        log.info("开始查询/编辑菜单,菜单ID={}", menuId);
+
+        // 参数校验:菜单ID不能为空
+        if (menuId == null) {
+            log.warn("查询/编辑菜单失败:ID为空");
+            return R.fail("菜单ID不能为空");
+        }
+
+        // 查询当前菜单信息
+        StorePlatformMenu currentMenu = storePlatformMenuMapper.selectById(menuId);
+        if (currentMenu == null) {
+            log.warn("查询失败:菜单不存在,菜单ID={}", menuId);
+            return R.fail("菜单不存在");
+        }
+
+        // 判断是否有更新操作(如果传入了菜单名称、路径或状态,则认为是更新操作)
+        boolean isUpdate = StringUtils.hasText(storePlatformMenu.getMenuName())
+                || StringUtils.hasText(storePlatformMenu.getPath())
+                || StringUtils.hasText(storePlatformMenu.getStatus());
+
+        if (isUpdate) {
+            // 更新操作:只允许更新本级菜单的名称、路径、状态
+            log.info("开始更新菜单信息,菜单ID={}", menuId);
+
+            // 只更新允许的字段:菜单名称、路径、状态
+            if (StringUtils.hasText(storePlatformMenu.getMenuName())) {
+                currentMenu.setMenuName(storePlatformMenu.getMenuName());
+            }
+            if (StringUtils.hasText(storePlatformMenu.getPath())) {
+                currentMenu.setPath(storePlatformMenu.getPath());
+            }
+            if (StringUtils.hasText(storePlatformMenu.getStatus())) {
+                currentMenu.setStatus(storePlatformMenu.getStatus());
+            }
+
+            currentMenu.setUpdatedTime(new Date());
+            currentMenu.setUpdateBy("admin");
+//            // 切换状态:0启用,1禁用
+//            String newStatus = "0".equals(currentMenu.getStatus()) ? "1" : "0";
+//            currentMenu.setStatus(newStatus);
+//            // 如果状态为空,默认设置为"0"(开启)
+//            if (!StringUtils.hasText(currentMenu.getStatus())) {
+//                currentMenu.setStatus("0");
+//            }
+
+            // 4. 校验菜单名称是否重复(同一父菜单下不能有重名)
+            LambdaQueryWrapper<StorePlatformMenu> nameCheck = new LambdaQueryWrapper<>();
+            nameCheck.eq(StorePlatformMenu::getMenuName, currentMenu.getMenuName())
+                    .eq(StorePlatformMenu::getParentId, currentMenu.getParentId())
+                    .eq(StorePlatformMenu::getDelFlag, "0");
+            StorePlatformMenu duplicateMenu = storePlatformMenuMapper.selectOne(nameCheck);
+            if (duplicateMenu != null) {
+                log.warn("更新菜单失败:菜单名称已存在,菜单名称={}, parentId={}",
+                        currentMenu.getMenuName(), currentMenu.getParentId());
+                return R.fail("该菜单名称在同级菜单中已存在,请更换其他名称");
+            }
+
+            boolean result = this.updateById(currentMenu);
+            if (result) {
+                log.info("更新菜单信息成功,菜单ID={}, 菜单名称={}, 路径={}, 状态={}",
+                        menuId, currentMenu.getMenuName(), currentMenu.getPath(), currentMenu.getStatus());
+                // 重新查询更新后的菜单
+                currentMenu = storePlatformMenuMapper.selectById(menuId);
+            } else {
+                log.warn("更新菜单信息失败,菜单ID={}", menuId);
+                return R.fail("更新菜单信息失败");
+            }
+        }
+
+        // 查询上级菜单信息并设置到返回对象中
+        Integer level = currentMenu.getLevel();
+        if (level != null) {
+            // 如果是二级菜单,查询一级菜单名称
+            if (level == 2 && currentMenu.getParentId() != null && currentMenu.getParentId() != 0) {
+                StorePlatformMenu firstLevelMenu = storePlatformMenuMapper.selectById(currentMenu.getParentId());
+                if (firstLevelMenu != null) {
+                    currentMenu.setFirstLevelMenuName(firstLevelMenu.getMenuName());
+                }
+            }
+            // 如果是三级菜单,查询一级和二级菜单名称
+            else if (level == 3 && currentMenu.getParentId() != null && currentMenu.getParentId() != 0) {
+                // 先查询二级菜单
+                StorePlatformMenu secondLevelMenu = storePlatformMenuMapper.selectById(currentMenu.getParentId());
+                if (secondLevelMenu != null) {
+                    currentMenu.setSecondLevelMenuName(secondLevelMenu.getMenuName());
+                    // 再查询一级菜单
+                    if (secondLevelMenu.getParentId() != null && secondLevelMenu.getParentId() != 0) {
+                        StorePlatformMenu firstLevelMenu = storePlatformMenuMapper.selectById(secondLevelMenu.getParentId());
+                        if (firstLevelMenu != null) {
+                            currentMenu.setFirstLevelMenuName(firstLevelMenu.getMenuName());
+                        }
+                    }
+                }
+            }
+        }
+
+
+
+        return R.data(currentMenu);
+    }
+
+    @Override
+    public R<StorePlatformMenu> deleteMenuDetails(StorePlatformMenu storePlatformMenu) {
+        Long menuId = storePlatformMenu.getMenuId();
+        
+        log.info("开始删除菜单,菜单ID={}", menuId);
+        
+        // 参数校验:菜单ID不能为空
+        if (menuId == null) {
+            log.warn("删除菜单失败:菜单ID为空");
+            return R.fail("菜单ID不能为空");
+        }
+        
+        // 查询当前菜单信息
+        StorePlatformMenu currentMenu = storePlatformMenuMapper.selectById(menuId);
+        if (currentMenu == null) {
+            log.warn("删除菜单失败:菜单不存在,菜单ID={}", menuId);
+            return R.fail("菜单不存在");
+        }
+        
+        Integer level = currentMenu.getLevel();
+        if (level == null) {
+            log.warn("删除菜单失败:层级信息为空,菜单ID={}", menuId);
+            return R.fail("层级信息不能为空");
+        }
+        
+        int totalUpdateCount = 0;
+        
+        // 检查当前菜单是否已删除
+        if (!"0".equals(currentMenu.getDelFlag())) {
+            log.warn("删除菜单失败:菜单已被删除,菜单ID={}, 层级={}, del_flag={}", menuId, level, currentMenu.getDelFlag());
+            return R.fail("菜单已被删除");
+        }
+        
+        // 根据层级进行级联删除
+        if (level == 1) {
+            // 删除层级1的菜单:同时删除层级2和3的菜单
+            log.info("删除一级菜单,同时删除层级2和3的菜单,菜单ID={}, 层级={}", menuId, level);
+            LambdaQueryWrapper<StorePlatformMenu> level1Query = new LambdaQueryWrapper<>();
+            level1Query
+                    .eq(StorePlatformMenu::getLevel, 1)
+                    .eq(StorePlatformMenu::getDelFlag, "0");
+            List<StorePlatformMenu> levelMenus = storePlatformMenuMapper.selectList(level1Query);
+            List<Long> levelMenuIds = levelMenus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            if (!CollectionUtils.isEmpty(levelMenus)){
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 1)
+                        .in(StorePlatformMenu::getMenuId, levelMenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getDelFlag, "2");
+                int count = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count;
+                log.info("删除一级菜单本身,菜单ID={}, 更新记录数={}", menuId, count);
+            }
+            // 1. 先查询所有层级2的子菜单
+            LambdaQueryWrapper<StorePlatformMenu> level2Query = new LambdaQueryWrapper<>();
+            level2Query
+                      .eq(StorePlatformMenu::getLevel, 2)
+                      .eq(StorePlatformMenu::getDelFlag, "0");
+            List<StorePlatformMenu> level2Menus = storePlatformMenuMapper.selectList(level2Query);
+            List<Long> level2MenuIds = level2Menus.stream()
+                                                  .map(StorePlatformMenu::getMenuId)
+                                                  .collect(Collectors.toList());
+            log.info("查询到层级2子菜单数量:{}", level2Menus != null ? level2Menus.size() : 0);
+
+
+            // 3. 删除所有层级2子菜单
+            if (!CollectionUtils.isEmpty(level2Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 2)
+                        .in(StorePlatformMenu::getMenuId, level2MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getDelFlag, "2");
+                int count2 = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count2;
+                log.info("删除层级2子菜单,parent_id={}, 更新记录数={}", menuId, count2);
+            } else {
+                log.info("没有层级2子菜单需要删除,菜单ID={}", menuId);
+            }
+            LambdaQueryWrapper<StorePlatformMenu> level3Query = new LambdaQueryWrapper<>();
+            level3Query
+                    .eq(StorePlatformMenu::getLevel, 3)
+                    .eq(StorePlatformMenu::getDelFlag, "0");
+            List<StorePlatformMenu> level3Menus = storePlatformMenuMapper.selectList(level3Query);
+            List<Long> level3MenuIds = level3Menus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            // 4. 删除所有层级3子菜单(parent_id在层级2菜单ID列表中)
+            if (!CollectionUtils.isEmpty(level3Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper3 = new LambdaUpdateWrapper<>();
+                wrapper3.eq(StorePlatformMenu::getLevel, 3)
+                        .in(StorePlatformMenu::getMenuId, level3MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getDelFlag, "2");
+                int count3 = storePlatformMenuMapper.update(null, wrapper3);
+                totalUpdateCount += count3;
+                log.info("删除层级3子菜单, 更新记录数={}", level3MenuIds, count3);
+            } else {
+                log.info("没有层级3子菜单需要删除,菜单ID={}", menuId);
+            }
+            
+            log.info("删除一级菜单成功,菜单ID={}, 层级={}, 总更新记录数={}", menuId, level, totalUpdateCount);
+            
+        } else if (level == 2) {
+            // 删除层级2的菜单:同时删除层级3的菜单
+            log.info("删除二级菜单,同时删除层级3的菜单,菜单ID={}, 层级={}", menuId, level);
+            LambdaQueryWrapper<StorePlatformMenu> level2Query = new LambdaQueryWrapper<>();
+            level2Query
+                    .eq(StorePlatformMenu::getLevel, 2)
+                    .eq(StorePlatformMenu::getDelFlag, "0");
+            List<StorePlatformMenu> level2Menus = storePlatformMenuMapper.selectList(level2Query);
+            List<Long> level2MenuIds = level2Menus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            // 3. 删除所有层级2子菜单
+            if (!CollectionUtils.isEmpty(level2Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 2)
+                        .in(StorePlatformMenu::getMenuId, level2MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getDelFlag, "2");
+                int count2 = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count2;
+                log.info("删除层级2子菜单,菜单ID={}, 更新记录数={}", menuId, count2);
+            } else {
+                log.info("没有层级2子菜单需要删除,菜单ID={}", menuId);
+            }
+
+            // 1. 先查询所有层级3的子菜单
+            LambdaQueryWrapper<StorePlatformMenu> level3Query = new LambdaQueryWrapper<>();
+            level3Query
+                      .eq(StorePlatformMenu::getLevel, 3)
+                      .eq(StorePlatformMenu::getDelFlag, "0");
+            List<StorePlatformMenu> level3Menus = storePlatformMenuMapper.selectList(level3Query);
+            List<Long> level3MenuIds = level3Menus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            log.info("查询到层级3子菜单数量:{}", level3Menus != null ? level3Menus.size() : 0);
+            
+            // 3. 删除所有层级3子菜单
+            if (!CollectionUtils.isEmpty(level3Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 3)
+                        .in(StorePlatformMenu::getMenuId, level3MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getDelFlag, "2");
+                int count2 = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count2;
+                log.info("删除层级3子菜单,菜单ID={}, 更新记录数={}", menuId, totalUpdateCount);
+            } else {
+                log.info("没有层级3子菜单需要删除,菜单ID={}", menuId);
+            }
+
+            
+        } else if (level == 3) {
+            // 删除层级3的菜单:只删除当前菜单
+            log.info("删除三级菜单,只删除当前菜单,菜单ID={}, 层级={}", menuId, level);
+            LambdaQueryWrapper<StorePlatformMenu> level3Query = new LambdaQueryWrapper<>();
+            level3Query
+                    .eq(StorePlatformMenu::getLevel, 3)
+                    .eq(StorePlatformMenu::getDelFlag, "0");
+            List<StorePlatformMenu> level3Menus = storePlatformMenuMapper.selectList(level3Query);
+            List<Long> level3MenuIds = level3Menus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            log.info("查询到层级3子菜单数量:{}", level3Menus != null ? level3Menus.size() : 0);
+
+            // 3. 删除所有层级3子菜单
+            if (!CollectionUtils.isEmpty(level3Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 3)
+                        .in(StorePlatformMenu::getMenuId, level3MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getDelFlag, "2");
+                int count2 = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count2;
+                log.info("删除层级3子菜单,parent_id={}, 更新记录数={}", menuId, count2);
+            } else {
+                log.info("没有层级3子菜单需要删除,菜单ID={}", menuId);
+            }
+            
+            if (totalUpdateCount > 0) {
+                log.info("删除三级菜单成功,菜单ID={}, 层级={}, 更新记录数={}", menuId, level, totalUpdateCount);
+            } else {
+                log.warn("删除三级菜单未找到记录或已删除,菜单ID={}, 层级={}, del_flag={}", menuId, level, currentMenu.getDelFlag());
+                return R.fail("删除失败:菜单不存在或已被删除");
+            }
+            
+        } else {
+            log.warn("删除菜单失败:层级值不正确,菜单ID={}, 层级={}", menuId, level);
+            return R.fail("层级值不正确,应为1、2或3");
+        }
+        
+        return R.data(currentMenu);
+    }
+
+    @Override
+    public R<StorePlatformMenu> editUpdateMenuStatus(Long menuId, String status, Integer level) {
+        // 参数校验:菜单ID不能为空
+        if (menuId == null) {
+            log.warn("更新菜单状态失败:菜单ID为空");
+            return R.fail("菜单ID不能为空");
+        }
+
+        // 查询当前菜单信息
+        StorePlatformMenu currentMenu = storePlatformMenuMapper.selectById(menuId);
+        if (currentMenu == null) {
+            log.warn("更新菜单状态失败:菜单不存在,菜单ID={}", menuId);
+            return R.fail("菜单不存在");
+        }
+
+        // 检查菜单是否已删除
+        if (!"0".equals(currentMenu.getDelFlag())) {
+            log.warn("更新菜单状态失败:菜单已被删除,菜单ID={}, del_flag={}", menuId, currentMenu.getDelFlag());
+            return R.fail("菜单已被删除");
+        }
+        int totalUpdateCount =0 ;
+
+        // 动态切换状态:如果数据库中是"1"则改为"0",如果是"0"则改为"1"
+        String currentStatus = currentMenu.getStatus();
+        String newStatus;
+        if ("1".equals(currentStatus)) {
+            newStatus = "0";  // 当前是禁用(1),切换为启用(0)
+        } else if ("0".equals(currentStatus)) {
+            newStatus = "1";  // 当前是启用(0),切换为禁用(1)
+        } else {
+            // 如果状态值异常,默认为启用(0)
+            log.warn("菜单状态值异常,菜单ID={}, 当前状态={},默认设置为启用(0)", menuId, currentStatus);
+            newStatus = "0";
+        }
+
+
+        // 根据层级进行级联删除
+        if (level == 1) {
+            // 删除层级1的菜单:同时删除层级2和3的菜单
+            log.info("启用禁用一级菜单,同时启用禁用层级2和3的菜单,菜单ID={}, 层级={}", menuId, level);
+            LambdaQueryWrapper<StorePlatformMenu> level1Query = new LambdaQueryWrapper<>();
+            level1Query
+                    .eq(StorePlatformMenu::getLevel, 1)
+                    .eq(StorePlatformMenu::getDelFlag, "0")
+                    .eq(StorePlatformMenu::getStatus, currentStatus);
+            List<StorePlatformMenu> levelMenus = storePlatformMenuMapper.selectList(level1Query);
+            List<Long> levelMenuIds = levelMenus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            if (!CollectionUtils.isEmpty(levelMenus)){
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 1)
+                        .in(StorePlatformMenu::getMenuId, levelMenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getStatus, newStatus);
+                int count = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count;
+                log.info("启用禁用一级菜单本身,菜单ID={}, 更新记录数={}", menuId, count);
+            }
+            // 1. 先查询所有层级2的子菜单
+            LambdaQueryWrapper<StorePlatformMenu> level2Query = new LambdaQueryWrapper<>();
+            level2Query
+                    .eq(StorePlatformMenu::getLevel, 2)
+                    .eq(StorePlatformMenu::getDelFlag, "0")
+                    .eq(StorePlatformMenu::getStatus, currentStatus);
+            List<StorePlatformMenu> level2Menus = storePlatformMenuMapper.selectList(level2Query);
+            List<Long> level2MenuIds = level2Menus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            log.info("查询到层级2子菜单数量:{}", level2Menus != null ? level2Menus.size() : 0);
+
+
+            // 3. 删除所有层级2子菜单
+            if (!CollectionUtils.isEmpty(level2Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 2)
+                        .in(StorePlatformMenu::getMenuId, level2MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getStatus, newStatus);
+                int count2 = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count2;
+                log.info("启用禁用层级2子菜单,菜单Id={}, 更新记录数={}", menuId, totalUpdateCount);
+            } else {
+                log.info("没有层级2子菜单需要启用禁用,菜单ID={}", menuId);
+            }
+            LambdaQueryWrapper<StorePlatformMenu> level3Query = new LambdaQueryWrapper<>();
+            level3Query
+                    .eq(StorePlatformMenu::getLevel, 3)
+                    .eq(StorePlatformMenu::getDelFlag, "0")
+                    .eq(StorePlatformMenu::getStatus, currentStatus);
+            List<StorePlatformMenu> level3Menus = storePlatformMenuMapper.selectList(level3Query);
+            List<Long> level3MenuIds = level3Menus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            // 4. 删除所有层级3子菜单(parent_id在层级2菜单ID列表中)
+            if (!CollectionUtils.isEmpty(level3Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper3 = new LambdaUpdateWrapper<>();
+                wrapper3.eq(StorePlatformMenu::getLevel, 3)
+                        .in(StorePlatformMenu::getMenuId, level3MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getStatus, newStatus);
+                int count3 = storePlatformMenuMapper.update(null, wrapper3);
+                totalUpdateCount += count3;
+                log.info("启用禁用层级3子菜单, 更新记录数={}", level3MenuIds, count3);
+            } else {
+                log.info("没有层级3子菜单需要操作,菜单ID={}", menuId);
+            }
+
+            log.info("操作一级菜单成功,菜单ID={}, 层级={}, 总更新记录数={}", menuId, level, totalUpdateCount);
+
+        } else if (level == 2) {
+            // 删除层级2的菜单:同时删除层级3的菜单
+            log.info("启用禁用二级菜单,同时启用禁用层级3的菜单,菜单ID={}, 层级={}", menuId, level);
+            LambdaQueryWrapper<StorePlatformMenu> level2Query = new LambdaQueryWrapper<>();
+            level2Query
+                    .eq(StorePlatformMenu::getLevel, 2)
+                    .eq(StorePlatformMenu::getDelFlag, "0")
+                    .eq(StorePlatformMenu::getStatus, currentStatus);
+            List<StorePlatformMenu> level2Menus = storePlatformMenuMapper.selectList(level2Query);
+            List<Long> level2MenuIds = level2Menus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            // 3. 删除所有层级2子菜单
+            if (!CollectionUtils.isEmpty(level2Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 2)
+                        .in(StorePlatformMenu::getMenuId, level2MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getStatus, newStatus);
+                int count2 = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count2;
+                log.info("启用禁用层级2子菜单,菜单ID={}, 更新记录数={}", menuId, count2);
+            } else {
+                log.info("没有层级2子菜单需要操作,菜单ID={}", menuId);
+            }
+
+            // 1. 先查询所有层级3的子菜单
+            LambdaQueryWrapper<StorePlatformMenu> level3Query = new LambdaQueryWrapper<>();
+            level3Query.eq(StorePlatformMenu::getMenuId, menuId)
+                    .eq(StorePlatformMenu::getLevel, 3)
+                    .eq(StorePlatformMenu::getDelFlag, "0")
+                    .eq(StorePlatformMenu::getStatus, currentStatus);
+            List<StorePlatformMenu> level3Menus = storePlatformMenuMapper.selectList(level3Query);
+            List<Long> level3MenuIds = level3Menus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            log.info("查询到层级3子菜单数量:{}", level3Menus != null ? level3Menus.size() : 0);
+
+            // 3. 删除所有层级3子菜单
+            if (!CollectionUtils.isEmpty(level3Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 3)
+                        .in(StorePlatformMenu::getMenuId, level3MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getStatus, newStatus);
+                int count2 = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count2;
+                log.info("启用禁用层级3子菜单,菜单ID={}, 更新记录数={}", menuId, totalUpdateCount);
+            } else {
+                log.info("没有层级3子菜单需要操作,菜单ID={}", menuId);
+            }
+
+
+        } else if (level == 3) {
+            // 删除层级3的菜单:只删除当前菜单
+            log.info("删除三级菜单,只删除当前菜单,菜单ID={}, 层级={}", menuId, level);
+            LambdaQueryWrapper<StorePlatformMenu> level3Query = new LambdaQueryWrapper<>();
+            level3Query.eq(StorePlatformMenu::getMenuId, menuId)
+                    .eq(StorePlatformMenu::getLevel, 3)
+                    .eq(StorePlatformMenu::getDelFlag, "0")
+                    .eq(StorePlatformMenu::getStatus, currentStatus);
+            List<StorePlatformMenu> level3Menus = storePlatformMenuMapper.selectList(level3Query);
+            List<Long> level3MenuIds = level3Menus.stream()
+                    .map(StorePlatformMenu::getMenuId)
+                    .collect(Collectors.toList());
+            log.info("查询到层级3子菜单数量:{}", level3Menus != null ? level3Menus.size() : 0);
+
+            // 3. 删除所有层级3子菜单
+            if (!CollectionUtils.isEmpty(level3Menus)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper2 = new LambdaUpdateWrapper<>();
+                wrapper2.eq(StorePlatformMenu::getLevel, 3)
+                        .in(StorePlatformMenu::getMenuId, level3MenuIds)
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .set(StorePlatformMenu::getStatus, newStatus);
+                int count2 = storePlatformMenuMapper.update(null, wrapper2);
+                totalUpdateCount += count2;
+                log.info("删除层级3子菜单,parent_id={}, 更新记录数={}", menuId, count2);
+            } else {
+                log.info("没有层级3子菜单需要删除,菜单ID={}", menuId);
+            }
+
+            if (totalUpdateCount > 0) {
+                log.info("删除三级菜单成功,菜单ID={}, 层级={}, 更新记录数={}", menuId, level, totalUpdateCount);
+            } else {
+                log.warn("删除三级菜单未找到记录或已删除,菜单ID={}, 层级={}, del_flag={}", menuId, level, currentMenu.getDelFlag());
+                return R.fail("删除失败:菜单不存在或已被删除");
+            }
+
+        } else {
+            log.warn("删除菜单失败:层级值不正确,菜单ID={}, 层级={}", menuId, level);
+            return R.fail("层级值不正确,应为1、2或3");
+        }
+
+
+        // 重新查询更新后的菜单信息
+        StorePlatformMenu updatedMenu = storePlatformMenuMapper.selectById(menuId);
+        return R.data(updatedMenu);
+    }
+
+    @Override
+    public R<StorePlatformMenu> addByMenu(StorePlatformMenu store) {
+        log.info("开始新增菜单,菜单名称={}, 一级分类名称={}, 二级分类名称={}", 
+                store != null ? store.getMenuName() : null,
+                store != null ? store.getFirstLevelMenuName() : null,
+                store != null ? store.getSecondLevelMenuName() : null);
+        
+        // 1. 参数校验
+        if (store == null) {
+            log.warn("新增菜单失败:菜单信息为空");
+            return R.fail("菜单信息不能为空");
+        }
+        if (!StringUtils.hasText(store.getMenuName())) {
+            log.warn("新增菜单失败:菜单名称为空");
+            return R.fail("菜单名称不能为空");
+        }
+        if (!StringUtils.hasText(store.getPath())) {
+            log.warn("新增菜单失败:路径为空");
+            return R.fail("路径不能为空");
+        }
+        
+        // 2. 根据一级、二级分类名称确定 parentId 和 level
+        Long parentId = determineParentIdByMenuNames(store);
+        if (parentId == null && (StringUtils.hasText(store.getFirstLevelMenuName()) || 
+            StringUtils.hasText(store.getSecondLevelMenuName()))) {
+            log.warn("新增菜单失败:无法确定父菜单,一级分类名称={}, 二级分类名称={}", 
+                    store.getFirstLevelMenuName(), store.getSecondLevelMenuName());
+            return R.fail("无法确定父菜单,请检查一级和二级分类名称是否正确");
+        }
+        
+        store.setParentId(parentId != null ? parentId : 0L);
+        
+        // 3. 根据 parentId 计算 level
+        Integer level = calculateLevelByParentId(store.getParentId());
+        if (level == null) {
+            log.warn("新增菜单失败:无法确定菜单层级,parentId={}", store.getParentId());
+            return R.fail("无法确定菜单层级");
+        }
+        store.setLevel(level);
+        
+        // 4. 校验菜单名称是否重复(同一父菜单下不能有重名)
+        LambdaQueryWrapper<StorePlatformMenu> nameCheck = new LambdaQueryWrapper<>();
+        nameCheck.eq(StorePlatformMenu::getMenuName, store.getMenuName())
+                .eq(StorePlatformMenu::getParentId, store.getParentId())
+                .eq(StorePlatformMenu::getDelFlag, "0");
+        StorePlatformMenu duplicateMenu = storePlatformMenuMapper.selectOne(nameCheck);
+        if (duplicateMenu != null) {
+            log.warn("新增菜单失败:菜单名称已存在,菜单名称={}, parentId={}", 
+                    store.getMenuName(), store.getParentId());
+            return R.fail("该菜单名称在同级菜单中已存在,请更换其他名称");
+        }
+        
+        // 5. 校验父菜单是否存在且未删除(如果不是一级菜单)
+        if (store.getParentId() != null && store.getParentId() != 0) {
+            StorePlatformMenu parentMenu = storePlatformMenuMapper.selectById(store.getParentId());
+            if (parentMenu == null) {
+                log.warn("新增菜单失败:父菜单不存在,parentId={}", store.getParentId());
+                return R.fail("父菜单不存在");
+            }
+            if (!"0".equals(parentMenu.getDelFlag())) {
+                log.warn("新增菜单失败:父菜单已被删除,parentId={}", store.getParentId());
+                return R.fail("父菜单已被删除");
+            }
+        }
+        
+        // 6. 设置默认值
+        setDefaultValues(store);
+        
+        // 7. 设置排序值(自动设置为最大值+1)
+        Integer maxSort = getMaxSortByParentId(store.getParentId());
+        store.setMenuSort(maxSort != null ? maxSort + 1 : 0);
+        
+        // 8. 设置创建时间和创建人
+        store.setCreatedTime(new Date());
+        store.setCreateBy("admin");
+        
+        // 9. 执行新增操作
+        int insert = storePlatformMenuMapper.insert(store);
+        if (insert > 0) {
+            log.info("新增菜单成功,菜单ID={}, 菜单名称={}, parentId={}, level={}", 
+                    store.getMenuId(), store.getMenuName(), store.getParentId(), store.getLevel());
+            
+            // 重新查询菜单信息(包含自动生成的ID等信息)
+            StorePlatformMenu savedMenu = storePlatformMenuMapper.selectById(store.getMenuId());
+            if (savedMenu != null) {
+                // 设置父菜单名称用于回显
+                setParentMenuNames(savedMenu);
+                return R.data(savedMenu);
+            }
+            return R.data(store);
+        } else {
+            log.warn("新增菜单失败,菜单名称={}", store.getMenuName());
+            return R.fail("新增菜单失败");
+        }
+    }
+    
+    /**
+     * 根据一级、二级分类名称确定 parentId
+     * 
+     * @param store 菜单对象(包含 firstLevelMenuName 和 secondLevelMenuName)
+     * @return parentId,如果是一级菜单则返回 0,如果无法确定则返回 null
+     */
+    private Long determineParentIdByMenuNames(StorePlatformMenu store) {
+        String firstLevelMenuName = store.getFirstLevelMenuName();
+        String secondLevelMenuName = store.getSecondLevelMenuName();
+        
+        // 如果直接指定了 parentId,优先使用
+        if (store.getParentId() != null && store.getParentId() != 0) {
+            return store.getParentId();
+        }
+        
+        // 如果一级和二级分类名称都为空,说明是一级菜单
+        if (!StringUtils.hasText(firstLevelMenuName) && !StringUtils.hasText(secondLevelMenuName)) {
+            return 0L;
+        }
+        
+        // 如果只有一级分类名称,说明是二级菜单
+        if (StringUtils.hasText(firstLevelMenuName) && !StringUtils.hasText(secondLevelMenuName)) {
+            // 查找一级菜单
+            LambdaQueryWrapper<StorePlatformMenu> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StorePlatformMenu::getMenuName, firstLevelMenuName)
+                       .and(w -> w.isNull(StorePlatformMenu::getParentId)
+                                .or().eq(StorePlatformMenu::getParentId, 0))
+                       .eq(StorePlatformMenu::getDelFlag, "0");
+            StorePlatformMenu firstLevelMenu = storePlatformMenuMapper.selectOne(queryWrapper);
+            if (firstLevelMenu != null) {
+                return firstLevelMenu.getMenuId();
+            }
+            log.warn("未找到一级菜单,菜单名称={}", firstLevelMenuName);
+            return null;
+        }
+        
+        // 如果有一级和二级分类名称,说明是三级菜单
+        if (StringUtils.hasText(firstLevelMenuName) && StringUtils.hasText(secondLevelMenuName)) {
+            // 先查找一级菜单
+            LambdaQueryWrapper<StorePlatformMenu> level1Query = new LambdaQueryWrapper<>();
+            level1Query.eq(StorePlatformMenu::getMenuName, firstLevelMenuName)
+                      .and(w -> w.isNull(StorePlatformMenu::getParentId)
+                               .or().eq(StorePlatformMenu::getParentId, 0))
+                      .eq(StorePlatformMenu::getDelFlag, "0");
+            StorePlatformMenu firstLevelMenu = storePlatformMenuMapper.selectOne(level1Query);
+            if (firstLevelMenu == null) {
+                log.warn("未找到一级菜单,菜单名称={}", firstLevelMenuName);
+                return null;
+            }
+            
+            // 再查找二级菜单
+            LambdaQueryWrapper<StorePlatformMenu> level2Query = new LambdaQueryWrapper<>();
+            level2Query.eq(StorePlatformMenu::getMenuName, secondLevelMenuName)
+                      .eq(StorePlatformMenu::getParentId, firstLevelMenu.getMenuId())
+                      .eq(StorePlatformMenu::getDelFlag, "0");
+            StorePlatformMenu secondLevelMenu = storePlatformMenuMapper.selectOne(level2Query);
+            if (secondLevelMenu != null) {
+                return secondLevelMenu.getMenuId();
+            }
+            log.warn("未找到二级菜单,菜单名称={}, 父菜单ID={}", 
+                    secondLevelMenuName, firstLevelMenu.getMenuId());
+            return null;
+        }
+        
+        return 0L;
+    }
+    
+    /**
+     * 根据 parentId 计算菜单层级
+     * 
+     * @param parentId 父菜单ID
+     * @return 菜单层级(1一级菜单 2二级菜单 3三级菜单)
+     */
+    private Integer calculateLevelByParentId(Long parentId) {
+        if (parentId == null || parentId == 0) {
+            return 1; // 一级菜单
+        }
+        
+        // 查询父菜单
+        StorePlatformMenu parentMenu = storePlatformMenuMapper.selectById(parentId);
+        if (parentMenu == null) {
+            return null;
+        }
+        
+        Integer parentLevel = parentMenu.getLevel();
+        if (parentLevel == null) {
+            // 如果父菜单没有层级信息,尝试根据parentId推断
+            if (parentMenu.getParentId() == null || parentMenu.getParentId() == 0) {
+                return 2; // 父菜单是一级菜单,当前是二级菜单
+            } else {
+                return 3; // 父菜单不是一级菜单,当前是三级菜单
+            }
+        }
+        
+        // 层级不能超过3
+        int newLevel = parentLevel + 1;
+        if (newLevel > 3) {
+            log.warn("菜单层级超过3级,parentId={}, parentLevel={}", parentId, parentLevel);
+            return null;
+        }
+        
+        return newLevel;
+    }
+    
+    /**
+     * 设置默认值
+     */
+    private void setDefaultValues(StorePlatformMenu store) {
+        if (!StringUtils.hasText(store.getStatus())) {
+            store.setStatus("0"); // 默认开启
+        }
+        if (!StringUtils.hasText(store.getDelFlag())) {
+            store.setDelFlag("0"); // 默认未删除
+        }
+        if (!StringUtils.hasText(store.getVisible())) {
+            store.setVisible("0"); // 默认显示
+        }
+        if (!StringUtils.hasText(store.getIsFrame())) {
+            store.setIsFrame("1"); // 默认非外链
+        }
+        if (!StringUtils.hasText(store.getIsCache())) {
+            store.setIsCache("0"); // 默认缓存
+        }
+        if (store.getMenuSort() == null) {
+            store.setMenuSort(0); // 默认排序值
+        }
+        if (!StringUtils.hasText(store.getMenuType())) {
+            // 根据层级设置默认菜单类型:一级菜单为M(目录),其他为C(菜单)
+            if (store.getLevel() != null && store.getLevel() == 1) {
+                store.setMenuType("M");
+            } else {
+                store.setMenuType("C");
+            }
+        }
+    }
+    
+    /**
+     * 获取指定父菜单下的最大排序值
+     * 
+     * @param parentId 父菜单ID
+     * @return 最大排序值,如果没有子菜单则返回null
+     */
+    private Integer getMaxSortByParentId(Long parentId) {
+        LambdaQueryWrapper<StorePlatformMenu> queryWrapper = new LambdaQueryWrapper<>();
+        if (parentId == null || parentId == 0) {
+            queryWrapper.isNull(StorePlatformMenu::getParentId)
+                       .or().eq(StorePlatformMenu::getParentId, 0);
+        } else {
+            queryWrapper.eq(StorePlatformMenu::getParentId, parentId);
+        }
+        queryWrapper.eq(StorePlatformMenu::getDelFlag, "0")
+                   .orderByDesc(StorePlatformMenu::getMenuSort)
+                   .last("LIMIT 1");
+        
+        StorePlatformMenu menu = storePlatformMenuMapper.selectOne(queryWrapper);
+        return menu != null && menu.getMenuSort() != null ? menu.getMenuSort() : null;
+    }
+    
+    /**
+     * 设置父菜单名称用于回显
+     * 
+     * @param menu 菜单对象
+     */
+    private void setParentMenuNames(StorePlatformMenu menu) {
+        if (menu.getParentId() == null || menu.getParentId() == 0) {
+            // 一级菜单,不需要设置父菜单名称
+            return;
+        }
+        
+        // 查询父菜单
+        StorePlatformMenu parentMenu = storePlatformMenuMapper.selectById(menu.getParentId());
+        if (parentMenu == null) {
+            return;
+        }
+        
+        // 设置二级父菜单名称
+        menu.setSecondLevelMenuName(parentMenu.getMenuName());
+        
+        // 如果父菜单还有父菜单,设置一级父菜单名称
+        if (parentMenu.getParentId() != null && parentMenu.getParentId() != 0) {
+            StorePlatformMenu grandParentMenu = storePlatformMenuMapper.selectById(parentMenu.getParentId());
+            if (grandParentMenu != null) {
+                menu.setFirstLevelMenuName(grandParentMenu.getMenuName());
+            }
+        } else {
+            // 如果父菜单是一级菜单,设置一级父菜单名称
+            menu.setFirstLevelMenuName(parentMenu.getMenuName());
+        }
+    }
+
+
+}
+

+ 239 - 9
alien-store/src/main/java/shop/alien/store/service/impl/StoreUserServiceImpl.java

@@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.netease.yidun.sdk.anticheat.v3.AnticheatCheckRequest;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.tuple.Triple;
@@ -21,6 +22,7 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.excelVo.StoreUserExcelVo;
 import shop.alien.entity.store.excelVo.util.ExcelGenerator;
+import shop.alien.entity.store.vo.StoreSubExcelVo;
 import shop.alien.entity.store.vo.StoreUserVo;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
@@ -44,6 +46,7 @@ import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 二期-门店用户 服务实现类
@@ -71,6 +74,8 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
 
     private final LifeMessageMapper lifeMessageMapper;
 
+    private final StorePlatformUserRoleMapper storePlatformUserRoleMapper;
+
     private final NearMeService nearMeService;
 
     private final AliOSSUtil aliOSSUtil;
@@ -397,21 +402,157 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
     }
 
     @Override
-    public R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status) {
+    public R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status,Integer accountType) {
+
         IPage<StoreUser> page = new Page<>(pageNum, pageSize);
+        IPage<StoreUserVo> storeUserVoIPage = new Page<>();
+        // 查询子账号(accountType == 2)
+        if (accountType == 2) {
+            // 构建子账号查询条件
+            LambdaQueryWrapper<StoreUser> subAccountWrapper = new LambdaQueryWrapper<>();
+            subAccountWrapper.eq(StoreUser::getAccountType, 2) // 子账号类型
+                    .eq(status != null, StoreUser::getStatus, status);
+
+            // 先通过中间表storePlatformUserRole获取所有子账号的userId列表
+            LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
+            List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
+            List<Integer> subAccountUserIds = null;
+            if (!CollectionUtils.isEmpty(userRoles)) {
+                // 提取所有在中间表中的子账号userId
+                subAccountUserIds = userRoles.stream()
+                        .map(StorePlatformUserRole::getUserId)
+                        .distinct()
+                        .collect(Collectors.toList());
+            }
+
+            // 当id不为空时,通过中间表storePlatformUserRole判断是否为子账号(必须是子账号)
+            if (StringUtils.isNotEmpty(id)) {
+                Integer parsedId = safeParseInt(id);
+                if (parsedId != null) {
+                    // 必须通过中间表判断,只有存在中间表中的才是子账号
+                    if (subAccountUserIds != null && subAccountUserIds.contains(parsedId)) {
+                        // 如果在中间表中存在,说明是子账号,添加ID条件
+                        subAccountWrapper.eq(StoreUser::getId, parsedId);
+                    } else {
+                        // 如果不在中间表中,添加一个无法匹配的条件,避免查询到非子账号
+                        subAccountWrapper.eq(StoreUser::getId, -1);
+                    }
+                }
+            }
+
+            // 当phone不为空时,对子账号和主账号电话进行模糊查询,并关联storePlatformUserRole
+            if (StringUtils.isNotEmpty(phone)) {
+                if (subAccountUserIds != null && !subAccountUserIds.isEmpty()) {
+                    // 先查询主账号phone匹配的主账号ID列表
+                    LambdaQueryWrapper<StoreUser> mainAccountWrapper = new LambdaQueryWrapper<>();
+                    mainAccountWrapper.eq(StoreUser::getAccountType, 1)
+                            .like(StoreUser::getPhone, phone);
+                    List<StoreUser> mainAccounts = storeUserMapper.selectList(mainAccountWrapper);
+                    List<Integer> mainAccountIds = mainAccounts.stream()
+                            .map(StoreUser::getId)
+                            .collect(Collectors.toList());
+
+                    // 限制查询范围:只查询在中间表中存在的子账号
+                    subAccountWrapper.in(StoreUser::getId, subAccountUserIds);
+                    // 查询条件:在中间表范围内的子账号中,子账号phone匹配 OR 主账号phone匹配
+                    subAccountWrapper.and(wrapper -> {
+                        // 子账号本身的phone模糊查询
+                        wrapper.like(StoreUser::getPhone, phone);
+                        // 或者主账号phone匹配(通过subAccountId关联到主账号)
+                        if (!CollectionUtils.isEmpty(mainAccountIds)) {
+                            wrapper.or().in(StoreUser::getSubAccountId, mainAccountIds);
+                        }
+                    });
+                } else {
+                    // 如果没有中间表数据,先查询主账号phone匹配的主账号ID
+                    LambdaQueryWrapper<StoreUser> mainAccountWrapper = new LambdaQueryWrapper<>();
+                    mainAccountWrapper.eq(StoreUser::getAccountType, 1)
+                            .like(StoreUser::getPhone, phone);
+                    List<StoreUser> mainAccounts = storeUserMapper.selectList(mainAccountWrapper);
+                    List<Integer> mainAccountIds = mainAccounts.stream()
+                            .map(StoreUser::getId)
+                            .collect(Collectors.toList());
+
+                    // 查询条件:子账号phone匹配 OR 主账号phone匹配
+                    if (!CollectionUtils.isEmpty(mainAccountIds)) {
+                        subAccountWrapper.and(wrapper -> {
+                            wrapper.like(StoreUser::getPhone, phone)
+                                    .or().in(StoreUser::getSubAccountId, mainAccountIds);
+                        });
+                    } else {
+                        // 如果没有主账号匹配,只查询子账号phone
+                        subAccountWrapper.like(StoreUser::getPhone, phone);
+                    }
+                }
+            } else {
+                // phone为空时,如果中间表有数据,限制只查询中间表中的子账号
+                if (subAccountUserIds != null && !subAccountUserIds.isEmpty()) {
+                    subAccountWrapper.in(StoreUser::getId, subAccountUserIds);
+                }
+            }
+
+            subAccountWrapper.orderByDesc(StoreUser::getCreatedTime);
+
+            IPage<StoreUser> subAccountsPage = storeUserMapper.selectPage(page, subAccountWrapper);
+            BeanUtils.copyProperties(subAccountsPage, storeUserVoIPage);
+            
+            List<StoreUserVo> resultRecords = new ArrayList<>();
+            for (StoreUser subAccount : subAccountsPage.getRecords()) {
+                StoreUserVo storeUserVo = new StoreUserVo();
+                BeanUtils.copyProperties(subAccount, storeUserVo);
+                
+                // 查询主账号信息(通过 subAccountId 关联)
+                if (subAccount.getSubAccountId() != null) {
+                    StoreUser mainAccount = storeUserMapper.selectById(subAccount.getSubAccountId());
+                    if (mainAccount != null) {
+                        // 设置主账号联系电话
+                        storeUserVo.setParentAccountPhone(mainAccount.getPhone());
+                        storeUserVo.setParentAccountId(mainAccount.getId());
+                        storeUserVo.setParentAccountName(mainAccount.getName());
+                    }
+                }
+                
+                // 不返回密码
+                storeUserVo.setPassword(null);
+                storeUserVo.setPayPassword(null);
+                
+                resultRecords.add(storeUserVo);
+            }
+            
+            storeUserVoIPage.setRecords(resultRecords);
+            return R.data(storeUserVoIPage);
+        }
+
+        // 查询主账号(accountType == 1)
         LambdaQueryWrapper<StoreUser> storeUserLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        storeUserLambdaQueryWrapper.like(!StringUtils.isEmpty(id), StoreUser::getId, id);
-        storeUserLambdaQueryWrapper.like(!StringUtils.isEmpty(phone), StoreUser::getPhone, phone);
-        storeUserLambdaQueryWrapper.eq(status != null, StoreUser::getStatus, status);
-        storeUserLambdaQueryWrapper.orderByDesc(StoreUser::getCreatedTime);
+        storeUserLambdaQueryWrapper.like(!StringUtils.isEmpty(id), StoreUser::getId, id)
+                .like(!StringUtils.isEmpty(phone), StoreUser::getPhone, phone)
+                .eq(status != null, StoreUser::getStatus, status)
+                .eq(StoreUser::getAccountType, accountType)
+                .orderByDesc(StoreUser::getCreatedTime);
+        
         IPage<StoreUser> storeUsers = storeUserMapper.selectPage(page, storeUserLambdaQueryWrapper);
-        IPage<StoreUserVo> storeUserVoIPage = new Page<>();
         BeanUtils.copyProperties(storeUsers, storeUserVoIPage);
+        
         for (StoreUser storeUser : storeUserVoIPage.getRecords()) {
-            //不返回密码
+            // 不返回密码
             storeUser.setPassword(null);
             storeUser.setPayPassword(null);
+
+            // 如果是主账号,统计子账号数量
+            if (accountType == 1) {
+                List<StoreUser> childAccounts = getChildAccountsByParentId(String.valueOf(storeUser.getId()));
+                Integer childCount = childAccounts != null ? childAccounts.size() : 0;
+                storeUser.setChildAccountCount(childCount);
+                if (childAccounts != null && !childAccounts.isEmpty()) {
+                    List<String> childPhoneNumbers = childAccounts.stream()
+                            .map(StoreUser::getPhone)
+                            .collect(Collectors.toList());
+                    storeUser.setChildPhoneNumbers(childPhoneNumbers);
+                }
+            }
         }
+
         return R.data(storeUserVoIPage);
     }
 
@@ -481,21 +622,26 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
     }
 
     @Override
-    public String exportExcel(String id, String phone, String status) throws IOException {
+    public String exportExcel(String id, String phone, String status, Integer accountType) throws IOException {
         // 定义格式化模式
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-        // 先将 Date 转换为 Instant
+        if (accountType==1){
         List<StoreUser> storeUsers = storeUserMapper.selectList(
                 new LambdaQueryWrapper<StoreUser>()
                         .like(StringUtils.isNotEmpty(id), StoreUser::getId, id)
                         .like(StringUtils.isNotEmpty(phone), StoreUser::getPhone, phone)
                         .eq(StringUtils.isNotEmpty(status), StoreUser::getStatus, status)
+                        .eq(StoreUser::getAccountType, 1)
+                        .orderByDesc(StoreUser::getCreatedTime)
         );
+
         List<StoreUserExcelVo> storeUserExcelVoList = new ArrayList<>();
         int serialNumber = 0;
         for (StoreUser storeUser : storeUsers) {
             StoreUserExcelVo storeUserExcelVo = new StoreUserExcelVo();
             storeUserExcelVo.setSerialNumber(++serialNumber);
+
+            Integer currentUserId = storeUser.getId();
             BeanUtils.copyProperties(storeUser, storeUserExcelVo);
             Instant instant = storeUser.getCreatedTime().toInstant();
             // 格式化时间
@@ -503,13 +649,51 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
             storeUserExcelVo.setCreatedTime(formattedTime);
             //格式化状态
             storeUserExcelVo.setStatus(storeUser.getStatus() == 0 ? "启用" : "禁用");
+            List<StoreUser> childAccounts = getChildAccountsByParentId(String.valueOf(currentUserId));
+            Integer childCount = childAccounts != null ? childAccounts.size() : 0;
+            storeUserExcelVo.setChildAccountCount(childCount);
             storeUserExcelVoList.add(storeUserExcelVo);
         }
         String fileName = UUID.randomUUID().toString().replace("-", "");
         String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", storeUserExcelVoList, StoreUserExcelVo.class);
         return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
+        }
+        List<StoreUser> storeUsers = storeUserMapper.selectList(
+                new LambdaQueryWrapper<StoreUser>()
+                        .like(StringUtils.isNotEmpty(id), StoreUser::getId, id)
+                        .like(StringUtils.isNotEmpty(phone), StoreUser::getPhone, phone)
+                        .eq(StringUtils.isNotEmpty(status), StoreUser::getStatus, status)
+                        .eq(StoreUser::getAccountType, 2)
+                        .orderByDesc(StoreUser::getCreatedTime)
+        );
+        List<StoreSubExcelVo> storeUserExcelVoList = new ArrayList<>();
+        int serialNumber = 0;
+        for (StoreUser subAccount : storeUsers) {
+            StoreSubExcelVo storeUserExcelVo = new StoreSubExcelVo();
+            storeUserExcelVo.setSerialNumber(++serialNumber);
+            BeanUtils.copyProperties(subAccount, storeUserExcelVo);
+            if (subAccount.getSubAccountId() != null){
+                StoreUser mainAccount = storeUserMapper.selectById(subAccount.getSubAccountId());
+                if (mainAccount != null){
+                    storeUserExcelVo.setId(mainAccount.getId());
+                    storeUserExcelVo.setParentAccountPhone(mainAccount.getPhone());
+                }
+            }
+
+            Instant instant = subAccount.getCreatedTime().toInstant();
+            // 格式化时间
+            String formattedTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime().format(formatter);
+            storeUserExcelVo.setCreatedTime(formattedTime);
+            //格式化状态
+            storeUserExcelVo.setStatus(subAccount.getStatus() == 0 ? "启用" : "禁用");
+            storeUserExcelVoList.add(storeUserExcelVo);
+        }
+        String fileName = UUID.randomUUID().toString().replace("-", "");
+        String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", storeUserExcelVoList, StoreSubExcelVo.class);
+        return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
     }
 
+
     /**
      * 注册校验
      *
@@ -794,4 +978,50 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
         return R.data(returnMap);
     }
 
+    @Override
+    public List<StoreUser> getSubAccountsByMainAccountId(Integer mainAccountId) {
+        LambdaQueryWrapper<StoreUser> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreUser::getAccountType, 2)  // 子账号类型
+                .eq(StoreUser::getSubAccountId, mainAccountId)  // 关联主账号ID
+                .eq(StoreUser::getDeleteFlag, 0)  // 未删除的账号
+                .orderByDesc(StoreUser::getCreatedTime);  // 按创建时间倒序
+
+        return storeUserMapper.selectList(queryWrapper);
+    }
+
+
+    @Override
+    public List<StoreUser> getChildAccountsByParentId(String Id) {
+        Integer parentId = safeParseInt(Id);
+        if (parentId == null) {
+            return Collections.emptyList();
+        }
+
+        LambdaQueryWrapper<StoreUser> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreUser::getSubAccountId, Id)
+                .eq(StoreUser::getAccountType, 2)  // 子账号
+                .eq(StoreUser::getDeleteFlag, 0)
+                .orderByAsc(StoreUser::getCreatedTime);
+
+        List<StoreUser> result = this.list(wrapper);
+
+        // 确保返回非空集合
+        return result != null ? result : Collections.emptyList();
+
+    }
+
+    /**
+     * 安全解析字符串为整数
+     */
+    private Integer safeParseInt(String str) {
+        if (str == null || str.trim().isEmpty()) {
+            return null;
+        }
+        try {
+            return Integer.valueOf(str.trim());
+        } catch (NumberFormatException e) {
+            log.warn("无法解析字符串为整数: {}", str);
+            return null;
+        }
+    }
 }

+ 334 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreVideoServiceImpl.java

@@ -1,15 +1,29 @@
 package shop.alien.store.service.impl;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.apache.commons.lang3.StringUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import shop.alien.entity.store.StoreVideo;
 import shop.alien.mapper.StoreVideoMapper;
 import shop.alien.store.service.StoreVideoService;
+import shop.alien.util.ali.AliOSSUtil;
+import shop.alien.util.common.RandomCreateUtil;
+import shop.alien.util.common.VideoUtils;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -24,6 +38,326 @@ import java.util.List;
 @RequiredArgsConstructor
 public class StoreVideoServiceImpl extends ServiceImpl<StoreVideoMapper, StoreVideo> implements StoreVideoService {
 
+    private final VideoUtils videoUtils;
+
+    private final AliOSSUtil aliOSSUtil;
+
+    /**
+     * 视频文件类型列表
+     */
+    private static final List<String> VIDEO_FILE_TYPES = Arrays.asList("mp4", "avi", "flv", "mkv", "rmvb", "wmv", "3gp", "mov");
+
+    @Value("${spring.web.resources.static-locations}")
+    private String uploadDir;
+
+    /**
+     * 保存视频,自动截取第一帧作为封面
+     *
+     * @param entity 视频实体
+     * @return 是否保存成功
+     */
+    @Override
+    public boolean save(StoreVideo entity) {
+        // 参数验证
+        if (entity == null) {
+            log.error("StoreVideoServiceImpl.save ERROR: entity is null");
+            return false;
+        }
+
+        // 如果imgUrl不为空,尝试处理视频和封面
+        if (StringUtils.isNotBlank(entity.getImgUrl())) {
+            try {
+                // 处理视频URL,截取封面并更新imgUrl字段
+                String processedImgUrl = processVideoAndCover(entity.getImgUrl());
+                if (StringUtils.isNotBlank(processedImgUrl)) {
+                    entity.setImgUrl(processedImgUrl);
+                }
+            } catch (Exception e) {
+                log.error("StoreVideoServiceImpl.save 处理视频封面失败, imgUrl: {}", entity.getImgUrl(), e);
+                // 如果处理失败,记录错误日志但不影响保存操作
+            }
+        }
+
+        // 调用父类的save方法保存
+        return super.save(entity);
+    }
+
+    /**
+     * 处理视频URL,截取第一帧作为封面并生成JSON数组
+     *
+     * @param imgUrl 视频URL或JSON字符串
+     * @return 处理后的JSON数组字符串
+     */
+    private String processVideoAndCover(String imgUrl) {
+        log.info("StoreVideoServiceImpl.processVideoAndCover imgUrl={}", imgUrl);
+        
+        // 参数验证
+        if (StringUtils.isBlank(imgUrl)) {
+            log.warn("StoreVideoServiceImpl.processVideoAndCover imgUrl is blank");
+            return imgUrl;
+        }
+
+        String videoUrl = null;
+
+        // 判断imgUrl是否为JSON格式
+        try {
+            JSONArray jsonArray = JSONArray.parseArray(imgUrl);
+            // 如果已经是JSON数组格式,检查是否包含video和cover
+            if (jsonArray != null && !jsonArray.isEmpty()) {
+                JSONObject firstItem = jsonArray.getJSONObject(0);
+                if (firstItem != null && firstItem.containsKey("video")) {
+                    videoUrl = firstItem.getString("video");
+                    // 如果已经有cover,则直接返回
+                    if (firstItem.containsKey("cover") && StringUtils.isNotBlank(firstItem.getString("cover"))) {
+                        log.info("StoreVideoServiceImpl.processVideoAndCover 已有封面,无需重新生成");
+                        return imgUrl;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // 如果不是JSON格式,则将imgUrl视为视频URL
+            log.debug("StoreVideoServiceImpl.processVideoAndCover imgUrl不是JSON格式,视为视频URL");
+            videoUrl = imgUrl;
+        }
+
+        // 如果videoUrl为空,无法处理
+        if (StringUtils.isBlank(videoUrl)) {
+            log.warn("StoreVideoServiceImpl.processVideoAndCover videoUrl is blank");
+            return imgUrl;
+        }
+
+        // 检查是否为视频URL
+        if (!isVideoUrl(videoUrl)) {
+            log.warn("StoreVideoServiceImpl.processVideoAndCover 不是视频URL: {}", videoUrl);
+            return imgUrl;
+        }
+
+        // 下载视频并截取封面
+        File tempVideoFile = null;
+        File coverFile = null;
+        try {
+            // 从URL下载视频到临时文件
+            tempVideoFile = downloadVideoFromUrl(videoUrl);
+            if (tempVideoFile == null || !tempVideoFile.exists()) {
+                log.error("StoreVideoServiceImpl.processVideoAndCover 下载视频失败: {}", videoUrl);
+                return imgUrl;
+            }
+
+            // 截取第一帧作为封面
+            String coverPath = videoUtils.getFirstFrame(tempVideoFile.getAbsolutePath());
+            if (StringUtils.isBlank(coverPath)) {
+                log.error("StoreVideoServiceImpl.processVideoAndCover 截取封面失败: {}", tempVideoFile.getAbsolutePath());
+                return imgUrl;
+            }
+
+            coverFile = new File(coverPath);
+            if (!coverFile.exists()) {
+                log.error("StoreVideoServiceImpl.processVideoAndCover 封面文件不存在: {}", coverPath);
+                return imgUrl;
+            }
+
+            // 上传封面到OSS
+            String coverFileName = generateCoverFileName(videoUrl);
+            String coverOssPath = "video/" + coverFileName + ".jpg";
+            String coverUrl = aliOSSUtil.uploadFile(coverFile, coverOssPath);
+            if (StringUtils.isBlank(coverUrl)) {
+                log.error("StoreVideoServiceImpl.processVideoAndCover 上传封面失败: {}", coverOssPath);
+                return imgUrl;
+            }
+
+            // 构建JSON数组
+            JSONArray resultArray = new JSONArray();
+            JSONObject videoObject = new JSONObject();
+            videoObject.put("video", videoUrl);
+            videoObject.put("cover", coverUrl);
+            resultArray.add(videoObject);
+
+            log.info("StoreVideoServiceImpl.processVideoAndCover 处理成功, videoUrl: {}, coverUrl: {}", videoUrl, coverUrl);
+            return JSON.toJSONString(resultArray);
+
+        } catch (Exception e) {
+            log.error("StoreVideoServiceImpl.processVideoAndCover 处理异常", e);
+            return imgUrl;
+        } finally {
+            // 清理临时文件
+            if (tempVideoFile != null && tempVideoFile.exists()) {
+                boolean deleted = tempVideoFile.delete();
+                if (!deleted) {
+                    log.warn("StoreVideoServiceImpl.processVideoAndCover 删除临时视频文件失败: {}", tempVideoFile.getAbsolutePath());
+                }
+            }
+            if (coverFile != null && coverFile.exists()) {
+                boolean deleted = coverFile.delete();
+                if (!deleted) {
+                    log.warn("StoreVideoServiceImpl.processVideoAndCover 删除临时封面文件失败: {}", coverFile.getAbsolutePath());
+                }
+            }
+        }
+    }
+
+    /**
+     * 检查URL是否为视频URL
+     *
+     * @param url 视频URL
+     * @return 是否为视频URL
+     */
+    private boolean isVideoUrl(String url) {
+        if (StringUtils.isBlank(url)) {
+            return false;
+        }
+        // 检查URL是否以视频文件扩展名结尾
+        String lowerUrl = url.toLowerCase();
+        for (String videoType : VIDEO_FILE_TYPES) {
+            if (lowerUrl.endsWith("." + videoType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 从URL下载视频到临时文件
+     *
+     * @param videoUrl 视频URL
+     * @return 临时文件对象
+     */
+    private File downloadVideoFromUrl(String videoUrl) {
+        log.info("StoreVideoServiceImpl.downloadVideoFromUrl videoUrl={}", videoUrl);
+        
+        InputStream inputStream = null;
+        FileOutputStream outputStream = null;
+        File tempFile = null;
+        
+        try {
+            // 创建URL连接
+            URL url = new URL(videoUrl);
+            URLConnection connection = url.openConnection();
+            connection.setConnectTimeout(10000); // 10秒连接超时
+            connection.setReadTimeout(60000); // 60秒读取超时
+
+            // 创建临时文件
+            String tempDir = uploadDir.endsWith("/") ? uploadDir : uploadDir + "/";
+            String tempFileName = "temp_video_" + System.currentTimeMillis() + "_" + RandomCreateUtil.getRandomNum(6);
+            
+            // 从URL中提取文件扩展名
+            String fileExtension = getFileExtensionFromUrl(videoUrl);
+            if (StringUtils.isNotBlank(fileExtension)) {
+                tempFileName += "." + fileExtension;
+            } else {
+                tempFileName += ".mp4"; // 默认使用mp4扩展名
+            }
+            
+            tempFile = new File(tempDir + tempFileName);
+            
+            // 确保目录存在
+            File parentDir = tempFile.getParentFile();
+            if (parentDir != null && !parentDir.exists()) {
+                parentDir.mkdirs();
+            }
+
+            // 下载文件
+            inputStream = connection.getInputStream();
+            outputStream = new FileOutputStream(tempFile);
+            
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+            }
+            outputStream.flush();
+
+            log.info("StoreVideoServiceImpl.downloadVideoFromUrl 下载成功, tempFile: {}", tempFile.getAbsolutePath());
+            return tempFile;
+
+        } catch (Exception e) {
+            log.error("StoreVideoServiceImpl.downloadVideoFromUrl 下载失败, videoUrl: {}", videoUrl, e);
+            // 如果下载失败,删除可能创建的临时文件
+            if (tempFile != null && tempFile.exists()) {
+                tempFile.delete();
+            }
+            return null;
+        } finally {
+            // 关闭流
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (Exception e) {
+                    log.warn("StoreVideoServiceImpl.downloadVideoFromUrl 关闭输入流失败", e);
+                }
+            }
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (Exception e) {
+                    log.warn("StoreVideoServiceImpl.downloadVideoFromUrl 关闭输出流失败", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 从URL中提取文件扩展名
+     *
+     * @param url 文件URL
+     * @return 文件扩展名(不包含点)
+     */
+    private String getFileExtensionFromUrl(String url) {
+        if (StringUtils.isBlank(url)) {
+            return null;
+        }
+        
+        // 移除URL参数
+        int questionMarkIndex = url.indexOf('?');
+        if (questionMarkIndex > 0) {
+            url = url.substring(0, questionMarkIndex);
+        }
+        
+        // 提取扩展名
+        int lastDotIndex = url.lastIndexOf('.');
+        if (lastDotIndex > 0 && lastDotIndex < url.length() - 1) {
+            return url.substring(lastDotIndex + 1).toLowerCase();
+        }
+        
+        return null;
+    }
+
+    /**
+     * 生成封面文件名
+     *
+     * @param videoUrl 视频URL
+     * @return 封面文件名(不包含扩展名)
+     */
+    private String generateCoverFileName(String videoUrl) {
+        // 从视频URL中提取文件名(去除扩展名)
+        String fileName = "cover_" + System.currentTimeMillis() + "_" + RandomCreateUtil.getRandomNum(6);
+        
+        try {
+            // 尝试从URL中提取原始文件名
+            String urlPath = new URL(videoUrl).getPath();
+            int lastSlashIndex = urlPath.lastIndexOf('/');
+            if (lastSlashIndex >= 0 && lastSlashIndex < urlPath.length() - 1) {
+                String originalFileName = urlPath.substring(lastSlashIndex + 1);
+                // 移除文件扩展名
+                int lastDotIndex = originalFileName.lastIndexOf('.');
+                if (lastDotIndex > 0) {
+                    String nameWithoutExt = originalFileName.substring(0, lastDotIndex);
+                    // 清理文件名,移除特殊字符
+                    nameWithoutExt = nameWithoutExt.replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5]", "");
+                    if (StringUtils.isNotBlank(nameWithoutExt)) {
+                        fileName = nameWithoutExt + RandomCreateUtil.getRandomNum(6);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.debug("StoreVideoServiceImpl.generateCoverFileName 从URL提取文件名失败", e);
+        }
+        
+        // 清理文件名,避免包含逗号等特殊字符
+        fileName = fileName.replaceAll(",", "");
+        
+        return fileName;
+    }
+
     /**
      * 根据门店ID获取视频列表
      *

+ 37 - 0
alien-store/src/main/java/shop/alien/store/service/impl/SubAccountStoreServiceImpl.java

@@ -0,0 +1,37 @@
+package shop.alien.store.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.vo.SubAccountStoreListVo;
+import shop.alien.mapper.SubAccountStoreMapper;
+import shop.alien.store.service.SubAccountStoreService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 子账号关联门店服务实现类
+ *
+ * @author system
+ * @since 2025-01-01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SubAccountStoreServiceImpl implements SubAccountStoreService {
+
+    private final SubAccountStoreMapper subAccountStoreMapper;
+
+    @Override
+    public List<SubAccountStoreListVo> getSubAccountStoreList(Integer userId) {
+        log.info("SubAccountStoreServiceImpl.getSubAccountStoreList?userId={}", userId);
+        if (userId == null) {
+            log.warn("用户ID为空");
+            return new ArrayList<>();
+        }
+        List<SubAccountStoreListVo> storeList = subAccountStoreMapper.selectSubAccountStoreListByUserId(userId);
+        log.info("查询子账号关联门店列表完成 - 用户ID: {}, 门店数量: {}", userId, storeList != null ? storeList.size() : 0);
+        return storeList;
+    }
+}

+ 55 - 1
alien-util/src/main/java/shop/alien/util/common/VideoUtils.java

@@ -29,7 +29,7 @@ public class VideoUtils {
     public String getImg(String videoFilePath) {
         log.info("VideoUtils.getImg videoFilePath={}", videoFilePath);
         String[] file = videoFilePath.split("\\.");
-        log.info("VideoUtils.getImg file={}", file);
+        log.info("VideoUtils.getImg file={}", (Object) file);
         // 截图保存位置
         String imgFilePath = file[0] + ".jpg";
         log.info("VideoUtils.getImg imgFilePath={}", imgFilePath);
@@ -66,6 +66,60 @@ public class VideoUtils {
     }
 
     /**
+     * 视频截取第一帧作为封面
+     *
+     * @param videoFilePath 视频媒体文件路径
+     * @return 封面图片文件路径
+     */
+    public String getFirstFrame(String videoFilePath) {
+        log.info("VideoUtils.getFirstFrame videoFilePath={}", videoFilePath);
+        // 验证参数
+        if (videoFilePath == null || videoFilePath.isEmpty()) {
+            log.error("VideoUtils.getFirstFrame ERROR: videoFilePath is null or empty");
+            return "";
+        }
+        
+        String[] file = videoFilePath.split("\\.");
+        log.info("VideoUtils.getFirstFrame file={}", (Object) file);
+        // 截图保存位置
+        String imgFilePath = file[0] + ".jpg";
+        log.info("VideoUtils.getFirstFrame imgFilePath={}", imgFilePath);
+        
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        // 重定向错误流,防阻塞
+        processBuilder.redirectErrorStream(true);
+
+        // 如果为本地测试,ffmpegPath地址需求修改为本地安装程序的地址(环境变量不好用)
+        String ffmpegPath = "ffmpeg";
+        if ("windows".equals(OSUtil.getOsName())) {
+            // ffmpegPath = "C:/Program Files (x86)/ffmpeg-6.0/bin/ffmpeg.exe";
+            ffmpegPath = "C:/project/ext/ffmpeg-6.0/bin/ffmpeg.exe";
+        }
+
+        // 调用ffmpeg 执行截取命令,截取第一帧(00:00:00),需要服务器中安装了ffmpeg并配置了环境变量
+        processBuilder.command(ffmpegPath, "-i", videoFilePath, "-ss", "00:00:00", "-vframes", "1", imgFilePath);
+        try {
+            Process process = processBuilder.start();
+            // 获取流信息
+            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                log.debug("VideoUtils.getFirstFrame ffmpeg output: {}", line);
+            }
+            int exitCode = process.waitFor();
+            log.info("VideoUtils.getFirstFrame ffmpeg exit code: {}", exitCode);
+            if (0 == exitCode) {
+                return imgFilePath;
+            }
+            log.error("VideoUtils.getFirstFrame ERROR: ffmpeg exit code is not 0");
+            return "";
+        } catch (IOException | InterruptedException e) {
+            log.error("VideoUtils.getFirstFrame ERROR Msg={}", e.getMessage(), e);
+            return "";
+        }
+    }
+
+    /**
      * FFMpeg视频截取图片
      *
      * @param videoFilePath 视频媒体文件

+ 2 - 2
alien-util/src/main/java/shop/alien/util/encryption/EncryptTypeHandler.java

@@ -28,7 +28,7 @@ public class EncryptTypeHandler extends BaseTypeHandler<String> {
     @Override
     public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
         EncryptProperties properties = SpringContextUtil.getBean(EncryptProperties.class);
-        if (properties != null && properties.isEnabled() && parameter != null) {
+        if (properties != null && properties.isDbEnabled() && parameter != null) {
             String encrypted = StandardAesUtil.encrypt(parameter, properties.getKey(), properties.getIv());
             ps.setString(i, encrypted);
         } else {
@@ -56,7 +56,7 @@ public class EncryptTypeHandler extends BaseTypeHandler<String> {
             return value;
         }
         EncryptProperties properties = SpringContextUtil.getBean(EncryptProperties.class);
-        if (properties != null && properties.isEnabled()) {
+        if (properties != null && properties.isDbEnabled()) {
             try {
                 String decrypted = StandardAesUtil.decrypt(value, properties.getKey(), properties.getIv());
                 return decrypted != null ? decrypted : value;

+ 7 - 1
alien-util/src/main/java/shop/alien/util/encryption/StandardAesUtil.java

@@ -36,6 +36,11 @@ public class StandardAesUtil {
 
     public static String decrypt(String base64Data, String key, String iv) {
         try {
+            if (base64Data == null || base64Data.isEmpty()) {
+                return null;
+            }
+            log.debug("AES解密尝试: data_len={}, key_len={}, iv_len={}", base64Data.length(), key == null ? 0 : key.length(), iv == null ? 0 : iv.length());
+            
             Cipher cipher = Cipher.getInstance(ALGORITHM);
             SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
             IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
@@ -45,7 +50,8 @@ public class StandardAesUtil {
             byte[] original = cipher.doFinal(decoded);
             return new String(original, StandardCharsets.UTF_8);
         } catch (Exception e) {
-            log.error("AES解密失败: {}", e.getMessage());
+            log.error("AES解密失败: data_preview={}, error={}", 
+                      base64Data.substring(0, Math.min(base64Data.length(), 20)), e.getMessage());
             return null;
         }
     }

+ 43 - 5
alien-util/src/main/java/shop/alien/util/encryption/advice/DecryptRequestBodyAdvice.java

@@ -5,18 +5,20 @@ import org.springframework.core.MethodParameter;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpInputMessage;
 import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.util.AntPathMatcher;
 import org.springframework.util.StreamUtils;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
-import shop.alien.util.encryption.Decrypt;
 import shop.alien.util.encryption.StandardAesUtil;
 import shop.alien.util.encryption.properties.EncryptProperties;
 
+import javax.servlet.http.HttpServletRequest;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Type;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 
 /**
  * 请求体解密 Advice
@@ -28,12 +30,31 @@ public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {
     @Autowired
     private EncryptProperties encryptProperties;
 
+    @Autowired
+    private HttpServletRequest request;
+
+    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
+
     @Override
     public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
-        // 只有开启了配置,且方法或类上有 @Decrypt 注解时才拦截
-        return encryptProperties.isEnabled() && 
-               (methodParameter.hasMethodAnnotation(Decrypt.class) || 
-                methodParameter.getContainingClass().isAnnotationPresent(Decrypt.class));
+        // 1. 如果接口加密关闭,则不解密
+        if (!encryptProperties.isApiEnabled()) {
+            return false;
+        }
+
+        // 2. 校验路径排除
+        String uri = request.getRequestURI();
+        List<String> excludePaths = encryptProperties.getExcludePaths();
+        if (excludePaths != null && !excludePaths.isEmpty()) {
+            for (String pattern : excludePaths) {
+                if (PATH_MATCHER.match(pattern, uri)) {
+                    return false;
+                }
+            }
+        }
+
+        // 3. 默认全开启
+        return true;
     }
 
     @Override
@@ -41,6 +62,23 @@ public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {
         // 读取加密的请求体
         byte[] bodyBytes = StreamUtils.copyToByteArray(inputMessage.getBody());
         String encryptedData = new String(bodyBytes, StandardCharsets.UTF_8);
+
+        // 核心优化:判断是否为明文 JSON。如果是明文 JSON,则直接跳过解密
+        String trimmedData = encryptedData.trim();
+        if ((trimmedData.startsWith("{") && (trimmedData.contains("\":") || trimmedData.endsWith("}"))) ||
+            (trimmedData.startsWith("[") && (trimmedData.contains("\":") || trimmedData.endsWith("]")))) {
+            return new HttpInputMessage() {
+                @Override
+                public InputStream getBody() throws IOException {
+                    return new ByteArrayInputStream(bodyBytes);
+                }
+
+                @Override
+                public HttpHeaders getHeaders() {
+                    return inputMessage.getHeaders();
+                }
+            };
+        }
         
         // 如果是 JSON 字符串格式(带有双引号),先去掉前后的双引号
         if (encryptedData.startsWith("\"") && encryptedData.endsWith("\"")) {

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно