sujiajie 3 weeks ago
commit
daa2808e6c
24 changed files with 1431 additions and 0 deletions
  1. 36 0
      src/main/java/com/example/demo/sujiajie/common/constant/TravelPlanConstant.java
  2. 24 0
      src/main/java/com/example/demo/sujiajie/common/exception/TravelPlanException.java
  3. 55 0
      src/main/java/com/example/demo/sujiajie/common/result/Result.java
  4. 31 0
      src/main/java/com/example/demo/sujiajie/config/ApplicationConfig.java
  5. 32 0
      src/main/java/com/example/demo/sujiajie/config/ModelMapperConfig.java
  6. 67 0
      src/main/java/com/example/demo/sujiajie/controller/ExceptionHandlerController.java
  7. 128 0
      src/main/java/com/example/demo/sujiajie/controller/TravelPlanController.java
  8. 26 0
      src/main/java/com/example/demo/sujiajie/dto/request/AddCollaboratorRequest.java
  9. 39 0
      src/main/java/com/example/demo/sujiajie/dto/request/AddTravelPlanDetailRequest.java
  10. 29 0
      src/main/java/com/example/demo/sujiajie/dto/request/CreateTravelPlanRequest.java
  11. 26 0
      src/main/java/com/example/demo/sujiajie/dto/request/RecordOperationRequest.java
  12. 33 0
      src/main/java/com/example/demo/sujiajie/dto/request/UpdateTravelPlanRequest.java
  13. 31 0
      src/main/java/com/example/demo/sujiajie/dto/response/TravelPlanDetailResponse.java
  14. 36 0
      src/main/java/com/example/demo/sujiajie/dto/response/TravelPlanResponse.java
  15. 46 0
      src/main/java/com/example/demo/sujiajie/entity/TravelPlan.java
  16. 32 0
      src/main/java/com/example/demo/sujiajie/entity/TravelPlanCollaborator.java
  17. 38 0
      src/main/java/com/example/demo/sujiajie/entity/TravelPlanDetail.java
  18. 33 0
      src/main/java/com/example/demo/sujiajie/entity/TravelPlanOperation.java
  19. 31 0
      src/main/java/com/example/demo/sujiajie/repository/TravelPlanCollaboratorRepository.java
  20. 35 0
      src/main/java/com/example/demo/sujiajie/repository/TravelPlanDetailRepository.java
  21. 31 0
      src/main/java/com/example/demo/sujiajie/repository/TravelPlanOperationRepository.java
  22. 52 0
      src/main/java/com/example/demo/sujiajie/repository/TravelPlanRepository.java
  23. 75 0
      src/main/java/com/example/demo/sujiajie/service/TravelPlanService.java
  24. 465 0
      src/main/java/com/example/demo/sujiajie/service/impl/TravelPlanServiceImpl.java

+ 36 - 0
src/main/java/com/example/demo/sujiajie/common/constant/TravelPlanConstant.java

@@ -0,0 +1,36 @@
+package com.example.demo.sujiajie.common.constant;
+
+/**
+ * 行程规划常量类
+ * 定义系统中使用的常量,如状态码、权限级别等
+ */
+public class TravelPlanConstant {
+
+    /**
+     * 行程状态常量
+     */
+    public static class Status {
+        public static final int DRAFT = 1;       // 草稿
+        public static final int CONFIRMED = 2;   // 已确认
+        public static final int COMPLETED = 3;   // 已完成
+    }
+
+    /**
+     * 操作类型常量
+     */
+    public static class OperationType {
+        public static final int EXPORT = 1;      // 导出
+        public static final int SHARE = 2;       // 分享
+        public static final int SAVE = 3;        // 保存
+        public static final int OTHER = 4;       // 其他
+    }
+
+    /**
+     * 权限级别常量
+     */
+    public static class Permission {
+        public static final int VIEW = 1;        // 仅查看
+        public static final int EDIT = 2;        // 可编辑
+        public static final int ADMIN = 3;       // 管理员
+    }
+}

+ 24 - 0
src/main/java/com/example/demo/sujiajie/common/exception/TravelPlanException.java

@@ -0,0 +1,24 @@
+package com.example.demo.sujiajie.common.exception;
+
+/**
+ * 行程规划自定义异常类
+ * 继承自RuntimeException,用于业务逻辑异常处理
+ */
+public class TravelPlanException extends RuntimeException {
+
+    private int code;
+
+    public TravelPlanException(int code, String message) {
+        super(message);
+        this.code = code;
+    }
+
+    public TravelPlanException(String message) {
+        super(message);
+        this.code = 500;
+    }
+
+    public int getCode() {
+        return code;
+    }
+}

+ 55 - 0
src/main/java/com/example/demo/sujiajie/common/result/Result.java

@@ -0,0 +1,55 @@
+package com.example.demo.sujiajie.common.result;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * 统一返回结果类
+ * 用于API接口的统一返回格式
+ */
+@Data
+public class Result<T> {
+
+    private int code;       // 状态码
+    private String message; // 消息
+    private T data;         // 数据
+
+    /**
+     * 成功返回结果(带数据)
+     */
+    public static <T> Result<T> success(T data) {
+        Result<T> result = new Result<>();
+        result.code = 200;
+        result.message = "success";
+        result.data = data;
+        return result;
+    }
+
+    /**
+     * 成功返回结果(不带数据)
+     */
+    public static <T> Result<T> success() {
+        return success(null);
+    }
+
+    /**
+     * 失败返回结果
+     */
+    public static <T> Result<T> fail(int code, String message) {
+        Result<T> result = new Result<>();
+        result.code = code;
+        result.message = message;
+        result.data = null;
+        return result;
+    }
+
+    /**
+     * 失败返回结果
+     */
+    public static <T> Result<T> fail(String message) {
+        return fail(500, message);
+    }
+
+
+}

+ 31 - 0
src/main/java/com/example/demo/sujiajie/config/ApplicationConfig.java

@@ -0,0 +1,31 @@
+package com.example.demo.sujiajie.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 应用配置类
+ * 配置跨域访问、拦截器等应用级设置
+ */
+@Configuration
+public class ApplicationConfig {
+
+    /**
+     * 配置跨域访问规则
+     * 允许所有来源、方法和头信息
+     */
+    @Bean
+    public WebMvcConfigurer corsConfigurer() {
+        return new WebMvcConfigurer() {
+            @Override
+            public void addCorsMappings(CorsRegistry registry) {
+                registry.addMapping("/**")
+                        .allowedOrigins("*")
+                        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+                        .allowedHeaders("*");
+            }
+        };
+    }
+}

+ 32 - 0
src/main/java/com/example/demo/sujiajie/config/ModelMapperConfig.java

@@ -0,0 +1,32 @@
+package com.example.demo.sujiajie.config;
+
+import org.modelmapper.ModelMapper;
+import org.modelmapper.convention.MatchingStrategies;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * ModelMapper配置类
+ * 用于注册ModelMapper为Spring Bean
+ */
+@Configuration
+public class ModelMapperConfig {
+
+    /**
+     * 注册ModelMapper为Spring Bean
+     */
+    @Bean
+    public ModelMapper modelMapper() {
+        ModelMapper modelMapper = new ModelMapper();
+        
+        // 配置严格匹配
+        modelMapper.getConfiguration()
+                .setMatchingStrategy(MatchingStrategies.STRICT)  // 使用严格匹配策略
+                .setSkipNullEnabled(true)                        // 跳过空值
+                .setFieldMatchingEnabled(true)                   // 启用字段匹配
+                .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE) // 允许访问私有字段
+                .setCollectionsMergeEnabled(false);             // 禁用集合合并,避免懒加载问题
+        
+        return modelMapper;
+    }
+} 

+ 67 - 0
src/main/java/com/example/demo/sujiajie/controller/ExceptionHandlerController.java

@@ -0,0 +1,67 @@
+package com.example.demo.sujiajie.controller;
+
+
+import com.example.demo.sujiajie.common.exception.TravelPlanException;
+import com.example.demo.sujiajie.common.result.Result;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.validation.ConstraintViolationException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 全局异常处理器
+ * 处理应用程序中的异常并返回统一格式的响应
+ */
+@RestControllerAdvice
+public class ExceptionHandlerController {
+
+    /**
+     * 处理行程规划自定义异常
+     */
+    @ExceptionHandler(TravelPlanException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Result<?> handleTravelPlanException(TravelPlanException ex) {
+        return Result.fail(ex.getCode(), ex.getMessage());
+    }
+
+    /**
+     * 处理方法参数验证异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
+        Map<String, String> errors = new HashMap<>();
+        ex.getBindingResult().getFieldErrors().forEach(error ->
+            errors.put(error.getField(), error.getDefaultMessage())
+        );
+        return Result.fail(400, "参数验证失败");
+    }
+
+    /**
+     * 处理约束违反异常
+     */
+    @ExceptionHandler(ConstraintViolationException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {
+        Map<String, String> errors = new HashMap<>();
+        ex.getConstraintViolations().forEach(violation ->
+            errors.put(violation.getPropertyPath().toString(), violation.getMessage())
+        );
+        return Result.fail(400, "参数验证失败");
+    }
+
+    /**
+     * 处理其他未捕获的异常
+     */
+    @ExceptionHandler(Exception.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    public Result<?> handleException(Exception ex) {
+        ex.printStackTrace();
+        return Result.fail(500, "服务器内部错误: " + ex.getMessage());
+    }
+}

+ 128 - 0
src/main/java/com/example/demo/sujiajie/controller/TravelPlanController.java

@@ -0,0 +1,128 @@
+package com.example.demo.sujiajie.controller;
+
+
+import com.example.demo.sujiajie.common.result.Result;
+import com.example.demo.sujiajie.dto.request.*;
+import com.example.demo.sujiajie.dto.response.TravelPlanResponse;
+import com.example.demo.sujiajie.service.TravelPlanService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 行程控制器
+ * 处理与行程相关的HTTP请求
+ */
+@RestController
+@RequestMapping("/api/travel-plans")
+public class TravelPlanController {
+
+    @Autowired
+    private TravelPlanService travelPlanService;
+
+    /**
+     * 创建行程
+     */
+    @PostMapping
+    public Result<?> createTravelPlan(@Valid @RequestBody CreateTravelPlanRequest request) {
+        travelPlanService.createTravelPlan(request);
+        return Result.success();
+    }
+
+    /**
+     * 更新行程
+     */
+    @PutMapping
+    public Result<?> updateTravelPlan(@Valid @RequestBody UpdateTravelPlanRequest request) {
+        travelPlanService.updateTravelPlan(request);
+        return Result.success();
+    }
+
+    /**
+     * 删除行程
+     */
+    @DeleteMapping("/{planId}")
+    public Result<?> deleteTravelPlan(@PathVariable Long planId,
+                                     @RequestParam String userId) {
+        travelPlanService.deleteTravelPlan(planId, userId);
+        return Result.success();
+    }
+
+    /**
+     * 获取行程详情
+     */
+    @GetMapping("/{planId}")
+    public Result<TravelPlanResponse> getTravelPlanDetail(@PathVariable Long planId,
+                                                          @RequestParam String userId) {
+        TravelPlanResponse response = travelPlanService.getTravelPlanDetail(planId, userId);
+        return Result.success(response);
+    }
+
+    /**
+     * 获取用户的行程列表
+     */
+    @GetMapping("/user/{userId}")
+    public Result<List<TravelPlanResponse>> getUserTravelPlans(@PathVariable String userId) {
+        List<TravelPlanResponse> responses = travelPlanService.getUserTravelPlans(userId);
+        return Result.success(responses);
+    }
+
+    /**
+     * 获取用户参与协作的行程列表
+     */
+    @GetMapping("/user/{userId}/collaborated")
+    public Result<List<TravelPlanResponse>> getUserCollaboratedPlans(@PathVariable String userId) {
+        List<TravelPlanResponse> responses = travelPlanService.getUserCollaboratedPlans(userId);
+        return Result.success(responses);
+    }
+
+    /**
+     * 添加行程详情
+     */
+    @PostMapping("/details")
+    public Result<?> addTravelPlanDetail(@Valid @RequestBody AddTravelPlanDetailRequest request) {
+        travelPlanService.addTravelPlanDetail(request);
+        return Result.success();
+    }
+
+    /**
+     * 删除行程详情
+     */
+    @DeleteMapping("/details/{detailId}")
+    public Result<?> deleteTravelPlanDetail(@PathVariable Long detailId,
+                                          @RequestParam String userId) {
+        travelPlanService.deleteTravelPlanDetail(detailId, userId);
+        return Result.success();
+    }
+
+    /**
+     * 添加协作人
+     */
+    @PostMapping("/collaborators")
+    public Result<?> addCollaborator(@Valid @RequestBody AddCollaboratorRequest request) {
+        travelPlanService.addCollaborator(request);
+        return Result.success();
+    }
+
+    /**
+     * 删除协作人
+     */
+    @DeleteMapping("/collaborators")
+    public Result<?> removeCollaborator(@RequestParam Long planId,
+                                      @RequestParam String collabUserId,
+                                      @RequestParam String operatorUserId) {
+        travelPlanService.removeCollaborator(planId, collabUserId, operatorUserId);
+        return Result.success();
+    }
+
+    /**
+     * 记录行程操作
+     */
+    @PostMapping("/operations")
+    public Result<?> recordOperation(@Valid @RequestBody RecordOperationRequest request) {
+        travelPlanService.recordOperation(request);
+        return Result.success();
+    }
+}

+ 26 - 0
src/main/java/com/example/demo/sujiajie/dto/request/AddCollaboratorRequest.java

@@ -0,0 +1,26 @@
+package com.example.demo.sujiajie.dto.request;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 添加协作人请求DTO
+ * 用于接收前端添加协作人的请求参数
+ */
+@Data
+public class AddCollaboratorRequest {
+
+    @NotNull(message = "行程ID不能为空")
+    private Long planId;
+
+    @NotBlank(message = "操作者用户ID不能为空")
+    private String operatorUserId;
+
+    @NotBlank(message = "协作人用户ID不能为空")
+    private String collabUserId;
+
+    @NotNull(message = "权限级别不能为空")
+    private Integer permission; // 1-仅查看 2-可编辑 3-管理员
+}

+ 39 - 0
src/main/java/com/example/demo/sujiajie/dto/request/AddTravelPlanDetailRequest.java

@@ -0,0 +1,39 @@
+package com.example.demo.sujiajie.dto.request;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+/**
+ * 添加行程详情请求DTO
+ * 用于接收前端添加行程详情的请求参数
+ */
+@Data
+public class AddTravelPlanDetailRequest {
+
+    @NotNull(message = "行程ID不能为空")
+    private Long planId;
+
+    @NotBlank(message = "用户ID不能为空")
+    private String userId;
+
+    @NotNull(message = "行程天数不能为空")
+    private Integer daySeq;
+
+    @NotBlank(message = "景点名称不能为空")
+    private String spotName;
+
+    private String spotId;
+
+    private Date startTime;
+
+    private Date endTime;
+
+    private String trafficWay;
+
+    private String trafficDesc;
+
+    private String remark;
+}

+ 29 - 0
src/main/java/com/example/demo/sujiajie/dto/request/CreateTravelPlanRequest.java

@@ -0,0 +1,29 @@
+package com.example.demo.sujiajie.dto.request;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+/**
+ * 创建行程请求DTO
+ * 用于接收前端创建行程的请求参数
+ */
+@Data
+public class CreateTravelPlanRequest {
+
+    @NotBlank(message = "用户ID不能为空")
+    private String userId;
+
+    @NotBlank(message = "行程名称不能为空")
+    private String planName;
+
+    @NotNull(message = "总天数不能为空")
+    private Integer totalDays;
+
+    @NotNull(message = "总预算不能为空")
+    private BigDecimal totalBudget;
+
+    private String preference; // 用户偏好,可选字段
+}

+ 26 - 0
src/main/java/com/example/demo/sujiajie/dto/request/RecordOperationRequest.java

@@ -0,0 +1,26 @@
+package com.example.demo.sujiajie.dto.request;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 记录行程操作请求DTO
+ * 用于接收前端记录行程操作的请求参数
+ */
+@Data
+public class RecordOperationRequest {
+
+    @NotNull(message = "行程ID不能为空")
+    private Long planId;
+
+    @NotBlank(message = "操作用户ID不能为空")
+    private String opUserId;
+
+    @NotNull(message = "操作类型不能为空")
+    private Integer opType;
+
+    @NotBlank(message = "操作描述不能为空")
+    private String opDesc;
+}

+ 33 - 0
src/main/java/com/example/demo/sujiajie/dto/request/UpdateTravelPlanRequest.java

@@ -0,0 +1,33 @@
+package com.example.demo.sujiajie.dto.request;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.NotBlank;
+import java.math.BigDecimal;
+
+/**
+ * 更新行程请求DTO
+ * 用于接收前端更新行程的请求参数
+ */
+@Data
+public class UpdateTravelPlanRequest {
+
+    @NotNull(message = "行程ID不能为空")
+    private Long planId;
+
+    @NotBlank(message = "用户ID不能为空")
+    private String userId;
+
+    private String planName;
+
+    private Integer totalDays;
+
+    private BigDecimal totalBudget;
+
+    private String preference;
+
+    private Integer status; // 行程状态:1-草稿 2-已确认 3-已完成
+
+
+}

+ 31 - 0
src/main/java/com/example/demo/sujiajie/dto/response/TravelPlanDetailResponse.java

@@ -0,0 +1,31 @@
+package com.example.demo.sujiajie.dto.response;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 行程详情响应DTO
+ * 用于返回行程详情信息给前端
+ */
+@Data
+public class TravelPlanDetailResponse {
+
+    private Long detailId;
+
+    private Integer daySeq;
+
+    private String spotId;
+
+    private String spotName;
+
+    private Date startTime;
+
+    private Date endTime;
+
+    private String trafficWay;
+
+    private String trafficDesc;
+
+    private String remark;
+}

+ 36 - 0
src/main/java/com/example/demo/sujiajie/dto/response/TravelPlanResponse.java

@@ -0,0 +1,36 @@
+package com.example.demo.sujiajie.dto.response;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 行程响应DTO
+ * 用于返回行程信息给前端
+ */
+@Data
+public class TravelPlanResponse {
+
+    private Long planId;
+
+    private String userId;
+
+    private String planName;
+
+    private Integer totalDays;
+
+    private BigDecimal totalBudget;
+
+    private String preference;
+
+    private Integer status;
+
+    private Date createTime;
+
+    private Date updateTime;
+
+    private Set<TravelPlanDetailResponse> details = new HashSet<>();
+}

+ 46 - 0
src/main/java/com/example/demo/sujiajie/entity/TravelPlan.java

@@ -0,0 +1,46 @@
+package com.example.demo.sujiajie.entity;
+
+import lombok.Data;
+
+import javax.persistence.*;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 行程主表实体类
+ * 对应数据库中的travel_plans表
+ * 存储行程的基本信息,如名称、天数、预算等
+ */
+@Data
+@Entity
+@Table(name = "travel_plans")
+public class TravelPlan {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long planId;
+
+    private String userId;          // 用户ID
+    private String planName;        // 行程名称
+    private Integer totalDays;      // 总天数
+    private BigDecimal totalBudget; // 总预算
+    private String preference;      // 用户偏好
+    private Integer status;         // 行程状态:1-草稿 2-已确认 3-已完成
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(updatable = false)
+    private Date createTime;        // 创建时间
+
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date updateTime;        // 更新时间
+
+    @OneToMany(mappedBy = "travelPlan", cascade = CascadeType.ALL, orphanRemoval = true)
+    private Set<TravelPlanDetail> details = new HashSet<>(); // 行程详情列表
+
+    @OneToMany(mappedBy = "travelPlan", cascade = CascadeType.ALL, orphanRemoval = true)
+    private Set<TravelPlanCollaborator> collaborators = new HashSet<>(); // 协作人列表
+
+    @OneToMany(mappedBy = "travelPlan", cascade = CascadeType.ALL, orphanRemoval = true)
+    private Set<TravelPlanOperation> operations = new HashSet<>(); // 操作记录列表
+}

+ 32 - 0
src/main/java/com/example/demo/sujiajie/entity/TravelPlanCollaborator.java

@@ -0,0 +1,32 @@
+package com.example.demo.sujiajie.entity;
+
+import lombok.Data;
+
+import javax.persistence.*;
+import java.util.Date;
+
+/**
+ * 行程协作表实体类
+ * 对应数据库中的travel_plan_collaborators表
+ * 存储行程的协作人信息
+ */
+@Data
+@Entity
+@Table(name = "travel_plan_collaborators")
+public class TravelPlanCollaborator {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long collabId;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "plan_id")
+    private TravelPlan travelPlan; // 关联的行程主表
+
+    private String collabUserId;  // 协作人用户ID
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(updatable = false)
+    private Date joinTime;        // 加入时间
+
+    private Integer permission;   // 权限:1-仅查看 2-可编辑 3-管理员
+}

+ 38 - 0
src/main/java/com/example/demo/sujiajie/entity/TravelPlanDetail.java

@@ -0,0 +1,38 @@
+package com.example.demo.sujiajie.entity;
+
+import lombok.Data;
+
+import javax.persistence.*;
+import java.util.Date;
+
+/**
+ * 行程详情表实体类
+ * 对应数据库中的travel_plan_details表
+ * 存储每日行程的详细信息,如景点、时间、交通等
+ */
+@Data
+@Entity
+@Table(name = "travel_plan_details")
+public class TravelPlanDetail {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long detailId;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "plan_id")
+    private TravelPlan travelPlan; // 关联的行程主表
+
+    private Integer daySeq;       // 第几天行程
+    private String spotId;        // 景点ID
+    private String spotName;      // 景点名称
+
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date startTime;       // 开始时间
+
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date endTime;         // 结束时间
+
+    private String trafficWay;    // 交通方式
+    private String trafficDesc;   // 交通详情
+    private String remark;        // 备注信息
+}

+ 33 - 0
src/main/java/com/example/demo/sujiajie/entity/TravelPlanOperation.java

@@ -0,0 +1,33 @@
+package com.example.demo.sujiajie.entity;
+
+import lombok.Data;
+
+import javax.persistence.*;
+import java.util.Date;
+
+/**
+ * 行程操作记录表实体类
+ * 对应数据库中的travel_plan_operations表
+ * 存储行程的操作记录,如导出、分享等
+ */
+@Data
+@Entity
+@Table(name = "travel_plan_operations")
+public class TravelPlanOperation {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long opId;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "plan_id")
+    private TravelPlan travelPlan; // 关联的行程主表
+
+    private Integer opType;       // 操作类型:1-导出 2-分享 3-保存 4-其他
+    private String opUserId;      // 操作人用户ID
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(updatable = false)
+    private Date opTime;          // 操作时间
+
+    private String opDesc;        // 操作描述
+}

+ 31 - 0
src/main/java/com/example/demo/sujiajie/repository/TravelPlanCollaboratorRepository.java

@@ -0,0 +1,31 @@
+package com.example.demo.sujiajie.repository;
+
+
+import com.example.demo.sujiajie.entity.TravelPlanCollaborator;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 行程协作表数据访问接口
+ * 提供对travel_plan_collaborators表的CRUD操作
+ */
+@Repository
+public interface TravelPlanCollaboratorRepository extends JpaRepository<TravelPlanCollaborator, Long> {
+
+    /**
+     * 根据行程ID查询所有协作人
+     */
+    List<TravelPlanCollaborator> findByTravelPlanPlanId(Long planId);
+
+    /**
+     * 根据行程ID和协作人用户ID查询协作关系
+     */
+    TravelPlanCollaborator findByTravelPlanPlanIdAndCollabUserId(Long planId, String collabUserId);
+
+    /**
+     * 删除指定行程的所有协作关系
+     */
+    void deleteByTravelPlanPlanId(Long planId);
+}

+ 35 - 0
src/main/java/com/example/demo/sujiajie/repository/TravelPlanDetailRepository.java

@@ -0,0 +1,35 @@
+package com.example.demo.sujiajie.repository;
+
+import com.example.demo.sujiajie.entity.TravelPlanDetail;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 行程详情表数据访问接口
+ * 提供对travel_plan_details表的CRUD操作
+ */
+@Repository
+public interface TravelPlanDetailRepository extends JpaRepository<TravelPlanDetail, Long> {
+
+    /**
+     * 根据行程ID查询所有行程详情
+     */
+    List<TravelPlanDetail> findByTravelPlanPlanId(Long planId);
+
+    /**
+     * 根据行程ID和天数查询行程详情
+     */
+    List<TravelPlanDetail> findByTravelPlanPlanIdAndDaySeq(Long planId, Integer daySeq);
+
+    /**
+     * 根据行程ID删除所有行程详情
+     */
+    void deleteByTravelPlanPlanId(Long planId);
+
+    /**
+     * 根据详情ID删除行程详情
+     */
+    void deleteByDetailId(Long detailId);
+}

+ 31 - 0
src/main/java/com/example/demo/sujiajie/repository/TravelPlanOperationRepository.java

@@ -0,0 +1,31 @@
+package com.example.demo.sujiajie.repository;
+
+
+import com.example.demo.sujiajie.entity.TravelPlanOperation;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 行程操作记录表数据访问接口
+ * 提供对travel_plan_operations表的CRUD操作
+ */
+@Repository
+public interface TravelPlanOperationRepository extends JpaRepository<TravelPlanOperation, Long> {
+
+    /**
+     * 根据行程ID查询所有操作记录
+     */
+    List<TravelPlanOperation> findByTravelPlanPlanId(Long planId);
+
+    /**
+     * 根据行程ID和操作类型查询操作记录
+     */
+    List<TravelPlanOperation> findByTravelPlanPlanIdAndOpType(Long planId, Integer opType);
+
+    /**
+     * 根据行程ID删除所有操作记录
+     */
+    void deleteByTravelPlanPlanId(Long planId);
+}

+ 52 - 0
src/main/java/com/example/demo/sujiajie/repository/TravelPlanRepository.java

@@ -0,0 +1,52 @@
+package com.example.demo.sujiajie.repository;
+
+
+import com.example.demo.sujiajie.entity.TravelPlan;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 行程主表数据访问接口
+ * 提供对travel_plans表的CRUD操作
+ */
+@Repository
+public interface TravelPlanRepository extends JpaRepository<TravelPlan, Long> {
+
+    /**
+     * 根据用户ID查询行程列表
+     */
+    List<TravelPlan> findByUserId(String userId);
+
+    /**
+     * 查询用户参与协作的行程列表
+     */
+    @Query("SELECT p FROM TravelPlan p JOIN p.collaborators c WHERE c.collabUserId = ?1")
+    List<TravelPlan> findCollaboratedPlans(String userId);
+
+    /**
+     * 查询行程详情(只获取基本信息)
+     */
+    @Query("SELECT p FROM TravelPlan p WHERE p.planId = ?1")
+    TravelPlan findWithDetailsById(Long planId);
+
+    /**
+     * 查询行程详情(包含详情列表)
+     */
+    @Query("SELECT p FROM TravelPlan p LEFT JOIN FETCH p.details WHERE p.planId = ?1")
+    TravelPlan findWithDetailListById(Long planId);
+
+    /**
+     * 查询行程详情(包含协作者列表)
+     */
+    @Query("SELECT p FROM TravelPlan p LEFT JOIN FETCH p.collaborators WHERE p.planId = ?1")
+    TravelPlan findWithCollaboratorsById(Long planId);
+
+    /**
+     * 查询行程详情(包含操作记录列表)
+     */
+    @Query("SELECT p FROM TravelPlan p LEFT JOIN FETCH p.operations WHERE p.planId = ?1")
+    TravelPlan findWithOperationsById(Long planId);
+}

+ 75 - 0
src/main/java/com/example/demo/sujiajie/service/TravelPlanService.java

@@ -0,0 +1,75 @@
+package com.example.demo.sujiajie.service;
+
+
+import com.example.demo.sujiajie.dto.request.*;
+import com.example.demo.sujiajie.dto.response.TravelPlanResponse;
+import com.example.demo.sujiajie.entity.TravelPlan;
+
+import java.util.List;
+
+/**
+ * 行程服务接口
+ * 定义行程管理的业务方法
+ */
+public interface TravelPlanService {
+
+    /**
+     * 创建行程
+     */
+    TravelPlan createTravelPlan(CreateTravelPlanRequest request);
+
+    /**
+     * 更新行程
+     */
+    TravelPlan updateTravelPlan(UpdateTravelPlanRequest request);
+
+    /**
+     * 删除行程
+     */
+    void deleteTravelPlan(Long planId, String userId);
+
+    /**
+     * 获取行程详情
+     */
+    TravelPlanResponse getTravelPlanDetail(Long planId, String userId);
+
+    /**
+     * 获取用户的行程列表
+     */
+    List<TravelPlanResponse> getUserTravelPlans(String userId);
+
+    /**
+     * 获取用户参与协作的行程列表
+     */
+    List<TravelPlanResponse> getUserCollaboratedPlans(String userId);
+
+    /**
+     * 添加行程详情
+     */
+    void addTravelPlanDetail(AddTravelPlanDetailRequest request);
+
+    /**
+     * 删除行程详情
+     */
+    void deleteTravelPlanDetail(Long detailId, String userId);
+
+    /**
+     * 添加协作人
+     */
+    void addCollaborator(AddCollaboratorRequest request);
+
+    /**
+     * 删除协作人
+     */
+    void removeCollaborator(Long planId, String collabUserId, String operatorUserId);
+
+    /**
+     * 记录行程操作
+     */
+    void recordOperation(RecordOperationRequest request);
+
+    /**
+     * 检查用户是否有访问行程的权限
+     */
+    boolean checkPermission(Long planId, String userId, int requiredPermission);
+}

+ 465 - 0
src/main/java/com/example/demo/sujiajie/service/impl/TravelPlanServiceImpl.java

@@ -0,0 +1,465 @@
+package com.example.demo.sujiajie.service.impl;
+
+
+import com.example.demo.sujiajie.common.constant.TravelPlanConstant;
+import com.example.demo.sujiajie.common.exception.TravelPlanException;
+import com.example.demo.sujiajie.dto.request.*;
+import com.example.demo.sujiajie.dto.response.TravelPlanResponse;
+import com.example.demo.sujiajie.dto.response.TravelPlanDetailResponse;
+import com.example.demo.sujiajie.entity.TravelPlan;
+import com.example.demo.sujiajie.entity.TravelPlanCollaborator;
+import com.example.demo.sujiajie.entity.TravelPlanDetail;
+import com.example.demo.sujiajie.entity.TravelPlanOperation;
+import com.example.demo.sujiajie.repository.TravelPlanCollaboratorRepository;
+import com.example.demo.sujiajie.repository.TravelPlanDetailRepository;
+import com.example.demo.sujiajie.repository.TravelPlanOperationRepository;
+import com.example.demo.sujiajie.repository.TravelPlanRepository;
+import com.example.demo.sujiajie.service.TravelPlanService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.modelmapper.ModelMapper;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 行程服务实现类
+ * 实现行程管理的业务逻辑
+ */
+@Service
+public class TravelPlanServiceImpl implements TravelPlanService {
+
+    @Autowired
+    private TravelPlanRepository travelPlanRepository;
+
+    @Autowired
+    private TravelPlanDetailRepository detailRepository;
+
+    @Autowired
+    private TravelPlanCollaboratorRepository collaboratorRepository;
+
+    @Autowired
+    private TravelPlanOperationRepository operationRepository;
+
+    @Autowired
+    private ModelMapper modelMapper;
+
+    /**
+     * 创建行程
+     */
+    @Override
+    @Transactional
+    public TravelPlan createTravelPlan(CreateTravelPlanRequest request) {
+        // 创建行程实体
+        TravelPlan travelPlan = new TravelPlan();
+        travelPlan.setUserId(request.getUserId());
+        travelPlan.setPlanName(request.getPlanName());
+        travelPlan.setTotalDays(request.getTotalDays());
+        travelPlan.setTotalBudget(request.getTotalBudget());
+        travelPlan.setPreference(request.getPreference());
+        travelPlan.setStatus(TravelPlanConstant.Status.DRAFT);
+        travelPlan.setCreateTime(new Date());
+        travelPlan.setUpdateTime(new Date());
+
+        // 保存行程
+        travelPlan = travelPlanRepository.save(travelPlan);
+
+        // 记录创建操作
+        recordOperationInternal(travelPlan.getPlanId(), TravelPlanConstant.OperationType.SAVE,
+                request.getUserId(), "创建行程");
+
+        return travelPlan;
+    }
+
+    /**
+     * 更新行程
+     */
+    @Override
+    @Transactional
+    public TravelPlan updateTravelPlan(UpdateTravelPlanRequest request) {
+        // 获取行程
+        TravelPlan travelPlan = getTravelPlanById(request.getPlanId());
+
+        // 检查权限
+        checkPermissionInternal(travelPlan, request.getUserId(), TravelPlanConstant.Permission.EDIT);
+
+        // 更新行程信息
+        if (request.getPlanName() != null) {
+            travelPlan.setPlanName(request.getPlanName());
+        }
+        if (request.getTotalDays() != null) {
+            travelPlan.setTotalDays(request.getTotalDays());
+        }
+        if (request.getTotalBudget() != null) {
+            travelPlan.setTotalBudget(request.getTotalBudget());
+        }
+        if (request.getPreference() != null) {
+            travelPlan.setPreference(request.getPreference());
+        }
+        if (request.getStatus() != null) {
+            travelPlan.setStatus(request.getStatus());
+        }
+
+        travelPlan.setUpdateTime(new Date());
+
+        // 保存更新
+        travelPlan = travelPlanRepository.save(travelPlan);
+
+        // 记录更新操作
+        recordOperationInternal(travelPlan.getPlanId(), TravelPlanConstant.OperationType.SAVE,
+                request.getUserId(), "更新行程");
+
+        return travelPlan;
+    }
+
+    /**
+     * 删除行程
+     */
+    @Override
+    @Transactional
+    public void deleteTravelPlan(Long planId, String userId) {
+        // 获取行程
+        TravelPlan travelPlan = getTravelPlanById(planId);
+
+        // 检查权限(只有管理员或创建者可以删除)
+        if (!travelPlan.getUserId().equals(userId) &&
+                !checkPermissionInternal(travelPlan, userId, TravelPlanConstant.Permission.ADMIN)) {
+            throw new TravelPlanException("没有权限删除此行程");
+        }
+
+        // 删除行程相关的所有数据
+        detailRepository.deleteByTravelPlanPlanId(planId);
+        collaboratorRepository.deleteByTravelPlanPlanId(planId);
+        operationRepository.deleteByTravelPlanPlanId(planId);
+
+        // 删除行程
+        travelPlanRepository.deleteById(planId);
+
+        // 记录删除操作
+        recordOperationInternal(planId, TravelPlanConstant.OperationType.OTHER,
+                userId, "删除行程");
+    }
+
+    /**
+     * 获取行程详情
+     */
+    @Override
+    @Transactional(readOnly = true)
+    public TravelPlanResponse getTravelPlanDetail(Long planId, String userId) {
+        // 获取行程基本信息
+        TravelPlan travelPlan = travelPlanRepository.findWithDetailsById(planId);
+
+        if (travelPlan == null) {
+            throw new TravelPlanException("行程不存在");
+        }
+
+        // 检查权限
+        if (!checkPermissionInternal(travelPlan, userId, TravelPlanConstant.Permission.VIEW)) {
+            throw new TravelPlanException("没有权限查看此行程");
+        }
+
+        // 分别获取关联数据
+        TravelPlan planWithDetails = travelPlanRepository.findWithDetailListById(planId);
+        TravelPlan planWithCollaborators = travelPlanRepository.findWithCollaboratorsById(planId);
+        TravelPlan planWithOperations = travelPlanRepository.findWithOperationsById(planId);
+
+        // 合并数据
+        travelPlan.setDetails(planWithDetails.getDetails());
+        travelPlan.setCollaborators(planWithCollaborators.getCollaborators());
+        travelPlan.setOperations(planWithOperations.getOperations());
+
+        // 手动映射响应对象
+        TravelPlanResponse response = new TravelPlanResponse();
+        response.setPlanId(travelPlan.getPlanId());
+        response.setUserId(travelPlan.getUserId());
+        response.setPlanName(travelPlan.getPlanName());
+        response.setTotalDays(travelPlan.getTotalDays());
+        response.setTotalBudget(travelPlan.getTotalBudget());
+        response.setPreference(travelPlan.getPreference());
+        response.setStatus(travelPlan.getStatus());
+        response.setCreateTime(travelPlan.getCreateTime());
+        response.setUpdateTime(travelPlan.getUpdateTime());
+        
+        // 手动映射详情列表
+        if (travelPlan.getDetails() != null && !travelPlan.getDetails().isEmpty()) {
+            Set<TravelPlanDetailResponse> detailResponses = new HashSet<>();
+            for (TravelPlanDetail detail : travelPlan.getDetails()) {
+                TravelPlanDetailResponse detailResponse = new TravelPlanDetailResponse();
+                detailResponse.setDetailId(detail.getDetailId());
+                detailResponse.setDaySeq(detail.getDaySeq());
+                detailResponse.setSpotId(detail.getSpotId());
+                detailResponse.setSpotName(detail.getSpotName());
+                detailResponse.setStartTime(detail.getStartTime());
+                detailResponse.setEndTime(detail.getEndTime());
+                detailResponse.setTrafficWay(detail.getTrafficWay());
+                detailResponse.setTrafficDesc(detail.getTrafficDesc());
+                detailResponse.setRemark(detail.getRemark());
+                detailResponses.add(detailResponse);
+            }
+            response.setDetails(detailResponses);
+        }
+
+        return response;
+    }
+
+    /**
+     * 获取用户的行程列表
+     */
+    @Override
+    @Transactional(readOnly = true)
+    public List<TravelPlanResponse> getUserTravelPlans(String userId) {
+        // 查询用户的行程列表
+        List<TravelPlan> travelPlans = travelPlanRepository.findByUserId(userId);
+
+        // 转换为响应DTO列表
+        return convertToResponseList(travelPlans);
+    }
+
+    /**
+     * 获取用户参与协作的行程列表
+     */
+    @Override
+    @Transactional(readOnly = true)
+    public List<TravelPlanResponse> getUserCollaboratedPlans(String userId) {
+        // 查询用户参与协作的行程列表
+        List<TravelPlan> travelPlans = travelPlanRepository.findCollaboratedPlans(userId);
+
+        // 转换为响应DTO列表
+        return convertToResponseList(travelPlans);
+    }
+
+    /**
+     * 添加行程详情
+     */
+    @Override
+    @Transactional
+    public void addTravelPlanDetail(AddTravelPlanDetailRequest request) {
+        // 获取行程
+        TravelPlan travelPlan = getTravelPlanById(request.getPlanId());
+
+        // 检查权限
+        checkPermissionInternal(travelPlan, request.getUserId(), TravelPlanConstant.Permission.EDIT);
+
+        // 创建行程详情实体
+        TravelPlanDetail detail = new TravelPlanDetail();
+        detail.setTravelPlan(travelPlan);
+        detail.setDaySeq(request.getDaySeq());
+        detail.setSpotId(request.getSpotId());
+        detail.setSpotName(request.getSpotName());
+        detail.setStartTime(request.getStartTime());
+        detail.setEndTime(request.getEndTime());
+        detail.setTrafficWay(request.getTrafficWay());
+        detail.setTrafficDesc(request.getTrafficDesc());
+        detail.setRemark(request.getRemark());
+
+        // 保存行程详情
+        detailRepository.save(detail);
+
+        // 更新行程更新时间
+        travelPlan.setUpdateTime(new Date());
+        travelPlanRepository.save(travelPlan);
+
+        // 记录操作
+        recordOperationInternal(travelPlan.getPlanId(), TravelPlanConstant.OperationType.SAVE,
+                request.getUserId(), "添加行程详情");
+    }
+
+    /**
+     * 删除行程详情
+     */
+    @Override
+    @Transactional
+    public void deleteTravelPlanDetail(Long detailId, String userId) {
+        // 获取行程详情
+        TravelPlanDetail detail = detailRepository.findById(detailId)
+                .orElseThrow(() -> new TravelPlanException("行程详情不存在"));
+
+        // 获取关联的行程
+        TravelPlan travelPlan = detail.getTravelPlan();
+
+        // 检查权限
+        checkPermissionInternal(travelPlan, userId, TravelPlanConstant.Permission.EDIT);
+
+        // 删除行程详情
+        detailRepository.deleteById(detailId);
+
+        // 更新行程更新时间
+        travelPlan.setUpdateTime(new Date());
+        travelPlanRepository.save(travelPlan);
+
+        // 记录操作
+        recordOperationInternal(travelPlan.getPlanId(), TravelPlanConstant.OperationType.SAVE,
+                userId, "删除行程详情");
+    }
+
+    /**
+     * 添加协作人
+     */
+    @Override
+    @Transactional
+    public void addCollaborator(AddCollaboratorRequest request) {
+        // 获取行程
+        TravelPlan travelPlan = getTravelPlanById(request.getPlanId());
+
+        // 检查权限(只有管理员或创建者可以添加协作人)
+        if (!travelPlan.getUserId().equals(request.getOperatorUserId()) &&
+                !checkPermissionInternal(travelPlan, request.getOperatorUserId(), TravelPlanConstant.Permission.ADMIN)) {
+            throw new TravelPlanException("没有权限添加协作人");
+        }
+
+        // 检查协作人是否已存在
+        TravelPlanCollaborator existingCollaborator = collaboratorRepository
+                .findByTravelPlanPlanIdAndCollabUserId(request.getPlanId(), request.getCollabUserId());
+
+        if (existingCollaborator != null) {
+            // 更新现有协作人的权限
+            existingCollaborator.setPermission(request.getPermission());
+            collaboratorRepository.save(existingCollaborator);
+        } else {
+            // 创建新的协作人关系
+            TravelPlanCollaborator collaborator = new TravelPlanCollaborator();
+            collaborator.setTravelPlan(travelPlan);
+            collaborator.setCollabUserId(request.getCollabUserId());
+            collaborator.setPermission(request.getPermission());
+            collaborator.setJoinTime(new Date());
+
+            // 保存协作人关系
+            collaboratorRepository.save(collaborator);
+        }
+
+        // 记录操作
+        recordOperationInternal(travelPlan.getPlanId(), TravelPlanConstant.OperationType.OTHER,
+                request.getOperatorUserId(), "添加协作人");
+    }
+
+    /**
+     * 删除协作人
+     */
+    @Override
+    @Transactional
+    public void removeCollaborator(Long planId, String collabUserId, String operatorUserId) {
+        // 获取行程
+        TravelPlan travelPlan = getTravelPlanById(planId);
+
+        // 检查权限(只有管理员或创建者可以删除协作人)
+        if (!travelPlan.getUserId().equals(operatorUserId) &&
+                !checkPermissionInternal(travelPlan, operatorUserId, TravelPlanConstant.Permission.ADMIN)) {
+            throw new TravelPlanException("没有权限删除协作人");
+        }
+
+        // 删除协作人关系
+        TravelPlanCollaborator collaborator = collaboratorRepository
+                .findByTravelPlanPlanIdAndCollabUserId(planId, collabUserId);
+
+        if (collaborator != null) {
+            collaboratorRepository.delete(collaborator);
+        }
+
+        // 记录操作
+        recordOperationInternal(planId, TravelPlanConstant.OperationType.OTHER,
+                operatorUserId, "删除协作人");
+    }
+
+    /**
+     * 记录行程操作
+     */
+    @Override
+    @Transactional
+    public void recordOperation(RecordOperationRequest request) {
+        // 获取行程
+        TravelPlan travelPlan = getTravelPlanById(request.getPlanId());
+
+        // 检查权限
+        checkPermissionInternal(travelPlan, request.getOpUserId(), TravelPlanConstant.Permission.VIEW);
+
+        // 记录操作
+        recordOperationInternal(request.getPlanId(), request.getOpType(),
+                request.getOpUserId(), request.getOpDesc());
+    }
+
+    /**
+     * 检查用户是否有访问行程的权限
+     */
+    @Override
+    public boolean checkPermission(Long planId, String userId, int requiredPermission) {
+        // 获取行程
+        TravelPlan travelPlan = getTravelPlanById(planId);
+
+        // 检查权限
+        return checkPermissionInternal(travelPlan, userId, requiredPermission);
+    }
+
+    /**
+     * 内部方法:根据ID获取行程
+     */
+    private TravelPlan getTravelPlanById(Long planId) {
+        return travelPlanRepository.findById(planId)
+                .orElseThrow(() -> new TravelPlanException("行程不存在"));
+    }
+
+    /**
+     * 内部方法:检查用户权限
+     */
+    private boolean checkPermissionInternal(TravelPlan travelPlan, String userId, int requiredPermission) {
+        // 如果是行程创建者,拥有所有权限
+        if (travelPlan.getUserId().equals(userId)) {
+            return true;
+        }
+
+        // 查找协作人关系
+        TravelPlanCollaborator collaborator = collaboratorRepository
+                .findByTravelPlanPlanIdAndCollabUserId(travelPlan.getPlanId(), userId);
+
+        // 如果不是协作人,没有权限
+        if (collaborator == null) {
+            return false;
+        }
+
+        // 检查权限级别
+        return collaborator.getPermission() >= requiredPermission;
+    }
+
+    /**
+     * 内部方法:记录操作
+     */
+    private void recordOperationInternal(Long planId, int opType, String opUserId, String opDesc) {
+        TravelPlanOperation operation = new TravelPlanOperation();
+        operation.setTravelPlan(travelPlanRepository.getOne(planId));
+        operation.setOpType(opType);
+        operation.setOpUserId(opUserId);
+        operation.setOpTime(new Date());
+        operation.setOpDesc(opDesc);
+
+        operationRepository.save(operation);
+    }
+
+    /**
+     * 内部方法:将行程列表转换为响应DTO列表
+     */
+    private List<TravelPlanResponse> convertToResponseList(List<TravelPlan> travelPlans) {
+        List<TravelPlanResponse> responses = new ArrayList<>();
+
+        for (TravelPlan plan : travelPlans) {
+            // 手动映射属性,避免懒加载问题
+            TravelPlanResponse response = new TravelPlanResponse();
+            response.setPlanId(plan.getPlanId());
+            response.setUserId(plan.getUserId());
+            response.setPlanName(plan.getPlanName());
+            response.setTotalDays(plan.getTotalDays());
+            response.setTotalBudget(plan.getTotalBudget());
+            response.setPreference(plan.getPreference());
+            response.setStatus(plan.getStatus());
+            response.setCreateTime(plan.getCreateTime());
+            response.setUpdateTime(plan.getUpdateTime());
+            
+            responses.add(response);
+        }
+
+        return responses;
+    }
+}