lyx 4 месяцев назад
Родитель
Сommit
af8d4fea29
100 измененных файлов с 10969 добавлено и 28 удалено
  1. 16 0
      alien-entity/src/main/java/shop/alien/entity/second/vo/SecondUserViolationDetailVo.java
  2. 1 1
      alien-entity/src/main/java/shop/alien/entity/second/vo/SecondUserViolationVo.java
  3. 4 4
      alien-entity/src/main/java/shop/alien/entity/store/LifeFeedback.java
  4. 16 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeSys.java
  5. 87 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeSysDept.java
  6. 108 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeSysMenu.java
  7. 78 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeSysRole.java
  8. 44 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeSysRoleMenu.java
  9. 44 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeSysUserRole.java
  10. 118 0
      alien-entity/src/main/java/shop/alien/entity/store/OperationLog.java
  11. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/ProtocolManagement.java
  12. 14 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java
  13. 2 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackQueryDto.java
  14. 45 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeSysDeptAddDto.java
  15. 27 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeSysDeptSortDto.java
  16. 33 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeSysDeptUpdateDto.java
  17. 10 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeUserViolationDto.java
  18. 19 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java
  19. 48 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/SystemUserAddDto.java
  20. 39 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/SystemUserQueryDto.java
  21. 30 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/SystemUserStatusDto.java
  22. 51 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/SystemUserUpdateDto.java
  23. 43 0
      alien-entity/src/main/java/shop/alien/entity/store/excelVo/DictionaryLibraryExcelVo.java
  24. 72 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeSysDeptVo.java
  25. 9 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeSysVo.java
  26. 9 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeUserViolationVo.java
  27. 123 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/MenuWithRoleVo.java
  28. 22 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreInfoVo.java
  29. 83 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreLicenseInfoVo.java
  30. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/SystemLoginVo.java
  31. 14 0
      alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java
  32. 16 0
      alien-entity/src/main/java/shop/alien/mapper/LifeSysDeptMapper.java
  33. 6 0
      alien-entity/src/main/java/shop/alien/mapper/LifeSysMapper.java
  34. 19 0
      alien-entity/src/main/java/shop/alien/mapper/LifeSysMenuMapper.java
  35. 33 0
      alien-entity/src/main/java/shop/alien/mapper/LifeSysRoleMapper.java
  36. 16 0
      alien-entity/src/main/java/shop/alien/mapper/LifeSysRoleMenuMapper.java
  37. 16 0
      alien-entity/src/main/java/shop/alien/mapper/LifeSysUserRoleMapper.java
  38. 1 1
      alien-entity/src/main/java/shop/alien/mapper/LifeUserDynamicsMapper.java
  39. 39 0
      alien-entity/src/main/java/shop/alien/mapper/LifeUserViolationMapper.java
  40. 16 0
      alien-entity/src/main/java/shop/alien/mapper/OperationLogMapper.java
  41. 15 0
      alien-entity/src/main/java/shop/alien/mapper/ProtocolManagementMapper.java
  42. 25 0
      alien-entity/src/main/java/shop/alien/mapper/StoreInfoMapper.java
  43. 3 0
      alien-entity/src/main/java/shop/alien/mapper/second/SecondUserViolationMapper.java
  44. 15 0
      alien-entity/src/main/resources/mapper/LifeSysMapper.xml
  45. 49 0
      alien-entity/src/main/resources/mapper/LifeSysMenuMapper.xml
  46. 28 0
      alien-entity/src/main/resources/mapper/LifeSysRoleMapper.xml
  47. 19 0
      alien-entity/src/main/resources/mapper/LifeSysRoleMenuMapper.xml
  48. 6 2
      alien-entity/src/main/resources/mapper/LifeUserDynamicsMapper.xml
  49. 110 0
      alien-entity/src/main/resources/mapper/StoreInfoMapper.xml
  50. 35 0
      alien-entity/src/main/resources/mapper/second/SecondUserViolationMapper.xml
  51. 35 0
      alien-gateway/src/main/java/shop/alien/gateway/config/FeignHttpMessageConvertersConfig.java
  52. 22 0
      alien-gateway/src/main/java/shop/alien/gateway/feign/StoreServiceFeign.java
  53. 11 0
      alien-gateway/src/main/java/shop/alien/gateway/service/impl/SystemServiceImpl.java
  54. 4 0
      alien-store/pom.xml
  55. 60 0
      alien-store/src/main/java/shop/alien/store/annotation/ChangeRecordLog.java
  56. 1043 0
      alien-store/src/main/java/shop/alien/store/aspect/OperationLogAspect.java
  57. 33 0
      alien-store/src/main/java/shop/alien/store/config/CustomAccessDeniedHandler.java
  58. 74 0
      alien-store/src/main/java/shop/alien/store/config/SecurityConfig.java
  59. 167 0
      alien-store/src/main/java/shop/alien/store/controller/DictOpinionFeedbackController.java
  60. 167 0
      alien-store/src/main/java/shop/alien/store/controller/DictStoreTagController.java
  61. 215 0
      alien-store/src/main/java/shop/alien/store/controller/DictionaryLibraryController.java
  62. 158 0
      alien-store/src/main/java/shop/alien/store/controller/FilterConditionController.java
  63. 9 0
      alien-store/src/main/java/shop/alien/store/controller/LifeCouponController.java
  64. 1 4
      alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java
  65. 117 0
      alien-store/src/main/java/shop/alien/store/controller/LifeSysDeptController.java
  66. 135 0
      alien-store/src/main/java/shop/alien/store/controller/LifeSysMenuController.java
  67. 247 0
      alien-store/src/main/java/shop/alien/store/controller/LifeSysRoleController.java
  68. 2 1
      alien-store/src/main/java/shop/alien/store/controller/LifeUserDynamicsController.java
  69. 83 0
      alien-store/src/main/java/shop/alien/store/controller/OperationLogController.java
  70. 4 4
      alien-store/src/main/java/shop/alien/store/controller/PlatformBusinessSectionController.java
  71. 238 0
      alien-store/src/main/java/shop/alien/store/controller/ProtocolManagementController.java
  72. 61 5
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  73. 95 1
      alien-store/src/main/java/shop/alien/store/controller/SystemController.java
  74. 138 0
      alien-store/src/main/java/shop/alien/store/controller/TemplateDownloadController.java
  75. 40 0
      alien-store/src/main/java/shop/alien/store/controller/UserViolationController.java
  76. 55 0
      alien-store/src/main/java/shop/alien/store/enums/OperationType.java
  77. 82 0
      alien-store/src/main/java/shop/alien/store/filter/PreAuthFilter.java
  78. 58 0
      alien-store/src/main/java/shop/alien/store/service/DictOpinionFeedbackService.java
  79. 58 0
      alien-store/src/main/java/shop/alien/store/service/DictStoreTagService.java
  80. 78 0
      alien-store/src/main/java/shop/alien/store/service/DictionaryLibraryService.java
  81. 61 0
      alien-store/src/main/java/shop/alien/store/service/FilterConditionService.java
  82. 8 0
      alien-store/src/main/java/shop/alien/store/service/LifeCouponService.java
  83. 2 0
      alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java
  84. 69 0
      alien-store/src/main/java/shop/alien/store/service/LifeSysDeptService.java
  85. 71 0
      alien-store/src/main/java/shop/alien/store/service/LifeSysMenuService.java
  86. 76 0
      alien-store/src/main/java/shop/alien/store/service/LifeSysRoleMenuService.java
  87. 85 0
      alien-store/src/main/java/shop/alien/store/service/LifeSysRoleService.java
  88. 59 0
      alien-store/src/main/java/shop/alien/store/service/LifeSysUserRoleService.java
  89. 2 2
      alien-store/src/main/java/shop/alien/store/service/LifeUserDynamicsService.java
  90. 5 0
      alien-store/src/main/java/shop/alien/store/service/LifeUserViolationService.java
  91. 20 0
      alien-store/src/main/java/shop/alien/store/service/OperationLogService.java
  92. 4 1
      alien-store/src/main/java/shop/alien/store/service/PlatformBusinessSectionService.java
  93. 76 0
      alien-store/src/main/java/shop/alien/store/service/ProtocolManagementService.java
  94. 35 1
      alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java
  95. 60 1
      alien-store/src/main/java/shop/alien/store/service/SystemService.java
  96. 1087 0
      alien-store/src/main/java/shop/alien/store/service/impl/DictOpinionFeedbackServiceImpl.java
  97. 1092 0
      alien-store/src/main/java/shop/alien/store/service/impl/DictStoreTagServiceImpl.java
  98. 1416 0
      alien-store/src/main/java/shop/alien/store/service/impl/DictionaryLibraryServiceImpl.java
  99. 1238 0
      alien-store/src/main/java/shop/alien/store/service/impl/FilterConditionServiceImpl.java
  100. 265 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeCouponServiceImpl.java

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

@@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import shop.alien.entity.store.LifeUserDynamics;
+import shop.alien.entity.store.StoreComment;
 
 import java.util.List;
 import java.util.Map;
@@ -24,6 +26,20 @@ public class SecondUserViolationDetailVo extends SecondUserViolationVo{
     // 商品信息
     private SecondGoodsVo secondGoods;
 
+    // 动态图片
+    @ApiModelProperty(value = "动态图片")
+    private List<String> dynamicsImg;
 
+    // 评论图片
+    @ApiModelProperty(value = "评论图片")
+    private List<String> commentImg;
+
+    // 动态信息
+    @ApiModelProperty(value = "动态信息")
+    LifeUserDynamics dynamicsInfo;
+
+    // 评论信息
+    @ApiModelProperty(value = "评论信息")
+    StoreComment commentInfo;
 
 }

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/second/vo/SecondUserViolationVo.java

@@ -52,7 +52,7 @@ public class SecondUserViolationVo extends LifeUserViolation {
     @ApiModelProperty(value = "举报状态")
     private String processingContext;
 
-    @ApiModelProperty(value = "商品图片")
+    @ApiModelProperty(value = "图片")
     private List<String> imgUrl;
 
 }

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

@@ -53,14 +53,14 @@ public class LifeFeedback implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date feedbackTime;
 
+    @ApiModelProperty(value = "工作人员ID(关联life_sys)")
+    @TableField("staff_id")
+    private Integer staffId;
+
     @ApiModelProperty(value = "处理状态:0-处理中,1-已解决")
     @TableField("handle_status")
     private Integer handleStatus;
 
-    @ApiModelProperty(value = "跟进人员ID(关联life_sys表的id)")
-    @TableField("staff_id")
-    private Integer staffId;
-
     @ApiModelProperty(value = "创建时间")
     @TableField(value = "create_time", fill = FieldFill.INSERT)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")

+ 16 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeSys.java

@@ -29,6 +29,22 @@ public class LifeSys {
     @ApiModelProperty(value = "角色id")
     private String roleId;
 
+    @ApiModelProperty(value = "真实姓名")
+    @TableField("real_name")
+    private String realName;
+
+    @ApiModelProperty(value = "联系电话")
+    @TableField("phone")
+    private String phone;
+
+    @ApiModelProperty(value = "邮箱")
+    @TableField("email")
+    private String email;
+
+    @ApiModelProperty(value = "所属部门ID")
+    @TableField("department_id")
+    private String departmentId;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

+ 87 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeSysDept.java

@@ -0,0 +1,87 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 部门表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("life_sys_dept")
+@ApiModel(value = "LifeSysDept对象", description = "部门表")
+public class LifeSysDept {
+
+    @ApiModelProperty(value = "部门ID")
+    @TableId(value = "dept_id", type = IdType.AUTO)
+    private Long deptId;
+
+    @ApiModelProperty(value = "父部门ID(0表示根部门)")
+    @TableField("parent_id")
+    private Long parentId;
+
+    @ApiModelProperty(value = "祖级列表(用于数据权限查询,如:0,100,101)")
+    @TableField("ancestors")
+    private String ancestors;
+
+    @ApiModelProperty(value = "部门名称")
+    @TableField("dept_name")
+    private String deptName;
+
+    @ApiModelProperty(value = "显示顺序")
+    @TableField("dept_sort")
+    private Integer deptSort;
+
+    @ApiModelProperty(value = "部门负责人")
+    @TableField("leader")
+    private Integer leader;
+
+    @ApiModelProperty(value = "联系电话")
+    @TableField("phone")
+    private String phone;
+
+    @ApiModelProperty(value = "邮箱")
+    @TableField("email")
+    private String email;
+
+    @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("description")
+    private String description;
+}
+

+ 108 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeSysMenu.java

@@ -0,0 +1,108 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 菜单权限表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("life_sys_menu")
+@ApiModel(value = "LifeSysMenu对象", description = "菜单权限表")
+public class LifeSysMenu {
+
+    @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 = "路由地址")
+    @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<LifeSysMenu> children;
+}
+

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

@@ -0,0 +1,78 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 平台角色信息表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("life_sys_role")
+@ApiModel(value = "LifeSysRole对象", description = "平台角色信息表")
+public class LifeSysRole implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "角色ID")
+    @TableId(value = "role_id", type = IdType.AUTO)
+    private Long roleId;
+
+    @ApiModelProperty(value = "角色名称")
+    @TableField("role_name")
+    private String roleName;
+
+    @ApiModelProperty(value = "角色名称")
+    @TableField("role_name_en")
+    private String roleNameEn;
+
+    @ApiModelProperty(value = "显示顺序")
+    @TableField("role_sort")
+    private Integer roleSort;
+
+    @ApiModelProperty(value = "角色状态(0正常 1停用)")
+    @TableField("status")
+    private String status;
+
+    @ApiModelProperty(value = "角色描述")
+    @TableField("description")
+    private String description;
+
+    @ApiModelProperty(value = "删除标志(0代表存在 2代表删除)")
+    @TableField("del_flag")
+    @TableLogic
+    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.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "备注")
+    @TableField("remark")
+    private String remark;
+}
+

+ 44 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeSysRoleMenu.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("life_sys_role_menu")
+@ApiModel(value = "LifeSysRoleMenu对象", description = "角色菜单关联表")
+public class LifeSysRoleMenu implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "角色ID(关联life_sys_role.role_id)")
+    @TableField("role_id")
+    private Long roleId;
+
+    @ApiModelProperty(value = "菜单ID(关联life_sys_menu.menu_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;
+}
+

+ 44 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeSysUserRole.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("life_sys_user_role")
+@ApiModel(value = "LifeSysUserRole对象", description = "用户角色关联表")
+public class LifeSysUserRole implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "用户ID(关联life_sys.id)")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "角色ID(关联life_sys_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;
+}
+

+ 118 - 0
alien-entity/src/main/java/shop/alien/entity/store/OperationLog.java

@@ -0,0 +1,118 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 操作日志实体类
+ *
+ * @author ssk
+ * @since 2025-12-09
+ */
+@Data
+@TableName("operation_log")
+@ApiModel(value = "OperationLog对象", description = "操作日志")
+public class OperationLog {
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "账号ID")
+    @TableField("account_id")
+    private String accountId;
+
+    @ApiModelProperty(value = "账号")
+    @TableField("account")
+    private String account;
+
+    @ApiModelProperty(value = "姓名")
+    @TableField("name")
+    private String name;
+
+    @ApiModelProperty(value = "职务")
+    @TableField("position")
+    private String position;
+
+    @ApiModelProperty(value = "部门")
+    @TableField("department")
+    private String department;
+
+    @ApiModelProperty(value = "操作时间")
+    @TableField("operation_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date operationTime;
+
+    @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 = "用户类型(平台、用户、商家、律师)")
+    @TableField("user_type")
+    private String userType;
+
+    @ApiModelProperty(value = "修改前的数据(JSON格式)")
+    @TableField("before_data")
+    private String beforeData;
+
+    @ApiModelProperty(value = "修改后的数据(JSON格式)")
+    @TableField("after_data")
+    private String afterData;
+
+    @ApiModelProperty(value = "请求方法")
+    @TableField("method")
+    private String method;
+
+    @ApiModelProperty(value = "请求路径")
+    @TableField("request_path")
+    private String requestPath;
+
+    @ApiModelProperty(value = "请求参数")
+    @TableField("request_params")
+    private String requestParams;
+
+    @ApiModelProperty(value = "IP地址")
+    @TableField("ip_address")
+    private String ipAddress;
+
+    @ApiModelProperty(value = "文件地址")
+    @TableField("file_url")
+    private String fileUrl;
+
+    @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(value = "created_user_id", fill = FieldFill.INSERT)
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "更新人ID")
+    @TableField(value = "updated_user_id", fill = FieldFill.INSERT_UPDATE)
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "删除标识(0:未删除,1:已删除)")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+}
+

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

@@ -0,0 +1,66 @@
+package shop.alien.entity.store;
+
+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 io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 协议管理表
+ *
+ * @author alien
+ * @since 2025-12-09
+ */
+@Data
+@JsonInclude
+@EqualsAndHashCode(callSuper = false)
+@TableName("protocol_management")
+@ApiModel(value = "ProtocolManagement对象", description = "协议管理表")
+public class ProtocolManagement extends Model<ProtocolManagement> {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "协议名称")
+    @TableField("protocol_file_name")
+    private String protocolFileName;
+
+    @ApiModelProperty(value = "展示位置")
+    @TableField("display_position")
+    private String displayPosition;
+
+    @ApiModelProperty(value = "协议文件存储路径")
+    @TableField("protocol_file_path")
+    private String protocolFilePath;
+
+    @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(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "创建人")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改人")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "删除标识, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+}
+

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

@@ -311,4 +311,18 @@ public class StoreInfo {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @TableField("review_date")
     private Date reviewDate;
+
+    @ApiModelProperty(value = "人工复核状态(1:通过,2:拒绝)")
+    @TableField("manual_review_status")
+    private Integer manualReviewStatus;
+
+    @ApiModelProperty(value = "人工复核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("manual_review_time")
+    private Date manualReviewTime;
+
+    @ApiModelProperty(value = "人工复核原因")
+    @TableField("manual_review_reason")
+    private String manualReviewReason;
+
 }

+ 2 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackQueryDto.java

@@ -28,8 +28,10 @@ public class LifeFeedbackQueryDto implements Serializable {
 
     @ApiModelProperty(value = "页码", required = true)
     private Integer pageNum = 1;
+    private Integer page = 1;
 
     @ApiModelProperty(value = "每页数量", required = true)
     private Integer pageSize = 10;
+    private Integer size = 10;
 }
 

+ 45 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeSysDeptAddDto.java

@@ -0,0 +1,45 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 部门新增DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value = "LifeSysDeptAddDto对象", description = "部门新增信息")
+public class LifeSysDeptAddDto {
+
+    @ApiModelProperty(value = "父部门ID(0表示根部门)")
+    private Long parentId;
+
+    @ApiModelProperty(value = "部门名称", required = true)
+    private String deptName;
+
+    @ApiModelProperty(value = "显示顺序")
+    private Integer deptSort;
+
+    @ApiModelProperty(value = "部门负责人")
+    private Integer leader;
+
+    @ApiModelProperty(value = "联系电话")
+    private String phone;
+
+    @ApiModelProperty(value = "邮箱")
+    private String email;
+
+    @ApiModelProperty(value = "部门状态(0正常 1停用),默认为0")
+    private String status = "0";
+
+    @ApiModelProperty(value = "部门描述")
+    private String description;
+}
+

+ 27 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeSysDeptSortDto.java

@@ -0,0 +1,27 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 一级部门排序DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value = "LifeSysDeptSortDto对象", description = "一级部门排序信息")
+public class LifeSysDeptSortDto {
+
+    @ApiModelProperty(value = "部门ID", required = true)
+    private Long deptId;
+
+    @ApiModelProperty(value = "目标排序位置(新排序值)", required = true)
+    private Integer newSort;
+}
+

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

@@ -0,0 +1,33 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 部门编辑DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value = "LifeSysDeptUpdateDto对象", description = "部门编辑信息")
+public class LifeSysDeptUpdateDto {
+
+    @ApiModelProperty(value = "部门ID", required = true)
+    private Long deptId;
+
+    @ApiModelProperty(value = "部门名称", required = true)
+    private String deptName;
+
+    @ApiModelProperty(value = "部门描述")
+    private String description;
+
+    @ApiModelProperty(value = "部门负责人")
+    private Integer leader;
+}
+

+ 10 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeUserViolationDto.java

@@ -105,6 +105,16 @@ public class LifeUserViolationDto {
     @ApiModelProperty(value = "举报内容")
     private String LifeNotice;
 
+    //  举报类型状态名称
+    @ApiModelProperty(value = "举报类型状态名称")
+    private String violationTypeName;
 
+    // 举报类型名称
+    @ApiModelProperty(value = "举报类型名称")
+    private String reportContextName;
+
+    // 处理状态名称
+    @ApiModelProperty(value = "处理状态名称")
+    private String processingName;
 
 }

+ 19 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java

@@ -213,6 +213,25 @@ public class StoreInfoDto {
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private Date entertainmentLicenceExpirationTime;
 
+    @ApiModelProperty(value = "人工复核状态(1:通过,2:拒绝)")
+    private Integer manualReviewStatus;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "人工复核时间")
+    private Date manualReviewTime;
+
+    @ApiModelProperty(value = "人工复核原因")
+    private String manualReviewReason;
+
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    @ApiModelProperty(value = "审核时间")
+    private Date reviewDate;
+
+//    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)(多个ID用逗号拼接)")
+//    @JsonDeserialize(using = StringToListDeserializer.class)
+//    private List<String> businessClassify;
     @ApiModelProperty(value = "营业执照图片URL")
     private String businessLicenseUrl;
 

+ 48 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/SystemUserAddDto.java

@@ -0,0 +1,48 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 系统用户新增DTO
+ *
+ * @author system
+ * @since 2025-12-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value = "SystemUserAddDto对象", description = "系统用户新增信息")
+public class SystemUserAddDto {
+
+    @ApiModelProperty(value = "用户名", required = true)
+    private String userName;
+
+    @ApiModelProperty(value = "真实姓名", required = true)
+    private String realName;
+
+    @ApiModelProperty(value = "联系电话", required = true)
+    private String phone;
+
+    @ApiModelProperty(value = "邮箱", required = true)
+    private String email;
+
+    @ApiModelProperty(value = "密码(初始密码)", required = true)
+    private String password;
+
+    @ApiModelProperty(value = "角色ID列表(支持多角色选择)", required = true)
+    private String roleIds;
+
+    @ApiModelProperty(value = "所属部门ID", required = true)
+    private String departmentId;
+
+    @ApiModelProperty(value = "备注说明")
+    private String remark;
+
+    @ApiModelProperty(value = "启用状态 1:启用 0:禁用,默认为1")
+    private Integer status = 1;
+}
+

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/SystemUserQueryDto.java

@@ -0,0 +1,39 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 系统用户查询DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value = "SystemUserQueryDto对象", description = "系统用户查询信息")
+public class SystemUserQueryDto {
+
+    @ApiModelProperty(value = "用户名(支持模糊查询)")
+    private String userName;
+
+    @ApiModelProperty(value = "真实姓名(支持模糊查询)")
+    private String realName;
+
+    @ApiModelProperty(value = "联系电话(支持模糊查询)")
+    private String phone;
+
+    @ApiModelProperty(value = "角色ID(精确查询)")
+    private Long roleId;
+
+    @ApiModelProperty(value = "部门ID(精确查询)")
+    private String departmentId;
+
+    @ApiModelProperty(value = "启用状态 1:启用 0:禁用")
+    private Integer status;
+}
+

+ 30 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/SystemUserStatusDto.java

@@ -0,0 +1,30 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 系统用户状态修改DTO
+ *
+ * @author system
+ * @since 2025-12-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value = "SystemUserStatusDto对象", description = "系统用户状态修改信息")
+public class SystemUserStatusDto {
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "状态 1:启用 0:禁用", required = true)
+    private Integer status;
+
+    @ApiModelProperty(value = "操作原因/备注")
+    private String remark;
+}
+

+ 51 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/SystemUserUpdateDto.java

@@ -0,0 +1,51 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 系统用户修改DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value = "SystemUserUpdateDto对象", description = "系统用户修改信息")
+public class SystemUserUpdateDto {
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "用户名")
+    private String userName;
+
+    @ApiModelProperty(value = "真实姓名")
+    private String realName;
+
+    @ApiModelProperty(value = "联系电话")
+    private String phone;
+
+    @ApiModelProperty(value = "邮箱")
+    private String email;
+
+    @ApiModelProperty(value = "密码(如果提供则更新密码)")
+    private String password;
+
+    @ApiModelProperty(value = "角色ID列表(支持多角色选择,如果提供则更新角色关系)")
+    private String roleIds;
+
+    @ApiModelProperty(value = "所属部门ID")
+    private String departmentId;
+
+    @ApiModelProperty(value = "备注说明")
+    private String remark;
+
+    @ApiModelProperty(value = "启用状态 1:启用 0:禁用")
+    private Integer status;
+}
+

+ 43 - 0
alien-entity/src/main/java/shop/alien/entity/store/excelVo/DictionaryLibraryExcelVo.java

@@ -0,0 +1,43 @@
+package shop.alien.entity.store.excelVo;
+
+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;
+
+/**
+ * 经营版块Excel导入导出对象
+ *
+ * @author ssk
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "DictionaryLibraryExcelVo对象", description = "举报Excel导入导出对象")
+public class DictionaryLibraryExcelVo {
+
+    @ExcelHeader("序号")
+    @ApiModelProperty(value = "序号")
+    private String sort;
+
+    @ExcelHeader("一级分类名称")
+    @ApiModelProperty(value = "一级分类名称(必填,如果二级和三级为空,则创建一级分类)")
+    private String firstLevelName;
+
+    @ExcelHeader("二级分类名称")
+    @ApiModelProperty(value = "二级分类名称(可选,如果三级为空,则创建二级分类)")
+    private String secondLevelName;
+
+    @ExcelHeader("三级分类名称")
+    @ApiModelProperty(value = "三级分类名称(可选,如果填写则创建三级分类)")
+    private String thirdLevelName;
+
+    @ExcelHeader("四级分类名称")
+    @ApiModelProperty(value = "四级分类名称(可选,如果填写则创建四级分类)")
+    private String fourLevelName;
+
+    @ExcelHeader("状态")
+    @ApiModelProperty(value = "状态为显示隐藏,如不填默认显示")
+    private String hidden;
+}

+ 72 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeSysDeptVo.java

@@ -0,0 +1,72 @@
+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.LifeSysDept;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 部门树形结构VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@JsonInclude
+@ApiModel(value = "LifeSysDeptVo对象", description = "部门树形结构")
+public class LifeSysDeptVo extends LifeSysDept {
+
+    @ApiModelProperty(value = "子部门列表")
+    private List<LifeSysDeptVo> children;
+
+    @ApiModelProperty(value = "部门人员数量(包含子部门)")
+    private Integer memberCount;
+
+    @ApiModelProperty(value = "当前部门人数")
+    private Integer currentMemberCount;
+
+    public LifeSysDeptVo() {
+        this.children = new ArrayList<>();
+        this.memberCount = 0;
+        this.currentMemberCount = 0;
+    }
+
+    /**
+     * 从LifeSysDept转换为LifeSysDeptVo
+     *
+     * @param dept 部门实体
+     * @return LifeSysDeptVo
+     */
+    public static LifeSysDeptVo fromEntity(LifeSysDept dept) {
+        if (dept == null) {
+            return null;
+        }
+        LifeSysDeptVo vo = new LifeSysDeptVo();
+        vo.setDeptId(dept.getDeptId());
+        vo.setParentId(dept.getParentId());
+        vo.setAncestors(dept.getAncestors());
+        vo.setDeptName(dept.getDeptName());
+        vo.setDeptSort(dept.getDeptSort());
+        vo.setLeader(dept.getLeader());
+        vo.setPhone(dept.getPhone());
+        vo.setEmail(dept.getEmail());
+        vo.setStatus(dept.getStatus());
+        vo.setDelFlag(dept.getDelFlag());
+        vo.setCreateBy(dept.getCreateBy());
+        vo.setCreatedTime(dept.getCreatedTime());
+        vo.setUpdateBy(dept.getUpdateBy());
+        vo.setUpdatedTime(dept.getUpdatedTime());
+        vo.setDescription(dept.getDescription());
+        vo.setChildren(new ArrayList<>());
+        vo.setMemberCount(0); // 初始化为0,后续会通过统计方法计算
+        vo.setCurrentMemberCount(0); // 初始化为0,后续会通过统计方法计算
+        return vo;
+    }
+}
+

+ 9 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeSysVo.java

@@ -0,0 +1,9 @@
+package shop.alien.entity.store.vo;
+
+import lombok.Data;
+import shop.alien.entity.store.LifeSys;
+
+@Data
+public class LifeSysVo extends LifeSys {
+    private String roleName;
+}

+ 9 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeUserViolationVo.java

@@ -29,4 +29,13 @@ public class LifeUserViolationVo extends LifeUserViolation {
     private String nickName;
 
     private String image;
+
+    //  举报类型状态名称
+    private String violationTypeName;
+
+    // 举报类型名称
+    private String reportContextName;
+
+    // 处理状态名称
+    private String processingName;
 }

+ 123 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/MenuWithRoleVo.java

@@ -0,0 +1,123 @@
+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.LifeSysMenu;
+
+import java.util.List;
+
+/**
+ * 菜单权限VO(包含角色拥有状态)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@JsonInclude
+@ApiModel(value = "MenuWithRoleVo对象", description = "菜单权限VO(包含角色拥有状态)")
+public class MenuWithRoleVo {
+
+    @ApiModelProperty(value = "菜单ID")
+    private Long menuId;
+
+    @ApiModelProperty(value = "菜单名称")
+    private String menuName;
+
+    @ApiModelProperty(value = "父菜单ID(0表示根菜单)")
+    private Long parentId;
+
+    @ApiModelProperty(value = "菜单类型(M目录 C菜单 F按钮)")
+    private String menuType;
+
+    @ApiModelProperty(value = "显示顺序")
+    private Integer menuSort;
+
+    @ApiModelProperty(value = "路由地址")
+    private String path;
+
+    @ApiModelProperty(value = "组件路径")
+    private String component;
+
+    @ApiModelProperty(value = "权限标识(如:sys:user:list)")
+    private String perms;
+
+    @ApiModelProperty(value = "菜单图标")
+    private String icon;
+
+    @ApiModelProperty(value = "菜单状态(0正常 1停用)")
+    private String status;
+
+    @ApiModelProperty(value = "是否显示(0显示 1隐藏)")
+    private String visible;
+
+    @ApiModelProperty(value = "是否为外链(0是 1否)")
+    private String isFrame;
+
+    @ApiModelProperty(value = "是否缓存(0缓存 1不缓存)")
+    private String isCache;
+
+    @ApiModelProperty(value = "删除标志(0存在 2删除)")
+    private String delFlag;
+
+    @ApiModelProperty(value = "创建者")
+    private String createBy;
+
+    @ApiModelProperty(value = "创建时间")
+    private java.util.Date createdTime;
+
+    @ApiModelProperty(value = "更新者")
+    private String updateBy;
+
+    @ApiModelProperty(value = "更新时间")
+    private java.util.Date updatedTime;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "是否拥有该权限(true:拥有 false:未拥有)")
+    private Boolean hasPermission;
+
+    @ApiModelProperty(value = "子菜单列表(用于树形结构)")
+    private List<MenuWithRoleVo> children;
+
+    /**
+     * 从LifeSysMenu转换为MenuWithRoleVo
+     *
+     * @param menu 菜单实体
+     * @param hasPermission 是否拥有权限
+     * @return MenuWithRoleVo
+     */
+    public static MenuWithRoleVo fromEntity(LifeSysMenu menu, Boolean hasPermission) {
+        if (menu == null) {
+            return null;
+        }
+        MenuWithRoleVo vo = new MenuWithRoleVo();
+        vo.setMenuId(menu.getMenuId());
+        vo.setMenuName(menu.getMenuName());
+        vo.setParentId(menu.getParentId());
+        vo.setMenuType(menu.getMenuType());
+        vo.setMenuSort(menu.getMenuSort());
+        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.setHasPermission(hasPermission != null ? hasPermission : false);
+        vo.setChildren(null); // 子菜单会在构建树形结构时设置
+        return vo;
+    }
+}
+

+ 22 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreInfoVo.java

@@ -1,6 +1,7 @@
 package shop.alien.entity.store.vo;
 
 import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModel;
@@ -247,4 +248,25 @@ public class StoreInfoVo extends StoreInfo {
     private JSONObject foodLicence;
     @ApiModelProperty(value = "娱乐经营许可证")
     private JSONObject entertainmentLicence;
+
+    @ApiModelProperty(value = "人工复核状态(1:通过,2:拒绝)")
+    private Integer manualReviewStatus;
+
+    @ApiModelProperty(value = "人工复核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date manualReviewTime;
+
+    @ApiModelProperty(value = "人工复核原因")
+    private String manualReviewReason;
+
+    @ApiModelProperty(value = "审核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date reviewDate;
+
+    @ApiModelProperty(value = "续签合同状态")
+    private Integer renewContractStatus;
+
+    @ApiModelProperty(value = "续签合同拒绝原因")
+    private String contractReason;
+
 }

+ 83 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreLicenseInfoVo.java

@@ -0,0 +1,83 @@
+package shop.alien.entity.store.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 门店证照查询结果 VO
+ *
+ * 对应证照类型(营业执照 / 娱乐经营许可证 / 食品经营许可证)
+ * 及其当前状态、提交时间、到期时间等信息。
+ */
+@Data
+@ApiModel(value = "StoreLicenseInfoVo对象", description = "门店证照信息")
+@JsonInclude(JsonInclude.Include.ALWAYS)
+public class StoreLicenseInfoVo {
+
+    @ApiModelProperty("证照类型描述:营业执照 / 娱乐经营许可证 / 食品经营许可证")
+    private String imgDescription;
+
+    @ApiModelProperty("证照图片类型(14:营业执照;24/25:食品经营许可证;31/32:娱乐经营许可证)")
+    private Integer imgType;
+
+    @ApiModelProperty("门店ID")
+    private Integer id;
+
+    @ApiModelProperty("门店名称")
+    private String storeName;
+
+    @ApiModelProperty("门店联系电话")
+    private String storeTel;
+
+    @ApiModelProperty("当前证照状态")
+    private String states;
+
+    @ApiModelProperty("当前证照状态名字")
+    private String statesName;
+
+    @ApiModelProperty("证照图片地址")
+    private String imgUrl;
+
+    @ApiModelProperty("证照最近一次提交时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date submitDate;
+
+    @ApiModelProperty("证照到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date expirationTime;
+
+    @ApiModelProperty("用户")
+    private String name;
+
+    @ApiModelProperty("审核失败原因")
+    private String expirationReason;
+
+    @ApiModelProperty(value = "经营板块id(词典表 键为 business_section)")
+    private Integer businessSection;
+
+    @ApiModelProperty(value = "经营板块名称")
+    private String businessSectionName;
+
+    @ApiModelProperty(value = "经营种类ids")
+    private String businessTypes;
+
+    @ApiModelProperty(value = "经营种类名称s")
+    private String businessTypesName;
+
+    @ApiModelProperty(value = "分类ids")
+    private String businessClassify;
+
+    @ApiModelProperty(value = "分类名称s")
+    private String businessClassifyName;
+}
+
+
+
+
+

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

@@ -5,6 +5,9 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
+import shop.alien.entity.store.LifeSysMenu;
+
+import java.util.List;
 
 @Data
 @EqualsAndHashCode(callSuper = false)
@@ -23,4 +26,7 @@ public class SystemLoginVo {
 
     @ApiModelProperty(value = "角色id")
     public String roleId;
+
+    @ApiModelProperty(value = "菜单列表")
+    public List<LifeSysMenu> menuList;
 }

+ 14 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java

@@ -10,6 +10,7 @@ import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
 import shop.alien.entity.store.vo.LifeFeedbackListVo;
 import shop.alien.entity.store.vo.LifeFeedbackVo;
 
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -49,11 +50,24 @@ public interface LifeFeedbackMapper extends BaseMapper<LifeFeedback> {
      */
     Integer countPendingFeedback(@Param("feedbackSource") Integer feedbackSource);
 
+    /**
+     * 查询平台回复列表
+     * @param userId 用户ID
+     * @param feedbackSource 反馈来源
+     * @param startTime 开始时间
+     * @return 平台回复列表
+     */
+    List<LifeFeedbackVo> selectPlatformReplies(
+            @Param("userId") Integer userId,
+            @Param("feedbackSource") Integer feedbackSource,
+            @Param("startTime") Date startTime
+    );
 
     // ==================== Web中台接口 ====================
 
     /**
      * 中台-查询意见反馈列表
+     * @param page 分页对象
      * @param feedbackType 反馈类型
      * @param handleStatus 处理状态
      * @param feedbackSource 反馈来源

+ 16 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeSysDeptMapper.java

@@ -0,0 +1,16 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.LifeSysDept;
+
+/**
+ * 部门表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface LifeSysDeptMapper extends BaseMapper<LifeSysDept> {
+}
+

+ 6 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeSysMapper.java

@@ -1,12 +1,18 @@
 package shop.alien.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 import shop.alien.entity.store.LifeSys;
+import shop.alien.entity.store.vo.LifeSysVo;
 
 /**
  * 系统用户
  */
 @Mapper
 public interface LifeSysMapper extends BaseMapper<LifeSys> {
+    IPage<LifeSysVo> selectPageAndRoleName(IPage<LifeSysVo> pageObj, @Param(Constants.WRAPPER) QueryWrapper<LifeSys> queryWrapper);
 }

+ 19 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeSysMenuMapper.java

@@ -0,0 +1,19 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.LifeSysMenu;
+
+import java.util.List;
+
+/**
+ * 菜单权限表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface LifeSysMenuMapper extends BaseMapper<LifeSysMenu> {
+    List<LifeSysMenu> getMenuByUserId(Long userId);
+}
+

+ 33 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeSysRoleMapper.java

@@ -0,0 +1,33 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import shop.alien.entity.store.LifeSysRole;
+
+import java.util.List;
+
+/**
+ * 平台角色信息表 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface LifeSysRoleMapper extends BaseMapper<LifeSysRole> {
+    /**
+     * 根据用户ID查询角色
+     *
+     * @param queryWrapper 查询条件
+     * @return 角色列表
+     */
+    @Select("select lsr.*\n" +
+            "from life_sys_role lsr\n" +
+            "left join life_sys_user_role lsur on lsur.role_id = lsr.role_id\n" +
+            "${ew.customSqlSegment}")
+    List<LifeSysRole> getRoleByUserId(@Param(Constants.WRAPPER) QueryWrapper<LifeSysRole> queryWrapper);
+}
+

+ 16 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeSysRoleMenuMapper.java

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

+ 16 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeSysUserRoleMapper.java

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

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

@@ -88,7 +88,7 @@ public interface LifeUserDynamicsMapper extends BaseMapper<LifeUserDynamics> {
             "                      and lf1.delete_flag = '0'")
     List<LifeUserDynamicsVo> selectDianZanList(String phoneId);
 
-    List<LifeUserDynamicsVo> getDynamicsList(@Param("nickName") String nickName, @Param("userType") String userType, @Param("dynamicsType") Integer dynamicsType, @Param("releaseStartTime") String releaseStartTime, @Param("releaseEndTime") String releaseEndTime);
+    List<LifeUserDynamicsVo> getDynamicsList(@Param("nickName") String nickName, @Param("userType") String userType, @Param("dynamicsType") Integer dynamicsType, @Param("releaseStartTime") String releaseStartTime, @Param("releaseEndTime") String releaseEndTime, @Param("storeName") String storeName);
 
     List<LifeUserDynamicsVo> getDynamicsDetail(@Param("id") Integer id);
 

+ 39 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeUserViolationMapper.java

@@ -113,4 +113,43 @@ public interface LifeUserViolationMapper extends BaseMapper<LifeUserViolation> {
     List<LifeUserViolationVo> getViolationList(
             @Param(Constants.WRAPPER) QueryWrapper<LifeUserViolationVo> queryWrapper
     );
+
+
+    @Select("<script>" +
+            " WITH userInfo AS ( " +
+            " SELECT su.phone, su.id, CASE su.delete_flag WHEN 1 THEN CONCAT(su.nick_name, '(账号已注销)') ELSE su.nick_name END AS nick_name, '1' AS type FROM store_user su " +
+            " UNION ALL " +
+            " SELECT lu.user_phone AS phone, lu.id, CASE lu.delete_flag WHEN 1 THEN CONCAT(lu.user_name, '(账号已注销)') ELSE lu.user_name END AS nick_name, '2' AS type FROM life_user lu " +
+            " ) " +
+            " , violationInfo as ( " +
+            " SELECT " +
+            " luv.id, ui.nick_name, ui.phone, luv.report_context_type,  " +
+            " case when luv.violation_type = 1 then '用户违规' when luv.violation_type = 2 then '色情低俗' when luv.violation_type = 3 then '违法违规' when luv.violation_type = 4 then '谩骂嘲讽、煽动对立' when luv.violation_type = 5 then '涉嫌诈骗' " +
+            " when luv.violation_type = 6 then '人身攻击' when luv.violation_type = 7 then '种族歧视' when luv.violation_type = 8 then '政治敏感' when luv.violation_type = 9 then '虚假、不实内容' " +
+            " when luv.violation_type = 10 then '违反公德秩序' when luv.violation_type = 11 then '危害人身安全' when luv.violation_type = 12 then '网络暴力' else '其他原因' end violation_type_name,  " +
+            " case when luv.report_context_type = 0 then '商户' when luv.report_context_type = 1 then '用户' when luv.report_context_type = 2 then '动态' else '评论' end report_context_name, " +
+            " luv.processing_status, case when luv.processing_status = 0 then '待处理' when luv.processing_status = 1 then '已通过' else '已驳回' end processing_name,  " +
+            " luv.processing_time, luv.created_time, img.img_url image, luv.updated_time " +
+            " FROM life_user_violation luv " +
+            " LEFT JOIN userInfo ui ON ui.type = luv.reporting_user_type AND ui.id = luv.reporting_user_id " +
+            " left join store_img img on luv.id = img.store_id and img.delete_flag = 0 " +
+            " where luv.delete_flag = 0 and luv.report_context_type in ('1', '2', '3') " +
+            " union all " +
+            " select " +
+            " luv.id, lu.user_name nick_name, lu.user_phone phone, luv.report_context_type, sd.dict_detail violation_type_name,  " +
+            " case when luv.report_context_type = '4' then '商品' else '用户' end report_context_name,  " +
+            " luv.processing_status, case when luv.processing_status = 0 then '待处理' when luv.processing_status = 1 then '已通过' else '已驳回' end processing_name, " +
+            " luv.processing_time, luv.created_time, '' image, luv.updated_time " +
+            " from life_user_violation luv " +
+            " left join life_user lu on luv.reporting_user_id = lu.id and lu.delete_flag = 0 " +
+            " left join store_dictionary sd on luv.dict_type = sd.type_name and luv.dict_id = sd.dict_id and sd.delete_flag = 0 " +
+            " where luv.report_context_type in ('4', '5') and sd.delete_flag = 0 " +
+            " ) " +
+            " select * from violationInfo " +
+            " ${ew.customSqlSegment}" +
+            "</script>")
+    IPage<LifeUserViolationVo> getAllViolationPage(
+            IPage<LifeUserViolationVo> page,
+            @Param(Constants.WRAPPER) QueryWrapper<LifeUserViolationVo> queryWrapper
+    );
 }

+ 16 - 0
alien-entity/src/main/java/shop/alien/mapper/OperationLogMapper.java

@@ -0,0 +1,16 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.OperationLog;
+
+/**
+ * 操作日志 Mapper接口
+ *
+ * @author ssk
+ * @since 2025-12-09
+ */
+@Mapper
+public interface OperationLogMapper extends BaseMapper<OperationLog> {
+}
+

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

@@ -0,0 +1,15 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.entity.store.ProtocolManagement;
+
+/**
+ * 协议管理表 Mapper 接口
+ *
+ * @author alien
+ * @since 2025-12-09
+ */
+public interface ProtocolManagementMapper extends BaseMapper<ProtocolManagement> {
+
+}
+

+ 25 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreInfoMapper.java

@@ -10,6 +10,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import shop.alien.entity.store.StoreInfo;
 import shop.alien.entity.store.vo.StoreInfoVo;
+import shop.alien.entity.store.vo.StoreLicenseInfoVo;
 import shop.alien.entity.store.vo.StoreMainInfoVo;
 
 import java.util.List;
@@ -184,4 +185,28 @@ public interface StoreInfoMapper extends BaseMapper<StoreInfo> {
             "and ROUND(ST_Distance_Sphere(ST_GeomFromText(CONCAT('POINT(', REPLACE(#{position}, ',', ' '), ')' )), ST_GeomFromText(CONCAT('POINT(', REPLACE(a.store_position, ',', ' '), ')' ))) / 1000, 2) <= 1 " +
             "order by distance3 asc limit 20")
     List<StoreInfoVo> getMoreRecommendedStores(@Param(Constants.WRAPPER) QueryWrapper<StoreInfoVo> queryWrapper, @Param("position") String position);
+
+    /**
+     * 门店证照查询(分页)
+     *
+     * @param page            分页对象
+     * @param storeName       门店名称(模糊)
+     * @param storeContact    门店联系人(模糊)
+     * @param storeTel        门店电话(模糊)
+     * @param businessSection 经营板块
+     * @param imgType         证照图片类型(14:营业执照;24/25:食品经营许可证;31/32:娱乐经营许可证)
+     * @param states          证照状态
+     * @param startSubmitDate 提交开始时间(yyyy-MM-dd HH:mm:ss)
+     * @param endSubmitDate   提交结束时间(yyyy-MM-dd HH:mm:ss)
+     * @return 证照分页结果
+     */
+    IPage<StoreLicenseInfoVo> getStoreLicensePage(IPage<StoreLicenseInfoVo> page,
+                                                  @Param("storeName") String storeName,
+                                                  @Param("storeContact") String storeContact,
+                                                  @Param("storeTel") String storeTel,
+                                                  @Param("businessSection") String businessSection,
+                                                  @Param("imgType") Integer imgType,
+                                                  @Param("states") String states,
+                                                  @Param("startSubmitDate") String startSubmitDate,
+                                                  @Param("endSubmitDate") String endSubmitDate);
 }

+ 3 - 0
alien-entity/src/main/java/shop/alien/mapper/second/SecondUserViolationMapper.java

@@ -17,4 +17,7 @@ public interface SecondUserViolationMapper extends BaseMapper<SecondUserViolatio
                                                         @Param("reportContextType") String reportContextType);
 
     SecondUserViolationDetailVo getUserViolationInfo(@Param("id") Integer id);
+
+
+    SecondUserViolationDetailVo getUserViolationDetailInfo(@Param("id") Integer id);
 }

+ 15 - 0
alien-entity/src/main/resources/mapper/LifeSysMapper.xml

@@ -0,0 +1,15 @@
+<?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.LifeSysMapper">
+
+
+    <select id="selectPageAndRoleName" resultType="shop.alien.entity.store.vo.LifeSysVo">
+        SELECT
+            ls.*,
+            IFNULL(GROUP_CONCAT(DISTINCT lsr.role_name SEPARATOR ','), '') AS roleName
+        FROM life_sys ls
+                 LEFT JOIN life_sys_user_role lsur ON lsur.user_id = ls.id
+                 LEFT JOIN life_sys_role lsr ON lsr.role_id = lsur.role_id
+            ${ew.customSqlSegment}
+    </select>
+</mapper>

+ 49 - 0
alien-entity/src/main/resources/mapper/LifeSysMenuMapper.xml

@@ -0,0 +1,49 @@
+<?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.LifeSysMenuMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeSysMenu">
+        <id column="menu_id" property="menuId" />
+        <result column="menu_name" property="menuName" />
+        <result column="parent_id" property="parentId" />
+        <result column="menu_type" property="menuType" />
+        <result column="menu_sort" property="menuSort" />
+        <result column="path" property="path" />
+        <result column="component" property="component" />
+        <result column="perms" property="perms" />
+        <result column="icon" property="icon" />
+        <result column="status" property="status" />
+        <result column="visible" property="visible" />
+        <result column="is_frame" property="isFrame" />
+        <result column="is_cache" property="isCache" />
+        <result column="del_flag" property="delFlag" />
+        <result column="create_by" property="createBy" />
+        <result column="created_time" property="createdTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="updated_time" property="updatedTime" />
+        <result column="remark" property="remark" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        menu_id, menu_name, parent_id, menu_type, menu_sort, path, component, perms, icon,
+        status, visible, is_frame, is_cache, del_flag, create_by, created_time, update_by,
+        updated_time, remark
+    </sql>
+
+
+    <select id="getMenuByUserId" resultType="shop.alien.entity.store.LifeSysMenu">
+        select lsm.*
+        from life_sys_menu lsm
+                 left join life_sys_role_menu lsrm on lsrm.menu_id = lsm.menu_id
+        where lsm.del_flag = 0
+          and lsrm.role_id in (
+            select lsur.role_id
+            from life_sys_user_role lsur
+            where lsur.user_id = #{userId}
+        )
+    </select>
+
+</mapper>
+

+ 28 - 0
alien-entity/src/main/resources/mapper/LifeSysRoleMapper.xml

@@ -0,0 +1,28 @@
+<?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.LifeSysRoleMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeSysRole">
+        <id column="role_id" property="roleId" />
+        <result column="role_name" property="roleName" />
+        <result column="role_key" property="roleKey" />
+        <result column="role_sort" property="roleSort" />
+        <result column="data_scope" property="dataScope" />
+        <result column="status" property="status" />
+        <result column="del_flag" property="delFlag" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="remark" property="remark" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        role_id, role_name, role_key, role_sort, data_scope, status, del_flag,
+        create_by, create_time, update_by, update_time, remark
+    </sql>
+
+</mapper>
+

+ 19 - 0
alien-entity/src/main/resources/mapper/LifeSysRoleMenuMapper.xml

@@ -0,0 +1,19 @@
+<?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.LifeSysRoleMenuMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeSysRoleMenu">
+        <id column="id" property="id" />
+        <result column="role_id" property="roleId" />
+        <result column="menu_id" property="menuId" />
+        <result column="created_time" property="createdTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, role_id, menu_id, created_time
+    </sql>
+
+</mapper>
+

+ 6 - 2
alien-entity/src/main/resources/mapper/LifeUserDynamicsMapper.xml

@@ -15,14 +15,14 @@
         from life_user_dynamics
         where delete_flag = 0 and draft = 0 order by created_time desc
         )
-        select dynamice.*, user.nick_name userName, user.head_img userImage, info.id storeUserId, user.id storeOrUserId, 0 isExpert
+        select dynamice.*, user.nick_name userName, user.head_img userImage, info.id storeUserId, user.id storeOrUserId, 0 isExpert, info.store_name
         from dynamice
         left join store_user user on dynamice.phone = user.phone and user.delete_flag = 0
         left join store_info info on info.id = user.store_id and info.delete_flag = 0
         left join store_img img on img.store_id = user.store_id and img.img_type = '10' and img.delete_flag = 0
         where dynamice.userType = 'store'
         union
-        select dynamice.*, user.user_name userName, user.user_image userImage, user.id storeUserId, user.id storeOrUserId, IF(lue.expert_code IS NOT NULL , 1, 0) AS isExpert
+        select dynamice.*, user.user_name userName, user.user_image userImage, user.id storeUserId, user.id storeOrUserId, IF(lue.expert_code IS NOT NULL , 1, 0) AS isExpert, '' store_name
         from dynamice
         join life_user user on dynamice.phone = user.user_phone and user.delete_flag = 0
         left join life_user_expert  lue on lue.user_id = user.id and lue.delete_flag = 0
@@ -49,6 +49,10 @@
             AND dyna1.created_time >= #{releaseStartTime}
         </if>
 
+        <if test="storeName != null and storeName != ''">
+            AND dyna1.store_name LIKE CONCAT('%', #{storeName}, '%')
+        </if>
+
         <if test="releaseEndTime != null and releaseEndTime != ''">
             AND dyna1.created_time &lt;= #{releaseEndTime}
         </if>

+ 110 - 0
alien-entity/src/main/resources/mapper/StoreInfoMapper.xml

@@ -0,0 +1,110 @@
+<?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.StoreInfoMapper">
+
+    <!--
+        门店证照查询
+        证照类型与状态、提交时间、到期时间映射关系:
+          - img_type = 14: 营业执照
+          - img_type IN (31, 32): 娱乐经营许可证
+          - img_type IN (24, 25): 食品经营许可证
+    -->
+    <select id="getStoreLicensePage"
+            resultType="shop.alien.entity.store.vo.StoreLicenseInfoVo">
+        SELECT
+            CASE
+                WHEN si.img_type = 14 THEN '营业执照'
+                WHEN si.img_type IN (31, 32) THEN '娱乐经营许可证'
+                WHEN si.img_type IN (24, 25) THEN '食品经营许可证'
+                WHEN si.img_type IN (33) THEN '身份证正面'
+                WHEN si.img_type IN (34) THEN '身份证反面'
+                ELSE ''
+            END AS img_description,
+            si.img_type AS img_type,
+            s.id AS id,
+            s.store_name AS store_name,
+            s.store_tel AS store_tel,
+            su.name AS name,
+            si.img_url,
+            s.business_section_name,
+            s.business_classify_name,
+            s.business_types_name,
+            CASE
+                WHEN si.img_type = 14 THEN s.business_license_status
+                WHEN si.img_type IN (31, 32) THEN s.entertainment_licence_status
+                WHEN si.img_type IN (24, 25) THEN s.food_licence_status
+                WHEN si.img_type IN (33, 34) THEN s.id_card_status
+                ELSE ''
+            END AS states,
+            CASE
+                WHEN si.img_type = 14 THEN s.update_business_license_time
+                WHEN si.img_type IN (31, 32) THEN s.update_entertainment_licence_time
+                WHEN si.img_type IN (24, 25) THEN s.update_food_licence_time
+                WHEN si.img_type IN (33, 34) THEN s.update_id_card_time
+                ELSE NULL
+            END AS submit_date,
+            CASE
+                WHEN si.img_type = 14 THEN s.business_license_expiration_time
+                WHEN si.img_type IN (31, 32) THEN s.entertainment_licence_expiration_time
+                WHEN si.img_type IN (24, 25) THEN s.food_licence_expiration_time
+                WHEN si.img_type IN (33, 34) THEN s.id_card_expiration_time
+                ELSE NULL
+            END AS expiration_time,
+            CASE
+                WHEN si.img_type = 14 THEN s.business_license_reason
+                WHEN si.img_type IN (31, 32) THEN s.entertainment_licence_reason
+                WHEN si.img_type IN (24, 25) THEN s.food_licence_reason
+                WHEN si.img_type IN (33, 34) THEN s.id_card_reason
+                ELSE NULL
+            END AS expiration_reason
+        FROM store_info s
+                 LEFT JOIN store_img si
+                           ON s.id = si.store_id
+                               AND si.img_type IN (14, 24, 25, 31, 32, 33, 34)
+                               AND si.delete_flag = 0
+                 left join store_user su on s.id = su.store_id
+        WHERE s.delete_flag = 0
+        <if test="storeName != null and storeName != ''">
+            AND s.store_name LIKE CONCAT('%', #{storeName}, '%')
+        </if>
+        <if test="storeContact != null and storeContact != ''">
+            AND su.name LIKE CONCAT('%', #{storeContact}, '%')
+        </if>
+        <if test="storeTel != null and storeTel != ''">
+            AND s.store_tel LIKE CONCAT('%', #{storeTel}, '%')
+        </if>
+        <if test="businessSection != null and businessSection != ''">
+            AND s.business_section = #{businessSection}
+        </if>
+        <if test="imgType != null">
+            AND si.img_type = #{imgType}
+        </if>
+        <if test="states != null and states != ''">
+            AND (
+                (si.img_type = 14 AND s.business_license_status = #{states})
+                OR (si.img_type IN (31, 32) AND s.entertainment_licence_status = #{states})
+                OR (si.img_type IN (24, 25) AND s.food_licence_status = #{states})
+            )
+        </if>
+        <if test="startSubmitDate != null and startSubmitDate != ''">
+            AND (
+                (si.img_type = 14 AND s.update_business_license_time &gt;= #{startSubmitDate})
+                OR (si.img_type IN (31, 32) AND s.update_entertainment_licence_time &gt;= #{startSubmitDate})
+                OR (si.img_type IN (24, 25) AND s.update_food_licence_time &gt;= #{startSubmitDate})
+            )
+        </if>
+        <if test="endSubmitDate != null and endSubmitDate != ''">
+            AND (
+                (si.img_type = 14 AND s.update_business_license_time &lt;= #{endSubmitDate})
+                OR (si.img_type IN (31, 32) AND s.update_entertainment_licence_time &lt;= #{endSubmitDate})
+                OR (si.img_type IN (24, 25) AND s.update_food_licence_time &lt;= #{endSubmitDate})
+            )
+        </if>
+        ORDER BY s.store_name ASC
+    </select>
+
+</mapper>
+
+

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

@@ -92,4 +92,39 @@
         </if>
     </select>
 
+
+    <!-- 分页查询举报信息 -->
+    <select id="getUserViolationDetailInfo" resultType="shop.alien.entity.second.vo.SecondUserViolationDetailVo">
+        select
+            v.id,
+            case when reported_user_type = 1 then s.nick_name else u.user_name end reported_user_name,
+            case when reported_user_type = 1 then s.phone else u.user_phone end reported_user_phone,
+            case when reporting_user_type = 1 then su.nick_name else lu.user_name end reporting_user_name,
+            case when reporting_user_type = 1 then su.phone else lu.user_phone end reporting_user_phone,
+            v.created_time,
+            case when v.processing_status = 0 then '待处理' when v.processing_status = 1 then '已通过' else '已驳回' end processing_status,
+            case when v.violation_type = 1 then '用户违规' when v.violation_type = 2 then '色情低俗' when v.violation_type = 3 then '违法违规' when v.violation_type = 4 then '谩骂嘲讽、煽动对立' when v.violation_type = 5 then '涉嫌诈骗'
+            when v.violation_type = 6 then '人身攻击' when v.violation_type = 7 then '种族歧视' when v.violation_type = 8 then '政治敏感' when v.violation_type = 9 then '虚假、不实内容'
+            when v.violation_type = 10 then '违反公德秩序' when v.violation_type = 11 then '危害人身安全' when v.violation_type = 12 then '网络暴力' else '其他原因' end dict_detail,
+            v.business_id,
+            v.report_context_type,
+            v.processing_status,
+            v.other_reason_content,
+            v.report_evidence_img,
+            v.processing_time,
+            v.report_result,
+            v.video_first_frame,
+            v.comment_id,
+            v.dynamics_id
+        from
+            life_user_violation v
+            left join life_user u on v.reported_user_id = u.id
+            left join store_user s on v.reported_user_id = s.id
+            left join life_user lu on v.reporting_user_id = lu.id
+            left join store_user su on v.reporting_user_id = su.id
+        <if test="id != null and id != ''">
+            where v.id = #{id}
+        </if>
+    </select>
+
 </mapper>

+ 35 - 0
alien-gateway/src/main/java/shop/alien/gateway/config/FeignHttpMessageConvertersConfig.java

@@ -0,0 +1,35 @@
+package shop.alien.gateway.config;
+
+import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Feign HttpMessageConverters 配置
+ * 在 WebFlux 环境中,Spring Boot 可能不会自动配置 HttpMessageConverters,
+ * 但 OpenFeign 仍然需要它来进行消息转换
+ *
+ * @author system
+ * @date 2025-01-XX
+ */
+@Configuration
+public class FeignHttpMessageConvertersConfig {
+
+    /**
+     * 手动创建 HttpMessageConverters bean,确保 OpenFeign 能够正常使用
+     * 这个 bean 会被 OpenFeign 的 SpringDecoder 使用
+     */
+    @Bean
+    public HttpMessageConverters httpMessageConverters() {
+        List<HttpMessageConverter<?>> converters = new ArrayList<>();
+        converters.add(new MappingJackson2HttpMessageConverter());
+        return new HttpMessageConverters(converters);
+    }
+}
+
+

+ 22 - 0
alien-gateway/src/main/java/shop/alien/gateway/feign/StoreServiceFeign.java

@@ -0,0 +1,22 @@
+package shop.alien.gateway.feign;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeSysMenu;
+
+import java.util.List;
+
+@FeignClient(name = "alien-store", url = "${feign.alienStore.url}")
+public interface StoreServiceFeign {
+    /**
+     * 根据用户ID查询用户菜单树形结构
+     *
+     * @param userId 用户ID
+     * @param ifTree 是否返回树形结构(0否 1是)
+     * @return 用户菜单树形结构
+     */
+    @GetMapping(value = "/sys/menu/getMenuByUserId")
+    R<List<LifeSysMenu>> getMenuByUserId(@RequestParam("userId") Long userId, @RequestParam(name = "ifTree",required = false) Long ifTree);
+}

+ 11 - 0
alien-gateway/src/main/java/shop/alien/gateway/service/impl/SystemServiceImpl.java

@@ -4,14 +4,17 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeSys;
+import shop.alien.entity.store.LifeSysMenu;
 import shop.alien.entity.store.UserLoginInfo;
 import shop.alien.entity.store.vo.SystemLoginVo;
 import shop.alien.gateway.config.BaseRedisService;
+import shop.alien.gateway.feign.StoreServiceFeign;
 import shop.alien.gateway.mapper.LifeSysGatewayMapper;
 import shop.alien.gateway.service.SystemService;
 import shop.alien.util.common.JwtUtil;
@@ -38,6 +41,9 @@ public class SystemServiceImpl implements SystemService {
 
     private final LifeSysGatewayMapper lifeSysMapper;
 
+    @Autowired
+    private final StoreServiceFeign storeServiceFeign;
+
     private final BaseRedisService baseRedisService;
 
     @Value("${jwt.expiration-time}")
@@ -78,6 +84,9 @@ public class SystemServiceImpl implements SystemService {
             tokenMap.put("userName", lifeSys.getUserName());
             tokenMap.put("userId", String.valueOf(lifeSys.getId()));
             tokenMap.put("userType", "web");
+            R<List<LifeSysMenu>> menuByUserId = storeServiceFeign.getMenuByUserId(lifeSys.getId().longValue(), 1L);
+            List<LifeSysMenu> data = menuByUserId.getData();
+            tokenMap.put("menuList", JSONObject.toJSONString(data));
             //存入token
             result.setToken(JwtUtil.createJWT("web_" + lifeSys.getId(), lifeSys.getUserName(), JSONObject.toJSONString(tokenMap), effectiveTimeIntLong));
             baseRedisService.setString("web_" + lifeSys.getUserName(), result.getToken());
@@ -85,6 +94,8 @@ public class SystemServiceImpl implements SystemService {
             result.setResult(true);
             //角色Id
             result.setRoleId(lifeSys.getRoleId());
+            //菜单列表
+            result.setMenuList(data);
             //登录结果
             result.setMessage("登录成功!!");
         } else {

+ 4 - 0
alien-store/pom.xml

@@ -288,6 +288,10 @@
             <artifactId>okhttp</artifactId>
             <version>4.12.0</version>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 60 - 0
alien-store/src/main/java/shop/alien/store/annotation/ChangeRecordLog.java

@@ -0,0 +1,60 @@
+package shop.alien.store.annotation;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import shop.alien.store.enums.OperationType;
+
+import java.lang.annotation.*;
+
+/**
+ * 操作日志注解
+ *
+ * @author ssk
+ * @since 2025-12-09
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ChangeRecordLog  {
+
+    /**
+     * 操作模块
+     */
+    String module() default "";
+
+    /**
+     * 操作类型
+     */
+    OperationType type() default OperationType.OTHER;
+
+    /**
+     * 操作描述(支持SpEL表达式,如:"导入了excel文件,文件名#{fileName},共#{count}条数据")
+     */
+    String content() default "";
+
+    /**
+     * 是否记录修改前的数据(自動從方法參數中提取ID並查詢原始數據)
+     */
+    boolean recordBefore() default false;
+
+    /**
+     * ID字段名稱(默認為"id",如果參數對象中的ID字段名不同,可指定)
+     */
+    String idField() default "id";
+
+    /**
+     * ID參數名稱(默認為空,如果方法參數中ID參數的名稱不是"id",可指定,如:"holidayId"、"userId"等)
+     * 如果指定了此參數,會優先從方法參數中按名稱查找ID值
+     */
+    String idParam() default "";
+
+    /**
+     * 是否记录修改后的数据
+     */
+    boolean recordAfter() default true;
+
+    /**
+     * Mapper类(用于查询修改前的数据,如果不指定则从Spring容器中查找)
+     */
+    Class<? extends BaseMapper> mapper() default BaseMapper.class;
+}
+

+ 1043 - 0
alien-store/src/main/java/shop/alien/store/aspect/OperationLogAspect.java

@@ -0,0 +1,1043 @@
+package shop.alien.store.aspect;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+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.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Component;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import shop.alien.entity.store.OperationLog;
+import shop.alien.store.service.OperationLogService;
+import shop.alien.store.util.ContentGeneratorUtil;
+import shop.alien.store.util.DataCompareUtil;
+import shop.alien.util.common.JwtUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 操作日志切面
+ *
+ * @author ssk
+ * @since 2025-12-09
+ */
+@Slf4j
+@Aspect
+@Component
+@Order(1)
+@RequiredArgsConstructor
+public class OperationLogAspect implements ApplicationContextAware {
+    
+    private ApplicationContext applicationContext;
+
+    private final OperationLogService operationLogService;
+
+    private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
+    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
+    // 用於解析模板表達式(如 "新增了節假日,節日名稱:#{#holiday.festivalName}")
+    private final TemplateParserContext templateParserContext = new TemplateParserContext("#{", "}");
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.applicationContext = applicationContext;
+    }
+
+    /**
+     * 定义切点:所有标注了@OperationLog注解的方法
+     */
+    @Pointcut("@annotation(shop.alien.store.annotation.ChangeRecordLog)")
+    public void operationLogPointcut() {
+    }
+
+    /**
+     * 环绕通知:记录操作日志
+     */
+    @Around("operationLogPointcut()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        log.info("========== AOP 切面被觸發 ==========");
+        log.info("目標類: {}", joinPoint.getTarget().getClass().getName());
+        log.info("方法: {}", joinPoint.getSignature().getName());
+        
+        // 在方法执行前获取注解信息,用于查询修改前的数据
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        shop.alien.store.annotation.ChangeRecordLog annotation = method.getAnnotation(shop.alien.store.annotation.ChangeRecordLog.class);
+        
+        // 在方法执行前查询修改前的数据(对于UPDATE操作,必须在方法执行前查询)
+        Object beforeDataObj = null;
+        if (annotation != null && annotation.recordBefore() && annotation.type().name().equals("UPDATE")) {
+            try {
+                log.info("========== 方法執行前:開始查詢修改前的數據 ==========");
+                Object[] args = joinPoint.getArgs();
+                String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
+                
+                Object paramObj = null;
+                Object idValue = null;
+                
+                // 優先使用注解中指定的ID參數名
+                if (annotation != null && StringUtils.isNotBlank(annotation.idParam()) && args != null && parameterNames != null) {
+                    for (int i = 0; i < parameterNames.length; i++) {
+                        if (annotation.idParam().equals(parameterNames[i]) && args[i] != null) {
+                            idValue = args[i];
+                            log.info("從注解指定的參數名中獲取ID: 參數名={}, ID值={}, 類型: {}", 
+                                    annotation.idParam(), idValue, args[i].getClass().getSimpleName());
+                            break;
+                        }
+                    }
+                }
+                
+                // 如果沒有通過指定參數名找到ID,則使用原來的邏輯
+                if (idValue == null && args != null && parameterNames != null) {
+                    for (int i = 0; i < args.length; i++) {
+                        Object arg = args[i];
+                        if (arg != null) {
+                            // 如果參數本身就是ID(Integer、Long、String等基本類型)
+                            if (isPrimitiveOrWrapper(arg.getClass()) || isString(arg.getClass())) {
+                                // 如果指定了mapper,可以直接使用這個參數作為ID
+                                if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                                    // 如果沒有指定idParam,或者參數名是"id",則使用
+                                    if (StringUtils.isBlank(annotation.idParam()) || "id".equals(parameterNames[i])) {
+                                        idValue = arg;
+                                        log.info("參數本身就是ID: {}, 參數名: {}, 類型: {}", 
+                                                idValue, parameterNames[i], arg.getClass().getSimpleName());
+                                        break;
+                                    }
+                                }
+                            } else {
+                                // 嘗試從對象中提取ID
+                                idValue = extractIdFromObject(arg, annotation.idField());
+                                if (idValue != null) {
+                                    paramObj = arg;
+                                    log.info("從參數中提取到ID: {}, 對象類型: {}", idValue, arg.getClass().getSimpleName());
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+                
+                // 如果找到了ID,通過Mapper查詢原始數據(方法執行前)
+                // 如果注解中指定了mapper,可以直接使用mapper查詢,不需要paramObj
+                if (idValue != null) {
+                    Class<?> entityClass = paramObj != null ? paramObj.getClass() : null;
+                    // 如果指定了mapper,實體類會從mapper中提取,所以entityClass可以為null
+                    if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                        entityClass = null; // 實體類將從mapper中提取
+                    }
+                    beforeDataObj = queryBeforeDataById(entityClass, idValue, annotation);
+                    if (beforeDataObj != null) {
+                        log.info("✓ 成功查詢到修改前的數據,類型: {}", beforeDataObj.getClass().getSimpleName());
+                        log.info("修改前的數據JSON: {}", JSON.toJSONString(beforeDataObj));
+                    } else {
+                        log.warn("查詢修改前的數據返回null,ID: {}", idValue);
+                    }
+                } else {
+                    log.warn("無法從參數中提取ID,跳過查詢修改前的數據");
+                }
+            } catch (Exception e) {
+                log.error("方法執行前查詢修改前的數據失敗", e);
+            }
+        }
+        
+        long startTime = System.currentTimeMillis();
+        Object result = null;
+        Exception exception = null;
+        
+        try {
+            // 执行目标方法
+            result = joinPoint.proceed();
+            log.info("目標方法執行成功,返回值: {}", result != null ? result.getClass().getSimpleName() : "null");
+            return result;
+        } catch (Exception e) {
+            exception = e;
+            log.error("目標方法執行失敗", e);
+            throw e;
+        } finally {
+            try {
+                // 记录操作日志(传入方法执行前查询的修改前数据)
+                log.info("開始記錄操作日誌...");
+                recordOperationLog(joinPoint, result, exception, startTime, beforeDataObj);
+            } catch (Exception e) {
+                log.error("记录操作日志失败", e);
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 记录操作日志
+     */
+    private void recordOperationLog(ProceedingJoinPoint joinPoint, Object result, Exception exception, long startTime, Object preQueryBeforeData) {
+        try {
+            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+            Method method = signature.getMethod();
+            shop.alien.store.annotation.ChangeRecordLog operationLogAnnotation = method.getAnnotation(shop.alien.store.annotation.ChangeRecordLog.class);
+
+            if (operationLogAnnotation == null) {
+                log.warn("方法 {} 上未找到 @ChangeRecordLog 註解", method.getName());
+                return;
+            }
+
+            log.info("開始記錄操作日誌: 方法={}, 模組={}", method.getName(), operationLogAnnotation.module());
+
+            // 获取方法参数
+            Object[] args = joinPoint.getArgs();
+            String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
+            
+            // 创建SpEL上下文
+            EvaluationContext context = createEvaluationContext(method, args, parameterNames, result);
+
+            // 构建操作日志对象
+            OperationLog operationLog = new OperationLog();
+            
+            // 设置用户信息
+            setUserInfo(operationLog);
+
+            // 设置操作模块
+            operationLog.setOperationModule(parseSpEL(operationLogAnnotation.module(), context, String.class));
+
+            // 设置操作类型
+            operationLog.setOperationType(operationLogAnnotation.type().getDescription());
+
+            // 设置操作内容(支持SpEL表达式,但優先使用自動生成)
+            String content = null;
+            
+            // 如果content不為空,嘗試解析SpEL表達式
+            if (StringUtils.isNotBlank(operationLogAnnotation.content())) {
+                content = parseSpEL(operationLogAnnotation.content(), context, String.class);
+            }
+            
+            // 如果content為空或為空字符串,嘗試自動生成
+            if (StringUtils.isBlank(content)) {
+                content = autoGenerateContent(operationLogAnnotation, joinPoint, args, result);
+            }
+            
+            operationLog.setOperationContent(content);
+
+            // 设置请求信息
+            setRequestInfo(operationLog, joinPoint);
+
+            // 设置操作时间
+            operationLog.setOperationTime(new Date());
+
+            // 记录修改前后的数据(传入方法执行前查询的修改前数据)
+            if (operationLogAnnotation.recordBefore() || operationLogAnnotation.recordAfter()) {
+                recordDataChange(operationLog, joinPoint, operationLogAnnotation, context, result, preQueryBeforeData);
+            }
+
+            // 保存操作日志
+            operationLogService.saveOperationLog(operationLog);
+            log.info("操作日誌記錄完成: 模組={}, 類型={}, 內容={}", 
+                    operationLog.getOperationModule(), 
+                    operationLog.getOperationType(),
+                    operationLog.getOperationContent());
+        } catch (Exception e) {
+            log.error("記錄操作日誌時發生異常", e);
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 创建SpEL表达式上下文
+     */
+    private EvaluationContext createEvaluationContext(Method method, Object[] args, String[] parameterNames, Object result) {
+        StandardEvaluationContext context = new StandardEvaluationContext();
+        
+        // 添加方法参数
+        if (parameterNames != null && args != null) {
+            for (int i = 0; i < parameterNames.length; i++) {
+                context.setVariable(parameterNames[i], args[i]);
+            }
+        }
+        
+        // 添加返回值
+        if (result != null) {
+            context.setVariable("result", result);
+        }
+
+        // 添加方法信息
+        context.setVariable("method", method);
+        
+        // 添加 Spring Bean 解析器,支持在 SpEL 中訪問 Spring Bean
+        // 例如:@holidayService.getHolidayById(#id) 或 @holidayServiceImpl.getHolidayById(#id)
+        if (applicationContext != null) {
+            try {
+                context.setBeanResolver(new BeanFactoryResolver(applicationContext));
+                log.debug("BeanResolver設置成功");
+            } catch (Exception e) {
+                log.error("設置BeanResolver失敗", e);
+            }
+        } else {
+            log.warn("ApplicationContext為null,無法設置BeanResolver");
+        }
+        
+        return context;
+    }
+
+    /**
+     * 解析SpEL表达式
+     * 支持兩種模式:
+     * 1. 純字符串:直接返回
+     * 2. 包含SpEL表達式的模板字符串(如 "新增了節假日,節日名稱:#{#holiday.festivalName}"):解析表達式並替換
+     * 3. 純SpEL表達式(如 "#{#holiday.festivalName}"):解析並返回結果
+     */
+    private <T> T parseSpEL(String expression, EvaluationContext context, Class<T> clazz) {
+        if (expression == null || expression.trim().isEmpty()) {
+            return null;
+        }
+        
+        // 如果字符串不包含SpEL表達式語法(#{}),直接返回字符串
+        if (!expression.contains("#{") && !expression.contains("${")) {
+            if (clazz == String.class) {
+                return clazz.cast(expression);
+            }
+            // 如果不是String類型,嘗試轉換
+            try {
+                if (clazz.isInstance(expression)) {
+                    return clazz.cast(expression);
+                }
+            } catch (Exception e) {
+                log.debug("類型轉換失敗: {}", expression, e);
+            }
+            return null;
+        }
+        
+        try {
+            // 如果字符串包含模板表達式(如 "新增了節假日,節日名稱:#{#holiday.festivalName}")
+            // 使用 TemplateParserContext 來解析模板
+            if (expression.contains("#{") && clazz == String.class) {
+                Expression exp = spelExpressionParser.parseExpression(expression, templateParserContext);
+                Object value = exp.getValue(context, String.class);
+                if (value != null) {
+                    return clazz.cast(value);
+                }
+            }
+            
+            // 如果是純SpEL表達式(如 "#{#holiday.festivalName}" 或 "@holidayService.getHolidayById(#id)"),直接解析
+            Expression exp = spelExpressionParser.parseExpression(expression);
+            Object value = exp.getValue(context);
+            if (value == null) {
+                log.debug("SpEL表達式解析結果為null: {}", expression);
+                return null;
+            }
+            
+            // 檢查解析結果是否為字符串且等於原始表達式(表示解析失敗)
+            if (value instanceof String) {
+                String valueStr = (String) value;
+                // 如果返回的字符串等於原始表達式,或者包含 @ 或 # 符號,可能是解析失敗
+                if (valueStr.equals(expression) || 
+                    (expression.startsWith("@") && valueStr.startsWith("@")) ||
+                    (expression.startsWith("#") && valueStr.startsWith("#"))) {
+                    log.error("SpEL表達式解析失敗,返回了原始表達式字符串。表達式: {}, 返回值: {}", expression, valueStr);
+                    // 對於非String類型,返回null而不是原始字符串,避免後續處理錯誤
+                    if (clazz != String.class) {
+                        return null;
+                    }
+                }
+            }
+            
+            if (clazz.isInstance(value)) {
+                return clazz.cast(value);
+            }
+            return (T) value;
+        } catch (Exception e) {
+            log.error("解析SpEL表达式失败: {}, 錯誤: {}", expression, e.getMessage(), e);
+            // 如果解析失敗,且是String類型,返回原字符串
+            // 對於Object類型,返回null而不是原始字符串,避免後續JSON解析錯誤
+            if (clazz == String.class) {
+                return clazz.cast(expression);
+            }
+            // 對於Object.class,返回null
+            if (clazz == Object.class) {
+                return null;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * 设置用户信息
+     */
+    private void setUserInfo(OperationLog operationLog) {
+        try {
+            JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+            if (userInfo != null) {
+                operationLog.setAccountId(userInfo.getString("userId"));
+                operationLog.setAccount(userInfo.getString("phone") != null ? userInfo.getString("phone") : userInfo.getString("account"));
+                operationLog.setName(userInfo.getString("userName") != null ? userInfo.getString("userName") : userInfo.getString("name"));
+                operationLog.setPosition(userInfo.getString("position"));
+                operationLog.setDepartment(userInfo.getString("department"));
+                
+                // 设置用户类型
+                String userType = userInfo.getString("userType");
+                if ("platform".equals(userType) || "platform".equals(userInfo.getString("role"))) {
+                    operationLog.setUserType("平台");
+                } else if ("store".equals(userType) || "merchant".equals(userType)) {
+                    operationLog.setUserType("商家");
+                } else if ("lawyer".equals(userType)) {
+                    operationLog.setUserType("律师");
+                } else {
+                    operationLog.setUserType("用户");
+                }
+            }
+        } catch (Exception e) {
+            log.warn("获取用户信息失败", e);
+        }
+    }
+
+    /**
+     * 设置请求信息
+     */
+    private void setRequestInfo(OperationLog operationLog, ProceedingJoinPoint joinPoint) {
+        try {
+            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            if (attributes != null) {
+                HttpServletRequest request = attributes.getRequest();
+                operationLog.setMethod(request.getMethod());
+                operationLog.setRequestPath(request.getRequestURI());
+                operationLog.setIpAddress(getIpAddress(request));
+                
+                // 获取请求参数
+                Map<String, Object> params = new HashMap<>();
+                if (request.getParameterMap() != null && !request.getParameterMap().isEmpty()) {
+                    request.getParameterMap().forEach((key, values) -> {
+                        if (values != null && values.length > 0) {
+                            params.put(key, values.length == 1 ? values[0] : values);
+                        }
+                    });
+                }
+                operationLog.setRequestParams(JSON.toJSONString(params));
+            }
+
+            // 设置方法信息
+            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+            operationLog.setMethod(signature.getDeclaringTypeName() + "." + signature.getMethod().getName());
+        } catch (Exception e) {
+            log.warn("获取请求信息失败", e);
+        }
+    }
+
+    /**
+     * 记录数据变更(修改前后的数据)
+     */
+    private void recordDataChange(OperationLog operationLog, ProceedingJoinPoint joinPoint, 
+                                  shop.alien.store.annotation.ChangeRecordLog annotation,
+                                  EvaluationContext context, Object result, Object preQueryBeforeData) {
+        try {
+            // 使用在方法执行前查询的修改前数据(如果存在)
+            Object beforeDataObj = preQueryBeforeData;
+            Object afterDataObj = null;  // 修改後的數據對象
+            
+            // 對於UPDATE操作,先從方法參數中獲取修改後的數據
+            if (annotation.type().name().equals("UPDATE")) {
+                Object[] args = joinPoint.getArgs();
+                if (args != null) {
+                    for (Object arg : args) {
+                        if (arg != null && !isPrimitiveOrWrapper(arg.getClass()) && !isString(arg.getClass())) {
+                            // 找到第一個非基本類型的參數對象(通常是實體對象)
+                            afterDataObj = arg;
+                            log.info("從方法參數中獲取修改後的數據,類型: {}", afterDataObj.getClass().getSimpleName());
+                            break;
+                        }
+                    }
+                }
+            }
+            
+            // 如果方法执行前没有查询到修改前的数据,则在这里查询(对于非UPDATE操作或其他情况)
+            if (annotation.recordBefore() && beforeDataObj == null) {
+                try {
+                    log.info("========== 方法執行後:開始查詢修改前的數據 ==========");
+                    // 從方法參數中提取ID和對象
+                    Object[] args = joinPoint.getArgs();
+                    String[] parameterNames = parameterNameDiscoverer.getParameterNames(
+                            ((MethodSignature) joinPoint.getSignature()).getMethod());
+                    
+                    Object paramObj = null;
+                    Object idValue = null;
+                    
+                    // 優先使用注解中指定的ID參數名
+                    if (annotation != null && StringUtils.isNotBlank(annotation.idParam()) && args != null && parameterNames != null) {
+                        for (int i = 0; i < parameterNames.length; i++) {
+                            if (annotation.idParam().equals(parameterNames[i]) && args[i] != null) {
+                                idValue = args[i];
+                                log.info("從注解指定的參數名中獲取ID: 參數名={}, ID值={}, 類型: {}", 
+                                        annotation.idParam(), idValue, args[i].getClass().getSimpleName());
+                                break;
+                            }
+                        }
+                    }
+                    
+                    // 如果沒有通過指定參數名找到ID,則使用原來的邏輯
+                    if (idValue == null && args != null && parameterNames != null) {
+                        for (int i = 0; i < args.length; i++) {
+                            Object arg = args[i];
+                            if (arg != null) {
+                                // 如果參數本身就是ID(Integer、Long、String等基本類型)
+                                if (isPrimitiveOrWrapper(arg.getClass()) || isString(arg.getClass())) {
+                                    // 如果指定了mapper,可以直接使用這個參數作為ID
+                                    if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                                        // 如果沒有指定idParam,或者參數名是"id",則使用
+                                        if (StringUtils.isBlank(annotation.idParam()) || "id".equals(parameterNames[i])) {
+                                            idValue = arg;
+                                            log.info("參數本身就是ID: {}, 參數名: {}, 類型: {}", 
+                                                    idValue, parameterNames[i], arg.getClass().getSimpleName());
+                                            break;
+                                        }
+                                    } else {
+                                        // 沒有指定mapper,需要找到對應的實體類
+                                        idValue = arg;
+                                        log.info("參數本身就是ID: {}", idValue);
+                                        // 嘗試從其他參數中找實體對象
+                                        for (int j = 0; j < args.length; j++) {
+                                            if (j != i && args[j] != null && 
+                                                !isPrimitiveOrWrapper(args[j].getClass()) && 
+                                                !isString(args[j].getClass())) {
+                                                paramObj = args[j];
+                                                break;
+                                            }
+                                        }
+                                        break;
+                                    }
+                                } else {
+                                    // 嘗試從對象中提取ID
+                                    idValue = extractIdFromObject(arg, annotation.idField());
+                                    if (idValue != null) {
+                                        paramObj = arg;
+                                        log.info("從參數中提取到ID: {}, 對象類型: {}", idValue, arg.getClass().getSimpleName());
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    
+                    // 如果找到了ID,通過Mapper查詢原始數據
+                    // 如果注解中指定了mapper,可以直接使用mapper查詢,不需要paramObj
+                    if (idValue != null) {
+                        Class<?> entityClass = paramObj != null ? paramObj.getClass() : null;
+                        // 如果指定了mapper,實體類會從mapper中提取,所以entityClass可以為null
+                        if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                            entityClass = null; // 實體類將從mapper中提取
+                        }
+                        beforeDataObj = queryBeforeDataById(entityClass, idValue, annotation);
+                    } else {
+                        log.warn("無法從參數中提取ID,跳過查詢修改前的數據");
+                    }
+                } catch (Exception e) {
+                    log.error("查詢修改前的數據失敗", e);
+                    // 記錄錯誤但不影響主流程
+                }
+            }
+            
+            // 记录修改前的数据
+            if (beforeDataObj != null) {
+                try {
+                    String beforeData = JSON.toJSONString(beforeDataObj);
+                    operationLog.setBeforeData(beforeData);
+                    log.info("========== 成功獲取修改前的數據 ==========");
+                    log.info("修改前的數據類型: {}", beforeDataObj.getClass().getName());
+                    log.info("修改前的完整JSON: {}", beforeData);
+                    log.info("數據長度: {}", beforeData.length());
+                } catch (Exception e) {
+                    log.error("序列化修改前的數據失敗", e);
+                    operationLog.setBeforeData("序列化失敗: " + e.getMessage());
+                }
+            } else if (annotation.recordBefore()) {
+                log.error("========== 修改前的數據為null ==========");
+            }
+
+            // 记录修改后的数据
+            if (annotation.recordAfter()) {
+                // 優先使用從參數中獲取的修改後數據(對於UPDATE操作)
+                Object actualResult = afterDataObj;
+                
+                // 如果參數中沒有,則從返回值中提取
+                if (actualResult == null && result != null) {
+                    log.info("========== 從返回值提取修改後的數據 ==========");
+                    log.info("result類型: {}", result.getClass().getName());
+                    log.info("result的JSON: {}", JSON.toJSONString(result));
+                    
+                    actualResult = extractDataFromResult(result);
+                    
+                    log.info("提取後的actualResult類型: {}", actualResult != null ? actualResult.getClass().getName() : "null");
+                    log.info("提取後的actualResult的JSON: {}", actualResult != null ? JSON.toJSONString(actualResult) : "null");
+                }
+                
+                if (actualResult != null) {
+                    String afterData = JSON.toJSONString(actualResult);
+                    operationLog.setAfterData(afterData);
+                    log.info("記錄修改後的數據,類型: {}, 數據長度: {}", 
+                            actualResult.getClass().getSimpleName(), 
+                            afterData.length());
+                } else {
+                    log.warn("無法獲取修改後的數據");
+                }
+                
+                // 如果同時有修改前的數據,則生成詳細的變更描述
+                if (beforeDataObj != null && annotation.recordBefore() && actualResult != null) {
+                    try {
+                        // 使用修改後的數據對象
+                        Object afterObj = actualResult;
+                        
+                        if (afterObj == null) {
+                            log.warn("修改後的對象為null,無法進行比較");
+                        } else {
+                            log.info("開始生成詳細變更描述,修改前對象類型: {}, 修改後對象類型: {}", 
+                                    beforeDataObj.getClass().getSimpleName(), 
+                                    afterObj.getClass().getSimpleName());
+                            
+                            // 輸出實際的JSON數據以便調試
+                            String beforeJson = JSON.toJSONString(beforeDataObj);
+                            String afterJson = JSON.toJSONString(afterObj);
+                            log.info("========== 數據比較開始 ==========");
+                            log.info("修改前的完整JSON: {}", beforeJson);
+                            log.info("修改後的完整JSON: {}", afterJson);
+                            log.info("修改前的對象類型: {}", beforeDataObj.getClass().getName());
+                            log.info("修改後的對象類型: {}", afterObj.getClass().getName());
+                            
+                            // 自動推斷id和name字段
+                            String[] idAndName = ContentGeneratorUtil.inferIdAndNameFields(beforeDataObj);
+                            String idField = idAndName[0];
+                            String nameField = idAndName[1];
+                            
+                            log.info("推斷的字段: idField={}, nameField={}", idField, nameField);
+                            
+                            // 生成詳細的變更描述
+                            String changeDesc = DataCompareUtil.generateChangeDescription(beforeDataObj, afterObj, idField, nameField);
+                            if (changeDesc != null && !changeDesc.isEmpty()) {
+                                log.info("生成的變更描述: {}", changeDesc);
+                                // 如果操作內容為空或使用默認值,則使用變更描述
+                                String currentContent = operationLog.getOperationContent();
+                                if (currentContent == null || currentContent.isEmpty() || 
+                                    (annotation.content() != null && !annotation.content().isEmpty() && currentContent.equals(annotation.content()))) {
+                                    operationLog.setOperationContent(changeDesc);
+                                } else {
+                                    // 否則追加變更描述
+                                    operationLog.setOperationContent(currentContent + " - " + changeDesc);
+                                }
+                            } else {
+                                log.warn("變更描述為空,可能沒有字段變更或比較失敗");
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.error("生成變更描述失敗", e);
+                        e.printStackTrace();
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.warn("记录数据变更失败", e);
+        }
+    }
+
+    /**
+     * 自動生成操作內容
+     * 當content為空時,根據操作類型和對象字段自動生成
+     */
+    private String autoGenerateContent(shop.alien.store.annotation.ChangeRecordLog annotation,
+                                      ProceedingJoinPoint joinPoint, Object[] args, Object result) {
+        try {
+            String operationType = annotation.type().getDescription();
+            
+            // 嘗試從方法參數或返回值中獲取對象
+            Object targetObj = null;
+            
+            // 優先使用返回值(對於新增、修改操作)
+            if (result != null && ("新增".equals(operationType) || "修改".equals(operationType) || "更新".equals(operationType))) {
+                // 如果返回值是 R 包裝類,嘗試獲取 data
+                if (result.getClass().getName().contains("R")) {
+                    try {
+                        java.lang.reflect.Method getDataMethod = result.getClass().getMethod("getData");
+                        Object data = getDataMethod.invoke(result);
+                        if (data != null) {
+                            targetObj = data;
+                        }
+                    } catch (Exception e) {
+                        // 如果獲取失敗,使用原返回值
+                        targetObj = result;
+                    }
+                } else {
+                    targetObj = result;
+                }
+            }
+            // 如果沒有返回值,嘗試從參數中獲取第一個對象參數
+            else if (args != null && args.length > 0) {
+                for (Object arg : args) {
+                    if (arg != null && !isPrimitiveOrWrapper(arg.getClass()) && !isString(arg.getClass())) {
+                        targetObj = arg;
+                        break;
+                    }
+                }
+            }
+            
+            if (targetObj != null) {
+                // 自動推斷id和name字段
+                String[] idAndName = ContentGeneratorUtil.inferIdAndNameFields(targetObj);
+                String idField = idAndName[0];
+                String nameField = idAndName[1];
+                
+                // 生成內容
+                return ContentGeneratorUtil.generateContent(operationType, targetObj, idField, nameField);
+            }
+            
+            // 如果無法獲取對象,返回默認內容
+            return operationType + "操作";
+            
+        } catch (Exception e) {
+            log.warn("自動生成操作內容失敗", e);
+            return annotation.type().getDescription() + "操作";
+        }
+    }
+
+    /**
+     * 判斷是否為基本類型或包裝類型
+     */
+    private boolean isPrimitiveOrWrapper(Class<?> clazz) {
+        return clazz.isPrimitive() ||
+               clazz == Boolean.class ||
+               clazz == Byte.class ||
+               clazz == Character.class ||
+               clazz == Short.class ||
+               clazz == Integer.class ||
+               clazz == Long.class ||
+               clazz == Float.class ||
+               clazz == Double.class;
+    }
+
+    /**
+     * 判斷是否為字符串類型
+     */
+    private boolean isString(Class<?> clazz) {
+        return clazz == String.class;
+    }
+
+    /**
+     * 获取客户端IP地址
+     */
+    private String getIpAddress(HttpServletRequest request) {
+        String ip = request.getHeader("X-Forwarded-For");
+        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_CLIENT_IP");
+        }
+        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+        }
+        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        return ip;
+    }
+
+    /**
+     * 從對象中提取ID值
+     */
+    private Object extractIdFromObject(Object obj, String idFieldName) {
+        if (obj == null) {
+            return null;
+        }
+        
+        try {
+            // 先嘗試通過JSON方式獲取
+            JSONObject jsonObj = JSONObject.parseObject(JSON.toJSONString(obj));
+            Object id = jsonObj.get(idFieldName);
+            if (id != null) {
+                return id;
+            }
+            
+            // 如果JSON方式失敗,嘗試反射
+            Class<?> clazz = obj.getClass();
+            Field field = clazz.getDeclaredField(idFieldName);
+            field.setAccessible(true);
+            return field.get(obj);
+        } catch (Exception e) {
+            log.debug("提取ID失敗,字段名: {}, 對象類型: {}", idFieldName, obj.getClass().getSimpleName(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 通過ID查詢修改前的數據
+     */
+    private Object queryBeforeDataById(Class<?> entityClass, Object id, shop.alien.store.annotation.ChangeRecordLog annotation) {
+        if (id == null) {
+            return null;
+        }
+        
+        try {
+            BaseMapper<?> mapper = null;
+            Class<?> actualEntityClass = entityClass;
+            
+            // 優先使用注解中指定的Mapper
+            if (annotation != null && annotation.mapper() != null && annotation.mapper() != BaseMapper.class) {
+                try {
+                    if (applicationContext != null) {
+                        // 從Spring容器中獲取指定類型的Mapper實例
+                        mapper = applicationContext.getBean(annotation.mapper());
+                        log.info("使用注解中指定的Mapper: {}", annotation.mapper().getSimpleName());
+                        
+                        // 從Mapper的泛型參數中提取實體類
+                        actualEntityClass = extractEntityClassFromMapper(annotation.mapper());
+                        if (actualEntityClass != null) {
+                            log.info("從Mapper泛型中提取到實體類: {}", actualEntityClass.getSimpleName());
+                        } else {
+                            log.warn("無法從Mapper泛型中提取實體類,使用參數中的實體類");
+                            actualEntityClass = entityClass;
+                        }
+                    }
+                } catch (Exception e) {
+                    log.warn("無法從容器中獲取注解指定的Mapper: {}", annotation.mapper().getSimpleName(), e);
+                }
+            }
+            
+            // 如果注解中沒有指定Mapper或獲取失敗,則從容器中查找
+            if (mapper == null && applicationContext != null) {
+                if (entityClass == null) {
+                    log.warn("未指定Mapper且無法從參數中獲取實體類,無法查詢數據");
+                    return null;
+                }
+                
+                // 根據實體類名推斷Mapper名稱
+                // 例如:EssentialHolidayComparison -> EssentialHolidayComparisonMapper
+                String entityName = entityClass.getSimpleName();
+                String mapperBeanName = entityName.substring(0, 1).toLowerCase() + entityName.substring(1) + "Mapper";
+                
+                try {
+                    // 先嘗試標準命名
+                    mapper = (BaseMapper<?>) applicationContext.getBean(mapperBeanName);
+                    if (mapper != null) {
+                        log.info("通過標準命名找到Mapper: {}", mapperBeanName);
+                    }
+                } catch (Exception e) {
+                    log.debug("無法獲取Mapper: {}", mapperBeanName, e);
+                }
+                
+                // 如果標準命名失敗,嘗試查找所有BaseMapper類型的Bean
+                if (mapper == null) {
+                    try {
+                        String[] beanNames = applicationContext.getBeanNamesForType(BaseMapper.class);
+                        for (String beanName : beanNames) {
+                            try {
+                                BaseMapper<?> candidateMapper = applicationContext.getBean(beanName, BaseMapper.class);
+                                // 檢查Mapper的泛型類型是否匹配
+                                java.lang.reflect.Type[] types = candidateMapper.getClass().getGenericInterfaces();
+                                for (java.lang.reflect.Type type : types) {
+                                    if (type instanceof java.lang.reflect.ParameterizedType) {
+                                        java.lang.reflect.ParameterizedType pt = (java.lang.reflect.ParameterizedType) type;
+                                        if (pt.getRawType().equals(BaseMapper.class)) {
+                                            java.lang.reflect.Type[] actualTypes = pt.getActualTypeArguments();
+                                            if (actualTypes.length > 0 && actualTypes[0] instanceof Class) {
+                                                Class<?> mapperEntityClass = (Class<?>) actualTypes[0];
+                                                if (mapperEntityClass.equals(entityClass)) {
+                                                    mapper = candidateMapper;
+                                                    log.info("通過泛型匹配找到Mapper: {}", beanName);
+                                                    break;
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                                if (mapper != null) {
+                                    break;
+                                }
+                            } catch (Exception e) {
+                                // 繼續查找下一個
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.debug("查找Mapper失敗", e);
+                    }
+                }
+            }
+            
+            // 使用找到的Mapper查詢數據
+            if (mapper != null) {
+                Serializable serializableId = convertToSerializable(id);
+                if (serializableId != null) {
+                    Object result = mapper.selectById(serializableId);
+                    log.info("通過Mapper查詢成功,Mapper: {}, 實體類: {}, ID: {}", 
+                            mapper.getClass().getSimpleName(), 
+                            actualEntityClass != null ? actualEntityClass.getSimpleName() : "未知", 
+                            id);
+                    return result;
+                }
+            }
+            
+            log.warn("無法找到對應的Mapper,實體類: {}, ID: {}", 
+                    actualEntityClass != null ? actualEntityClass.getSimpleName() : "未知", id);
+            return null;
+        } catch (Exception e) {
+            log.error("查詢修改前的數據失敗,實體類: {}, ID: {}", 
+                    entityClass != null ? entityClass.getSimpleName() : "未知", id, e);
+            return null;
+        }
+    }
+    
+    /**
+     * 從Mapper類的泛型參數中提取實體類
+     */
+    private Class<?> extractEntityClassFromMapper(Class<? extends BaseMapper> mapperClass) {
+        if (mapperClass == null || mapperClass == BaseMapper.class) {
+            return null;
+        }
+        
+        try {
+            // 獲取Mapper接口的泛型參數
+            java.lang.reflect.Type[] types = mapperClass.getGenericInterfaces();
+            for (java.lang.reflect.Type type : types) {
+                if (type instanceof java.lang.reflect.ParameterizedType) {
+                    java.lang.reflect.ParameterizedType pt = (java.lang.reflect.ParameterizedType) type;
+                    if (pt.getRawType().equals(BaseMapper.class)) {
+                        java.lang.reflect.Type[] actualTypes = pt.getActualTypeArguments();
+                        if (actualTypes.length > 0 && actualTypes[0] instanceof Class) {
+                            return (Class<?>) actualTypes[0];
+                        }
+                    }
+                }
+            }
+            
+            // 如果直接繼承的接口沒有泛型,嘗試從父類獲取
+            java.lang.reflect.Type superclass = mapperClass.getGenericSuperclass();
+            if (superclass instanceof java.lang.reflect.ParameterizedType) {
+                java.lang.reflect.ParameterizedType pt = (java.lang.reflect.ParameterizedType) superclass;
+                if (pt.getRawType().equals(BaseMapper.class)) {
+                    java.lang.reflect.Type[] actualTypes = pt.getActualTypeArguments();
+                    if (actualTypes.length > 0 && actualTypes[0] instanceof Class) {
+                        return (Class<?>) actualTypes[0];
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.warn("從Mapper類中提取實體類失敗: {}", mapperClass.getSimpleName(), e);
+        }
+        
+        return null;
+    }
+
+    /**
+     * 從結果對象中提取實際數據(處理R包裝類)
+     */
+    private Object extractDataFromResult(Object result) {
+        if (result == null) {
+            return null;
+        }
+        
+        String resultClassName = result.getClass().getName();
+        log.debug("提取數據,result類型: {}", resultClassName);
+        
+        // 如果不是R包裝類,直接返回
+        if (!resultClassName.contains("R")) {
+            return result;
+        }
+        
+        // 嘗試多種方法獲取data
+        try {
+            // 方法1: 嘗試 getData() 方法
+            try {
+                java.lang.reflect.Method getDataMethod = result.getClass().getMethod("getData");
+                Object data = getDataMethod.invoke(result);
+                if (data != null) {
+                    log.info("通過getData()方法從R對象中提取data成功,類型: {}", data.getClass().getSimpleName());
+                    return data;
+                }
+            } catch (Exception e) {
+                log.debug("getData()方法不存在", e);
+            }
+            
+            // 方法2: 嘗試 data() 方法
+            try {
+                java.lang.reflect.Method dataMethod = result.getClass().getMethod("data");
+                Object data = dataMethod.invoke(result);
+                if (data != null) {
+                    log.info("通過data()方法從R對象中提取data成功,類型: {}", data.getClass().getSimpleName());
+                    return data;
+                }
+            } catch (Exception e) {
+                log.debug("data()方法不存在", e);
+            }
+            
+            // 方法3: 嘗試通過字段獲取
+            try {
+                java.lang.reflect.Field dataField = result.getClass().getDeclaredField("data");
+                dataField.setAccessible(true);
+                Object data = dataField.get(result);
+                if (data != null) {
+                    log.info("通過字段從R對象中提取data成功,類型: {}", data.getClass().getSimpleName());
+                    return data;
+                }
+            } catch (Exception e) {
+                log.debug("無法通過字段獲取data", e);
+            }
+            
+            log.warn("無法從R對象中提取data,返回原對象。R對象: {}", JSON.toJSONString(result));
+            return result;
+        } catch (Exception e) {
+            log.error("提取R對象中的data失敗", e);
+            return result;
+        }
+    }
+
+    /**
+     * 將對象轉換為 Serializable 類型
+     */
+    private Serializable convertToSerializable(Object id) {
+        if (id == null) {
+            return null;
+        }
+        
+        // 如果已經是 Serializable 類型,直接返回
+        if (id instanceof Serializable) {
+            return (Serializable) id;
+        }
+        
+        // 處理常見的 ID 類型
+        if (id instanceof Integer) {
+            return (Integer) id;
+        } else if (id instanceof Long) {
+            return (Long) id;
+        } else if (id instanceof String) {
+            return (String) id;
+        } else if (id instanceof Number) {
+            // 其他數字類型轉換為 Long
+            return ((Number) id).longValue();
+        }
+        
+        // 嘗試轉換為字符串
+        try {
+            return id.toString();
+        } catch (Exception e) {
+            log.warn("無法將ID轉換為Serializable: {}", id, e);
+            return null;
+        }
+    }
+}
+

+ 33 - 0
alien-store/src/main/java/shop/alien/store/config/CustomAccessDeniedHandler.java

@@ -0,0 +1,33 @@
+package shop.alien.store.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class CustomAccessDeniedHandler implements AccessDeniedHandler {
+
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
+            throws IOException {
+        response.setStatus(HttpStatus.FORBIDDEN.value());
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+        response.setCharacterEncoding("UTF-8");
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("code", 403);
+        result.put("msg", "权限不足,无法访问该接口");
+        result.put("detail", e.getMessage());
+
+        new ObjectMapper().writeValue(response.getWriter(), result);
+    }
+}

+ 74 - 0
alien-store/src/main/java/shop/alien/store/config/SecurityConfig.java

@@ -0,0 +1,74 @@
+package shop.alien.store.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import shop.alien.store.filter.PreAuthFilter;
+
+import javax.annotation.Resource;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Resource
+    private CustomAccessDeniedHandler customAccessDeniedHandler;
+
+    @Resource
+    private PreAuthFilter preAuthFilter;
+
+    /**
+     * 核心:跳过前端静态资源的Security过滤(不走过滤器链,性能最优)
+     * 适用于前端打包后的静态资源(JS/CSS/图片/字体等)
+     */
+    @Override
+    public void configure(WebSecurity web) throws Exception {
+        web.ignoring()
+                // 前端静态资源路径(根据实际项目调整)
+                .antMatchers("/static/**", "/css/**", "/js/**", "/images/**", "/fonts/**")
+                // 若前端部署在后端,放行打包后的dist目录(Vue/React打包后)
+                .antMatchers("/dist/**", "/public/**")
+                // 放行Swagger(若有)
+                .antMatchers("/swagger-ui/**", "/v3/api-docs/**","/doc.html","/webjars/**");
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+                // 1. 禁用CSRF(前后端分离无Cookie,无需保护)
+                .csrf().disable()
+                // 2. 禁用Session(无状态,仅授权)
+                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+                .and()
+                // 3. 关闭所有默认认证入口(表单登录、HTTP Basic)
+                .formLogin().disable()
+                .httpBasic().disable()
+                // 4. 禁用注销
+                .logout().disable()
+                // 5. 配置授权规则
+                .authorizeRequests()
+//                // 公开接口:无需权限
+//                .antMatchers("/doc.html").permitAll()
+//                // 管理员接口:需ROLE_ADMIN角色
+//                .antMatchers("/**").hasRole("ADMIN")
+//                // 普通用户接口:需ROLE_USER角色
+//                .antMatchers("/user/**").hasRole("USER")
+                // 其他接口:需任意认证(即请求头带X-User-Roles)
+                .anyRequest().permitAll()
+                .and()
+                // 6. 添加自定义预认证过滤器(核心:解析权限)
+                .addFilterBefore(preAuthFilter, UsernamePasswordAuthenticationFilter.class)
+                // 7. 配置授权异常处理器
+                .exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
+
+        // 允许跨域
+        http.cors();
+    }
+
+}

+ 167 - 0
alien-store/src/main/java/shop/alien/store/controller/DictOpinionFeedbackController.java

@@ -0,0 +1,167 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.store.service.DictOpinionFeedbackService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * 意见反馈字典控制器
+ */
+@Api(tags = {"平台-意见反馈字典管理"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/dictOpinionFeedback")
+@RequiredArgsConstructor
+public class DictOpinionFeedbackController {
+
+    private final DictOpinionFeedbackService dictOpinionFeedbackService;
+
+    @ApiOperation("查询意见反馈字典(三级树形结构),支持根据dictDetail过滤")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/queryDictOpinionFeedbackTree")
+    public R<List<StoreDictionary>> queryDictOpinionFeedbackTree(@RequestParam(required = false) String dictDetail) {
+        log.info("dictOpinionFeedback.queryDictOpinionFeedbackTree dictDetail={}", dictDetail);
+        List<StoreDictionary> result = dictOpinionFeedbackService.queryDictOpinionFeedbackTree(dictDetail);
+        return R.data(result);
+    }
+
+    @ApiOperation("新增意见反馈字典(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/addDictOpinionFeedback")
+    public R<Boolean> addDictOpinionFeedback(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictOpinionFeedback.addDictOpinionFeedback:{}", storeDictionary);
+        try {
+            boolean result = dictOpinionFeedbackService.addDictOpinionFeedback(storeDictionary);
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictOpinionFeedback.addDictOpinionFeedback error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictOpinionFeedback.addDictOpinionFeedback error", e);
+            return R.fail("新增失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改意见反馈字典(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 3)
+    @PutMapping("/updateDictOpinionFeedback")
+    public R<Boolean> updateDictOpinionFeedback(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictOpinionFeedback.updateDictOpinionFeedback:{}", storeDictionary);
+        try {
+            boolean result = dictOpinionFeedbackService.updateDictOpinionFeedback(storeDictionary);
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictOpinionFeedback.updateDictOpinionFeedback error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictOpinionFeedback.updateDictOpinionFeedback error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量导入意见反馈字典")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/import")
+    public R<String> importDictOpinionFeedback(@RequestParam("file") MultipartFile file) {
+        log.info("dictOpinionFeedback.importDictOpinionFeedback fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            return dictOpinionFeedbackService.importDictOpinionFeedback(file);
+        } catch (Exception e) {
+            log.error("dictOpinionFeedback.importDictOpinionFeedback error", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+//    @ApiOperation("下载意见反馈字典导入模板")
+//    @ApiOperationSupport(order = 5)
+//    @GetMapping("/downloadTemplate")
+//    public void downloadTemplate(HttpServletResponse response) throws IOException {
+//        log.info("dictOpinionFeedback.downloadTemplate");
+//        dictOpinionFeedbackService.downloadTemplate(response);
+//    }
+
+    @ApiOperation("下载意见反馈导入模板")
+    @ApiOperationSupport(order = 10)
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        log.info("StoreMenuPlatformController.downloadTemplate");
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        try {
+            // 从resources/templates目录读取模板文件
+            Resource resource = new ClassPathResource("templates/意见反馈导入模版.xlsx");
+            inputStream = resource.getInputStream();
+
+            // 设置响应头
+            response.reset();
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+
+            String fileName = "意见反馈导入模板.xlsx";
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+            response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + "\";filename*=utf-8''" + encodedFileName);
+
+            // 输出文件流
+            outputStream = response.getOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) > 0) {
+                outputStream.write(buffer, 0, length);
+            }
+            outputStream.flush();
+
+            log.info("意见反馈导入模板下载成功");
+        } catch (Exception e) {
+            log.error("下载意见反馈导入模板失败", e);
+            throw new RuntimeException("下载模板失败:" + e.getMessage());
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                log.error("关闭流失败", e);
+            }
+        }
+    }
+}
+
+

+ 167 - 0
alien-store/src/main/java/shop/alien/store/controller/DictStoreTagController.java

@@ -0,0 +1,167 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.store.service.DictStoreTagService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * 店铺字典标签控制器
+ */
+@Api(tags = {"2.7平台-店铺字典标签管理"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/dictStoreTag")
+@RequiredArgsConstructor
+public class DictStoreTagController {
+
+    private final DictStoreTagService dictStoreTagService;
+
+    @ApiOperation("查询店铺标签字典(三级树形结构),支持根据dictDetail过滤")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/queryDictStoreTagTree")
+    public R<List<StoreDictionary>> queryDictStoreTagTree(@RequestParam(required = false) String dictDetail) {
+        log.info("dictStoreTag.queryDictStoreTagTree dictDetail={}", dictDetail);
+        List<StoreDictionary> result = dictStoreTagService.queryDictStoreTagTree(dictDetail);
+        return R.data(result);
+    }
+
+    @ApiOperation("新增店铺标签字典(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/addDictStoreTag")
+    public R<Boolean> addDictStoreTag(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictStoreTag.addDictStoreTag:{}", storeDictionary);
+        try {
+            boolean result = dictStoreTagService.addDictStoreTag(storeDictionary);
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictStoreTag.addDictStoreTag error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictStoreTag.addDictStoreTag error", e);
+            return R.fail("新增失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改店铺标签字典(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 3)
+    @PutMapping("/updateDictStoreTag")
+    public R<Boolean> updateDictStoreTag(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictStoreTag.updateDictStoreTag:{}", storeDictionary);
+        try {
+            boolean result = dictStoreTagService.updateDictStoreTag(storeDictionary);
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictStoreTag.updateDictStoreTag error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictStoreTag.updateDictStoreTag error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量导入店铺标签字典")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/import")
+    public R<String> importDictStoreTag(@RequestParam("file") MultipartFile file) {
+        log.info("dictStoreTag.importDictStoreTag fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            return dictStoreTagService.importDictStoreTag(file);
+        } catch (Exception e) {
+            log.error("dictStoreTag.importDictStoreTag error", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+//    @ApiOperation("下载店铺标签字典导入模板")
+//    @ApiOperationSupport(order = 5)
+//    @GetMapping("/downloadTemplate")
+//    public void downloadTemplate(HttpServletResponse response) throws IOException {
+//        log.info("dictStoreTag.downloadTemplate");
+//        dictStoreTagService.downloadTemplate(response);
+//    }
+
+    @ApiOperation("下载门店标签导入模板")
+    @ApiOperationSupport(order = 10)
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        log.info("StoreMenuPlatformController.downloadTemplate");
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        try {
+            // 从resources/templates目录读取模板文件
+            Resource resource = new ClassPathResource("templates/门店标签导入模版.xlsx");
+            inputStream = resource.getInputStream();
+
+            // 设置响应头
+            response.reset();
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+
+            String fileName = "门店标签导入模板.xlsx";
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+            response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + "\";filename*=utf-8''" + encodedFileName);
+
+            // 输出文件流
+            outputStream = response.getOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) > 0) {
+                outputStream.write(buffer, 0, length);
+            }
+            outputStream.flush();
+
+            log.info("门店标签导入模板下载成功");
+        } catch (Exception e) {
+            log.error("下载门店标签导入模板失败", e);
+            throw new RuntimeException("下载模板失败:" + e.getMessage());
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                log.error("关闭流失败", e);
+            }
+        }
+    }
+}
+
+

+ 215 - 0
alien-store/src/main/java/shop/alien/store/controller/DictionaryLibraryController.java

@@ -0,0 +1,215 @@
+package shop.alien.store.controller;
+
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.store.service.DictionaryLibraryService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+@Api(tags = {"2.6平台-字典库管理"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/dictionaryLibrary")
+@RequiredArgsConstructor
+public class DictionaryLibraryController {
+
+    private final DictionaryLibraryService dictionaryLibraryService;
+
+    @ApiOperation("查询字典库(三级树形结构),支持根据dictDetail过滤")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/queryDictionaryLibraryTree")
+    public R<List<StoreDictionary>> queryDictionaryLibraryTree(@RequestParam(required = false) String dictDetail) {
+        log.info("dictionaryLibrary.queryDictionaryLibraryTree dictDetail={}", dictDetail);
+        List<StoreDictionary> result = dictionaryLibraryService.queryDictionaryLibraryTree(dictDetail);
+        return R.data(result);
+    }
+
+    @ApiOperation("新增字典库(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/addDictionaryLibrary")
+    public R<Boolean> addDictionaryLibrary(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictionaryLibrary.addDictionaryLibrary:{}", storeDictionary);
+        try {
+            boolean result = dictionaryLibraryService.addDictionaryLibrary(storeDictionary);
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictionaryLibrary.addDictionaryLibrary error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictionaryLibrary.addDictionaryLibrary error", e);
+            return R.fail("新增失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改字典库(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 3)
+    @PutMapping("/updateDictionaryLibrary")
+    public R<Boolean> updateDictionaryLibrary(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictionaryLibrary.updateDictionaryLibrary:{}", storeDictionary);
+        try {
+            boolean result = dictionaryLibraryService.updateDictionaryLibrary(storeDictionary);
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictionaryLibrary.updateDictionaryLibrary error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictionaryLibrary.updateDictionaryLibrary error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量导入字典库")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/import")
+    public R<String> importDictionaryLibrary(@RequestParam("file") MultipartFile file) {
+        log.info("dictionaryLibrary.importDictionaryLibrary fileName={}", 
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            return dictionaryLibraryService.importDictionaryLibrary(file);
+        } catch (Exception e) {
+            log.error("dictionaryLibrary.importDictionaryLibrary error", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+//    @ApiOperation("下载字典库导入模板")
+//    @ApiOperationSupport(order = 5)
+//    @GetMapping("/downloadTemplate")
+//    public void downloadTemplate(HttpServletResponse response) throws IOException {
+//        log.info("dictionaryLibrary.downloadTemplate");
+//        dictionaryLibraryService.downloadTemplate(response);
+//    }
+
+    @ApiOperation("下载举报导入模板")
+    @ApiOperationSupport(order = 10)
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        log.info("StoreMenuPlatformController.downloadTemplate");
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        try {
+            // 从resources/templates目录读取模板文件
+            Resource resource = new ClassPathResource("templates/举报导入模版.xlsx");
+            inputStream = resource.getInputStream();
+
+            // 设置响应头
+            response.reset();
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+
+            String fileName = "举报导入模板.xlsx";
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+            response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + "\";filename*=utf-8''" + encodedFileName);
+
+            // 输出文件流
+            outputStream = response.getOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) > 0) {
+                outputStream.write(buffer, 0, length);
+            }
+            outputStream.flush();
+
+            log.info("举报导入模板下载成功");
+        } catch (Exception e) {
+            log.error("下载举报导入模板失败", e);
+            throw new RuntimeException("下载模板失败:" + e.getMessage());
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                log.error("关闭流失败", e);
+            }
+        }
+    }
+
+
+    @ApiOperation("删除字典库(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 6)
+    @DeleteMapping("/deleteDictionaryLibrary/{id}")
+    public R<Boolean> deleteDictionaryLibrary(@PathVariable Long id) {
+        log.info("dictionaryLibrary.deleteDictionaryLibrary id={}", id);
+        try {
+            int result = dictionaryLibraryService.deleteDictionaryLibrary(id);
+            if (result > 0) {
+                return R.success("删除成功");
+            } else {
+                return R.fail("删除失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictionaryLibrary.deleteDictionaryLibrary error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictionaryLibrary.deleteDictionaryLibrary error", e);
+            return R.fail("删除失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改字典库状态(通过id修改hidden为1)")
+    @ApiOperationSupport(order = 7)
+    @PutMapping("/updateHiddenStatus/{id}")
+    public R<Boolean> updateHiddenStatus(@PathVariable Long id) {
+        log.info("dictionaryLibrary.updateHiddenStatus id={}", id);
+        try {
+            int result = dictionaryLibraryService.updateHiddenStatus(id);
+            if (result > 0) {
+                return R.success("删除成功");
+            } else {
+                return R.fail("删除失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictionaryLibrary.updateHiddenStatus error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictionaryLibrary.updateHiddenStatus error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据dictDetail查询字典库(包含父级或子级树形结构)")
+    @ApiOperationSupport(order = 8)
+    @GetMapping("/selectDictBydictDetail")
+    public R<List<StoreDictionary>> selectDictBydictDetail(@RequestParam String dictDetail) {
+        log.info("dictionaryLibrary.selectDictBydictDetail dictDetail={}", dictDetail);
+        try {
+            // 内部调用queryDictionaryLibraryTree方法
+            List<StoreDictionary> result = dictionaryLibraryService.queryDictionaryLibraryTree(dictDetail);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("dictionaryLibrary.selectDictBydictDetail error", e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+}

+ 158 - 0
alien-store/src/main/java/shop/alien/store/controller/FilterConditionController.java

@@ -0,0 +1,158 @@
+package shop.alien.store.controller;
+
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.store.service.FilterConditionService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+@Api(tags = {"平台-筛选条件管理"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/filterCondition")
+@RequiredArgsConstructor
+public class FilterConditionController {
+
+    private final FilterConditionService filterConditionService;
+
+    @ApiOperation("查询筛选条件(四级树形结构),支持根据dictDetail过滤")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/queryFilterConditionTree")
+    public R<List<StoreDictionary>> queryFilterConditionTree(@RequestParam(required = false) String dictDetail) {
+        log.info("filterCondition.queryFilterConditionTree dictDetail={}", dictDetail);
+        List<StoreDictionary> result = filterConditionService.queryFilterConditionTree(dictDetail);
+        return R.data(result);
+    }
+
+    @ApiOperation("新增筛选条件(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/addFilterCondition")
+    public R<Boolean> addFilterCondition(@RequestBody StoreDictionary storeDictionary) {
+        log.info("storeDictionary.addFilterCondition:{}", storeDictionary);
+        try {
+            boolean result = filterConditionService.addFilterCondition(storeDictionary);
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("filterCondition.addFilterCondition error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("filterCondition.addFilterCondition error", e);
+            return R.fail("新增失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改筛选条件(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 3)
+    @PutMapping("/updateFilterCondition")
+    public R<Boolean> updateFilterCondition(@RequestBody StoreDictionary storeDictionary) {
+        log.info("storeDictionary.updateFilterCondition:{}", storeDictionary);
+        try {
+            boolean result = filterConditionService.updateFilterCondition(storeDictionary);
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("storeDictionary.updateFilterCondition error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("storeDictionary.updateFilterCondition error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量导入筛选条件")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/import")
+    public R<String> importFilterCondition(@RequestParam("file") MultipartFile file) {
+        log.info("filterCondition.importFilterCondition fileName={}", 
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            return filterConditionService.importFilterCondition(file);
+        } catch (Exception e) {
+            log.error("filterCondition.importFilterCondition error", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+//    @ApiOperation("下载筛选条件导入模板")
+//    @ApiOperationSupport(order = 5)
+//    @GetMapping("/downloadTemplate")
+//    public void downloadTemplate(HttpServletResponse response) throws IOException {
+//        log.info("filterCondition.downloadTemplate");
+//        filterConditionService.downloadTemplate(response);
+//    }
+
+    @ApiOperation("下载筛选条件导入模板")
+    @ApiOperationSupport(order = 10)
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        log.info("StoreMenuPlatformController.downloadTemplate");
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        try {
+            // 从resources/templates目录读取模板文件
+            Resource resource = new ClassPathResource("templates/筛选条件导入模版.xlsx");
+            inputStream = resource.getInputStream();
+
+            // 设置响应头
+            response.reset();
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+
+            String fileName = "筛选条件导入模板.xlsx";
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+            response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + "\";filename*=utf-8''" + encodedFileName);
+
+            // 输出文件流
+            outputStream = response.getOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) > 0) {
+                outputStream.write(buffer, 0, length);
+            }
+            outputStream.flush();
+
+            log.info("筛选条件导入模板下载成功");
+        } catch (Exception e) {
+            log.error("下载筛选条件导入模板失败", e);
+            throw new RuntimeException("下载模板失败:" + e.getMessage());
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                log.error("关闭流失败", e);
+            }
+        }
+    }
+
+}
+

+ 9 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeCouponController.java

@@ -6,6 +6,7 @@ import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.EssentialHolidayComparison;
 import shop.alien.entity.store.LifeCoupon;
@@ -181,4 +182,12 @@ public class LifeCouponController {
         }
         return R.fail("失败");
     }
+
+    @ApiOperation("导入假期管理")
+    //@ApiImplicitParams({@ApiImplicitParam(name = "file", value = "Excel文件", dataType = "MultipartFile", paramType = "form", required = true)})
+    @PostMapping("/importHoliday")
+    public R<String> importHoliday(MultipartFile file) {
+        log.info("LifeCouponController.importHoliday fileName={}", file.getOriginalFilename());
+        return lifeCouponService.importHolidayFromExcel(file);
+    }
 }

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

@@ -1,10 +1,7 @@
 package shop.alien.store.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.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;

+ 117 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeSysDeptController.java

@@ -0,0 +1,117 @@
+package shop.alien.store.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.LifeSysDept;
+import shop.alien.entity.store.dto.LifeSysDeptAddDto;
+import shop.alien.entity.store.dto.LifeSysDeptSortDto;
+import shop.alien.entity.store.dto.LifeSysDeptUpdateDto;
+import shop.alien.entity.store.vo.LifeSysDeptVo;
+import shop.alien.entity.store.vo.LifeSysVo;
+import shop.alien.store.service.LifeSysDeptService;
+
+import java.util.List;
+
+/**
+ * 部门表 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"数据中台-部门管理"})
+@ApiSort(2)
+@CrossOrigin
+@RestController
+@RequestMapping("/sys/dept")
+@RequiredArgsConstructor
+public class LifeSysDeptController {
+
+    private final LifeSysDeptService lifeSysDeptService;
+
+    @ApiOperation("新增部门")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "parentId", value = "父部门ID(0表示根部门)", dataType = "Long", paramType = "body"),
+            @ApiImplicitParam(name = "deptName", value = "部门名称", dataType = "String", paramType = "body", required = true),
+            @ApiImplicitParam(name = "deptSort", value = "显示顺序", dataType = "Integer", paramType = "body"),
+            @ApiImplicitParam(name = "leader", value = "部门负责人", dataType = "Integer", paramType = "body"),
+            @ApiImplicitParam(name = "phone", value = "联系电话", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "email", value = "邮箱", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "status", value = "部门状态(0正常 1停用)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "description", value = "部门描述", dataType = "String", paramType = "body"),
+    })
+    @PostMapping(value = "/addDept")
+    public R<LifeSysDept> addDept(@RequestBody LifeSysDeptAddDto addDto) {
+        log.info("LifeSysDeptController.addDept => deptName={}, parentId={}, deptSort={}, leader={}, phone={}, email={}, status={}, description={}",
+                addDto.getDeptName(), addDto.getParentId(), addDto.getDeptSort(),
+                addDto.getLeader(), addDto.getPhone(), addDto.getEmail(), addDto.getStatus(), addDto.getDescription());
+        return lifeSysDeptService.addDept(addDto);
+    }
+
+    @ApiOperation("更新一级部门排序")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "deptId", value = "部门ID", dataType = "Long", paramType = "body", required = true),
+            @ApiImplicitParam(name = "newSort", value = "目标排序位置(新排序值)", dataType = "Integer", paramType = "body", required = true),
+    })
+    @PostMapping(value = "/updateFirstLevelDeptSort")
+    public R<Boolean> updateFirstLevelDeptSort(@RequestBody LifeSysDeptSortDto sortDto) {
+        log.info("LifeSysDeptController.updateFirstLevelDeptSort => deptId={}, newSort={}", sortDto.getDeptId(), sortDto.getNewSort());
+        return lifeSysDeptService.updateFirstLevelDeptSort(sortDto);
+    }
+
+    @ApiOperation("编辑部门")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "deptId", value = "部门ID", dataType = "Long", paramType = "body", required = true),
+            @ApiImplicitParam(name = "deptName", value = "部门名称", dataType = "String", paramType = "body", required = true),
+            @ApiImplicitParam(name = "description", value = "部门描述", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "leader", value = "部门负责人", dataType = "Integer", paramType = "body"),
+    })
+    @PostMapping(value = "/updateDept")
+    public R<LifeSysDept> updateDept(@RequestBody LifeSysDeptUpdateDto updateDto) {
+        log.info("LifeSysDeptController.updateDept => deptId={}, deptName={}, description={}, leader={}",
+                updateDto.getDeptId(), updateDto.getDeptName(), updateDto.getDescription(), updateDto.getLeader());
+        return lifeSysDeptService.updateDept(updateDto);
+    }
+
+    @ApiOperation("搜索部门(根据部门名称模糊查询)")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "deptName", value = "部门名称(支持模糊查询,为空时返回所有未删除的部门)", dataType = "String", paramType = "query", required = false),
+    })
+    @GetMapping(value = "/searchDept")
+    public R<List<LifeSysDeptVo>> searchDept(@RequestParam(required = false) String deptName) {
+        log.info("LifeSysDeptController.searchDept => deptName={}", deptName);
+        return lifeSysDeptService.searchDeptByName(deptName);
+    }
+
+    @ApiOperation("删除部门")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "deptId", value = "部门ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @DeleteMapping(value = "/deleteDept")
+    public R<Boolean> deleteDept(@RequestParam Long deptId) {
+        log.info("LifeSysDeptController.deleteDept => deptId={}", deptId);
+        return lifeSysDeptService.deleteDept(deptId);
+    }
+
+    @ApiOperation("查看部门成员")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "当前页码", dataType = "Integer", paramType = "query", required = false, defaultValue = "1"),
+            @ApiImplicitParam(name = "pageSize", value = "每页显示数量", dataType = "Integer", paramType = "query", required = false, defaultValue = "10"),
+            @ApiImplicitParam(name = "deptId", value = "部门ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @GetMapping(value = "/viewMembers")
+    public R<List<LifeSysVo>> viewMembers(@RequestParam(defaultValue = "1", required = false) Integer pageNum, @RequestParam(defaultValue = "10", required = false) Integer pageSize, @RequestParam Long deptId) {
+        log.info("LifeSysDeptController.viewMembers => pageNum={}, pageSize={}, deptId={}", pageNum, pageSize, deptId);
+        return lifeSysDeptService.viewMembers(pageNum, pageSize, deptId);
+    }
+}
+

+ 135 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeSysMenuController.java

@@ -0,0 +1,135 @@
+package shop.alien.store.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.LifeSysMenu;
+import shop.alien.store.service.LifeSysMenuService;
+
+import java.util.List;
+
+/**
+ * 菜单权限表 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"数据中台-菜单管理"})
+@ApiSort(3)
+@CrossOrigin
+@RestController
+@RequestMapping("/sys/menu")
+@RequiredArgsConstructor
+public class LifeSysMenuController {
+
+    private final LifeSysMenuService lifeSysMenuService;
+
+    @ApiOperation("新增菜单")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuName", value = "菜单名称", dataType = "String", paramType = "body", required = true),
+            @ApiImplicitParam(name = "parentId", value = "父菜单ID(0表示根菜单)", dataType = "Long", paramType = "body"),
+            @ApiImplicitParam(name = "menuType", value = "菜单类型(M目录 C菜单 F按钮)", dataType = "String", paramType = "body", required = true),
+            @ApiImplicitParam(name = "menuSort", value = "显示顺序", dataType = "Integer", paramType = "body"),
+            @ApiImplicitParam(name = "path", value = "路由地址", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "component", value = "组件路径", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "perms", value = "权限标识(如:sys:user:list)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "icon", value = "菜单图标", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "status", value = "菜单状态(0正常 1停用)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "visible", value = "是否显示(0显示 1隐藏)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "isFrame", value = "是否为外链(0是 1否)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "isCache", value = "是否缓存(0缓存 1不缓存)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "remark", value = "备注", dataType = "String", paramType = "body"),
+    })
+    @PostMapping(value = "/addMenu")
+    public R<LifeSysMenu> addMenu(@RequestBody LifeSysMenu menu) {
+        log.info("LifeSysMenuController.addMenu => menuName={}, parentId={}, menuType={}, menuSort={}, path={}, component={}, perms={}, icon={}, status={}, visible={}, isFrame={}, isCache={}, remark={}",
+                menu.getMenuName(), menu.getParentId(), menu.getMenuType(), menu.getMenuSort(),
+                menu.getPath(), menu.getComponent(), menu.getPerms(), menu.getIcon(),
+                menu.getStatus(), menu.getVisible(), menu.getIsFrame(), menu.getIsCache(), menu.getRemark());
+        return lifeSysMenuService.addMenu(menu);
+    }
+
+    @ApiOperation("编辑菜单")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "body", required = true),
+            @ApiImplicitParam(name = "menuName", value = "菜单名称", dataType = "String", paramType = "body", required = true),
+            @ApiImplicitParam(name = "menuType", value = "菜单类型(M目录 C菜单 F按钮)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "menuSort", value = "显示顺序", dataType = "Integer", paramType = "body"),
+            @ApiImplicitParam(name = "path", value = "路由地址", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "component", value = "组件路径", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "perms", value = "权限标识(如:sys:user:list)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "icon", value = "菜单图标", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "status", value = "菜单状态(0正常 1停用)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "visible", value = "是否显示(0显示 1隐藏)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "isFrame", value = "是否为外链(0是 1否)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "isCache", value = "是否缓存(0缓存 1不缓存)", dataType = "String", paramType = "body"),
+            @ApiImplicitParam(name = "remark", value = "备注", dataType = "String", paramType = "body"),
+    })
+    @PostMapping(value = "/updateMenu")
+    public R<LifeSysMenu> updateMenu(@RequestBody LifeSysMenu menu) {
+        log.info("LifeSysMenuController.updateMenu => menuId={}, menuName={}, menuType={}, menuSort={}, path={}, component={}, perms={}, icon={}, status={}, visible={}, isFrame={}, isCache={}, remark={}",
+                menu.getMenuId(), menu.getMenuName(), menu.getMenuType(), menu.getMenuSort(),
+                menu.getPath(), menu.getComponent(), menu.getPerms(), menu.getIcon(),
+                menu.getStatus(), menu.getVisible(), menu.getIsFrame(), menu.getIsCache(), menu.getRemark());
+        return lifeSysMenuService.updateMenu(menu);
+    }
+
+    @ApiOperation("删除菜单")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @DeleteMapping(value = "/deleteMenu")
+    public R<Boolean> deleteMenu(@RequestParam Long menuId) {
+        log.info("LifeSysMenuController.deleteMenu => menuId={}", menuId);
+        return lifeSysMenuService.deleteMenu(menuId);
+    }
+
+    @ApiOperation("根据ID查询菜单")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @GetMapping(value = "/getMenuById")
+    public R<LifeSysMenu> getMenuById(@RequestParam Long menuId) {
+        log.info("LifeSysMenuController.getMenuById => menuId={}", menuId);
+        return lifeSysMenuService.getMenuById(menuId);
+    }
+
+    @ApiOperation("查询菜单树形结构")
+    @ApiOperationSupport(order = 5)
+    @GetMapping(value = "/getMenuTree")
+    public R<List<LifeSysMenu>> getMenuTree() {
+        log.info("LifeSysMenuController.getMenuTree");
+        return lifeSysMenuService.getMenuTree();
+    }
+
+    @ApiOperation("根据父菜单ID查询子菜单列表")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "parentId", value = "父菜单ID(0表示根菜单,为空时默认查询根菜单)", dataType = "Long", paramType = "query", required = false),
+    })
+    @GetMapping(value = "/getMenuListByParentId")
+    public R<List<LifeSysMenu>> getMenuListByParentId(@RequestParam(required = false) Long parentId) {
+        log.info("LifeSysMenuController.getMenuListByParentId => parentId={}", parentId);
+        return lifeSysMenuService.getMenuListByParentId(parentId);
+    }
+
+    @ApiOperation("根据用户ID查询用户菜单树形结构")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "ifTree", value = "是否返回树形结构(0否 1是)", dataType = "Long", paramType = "query")
+    })
+    @GetMapping(value = "/getMenuByUserId")
+    public R<List<LifeSysMenu>> getMenuByUserId(@RequestParam Long userId, @RequestParam(required = false, defaultValue = "0") Long ifTree) {
+        log.info("LifeSysMenuController.getMenuByUserId => userId={}, ifTree={}", userId, ifTree);
+        return lifeSysMenuService.getMenuByUserId(userId,ifTree);
+    }
+}
+

+ 247 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeSysRoleController.java

@@ -0,0 +1,247 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+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.entity.store.LifeSysRole;
+import shop.alien.entity.store.vo.MenuWithRoleVo;
+import shop.alien.store.service.LifeSysRoleMenuService;
+import shop.alien.store.service.LifeSysRoleService;
+
+import java.util.List;
+
+/**
+ * 平台角色信息表 前端控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"平台角色管理"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/sys/role")
+@RequiredArgsConstructor
+public class LifeSysRoleController {
+
+    private final LifeSysRoleService lifeSysRoleService;
+    private final LifeSysRoleMenuService lifeSysRoleMenuService;
+
+    @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 = "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<LifeSysRole>> getRolePage(
+            @RequestParam(name = "page", defaultValue = "1") int page,
+            @RequestParam(name = "size", defaultValue = "10") int size,
+            @RequestParam(value = "roleName", required = false) String roleName,
+            @RequestParam(value = "description", required = false) String description,
+            @RequestParam(value = "status", required = false) String status) {
+        log.info("LifeSysRoleController.getRolePage?page={}, size={}, roleName={}, roleKey={}, status={}", 
+                page, size, roleName, description, status);
+        IPage<LifeSysRole> rolePage = lifeSysRoleService.getRolePage(page, size, roleName, description, status);
+        return R.data(rolePage);
+    }
+
+    @ApiOperation("根据ID查询角色详情")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "path", required = true)
+    })
+    @GetMapping("/getRoleById")
+    public R<LifeSysRole> getRoleById(@RequestParam("roleId") Long roleId) {
+        log.info("LifeSysRoleController.getRoleById?roleId={}", roleId);
+        LifeSysRole role = lifeSysRoleService.getRoleById(roleId);
+        if (role == null) {
+            return R.fail("未找到该角色信息");
+        }
+        return R.data(role);
+    }
+
+    @ApiOperation("新增角色")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/saveRole")
+    public R<String> saveRole(@RequestBody LifeSysRole role) {
+        log.info("LifeSysRoleController.saveRole?role={}", role);
+        boolean result = lifeSysRoleService.saveRole(role);
+        if (result) {
+            return R.success("新增成功");
+        }
+        return R.fail("新增失败");
+    }
+
+    @ApiOperation("修改角色")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/updateRole")
+    public R<String> updateRole(@RequestBody LifeSysRole role) {
+        log.info("LifeSysRoleController.updateRole?role={}", role);
+        boolean result = lifeSysRoleService.updateRole(role);
+        if (result) {
+            return R.success("修改成功");
+        }
+        return R.fail("修改失败");
+    }
+
+    @ApiOperation("删除角色")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "path", required = true)
+    })
+    @DeleteMapping("/deleteRole")
+    public R<String> deleteRole(@RequestParam("roleId") Long roleId) {
+        log.info("LifeSysRoleController.deleteRole?roleId={}", roleId);
+        boolean result = lifeSysRoleService.deleteRole(roleId);
+        if (result) {
+            return R.success("删除成功");
+        }
+        return R.fail("删除失败");
+    }
+
+    @ApiOperation("修改角色状态")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "path", required = true),
+            @ApiImplicitParam(name = "status", value = "角色状态(0正常 1停用)", dataType = "String", paramType = "query", required = true)
+    })
+    @PutMapping("/updateStatus")
+    public R<String> updateStatus(
+            @RequestParam("roleId") Long roleId,
+            @RequestParam("status") String status) {
+        log.info("LifeSysRoleController.updateStatus?roleId={}, status={}", roleId, status);
+        boolean result = lifeSysRoleService.updateStatus(roleId, status);
+        if (result) {
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @ApiOperation("查询所有正常状态的角色列表")
+    @ApiOperationSupport(order = 7)
+    @GetMapping("/getAllNormalRoles")
+    public R<List<LifeSysRole>> getAllNormalRoles() {
+        log.info("LifeSysRoleController.getAllNormalRoles");
+        List<LifeSysRole> roles = lifeSysRoleService.getAllNormalRoles();
+        return R.data(roles);
+    }
+
+    @ApiOperation("根据角色ID查询所有权限(区分已拥有和未拥有的权限)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @GetMapping(value = "/getMenusWithRoleStatus")
+    public R<List<MenuWithRoleVo>> getMenusWithRoleStatus(@RequestParam Long roleId) {
+        log.info("LifeSysRoleMenuController.getMenusWithRoleStatus => roleId={}", roleId);
+        return lifeSysRoleMenuService.getMenusWithRoleStatus(roleId);
+    }
+
+    @ApiOperation("批量为角色分配菜单")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "body", required = true),
+            @ApiImplicitParam(name = "menuIds", value = "菜单ID列表", dataType = "List", paramType = "body", required = true),
+    })
+    @PostMapping(value = "/assignMenus")
+    public R<Boolean> assignMenus(@RequestBody AssignMenusDto assignMenusDto) {
+        log.info("LifeSysRoleMenuController.assignMenus => roleId={}, menuIds={}",
+                assignMenusDto.getRoleId(), assignMenusDto.getMenuIds());
+        return lifeSysRoleMenuService.assignMenus(assignMenusDto.getRoleId(), assignMenusDto.getMenuIds());
+    }
+
+    @ApiOperation("移除角色的所有菜单")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @DeleteMapping(value = "/removeAllMenus")
+    public R<Boolean> removeAllMenus(@RequestParam Long roleId) {
+        log.info("LifeSysRoleMenuController.removeAllMenus => roleId={}", roleId);
+        return lifeSysRoleMenuService.removeAllMenus(roleId);
+    }
+
+
+    @ApiOperation("根据角色ID获取菜单ID列表")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @GetMapping(value = "/getMenuIdsByRoleId")
+    public R<List<Long>> getMenuIdsByRoleId(@RequestParam Long roleId) {
+        log.info("LifeSysRoleMenuController.getMenuIdsByRoleId => roleId={}", roleId);
+        return lifeSysRoleMenuService.getMenuIdsByRoleId(roleId);
+    }
+
+    @ApiOperation("为角色添加单个菜单")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "body", required = true),
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "body", required = true),
+    })
+    @PostMapping(value = "/addMenu")
+    public R<Boolean> addMenu(@RequestBody AddMenuDto addMenuDto) {
+        log.info("LifeSysRoleMenuController.addMenu => roleId={}, menuId={}",
+                addMenuDto.getRoleId(), addMenuDto.getMenuId());
+        return lifeSysRoleMenuService.addMenu(addMenuDto.getRoleId(), addMenuDto.getMenuId());
+    }
+
+    @ApiOperation("移除角色的单个菜单")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roleId", value = "角色ID", dataType = "Long", paramType = "query", required = true),
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @DeleteMapping(value = "/removeMenu")
+    public R<Boolean> removeMenu(@RequestParam Long roleId, @RequestParam Long menuId) {
+        log.info("LifeSysRoleMenuController.removeMenu => roleId={}, menuId={}", roleId, menuId);
+        return lifeSysRoleMenuService.removeMenu(roleId, menuId);
+    }
+
+    @ApiOperation("根据菜单ID获取角色ID列表")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "menuId", value = "菜单ID", dataType = "Long", paramType = "query", required = true),
+    })
+    @GetMapping(value = "/getRoleIdsByMenuId")
+    public R<List<Long>> getRoleIdsByMenuId(@RequestParam Long menuId) {
+        log.info("LifeSysRoleMenuController.getRoleIdsByMenuId => menuId={}", menuId);
+        return lifeSysRoleMenuService.getRoleIdsByMenuId(menuId);
+    }
+
+    /**
+     * 批量分配菜单请求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;
+    }
+}
+

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

@@ -195,12 +195,13 @@ public class LifeUserDynamicsController {
     public R<IPage<LifeUserDynamicsVo>> getDynamicsList(@RequestParam(defaultValue = "1") Integer page,
                                                         @RequestParam(defaultValue = "10") Integer size,
                                                         @RequestParam(required = false) String userName,
+                                                        @RequestParam(required = false) String storeName,
                                                         @RequestParam(required = false) String userType,
                                                         @RequestParam(defaultValue = "0",required = false) Integer dynamicsType,
                                                         @RequestParam(required = false) String releaseStartTime,
                                                         @RequestParam(required = false) String releaseEndTime) {
         log.info("LifeUserDynamicsController.getDynamicsList?page={}&size={}&nickName={}&userType={}&dynamicsType={}", page, size, userName, userType, dynamicsType);
-        List<LifeUserDynamicsVo> stores = lifeUserDynamicsService.getDynamicsList(page, size, userName, userType, dynamicsType, releaseStartTime, releaseEndTime);
+        List<LifeUserDynamicsVo> stores = lifeUserDynamicsService.getDynamicsList(page, size, userName, userType, dynamicsType, releaseStartTime, releaseEndTime, storeName);
         return R.data(ListToPage.setPage(stores, page, size));
     }
 

+ 83 - 0
alien-store/src/main/java/shop/alien/store/controller/OperationLogController.java

@@ -0,0 +1,83 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.RequiredArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.OperationLog;
+import shop.alien.mapper.OperationLogMapper;
+import shop.alien.store.annotation.ChangeRecordLog;
+import shop.alien.store.enums.OperationType;
+
+import java.util.Date;
+
+/**
+ * 操作日志Controller
+ *
+ * @author ssk
+ * @since 2025-12-09
+ */
+@Api(tags = {"操作日志管理"})
+@RestController
+@RequestMapping("/operation/log")
+@RequiredArgsConstructor
+public class OperationLogController {
+
+    private final OperationLogMapper operationLogMapper;
+
+    @ApiOperation("查询操作日志列表")
+    @GetMapping("/list")
+    @ChangeRecordLog(module = "操作日志管理", type = OperationType.QUERY, content = "查询了操作日志列表")
+    public R<IPage<OperationLog>> list(
+            @ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNum,
+            @ApiParam("每页数量") @RequestParam(defaultValue = "10") Integer pageSize,
+            @ApiParam("姓名") @RequestParam(required = false) String name,
+            @ApiParam("开始时间") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
+            @ApiParam("结束时间") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
+            @ApiParam("用户类型") @RequestParam(required = false) String userType) {
+        
+        Page<OperationLog> page = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<OperationLog> wrapper = new LambdaQueryWrapper<>();
+        
+        wrapper.like(name != null, OperationLog::getName, name)
+               .ge(startTime != null, OperationLog::getOperationTime, startTime)
+               .le(endTime != null, OperationLog::getOperationTime, endTime)
+               .eq(userType != null, OperationLog::getUserType, userType)
+               .orderByDesc(OperationLog::getOperationTime);
+        
+        IPage<OperationLog> result = operationLogMapper.selectPage(page, wrapper);
+        return R.data(result);
+    }
+
+    @ApiOperation("根据ID查询操作日志详情")
+    @GetMapping("/{id}")
+    @ChangeRecordLog(module = "操作日志管理", type = OperationType.QUERY, content = "查询了操作日志详情,id=#{#id}")
+    public R<OperationLog> getById(@ApiParam("日志ID") @PathVariable Long id) {
+        OperationLog operationLog = operationLogMapper.selectById(id);
+        return R.data(operationLog);
+    }
+
+    @ApiOperation("直接插入操作日志")
+    @PostMapping("/insert")
+    public R insertOperationLog(@RequestBody OperationLog operationLog) {
+        try {
+            // 如果操作时间为空,设置为当前时间
+            if (operationLog.getOperationTime() == null) {
+                operationLog.setOperationTime(new Date());
+            }
+            
+            // 直接保存到数据库
+            operationLogMapper.insert(operationLog);
+            return R.data("操作日志保存成功");
+        } catch (Exception e) {
+            return R.fail("操作日志保存失败:" + e.getMessage());
+        }
+    }
+}
+

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

@@ -26,12 +26,12 @@ public class PlatformBusinessSectionController {
 
     private final PlatformBusinessSectionService platformBusinessSectionService;
 
-    @ApiOperation("查询经营种类(三级树形结构)")
+    @ApiOperation("查询经营种类(三级树形结构),支持根据dictDetail过滤")
     @ApiOperationSupport(order = 1)
     @GetMapping("/queryBusinessSectionTree")
-    public R<List<StoreDictionary>> queryBusinessSectionTree() {
-        log.info("platformBusinessSection.queryBusinessSectionTree");
-        List<StoreDictionary> result = platformBusinessSectionService.queryBusinessSectionTree();
+    public R<List<StoreDictionary>> queryBusinessSectionTree(@RequestParam(required = false) String dictDetail) {
+        log.info("platformBusinessSection.queryBusinessSectionTree dictDetail={}", dictDetail);
+        List<StoreDictionary> result = platformBusinessSectionService.queryBusinessSectionTree(dictDetail);
         return R.data(result);
     }
 

+ 238 - 0
alien-store/src/main/java/shop/alien/store/controller/ProtocolManagementController.java

@@ -0,0 +1,238 @@
+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 org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartRequest;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.ProtocolManagement;
+import shop.alien.store.service.ProtocolManagementService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 协议管理表 前端控制器
+ *
+ * @author alien
+ * @since 2025-12-09
+ */
+@Slf4j
+@Api(tags = {"协议管理"})
+@ApiSort(100)
+@CrossOrigin
+@RestController
+@RequestMapping("/protocolManagement")
+@RequiredArgsConstructor
+public class ProtocolManagementController {
+
+    private final ProtocolManagementService protocolManagementService;
+
+    @ApiOperation("协议列表-分页查询")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "protocolFileName", value = "协议名称", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "displayPosition", value = "展示位置", dataType = "String", paramType = "query")
+    })
+    @GetMapping("/getPage")
+    public R<IPage<ProtocolManagement>> getPage(
+            @RequestParam("pageNum") Integer pageNum,
+            @RequestParam("pageSize") Integer pageSize,
+            @RequestParam(value = "protocolFileName", required = false) String protocolFileName,
+            @RequestParam(value = "displayPosition", required = false) String displayPosition) {
+        log.info("ProtocolManagementController.getPage?pageNum={}&pageSize={}&protocolFileName={}&displayPosition={}", 
+                pageNum, pageSize, protocolFileName, displayPosition);
+        
+        try {
+            IPage<ProtocolManagement> page = protocolManagementService.getProtocolPage(pageNum, pageSize, protocolFileName, displayPosition);
+            return R.data(page);
+        } catch (Exception e) {
+            log.error("查询协议列表失败", e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("协议详情-根据ID查询")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "协议ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getById")
+    public void getById(@RequestParam("id") Integer id, HttpServletResponse response) throws IOException {
+        log.info("ProtocolManagementController.getById?id={}", id);
+
+        try {
+            protocolManagementService.getProtocolById(id, response);
+        } catch (IllegalArgumentException e) {
+            log.error("查询协议详情失败", e);
+            if (!response.isCommitted()) {
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
+            }
+        } catch (Exception e) {
+            log.error("查询协议详情失败", e);
+            if (!response.isCommitted()) {
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "查询失败: " + e.getMessage());
+            }
+        }
+    }
+
+    @ApiOperation("新增协议")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "protocolFileName", value = "协议名称", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "displayPosition", value = "展示位置", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "protocolFilePath", value = "协议文件存储路径", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "createdUserId", value = "创建人ID", dataType = "Integer", paramType = "query")
+
+    })
+    @PostMapping("/add")
+    public R<Boolean> add(
+            @RequestParam("protocolFileName") String protocolFileName,
+            @RequestParam(value = "displayPosition") String displayPosition,
+            @RequestParam(value = "createdUserId", required = false) Integer createdUserId,
+            MultipartFile multipartFile) {
+        log.info("ProtocolManagementController.add?protocolFileName={}&displayPosition={}&createdUserId={}",
+                protocolFileName, displayPosition, createdUserId);
+        
+        try {
+            ProtocolManagement protocolManagement = new ProtocolManagement();
+            protocolManagement.setProtocolFileName(protocolFileName);
+            protocolManagement.setDisplayPosition(displayPosition);
+            
+            // 当createdUserId不为空时才设置值
+            if (createdUserId != null) {
+                protocolManagement.setCreatedUserId(createdUserId);
+            }
+            
+            boolean result = protocolManagementService.addProtocol(multipartFile, protocolManagement);
+            
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("新增协议失败", e);
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("新增协议失败", e);
+            return R.fail("新增失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改协议")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "协议ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "protocolFileName", value = "协议名称", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "displayPosition", value = "展示位置", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "protocolFilePath", value = "协议文件存储路径", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "updatedUserId", value = "修改人ID", dataType = "Integer", paramType = "query")
+    })
+    @PostMapping("/update")
+    public R<Boolean> update(
+            @RequestParam("id") Integer id,
+            @RequestParam(value = "protocolFileName", required = false) String protocolFileName,
+            @RequestParam(value = "displayPosition", required = false) String displayPosition,
+            @RequestParam(value = "protocolFilePath", required = false) String protocolFilePath,
+            @RequestParam(value = "updatedUserId", required = false) Integer updatedUserId,
+            MultipartRequest multipartRequest) {
+        log.info("ProtocolManagementController.update?id={}&protocolFileName={}&displayPosition={}&protocolFilePath={}&updatedUserId={}", 
+                id, protocolFileName, displayPosition, protocolFilePath, updatedUserId);
+        
+        try {
+            ProtocolManagement protocolManagement = new ProtocolManagement();
+            protocolManagement.setId(id);
+            protocolManagement.setProtocolFileName(protocolFileName);
+            protocolManagement.setDisplayPosition(displayPosition);
+            protocolManagement.setProtocolFilePath(protocolFilePath);
+            protocolManagement.setUpdatedUserId(updatedUserId);
+            
+            boolean result = protocolManagementService.updateProtocol(multipartRequest, protocolManagement);
+            
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("修改协议失败", e);
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("修改协议失败", e);
+            return R.fail("修改失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除协议")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "协议ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/delete")
+    public R<Boolean> delete(@RequestParam("id") Integer id) {
+        log.info("ProtocolManagementController.delete?id={}", id);
+        
+        try {
+            boolean result = protocolManagementService.deleteProtocol(id);
+            
+            if (result) {
+                return R.success("删除成功");
+            } else {
+                return R.fail("删除失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("删除协议失败", e);
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("删除协议失败", e);
+            return R.fail("删除失败: " + e.getMessage());
+        }
+    }
+
+//    @ApiOperation("批量删除协议")
+//    @ApiImplicitParams({
+//            @ApiImplicitParam(name = "ids", value = "协议ID列表(逗号分隔)", dataType = "String", paramType = "query", required = true, example = "1,2,3")
+//    })
+//    @PostMapping("/batchDelete")
+//    public R<Boolean> batchDelete(@RequestParam("ids") String ids) {
+//        log.info("ProtocolManagementController.batchDelete?ids={}", ids);
+//
+//        try {
+//            boolean result = protocolManagementService.batchDeleteProtocol(ids);
+//
+//            if (result) {
+//                return R.success("批量删除成功");
+//            } else {
+//                return R.fail("批量删除失败");
+//            }
+//        } catch (IllegalArgumentException e) {
+//            log.error("批量删除协议失败", e);
+//            return R.fail(e.getMessage());
+//        } catch (Exception e) {
+//            log.error("批量删除协议失败", e);
+//            return R.fail("批量删除失败: " + e.getMessage());
+//        }
+//    }
+
+    @ApiOperation("校验文件名是否重复")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "protocolFileName", value = "协议文件名", dataType = "String", paramType = "query", required = true)
+    })
+    @GetMapping("/checkFileNameDuplicate")
+    public R<Boolean> checkFileNameDuplicate(@RequestParam("protocolFileName") String protocolFileName) {
+        log.info("ProtocolManagementController.checkFileNameDuplicate?protocolFileName={}", protocolFileName);
+
+        try {
+            Boolean result = protocolManagementService.checkFileNameDuplicate(protocolFileName);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            log.error("校验文件名失败", e);
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("校验文件名失败", e);
+            return R.fail("校验失败: " + e.getMessage());
+        }
+    }
+}
+

+ 61 - 5
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -252,7 +252,9 @@ public class StoreInfoController {
             @ApiImplicitParam(name = "weidu", value = "weidu", dataType = "String", paramType = "query"),
             @ApiImplicitParam(name = "renewContractStatus", value = "门店续签合同状态", dataType = "String", paramType = "query"),
             @ApiImplicitParam(name = "foodLicenceStatus", value = "门店经营许可证状态", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "foodLicenceWhetherExpiredStatus", value = "门店经营许可证是否过期状态", dataType = "String", paramType = "query")
+            @ApiImplicitParam(name = "foodLicenceWhetherExpiredStatus", value = "门店经营许可证是否过期状态", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "startApplyTime", value = "开始时间", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endApplyTime", value = "结束时间", dataType = "String", paramType = "query")
     })
     @GetMapping("/getNewStorePage")
     public R<IPage<StoreInfoVo>> getNewStorePage(
@@ -271,7 +273,9 @@ public class StoreInfoController {
             @RequestParam(required = false) String weidu,
             @RequestParam(required = false) String renewContractStatus,
             @RequestParam(required = false) String foodLicenceStatus,
-            @RequestParam(required = false) String foodLicenceWhetherExpiredStatus)
+            @RequestParam(required = false) String foodLicenceWhetherExpiredStatus,
+            @RequestParam(required = false) String startApplyTime,
+            @RequestParam(required = false) String endApplyTime)
     {
         log.info("StoreInfoController.getStoresPage?pageNum={}," +
                         "pageSize={},storeName={},storeContact={}," +
@@ -279,12 +283,12 @@ public class StoreInfoController {
                         "storeApplicationStatus={},storeStatus={}," +
                         "businessSection={},jingdu={},weidu={}," +
                         "renewContractStatus={},foodLicenceStatus={}" +
-                        ",foodLicenceWhetherExpiredStatus={}",
+                        ",foodLicenceWhetherExpiredStatus={},startApplyTime={},endApplyTime={}",
                 pageNum, pageSize, storeName, storeContact,
                 id, storePhone, storeType, expiredState, storeApplicationStatus,
                 storeStatus, businessSection, jingdu, weidu, renewContractStatus,
-                foodLicenceStatus, foodLicenceWhetherExpiredStatus);
-        return R.data(storeInfoService.getNewStorePage(pageNum, pageSize, storeName, storeContact, id, storePhone, storeType, expiredState, storeApplicationStatus, storeStatus, businessSection, jingdu, weidu, renewContractStatus, foodLicenceStatus, foodLicenceWhetherExpiredStatus));
+                foodLicenceStatus, foodLicenceWhetherExpiredStatus, startApplyTime, endApplyTime);
+        return R.data(storeInfoService.getNewStorePage(pageNum, pageSize, storeName, storeContact, id, storePhone, storeType, expiredState, storeApplicationStatus, storeStatus, businessSection, jingdu, weidu, renewContractStatus, foodLicenceStatus, foodLicenceWhetherExpiredStatus, startApplyTime, endApplyTime));
     }
 
     /**
@@ -421,6 +425,25 @@ public class StoreInfoController {
         return R.success("审批完成");
     }
 
+    @ApiOperation(value = "web端人工复核店铺")
+    @ApiOperationSupport(order = 24)
+    @PostMapping("/manualReview")
+    public R<Boolean> manualReview(@RequestBody StoreInfoDto storeInfoDto) {
+        log.info("StoreInfoController.manualReview?storeInfoDto={}", storeInfoDto);
+        try {
+            boolean success = storeInfoService.manualReview(storeInfoDto);
+            if (success) {
+                return R.success("人工复核保存成功");
+            }
+            return R.fail("人工复核保存失败");
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreInfoController.manualReview ERROR", e);
+            return R.fail("人工复核失败");
+        }
+    }
+
     @ApiOperation(value = "中台web端获取店铺明细详情")
     @ApiOperationSupport(order = 21)
     @GetMapping("/getNewStoreDetail")
@@ -1103,6 +1126,39 @@ public class StoreInfoController {
     }
 
 
+    @ApiOperation(value = "证照查询")
+    @ApiOperationSupport(order = 23)
+    @GetMapping("/getStoreLicenseList")
+    @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 = "storeTel", value = "门店电话", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "businessSection", value = "经营板块", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "imgType", value = "证照图片类型(14:营业执照;24/25:食品经营许可证;31/32:娱乐经营许可证)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "states", value = "证照状态", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "startSubmitDate", value = "提交开始时间(yyyy-MM-dd HH:mm:ss)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endSubmitDate", value = "提交结束时间(yyyy-MM-dd HH:mm:ss)", dataType = "String", paramType = "query")
+    })
+    public R<IPage<StoreLicenseInfoVo>> getStoreLicenseList(
+            @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") int pageSize,
+            @RequestParam(value = "storeName", required = false) String storeName,
+            @RequestParam(value = "storeContact", required = false) String storeContact,
+            @RequestParam(value = "storeTel", required = false) String storeTel,
+            @RequestParam(value = "businessSection", required = false) String businessSection,
+            @RequestParam(value = "imgType", required = false) Integer imgType,
+            @RequestParam(value = "states", required = false) String states,
+            @RequestParam(value = "startSubmitDate", required = false) String startSubmitDate,
+            @RequestParam(value = "endSubmitDate", required = false) String endSubmitDate) {
+        log.info("StoreInfoController.getStoreLicenseList?pageNum={},pageSize={},storeName={},storeContact={},storeTel={},businessSection={},imgType={},states={},startSubmitDate={},endSubmitDate={}",
+                pageNum, pageSize, storeName, storeContact, storeTel, businessSection, imgType, states, startSubmitDate, endSubmitDate);
+        IPage<StoreLicenseInfoVo> page = storeInfoService.getStoreLicenseList(pageNum, pageSize, storeName, storeContact, storeTel, businessSection, imgType, states, startSubmitDate, endSubmitDate);
+        return R.data(page);
+    }
+
+
     /**
      * 查询两种类型店铺(丽人美发、运动健身)并按距离筛选
      *

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

@@ -1,5 +1,7 @@
 package shop.alien.store.controller;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -8,12 +10,17 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeSys;
 import shop.alien.entity.store.UserLoginInfo;
 import shop.alien.entity.store.dto.SystemLoginDto;
-import shop.alien.entity.store.dto.SystemRegisterDto;
+import shop.alien.entity.store.dto.SystemUserAddDto;
+import shop.alien.entity.store.dto.SystemUserStatusDto;
+import shop.alien.entity.store.dto.SystemUserUpdateDto;
+import shop.alien.entity.store.vo.LifeSysVo;
 import shop.alien.entity.store.vo.SystemLoginVo;
 import shop.alien.store.service.SystemService;
 import shop.alien.util.common.TokenInfo;
 import springfox.documentation.annotations.ApiIgnore;
 
+import java.util.List;
+
 /**
  * 商家会员记录 前端控制器
  *
@@ -53,6 +60,93 @@ public class SystemController {
         return R.success("退出成功");
     }
 
+    @ApiOperation("新增系统用户账号")
+    @ApiOperationSupport(order = 3)
+    @PostMapping(value = "/addUser")
+    public R<LifeSys> addUser(@RequestBody SystemUserAddDto addDto) {
+        log.info("SystemController.addUser => userName={}, realName={}, phone={}, email={}, departmentId={},roleIds={}",
+                addDto.getUserName(), addDto.getRealName(), addDto.getPhone(), 
+                addDto.getEmail(), addDto.getDepartmentId(),addDto.getRoleIds());
+        return systemService.addUser(addDto);
+    }
+
+    @ApiOperation("修改系统用户状态")
+    @ApiOperationSupport(order = 4)
+    @PostMapping(value = "/updateUserStatus")
+    public R<LifeSys> updateUserStatus(@RequestBody SystemUserStatusDto statusDto) {
+        log.info("SystemController.updateUserStatus => userId={}, status={}, remark={}", 
+                statusDto.getUserId(), statusDto.getStatus(), statusDto.getRemark());
+        return systemService.updateUserStatus(statusDto);
+    }
+
+    @ApiOperation("修改系统用户账号信息")
+    @ApiOperationSupport(order = 5)
+    @PostMapping(value = "/updateUser")
+    public R<LifeSys> updateUser(@RequestBody SystemUserUpdateDto updateDto) {
+        log.info("SystemController.updateUser => userId={}, userName={}, realName={}, phone={}, email={}, departmentId={},roleIds={}",
+                updateDto.getUserId(), updateDto.getUserName(), updateDto.getRealName(), updateDto.getPhone(),
+                updateDto.getEmail(), updateDto.getDepartmentId(),updateDto.getRoleIds());
+        return systemService.updateUser(updateDto);
+    }
+
+    @ApiOperation("重置密码")
+    @ApiOperationSupport(order = 6)
+    @GetMapping(value = "/resetPassword")
+    public R<LifeSys> resetPassword(@RequestParam Integer userId) {
+        log.info("SystemController.resetPassword => userId={}", userId);
+        return systemService.resetPassword(userId);
+    }
+
+    @ApiOperation("分页查询系统用户列表")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页码(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "每页大小(默认10)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "userName", value = "用户名(支持模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "realName", value = "真实姓名(支持模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "phone", value = "联系电话(支持模糊查询)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "roleId", value = "角色ID(精确查询)", dataType = "Long", paramType = "query"),
+            @ApiImplicitParam(name = "departmentId", value = "部门ID(精确查询)", dataType = "String", paramType = "query"),
+    })
+    @GetMapping(value = "/getUserPage")
+    public R<IPage<LifeSysVo>> getUserPage(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam(required = false) String userName,
+            @RequestParam(required = false) String realName,
+            @RequestParam(required = false) String phone,
+            @RequestParam(required = false) String departmentId,
+            @RequestParam(required = false) Long roleId
+            ) {
+        log.info("SystemController.getUserPage => page={}, size={}, userName={}, realName={}, phone={}, roleId={}, departmentId={}, status={}", page, size, userName, realName, phone, roleId, departmentId);
+
+        return R.data(systemService.getUserPage(page, size, userName, realName, phone, departmentId, roleId));
+    }
+
+    // 清除用户的部门
+    @ApiOperation("清除用户的部门")
+    @ApiOperationSupport(order = 8)
+    @GetMapping(value = "/clearUserDepartment")
+    public R clearUserDepartment(@RequestParam String userId) {
+        log.info("SystemController.clearUserDepartment => userId={}", userId);
+        return systemService.clearUserDepartment(userId);
+    }
+
+    // 查询部门所有用户
+    @ApiOperation("查询所有用户")
+    @ApiOperationSupport(order = 9)
+    @GetMapping(value = "/getUsers")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "页码(默认1)", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "每页大小(默认10)", dataType = "int", paramType = "query"),
+    })
+    public R<List<LifeSys>> getUsers(@RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size) {
+        log.info("SystemController.getUsers => page={}, size={}", page, size);
+        List<LifeSys> records = systemService.page(new Page<>(page, size)).getRecords();
+        return R.data(records);
+    }
+
 /**
  * 废弃
   */

+ 138 - 0
alien-store/src/main/java/shop/alien/store/controller/TemplateDownloadController.java

@@ -0,0 +1,138 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.web.bind.annotation.CrossOrigin;
+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 javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 通用模板下载控制器
+ * 支持通过type参数下载不同类型的模板文件
+ *
+ * @author system
+ */
+@Api(tags = {"通用模板下载"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/template")
+public class TemplateDownloadController {
+
+    /**
+     * 模板类型与文件名的映射关系
+     * key: 模板类型
+     * value: 模板文件名(相对于 resources/templates 目录)
+     */
+    private static final Map<String, String> TEMPLATE_MAP = new HashMap<String, String>() {{
+        put("clearing_receipt", "clearing_receipt.xlsx");
+        put("holiday", "holiday.xlsx");
+        // 可以根据需要添加更多模板类型映射
+        // put("type2", "template2.xlsx");
+        // put("type3", "template3.xls");
+    }};
+
+    /**
+     * 通用模板下载接口
+     * 
+     * @param type 模板类型,对应 TEMPLATE_MAP 中的 key
+     * @param response HTTP响应对象
+     */
+    @ApiOperation("下载模板文件")
+    @GetMapping("/download")
+    public void downloadTemplate(
+            @ApiParam(value = "模板类型", required = true, example = "clearing_receipt")
+            @RequestParam("type") String type,
+            HttpServletResponse response) {
+        
+        log.info("开始下载模板,type: {}", type);
+        
+        // 验证模板类型
+        if (type == null || type.trim().isEmpty()) {
+            log.error("模板类型参数不能为空");
+            throw new RuntimeException("模板类型参数不能为空");
+        }
+        
+        String fileName = TEMPLATE_MAP.get(type);
+        if (fileName == null) {
+            log.error("不支持的模板类型: {}", type);
+            throw new RuntimeException("不支持的模板类型: " + type);
+        }
+        
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+        
+        try {
+            // 从resources/templates目录读取模板文件
+            Resource resource = new ClassPathResource("templates/" + fileName);
+            
+            // 检查文件是否存在
+            if (!resource.exists()) {
+                log.error("模板文件不存在: templates/{}", fileName);
+                throw new RuntimeException("模板文件不存在: " + fileName);
+            }
+            
+            inputStream = resource.getInputStream();
+            
+            // 根据文件扩展名设置Content-Type
+            String contentType;
+            if (fileName.toLowerCase().endsWith(".xlsx")) {
+                contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+            } else if (fileName.toLowerCase().endsWith(".xls")) {
+                contentType = "application/vnd.ms-excel";
+            } else {
+                contentType = "application/octet-stream";
+            }
+            
+            // 设置响应头
+            response.reset();
+            response.setContentType(contentType);
+            response.setCharacterEncoding("utf-8");
+            
+            // 设置文件下载响应头,支持中文文件名
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+            response.setHeader("Content-Disposition", 
+                    "attachment;filename=\"" + encodedFileName + "\";filename*=utf-8''" + encodedFileName);
+            
+            // 输出文件流
+            outputStream = response.getOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) > 0) {
+                outputStream.write(buffer, 0, length);
+            }
+            outputStream.flush();
+            
+            log.info("模板下载成功,type: {}, fileName: {}", type, fileName);
+        } catch (Exception e) {
+            log.error("下载模板失败,type: {}, fileName: {}", type, fileName, e);
+            throw new RuntimeException("下载模板失败:" + e.getMessage());
+        } finally {
+            // 关闭流
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                log.error("关闭流失败", e);
+            }
+        }
+    }
+}

+ 40 - 0
alien-store/src/main/java/shop/alien/store/controller/UserViolationController.java

@@ -8,6 +8,7 @@ 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;
@@ -81,6 +82,32 @@ public class UserViolationController {
         return R.data(lifeUserViolationService.getViolationPage(pageNum, pageSize, nickname, phone, processingStatus));
     }
 
+    @ApiOperation("举报分页(带时间筛选)")
+    @ApiOperationSupport(order = 9)
+    @GetMapping("/allPage")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", defaultValue = "1"),
+            @ApiImplicitParam(name = "pageSize", value = "每页条数", dataType = "int", paramType = "query", defaultValue = "10"),
+            @ApiImplicitParam(name = "nickname", value = "昵称", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "processingStatus", value = "处理状态", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "startTime", value = "开始时间", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "endTime", value = "结束时间", dataType = "String", paramType = "query")
+    })
+    public R<IPage<LifeUserViolationDto>> getAllViolationPage(
+            @RequestParam(defaultValue = "1") int pageNum,
+            @RequestParam(defaultValue = "10") int pageSize,
+            @RequestParam(value = "nickname", required = false) String nickname,
+            @RequestParam(value = "phone", required = false) String phone,
+            @RequestParam(value = "processingStatus", required = false) String processingStatus,
+            @RequestParam(value = "reportContextType", required = false) String reportContextType,
+            @RequestParam(value = "startTime", required = false) String startTime,
+            @RequestParam(value = "endTime", required = false) String endTime ) {
+        log.info("UserViolationController.getAllViolationPage?pageNum={},pageSize={},nickName={},phone={},processingStatus={},startTime={},endTime={}", 
+                pageNum, pageSize, nickname, phone, processingStatus, startTime, endTime);
+        return R.data(lifeUserViolationService.getAllViolationPage(pageNum, pageSize, nickname, phone, processingStatus, startTime, endTime, reportContextType));
+    }
+
     @ApiOperation(value = "举报审核")
     @ApiOperationSupport(order = 5)
     @GetMapping("/approve")
@@ -135,4 +162,17 @@ public class UserViolationController {
     public R<String> level(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo) {
         return R.data(lifeUserViolationService.level(userLoginInfo));
     }
+
+
+    @ApiOperation("中台举报详情")
+    @ApiOperationSupport(order = 9)
+    @GetMapping("/queryViolationDetail")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "ID", dataType = "Integer", 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));
+    }
 }

+ 55 - 0
alien-store/src/main/java/shop/alien/store/enums/OperationType.java

@@ -0,0 +1,55 @@
+package shop.alien.store.enums;
+
+/**
+ * 操作类型枚举
+ *
+ * @author ssk
+ * @since 2025-12-09
+ */
+public enum OperationType {
+    /**
+     * 新增
+     */
+    ADD("新增"),
+    
+    /**
+     * 删除
+     */
+    DELETE("删除"),
+    
+    /**
+     * 修改
+     */
+    UPDATE("修改"),
+    
+    /**
+     * 导入
+     */
+    IMPORT("导入"),
+    
+    /**
+     * 导出
+     */
+    EXPORT("导出"),
+    
+    /**
+     * 查询
+     */
+    QUERY("查询"),
+    
+    /**
+     * 其他
+     */
+    OTHER("其他");
+
+    private final String description;
+
+    OperationType(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
+

+ 82 - 0
alien-store/src/main/java/shop/alien/store/filter/PreAuthFilter.java

@@ -0,0 +1,82 @@
+package shop.alien.store.filter;
+
+import com.alibaba.fastjson.JSONObject;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+import shop.alien.entity.store.LifeSysMenu;
+import shop.alien.entity.store.LifeSysRole;
+import shop.alien.mapper.LifeSysMenuMapper;
+import shop.alien.store.service.impl.LifeSysRoleServiceImpl;
+import shop.alien.util.common.JwtUtil;
+
+import javax.annotation.Resource;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component
+public class PreAuthFilter extends OncePerRequestFilter {
+    // 自定义权限请求头(前端传递角色/权限)
+    private static final String USER_TOKEN_HEADER = "Authorization";
+    private static final String PLATFORM_TYPE_HEADER = "X-platform-type";
+
+    @Resource
+    private LifeSysMenuMapper lifeSysMenuMapper;
+    @Resource
+    private LifeSysRoleServiceImpl lifeSysRoleServiceImpl;
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+        // 1. 从请求头获取角色信息(前端传递,比如:ROLE_ADMIN,ROLE_USER)
+        String token = request.getHeader(USER_TOKEN_HEADER);
+        String platformType = request.getHeader(PLATFORM_TYPE_HEADER);
+
+        /**
+         * 如果不是平台端请求,直接放行,加admin权放行
+         */
+        if(null == platformType || !"platform".equalsIgnoreCase(platformType)) {
+            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
+                    "notPlatformUser", // 无认证场景,用户名可自定义
+                    null, // 无需密码,置空
+                    AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", "ROLE_ADMIN")));// 权限列表
+            chain.doFilter(request, response);
+            return;
+        }
+
+        if (token == null) {
+            // 无权限头:视为匿名用户(仅能访问公开接口)
+            chain.doFilter(request, response);
+            return;
+        }
+
+        // 2. 解析角色为权限集合(Spring Security 角色需以ROLE_开头)
+        JSONObject tokenInfo = JwtUtil.getTokenInfo(token);
+        String userId = tokenInfo.getString("userId");
+        String userName = tokenInfo.getString("userName");
+        List<LifeSysMenu> menuByUserId = lifeSysMenuMapper.getMenuByUserId(Long.parseLong(userId));
+        List<String> permsList = menuByUserId.stream().map(LifeSysMenu::getPerms).collect(Collectors.toList());
+        List<LifeSysRole> roleByUserId = lifeSysRoleServiceImpl.getRoleByUserId(Long.parseLong(userId));
+        List<String> roleList = roleByUserId.stream().map(x->"ROLE_"+x.getRoleNameEn().toUpperCase()).collect(Collectors.toList());
+        permsList.addAll(roleList);
+        // 3. 构建认证令牌(用户名可固定/自定义,密码置空,核心是权限)
+        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
+                userName, // 无认证场景,用户名可自定义
+                null, // 无需密码,置空
+                AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", permsList)));// 权限列表
+        // 4. 设置请求上下文
+        authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+        // 5. 将认证信息存入SecurityContext(标记用户已"认证",仅用于授权)
+        SecurityContextHolder.getContext().setAuthentication(authToken);
+
+        // 继续执行过滤器链
+        chain.doFilter(request, response);
+    }
+}

+ 58 - 0
alien-store/src/main/java/shop/alien/store/service/DictOpinionFeedbackService.java

@@ -0,0 +1,58 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 意见反馈字典管理Service
+ */
+public interface DictOpinionFeedbackService {
+
+    /**
+     * 查询意见反馈字典(三级树形结构)
+     * 如果传入dictDetail,则查询匹配的记录及其父级和子级树形结构
+     * 如果不传dictDetail,则返回完整的字典库树形结构
+     *
+     * @param dictDetail 字典描述(可选),如果传入则根据此参数过滤
+     * @return 字典树形结构列表
+     */
+    List<StoreDictionary> queryDictOpinionFeedbackTree(String dictDetail);
+
+    /**
+     * 新增意见反馈字典(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典信息
+     * @return 新增结果
+     */
+    boolean addDictOpinionFeedback(StoreDictionary storeDictionary);
+
+    /**
+     * 修改意见反馈字典(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典信息
+     * @return 修改结果
+     */
+    boolean updateDictOpinionFeedback(StoreDictionary storeDictionary);
+
+    /**
+     * 批量导入意见反馈字典
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importDictOpinionFeedback(org.springframework.web.multipart.MultipartFile file);
+
+    /**
+     * 下载意见反馈字典导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void downloadTemplate(HttpServletResponse response) throws IOException;
+}
+
+

+ 58 - 0
alien-store/src/main/java/shop/alien/store/service/DictStoreTagService.java

@@ -0,0 +1,58 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 店铺字典标签管理Service
+ */
+public interface DictStoreTagService {
+
+    /**
+     * 查询店铺字典标签(三级树形结构)
+     * 如果传入dictDetail,则查询匹配的记录及其父级和子级树形结构
+     * 如果不传dictDetail,则返回完整的字典库树形结构
+     *
+     * @param dictDetail 字典描述(可选),如果传入则根据此参数过滤
+     * @return 标签树形结构列表
+     */
+    List<StoreDictionary> queryDictStoreTagTree(String dictDetail);
+
+    /**
+     * 新增店铺字典标签(支持一级、二级、三级)
+     *
+     * @param storeDictionary 标签信息
+     * @return 新增结果
+     */
+    boolean addDictStoreTag(StoreDictionary storeDictionary);
+
+    /**
+     * 修改店铺字典标签(支持一级、二级、三级)
+     *
+     * @param storeDictionary 标签信息
+     * @return 修改结果
+     */
+    boolean updateDictStoreTag(StoreDictionary storeDictionary);
+
+    /**
+     * 批量导入店铺字典标签
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importDictStoreTag(org.springframework.web.multipart.MultipartFile file);
+
+    /**
+     * 下载店铺字典标签导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void downloadTemplate(HttpServletResponse response) throws IOException;
+}
+
+

+ 78 - 0
alien-store/src/main/java/shop/alien/store/service/DictionaryLibraryService.java

@@ -0,0 +1,78 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.result.R;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 平台字典库管理Service
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+public interface DictionaryLibraryService {
+
+    /**
+     * 查询字典库(三级树形结构)
+     * 如果传入dictDetail,则查询匹配的记录及其父级和子级树形结构
+     * 如果不传dictDetail,则返回完整的字典库树形结构
+     *
+     * @param dictDetail 字典描述(可选),如果传入则根据此参数过滤
+     * @return 字典库树形结构列表
+     */
+    List<StoreDictionary> queryDictionaryLibraryTree(String dictDetail);
+
+    /**
+     * 新增字典库(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典库信息
+     * @return 新增结果
+     */
+    boolean addDictionaryLibrary(StoreDictionary storeDictionary);
+
+    /**
+     * 修改字典库(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典库信息
+     * @return 修改结果
+     */
+    boolean updateDictionaryLibrary(StoreDictionary storeDictionary);
+
+    /**
+     * 批量导入字典库
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importDictionaryLibrary(org.springframework.web.multipart.MultipartFile file);
+
+    /**
+     * 下载字典库导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void downloadTemplate(HttpServletResponse response) throws IOException;
+
+    int deleteDictionaryLibrary(Long id);
+
+    /**
+     * 修改字典库状态(通过id修改hidden为1)
+     *
+     * @param id 字典库ID
+     * @return 修改结果
+     */
+    int updateHiddenStatus(Long id);
+
+    /**
+     * 根据dictDetail查询字典库(包含父级或子级树形结构)
+     *
+     * @param dictDetail 字典描述
+     * @return 字典库树形结构列表
+     */
+    List<StoreDictionary> selectDictBydictDetail(String dictDetail);
+}

+ 61 - 0
alien-store/src/main/java/shop/alien/store/service/FilterConditionService.java

@@ -0,0 +1,61 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 平台筛选条件管理Service
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+public interface FilterConditionService {
+
+    /**
+     * 查询筛选条件(四级树形结构)
+     * 如果传入dictDetail,则查询匹配的记录及其父级和子级树形结构
+     * 如果不传dictDetail,则返回完整的字典库树形结构
+     *
+     * @param dictDetail 字典描述(可选),如果传入则根据此参数过滤
+     * @return 筛选条件树形结构列表
+     */
+    List<StoreDictionary> queryFilterConditionTree(String dictDetail);
+
+    /**
+     * 新增筛选条件(支持一级、二级、三级)
+     *
+     * @param storeDictionary 筛选条件信息
+     * @return 新增结果
+     */
+    boolean addFilterCondition(StoreDictionary storeDictionary);
+
+    /**
+     * 修改筛选条件(支持一级、二级、三级)
+     *
+     * @param storeDictionary 筛选条件信息
+     * @return 修改结果
+     */
+    boolean updateFilterCondition(StoreDictionary storeDictionary);
+
+    /**
+     * 批量导入筛选条件
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importFilterCondition(org.springframework.web.multipart.MultipartFile file);
+
+    /**
+     * 下载筛选条件导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void downloadTemplate(HttpServletResponse response) throws IOException;
+}
+

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

@@ -3,6 +3,7 @@ package shop.alien.store.service;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import org.springframework.web.bind.annotation.RequestParam;
 import shop.alien.entity.store.EssentialHolidayComparison;
@@ -63,4 +64,11 @@ public interface LifeCouponService extends IService<LifeCoupon> {
      * @return LifeCouponVo
      */
     shop.alien.entity.store.vo.LifeCouponVo getNewCouponDetail(String id);
+
+    /**
+     * 导入假期管理
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importHolidayFromExcel(MultipartFile file);
 }

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

@@ -7,6 +7,8 @@ import shop.alien.entity.store.LifeFeedback;
 import shop.alien.entity.store.dto.*;
 import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
 import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.dto.LifeFeedbackDto;
+import shop.alien.entity.store.dto.UserReplyDto;
 import shop.alien.entity.store.vo.LifeFeedbackVo;
 
 /**

+ 69 - 0
alien-store/src/main/java/shop/alien/store/service/LifeSysDeptService.java

@@ -0,0 +1,69 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeSysDept;
+import shop.alien.entity.store.dto.LifeSysDeptAddDto;
+import shop.alien.entity.store.dto.LifeSysDeptSortDto;
+import shop.alien.entity.store.dto.LifeSysDeptUpdateDto;
+import shop.alien.entity.store.vo.LifeSysDeptVo;
+import shop.alien.entity.store.vo.LifeSysVo;
+
+import java.util.List;
+
+/**
+ * 部门表 服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface LifeSysDeptService {
+
+    /**
+     * 新增部门
+     *
+     * @param addDto 新增部门信息
+     * @return R<LifeSysDept>
+     */
+    R<LifeSysDept> addDept(LifeSysDeptAddDto addDto);
+
+    /**
+     * 更新一级部门排序
+     *
+     * @param sortDto 排序信息(部门ID列表,按排序顺序)
+     * @return R<Boolean>
+     */
+    R<Boolean> updateFirstLevelDeptSort(LifeSysDeptSortDto sortDto);
+
+    /**
+     * 删除部门
+     *
+     * @param deptId 部门ID
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteDept(Long deptId);
+
+    /**
+     * 编辑部门
+     *
+     * @param updateDto 编辑部门信息
+     * @return R<LifeSysDept>
+     */
+    R<LifeSysDept> updateDept(LifeSysDeptUpdateDto updateDto);
+
+    /**
+     * 根据部门名称搜索部门(模糊查询,返回树形结构)
+     *
+     * @param deptName 部门名称(支持模糊查询)
+     * @return R<List<LifeSysDeptVo>>
+     */
+    R<List<LifeSysDeptVo>> searchDeptByName(String deptName);
+
+    /**
+     * 查看部门成员
+     *
+     * @param deptId 部门ID
+     * @return R<List<LifeSysVo>>
+     */
+    R<List<LifeSysVo>> viewMembers(Integer pageNum, Integer pageSize, Long deptId);
+}
+

+ 71 - 0
alien-store/src/main/java/shop/alien/store/service/LifeSysMenuService.java

@@ -0,0 +1,71 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeSysMenu;
+
+import java.util.List;
+
+/**
+ * 菜单权限表 服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface LifeSysMenuService {
+
+    /**
+     * 新增菜单
+     *
+     * @param menu 菜单信息
+     * @return R<LifeSysMenu>
+     */
+    R<LifeSysMenu> addMenu(LifeSysMenu menu);
+
+    /**
+     * 更新菜单
+     *
+     * @param menu 菜单信息
+     * @return R<LifeSysMenu>
+     */
+    R<LifeSysMenu> updateMenu(LifeSysMenu menu);
+
+    /**
+     * 删除菜单
+     *
+     * @param menuId 菜单ID
+     * @return R<Boolean>
+     */
+    R<Boolean> deleteMenu(Long menuId);
+
+    /**
+     * 根据ID查询菜单
+     *
+     * @param menuId 菜单ID
+     * @return R<LifeSysMenu>
+     */
+    R<LifeSysMenu> getMenuById(Long menuId);
+
+    /**
+     * 查询菜单列表(树形结构)
+     *
+     * @return R<List<LifeSysMenu>>
+     */
+    R<List<LifeSysMenu>> getMenuTree();
+
+    /**
+     * 根据父菜单ID查询子菜单列表
+     *
+     * @param parentId 父菜单ID
+     * @return R<List<LifeSysMenu>>
+     */
+    R<List<LifeSysMenu>> getMenuListByParentId(Long parentId);
+
+     /**
+     * 根据用户ID查询用户菜单树形结构
+     *
+     * @param userId 用户ID
+     * @return R<List<LifeSysMenu>>
+     */
+    R<List<LifeSysMenu>> getMenuByUserId(Long userId, Long ifTree);
+}
+

+ 76 - 0
alien-store/src/main/java/shop/alien/store/service/LifeSysRoleMenuService.java

@@ -0,0 +1,76 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeSysRoleMenu;
+
+import java.util.List;
+
+/**
+ * 角色菜单关联表 服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface LifeSysRoleMenuService extends IService<LifeSysRoleMenu> {
+
+    /**
+     * 批量为角色分配菜单
+     *
+     * @param roleId  角色ID
+     * @param menuIds 菜单ID列表
+     * @return R<Boolean>
+     */
+    R<Boolean> assignMenus(Long roleId, List<Long> menuIds);
+
+    /**
+     * 移除角色的所有菜单
+     *
+     * @param roleId 角色ID
+     * @return R<Boolean>
+     */
+    R<Boolean> removeAllMenus(Long roleId);
+
+    /**
+     * 根据角色ID获取菜单ID列表
+     *
+     * @param roleId 角色ID
+     * @return R<List<Long>> 菜单ID列表
+     */
+    R<List<Long>> getMenuIdsByRoleId(Long roleId);
+
+    /**
+     * 为角色添加单个菜单
+     *
+     * @param roleId 角色ID
+     * @param menuId 菜单ID
+     * @return R<Boolean>
+     */
+    R<Boolean> addMenu(Long roleId, Long menuId);
+
+    /**
+     * 移除角色的单个菜单
+     *
+     * @param roleId 角色ID
+     * @param menuId 菜单ID
+     * @return R<Boolean>
+     */
+    R<Boolean> removeMenu(Long roleId, Long menuId);
+
+    /**
+     * 根据菜单ID获取角色ID列表
+     *
+     * @param menuId 菜单ID
+     * @return R<List<Long>> 角色ID列表
+     */
+    R<List<Long>> getRoleIdsByMenuId(Long menuId);
+
+    /**
+     * 根据角色ID查询所有权限(区分已拥有和未拥有的权限)
+     *
+     * @param roleId 角色ID
+     * @return R<List<MenuWithRoleVo>> 菜单列表(包含是否拥有标记)
+     */
+    R<List<shop.alien.entity.store.vo.MenuWithRoleVo>> getMenusWithRoleStatus(Long roleId);
+}
+

+ 85 - 0
alien-store/src/main/java/shop/alien/store/service/LifeSysRoleService.java

@@ -0,0 +1,85 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.LifeSysRole;
+
+import java.util.List;
+
+/**
+ * 平台角色信息表 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface LifeSysRoleService extends IService<LifeSysRole> {
+
+    /**
+     * 分页查询角色列表
+     *
+     * @param page     页码
+     * @param size     页容
+     * @param roleName 角色名称(支持模糊查询)
+     * @param roleKey  角色权限字符串(支持模糊查询)
+     * @param status   角色状态(0正常 1停用)
+     * @return IPage<LifeSysRole>
+     */
+    IPage<LifeSysRole> getRolePage(int page, int size, String roleName, String roleKey, String status);
+
+    /**
+     * 根据ID查询角色详情
+     *
+     * @param roleId 角色ID
+     * @return LifeSysRole
+     */
+    LifeSysRole getRoleById(Long roleId);
+
+    /**
+     * 新增角色
+     *
+     * @param role 角色信息
+     * @return boolean
+     */
+    boolean saveRole(LifeSysRole role);
+
+    /**
+     * 修改角色
+     *
+     * @param role 角色信息
+     * @return boolean
+     */
+    boolean updateRole(LifeSysRole role);
+
+    /**
+     * 删除角色(逻辑删除)
+     *
+     * @param roleId 角色ID
+     * @return boolean
+     */
+    boolean deleteRole(Long roleId);
+
+    /**
+     * 修改角色状态
+     *
+     * @param roleId 角色ID
+     * @param status 角色状态(0正常 1停用)
+     * @return boolean
+     */
+    boolean updateStatus(Long roleId, String status);
+
+    /**
+     * 查询所有正常状态的角色列表
+     *
+     * @return List<LifeSysRole>
+     */
+    List<LifeSysRole> getAllNormalRoles();
+
+     /**
+      * 根据用户ID查询角色
+      *
+      * @param userId 用户ID
+      * @return List<LifeSysRole> 角色列表
+      */
+    List<LifeSysRole> getRoleByUserId(Long userId);
+}
+

+ 59 - 0
alien-store/src/main/java/shop/alien/store/service/LifeSysUserRoleService.java

@@ -0,0 +1,59 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.LifeSysUserRole;
+
+import java.util.List;
+
+/**
+ * 用户角色关联表 服务类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface LifeSysUserRoleService extends IService<LifeSysUserRole> {
+
+    /**
+     * 批量为用户分配角色
+     *
+     * @param userId  用户ID
+     * @param roleIds 角色ID列表
+     * @return boolean
+     */
+    boolean assignRoles(Integer userId, List<Long> roleIds);
+
+    /**
+     * 移除用户的所有角色
+     *
+     * @param userId 用户ID
+     * @return boolean
+     */
+    boolean removeAllRoles(Integer userId);
+
+    /**
+     * 根据用户ID获取角色ID列表
+     *
+     * @param userId 用户ID
+     * @return List<Long> 角色ID列表
+     */
+    List<Long> getRoleIdsByUserId(Integer userId);
+
+    /**
+     * 为用户添加单个角色
+     *
+     * @param userId 用户ID
+     * @param roleId 角色ID
+     * @return boolean
+     */
+    boolean addRole(Integer userId, Long roleId);
+
+    /**
+     * 移除用户的单个角色
+     *
+     * @param userId 用户ID
+     * @param roleId 角色ID
+     * @return boolean
+     */
+    boolean removeRole(Integer userId, Long roleId);
+}
+

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

@@ -542,9 +542,9 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
         return  lifeUserDynamicsVos;
     }
 
-    public List<LifeUserDynamicsVo> getDynamicsList(Integer page, Integer size, String nickName, String userType, Integer dynamicsType, String releaseStartTime, String releaseEndTime) {
+    public List<LifeUserDynamicsVo> getDynamicsList(Integer page, Integer size, String nickName, String userType, Integer dynamicsType, String releaseStartTime, String releaseEndTime, String storeName) {
         // 查询动态数据并按类型过滤
-        return lifeUserDynamicsMapper.getDynamicsList(nickName, userType, dynamicsType, releaseStartTime, releaseEndTime);
+        return lifeUserDynamicsMapper.getDynamicsList(nickName, userType, dynamicsType, releaseStartTime, releaseEndTime, storeName);
     }
 
     public LifeUserDynamicsVo getDynamicsDetail(Integer id) {

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

@@ -2,6 +2,7 @@ package shop.alien.store.service;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.second.vo.SecondUserViolationDetailVo;
 import shop.alien.entity.store.LifeUserViolation;
 import shop.alien.entity.store.UserLoginInfo;
 import shop.alien.entity.store.dto.LifeUserViolationDto;
@@ -27,6 +28,8 @@ public interface LifeUserViolationService extends IService<LifeUserViolation> {
 
     IPage<LifeUserViolationDto> getViolationPage(int page, int size, String nickName, String phone, String processingStatus);
 
+    IPage<LifeUserViolationDto> getAllViolationPage(int page, int size, String nickName, String phone, String processingStatus, String startTime, String endTime, String reportContextType);
+
     void approve(int id, String processingStatus, String reportResult);
 
     LifeUserViolationDto byId(Integer id);
@@ -36,4 +39,6 @@ public interface LifeUserViolationService extends IService<LifeUserViolation> {
     String exportExcel(String nickName, String phone, String processingStatus) throws IOException;
 
     String level(UserLoginInfo userLoginInfo);
+
+    SecondUserViolationDetailVo queryViolationDetail(Integer id);
 }

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

@@ -0,0 +1,20 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.store.OperationLog;
+
+/**
+ * 操作日志服务接口
+ *
+ * @author ssk
+ * @since 2025-12-09
+ */
+public interface OperationLogService {
+
+    /**
+     * 保存操作日志
+     *
+     * @param operationLog 操作日志对象
+     */
+    void saveOperationLog(OperationLog operationLog);
+}
+

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

@@ -18,10 +18,13 @@ public interface PlatformBusinessSectionService {
 
     /**
      * 查询经营种类(三级树形结构)
+     * 如果传入dictDetail,则查询匹配的记录及其父级和子级树形结构
+     * 如果不传dictDetail,则返回完整的字典库树形结构
      *
+     * @param dictDetail 字典描述(可选),如果传入则根据此参数过滤
      * @return 经营种类树形结构列表
      */
-    List<StoreDictionary> queryBusinessSectionTree();
+    List<StoreDictionary> queryBusinessSectionTree(String dictDetail);
 
     /**
      * 新增经营种类(支持一级、二级、三级)

+ 76 - 0
alien-store/src/main/java/shop/alien/store/service/ProtocolManagementService.java

@@ -0,0 +1,76 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartRequest;
+import shop.alien.entity.store.ProtocolManagement;
+
+/**
+ * 协议管理表 服务类
+ *
+ * @author alien
+ * @since 2025-12-09
+ */
+public interface ProtocolManagementService extends IService<ProtocolManagement> {
+
+    /**
+     * 分页查询协议列表
+     *
+     * @param pageNum          页数
+     * @param pageSize         页容
+     * @param protocolFileName 协议名称
+     * @param displayPosition  展示位置
+     * @return IPage<ProtocolManagement>
+     */
+    IPage<ProtocolManagement> getProtocolPage(Integer pageNum, Integer pageSize, String protocolFileName, String displayPosition);
+
+    /**
+     * 根据ID查询协议详情
+     *
+     * @param id 协议ID
+     * @param response 响应流
+     */
+    void getProtocolById(Integer id, javax.servlet.http.HttpServletResponse response);
+
+    /**
+     * 新增协议
+     *
+     * @param protocolManagement 协议对象
+     * @return boolean
+     */
+    boolean addProtocol(MultipartFile multipartFile, ProtocolManagement protocolManagement);
+
+    /**
+     * 更新协议
+     *
+     * @param protocolManagement 协议对象
+     * @return boolean
+     */
+    boolean updateProtocol(MultipartRequest multipartRequest, ProtocolManagement protocolManagement);
+
+    /**
+     * 删除协议(逻辑删除)
+     *
+     * @param id 协议ID
+     * @return boolean
+     */
+    boolean deleteProtocol(Integer id);
+
+    /**
+     * 批量删除协议
+     *
+     * @param ids 协议ID列表
+     * @return boolean
+     */
+    boolean batchDeleteProtocol(String ids);
+
+    /**
+     * 校验文件名是否重复
+     *
+     * @param protocolFileName 协议文件名
+     * @return String "已重复" 或 "不重复"
+     */
+    Boolean checkFileNameDuplicate(String protocolFileName);
+}
+

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

@@ -72,7 +72,7 @@ public interface StoreInfoService extends IService<StoreInfo> {
      * @param storeType    门店类型
      * @return IPage<StoreInfoVo>
      */
-    IPage<StoreInfoVo> getNewStorePage(int page, int size, String storeName, String storeContact, String id, String storePhone, String storeType, String expiredState, String storeApplicationStatus, String storeStatus, String businessSection, String jingdu, String weidu, String renewContractStatus, String foodLicenceStatus, String foodLicenceWhetherExpiredStatus);
+    IPage<StoreInfoVo> getNewStorePage(int page, int size, String storeName, String storeContact, String id, String storePhone, String storeType, String expiredState, String storeApplicationStatus, String storeStatus, String businessSection, String jingdu, String weidu, String renewContractStatus, String foodLicenceStatus, String foodLicenceWhetherExpiredStatus, String startApplyTime, String endApplyTime);
 
     /**
      * web-重置门店密码
@@ -194,6 +194,14 @@ public interface StoreInfoService extends IService<StoreInfo> {
     void approveStoreInfo(String storeId, Integer approvalStatus, String reason);
 
     /**
+     * web端人工复核店铺
+     *
+     * @param storeInfoDto 复核信息(包含门店ID、复核状态、时间、原因)
+     * @return true 表示保存成功
+     */
+    boolean manualReview(StoreInfoDto storeInfoDto);
+
+    /**
      * web端导出商铺信息结果
      */
     String exportExcel(String id, String storePhone, String businessSection, String storeApplicationStatus) throws IOException;
@@ -446,4 +454,30 @@ public interface StoreInfoService extends IService<StoreInfo> {
      * @return R<IPage<StoreInfoVo>> 分页的门店信息列表
      */
     IPage<StoreInfoVo> getLifeServicesByDistance(Double lon, Double lat, Double distance, Integer sortType, Integer businessType, Integer categoryId, String storeName, int pageNum, int pageSize);
+
+    /**
+     * 门店证照查询(分页)
+     *
+     * @param pageNum         页码
+     * @param pageSize        页容
+     * @param storeName       门店名称(模糊)
+     * @param storeContact    联系人姓名(模糊)
+     * @param storeTel        门店电话(模糊)
+     * @param businessSection 经营板块
+     * @param imgType         证照图片类型(14:营业执照;24/25:食品经营许可证;31/32:娱乐经营许可证)
+     * @param states          证照状态
+     * @param startSubmitDate 提交开始时间(yyyy-MM-dd HH:mm:ss)
+     * @param endSubmitDate   提交结束时间(yyyy-MM-dd HH:mm:ss)
+     * @return 证照分页结果
+     */
+    IPage<StoreLicenseInfoVo> getStoreLicenseList(int pageNum,
+                                                  int pageSize,
+                                                  String storeName,
+                                                  String storeContact,
+                                                  String storeTel,
+                                                  String businessSection,
+                                                  Integer imgType,
+                                                  String states,
+                                                  String startSubmitDate,
+                                                  String endSubmitDate);
 }

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

@@ -1,8 +1,14 @@
 package shop.alien.store.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.LifeSys;
 import shop.alien.entity.store.UserLoginInfo;
+import shop.alien.entity.store.dto.SystemUserAddDto;
+import shop.alien.entity.store.dto.SystemUserStatusDto;
+import shop.alien.entity.store.dto.SystemUserUpdateDto;
+import shop.alien.entity.store.vo.LifeSysVo;
 import shop.alien.entity.store.vo.SystemLoginVo;
 
 /**
@@ -11,7 +17,7 @@ import shop.alien.entity.store.vo.SystemLoginVo;
  * @author YinDP
  * @since 2025-02-27
  */
-public interface SystemService {
+public interface SystemService extends IService<LifeSys> {
 
     public SystemLoginVo login(String username, String password);
 
@@ -27,4 +33,57 @@ public interface SystemService {
      */
     R<LifeSys> register(String userName, String password, String roleId);
 
+    /**
+     * 新增系统用户账号
+     *
+     * @param addDto 新增用户信息
+     * @return R<LifeSys>
+     */
+    R<LifeSys> addUser(SystemUserAddDto addDto);
+
+    /**
+     * 修改系统用户账号
+     *
+     * @param updateDto 修改用户信息
+     * @return R<LifeSys>
+     */
+    R<LifeSys> updateUser(SystemUserUpdateDto updateDto);
+
+    /**
+     * 修改系统用户状态
+     *
+     * @param statusDto 用户状态信息
+     * @return R<LifeSys>
+     */
+    R<LifeSys> updateUserStatus(SystemUserStatusDto statusDto);
+
+    /**
+     * 重置系统用户密码
+     *
+     * @param userId 用户ID
+     * @return R<LifeSys>
+     */
+    R<LifeSys> resetPassword(Integer userId);
+
+    /**
+     * 分页查询系统用户列表
+     *
+     * @param page  页码
+     * @param size  每页大小
+     * @param userName 用户名
+     * @param realName 真实姓名
+     * @param phone    联系电话
+     * @param departmentId 部门ID
+     * @param roleId   角色ID
+     * @return IPage<LifeSys>
+     */
+    IPage<LifeSysVo> getUserPage(int page, int size, String userName, String realName, String phone, String departmentId, Long roleId);
+
+    /**
+     * 清除用户的部门
+     *
+     * @param userId 用户ID
+     * @return R<Boolean>
+     */
+    R clearUserDepartment(String userId);
 }

+ 1087 - 0
alien-store/src/main/java/shop/alien/store/service/impl/DictOpinionFeedbackServiceImpl.java

@@ -0,0 +1,1087 @@
+package shop.alien.store.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.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.excelVo.DictionaryLibraryExcelVo;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.store.service.DictOpinionFeedbackService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 意见反馈字典管理ServiceImpl
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DictOpinionFeedbackServiceImpl extends ServiceImpl<StoreDictionaryMapper, StoreDictionary> implements DictOpinionFeedbackService {
+
+    private static final String DEFAULT_TYPE_NAME = "dict_opinion_feedback";
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 查询意见反馈字典(三级树形结构)
+     * 如果传入dictDetail,则查询匹配的记录及其父级和子级树形结构
+     * 如果不传dictDetail,则返回完整的字典库树形结构
+     *
+     * @param dictDetail 字典描述(可选),如果传入则根据此参数过滤
+     * @return 字典树形结构列表
+     */
+    @Override
+    public List<StoreDictionary> queryDictOpinionFeedbackTree(String dictDetail) {
+        // 如果传入了dictDetail,使用过滤逻辑
+        if (StringUtils.isNotBlank(dictDetail)) {
+            return selectDictBydictDetail(dictDetail);
+        }
+        
+        // 否则使用原有逻辑:查询所有意见反馈字典数据
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        List<StoreDictionary> dictList = storeDictionaryMapper.selectList(queryWrapper);
+
+        return buildTreeOptimized(dictList);
+    }
+    
+    /**
+     * 根据dictDetail查询意见反馈字典(包含父级或子级树形结构)
+     * 支持查询多个同名数据及其父级和子级
+     *
+     * @param dictDetail 字典描述
+     * @return 意见反馈字典树形结构列表
+     */
+    private List<StoreDictionary> selectDictBydictDetail(String dictDetail) {
+        if (StringUtils.isBlank(dictDetail)) {
+            throw new IllegalArgumentException("dictDetail不能为空");
+        }
+
+        // 通过dictDetail模糊查询所有匹配的记录(可能有多个同名数据)
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.like(StoreDictionary::getDictDetail, dictDetail);
+        
+        List<StoreDictionary> matchedDicts = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (matchedDicts == null || matchedDicts.isEmpty()) {
+            log.warn("未找到dictDetail为[{}]的记录", dictDetail);
+            return new ArrayList<>();
+        }
+
+        log.info("找到{}条dictDetail为[{}]的记录", matchedDicts.size(), dictDetail);
+
+        // 用于存储每个根节点对应的所有节点(按根节点分组)
+        Map<Integer, Set<Integer>> rootToNodeIdsMap = new HashMap<>();
+        
+        // 遍历所有匹配的记录,收集相关节点ID
+        for (StoreDictionary currentDict : matchedDicts) {
+            Integer parentId = currentDict.getParentId();
+            Integer rootId;
+            
+            if (parentId != null && parentId != 0) {
+                // 如果有父级,查找根节点
+                rootId = findRootId(currentDict.getId());
+                log.info("记录ID[{}]有父级[{}], 根节点ID[{}]", currentDict.getId(), parentId, rootId);
+                
+                // 收集从根节点到当前节点的路径上的所有节点ID
+                List<Integer> pathNodeIds = new ArrayList<>();
+                collectPathNodes(currentDict.getId(), pathNodeIds);
+                log.info("路径节点IDs: {}", pathNodeIds);
+                
+                // 收集该根节点下的所有相关节点ID(包括路径节点及其所有子节点)
+                Set<Integer> nodeIds = rootToNodeIdsMap.computeIfAbsent(rootId, k -> new HashSet<>());
+                for (Integer nodeId : pathNodeIds) {
+                    nodeIds.add(nodeId);
+                    collectChildrenIds(nodeId, nodeIds);
+                }
+            } else {
+                // 如果没有父级,以当前节点为根
+                rootId = currentDict.getId();
+                log.info("记录ID[{}]没有父级, 作为根节点", currentDict.getId());
+                
+                // 收集该根节点下的所有相关节点ID
+                Set<Integer> nodeIds = rootToNodeIdsMap.computeIfAbsent(rootId, k -> new HashSet<>());
+                nodeIds.add(currentDict.getId());
+                collectChildrenIds(currentDict.getId(), nodeIds);
+            }
+        }
+        
+        // 收集所有需要返回的节点数据
+        Map<Integer, StoreDictionary> allNodeMap = new HashMap<>();
+        for (Set<Integer> nodeIds : rootToNodeIdsMap.values()) {
+            for (Integer nodeId : nodeIds) {
+                if (!allNodeMap.containsKey(nodeId)) {
+                    StoreDictionary node = storeDictionaryMapper.selectById(nodeId);
+                    if (node != null && node.getDeleteFlag() == 0) {
+                        allNodeMap.put(nodeId, node);
+                    }
+                }
+            }
+        }
+        
+        log.info("总共收集到{}个节点", allNodeMap.size());
+        
+        // 构建树形结构,为每个唯一的根节点构建一棵树
+        List<StoreDictionary> resultList = new ArrayList<>();
+        
+        for (Map.Entry<Integer, Set<Integer>> entry : rootToNodeIdsMap.entrySet()) {
+            Integer rootId = entry.getKey();
+            Set<Integer> nodeIds = entry.getValue();
+            
+            log.info("处理根节点[{}], 包含{}个节点", rootId, nodeIds.size());
+            
+            // 从所有节点中筛选出该根节点下的相关节点
+            List<StoreDictionary> relatedNodes = new ArrayList<>();
+            for (Integer nodeId : nodeIds) {
+                StoreDictionary node = allNodeMap.get(nodeId);
+                if (node != null) {
+                    relatedNodes.add(node);
+                }
+            }
+            
+            log.info("根节点[{}]筛选出{}个相关节点", rootId, relatedNodes.size());
+            
+            // 如果找到相关节点,构建树形结构
+            if (!relatedNodes.isEmpty()) {
+                StoreDictionary root = buildTreeFromNodeList(relatedNodes, rootId);
+                if (root != null) {
+                    resultList.add(root);
+                    log.info("成功构建根节点[{}]的树形结构", rootId);
+                } else {
+                    log.warn("构建根节点[{}]的树形结构失败", rootId);
+                }
+            } else {
+                log.warn("根节点[{}]没有相关节点", rootId);
+            }
+        }
+        
+        log.info("最终返回{}棵树", resultList.size());
+        return resultList;
+    }
+    
+    /**
+     * 收集节点的所有子节点ID(递归)
+     *
+     * @param nodeId 节点ID
+     * @param childrenIds 子节点ID集合
+     */
+    private void collectChildrenIds(Integer nodeId, Set<Integer> childrenIds) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getParentId, nodeId);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children != null && !children.isEmpty()) {
+            for (StoreDictionary child : children) {
+                if (!childrenIds.contains(child.getId())) {
+                    childrenIds.add(child.getId());
+                    collectChildrenIds(child.getId(), childrenIds);
+                }
+            }
+        }
+    }
+
+    /**
+     * 递归查找根节点ID
+     *
+     * @param id 当前节点ID
+     * @return 根节点ID
+     */
+    private Integer findRootId(Integer id) {
+        StoreDictionary dict = storeDictionaryMapper.selectById(id);
+        if (dict == null || dict.getDeleteFlag() == 1) {
+            // 如果节点不存在或已删除,返回当前ID作为根节点
+            return id;
+        }
+        
+        Integer parentId = dict.getParentId();
+        if (parentId == null || parentId == 0) {
+            return id;
+        }
+        
+        return findRootId(parentId);
+    }
+
+    /**
+     * 收集从当前节点到根节点的路径上的所有节点ID
+     *
+     * @param nodeId 当前节点ID
+     * @param pathNodeIds 路径节点ID列表
+     */
+    private void collectPathNodes(Integer nodeId, List<Integer> pathNodeIds) {
+        StoreDictionary dict = storeDictionaryMapper.selectById(nodeId);
+        if (dict == null || dict.getDeleteFlag() == 1) {
+            return;
+        }
+        
+        // 避免重复添加
+        if (!pathNodeIds.contains(nodeId)) {
+            pathNodeIds.add(nodeId);
+        }
+        
+        Integer parentId = dict.getParentId();
+        if (parentId != null && parentId != 0) {
+            collectPathNodes(parentId, pathNodeIds);
+        }
+    }
+
+    /**
+     * 从节点列表构建树形结构
+     *
+     * @param nodeList 节点列表
+     * @param rootId 根节点ID
+     * @return 根节点
+     */
+    private StoreDictionary buildTreeFromNodeList(List<StoreDictionary> nodeList, Integer rootId) {
+        // 创建ID到节点的映射
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();
+        for (StoreDictionary node : nodeList) {
+            nodeMap.put(node.getId(), node);
+            node.setStoreDictionaryList(new ArrayList<>());
+        }
+        
+        // 找到根节点
+        StoreDictionary root = nodeMap.get(rootId);
+        if (root == null) {
+            return null;
+        }
+        
+        // 建立父子关系
+        for (StoreDictionary node : nodeList) {
+            if (node.getId().equals(rootId)) {
+                continue; // 跳过根节点
+            }
+            
+            Integer parentId = node.getParentId();
+            if (parentId != null && parentId != 0) {
+                StoreDictionary parent = nodeMap.get(parentId);
+                if (parent != null) {
+                    parent.getStoreDictionaryList().add(node);
+                }
+            }
+        }
+        
+        // 对子节点列表按sortId排序
+        sortChildren(root);
+        
+        return root;
+    }
+
+    /**
+     * 递归排序子节点
+     *
+     * @param node 节点
+     */
+    private void sortChildren(StoreDictionary node) {
+        if (node == null || node.getStoreDictionaryList() == null) {
+            return;
+        }
+        
+        node.getStoreDictionaryList().sort((a, b) -> {
+            Integer sortIdA = a.getSortId() != null ? a.getSortId() : 0;
+            Integer sortIdB = b.getSortId() != null ? b.getSortId() : 0;
+            return sortIdA.compareTo(sortIdB);
+        });
+        
+        // 递归排序所有子节点
+        for (StoreDictionary child : node.getStoreDictionaryList()) {
+            sortChildren(child);
+        }
+    }
+
+    /**
+     * 构建树形结构(优化版)
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();
+        List<StoreDictionary> result = new ArrayList<>();
+
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            nodeMap.put(id, entity);
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增意见反馈字典(支持一级、二级、三级)
+     */
+    @Override
+    public boolean addDictOpinionFeedback(StoreDictionary storeDictionary) {
+        if (storeDictionary == null || StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典描述不能为空");
+        }
+
+        storeDictionary.setDeleteFlag(0);
+        storeDictionary.setCreatedTime(new Date());
+
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        if (parentId != 0) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+            if (parent == null || parent.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("父节点不存在或已删除");
+            }
+        }
+
+        String typeName = storeDictionary.getTypeName();
+        if (StringUtils.isBlank(typeName)) {
+            throw new IllegalArgumentException("typeName不能为空");
+        }
+
+        String maxDictId = getMaxDictIdByTypeName(typeName);
+        int nextDictId = (maxDictId == null ? 1 : Integer.parseInt(maxDictId)) + 1;
+        storeDictionary.setDictId(String.valueOf(nextDictId));
+
+        Integer maxSortId = getMaxSortIdByParentId(parentId);
+        storeDictionary.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        return this.save(storeDictionary);
+    }
+
+    /**
+     * 修改意见反馈字典(支持一级、二级、三级)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateDictOpinionFeedback(StoreDictionary storeDictionary) {
+        if (storeDictionary == null || storeDictionary.getId() == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+        if (StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典描述不能为空");
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectById(storeDictionary.getId());
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        Integer existingParentId = existing.getParentId();
+        boolean isFirstLevel = (existingParentId == null || existingParentId == 0);
+        boolean isSecondLevel = false;
+
+        if (!isFirstLevel) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(existingParentId);
+            if (parent != null) {
+                Integer grandParentId = parent.getParentId();
+                isSecondLevel = (grandParentId == null || grandParentId == 0);
+            }
+        }
+
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        Integer normalizedParentId = parentId == null ? 0 : parentId;
+        Integer normalizedExistingParentId = existingParentId == null ? 0 : existingParentId;
+        Integer oldSortId = existing.getSortId();
+        Integer newSortId = storeDictionary.getSortId();
+        boolean parentIdChanged = !normalizedParentId.equals(normalizedExistingParentId);
+
+        // 判断是否修改了关键字段(parent_id)
+        boolean keyFieldChanged = parentIdChanged;
+
+        if ((isFirstLevel || isSecondLevel) && keyFieldChanged) {
+            boolean hasChildren = hasUndeletedChildren(existing.getId());
+            if (hasChildren) {
+                throw new IllegalArgumentException("该节点存在未删除的子节点,不允许修改关键字段(父节点)");
+            }
+        }
+
+        if (parentIdChanged) {
+            if (parentId != 0) {
+                StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+                if (parent == null || parent.getDeleteFlag() == 1) {
+                    throw new IllegalArgumentException("父节点不存在或已删除");
+                }
+            }
+
+            if (newSortId == null) {
+                Integer maxSortId = getMaxSortIdByParentId(parentId);
+                int nextSortId = (maxSortId == null ? 1 : maxSortId) + 1;
+                newSortId = nextSortId;
+                storeDictionary.setSortId(newSortId);
+            }
+
+            adjustSortOrderForMoveOut(existing.getId(), normalizedExistingParentId, oldSortId);
+
+            if (newSortId != null) {
+                try {
+                    int newOrder = newSortId;
+                    Integer maxSortId = getMaxSortIdByParentId(parentId);
+                    int maxOrder = (maxSortId == null ? 0 : maxSortId);
+                    if (newOrder <= maxOrder) {
+                        adjustSortOrderForMoveIn(existing.getId(), normalizedParentId, newSortId);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析新sortId: {}", newSortId, e);
+                }
+            }
+        }
+
+        Integer oldHidden = existing.getHidden();
+        Integer newHidden = storeDictionary.getHidden() == null ? oldHidden : storeDictionary.getHidden();
+        storeDictionary.setHidden(newHidden);
+        storeDictionary.setDeleteFlag(existing.getDeleteFlag());
+        storeDictionary.setCreatedTime(existing.getCreatedTime());
+        storeDictionary.setCreatedUserId(existing.getCreatedUserId());
+        storeDictionary.setUpdatedTime(new Date());
+
+        if (!parentIdChanged) {
+            if (newSortId == null) {
+                storeDictionary.setSortId(oldSortId);
+            } else if (!newSortId.equals(oldSortId)) {
+                adjustSortOrder(existing.getId(), normalizedParentId, oldSortId, newSortId);
+            }
+        }
+
+        boolean updateResult = this.updateById(storeDictionary);
+
+        boolean hiddenChanged = (oldHidden == null && newHidden != null) ||
+                (oldHidden != null && !oldHidden.equals(newHidden));
+        if (updateResult && (isFirstLevel || isSecondLevel) && hiddenChanged && newHidden != null) {
+            updateChildrenHidden(existing.getId(), newHidden);
+        }
+
+        return updateResult;
+    }
+
+    /**
+     * 判断节点是否有未删除的子节点
+     */
+    private boolean hasUndeletedChildren(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary child = storeDictionaryMapper.selectOne(queryWrapper);
+        return child != null;
+    }
+
+    /**
+     * 递归更新子节点的显示/隐藏状态
+     */
+    private void updateChildrenHidden(Integer parentId, Integer hidden) {
+        if (parentId == null || hidden == null) {
+            return;
+        }
+
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+
+        Date updateTime = new Date();
+        for (StoreDictionary child : children) {
+            child.setHidden(hidden);
+            child.setUpdatedTime(updateTime);
+            storeDictionaryMapper.updateById(child);
+            updateChildrenHidden(child.getId(), hidden);
+        }
+    }
+
+    /**
+     * 根据 typeName 获取最大 dict_id
+     */
+    private String getMaxDictIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null ? maxDict.getDictId() : null;
+    }
+
+    /**
+     * 根据parentId获取最大的sortId
+     */
+    private Integer getMaxSortIdByParentId(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        queryWrapper.orderByDesc(StoreDictionary::getSortId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null && maxDict.getSortId() != null ? maxDict.getSortId() : null;
+    }
+
+    /**
+     * 调整排序:当记录的sortId改变时,调整同级其他记录的排序
+     */
+    private void adjustSortOrder(Integer currentId, Integer parentId, Integer oldSortId, Integer newSortId) {
+        try {
+            int oldOrder = oldSortId;
+            int newOrder = newSortId;
+
+            if (oldOrder == newOrder) {
+                return;
+            }
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (parentId == null || parentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, parentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            if (newOrder < oldOrder) {
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder >= newOrder && siblingOrder < oldOrder) {
+                            sibling.setSortId(siblingOrder + 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            } else if (newOrder > oldOrder) {
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder > oldOrder && siblingOrder <= newOrder) {
+                            sibling.setSortId(siblingOrder - 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("调整排序失败,sortId格式错误: oldSortId={}, newSortId={}", oldSortId, newSortId, e);
+        } catch (Exception e) {
+            log.error("调整排序失败", e);
+        }
+    }
+
+    /**
+     * 处理从原节点移出时的排序调整
+     */
+    private void adjustSortOrderForMoveOut(Integer currentId, Integer oldParentId, Integer oldSortId) {
+        try {
+            int oldOrder = oldSortId;
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (oldParentId == null || oldParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, oldParentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder > oldOrder) {
+                        sibling.setSortId(siblingOrder - 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移出节点排序调整失败,sortId格式错误: oldSortId={}", oldSortId, e);
+        } catch (Exception e) {
+            log.error("移出节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 处理移入新节点时的排序调整
+     */
+    private void adjustSortOrderForMoveIn(Integer currentId, Integer newParentId, Integer newSortId) {
+        try {
+            int newOrder = newSortId;
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (newParentId == null || newParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, newParentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder >= newOrder) {
+                        sibling.setSortId(siblingOrder + 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getDictId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移入节点排序调整失败,sortId格式错误: newSortId={}", newSortId, e);
+        } catch (Exception e) {
+            log.error("移入节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 批量导入意见反馈字典
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> importDictOpinionFeedback(MultipartFile file) {
+        log.info("DictOpinionFeedbackServiceImpl.importDictOpinionFeedback fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            if (file == null || file.isEmpty()) {
+                return R.fail("文件不能为空");
+            }
+
+            String fileName = file.getOriginalFilename();
+            if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+                return R.fail("文件格式不正确,请上传Excel文件");
+            }
+
+            List<String> errorMessages = new ArrayList<>();
+            int successCount = 0;
+            int totalCount = 0;
+
+            try (InputStream inputStream = file.getInputStream();
+                 Workbook workbook = new XSSFWorkbook(inputStream)) {
+                Sheet sheet = workbook.getSheetAt(0);
+
+                Row headerRow = sheet.getRow(5);
+                if (headerRow == null) {
+                    return R.fail("Excel文件格式不正确,缺少表头");
+                }
+
+                Map<String, Integer> headerMap = new HashMap<>();
+                Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                    Cell cell = headerRow.getCell(i);
+                    if (cell != null) {
+                        String headerName = getCellValueAsString(cell);
+                        if (StringUtils.isNotBlank(headerName)) {
+                            headerMap.put(headerName.trim(), i);
+                        }
+                    }
+                }
+
+                for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                    Row row = sheet.getRow(rowIndex);
+                    if (row == null) {
+                        continue;
+                    }
+
+                    boolean isEmptyRow = true;
+                    for (int i = 0; i < row.getLastCellNum(); i++) {
+                        Cell cell = row.getCell(i);
+                        if (cell != null && cell.getCellType() != CellType.BLANK) {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                isEmptyRow = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isEmptyRow) {
+                        continue;
+                    }
+
+                    totalCount++;
+                    DictionaryLibraryExcelVo excelVo = new DictionaryLibraryExcelVo();
+
+                    for (Field field : fields) {
+                        if (!field.isAnnotationPresent(ExcelHeader.class)) {
+                            continue;
+                        }
+                        ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                        String headerName = excelHeader.value();
+                        Integer colIndex = headerMap.get(headerName);
+                        if (colIndex == null) {
+                            continue;
+                        }
+
+                        Cell cell = row.getCell(colIndex);
+                        if (cell == null) {
+                            continue;
+                        }
+
+                        field.setAccessible(true);
+                        try {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                field.set(excelVo, cellValue.trim());
+                            }
+                        } catch (Exception e) {
+                            log.warn("读取字段{}失败:{}", headerName, e.getMessage());
+                        }
+                    }
+
+                    try {
+                        processImportData(excelVo, rowIndex + 1, errorMessages);
+                        successCount++;
+                    } catch (Exception e) {
+                        errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                        log.error("导入第{}行数据失败", rowIndex + 1, e);
+                    }
+                }
+            }
+
+            StringBuilder message = new StringBuilder();
+            message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+            if (!errorMessages.isEmpty()) {
+                message.append("\n失败详情:\n");
+                int maxErrors = Math.min(errorMessages.size(), 10);
+                for (int i = 0; i < maxErrors; i++) {
+                    message.append(errorMessages.get(i)).append("\n");
+                }
+                if (errorMessages.size() > 10) {
+                    message.append(String.format("...还有%d条错误未显示", errorMessages.size() - 10));
+                }
+            }
+
+            if (successCount == 0) {
+                return R.fail(message.toString());
+            } else {
+                return R.success(message.toString());
+            }
+        } catch (Exception e) {
+            log.error("导入意见反馈字典失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理导入数据
+     */
+    private void processImportData(DictionaryLibraryExcelVo excelVo, int rowIndex, List<String> errorMessages) {
+        if (StringUtils.isBlank(excelVo.getFirstLevelName())) {
+            throw new IllegalArgumentException("一级分类名称不能为空");
+        }
+
+        String firstLevelName = excelVo.getFirstLevelName().trim();
+        String secondLevelName = StringUtils.isNotBlank(excelVo.getSecondLevelName())
+                ? excelVo.getSecondLevelName().trim() : null;
+        String thirdLevelName = StringUtils.isNotBlank(excelVo.getThirdLevelName())
+                ? excelVo.getThirdLevelName().trim() : null;
+
+        if (StringUtils.isNotBlank(thirdLevelName)) {
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, 3, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(secondLevelName)) {
+            createOrUpdateLevel(firstLevelName, secondLevelName, null, 2, excelVo.getHidden(), errorMessages, rowIndex);
+        } else {
+            createOrUpdateLevel(firstLevelName, null, null, 1, excelVo.getHidden(), errorMessages, rowIndex);
+        }
+    }
+
+    /**
+     * 创建或更新分类级别
+     */
+    private void createOrUpdateLevel(String firstLevelName, String secondLevelName, String thirdLevelName,
+                                     int level, String hidden, List<String> errorMessages, int rowIndex) {
+        try {
+            StoreDictionary firstLevel = findOrCreateLevel(firstLevelName, null, 0, hidden);
+            if (firstLevel == null) {
+                throw new IllegalArgumentException("创建一级分类失败");
+            }
+            if (level >= 2 && StringUtils.isNotBlank(secondLevelName)) {
+                StoreDictionary secondLevel = findOrCreateLevel(secondLevelName, firstLevel.getId(), 1, hidden);
+                if (secondLevel == null) {
+                    throw new IllegalArgumentException("创建二级分类失败");
+                }
+
+                if (level >= 3 && StringUtils.isNotBlank(thirdLevelName)) {
+                    StoreDictionary thirdLevel = findOrCreateLevel(thirdLevelName, secondLevel.getId(), 2, hidden);
+                    if (thirdLevel == null) {
+                        throw new IllegalArgumentException("创建三级分类失败");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 查找或创建分类
+     */
+    private StoreDictionary findOrCreateLevel(String dictDetail, Integer parentId, int expectedLevel, String hidden) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictDetail, dictDetail);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectOne(queryWrapper);
+
+        if (existing != null) {
+            return existing;
+        }
+
+        StoreDictionary newDict = new StoreDictionary();
+        newDict.setDictDetail(dictDetail);
+        // 一级为筛选条件,二级为筛选条件种类,三级为分类,四级为时间范围
+        if (expectedLevel == 0) {
+            newDict.setTypeDetail("意见反馈");
+            newDict.setTypeName("opinion_feedback");
+        } else if (expectedLevel == 1) {
+            newDict.setTypeDetail("意见反馈种类");
+            newDict.setTypeName("opinion_feedback_type");
+        } else if (expectedLevel == 2) {
+            newDict.setTypeDetail("意见反馈分类");
+            newDict.setTypeName("opinion_feedback_classify");
+        }
+        newDict.setParentId(parentId == null ? null : parentId);
+        newDict.setHidden(StringUtils.isNotBlank(hidden) && hidden.equals("隐藏") ? 1 : 0);
+        newDict.setDeleteFlag(0);
+        newDict.setCreatedTime(new Date());
+
+        String maxDictId = getMaxDictIdByTypeName(newDict.getTypeName());
+        newDict.setDictId(String.valueOf(maxDictId == null ? 1 : Integer.parseInt(maxDictId) + 1));
+
+        Integer maxSortId = getMaxSortIdByParentId(newDict.getParentId());
+        newDict.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        boolean saved = this.save(newDict);
+        return saved ? newDict : null;
+    }
+
+    private String buildTypeDetail(int expectedLevel) {
+        if (expectedLevel == 0) {
+            return "意见反馈一级";
+        } else if (expectedLevel == 1) {
+            return "意见反馈二级";
+        } else if (expectedLevel == 2) {
+            return "意见反馈三级";
+        }
+        return "意见反馈";
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue().toString();
+                } else {
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 下载意见反馈字典导入模板
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("DictOpinionFeedbackServiceImpl.downloadTemplate");
+
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        try {
+            Sheet sheet = workbook.createSheet("意见反馈字典导入模板");
+
+            Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+            List<Field> excelFields = new ArrayList<>();
+            for (Field field : fields) {
+                if (field.isAnnotationPresent(ExcelHeader.class)) {
+                    excelFields.add(field);
+                }
+            }
+
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerFont.setFontHeightInPoints((short) 12);
+            CellStyle headerCellStyle = workbook.createCellStyle();
+            headerCellStyle.setFont(headerFont);
+            headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+            headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerCellStyle.setBorderBottom(BorderStyle.THIN);
+            headerCellStyle.setBorderTop(BorderStyle.THIN);
+            headerCellStyle.setBorderLeft(BorderStyle.THIN);
+            headerCellStyle.setBorderRight(BorderStyle.THIN);
+
+            Row headerRow = sheet.createRow(0);
+            for (int i = 0; i < excelFields.size(); i++) {
+                Field field = excelFields.get(i);
+                ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(excelHeader.value());
+                cell.setCellStyle(headerCellStyle);
+            }
+
+            sheet.setColumnWidth(0, 25 * 256);
+            sheet.setColumnWidth(1, 25 * 256);
+            sheet.setColumnWidth(2, 25 * 256);
+            sheet.setColumnWidth(3, 15 * 256);
+
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("意见反馈字典导入模板", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+        } finally {
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭workbook失败", e);
+                }
+            }
+        }
+    }
+}
+
+

+ 1092 - 0
alien-store/src/main/java/shop/alien/store/service/impl/DictStoreTagServiceImpl.java

@@ -0,0 +1,1092 @@
+package shop.alien.store.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.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.excelVo.DictionaryLibraryExcelVo;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.store.service.DictStoreTagService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 店铺字典标签管理ServiceImpl
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DictStoreTagServiceImpl extends ServiceImpl<StoreDictionaryMapper, StoreDictionary> implements DictStoreTagService {
+
+    private static final String DEFAULT_TYPE_NAME = "dict_store_tag";
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 查询店铺字典标签(三级树形结构)
+     * 如果传入dictDetail,则查询匹配的记录及其父级和子级树形结构
+     * 如果不传dictDetail,则返回完整的字典库树形结构
+     *
+     * @param dictDetail 字典描述(可选),如果传入则根据此参数过滤
+     * @return 标签树形结构列表
+     */
+    @Override
+    public List<StoreDictionary> queryDictStoreTagTree(String dictDetail) {
+        // 如果传入了dictDetail,使用过滤逻辑
+        if (StringUtils.isNotBlank(dictDetail)) {
+            return selectDictBydictDetail(dictDetail);
+        }
+        
+        // 否则使用原有逻辑:查询所有店铺标签字典数据
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        List<StoreDictionary> dictStoreTags = storeDictionaryMapper.selectList(queryWrapper);
+
+        return buildTreeOptimized(dictStoreTags);
+    }
+    
+    /**
+     * 根据dictDetail查询店铺标签字典(包含父级或子级树形结构)
+     * 支持查询多个同名数据及其父级和子级
+     *
+     * @param dictDetail 字典描述
+     * @return 店铺标签字典树形结构列表
+     */
+    private List<StoreDictionary> selectDictBydictDetail(String dictDetail) {
+        if (StringUtils.isBlank(dictDetail)) {
+            throw new IllegalArgumentException("dictDetail不能为空");
+        }
+
+        // 通过dictDetail模糊查询所有匹配的记录(可能有多个同名数据)
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.like(StoreDictionary::getDictDetail, dictDetail);
+        
+        List<StoreDictionary> matchedDicts = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (matchedDicts == null || matchedDicts.isEmpty()) {
+            log.warn("未找到dictDetail为[{}]的记录", dictDetail);
+            return new ArrayList<>();
+        }
+
+        log.info("找到{}条dictDetail为[{}]的记录", matchedDicts.size(), dictDetail);
+
+        // 用于存储每个根节点对应的所有节点(按根节点分组)
+        Map<Integer, Set<Integer>> rootToNodeIdsMap = new HashMap<>();
+        
+        // 遍历所有匹配的记录,收集相关节点ID
+        for (StoreDictionary currentDict : matchedDicts) {
+            Integer parentId = currentDict.getParentId();
+            Integer rootId;
+            
+            if (parentId != null && parentId != 0) {
+                // 如果有父级,查找根节点
+                rootId = findRootId(currentDict.getId());
+                log.info("记录ID[{}]有父级[{}], 根节点ID[{}]", currentDict.getId(), parentId, rootId);
+                
+                // 收集从根节点到当前节点的路径上的所有节点ID
+                List<Integer> pathNodeIds = new ArrayList<>();
+                collectPathNodes(currentDict.getId(), pathNodeIds);
+                log.info("路径节点IDs: {}", pathNodeIds);
+                
+                // 收集该根节点下的所有相关节点ID(包括路径节点及其所有子节点)
+                Set<Integer> nodeIds = rootToNodeIdsMap.computeIfAbsent(rootId, k -> new HashSet<>());
+                for (Integer nodeId : pathNodeIds) {
+                    nodeIds.add(nodeId);
+                    collectChildrenIds(nodeId, nodeIds);
+                }
+            } else {
+                // 如果没有父级,以当前节点为根
+                rootId = currentDict.getId();
+                log.info("记录ID[{}]没有父级, 作为根节点", currentDict.getId());
+                
+                // 收集该根节点下的所有相关节点ID
+                Set<Integer> nodeIds = rootToNodeIdsMap.computeIfAbsent(rootId, k -> new HashSet<>());
+                nodeIds.add(currentDict.getId());
+                collectChildrenIds(currentDict.getId(), nodeIds);
+            }
+        }
+        
+        // 收集所有需要返回的节点数据
+        Map<Integer, StoreDictionary> allNodeMap = new HashMap<>();
+        for (Set<Integer> nodeIds : rootToNodeIdsMap.values()) {
+            for (Integer nodeId : nodeIds) {
+                if (!allNodeMap.containsKey(nodeId)) {
+                    StoreDictionary node = storeDictionaryMapper.selectById(nodeId);
+                    if (node != null && node.getDeleteFlag() == 0) {
+                        allNodeMap.put(nodeId, node);
+                    }
+                }
+            }
+        }
+        
+        log.info("总共收集到{}个节点", allNodeMap.size());
+        
+        // 构建树形结构,为每个唯一的根节点构建一棵树
+        List<StoreDictionary> resultList = new ArrayList<>();
+        
+        for (Map.Entry<Integer, Set<Integer>> entry : rootToNodeIdsMap.entrySet()) {
+            Integer rootId = entry.getKey();
+            Set<Integer> nodeIds = entry.getValue();
+            
+            log.info("处理根节点[{}], 包含{}个节点", rootId, nodeIds.size());
+            
+            // 从所有节点中筛选出该根节点下的相关节点
+            List<StoreDictionary> relatedNodes = new ArrayList<>();
+            for (Integer nodeId : nodeIds) {
+                StoreDictionary node = allNodeMap.get(nodeId);
+                if (node != null) {
+                    relatedNodes.add(node);
+                }
+            }
+            
+            log.info("根节点[{}]筛选出{}个相关节点", rootId, relatedNodes.size());
+            
+            // 如果找到相关节点,构建树形结构
+            if (!relatedNodes.isEmpty()) {
+                StoreDictionary root = buildTreeFromNodeList(relatedNodes, rootId);
+                if (root != null) {
+                    resultList.add(root);
+                    log.info("成功构建根节点[{}]的树形结构", rootId);
+                } else {
+                    log.warn("构建根节点[{}]的树形结构失败", rootId);
+                }
+            } else {
+                log.warn("根节点[{}]没有相关节点", rootId);
+            }
+        }
+        
+        log.info("最终返回{}棵树", resultList.size());
+        return resultList;
+    }
+    
+    /**
+     * 收集节点的所有子节点ID(递归)
+     *
+     * @param nodeId 节点ID
+     * @param childrenIds 子节点ID集合
+     */
+    private void collectChildrenIds(Integer nodeId, Set<Integer> childrenIds) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getParentId, nodeId);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children != null && !children.isEmpty()) {
+            for (StoreDictionary child : children) {
+                if (!childrenIds.contains(child.getId())) {
+                    childrenIds.add(child.getId());
+                    collectChildrenIds(child.getId(), childrenIds);
+                }
+            }
+        }
+    }
+
+    /**
+     * 递归查找根节点ID
+     *
+     * @param id 当前节点ID
+     * @return 根节点ID
+     */
+    private Integer findRootId(Integer id) {
+        StoreDictionary dict = storeDictionaryMapper.selectById(id);
+        if (dict == null || dict.getDeleteFlag() == 1) {
+            // 如果节点不存在或已删除,返回当前ID作为根节点
+            return id;
+        }
+        
+        Integer parentId = dict.getParentId();
+        if (parentId == null || parentId == 0) {
+            return id;
+        }
+        
+        return findRootId(parentId);
+    }
+
+    /**
+     * 收集从当前节点到根节点的路径上的所有节点ID
+     *
+     * @param nodeId 当前节点ID
+     * @param pathNodeIds 路径节点ID列表
+     */
+    private void collectPathNodes(Integer nodeId, List<Integer> pathNodeIds) {
+        StoreDictionary dict = storeDictionaryMapper.selectById(nodeId);
+        if (dict == null || dict.getDeleteFlag() == 1) {
+            return;
+        }
+        
+        // 避免重复添加
+        if (!pathNodeIds.contains(nodeId)) {
+            pathNodeIds.add(nodeId);
+        }
+        
+        Integer parentId = dict.getParentId();
+        if (parentId != null && parentId != 0) {
+            collectPathNodes(parentId, pathNodeIds);
+        }
+    }
+
+    /**
+     * 从节点列表构建树形结构
+     *
+     * @param nodeList 节点列表
+     * @param rootId 根节点ID
+     * @return 根节点
+     */
+    private StoreDictionary buildTreeFromNodeList(List<StoreDictionary> nodeList, Integer rootId) {
+        // 创建ID到节点的映射
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();
+        for (StoreDictionary node : nodeList) {
+            nodeMap.put(node.getId(), node);
+            node.setStoreDictionaryList(new ArrayList<>());
+        }
+        
+        // 找到根节点
+        StoreDictionary root = nodeMap.get(rootId);
+        if (root == null) {
+            return null;
+        }
+        
+        // 建立父子关系
+        for (StoreDictionary node : nodeList) {
+            if (node.getId().equals(rootId)) {
+                continue; // 跳过根节点
+            }
+            
+            Integer parentId = node.getParentId();
+            if (parentId != null && parentId != 0) {
+                StoreDictionary parent = nodeMap.get(parentId);
+                if (parent != null) {
+                    parent.getStoreDictionaryList().add(node);
+                }
+            }
+        }
+        
+        // 对子节点列表按sortId排序
+        sortChildren(root);
+        
+        return root;
+    }
+
+    /**
+     * 递归排序子节点
+     *
+     * @param node 节点
+     */
+    private void sortChildren(StoreDictionary node) {
+        if (node == null || node.getStoreDictionaryList() == null) {
+            return;
+        }
+        
+        node.getStoreDictionaryList().sort((a, b) -> {
+            Integer sortIdA = a.getSortId() != null ? a.getSortId() : 0;
+            Integer sortIdB = b.getSortId() != null ? b.getSortId() : 0;
+            return sortIdA.compareTo(sortIdB);
+        });
+        
+        // 递归排序所有子节点
+        for (StoreDictionary child : node.getStoreDictionaryList()) {
+            sortChildren(child);
+        }
+    }
+
+    /**
+     * 构建树形结构
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();  // 父ID到子节点列表的映射
+        List<StoreDictionary> result = new ArrayList<>();  // 结果列表
+
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            // 存入节点映射
+            nodeMap.put(id, entity);
+
+            // 初始化子节点列表
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            // 如果是根节点(parentId为null或0),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        // 建立父子关系
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增店铺字典标签(支持一级、二级、三级)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean addDictStoreTag(StoreDictionary storeDictionary) {
+        if (storeDictionary == null || StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典描述不能为空");
+        }
+
+        storeDictionary.setDeleteFlag(0);
+        storeDictionary.setCreatedTime(new Date());
+
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        if (parentId != 0) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+            if (parent == null || parent.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("父节点不存在或已删除");
+            }
+        }
+
+        String typeName = storeDictionary.getTypeName();
+        if (StringUtils.isBlank(typeName)) {
+            throw new IllegalArgumentException("typeName不能为空");
+        }
+        String maxDictId = getMaxDictIdByTypeName(typeName);
+        int nextDictId = (maxDictId == null ? 1 : Integer.parseInt(maxDictId)) + 1;
+        storeDictionary.setDictId(String.valueOf(nextDictId));
+
+        Integer maxSortId = getMaxSortIdByParentId(parentId);
+        storeDictionary.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        return this.save(storeDictionary);
+    }
+
+    /**
+     * 修改店铺字典标签(支持一级、二级、三级)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateDictStoreTag(StoreDictionary storeDictionary) {
+        // 参数校验
+        if (storeDictionary == null || storeDictionary.getId() == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+        if (StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典库描述不能为空");
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectById(storeDictionary.getId());
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        Integer existingParentId = existing.getParentId();
+        boolean isFirstLevel = (existingParentId == null || existingParentId == 0);
+        boolean isSecondLevel = false;
+
+        if (!isFirstLevel) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(existingParentId);
+            if (parent != null) {
+                Integer grandParentId = parent.getParentId();
+                isSecondLevel = (grandParentId == null || grandParentId == 0);
+            }
+        }
+
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        Integer normalizedParentId = parentId == null ? 0 : parentId;
+        Integer normalizedExistingParentId = existingParentId == null ? 0 : existingParentId;
+        Integer oldSortId = existing.getSortId();
+        Integer newSortId = storeDictionary.getSortId();
+        boolean parentIdChanged = !normalizedParentId.equals(normalizedExistingParentId);
+        boolean keyFieldChanged = parentIdChanged;
+
+        if ((isFirstLevel || isSecondLevel) && keyFieldChanged) {
+            boolean hasChildren = hasUndeletedChildren(existing.getId());
+            if (hasChildren) {
+                throw new IllegalArgumentException("该节点存在未删除的子节点,不允许修改关键字段(父节点)");
+            }
+        }
+
+        if (parentIdChanged) {
+            if (parentId != 0) {
+                StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+                if (parent == null || parent.getDeleteFlag() == 1) {
+                    throw new IllegalArgumentException("父节点不存在或已删除");
+                }
+            }
+
+            if (newSortId == null) {
+                Integer maxSortId = getMaxSortIdByParentId(parentId);
+                int nextSortId = (maxSortId == null ? 1 : maxSortId) + 1;
+                newSortId = nextSortId;
+                storeDictionary.setSortId(newSortId);
+            }
+
+            adjustSortOrderForMoveOut(existing.getId(), normalizedExistingParentId, oldSortId);
+
+            if (newSortId != null) {
+                try {
+                    int newOrder = newSortId;
+                    Integer maxSortId = getMaxSortIdByParentId(parentId);
+                    int maxOrder = (maxSortId == null ? 0 : maxSortId);
+                    if (newOrder <= maxOrder) {
+                        adjustSortOrderForMoveIn(existing.getId(), normalizedParentId, newSortId);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析新sortId: {}", newSortId, e);
+                }
+            }
+        }
+
+        Integer oldHidden = existing.getHidden();
+        Integer newHidden = storeDictionary.getHidden() == null ? oldHidden : storeDictionary.getHidden();
+        storeDictionary.setHidden(newHidden);
+        storeDictionary.setDeleteFlag(existing.getDeleteFlag());
+        storeDictionary.setCreatedTime(existing.getCreatedTime());
+        storeDictionary.setCreatedUserId(existing.getCreatedUserId());
+        storeDictionary.setUpdatedTime(new Date());
+
+        if (!parentIdChanged) {
+            if (newSortId == null) {
+                storeDictionary.setSortId(oldSortId);
+            } else if (!newSortId.equals(oldSortId)) {
+                adjustSortOrder(existing.getId(), normalizedParentId, oldSortId, newSortId);
+            }
+        }
+
+        boolean updateResult = this.updateById(storeDictionary);
+
+        boolean hiddenChanged = (oldHidden == null && newHidden != null) ||
+                (oldHidden != null && !oldHidden.equals(newHidden));
+        if (updateResult && (isFirstLevel || isSecondLevel) && hiddenChanged && newHidden != null) {
+            updateChildrenHidden(existing.getId(), newHidden);
+        }
+
+        return updateResult;
+    }
+
+    /**
+     * 判断节点是否有未删除的子节点
+     */
+    private boolean hasUndeletedChildren(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary child = storeDictionaryMapper.selectOne(queryWrapper);
+        return child != null;
+    }
+
+    /**
+     * 递归更新子节点的显示/隐藏状态
+     */
+    private void updateChildrenHidden(Integer parentId, Integer hidden) {
+        if (parentId == null || hidden == null) {
+            return;
+        }
+
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+
+        Date updateTime = new Date();
+        for (StoreDictionary child : children) {
+            child.setHidden(hidden);
+            child.setUpdatedTime(updateTime);
+            storeDictionaryMapper.updateById(child);
+            updateChildrenHidden(child.getId(), hidden);
+        }
+    }
+
+    /**
+     * 根据 typeName 获取最大 dict_id
+     */
+    private String getMaxDictIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null ? maxDict.getDictId() : null;
+    }
+
+    /**
+     * 根据parentId获取最大的sortId
+     */
+    private Integer getMaxSortIdByParentId(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        queryWrapper.orderByDesc(StoreDictionary::getSortId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null && maxDict.getSortId() != null ? maxDict.getSortId() : null;
+    }
+
+    /**
+     * 调整排序:当记录的sortId改变时,调整同级其他记录的排序
+     */
+    private void adjustSortOrder(Integer currentId, Integer parentId, Integer oldSortId, Integer newSortId) {
+        try {
+            int oldOrder = oldSortId;
+            int newOrder = newSortId;
+
+            if (oldOrder == newOrder) {
+                return;
+            }
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (parentId == null || parentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, parentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            if (newOrder < oldOrder) {
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder >= newOrder && siblingOrder < oldOrder) {
+                            sibling.setSortId(siblingOrder + 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            } else if (newOrder > oldOrder) {
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder > oldOrder && siblingOrder <= newOrder) {
+                            sibling.setSortId(siblingOrder - 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("调整排序失败,sortId格式错误: oldSortId={}, newSortId={}", oldSortId, newSortId, e);
+        } catch (Exception e) {
+            log.error("调整排序失败", e);
+        }
+    }
+
+    /**
+     * 处理从原节点移出时的排序调整
+     */
+    private void adjustSortOrderForMoveOut(Integer currentId, Integer oldParentId, Integer oldSortId) {
+        try {
+            int oldOrder = oldSortId;
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (oldParentId == null || oldParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, oldParentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder > oldOrder) {
+                        sibling.setSortId(siblingOrder - 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移出节点排序调整失败,sortId格式错误: oldSortId={}", oldSortId, e);
+        } catch (Exception e) {
+            log.error("移出节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 处理移入新节点时的排序调整
+     */
+    private void adjustSortOrderForMoveIn(Integer currentId, Integer newParentId, Integer newSortId) {
+        try {
+            int newOrder = newSortId;
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (newParentId == null || newParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, newParentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder >= newOrder) {
+                        sibling.setSortId(siblingOrder + 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getDictId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移入节点排序调整失败,sortId格式错误: newSortId={}", newSortId, e);
+        } catch (Exception e) {
+            log.error("移入节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 批量导入店铺字典标签
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> importDictStoreTag(MultipartFile file) {
+        log.info("DictStoreTagServiceImpl.importDictStoreTag fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            if (file == null || file.isEmpty()) {
+                return R.fail("文件不能为空");
+            }
+
+            String fileName = file.getOriginalFilename();
+            if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+                return R.fail("文件格式不正确,请上传Excel文件");
+            }
+
+            List<String> errorMessages = new ArrayList<>();
+            int successCount = 0;
+            int totalCount = 0;
+
+            try (InputStream inputStream = file.getInputStream();
+                 Workbook workbook = new XSSFWorkbook(inputStream)) {
+                Sheet sheet = workbook.getSheetAt(0);
+
+                Row headerRow = sheet.getRow(5);
+                if (headerRow == null) {
+                    return R.fail("Excel文件格式不正确,缺少表头");
+                }
+
+                Map<String, Integer> headerMap = new HashMap<>();
+                Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                    Cell cell = headerRow.getCell(i);
+                    if (cell != null) {
+                        String headerName = getCellValueAsString(cell);
+                        if (StringUtils.isNotBlank(headerName)) {
+                            headerMap.put(headerName.trim(), i);
+                        }
+                    }
+                }
+
+                for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                    Row row = sheet.getRow(rowIndex);
+                    if (row == null) {
+                        continue;
+                    }
+
+                    boolean isEmptyRow = true;
+                    for (int i = 0; i < row.getLastCellNum(); i++) {
+                        Cell cell = row.getCell(i);
+                        if (cell != null && cell.getCellType() != CellType.BLANK) {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                isEmptyRow = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isEmptyRow) {
+                        continue;
+                    }
+
+                    totalCount++;
+                    DictionaryLibraryExcelVo excelVo = new DictionaryLibraryExcelVo();
+
+                    for (Field field : fields) {
+                        if (!field.isAnnotationPresent(ExcelHeader.class)) {
+                            continue;
+                        }
+                        ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                        String headerName = excelHeader.value();
+                        Integer colIndex = headerMap.get(headerName);
+                        if (colIndex == null) {
+                            continue;
+                        }
+
+                        Cell cell = row.getCell(colIndex);
+                        if (cell == null) {
+                            continue;
+                        }
+
+                        field.setAccessible(true);
+                        try {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                field.set(excelVo, cellValue.trim());
+                            }
+                        } catch (Exception e) {
+                            log.warn("读取字段{}失败:{}", headerName, e.getMessage());
+                        }
+                    }
+
+                    try {
+                        processImportData(excelVo, rowIndex + 1, errorMessages);
+                        successCount++;
+                    } catch (Exception e) {
+                        errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                        log.error("导入第{}行数据失败", rowIndex + 1, e);
+                    }
+                }
+            }
+
+            StringBuilder message = new StringBuilder();
+            message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+            if (!errorMessages.isEmpty()) {
+                message.append("\n失败详情:\n");
+                int maxErrors = Math.min(errorMessages.size(), 10);
+                for (int i = 0; i < maxErrors; i++) {
+                    message.append(errorMessages.get(i)).append("\n");
+                }
+                if (errorMessages.size() > 10) {
+                    message.append(String.format("...还有%d条错误未显示", errorMessages.size() - 10));
+                }
+            }
+
+            if (successCount == 0) {
+                return R.fail(message.toString());
+            } else {
+                return R.success(message.toString());
+            }
+        } catch (Exception e) {
+            log.error("导入店铺字典标签失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理导入数据
+     */
+    private void processImportData(DictionaryLibraryExcelVo excelVo, int rowIndex, List<String> errorMessages) {
+        if (StringUtils.isBlank(excelVo.getFirstLevelName())) {
+            throw new IllegalArgumentException("一级分类名称不能为空");
+        }
+
+        String firstLevelName = excelVo.getFirstLevelName().trim();
+        String secondLevelName = StringUtils.isNotBlank(excelVo.getSecondLevelName())
+                ? excelVo.getSecondLevelName().trim() : null;
+        String thirdLevelName = StringUtils.isNotBlank(excelVo.getThirdLevelName())
+                ? excelVo.getThirdLevelName().trim() : null;
+
+        if (StringUtils.isNotBlank(thirdLevelName)) {
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, 3, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(secondLevelName)) {
+            createOrUpdateLevel(firstLevelName, secondLevelName, null, 2, excelVo.getHidden(), errorMessages, rowIndex);
+        } else {
+            createOrUpdateLevel(firstLevelName, null, null, 1, excelVo.getHidden(), errorMessages, rowIndex);
+        }
+    }
+
+    /**
+     * 创建或更新分类级别
+     */
+    private void createOrUpdateLevel(String firstLevelName, String secondLevelName, String thirdLevelName,
+                                     int level, String hidden, List<String> errorMessages, int rowIndex) {
+        try {
+            StoreDictionary firstLevel = findOrCreateLevel(firstLevelName, null, 0, hidden);
+            if (firstLevel == null) {
+                throw new IllegalArgumentException("创建一级分类失败");
+            }
+            if (level >= 2 && StringUtils.isNotBlank(secondLevelName)) {
+                StoreDictionary secondLevel = findOrCreateLevel(secondLevelName, firstLevel.getId(), 1, hidden);
+                if (secondLevel == null) {
+                    throw new IllegalArgumentException("创建二级分类失败");
+                }
+
+                if (level >= 3 && StringUtils.isNotBlank(thirdLevelName)) {
+                    StoreDictionary thirdLevel = findOrCreateLevel(thirdLevelName, secondLevel.getId(), 2, hidden);
+                    if (thirdLevel == null) {
+                        throw new IllegalArgumentException("创建三级分类失败");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 查找或创建分类
+     */
+    private StoreDictionary findOrCreateLevel(String dictDetail, Integer parentId, int expectedLevel, String hidden) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictDetail, dictDetail);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectOne(queryWrapper);
+
+        if (existing != null) {
+            return existing;
+        }
+
+        StoreDictionary newDict = new StoreDictionary();
+        newDict.setDictDetail(dictDetail);
+        // 一级为筛选条件,二级为筛选条件种类,三级为分类,四级为时间范围
+        if (expectedLevel == 0) {
+            newDict.setTypeDetail("门店标签");
+            newDict.setTypeName("store_tag");
+        } else if (expectedLevel == 1) {
+            newDict.setTypeDetail("门店标签种类");
+            newDict.setTypeName("store_tag_type");
+        } else if (expectedLevel == 2) {
+            newDict.setTypeDetail("门店标签分类");
+            newDict.setTypeName("store_tag_classify");
+        }
+        newDict.setParentId(parentId == null ? null : parentId);
+        newDict.setHidden(StringUtils.isNotBlank(hidden) && hidden.equals("隐藏") ? 1 : 0);
+        newDict.setDeleteFlag(0);
+        newDict.setCreatedTime(new Date());
+
+        String maxDictId = getMaxDictIdByTypeName(newDict.getTypeName());
+        newDict.setDictId(String.valueOf(maxDictId == null ? 1 : Integer.parseInt(maxDictId) + 1));
+
+        Integer maxSortId = getMaxSortIdByParentId(newDict.getParentId());
+        newDict.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        boolean saved = this.save(newDict);
+        return saved ? newDict : null;
+    }
+
+    private String buildTypeDetail(int expectedLevel) {
+        if (expectedLevel == 0) {
+            return "店铺标签一级";
+        } else if (expectedLevel == 1) {
+            return "店铺标签二级";
+        } else if (expectedLevel == 2) {
+            return "店铺标签三级";
+        }
+        return "店铺标签";
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue().toString();
+                } else {
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 下载店铺字典标签导入模板
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("DictStoreTagServiceImpl.downloadTemplate");
+
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        try {
+            Sheet sheet = workbook.createSheet("店铺字典标签导入模板");
+
+            Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+            List<Field> excelFields = new ArrayList<>();
+            for (Field field : fields) {
+                if (field.isAnnotationPresent(ExcelHeader.class)) {
+                    excelFields.add(field);
+                }
+            }
+
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerFont.setFontHeightInPoints((short) 12);
+            CellStyle headerCellStyle = workbook.createCellStyle();
+            headerCellStyle.setFont(headerFont);
+            headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+            headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerCellStyle.setBorderBottom(BorderStyle.THIN);
+            headerCellStyle.setBorderTop(BorderStyle.THIN);
+            headerCellStyle.setBorderLeft(BorderStyle.THIN);
+            headerCellStyle.setBorderRight(BorderStyle.THIN);
+
+            Row headerRow = sheet.createRow(0);
+            for (int i = 0; i < excelFields.size(); i++) {
+                Field field = excelFields.get(i);
+                ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(excelHeader.value());
+                cell.setCellStyle(headerCellStyle);
+            }
+
+            sheet.setColumnWidth(0, 25 * 256);
+            sheet.setColumnWidth(1, 25 * 256);
+            sheet.setColumnWidth(2, 25 * 256);
+            sheet.setColumnWidth(3, 15 * 256);
+
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("店铺字典标签导入模板", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+        } finally {
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭workbook失败", e);
+                }
+            }
+        }
+    }
+}
+
+

+ 1416 - 0
alien-store/src/main/java/shop/alien/store/service/impl/DictionaryLibraryServiceImpl.java

@@ -0,0 +1,1416 @@
+package shop.alien.store.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.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.excelVo.DictionaryLibraryExcelVo;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.store.service.DictionaryLibraryService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 平台字典库管理ServiceImpl
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DictionaryLibraryServiceImpl extends ServiceImpl<StoreDictionaryMapper, StoreDictionary> implements DictionaryLibraryService {
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 查询字典库(三级树形结构)
+     * 如果传入dictDetail,则查询匹配的记录及其父级和子级树形结构
+     * 如果不传dictDetail,则返回完整的字典库树形结构
+     *
+     * @param dictDetail 字典描述(可选),如果传入则根据此参数过滤
+     * @return 字典库树形结构列表
+     */
+    @Override
+    public List<StoreDictionary> queryDictionaryLibraryTree(String dictDetail) {
+        // 如果传入了dictDetail,使用过滤逻辑
+        if (StringUtils.isNotBlank(dictDetail)) {
+            return selectDictBydictDetail(dictDetail);
+        }
+        
+        // 否则使用原有逻辑:查询所有字典库数据
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        List<StoreDictionary> storeDictionaryList = storeDictionaryMapper.selectList(queryWrapper);
+
+        // 构建三级树形结构
+        return buildTreeOptimized(storeDictionaryList);
+    }
+
+    /**
+     * 构建树形结构(优化版)
+     *
+     * @param flatList 扁平列表
+     * @return 树形结构列表
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 创建三个存储结构
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();  // ID到节点的映射
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();  // 父ID到子节点列表的映射
+        List<StoreDictionary> result = new ArrayList<>();  // 结果列表
+
+        // 填充nodeMap和parentChildMap
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            // 存入节点映射
+            nodeMap.put(id, entity);
+
+            // 初始化子节点列表
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            // 如果是根节点(parentId为null或0),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        // 建立父子关系
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增字典库(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典库信息
+     * @return 新增结果
+     */
+    @Override
+    public boolean addDictionaryLibrary(StoreDictionary storeDictionary) {
+        // 参数校验
+        if (storeDictionary == null || StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典库描述不能为空");
+        }
+
+        // 设置固定字段
+//        storeDictionary.setTypeName("dictionary_library");
+//        if (StringUtils.isBlank(storeDictionary.getTypeDetail())) {
+//            storeDictionary.setTypeDetail("字典库");
+//        }
+        storeDictionary.setDeleteFlag(0);
+        storeDictionary.setCreatedTime(new Date());
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        // 如果是二级或三级,验证父节点是否存在
+        if (parentId != 0) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+            if (parent == null || parent.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("父节点不存在或已删除");
+            }
+        }
+
+        // 生成 dict_id:根据 typeName 查询最大 dict_id,然后 +1
+        String typeName = storeDictionary.getTypeName();
+        if (StringUtils.isBlank(typeName)) {
+            throw new IllegalArgumentException("typeName不能为空");
+        }
+        String maxDictId = getMaxDictIdByTypeName(typeName);
+        int nextDictId = (maxDictId == null ? 1 : Integer.parseInt(maxDictId)) + 1;
+        storeDictionary.setDictId(String.valueOf(nextDictId));
+
+        // 生成 sort_id
+        Integer maxSortId = getMaxSortIdByParentId(parentId);
+        storeDictionary.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        // 保存
+        return this.save(storeDictionary);
+    }
+
+    /**
+     * 修改字典库(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典库信息
+     * @return 修改结果
+     */
+    @Override
+    public boolean updateDictionaryLibrary(StoreDictionary storeDictionary) {
+        // 参数校验
+        if (storeDictionary == null || storeDictionary.getId() == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+        if (StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典库描述不能为空");
+        }
+
+        // 查询原记录
+        StoreDictionary existing = storeDictionaryMapper.selectById(storeDictionary.getId());
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        // 判断是否是一级或二级节点
+        Integer existingParentId = existing.getParentId();
+        boolean isFirstLevel = (existingParentId == null || existingParentId == 0);
+        boolean isSecondLevel = false;
+        
+        // 如果不是一级节点,判断是否是二级节点(父节点是一级节点)
+        if (!isFirstLevel) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(existingParentId);
+            if (parent != null) {
+                Integer grandParentId = parent.getParentId();
+                isSecondLevel = (grandParentId == null || grandParentId == 0);
+            }
+        }
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        // 标准化parentId用于比较(null转为0)
+        Integer normalizedParentId = (parentId == null) ? 0 : parentId;
+        Integer normalizedExistingParentId = (existingParentId == null) ? 0 : existingParentId;
+        Integer oldSortId = existing.getSortId();
+        Integer newSortId = storeDictionary.getSortId();
+        boolean parentIdChanged = !normalizedParentId.equals(normalizedExistingParentId);
+        
+        // 判断是否修改了关键字段(parent_id)
+        boolean keyFieldChanged = parentIdChanged;
+        
+        // 如果是一级或二级节点,且修改了关键字段(parent_id),检查是否有未删除的子节点
+        if ((isFirstLevel || isSecondLevel) && keyFieldChanged) {
+            boolean hasChildren = hasUndeletedChildren(existing.getId());
+            if (hasChildren) {
+                throw new IllegalArgumentException("该节点存在未删除的子节点,不允许修改关键字段(父节点)");
+            }
+        }
+        
+        // 如果修改了 parentId,需要验证新的父节点是否存在
+        if (parentIdChanged) {
+            if (parentId != 0) {
+                StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+                if (parent == null || parent.getDeleteFlag() == 1) {
+                    throw new IllegalArgumentException("父节点不存在或已删除");
+                }
+            }
+            
+            // 如果用户指定了新位置(newSortId不为空),使用用户指定的位置
+            // 否则,使用新节点下的最大+1
+            if (newSortId == null) {
+                Integer maxSortId = getMaxSortIdByParentId(parentId);
+                int nextSortId = (maxSortId == null ? 1 : maxSortId) + 1;
+                newSortId = nextSortId;
+                storeDictionary.setSortId(newSortId);
+            }
+            
+            // 原节点下,原位置之后的记录需要前移(dictId - 1)
+            adjustSortOrderForMoveOut(existing.getId(), normalizedExistingParentId, oldSortId);
+            
+            // 新节点下,如果指定了新位置,需要调整新节点下的排序
+            if (newSortId != null) {
+                try {
+                    int newOrder = newSortId;
+                    Integer maxSortId = getMaxSortIdByParentId(parentId);
+                    int maxOrder = (maxSortId == null ? 0 : maxSortId);
+                    // 如果新位置不是最大+1,需要调整新节点下的排序
+                    if (newOrder <= maxOrder) {
+                        adjustSortOrderForMoveIn(existing.getId(), normalizedParentId, newSortId);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析新sortId: {}", newSortId, e);
+                }
+            }
+        }
+
+        // 设置固定字段
+//        storeDictionary.setTypeName("dictionary_library");
+        // 如果没有指定删除标记,保持原有值;如果指定了,使用新值
+        Integer oldHidden = existing.getHidden();
+        Integer newHidden = storeDictionary.getHidden() == null ? oldHidden : storeDictionary.getHidden();
+        storeDictionary.setHidden(newHidden);
+        storeDictionary.setDeleteFlag(existing.getDeleteFlag()); // 保持原有删除标记
+        storeDictionary.setCreatedTime(existing.getCreatedTime()); // 保持原有创建时间
+        storeDictionary.setCreatedUserId(existing.getCreatedUserId()); // 保持原有创建人
+        storeDictionary.setUpdatedTime(new Date()); // 更新修改时间
+
+        // 处理排序逻辑:如果 parentId 没有改变,但 dictId 改变了,需要调整同级其他记录的排序
+        if (!parentIdChanged) {
+            // 如果 dictId 为空,保持原有值
+            if (newSortId == null) {
+                storeDictionary.setSortId(oldSortId);
+            } else if (!newSortId.equals(oldSortId)) {
+                // 调整同级其他记录的排序
+                adjustSortOrder(existing.getId(), normalizedParentId, oldSortId, newSortId);
+            }
+        }
+
+        // 更新当前节点
+        boolean updateResult = this.updateById(storeDictionary);
+        
+        // 如果是一级或二级节点,且hidden值发生了变化,同步更新所有子节点的hidden值
+        boolean hiddenChanged = (oldHidden == null && newHidden != null) || 
+                                (oldHidden != null && !oldHidden.equals(newHidden));
+        if (updateResult && (isFirstLevel || isSecondLevel) && hiddenChanged && newHidden != null) {
+            updateChildrenHidden(existing.getId(), newHidden);
+        }
+        
+        return updateResult;
+    }
+
+    /**
+     * 判断节点是否有未删除的子节点
+     *
+     * @param parentId 父节点ID
+     * @return 如果有未删除的子节点返回 true,否则返回 false
+     */
+    private boolean hasUndeletedChildren(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary child = storeDictionaryMapper.selectOne(queryWrapper);
+        return child != null;
+    }
+
+    /**
+     * 递归更新子节点的显示/隐藏状态
+     * 当一级或二级节点的hidden值改变时,同步更新其所有子节点的hidden值
+     *
+     * @param parentId 父节点ID
+     * @param hidden 新的hidden值(0:不隐藏, 1:隐藏)
+     */
+    private void updateChildrenHidden(Integer parentId, Integer hidden) {
+        if (parentId == null || hidden == null) {
+            return;
+        }
+        
+        // 查询所有未删除的子节点
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+        
+        // 批量更新子节点的hidden值
+        Date updateTime = new Date();
+        for (StoreDictionary child : children) {
+            child.setHidden(hidden);
+            child.setUpdatedTime(updateTime);
+            storeDictionaryMapper.updateById(child);
+            
+            // 递归更新子节点的子节点(二级节点的子节点是三级节点)
+            updateChildrenHidden(child.getId(), hidden);
+        }
+    }
+
+    /**
+     * 根据 typeName 获取最大 dict_id
+     *
+     * @param typeName 类型名称
+     * @return 最大 dict_id,如果不存在则返回 null
+     */
+    private String getMaxDictIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null ? maxDict.getDictId() : null;
+    }
+
+    /**
+     * 根据parentId获取最大的sortId
+     *
+     * @param parentId 父节点ID
+     * @return 最大的sortId,如果不存在则返回null
+     */
+    private Integer getMaxSortIdByParentId(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+        
+        queryWrapper.orderByDesc(StoreDictionary::getSortId);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null && maxDict.getSortId() != null ? maxDict.getSortId() : null;
+    }
+
+    /**
+     * 调整排序:当记录的dictId改变时,调整同级其他记录的排序
+     * 上升则顺推:如果新dictId < 原dictId,将原dictId到新dictId之间的记录的dictId都+1
+     * 下降则顺升:如果新dictId > 原dictId,将原dictId到新dictId之间的记录的dictId都-1
+     *
+     * @param currentId 当前记录ID
+     * @param parentId 父节点ID
+     * @param oldSortId 原sortId
+     * @param newSortId 新sortId
+     */
+    private void adjustSortOrder(Integer currentId, Integer parentId, Integer oldSortId, Integer newSortId) {
+        try {
+            int oldOrder = oldSortId;
+            int newOrder = newSortId;
+            
+            // 如果排序没有改变,直接返回
+            if (oldOrder == newOrder) {
+                return;
+            }
+            
+            // 查询同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (parentId == null || parentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, parentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 上升则顺推:新dictId < 原dictId
+            if (newOrder < oldOrder) {
+                // 将新dictId到原dictId之间的记录的dictId都+1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder >= newOrder && siblingOrder < oldOrder) {
+                            sibling.setSortId(siblingOrder + 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            } 
+            // 下降则顺升:新dictId > 原dictId
+            else if (newOrder > oldOrder) {
+                // 将原dictId到新dictId之间的记录的dictId都-1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder > oldOrder && siblingOrder <= newOrder) {
+                            sibling.setSortId(siblingOrder - 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("调整排序失败,sortId格式错误: oldSortId={}, newSortId={}", oldSortId, newSortId, e);
+        } catch (Exception e) {
+            log.error("调整排序失败", e);
+        }
+    }
+
+    /**
+     * 处理从原节点移出时的排序调整:原位置之后的记录需要前移(dictId - 1)
+     *
+     * @param currentId 当前记录ID
+     * @param oldParentId 原父节点ID
+     * @param oldSortId 原sortId
+     */
+    private void adjustSortOrderForMoveOut(Integer currentId, Integer oldParentId, Integer oldSortId) {
+        try {
+            int oldOrder = oldSortId;
+            
+            // 查询原节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (oldParentId == null || oldParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, oldParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 原位置之后的记录需要前移(dictId - 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder > oldOrder) {
+                        sibling.setSortId(siblingOrder - 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移出节点排序调整失败,sortId格式错误: oldSortId={}", oldSortId, e);
+        } catch (Exception e) {
+            log.error("移出节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 处理移入新节点时的排序调整:新位置之后的记录需要后移(dictId + 1)
+     *
+     * @param currentId 当前记录ID
+     * @param newParentId 新父节点ID
+     * @param newSortId 新sortId
+     */
+    private void adjustSortOrderForMoveIn(Integer currentId, Integer newParentId, Integer newSortId) {
+        try {
+            int newOrder = newSortId;
+            
+            // 查询新节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (newParentId == null || newParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, newParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 新位置之后的记录需要后移(dictId + 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder >= newOrder) {
+                        sibling.setSortId(siblingOrder + 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析dictId: {}", sibling.getDictId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移入节点排序调整失败,sortId格式错误: newSortId={}", newSortId, e);
+        } catch (Exception e) {
+            log.error("移入节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 批量导入字典库
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> importDictionaryLibrary(MultipartFile file) {
+        log.info("DictionaryLibraryServiceImpl.importDictionaryLibrary fileName={}", 
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            if (file == null || file.isEmpty()) {
+                return R.fail("文件不能为空");
+            }
+
+            String fileName = file.getOriginalFilename();
+            if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+                return R.fail("文件格式不正确,请上传Excel文件");
+            }
+
+            List<String> errorMessages = new ArrayList<>();
+            int successCount = 0;
+            int totalCount = 0;
+
+            // 使用POI读取Excel
+            try (InputStream inputStream = file.getInputStream();
+                 Workbook workbook = new XSSFWorkbook(inputStream)) {
+                Sheet sheet = workbook.getSheetAt(0);
+
+                // 获取表头
+                Row headerRow = sheet.getRow(5);
+                if (headerRow == null) {
+                    return R.fail("Excel文件格式不正确,缺少表头");
+                }
+
+                // 构建字段映射(表头名称 -> 列索引)
+                Map<String, Integer> headerMap = new HashMap<>();
+                Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                    Cell cell = headerRow.getCell(i);
+                    if (cell != null) {
+                        String headerName = getCellValueAsString(cell);
+                        if (StringUtils.isNotBlank(headerName)) {
+                            headerMap.put(headerName.trim(), i);
+                        }
+                    }
+                }
+
+                // 读取数据行
+                for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                    Row row = sheet.getRow(rowIndex);
+                    if (row == null) {
+                        continue;
+                    }
+
+                    // 检查是否为空行
+                    boolean isEmptyRow = true;
+                    for (int i = 0; i < row.getLastCellNum(); i++) {
+                        Cell cell = row.getCell(i);
+                        if (cell != null && cell.getCellType() != CellType.BLANK) {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                isEmptyRow = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isEmptyRow) {
+                        continue;
+                    }
+
+                    totalCount++;
+                    DictionaryLibraryExcelVo excelVo = new DictionaryLibraryExcelVo();
+
+                    // 读取每个字段
+                    for (Field field : fields) {
+                        if (!field.isAnnotationPresent(ExcelHeader.class)) {
+                            continue;
+                        }
+                        ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                        String headerName = excelHeader.value();
+                        Integer colIndex = headerMap.get(headerName);
+                        if (colIndex == null) {
+                            continue;
+                        }
+
+                        Cell cell = row.getCell(colIndex);
+                        if (cell == null) {
+                            continue;
+                        }
+
+                        field.setAccessible(true);
+                        try {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                field.set(excelVo, cellValue.trim());
+                            }
+                        } catch (Exception e) {
+                            log.warn("读取字段{}失败:{}", headerName, e.getMessage());
+                        }
+                    }
+
+                    // 处理导入数据
+                    try {
+                        processImportData(excelVo, rowIndex + 1, errorMessages);
+                        successCount++;
+                    } catch (Exception e) {
+                        errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                        log.error("导入第{}行数据失败", rowIndex + 1, e);
+                    }
+                }
+            }
+
+            // 构建返回消息
+            StringBuilder message = new StringBuilder();
+            message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+            if (!errorMessages.isEmpty()) {
+                message.append("\n失败详情:\n");
+                int maxErrors = Math.min(errorMessages.size(), 10); // 最多显示10条错误
+                for (int i = 0; i < maxErrors; i++) {
+                    message.append(errorMessages.get(i)).append("\n");
+                }
+                if (errorMessages.size() > 10) {
+                    message.append(String.format("...还有%d条错误未显示", errorMessages.size() - 10));
+                }
+            }
+
+            if (successCount == 0) {
+                return R.fail(message.toString());
+            } else if (totalCount - successCount > 0) {
+                return R.success(message.toString());
+            } else {
+                return R.success(message.toString());
+            }
+        } catch (Exception e) {
+            log.error("导入字典库失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理导入数据
+     *
+     * @param excelVo Excel数据对象
+     * @param rowIndex 行号
+     * @param errorMessages 错误消息列表
+     */
+    private void processImportData(DictionaryLibraryExcelVo excelVo, int rowIndex, List<String> errorMessages) {
+        // 校验必填字段
+        if (StringUtils.isBlank(excelVo.getFirstLevelName())) {
+            throw new IllegalArgumentException("一级分类名称不能为空");
+        }
+
+        String firstLevelName = excelVo.getFirstLevelName().trim();
+        String secondLevelName = StringUtils.isNotBlank(excelVo.getSecondLevelName()) 
+                ? excelVo.getSecondLevelName().trim() : null;
+        String thirdLevelName = StringUtils.isNotBlank(excelVo.getThirdLevelName()) 
+                ? excelVo.getThirdLevelName().trim() : null;
+
+        // 确定要创建的级别
+        if (StringUtils.isNotBlank(thirdLevelName)) {
+            // 创建三级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, 3, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(secondLevelName)) {
+            // 创建二级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, null, 2, excelVo.getHidden(), errorMessages, rowIndex);
+        } else {
+            // 创建一级分类
+            createOrUpdateLevel(firstLevelName, null, null, 1, excelVo.getHidden(), errorMessages, rowIndex);
+        }
+    }
+
+    /**
+     * 创建或更新分类级别
+     *
+     * @param firstLevelName 一级分类名称
+     * @param secondLevelName 二级分类名称
+     * @param thirdLevelName 三级分类名称
+     * @param level 级别(1、2、3)
+     * @param hidden 状态
+     * @param errorMessages 错误消息列表
+     * @param rowIndex 行号
+     */
+    private void createOrUpdateLevel(String firstLevelName, String secondLevelName, String thirdLevelName,
+                                     int level, String hidden, List<String> errorMessages, int rowIndex) {
+        try {
+            // 查找或创建一级分类
+            StoreDictionary firstLevel = findOrCreateLevel(firstLevelName, null, 0, hidden);
+            if (firstLevel == null) {
+                throw new IllegalArgumentException("创建一级分类失败");
+            }
+            if (level >= 2 && StringUtils.isNotBlank(secondLevelName)) {
+                // 查找或创建二级分类
+                StoreDictionary secondLevel = findOrCreateLevel(secondLevelName, firstLevel.getId(), 1, hidden);
+                if (secondLevel == null) {
+                    throw new IllegalArgumentException("创建二级分类失败");
+                }
+
+                if (level >= 3 && StringUtils.isNotBlank(thirdLevelName)) {
+                    // 查找或创建三级分类
+                    StoreDictionary thirdLevel = findOrCreateLevel(thirdLevelName, secondLevel.getId(), 2, hidden);
+                    if (thirdLevel == null) {
+                        throw new IllegalArgumentException("创建三级分类失败");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 查找或创建分类
+     *
+     * @param dictDetail 分类名称
+     * @param parentId 父节点ID
+     * @param expectedLevel 期望的级别(用于校验)
+     * @param hidden 状态
+     * @return 分类对象
+     */
+    private StoreDictionary findOrCreateLevel(String dictDetail, Integer parentId, int expectedLevel, String hidden) {
+        // 查找是否存在
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictDetail, dictDetail);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+        
+        StoreDictionary existing = storeDictionaryMapper.selectOne(queryWrapper);
+        
+        if (existing != null) {
+            return existing;
+        }
+
+        // 创建新分类
+        StoreDictionary newDict = new StoreDictionary();
+        newDict.setDictDetail(dictDetail);
+        // 一级为经营版块 ,二级为经营种类,三级为分类
+        if (expectedLevel == 0) {
+            newDict.setTypeDetail("举报");
+            newDict.setTypeName("report");
+        } else if (expectedLevel == 1) {
+            newDict.setTypeDetail("举报种类");
+            newDict.setTypeName("report_type");
+        } else if (expectedLevel == 2) {
+            newDict.setTypeDetail("分类");
+            newDict.setTypeName("report_classify");
+        }
+        newDict.setParentId(parentId == null ? null : parentId);
+        newDict.setHidden(StringUtils.isNotBlank(hidden) && hidden.equals("隐藏") ? 1 : 0);
+        newDict.setDeleteFlag(0);
+        newDict.setCreatedTime(new Date());
+
+        // 生成 dict_id
+        String maxDictId = getMaxDictIdByTypeName("dictionary_library");
+        newDict.setDictId(String.valueOf(maxDictId == null ? 1 : Integer.parseInt(maxDictId) + 1));
+        
+        // 生成 sort_id
+        Integer maxSortId = getMaxSortIdByParentId(newDict.getParentId());
+        newDict.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        boolean saved = this.save(newDict);
+        return saved ? newDict : null;
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     *
+     * @param cell 单元格
+     * @return 字符串值
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+        
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue().toString();
+                } else {
+                    // 处理数字,避免科学计数法
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 下载字典库导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("DictionaryLibraryServiceImpl.downloadTemplate");
+        
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        try {
+            Sheet sheet = workbook.createSheet("字典库导入模板");
+
+            // 获取带有 @ExcelHeader 注解的字段
+            Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+            List<Field> excelFields = new ArrayList<>();
+            for (Field field : fields) {
+                if (field.isAnnotationPresent(ExcelHeader.class)) {
+                    excelFields.add(field);
+                }
+            }
+
+            // 创建表头样式
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerFont.setFontHeightInPoints((short) 12);
+            CellStyle headerCellStyle = workbook.createCellStyle();
+            headerCellStyle.setFont(headerFont);
+            headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+            headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerCellStyle.setBorderBottom(BorderStyle.THIN);
+            headerCellStyle.setBorderTop(BorderStyle.THIN);
+            headerCellStyle.setBorderLeft(BorderStyle.THIN);
+            headerCellStyle.setBorderRight(BorderStyle.THIN);
+
+            // 创建表头
+            Row headerRow = sheet.createRow(0);
+            for (int i = 0; i < excelFields.size(); i++) {
+                Field field = excelFields.get(i);
+                ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(excelHeader.value());
+                cell.setCellStyle(headerCellStyle);
+            }
+
+            // 设置列宽
+            sheet.setColumnWidth(0, 25 * 256); // 一级分类名称
+            sheet.setColumnWidth(1, 25 * 256); // 二级分类名称
+            sheet.setColumnWidth(2, 25 * 256); // 三级分类名称
+            sheet.setColumnWidth(3, 15 * 256); // 状态(隐藏/显示)
+
+            // 设置响应头(必须在获取输出流之前设置)
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("字典库导入模板", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            // 输出到响应流
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+        } finally {
+            // 确保 workbook 被正确关闭
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭workbook失败", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public int deleteDictionaryLibrary(Long id) {
+        return storeDictionaryMapper.deleteById(id);
+    }
+
+    /**
+     * 修改字典库状态(通过id修改hidden为1)
+     *
+     * @param id 字典库ID
+     * @return 修改结果
+     */
+    @Override
+    public int updateHiddenStatus(Long id) {
+        if (id == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+
+        // 查询原记录
+        StoreDictionary existing = storeDictionaryMapper.selectById(id);
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        // 设置hidden为1
+        existing.setHidden(1);
+        existing.setUpdatedTime(new Date());
+
+        // 更新记录
+        return storeDictionaryMapper.updateById(existing);
+    }
+
+    /**
+     * 根据dictDetail查询字典库(包含父级或子级树形结构)
+     * 支持查询多个同名数据及其父级和子级
+     *
+     * @param dictDetail 字典描述
+     * @return 字典库树形结构列表
+     */
+    @Override
+    public List<StoreDictionary> selectDictBydictDetail(String dictDetail) {
+        if (StringUtils.isBlank(dictDetail)) {
+            throw new IllegalArgumentException("dictDetail不能为空");
+        }
+
+        // 通过dictDetail模糊查询所有匹配的记录(可能有多个同名数据)
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.like(StoreDictionary::getDictDetail, dictDetail);
+        
+        List<StoreDictionary> matchedDicts = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (matchedDicts == null || matchedDicts.isEmpty()) {
+            log.warn("未找到dictDetail为[{}]的记录", dictDetail);
+            return new ArrayList<>();
+        }
+
+        log.info("找到{}条dictDetail为[{}]的记录", matchedDicts.size(), dictDetail);
+
+        // 用于存储每个根节点对应的所有节点(按根节点分组)
+        Map<Integer, Set<Integer>> rootToNodeIdsMap = new HashMap<>();
+        
+        // 遍历所有匹配的记录,收集相关节点ID
+        for (StoreDictionary currentDict : matchedDicts) {
+            Integer parentId = currentDict.getParentId();
+            Integer rootId;
+            
+            if (parentId != null && parentId != 0) {
+                // 如果有父级,查找根节点
+                rootId = findRootId(currentDict.getId());
+                log.info("记录ID[{}]有父级[{}], 根节点ID[{}]", currentDict.getId(), parentId, rootId);
+                
+                // 收集从根节点到当前节点的路径上的所有节点ID
+                List<Integer> pathNodeIds = new ArrayList<>();
+                collectPathNodes(currentDict.getId(), pathNodeIds);
+                log.info("路径节点IDs: {}", pathNodeIds);
+                
+                // 收集该根节点下的所有相关节点ID(包括路径节点及其所有子节点)
+                Set<Integer> nodeIds = rootToNodeIdsMap.computeIfAbsent(rootId, k -> new HashSet<>());
+                for (Integer nodeId : pathNodeIds) {
+                    nodeIds.add(nodeId);
+                    collectChildrenIds(nodeId, nodeIds);
+                }
+            } else {
+                // 如果没有父级,以当前节点为根
+                rootId = currentDict.getId();
+                log.info("记录ID[{}]没有父级, 作为根节点", currentDict.getId());
+                
+                // 收集该根节点下的所有相关节点ID
+                Set<Integer> nodeIds = rootToNodeIdsMap.computeIfAbsent(rootId, k -> new HashSet<>());
+                nodeIds.add(currentDict.getId());
+                collectChildrenIds(currentDict.getId(), nodeIds);
+            }
+        }
+        
+        // 收集所有需要返回的节点数据
+        Map<Integer, StoreDictionary> allNodeMap = new HashMap<>();
+        for (Set<Integer> nodeIds : rootToNodeIdsMap.values()) {
+            for (Integer nodeId : nodeIds) {
+                if (!allNodeMap.containsKey(nodeId)) {
+                    StoreDictionary node = storeDictionaryMapper.selectById(nodeId);
+                    if (node != null && node.getDeleteFlag() == 0) {
+                        allNodeMap.put(nodeId, node);
+                    }
+                }
+            }
+        }
+        
+        log.info("总共收集到{}个节点", allNodeMap.size());
+        
+        // 构建树形结构,为每个唯一的根节点构建一棵树
+        List<StoreDictionary> resultList = new ArrayList<>();
+        
+        for (Map.Entry<Integer, Set<Integer>> entry : rootToNodeIdsMap.entrySet()) {
+            Integer rootId = entry.getKey();
+            Set<Integer> nodeIds = entry.getValue();
+            
+            log.info("处理根节点[{}], 包含{}个节点", rootId, nodeIds.size());
+            
+            // 从所有节点中筛选出该根节点下的相关节点
+            List<StoreDictionary> relatedNodes = new ArrayList<>();
+            for (Integer nodeId : nodeIds) {
+                StoreDictionary node = allNodeMap.get(nodeId);
+                if (node != null) {
+                    relatedNodes.add(node);
+                }
+            }
+            
+            log.info("根节点[{}]筛选出{}个相关节点", rootId, relatedNodes.size());
+            
+            // 如果找到相关节点,构建树形结构
+            if (!relatedNodes.isEmpty()) {
+                StoreDictionary root = buildTreeFromNodeList(relatedNodes, rootId);
+                if (root != null) {
+                    resultList.add(root);
+                    log.info("成功构建根节点[{}]的树形结构", rootId);
+                } else {
+                    log.warn("构建根节点[{}]的树形结构失败", rootId);
+                }
+            } else {
+                log.warn("根节点[{}]没有相关节点", rootId);
+            }
+        }
+        
+        log.info("最终返回{}棵树", resultList.size());
+        return resultList;
+    }
+    
+    /**
+     * 收集节点的所有子节点ID(递归)
+     *
+     * @param nodeId 节点ID
+     * @param childrenIds 子节点ID集合
+     */
+    private void collectChildrenIds(Integer nodeId, Set<Integer> childrenIds) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getParentId, nodeId);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children != null && !children.isEmpty()) {
+            for (StoreDictionary child : children) {
+                if (!childrenIds.contains(child.getId())) {
+                    childrenIds.add(child.getId());
+                    collectChildrenIds(child.getId(), childrenIds);
+                }
+            }
+        }
+    }
+
+    /**
+     * 递归查找根节点ID
+     *
+     * @param id 当前节点ID
+     * @return 根节点ID
+     */
+    private Integer findRootId(Integer id) {
+        StoreDictionary dict = storeDictionaryMapper.selectById(id);
+        if (dict == null || dict.getDeleteFlag() == 1) {
+            // 如果节点不存在或已删除,返回当前ID作为根节点
+            return id;
+        }
+        
+        Integer parentId = dict.getParentId();
+        if (parentId == null || parentId == 0) {
+            return id;
+        }
+        
+        return findRootId(parentId);
+    }
+
+    /**
+     * 收集从当前节点到根节点的路径上的所有节点ID
+     *
+     * @param nodeId 当前节点ID
+     * @param pathNodeIds 路径节点ID列表
+     */
+    private void collectPathNodes(Integer nodeId, List<Integer> pathNodeIds) {
+        StoreDictionary dict = storeDictionaryMapper.selectById(nodeId);
+        if (dict == null || dict.getDeleteFlag() == 1) {
+            return;
+        }
+        
+        // 避免重复添加
+        if (!pathNodeIds.contains(nodeId)) {
+            pathNodeIds.add(nodeId);
+        }
+        
+        Integer parentId = dict.getParentId();
+        if (parentId != null && parentId != 0) {
+            collectPathNodes(parentId, pathNodeIds);
+        }
+    }
+
+    /**
+     * 从树形结构中过滤出包含指定dictDetail的树
+     *
+     * @param treeList 树形结构列表
+     * @param dictDetail 字典描述
+     * @return 过滤后的树形结构列表
+     */
+    private List<StoreDictionary> filterTreeByDictDetail(List<StoreDictionary> treeList, String dictDetail) {
+        List<StoreDictionary> result = new ArrayList<>();
+        
+        for (StoreDictionary node : treeList) {
+            // 检查当前节点是否包含目标dictDetail
+            if (containsDictDetail(node, dictDetail)) {
+                // 如果包含,复制节点并保留树形结构
+                StoreDictionary filteredNode = filterNodeByDictDetail(node, dictDetail);
+                if (filteredNode != null) {
+                    result.add(filteredNode);
+                }
+            }
+        }
+        
+        return result;
+    }
+
+    /**
+     * 检查节点及其子树是否包含指定dictDetail
+     *
+     * @param node 节点
+     * @param dictDetail 字典描述
+     * @return 是否包含
+     */
+    private boolean containsDictDetail(StoreDictionary node, String dictDetail) {
+        if (node == null) {
+            return false;
+        }
+        
+        if (dictDetail.equals(node.getDictDetail())) {
+            return true;
+        }
+        
+        List<StoreDictionary> children = node.getStoreDictionaryList();
+        if (children != null && !children.isEmpty()) {
+            for (StoreDictionary child : children) {
+                if (containsDictDetail(child, dictDetail)) {
+                    return true;
+                }
+            }
+        }
+        
+        return false;
+    }
+
+    /**
+     * 过滤节点,只保留包含目标dictDetail的路径
+     *
+     * @param node 节点
+     * @param dictDetail 字典描述
+     * @return 过滤后的节点
+     */
+    private StoreDictionary filterNodeByDictDetail(StoreDictionary node, String dictDetail) {
+        if (node == null) {
+            return null;
+        }
+        
+        StoreDictionary result = new StoreDictionary();
+        result.setId(node.getId());
+        result.setTypeName(node.getTypeName());
+        result.setTypeDetail(node.getTypeDetail());
+        result.setParentId(node.getParentId());
+        result.setDictId(node.getDictId());
+        result.setDictDetail(node.getDictDetail());
+        result.setDeleteFlag(node.getDeleteFlag());
+        result.setCreatedTime(node.getCreatedTime());
+        result.setCreatedUserId(node.getCreatedUserId());
+        result.setUpdatedTime(node.getUpdatedTime());
+        result.setUpdatedUserId(node.getUpdatedUserId());
+        result.setHidden(node.getHidden());
+        result.setSortId(node.getSortId());
+        
+        List<StoreDictionary> filteredChildren = new ArrayList<>();
+        List<StoreDictionary> children = node.getStoreDictionaryList();
+        
+        if (children != null && !children.isEmpty()) {
+            for (StoreDictionary child : children) {
+                if (containsDictDetail(child, dictDetail)) {
+                    StoreDictionary filteredChild = filterNodeByDictDetail(child, dictDetail);
+                    if (filteredChild != null) {
+                        filteredChildren.add(filteredChild);
+                    }
+                }
+            }
+        }
+        
+        result.setStoreDictionaryList(filteredChildren);
+        
+        // 如果当前节点匹配,或者有子节点,则返回
+        if (dictDetail.equals(node.getDictDetail()) || !filteredChildren.isEmpty()) {
+            return result;
+        }
+        
+        return null;
+    }
+
+    /**
+     * 收集节点及其所有子节点
+     *
+     * @param nodeId 节点ID
+     * @param nodeList 节点列表
+     */
+    private void collectNodeAndChildren(Integer nodeId, List<StoreDictionary> nodeList) {
+        StoreDictionary node = storeDictionaryMapper.selectById(nodeId);
+        if (node == null || node.getDeleteFlag() == 1) {
+            return;
+        }
+        
+        // 检查是否已经添加过(避免重复)
+        boolean alreadyAdded = nodeList.stream().anyMatch(n -> n.getId().equals(nodeId));
+        if (!alreadyAdded) {
+            nodeList.add(node);
+        }
+        
+        // 查询所有子节点
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getParentId, nodeId);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children != null && !children.isEmpty()) {
+            for (StoreDictionary child : children) {
+                collectNodeAndChildren(child.getId(), nodeList);
+            }
+        }
+    }
+
+    /**
+     * 收集节点及其所有子节点(使用Map去重)
+     *
+     * @param nodeId 节点ID
+     * @param nodeMap 节点Map(用于去重)
+     */
+    private void collectNodeAndChildren(Integer nodeId, Map<Integer, StoreDictionary> nodeMap) {
+        StoreDictionary node = storeDictionaryMapper.selectById(nodeId);
+        if (node == null || node.getDeleteFlag() == 1) {
+            return;
+        }
+        
+        // 添加到Map(自动去重)
+        nodeMap.put(nodeId, node);
+        
+        // 查询所有子节点
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getParentId, nodeId);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children != null && !children.isEmpty()) {
+            for (StoreDictionary child : children) {
+                collectNodeAndChildren(child.getId(), nodeMap);
+            }
+        }
+    }
+
+    /**
+     * 从节点列表构建树形结构
+     *
+     * @param nodeList 节点列表
+     * @param rootId 根节点ID
+     * @return 根节点
+     */
+    private StoreDictionary buildTreeFromNodeList(List<StoreDictionary> nodeList, Integer rootId) {
+        // 创建ID到节点的映射
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();
+        for (StoreDictionary node : nodeList) {
+            nodeMap.put(node.getId(), node);
+            node.setStoreDictionaryList(new ArrayList<>());
+        }
+        
+        // 找到根节点
+        StoreDictionary root = nodeMap.get(rootId);
+        if (root == null) {
+            return null;
+        }
+        
+        // 建立父子关系
+        for (StoreDictionary node : nodeList) {
+            if (node.getId().equals(rootId)) {
+                continue; // 跳过根节点
+            }
+            
+            Integer parentId = node.getParentId();
+            if (parentId != null && parentId != 0) {
+                StoreDictionary parent = nodeMap.get(parentId);
+                if (parent != null) {
+                    parent.getStoreDictionaryList().add(node);
+                }
+            }
+        }
+        
+        // 对子节点列表按sortId排序
+        sortChildren(root);
+        
+        return root;
+    }
+
+    /**
+     * 递归排序子节点
+     *
+     * @param node 节点
+     */
+    private void sortChildren(StoreDictionary node) {
+        if (node == null || node.getStoreDictionaryList() == null) {
+            return;
+        }
+        
+        node.getStoreDictionaryList().sort((a, b) -> {
+            Integer sortIdA = a.getSortId() != null ? a.getSortId() : 0;
+            Integer sortIdB = b.getSortId() != null ? b.getSortId() : 0;
+            return sortIdA.compareTo(sortIdB);
+        });
+        
+        // 递归排序所有子节点
+        for (StoreDictionary child : node.getStoreDictionaryList()) {
+            sortChildren(child);
+        }
+    }
+}
+

+ 1238 - 0
alien-store/src/main/java/shop/alien/store/service/impl/FilterConditionServiceImpl.java

@@ -0,0 +1,1238 @@
+package shop.alien.store.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.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.excelVo.DictionaryLibraryExcelVo;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.store.service.FilterConditionService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 平台筛选条件管理ServiceImpl
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class FilterConditionServiceImpl extends ServiceImpl<StoreDictionaryMapper, StoreDictionary> implements FilterConditionService {
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 查询筛选条件(四级树形结构)
+     * 如果传入dictDetail,则查询匹配的记录及其父级和子级树形结构
+     * 如果不传dictDetail,则返回完整的字典库树形结构
+     *
+     * @param dictDetail 字典描述(可选),如果传入则根据此参数过滤
+     * @return 筛选条件树形结构列表
+     */
+    @Override
+    public List<StoreDictionary> queryFilterConditionTree(String dictDetail) {
+        // 如果传入了dictDetail,使用过滤逻辑
+        if (StringUtils.isNotBlank(dictDetail)) {
+            return selectDictBydictDetail(dictDetail);
+        }
+        
+        // 否则使用原有逻辑:查询所有筛选条件数据
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        List<StoreDictionary> filterConditionList = storeDictionaryMapper.selectList(queryWrapper);
+
+        // 构建四级树形结构
+        return buildTreeOptimized(filterConditionList);
+    }
+    
+    /**
+     * 根据dictDetail查询筛选条件(包含父级或子级树形结构)
+     * 支持查询多个同名数据及其父级和子级
+     *
+     * @param dictDetail 字典描述
+     * @return 筛选条件树形结构列表
+     */
+    private List<StoreDictionary> selectDictBydictDetail(String dictDetail) {
+        if (StringUtils.isBlank(dictDetail)) {
+            throw new IllegalArgumentException("dictDetail不能为空");
+        }
+
+        // 通过dictDetail模糊查询所有匹配的记录(可能有多个同名数据)
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.like(StoreDictionary::getDictDetail, dictDetail);
+        
+        List<StoreDictionary> matchedDicts = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (matchedDicts == null || matchedDicts.isEmpty()) {
+            log.warn("未找到dictDetail为[{}]的记录", dictDetail);
+            return new ArrayList<>();
+        }
+
+        log.info("找到{}条dictDetail为[{}]的记录", matchedDicts.size(), dictDetail);
+
+        // 用于存储每个根节点对应的所有节点(按根节点分组)
+        Map<Integer, Set<Integer>> rootToNodeIdsMap = new HashMap<>();
+        
+        // 遍历所有匹配的记录,收集相关节点ID
+        for (StoreDictionary currentDict : matchedDicts) {
+            Integer parentId = currentDict.getParentId();
+            Integer rootId;
+            
+            if (parentId != null && parentId != 0) {
+                // 如果有父级,查找根节点
+                rootId = findRootId(currentDict.getId());
+                log.info("记录ID[{}]有父级[{}], 根节点ID[{}]", currentDict.getId(), parentId, rootId);
+                
+                // 收集从根节点到当前节点的路径上的所有节点ID
+                List<Integer> pathNodeIds = new ArrayList<>();
+                collectPathNodes(currentDict.getId(), pathNodeIds);
+                log.info("路径节点IDs: {}", pathNodeIds);
+                
+                // 收集该根节点下的所有相关节点ID(包括路径节点及其所有子节点)
+                Set<Integer> nodeIds = rootToNodeIdsMap.computeIfAbsent(rootId, k -> new HashSet<>());
+                for (Integer nodeId : pathNodeIds) {
+                    nodeIds.add(nodeId);
+                    collectChildrenIds(nodeId, nodeIds);
+                }
+            } else {
+                // 如果没有父级,以当前节点为根
+                rootId = currentDict.getId();
+                log.info("记录ID[{}]没有父级, 作为根节点", currentDict.getId());
+                
+                // 收集该根节点下的所有相关节点ID
+                Set<Integer> nodeIds = rootToNodeIdsMap.computeIfAbsent(rootId, k -> new HashSet<>());
+                nodeIds.add(currentDict.getId());
+                collectChildrenIds(currentDict.getId(), nodeIds);
+            }
+        }
+        
+        // 收集所有需要返回的节点数据
+        Map<Integer, StoreDictionary> allNodeMap = new HashMap<>();
+        for (Set<Integer> nodeIds : rootToNodeIdsMap.values()) {
+            for (Integer nodeId : nodeIds) {
+                if (!allNodeMap.containsKey(nodeId)) {
+                    StoreDictionary node = storeDictionaryMapper.selectById(nodeId);
+                    if (node != null && node.getDeleteFlag() == 0) {
+                        allNodeMap.put(nodeId, node);
+                    }
+                }
+            }
+        }
+        
+        log.info("总共收集到{}个节点", allNodeMap.size());
+        
+        // 构建树形结构,为每个唯一的根节点构建一棵树
+        List<StoreDictionary> resultList = new ArrayList<>();
+        
+        for (Map.Entry<Integer, Set<Integer>> entry : rootToNodeIdsMap.entrySet()) {
+            Integer rootId = entry.getKey();
+            Set<Integer> nodeIds = entry.getValue();
+            
+            log.info("处理根节点[{}], 包含{}个节点", rootId, nodeIds.size());
+            
+            // 从所有节点中筛选出该根节点下的相关节点
+            List<StoreDictionary> relatedNodes = new ArrayList<>();
+            for (Integer nodeId : nodeIds) {
+                StoreDictionary node = allNodeMap.get(nodeId);
+                if (node != null) {
+                    relatedNodes.add(node);
+                }
+            }
+            
+            log.info("根节点[{}]筛选出{}个相关节点", rootId, relatedNodes.size());
+            
+            // 如果找到相关节点,构建树形结构
+            if (!relatedNodes.isEmpty()) {
+                StoreDictionary root = buildTreeFromNodeList(relatedNodes, rootId);
+                if (root != null) {
+                    resultList.add(root);
+                    log.info("成功构建根节点[{}]的树形结构", rootId);
+                } else {
+                    log.warn("构建根节点[{}]的树形结构失败", rootId);
+                }
+            } else {
+                log.warn("根节点[{}]没有相关节点", rootId);
+            }
+        }
+        
+        log.info("最终返回{}棵树", resultList.size());
+        return resultList;
+    }
+    
+    /**
+     * 收集节点的所有子节点ID(递归)
+     *
+     * @param nodeId 节点ID
+     * @param childrenIds 子节点ID集合
+     */
+    private void collectChildrenIds(Integer nodeId, Set<Integer> childrenIds) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getParentId, nodeId);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children != null && !children.isEmpty()) {
+            for (StoreDictionary child : children) {
+                if (!childrenIds.contains(child.getId())) {
+                    childrenIds.add(child.getId());
+                    collectChildrenIds(child.getId(), childrenIds);
+                }
+            }
+        }
+    }
+
+    /**
+     * 递归查找根节点ID
+     *
+     * @param id 当前节点ID
+     * @return 根节点ID
+     */
+    private Integer findRootId(Integer id) {
+        StoreDictionary dict = storeDictionaryMapper.selectById(id);
+        if (dict == null || dict.getDeleteFlag() == 1) {
+            // 如果节点不存在或已删除,返回当前ID作为根节点
+            return id;
+        }
+        
+        Integer parentId = dict.getParentId();
+        if (parentId == null || parentId == 0) {
+            return id;
+        }
+        
+        return findRootId(parentId);
+    }
+
+    /**
+     * 收集从当前节点到根节点的路径上的所有节点ID
+     *
+     * @param nodeId 当前节点ID
+     * @param pathNodeIds 路径节点ID列表
+     */
+    private void collectPathNodes(Integer nodeId, List<Integer> pathNodeIds) {
+        StoreDictionary dict = storeDictionaryMapper.selectById(nodeId);
+        if (dict == null || dict.getDeleteFlag() == 1) {
+            return;
+        }
+        
+        // 避免重复添加
+        if (!pathNodeIds.contains(nodeId)) {
+            pathNodeIds.add(nodeId);
+        }
+        
+        Integer parentId = dict.getParentId();
+        if (parentId != null && parentId != 0) {
+            collectPathNodes(parentId, pathNodeIds);
+        }
+    }
+
+    /**
+     * 从节点列表构建树形结构
+     *
+     * @param nodeList 节点列表
+     * @param rootId 根节点ID
+     * @return 根节点
+     */
+    private StoreDictionary buildTreeFromNodeList(List<StoreDictionary> nodeList, Integer rootId) {
+        // 创建ID到节点的映射
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();
+        for (StoreDictionary node : nodeList) {
+            nodeMap.put(node.getId(), node);
+            node.setStoreDictionaryList(new ArrayList<>());
+        }
+        
+        // 找到根节点
+        StoreDictionary root = nodeMap.get(rootId);
+        if (root == null) {
+            return null;
+        }
+        
+        // 建立父子关系
+        for (StoreDictionary node : nodeList) {
+            if (node.getId().equals(rootId)) {
+                continue; // 跳过根节点
+            }
+            
+            Integer parentId = node.getParentId();
+            if (parentId != null && parentId != 0) {
+                StoreDictionary parent = nodeMap.get(parentId);
+                if (parent != null) {
+                    parent.getStoreDictionaryList().add(node);
+                }
+            }
+        }
+        
+        // 对子节点列表按sortId排序
+        sortChildren(root);
+        
+        return root;
+    }
+
+    /**
+     * 递归排序子节点
+     *
+     * @param node 节点
+     */
+    private void sortChildren(StoreDictionary node) {
+        if (node == null || node.getStoreDictionaryList() == null) {
+            return;
+        }
+        
+        node.getStoreDictionaryList().sort((a, b) -> {
+            Integer sortIdA = a.getSortId() != null ? a.getSortId() : 0;
+            Integer sortIdB = b.getSortId() != null ? b.getSortId() : 0;
+            return sortIdA.compareTo(sortIdB);
+        });
+        
+        // 递归排序所有子节点
+        for (StoreDictionary child : node.getStoreDictionaryList()) {
+            sortChildren(child);
+        }
+    }
+
+    /**
+     * 构建树形结构(优化版)
+     *
+     * @param flatList 扁平列表
+     * @return 树形结构列表
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 创建三个存储结构
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();  // ID到节点的映射
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();  // 父ID到子节点列表的映射
+        List<StoreDictionary> result = new ArrayList<>();  // 结果列表
+
+        // 填充nodeMap和parentChildMap
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            // 存入节点映射
+            nodeMap.put(id, entity);
+
+            // 初始化子节点列表
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            // 如果是根节点(parentId为null或0),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        // 建立父子关系
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增筛选条件(支持一级、二级、三级、四级)
+     *
+     * @param StoreDictionary 筛选条件信息
+     * @return 新增结果
+     */
+    @Override
+    public boolean addFilterCondition(StoreDictionary StoreDictionary) {
+        // 参数校验
+        if (StoreDictionary == null || StringUtils.isBlank(StoreDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("筛选条件描述不能为空");
+        }
+
+        // 设置固定字段
+        StoreDictionary.setDeleteFlag(0);
+        StoreDictionary.setCreatedTime(new Date());
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = StoreDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        // 如果是二级或三级,验证父节点是否存在
+        if (parentId != 0) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+            if (parent == null || parent.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("父节点不存在或已删除");
+            }
+        }
+
+        // 生成 condition_id:根据 typeName 查询最大 condition_id,然后 +1
+        String typeName = StoreDictionary.getTypeName();
+        if (StringUtils.isBlank(typeName)) {
+            throw new IllegalArgumentException("typeName不能为空");
+        }
+        String maxConditionId = getMaxConditionIdByTypeName(typeName);
+        int nextConditionId = (maxConditionId == null ? 1 : Integer.parseInt(maxConditionId)) + 1;
+        StoreDictionary.setDictId(String.valueOf(nextConditionId));
+
+        // 生成 sort_id
+        Integer maxSortId = getMaxSortIdByParentId(parentId);
+        StoreDictionary.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        // 保存
+        return this.save(StoreDictionary);
+    }
+
+    /**
+     * 修改筛选条件(支持一级、二级、三级、四级)
+     *
+     * @param StoreDictionary 筛选条件信息
+     * @return 修改结果
+     */
+    @Override
+    public boolean updateFilterCondition(StoreDictionary StoreDictionary) {
+        // 参数校验
+        if (StoreDictionary == null || StoreDictionary.getId() == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+        if (StringUtils.isBlank(StoreDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("筛选条件描述不能为空");
+        }
+
+        // 查询原记录
+        StoreDictionary existing = storeDictionaryMapper.selectById(StoreDictionary.getId());
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        // 判断是否是一级或二级节点
+        Integer existingParentId = existing.getParentId();
+        boolean isFirstLevel = (existingParentId == null || existingParentId == 0);
+        boolean isSecondLevel = false;
+        
+        // 如果不是一级节点,判断是否是二级节点(父节点是一级节点)
+        if (!isFirstLevel) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(existingParentId);
+            if (parent != null) {
+                Integer grandParentId = parent.getParentId();
+                isSecondLevel = (grandParentId == null || grandParentId == 0);
+            }
+        }
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = StoreDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        // 标准化parentId用于比较(null转为0)
+        Integer normalizedParentId = (parentId == null) ? 0 : parentId;
+        Integer normalizedExistingParentId = (existingParentId == null) ? 0 : existingParentId;
+        Integer oldSortId = existing.getSortId();
+        Integer newSortId = StoreDictionary.getSortId();
+        boolean parentIdChanged = !normalizedParentId.equals(normalizedExistingParentId);
+        
+        // 判断是否修改了关键字段(parent_id)
+        boolean keyFieldChanged = parentIdChanged;
+        
+        // 如果是一级或二级节点,且修改了关键字段(parent_id),检查是否有未删除的子节点
+        if ((isFirstLevel || isSecondLevel) && keyFieldChanged) {
+            boolean hasChildren = hasUndeletedChildren(existing.getId());
+            if (hasChildren) {
+                throw new IllegalArgumentException("该节点存在未删除的子节点,不允许修改关键字段(父节点)");
+            }
+        }
+        
+        // 如果修改了 parentId,需要验证新的父节点是否存在
+        if (parentIdChanged) {
+            if (parentId != 0) {
+                StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+                if (parent == null || parent.getDeleteFlag() == 1) {
+                    throw new IllegalArgumentException("父节点不存在或已删除");
+                }
+            }
+            
+            // 如果用户指定了新位置(newSortId不为空),使用用户指定的位置
+            // 否则,使用新节点下的最大+1
+            if (newSortId == null) {
+                Integer maxSortId = getMaxSortIdByParentId(parentId);
+                int nextSortId = (maxSortId == null ? 1 : maxSortId) + 1;
+                newSortId = nextSortId;
+                StoreDictionary.setSortId(newSortId);
+            }
+            
+            // 原节点下,原位置之后的记录需要前移(sortId - 1)
+            adjustSortOrderForMoveOut(existing.getId(), normalizedExistingParentId, oldSortId);
+            
+            // 新节点下,如果指定了新位置,需要调整新节点下的排序
+            if (newSortId != null) {
+                try {
+                    int newOrder = newSortId;
+                    Integer maxSortId = getMaxSortIdByParentId(parentId);
+                    int maxOrder = (maxSortId == null ? 0 : maxSortId);
+                    // 如果新位置不是最大+1,需要调整新节点下的排序
+                    if (newOrder <= maxOrder) {
+                        adjustSortOrderForMoveIn(existing.getId(), normalizedParentId, newSortId);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析新sortId: {}", newSortId, e);
+                }
+            }
+        }
+
+        // 设置固定字段
+        // 如果没有指定删除标记,保持原有值;如果指定了,使用新值
+        Integer oldHidden = existing.getHidden();
+        Integer newHidden = StoreDictionary.getHidden() == null ? oldHidden : StoreDictionary.getHidden();
+        StoreDictionary.setHidden(newHidden);
+        StoreDictionary.setDeleteFlag(existing.getDeleteFlag()); // 保持原有删除标记
+        StoreDictionary.setCreatedTime(existing.getCreatedTime()); // 保持原有创建时间
+        StoreDictionary.setCreatedUserId(existing.getCreatedUserId()); // 保持原有创建人
+        StoreDictionary.setUpdatedTime(new Date()); // 更新修改时间
+
+        // 处理排序逻辑:如果 parentId 没有改变,但 sortId 改变了,需要调整同级其他记录的排序
+        if (!parentIdChanged) {
+            // 如果 sortId 为空,保持原有值
+            if (newSortId == null) {
+                StoreDictionary.setSortId(oldSortId);
+            } else if (!newSortId.equals(oldSortId)) {
+                // 调整同级其他记录的排序
+                adjustSortOrder(existing.getId(), normalizedParentId, oldSortId, newSortId);
+            }
+        }
+
+        // 更新当前节点
+        boolean updateResult = this.updateById(StoreDictionary);
+        
+        // 如果是一级或二级节点,且hidden值发生了变化,同步更新所有子节点的hidden值
+        boolean hiddenChanged = (oldHidden == null && newHidden != null) || 
+                                (oldHidden != null && !oldHidden.equals(newHidden));
+        if (updateResult && (isFirstLevel || isSecondLevel) && hiddenChanged && newHidden != null) {
+            updateChildrenHidden(existing.getId(), newHidden);
+        }
+        
+        return updateResult;
+    }
+
+    /**
+     * 判断节点是否有未删除的子节点
+     *
+     * @param parentId 父节点ID
+     * @return 如果有未删除的子节点返回 true,否则返回 false
+     */
+    private boolean hasUndeletedChildren(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary child = storeDictionaryMapper.selectOne(queryWrapper);
+        return child != null;
+    }
+
+    /**
+     * 递归更新子节点的显示/隐藏状态
+     * 当一级或二级节点的hidden值改变时,同步更新其所有子节点的hidden值
+     *
+     * @param parentId 父节点ID
+     * @param hidden 新的hidden值(0:不隐藏, 1:隐藏)
+     */
+    private void updateChildrenHidden(Integer parentId, Integer hidden) {
+        if (parentId == null || hidden == null) {
+            return;
+        }
+        
+        // 查询所有未删除的子节点
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+        
+        // 批量更新子节点的hidden值
+        Date updateTime = new Date();
+        for (StoreDictionary child : children) {
+            child.setHidden(hidden);
+            child.setUpdatedTime(updateTime);
+            storeDictionaryMapper.updateById(child);
+            
+            // 递归更新子节点的子节点(二级节点的子节点是三级节点)
+            updateChildrenHidden(child.getId(), hidden);
+        }
+    }
+
+    /**
+     * 根据 typeName 获取最大 condition_id
+     *
+     * @param typeName 类型名称
+     * @return 最大 condition_id,如果不存在则返回 null
+     */
+    private String getMaxConditionIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary maxCondition = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxCondition != null ? maxCondition.getDictId() : null;
+    }
+
+    /**
+     * 根据parentId获取最大的sortId
+     *
+     * @param parentId 父节点ID
+     * @return 最大的sortId,如果不存在则返回null
+     */
+    private Integer getMaxSortIdByParentId(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+        
+        queryWrapper.orderByDesc(StoreDictionary::getSortId);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary maxCondition = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxCondition != null && maxCondition.getSortId() != null ? maxCondition.getSortId() : null;
+    }
+
+    /**
+     * 调整排序:当记录的sortId改变时,调整同级其他记录的排序
+     * 上升则顺推:如果新sortId < 原sortId,将原sortId到新sortId之间的记录的sortId都+1
+     * 下降则顺升:如果新sortId > 原sortId,将原sortId到新sortId之间的记录的sortId都-1
+     *
+     * @param currentId 当前记录ID
+     * @param parentId 父节点ID
+     * @param oldSortId 原sortId
+     * @param newSortId 新sortId
+     */
+    private void adjustSortOrder(Integer currentId, Integer parentId, Integer oldSortId, Integer newSortId) {
+        try {
+            int oldOrder = oldSortId;
+            int newOrder = newSortId;
+            
+            // 如果排序没有改变,直接返回
+            if (oldOrder == newOrder) {
+                return;
+            }
+            
+            // 查询同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (parentId == null || parentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, parentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 上升则顺推:新sortId < 原sortId
+            if (newOrder < oldOrder) {
+                // 将新sortId到原sortId之间的记录的sortId都+1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder >= newOrder && siblingOrder < oldOrder) {
+                            sibling.setSortId(siblingOrder + 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            } 
+            // 下降则顺升:新sortId > 原sortId
+            else if (newOrder > oldOrder) {
+                // 将原sortId到新sortId之间的记录的sortId都-1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder > oldOrder && siblingOrder <= newOrder) {
+                            sibling.setSortId(siblingOrder - 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("调整排序失败,sortId格式错误: oldSortId={}, newSortId={}", oldSortId, newSortId, e);
+        } catch (Exception e) {
+            log.error("调整排序失败", e);
+        }
+    }
+
+    /**
+     * 处理从原节点移出时的排序调整:原位置之后的记录需要前移(sortId - 1)
+     *
+     * @param currentId 当前记录ID
+     * @param oldParentId 原父节点ID
+     * @param oldSortId 原sortId
+     */
+    private void adjustSortOrderForMoveOut(Integer currentId, Integer oldParentId, Integer oldSortId) {
+        try {
+            int oldOrder = oldSortId;
+            
+            // 查询原节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (oldParentId == null || oldParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, oldParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 原位置之后的记录需要前移(sortId - 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder > oldOrder) {
+                        sibling.setSortId(siblingOrder - 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移出节点排序调整失败,sortId格式错误: oldSortId={}", oldSortId, e);
+        } catch (Exception e) {
+            log.error("移出节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 处理移入新节点时的排序调整:新位置之后的记录需要后移(sortId + 1)
+     *
+     * @param currentId 当前记录ID
+     * @param newParentId 新父节点ID
+     * @param newSortId 新sortId
+     */
+    private void adjustSortOrderForMoveIn(Integer currentId, Integer newParentId, Integer newSortId) {
+        try {
+            int newOrder = newSortId;
+            
+            // 查询新节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (newParentId == null || newParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, newParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 新位置之后的记录需要后移(sortId + 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder >= newOrder) {
+                        sibling.setSortId(siblingOrder + 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移入节点排序调整失败,sortId格式错误: newSortId={}", newSortId, e);
+        } catch (Exception e) {
+            log.error("移入节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 批量导入筛选条件
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> importFilterCondition(MultipartFile file)  {
+        log.info("PlatformBusinessSectionServiceImpl.importBusinessSection fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            if (file == null || file.isEmpty()) {
+                return R.fail("文件不能为空");
+            }
+
+            String fileName = file.getOriginalFilename();
+            if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+                return R.fail("文件格式不正确,请上传Excel文件");
+            }
+
+            List<String> errorMessages = new ArrayList<>();
+            int successCount = 0;
+            int totalCount = 0;
+
+            // 使用POI读取Excel
+            try (InputStream inputStream = file.getInputStream();
+                 Workbook workbook = new XSSFWorkbook(inputStream)) {
+                Sheet sheet = workbook.getSheetAt(0);
+
+                // 获取表头
+                Row headerRow = sheet.getRow(5);
+                if (headerRow == null) {
+                    return R.fail("Excel文件格式不正确,缺少表头");
+                }
+
+                // 构建字段映射(表头名称 -> 列索引)
+                Map<String, Integer> headerMap = new HashMap<>();
+                Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                    Cell cell = headerRow.getCell(i);
+                    if (cell != null) {
+                        String headerName = getCellValueAsString(cell);
+                        if (StringUtils.isNotBlank(headerName)) {
+                            headerMap.put(headerName.trim(), i);
+                        }
+                    }
+                }
+
+                // 读取数据行
+                for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                    Row row = sheet.getRow(rowIndex);
+                    if (row == null) {
+                        continue;
+                    }
+
+                    // 检查是否为空行
+                    boolean isEmptyRow = true;
+                    for (int i = 0; i < row.getLastCellNum(); i++) {
+                        Cell cell = row.getCell(i);
+                        if (cell != null && cell.getCellType() != CellType.BLANK) {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                isEmptyRow = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isEmptyRow) {
+                        continue;
+                    }
+
+                    totalCount++;
+                    DictionaryLibraryExcelVo excelVo = new DictionaryLibraryExcelVo();
+
+                    // 读取每个字段
+                    for (Field field : fields) {
+                        if (!field.isAnnotationPresent(ExcelHeader.class)) {
+                            continue;
+                        }
+                        ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                        String headerName = excelHeader.value();
+                        Integer colIndex = headerMap.get(headerName);
+                        if (colIndex == null) {
+                            continue;
+                        }
+
+                        Cell cell = row.getCell(colIndex);
+                        if (cell == null) {
+                            continue;
+                        }
+
+                        field.setAccessible(true);
+                        try {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                field.set(excelVo, cellValue.trim());
+                            }
+                        } catch (Exception e) {
+                            log.warn("读取字段{}失败:{}", headerName, e.getMessage());
+                        }
+                    }
+
+                    // 处理导入数据
+                    try {
+                        processImportData(excelVo, rowIndex + 1, errorMessages);
+                        successCount++;
+                    } catch (Exception e) {
+                        errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                        log.error("导入第{}行数据失败", rowIndex + 1, e);
+                    }
+                }
+            }
+
+            // 构建返回消息
+            StringBuilder message = new StringBuilder();
+            message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+            if (!errorMessages.isEmpty()) {
+                message.append("\n失败详情:\n");
+                int maxErrors = Math.min(errorMessages.size(), 10); // 最多显示10条错误
+                for (int i = 0; i < maxErrors; i++) {
+                    message.append(errorMessages.get(i)).append("\n");
+                }
+                if (errorMessages.size() > 10) {
+                    message.append(String.format("...还有%d条错误未显示", errorMessages.size() - 10));
+                }
+            }
+
+            if (successCount == 0) {
+                return R.fail(message.toString());
+            } else if (totalCount - successCount > 0) {
+                return R.success(message.toString());
+            } else {
+                return R.success(message.toString());
+            }
+        } catch (Exception e) {
+            log.error("导入经营版块失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     *
+     * @param cell 单元格
+     * @return 字符串值
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue().toString();
+                } else {
+                    // 处理数字,避免科学计数法
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return null;
+        }
+    }
+
+
+    /**
+     * 处理导入数据
+     *
+     * @param excelVo Excel数据对象
+     * @param rowIndex 行号
+     * @param errorMessages 错误消息列表
+     */
+    private void processImportData(DictionaryLibraryExcelVo excelVo, int rowIndex, List<String> errorMessages) {
+        // 校验必填字段
+        if (StringUtils.isBlank(excelVo.getFirstLevelName())) {
+            throw new IllegalArgumentException("一级分类名称不能为空");
+        }
+
+        String firstLevelName = excelVo.getFirstLevelName().trim();
+        String secondLevelName = StringUtils.isNotBlank(excelVo.getSecondLevelName())
+                ? excelVo.getSecondLevelName().trim() : null;
+        String thirdLevelName = StringUtils.isNotBlank(excelVo.getThirdLevelName())
+                ? excelVo.getThirdLevelName().trim() : null;
+        String fourLevelName = StringUtils.isNotBlank(excelVo.getFourLevelName())
+                ? excelVo.getFourLevelName().trim() : null;
+
+        // 确定要创建的级别
+        if (StringUtils.isNotBlank(fourLevelName)) {
+            // 创建四级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, fourLevelName, 4, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(thirdLevelName)) {
+            // 创建三级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, null, 3, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(secondLevelName)) {
+            // 创建二级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, null, null, 2, excelVo.getHidden(), errorMessages, rowIndex);
+        } else {
+            // 创建一级分类
+            createOrUpdateLevel(firstLevelName, null, null, null, 1, excelVo.getHidden(), errorMessages, rowIndex);
+        }
+    }
+
+
+    /**
+     * 下载筛选条件导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("FilterConditionServiceImpl.downloadTemplate");
+        
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        try {
+            Sheet sheet = workbook.createSheet("筛选条件导入模板");
+
+            // 创建表头样式
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerFont.setFontHeightInPoints((short) 12);
+            CellStyle headerCellStyle = workbook.createCellStyle();
+            headerCellStyle.setFont(headerFont);
+            headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+            headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerCellStyle.setBorderBottom(BorderStyle.THIN);
+            headerCellStyle.setBorderTop(BorderStyle.THIN);
+            headerCellStyle.setBorderLeft(BorderStyle.THIN);
+            headerCellStyle.setBorderRight(BorderStyle.THIN);
+
+            // 创建表头
+            Row headerRow = sheet.createRow(0);
+            String[] headers = {"一级分类名称", "二级分类名称", "三级分类名称", "四级分类名称", "状态(隐藏/显示)"};
+            for (int i = 0; i < headers.length; i++) {
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(headers[i]);
+                cell.setCellStyle(headerCellStyle);
+            }
+
+            // 设置列宽
+            sheet.setColumnWidth(0, 25 * 256); // 一级分类名称
+            sheet.setColumnWidth(1, 25 * 256); // 二级分类名称
+            sheet.setColumnWidth(2, 25 * 256); // 三级分类名称
+            sheet.setColumnWidth(3, 25 * 256); // 四级分类名称
+            sheet.setColumnWidth(4, 15 * 256); // 状态(隐藏/显示)
+
+            // 设置响应头(必须在获取输出流之前设置)
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("筛选条件导入模板", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            // 输出到响应流
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+        } finally {
+            // 确保 workbook 被正确关闭
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭workbook失败", e);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 创建或更新分类级别
+     *
+     * @param firstLevelName 一级分类名称
+     * @param secondLevelName 二级分类名称
+     * @param thirdLevelName 三级分类名称
+     * @param fourLevelName 四级分类名称
+     * @param level 级别(1、2、3、4)
+     * @param hidden 状态
+     * @param errorMessages 错误消息列表
+     * @param rowIndex 行号
+     */
+    private void createOrUpdateLevel(String firstLevelName, String secondLevelName, String thirdLevelName,
+                                     String fourLevelName, int level, String hidden, List<String> errorMessages, int rowIndex) {
+        try {
+            // 查找或创建一级分类
+            StoreDictionary firstLevel = findOrCreateLevel(firstLevelName, null, 0, hidden);
+            if (firstLevel == null) {
+                throw new IllegalArgumentException("创建一级分类失败");
+            }
+            if (level >= 2 && StringUtils.isNotBlank(secondLevelName)) {
+                // 查找或创建二级分类
+                StoreDictionary secondLevel = findOrCreateLevel(secondLevelName, firstLevel.getId(), 1, hidden);
+                if (secondLevel == null) {
+                    throw new IllegalArgumentException("创建二级分类失败");
+                }
+
+                if (level >= 3 && StringUtils.isNotBlank(thirdLevelName)) {
+                    // 查找或创建三级分类
+                    StoreDictionary thirdLevel = findOrCreateLevel(thirdLevelName, secondLevel.getId(), 2, hidden);
+                    if (thirdLevel == null) {
+                        throw new IllegalArgumentException("创建三级分类失败");
+                    }
+
+                    if (level >= 4 && StringUtils.isNotBlank(fourLevelName)) {
+                        // 查找或创建四级分类
+                        StoreDictionary fourLevel = findOrCreateLevel(fourLevelName, thirdLevel.getId(), 3, hidden);
+                        if (fourLevel == null) {
+                            throw new IllegalArgumentException("创建四级分类失败");
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 查找或创建分类
+     *
+     * @param dictDetail 分类名称
+     * @param parentId 父节点ID
+     * @param expectedLevel 期望的级别(用于校验)
+     * @param hidden 状态
+     * @return 分类对象
+     */
+    private StoreDictionary findOrCreateLevel(String dictDetail, Integer parentId, int expectedLevel, String hidden) {
+        // 查找是否存在
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictDetail, dictDetail);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectOne(queryWrapper);
+
+        if (existing != null) {
+            return existing;
+        }
+
+        // 创建新分类
+        StoreDictionary newDict = new StoreDictionary();
+        newDict.setDictDetail(dictDetail);
+        // 一级为筛选条件,二级为筛选条件种类,三级为分类,四级为时间范围
+        if (expectedLevel == 0) {
+            newDict.setTypeDetail("筛选条件");
+            newDict.setTypeName("filter_condition");
+        } else if (expectedLevel == 1) {
+            newDict.setTypeDetail("筛选条件种类");
+            newDict.setTypeName("filter_condition_type");
+        } else if (expectedLevel == 2) {
+            newDict.setTypeDetail("分类");
+            newDict.setTypeName("filter_condition_classify");
+        } else if (expectedLevel == 3) {
+            newDict.setTypeDetail("时间范围");
+            newDict.setTypeName("filter_condition_timeRange");
+        }
+        newDict.setParentId(parentId == null ? null : parentId);
+        newDict.setHidden(StringUtils.isNotBlank(hidden) && hidden.equals("隐藏") ? 1 : 0);
+        newDict.setDeleteFlag(0);
+        newDict.setCreatedTime(new Date());
+
+        // 生成 dict_id
+        String maxDictId = getMaxDictIdByTypeName(newDict.getTypeName());
+        newDict.setDictId(String.valueOf(maxDictId == null ? 1 : Integer.parseInt(maxDictId) + 1));
+
+        // 生成 sort_id
+        Integer maxSortId = getMaxSortIdByParentId(newDict.getParentId());
+        newDict.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        boolean saved = this.save(newDict);
+        return saved ? newDict : null;
+    }
+
+    /**
+     * 根据 typeName 获取最大 dict_id
+     *
+     * @param typeName 类型名称
+     * @return 最大 dict_id,如果不存在则返回 null
+     */
+    private String getMaxDictIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null ? maxDict.getDictId() : null;
+    }
+}
+

+ 265 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeCouponServiceImpl.java

@@ -8,10 +8,14 @@ import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 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.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.LifeDiscountCouponStoreFriendDto;
@@ -23,8 +27,10 @@ import shop.alien.store.service.LifeDiscountCouponStoreFriendService;
 import shop.alien.util.common.UniqueRandomNumGenerator;
 import shop.alien.util.common.constant.OrderStatusEnum;
 
+import java.io.InputStream;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.time.format.TextStyle;
@@ -38,6 +44,7 @@ import java.util.stream.Collectors;
  * @version 1.0
  * @date 2024/12/23 15:08
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCoupon> implements LifeCouponService {
@@ -694,4 +701,262 @@ public class LifeCouponServiceImpl extends ServiceImpl<LifeCouponMapper, LifeCou
         return lifeCouponMapper.getNewCouponDetail(id);
     }
 
+    @Override
+    public R<String> importHolidayFromExcel(MultipartFile file) {
+        log.info("LifeCouponServiceImpl.importHolidayFromExcel fileName={}", file.getOriginalFilename());
+
+        if (file == null || file.isEmpty()) {
+            return R.fail("上传文件为空");
+        }
+
+        String fileName = file.getOriginalFilename();
+        if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+            return R.fail("文件格式不正确,请上传Excel文件");
+        }
+
+        List<String> errorMessages = new ArrayList<>();
+        int successCount = 0;
+        int totalCount = 0;
+
+        try (InputStream inputStream = file.getInputStream();
+             XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
+            Sheet sheet = workbook.getSheetAt(0);
+
+            // 获取表头(第6行,索引为5)
+            Row headerRow = sheet.getRow(5);
+            if (headerRow == null) {
+                return R.fail("Excel文件格式不正确,缺少表头");
+            }
+
+            // 构建字段映射(表头名称 -> 列索引)
+            Map<String, Integer> headerMap = new HashMap<>();
+            for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                Cell cell = headerRow.getCell(i);
+                if (cell != null) {
+                    String headerName = getCellValueAsString(cell);
+                    if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(headerName)) {
+                        headerMap.put(headerName.trim(), i);
+                    }
+                }
+            }
+
+            // 验证表头
+            if (!headerMap.containsKey("年份") || !headerMap.containsKey("节日名称") 
+                    || !headerMap.containsKey("开始时间") || !headerMap.containsKey("结束时间")) {
+                return R.fail("Excel文件格式不正确,缺少必要的表头字段(年份、节日名称、开始时间、结束时间)");
+            }
+
+            // 读取数据行(从第7行开始,索引为6)
+            for (int rowIndex = 6; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                Row row = sheet.getRow(rowIndex);
+                if (row == null) {
+                    continue;
+                }
+
+                // 检查是否为空行
+                boolean isEmptyRow = true;
+                for (int i = 0; i < row.getLastCellNum(); i++) {
+                    Cell cell = row.getCell(i);
+                    if (cell != null && cell.getCellType() != CellType.BLANK) {
+                        String cellValue = getCellValueAsString(cell);
+                        if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(cellValue)) {
+                            isEmptyRow = false;
+                            break;
+                        }
+                    }
+                }
+                if (isEmptyRow) {
+                    continue;
+                }
+
+                totalCount++;
+                EssentialHolidayComparison holiday = new EssentialHolidayComparison();
+
+                try {
+                    // 读取年份(必填)
+                    Integer yearColIndex = headerMap.get("年份");
+                    if (yearColIndex == null) {
+                        throw new RuntimeException("缺少年份字段");
+                    }
+                    Cell yearCell = row.getCell(yearColIndex);
+                    String yearValue = getCellValueAsString(yearCell);
+                    if (StringUtils.isEmpty(yearValue)) {
+                        throw new RuntimeException("年份不能为空");
+                    }
+                    // 处理年份可能是数字的情况
+                    if (yearCell != null && yearCell.getCellType() == CellType.NUMERIC) {
+                        double numericValue = yearCell.getNumericCellValue();
+                        yearValue = String.valueOf((long) numericValue);
+                    }
+                    holiday.setParticularYear(yearValue.trim());
+
+                    // 读取节日名称(必填)
+                    Integer nameColIndex = headerMap.get("节日名称");
+                    if (nameColIndex == null) {
+                        throw new RuntimeException("缺少节日名称字段");
+                    }
+                    Cell nameCell = row.getCell(nameColIndex);
+                    String nameValue = getCellValueAsString(nameCell);
+                    if (StringUtils.isEmpty(nameValue)) {
+                        throw new RuntimeException("节日名称不能为空");
+                    }
+                    holiday.setFestivalName(nameValue.trim());
+
+                    // 读取开始时间(必填,格式:2026-01-01)
+                    Integer startTimeColIndex = headerMap.get("开始时间");
+                    if (startTimeColIndex == null) {
+                        throw new RuntimeException("缺少开始时间字段");
+                    }
+                    Cell startTimeCell = row.getCell(startTimeColIndex);
+                    String startTimeValue = getCellValueAsString(startTimeCell);
+                    if (StringUtils.isEmpty(startTimeValue)) {
+                        throw new RuntimeException("开始时间不能为空");
+                    }
+                    Date startTime = parseDate(startTimeValue.trim(), rowIndex + 1);
+                    holiday.setStartTime(startTime);
+
+                    // 读取结束时间(必填,格式:2026-01-01)
+                    Integer endTimeColIndex = headerMap.get("结束时间");
+                    if (endTimeColIndex == null) {
+                        throw new RuntimeException("缺少结束时间字段");
+                    }
+                    Cell endTimeCell = row.getCell(endTimeColIndex);
+                    String endTimeValue = getCellValueAsString(endTimeCell);
+                    if (StringUtils.isEmpty(endTimeValue)) {
+                        throw new RuntimeException("结束时间不能为空");
+                    }
+                    Date endTime = parseDate(endTimeValue.trim(), rowIndex + 1);
+                    holiday.setEndTime(endTime);
+
+                    // 验证结束时间必须大于等于开始时间
+                    if (endTime.before(startTime)) {
+                        throw new RuntimeException("结束时间必须大于等于开始时间");
+                    }
+
+                    // 读取状态(可选,默认启用)
+                    Integer statusColIndex = headerMap.get("状态");
+                    int openFlag = 1; // 默认启用
+                    if (statusColIndex != null) {
+                        Cell statusCell = row.getCell(statusColIndex);
+                        String statusValue = getCellValueAsString(statusCell);
+                        if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(statusValue)) {
+                            String status = statusValue.trim();
+                            if ("启用".equals(status) || "1".equals(status)) {
+                                openFlag = 1;
+                            } else if ("禁用".equals(status) || "0".equals(status)) {
+                                openFlag = 0;
+                            } else {
+                                throw new RuntimeException("状态格式错误,请输入'启用'或'禁用'");
+                            }
+                        }
+                    }
+                    holiday.setOpenFlag(openFlag);
+                    holiday.setDelFlag(0);
+
+                    // 设置节日日期为开始时间
+                    LocalDate startLocalDate = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+                    holiday.setFestivalDate(startLocalDate);
+
+                    // 保存数据
+                    essentialHolidayComparisonMapper.insert(holiday);
+                    successCount++;
+                } catch (Exception e) {
+                    errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                    log.error("导入第{}行数据失败", rowIndex + 1, e);
+                }
+            }
+        } catch (Exception e) {
+            log.error("导入Excel失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+
+        // 构建返回消息
+        StringBuilder message = new StringBuilder();
+        message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+        if (!errorMessages.isEmpty()) {
+            message.append("\n失败详情:\n");
+            for (int i = 0; i < Math.min(errorMessages.size(), 10); i++) {
+                message.append(errorMessages.get(i)).append("\n");
+            }
+            if (errorMessages.size() > 10) {
+                message.append("...还有").append(errorMessages.size() - 10).append("条错误信息");
+            }
+        }
+
+        return R.success(message.toString());
+    }
+
+    /**
+     * 解析日期字符串
+     */
+    private Date parseDate(String dateStr, int rowNum) {
+        try {
+            // 尝试多种日期格式
+            SimpleDateFormat[] formats = {
+                new SimpleDateFormat("yyyy-MM-dd"),
+                new SimpleDateFormat("yyyy/MM/dd"),
+                new SimpleDateFormat("yyyy年MM月dd日")
+            };
+
+            for (SimpleDateFormat format : formats) {
+                try {
+                    return format.parse(dateStr);
+                } catch (Exception e) {
+                    // 继续尝试下一个格式
+                }
+            }
+
+            // 如果都失败,尝试解析数字日期(Excel日期格式)
+            try {
+                double numericValue = Double.parseDouble(dateStr);
+                return org.apache.poi.ss.usermodel.DateUtil.getJavaDate(numericValue);
+            } catch (Exception e) {
+                // 忽略
+            }
+
+            throw new RuntimeException("日期格式错误,请使用格式:2026-01-01");
+        } catch (Exception e) {
+            throw new RuntimeException("日期格式错误,请使用格式:2026-01-01");
+        }
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
+                    // 日期格式
+                    Date date = cell.getDateCellValue();
+                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+                    return sdf.format(date);
+                } else {
+                    // 处理数字,避免科学计数法
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                try {
+                    return cell.getStringCellValue();
+                } catch (Exception e) {
+                    return String.valueOf(cell.getNumericCellValue());
+                }
+            default:
+                return null;
+        }
+    }
+
 }

Некоторые файлы не были показаны из-за большого количества измененных файлов