diff --git a/.kiro/steering/项目结构说明.md b/.kiro/steering/项目结构说明.md
index 4810d83..4d87f46 100644
--- a/.kiro/steering/项目结构说明.md
+++ b/.kiro/steering/项目结构说明.md
@@ -39,10 +39,12 @@ offerpie/back-end
│ └─ pojo/
│ ├─ param/
│ │ ├─ userProfile/ # 个人资料入参(UserProfileParam、各子表Param)
+│ │ ├─ resume/ # 简历入参(ResumeParam、各子表Param、ResumeSubTableParam)
│ │ └─ job/ # 岗位相关入参(JobIntentionParam、JobQueryParam)
│ ├─ dto/
│ │ ├─ SmsLoginDto.java # 短信登录入参(mobileNumber + code + inviteCode)
│ │ ├─ userProfile/ # 个人资料出参(UserProfileDto、各子表Dto)
+│ │ ├─ resume/ # 简历出参(ResumeDto、ResumeListItemDto、各子表Dto)
│ │ └─ job/ # 岗位相关出参(JobIntentionDto、JobDto、JobMatchScoreDto)
│ └─ vo/
│ ├─ LoginVo.java # 登录返回(userId + nick)
diff --git a/client-api/src/main/java/org/jiayunet/controller/UserResumeController.java b/client-api/src/main/java/org/jiayunet/controller/UserResumeController.java
index a83770b..c0d4606 100644
--- a/client-api/src/main/java/org/jiayunet/controller/UserResumeController.java
+++ b/client-api/src/main/java/org/jiayunet/controller/UserResumeController.java
@@ -1,18 +1,22 @@
package org.jiayunet.controller;
import lombok.AllArgsConstructor;
-import org.jiayunet.pojo.dto.resume.ResumeListItemDto;
+import org.jiayunet.pojo.dto.resume.*;
+import org.jiayunet.pojo.param.resume.*;
+import org.jiayunet.pojo.po.*;
import org.jiayunet.service.UserResumeService;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
+import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户简历接口
- * 提供简历列表查询等功能
+ *
提供简历列表、主表及5张子表(教育/工作/实习/项目/竞赛)的查询与保存、简历删除功能
+ * 所有保存接口支持自动创建:前端不传resumeId时自动创建新简历并返回resumeId
*
* @author zk
*/
@@ -24,9 +28,9 @@ public class UserResumeController {
private UserResumeService userResumeService;
- /**
- * 查询当前用户的简历列表
- */
+ // ==================== 简历列表 ====================
+
+ /** 查询当前用户的简历列表 */
@GetMapping("/list")
public List listResume() {
return userResumeService.listResume().stream().map(po -> {
@@ -35,4 +39,144 @@ public class UserResumeController {
return dto;
}).collect(Collectors.toList());
}
+
+ // ==================== 主表 ====================
+
+ /** 查询简历主表 */
+ @GetMapping
+ public ResumeDto getResume(@RequestParam Long resumeId) {
+ UserResume po = userResumeService.getResume(resumeId);
+ ResumeDto dto = new ResumeDto();
+ BeanUtils.copyProperties(po, dto);
+ return dto;
+ }
+
+ /** 保存简历主表,返回resumeId(resumeId为空则自动创建新简历) */
+ @PostMapping
+ public Long saveResume(@Validated @RequestBody ResumeParam param) {
+ UserResume po = new UserResume();
+ BeanUtils.copyProperties(param, po);
+ return userResumeService.saveResume(po, param.getResumeId());
+ }
+
+ /** 删除简历(主表 + 全部子表) */
+ @PostMapping("/delete")
+ public void deleteResume(@RequestParam Long resumeId) {
+ userResumeService.deleteResume(resumeId);
+ }
+
+ // ==================== 教育经历 ====================
+
+ /** 查询简历的教育经历列表 */
+ @GetMapping("/education")
+ public List listEducation(@RequestParam Long resumeId) {
+ return userResumeService.listEducation(resumeId).stream().map(po -> {
+ ResumeEducationDto dto = new ResumeEducationDto();
+ BeanUtils.copyProperties(po, dto);
+ return dto;
+ }).collect(Collectors.toList());
+ }
+
+ /** 保存简历的教育经历列表(先删后插),返回resumeId */
+ @PostMapping("/education")
+ public Long saveEducation(@Validated @RequestBody ResumeSubTableParam<@Valid ResumeEducationParam> param) {
+ List list = param.getItems().stream().map(p -> {
+ UserResumeEducation po = new UserResumeEducation();
+ BeanUtils.copyProperties(p, po);
+ return po;
+ }).collect(Collectors.toList());
+ return userResumeService.saveEducationList(list, param.getResumeId());
+ }
+
+ // ==================== 工作经历 ====================
+
+ /** 查询简历的工作经历列表 */
+ @GetMapping("/work")
+ public List listWork(@RequestParam Long resumeId) {
+ return userResumeService.listWork(resumeId).stream().map(po -> {
+ ResumeWorkDto dto = new ResumeWorkDto();
+ BeanUtils.copyProperties(po, dto);
+ return dto;
+ }).collect(Collectors.toList());
+ }
+
+ /** 保存简历的工作经历列表(先删后插),返回resumeId */
+ @PostMapping("/work")
+ public Long saveWork(@Validated @RequestBody ResumeSubTableParam<@Valid ResumeWorkParam> param) {
+ List list = param.getItems().stream().map(p -> {
+ UserResumeWork po = new UserResumeWork();
+ BeanUtils.copyProperties(p, po);
+ return po;
+ }).collect(Collectors.toList());
+ return userResumeService.saveWorkList(list, param.getResumeId());
+ }
+
+ // ==================== 实习经历 ====================
+
+ /** 查询简历的实习经历列表 */
+ @GetMapping("/internship")
+ public List listInternship(@RequestParam Long resumeId) {
+ return userResumeService.listInternship(resumeId).stream().map(po -> {
+ ResumeInternshipDto dto = new ResumeInternshipDto();
+ BeanUtils.copyProperties(po, dto);
+ return dto;
+ }).collect(Collectors.toList());
+ }
+
+ /** 保存简历的实习经历列表(先删后插),返回resumeId */
+ @PostMapping("/internship")
+ public Long saveInternship(@Validated @RequestBody ResumeSubTableParam<@Valid ResumeInternshipParam> param) {
+ List list = param.getItems().stream().map(p -> {
+ UserResumeInternship po = new UserResumeInternship();
+ BeanUtils.copyProperties(p, po);
+ return po;
+ }).collect(Collectors.toList());
+ return userResumeService.saveInternshipList(list, param.getResumeId());
+ }
+
+ // ==================== 项目经历 ====================
+
+ /** 查询简历的项目经历列表 */
+ @GetMapping("/project")
+ public List listProject(@RequestParam Long resumeId) {
+ return userResumeService.listProject(resumeId).stream().map(po -> {
+ ResumeProjectDto dto = new ResumeProjectDto();
+ BeanUtils.copyProperties(po, dto);
+ return dto;
+ }).collect(Collectors.toList());
+ }
+
+ /** 保存简历的项目经历列表(先删后插),返回resumeId */
+ @PostMapping("/project")
+ public Long saveProject(@Validated @RequestBody ResumeSubTableParam<@Valid ResumeProjectParam> param) {
+ List list = param.getItems().stream().map(p -> {
+ UserResumeProject po = new UserResumeProject();
+ BeanUtils.copyProperties(p, po);
+ return po;
+ }).collect(Collectors.toList());
+ return userResumeService.saveProjectList(list, param.getResumeId());
+ }
+
+ // ==================== 竞赛经历 ====================
+
+ /** 查询简历的竞赛经历列表 */
+ @GetMapping("/competition")
+ public List listCompetition(@RequestParam Long resumeId) {
+ return userResumeService.listCompetition(resumeId).stream().map(po -> {
+ ResumeCompetitionDto dto = new ResumeCompetitionDto();
+ BeanUtils.copyProperties(po, dto);
+ return dto;
+ }).collect(Collectors.toList());
+ }
+
+ /** 保存简历的竞赛经历列表(先删后插),返回resumeId */
+ @PostMapping("/competition")
+ public Long saveCompetition(@Validated @RequestBody ResumeSubTableParam<@Valid ResumeCompetitionParam> param) {
+ List list = param.getItems().stream().map(p -> {
+ UserResumeCompetition po = new UserResumeCompetition();
+ BeanUtils.copyProperties(p, po);
+ return po;
+ }).collect(Collectors.toList());
+ return userResumeService.saveCompetitionList(list, param.getResumeId());
+ }
}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeCompetitionDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeCompetitionDto.java
new file mode 100644
index 0000000..f4b2605
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeCompetitionDto.java
@@ -0,0 +1,29 @@
+package org.jiayunet.pojo.dto.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import java.util.List;
+
+/**
+ * 简历-竞赛经历返回
+ *
+ * @author zk
+ */
+@Data
+public class ResumeCompetitionDto {
+
+ private Long id;
+
+ /** 竞赛名称 */
+ private String competitionName;
+
+ /** 获奖情况 */
+ private String award;
+
+ /** 获奖时间,格式:2023.07 */
+ private String awardDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeDto.java
new file mode 100644
index 0000000..24a6eb6
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeDto.java
@@ -0,0 +1,55 @@
+package org.jiayunet.pojo.dto.resume;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 简历主表返回
+ *
+ * @author zk
+ */
+@Data
+public class ResumeDto {
+
+ private Long id;
+
+ /** 简历名称 */
+ private String resumeName;
+
+ /** 目标岗位 */
+ private String targetPosition;
+
+ /** 是否默认简历 0=否 1=是 */
+ private Integer isDefault;
+
+ /** 头像URL */
+ private String avatarUrl;
+
+ /** 真实姓名 */
+ private String name;
+
+ /** 邮箱 */
+ private String email;
+
+ /** 手机号码 */
+ private String mobileNumber;
+
+ /** 所在城市 */
+ private String city;
+
+ /** 微信号 */
+ private String wechatNumber;
+
+ /** 作品集链接 */
+ private String portfolioUrl;
+
+ /** 技能标签列表 */
+ private List skills;
+
+ /** 证书标签列表 */
+ private List certificates;
+
+ /** 个人概述 */
+ private String summary;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeEducationDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeEducationDto.java
new file mode 100644
index 0000000..0f2e1e9
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeEducationDto.java
@@ -0,0 +1,38 @@
+package org.jiayunet.pojo.dto.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import java.util.List;
+
+/**
+ * 简历-教育经历返回
+ *
+ * @author zk
+ */
+@Data
+public class ResumeEducationDto {
+
+ private Long id;
+
+ /** 学校名称 */
+ private String school;
+
+ /** 专业 */
+ private String major;
+
+ /** 学历:大专/本科/硕士/博士 */
+ private String degree;
+
+ /** 学习形式:全日制/非全日制 */
+ private String studyType;
+
+ /** 开始时间,格式:2023.09 */
+ private String startDate;
+
+ /** 结束时间,格式:2024.06 */
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeInternshipDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeInternshipDto.java
new file mode 100644
index 0000000..ec044e3
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeInternshipDto.java
@@ -0,0 +1,32 @@
+package org.jiayunet.pojo.dto.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import java.util.List;
+
+/**
+ * 简历-实习经历返回
+ *
+ * @author zk
+ */
+@Data
+public class ResumeInternshipDto {
+
+ private Long id;
+
+ /** 公司名称 */
+ private String companyName;
+
+ /** 职位 */
+ private String position;
+
+ /** 开始时间 */
+ private String startDate;
+
+ /** 结束时间 */
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeProjectDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeProjectDto.java
new file mode 100644
index 0000000..faece75
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeProjectDto.java
@@ -0,0 +1,35 @@
+package org.jiayunet.pojo.dto.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import java.util.List;
+
+/**
+ * 简历-项目经历返回
+ *
+ * @author zk
+ */
+@Data
+public class ResumeProjectDto {
+
+ private Long id;
+
+ /** 所属公司 */
+ private String companyName;
+
+ /** 项目名称 */
+ private String projectName;
+
+ /** 担任角色 */
+ private String role;
+
+ /** 开始时间 */
+ private String startDate;
+
+ /** 结束时间 */
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeWorkDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeWorkDto.java
new file mode 100644
index 0000000..2c7c9b0
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/resume/ResumeWorkDto.java
@@ -0,0 +1,32 @@
+package org.jiayunet.pojo.dto.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import java.util.List;
+
+/**
+ * 简历-工作经历返回
+ *
+ * @author zk
+ */
+@Data
+public class ResumeWorkDto {
+
+ private Long id;
+
+ /** 公司名称 */
+ private String companyName;
+
+ /** 职位 */
+ private String position;
+
+ /** 开始时间 */
+ private String startDate;
+
+ /** 结束时间 */
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeCompetitionParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeCompetitionParam.java
new file mode 100644
index 0000000..e4ccfc2
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeCompetitionParam.java
@@ -0,0 +1,29 @@
+package org.jiayunet.pojo.param.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * 简历-竞赛经历保存入参
+ *
+ * @author zk
+ */
+@Data
+public class ResumeCompetitionParam {
+
+ /** 竞赛名称 */
+ @NotBlank(message = "竞赛名称不能为空")
+ private String competitionName;
+
+ /** 获奖情况,如全国二等奖 */
+ private String award;
+
+ /** 获奖时间,格式:2023.07 */
+ private String awardDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeEducationParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeEducationParam.java
new file mode 100644
index 0000000..5bdbb0a
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeEducationParam.java
@@ -0,0 +1,38 @@
+package org.jiayunet.pojo.param.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * 简历-教育经历保存入参
+ *
+ * @author zk
+ */
+@Data
+public class ResumeEducationParam {
+
+ /** 学校名称 */
+ @NotBlank(message = "学校名称不能为空")
+ private String school;
+
+ /** 专业 */
+ private String major;
+
+ /** 学历:大专/本科/硕士/博士 */
+ private String degree;
+
+ /** 学习形式:全日制/非全日制 */
+ private String studyType;
+
+ /** 开始时间,格式:2023.09 */
+ private String startDate;
+
+ /** 结束时间,格式:2024.06 */
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeInternshipParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeInternshipParam.java
new file mode 100644
index 0000000..4ff4332
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeInternshipParam.java
@@ -0,0 +1,34 @@
+package org.jiayunet.pojo.param.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * 简历-实习经历保存入参
+ *
+ * @author zk
+ */
+@Data
+public class ResumeInternshipParam {
+
+ /** 公司名称 */
+ @NotBlank(message = "公司名称不能为空")
+ private String companyName;
+
+ /** 职位 */
+ @NotBlank(message = "职位不能为空")
+ private String position;
+
+ /** 开始时间,格式:2023.06 */
+ @NotBlank(message = "开始时间不能为空")
+ private String startDate;
+
+ /** 结束时间,格式:2023.09,至今则为空 */
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeParam.java
new file mode 100644
index 0000000..485d3a8
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeParam.java
@@ -0,0 +1,53 @@
+package org.jiayunet.pojo.param.resume;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 简历主表保存入参
+ *
+ * @author zk
+ */
+@Data
+public class ResumeParam {
+
+ /** 简历ID,为空则自动创建新简历 */
+ private Long resumeId;
+
+ /** 简历名称 */
+ private String resumeName;
+
+ /** 目标岗位 */
+ private String targetPosition;
+
+ /** 头像URL */
+ private String avatarUrl;
+
+ /** 真实姓名 */
+ private String name;
+
+ /** 邮箱 */
+ private String email;
+
+ /** 手机号码 */
+ private String mobileNumber;
+
+ /** 所在城市 */
+ private String city;
+
+ /** 微信号 */
+ private String wechatNumber;
+
+ /** 作品集链接 */
+ private String portfolioUrl;
+
+ /** 技能标签列表 */
+ private List skills;
+
+ /** 证书标签列表 */
+ private List certificates;
+
+ /** 个人概述 */
+ private String summary;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeProjectParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeProjectParam.java
new file mode 100644
index 0000000..9c61807
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeProjectParam.java
@@ -0,0 +1,36 @@
+package org.jiayunet.pojo.param.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * 简历-项目经历保存入参
+ *
+ * @author zk
+ */
+@Data
+public class ResumeProjectParam {
+
+ /** 所属公司 */
+ private String companyName;
+
+ /** 项目名称 */
+ @NotBlank(message = "项目名称不能为空")
+ private String projectName;
+
+ /** 担任角色 */
+ private String role;
+
+ /** 开始时间,格式:2023.06 */
+ @NotBlank(message = "开始时间不能为空")
+ private String startDate;
+
+ /** 结束时间,格式:2023.09,至今则为空 */
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeSubTableParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeSubTableParam.java
new file mode 100644
index 0000000..2fe83b0
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeSubTableParam.java
@@ -0,0 +1,23 @@
+package org.jiayunet.pojo.param.resume;
+
+import lombok.Data;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 简历子表保存通用包装入参
+ * 携带可选的resumeId,为空则自动创建新简历
+ *
+ * @author zk
+ */
+@Data
+public class ResumeSubTableParam {
+
+ /** 简历ID,为空则自动创建新简历 */
+ private Long resumeId;
+
+ /** 子表数据列表 */
+ @Valid
+ private List items;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeWorkParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeWorkParam.java
new file mode 100644
index 0000000..cf6d9b1
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/param/resume/ResumeWorkParam.java
@@ -0,0 +1,34 @@
+package org.jiayunet.pojo.param.resume;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * 简历-工作经历保存入参
+ *
+ * @author zk
+ */
+@Data
+public class ResumeWorkParam {
+
+ /** 公司名称 */
+ @NotBlank(message = "公司名称不能为空")
+ private String companyName;
+
+ /** 职位 */
+ @NotBlank(message = "职位不能为空")
+ private String position;
+
+ /** 开始时间,格式:2023.06 */
+ @NotBlank(message = "开始时间不能为空")
+ private String startDate;
+
+ /** 结束时间,格式:2023.09,至今则为空 */
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/service/UserResumeService.java b/client-api/src/main/java/org/jiayunet/service/UserResumeService.java
index ee1b845..4eb08e2 100644
--- a/client-api/src/main/java/org/jiayunet/service/UserResumeService.java
+++ b/client-api/src/main/java/org/jiayunet/service/UserResumeService.java
@@ -1,19 +1,27 @@
package org.jiayunet.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.extern.slf4j.Slf4j;
+import org.jiayunet.exception.BusinessException;
+import org.jiayunet.exception.BusinessExpCodeEnum;
import org.jiayunet.mapper.*;
import org.jiayunet.pojo.po.*;
import org.jiayunet.tool.UserSecurityTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.time.Instant;
import java.util.List;
+import java.util.stream.IntStream;
/**
* 用户简历服务
* 依赖:无
- * 使用表:bg_user_resume(简历主表查询)
+ * 使用表:bg_user_resume(简历主表CRUD)、bg_user_resume_education(教育经历)、
+ * bg_user_resume_work(工作经历)、bg_user_resume_internship(实习经历)、
+ * bg_user_resume_project(项目经历)、bg_user_resume_competition(竞赛经历)
*
* @author zk
*/
@@ -24,6 +32,23 @@ public class UserResumeService {
@Autowired
private UserResumeMapper userResumeMapper;
+ @Autowired
+ private UserResumeEducationMapper educationMapper;
+
+ @Autowired
+ private UserResumeWorkMapper workMapper;
+
+ @Autowired
+ private UserResumeInternshipMapper internshipMapper;
+
+ @Autowired
+ private UserResumeProjectMapper projectMapper;
+
+ @Autowired
+ private UserResumeCompetitionMapper competitionMapper;
+
+ // ==================== 简历列表 ====================
+
/**
* 查询当前用户的简历列表
* 按sort_order升序、create_time降序排列
@@ -35,4 +60,299 @@ public class UserResumeService {
.orderByAsc(UserResume::getSortOrder)
.orderByDesc(UserResume::getCreateTime));
}
+
+ // ==================== 主表 ====================
+
+ /**
+ * 查询简历主表
+ * 校验简历归属当前用户
+ */
+ public UserResume getResume(Long resumeId) {
+ UserResume resume = userResumeMapper.selectById(resumeId);
+ checkOwnership(resume);
+ return resume;
+ }
+
+ /**
+ * 保存简历主表
+ * 1. resumeId为空则创建新简历(默认名"我的简历") 2. 不为空则更新 3. 返回resumeId
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public Long saveResume(UserResume resume, Long resumeId) {
+ Long userId = UserSecurityTool.getUserId();
+ Instant now = Instant.now();
+ if (resumeId == null) {
+ // 创建新简历
+ resume.setUserId(userId);
+ if (resume.getResumeName() == null) {
+ resume.setResumeName("我的简历");
+ }
+ resume.setIsDefault(0);
+ resume.setSortOrder(0);
+ resume.setCreateTime(now);
+ resume.setUpdateTime(now);
+ userResumeMapper.insert(resume);
+ return resume.getId();
+ }
+ // 更新已有简历
+ UserResume existing = userResumeMapper.selectById(resumeId);
+ checkOwnership(existing);
+ resume.setId(resumeId);
+ resume.setUserId(userId);
+ resume.setCreateTime(existing.getCreateTime());
+ resume.setUpdateTime(now);
+ resume.setIsDefault(existing.getIsDefault());
+ resume.setSortOrder(existing.getSortOrder());
+ userResumeMapper.updateById(resume);
+ return resumeId;
+ }
+
+ /**
+ * 删除简历(物理删除主表 + 全部子表)
+ * 1. 校验归属 2. 删除5张子表 3. 删除主表
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteResume(Long resumeId) {
+ UserResume resume = userResumeMapper.selectById(resumeId);
+ checkOwnership(resume);
+ // 删除全部子表
+ educationMapper.delete(new LambdaQueryWrapper().eq(UserResumeEducation::getResumeId, resumeId));
+ workMapper.delete(new LambdaQueryWrapper().eq(UserResumeWork::getResumeId, resumeId));
+ internshipMapper.delete(new LambdaQueryWrapper().eq(UserResumeInternship::getResumeId, resumeId));
+ projectMapper.delete(new LambdaQueryWrapper().eq(UserResumeProject::getResumeId, resumeId));
+ competitionMapper.delete(new LambdaQueryWrapper().eq(UserResumeCompetition::getResumeId, resumeId));
+ // 删除主表
+ userResumeMapper.deleteById(resumeId);
+ }
+
+ // ==================== 教育经历 ====================
+
+ /** 查询简历的教育经历列表 */
+ public List listEducation(Long resumeId) {
+ checkResumeOwnership(resumeId);
+ return educationMapper.selectList(new LambdaQueryWrapper()
+ .eq(UserResumeEducation::getResumeId, resumeId)
+ .orderByAsc(UserResumeEducation::getSortOrder));
+ }
+
+ /**
+ * 保存简历的教育经历列表(先删后插)
+ * 1. resumeId为空则自动创建新简历 2. 按resumeId删除旧数据 3. 批量插入 4. 更新主表修改时间
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public Long saveEducationList(List list, Long resumeId) {
+ Long userId = UserSecurityTool.getUserId();
+ resumeId = getOrCreateResumeId(resumeId, userId);
+ educationMapper.delete(new LambdaQueryWrapper().eq(UserResumeEducation::getResumeId, resumeId));
+ if (!list.isEmpty()) {
+ Instant now = Instant.now();
+ Long finalResumeId = resumeId;
+ IntStream.range(0, list.size()).forEach(i -> {
+ UserResumeEducation item = list.get(i);
+ item.setUserId(userId);
+ item.setResumeId(finalResumeId);
+ item.setSortOrder(i);
+ item.setCreateTime(now);
+ item.setUpdateTime(now);
+ });
+ educationMapper.batchInsert(list);
+ }
+ touchResumeUpdateTime(resumeId);
+ return resumeId;
+ }
+
+ // ==================== 工作经历 ====================
+
+ /** 查询简历的工作经历列表 */
+ public List listWork(Long resumeId) {
+ checkResumeOwnership(resumeId);
+ return workMapper.selectList(new LambdaQueryWrapper()
+ .eq(UserResumeWork::getResumeId, resumeId)
+ .orderByAsc(UserResumeWork::getSortOrder));
+ }
+
+ /**
+ * 保存简历的工作经历列表(先删后插)
+ * 1. resumeId为空则自动创建新简历 2. 按resumeId删除旧数据 3. 批量插入 4. 更新主表修改时间
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public Long saveWorkList(List list, Long resumeId) {
+ Long userId = UserSecurityTool.getUserId();
+ resumeId = getOrCreateResumeId(resumeId, userId);
+ workMapper.delete(new LambdaQueryWrapper().eq(UserResumeWork::getResumeId, resumeId));
+ if (!list.isEmpty()) {
+ Instant now = Instant.now();
+ Long finalResumeId = resumeId;
+ IntStream.range(0, list.size()).forEach(i -> {
+ UserResumeWork item = list.get(i);
+ item.setUserId(userId);
+ item.setResumeId(finalResumeId);
+ item.setSortOrder(i);
+ item.setCreateTime(now);
+ item.setUpdateTime(now);
+ });
+ workMapper.batchInsert(list);
+ }
+ touchResumeUpdateTime(resumeId);
+ return resumeId;
+ }
+
+ // ==================== 实习经历 ====================
+
+ /** 查询简历的实习经历列表 */
+ public List listInternship(Long resumeId) {
+ checkResumeOwnership(resumeId);
+ return internshipMapper.selectList(new LambdaQueryWrapper()
+ .eq(UserResumeInternship::getResumeId, resumeId)
+ .orderByAsc(UserResumeInternship::getSortOrder));
+ }
+
+ /**
+ * 保存简历的实习经历列表(先删后插)
+ * 1. resumeId为空则自动创建新简历 2. 按resumeId删除旧数据 3. 批量插入 4. 更新主表修改时间
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public Long saveInternshipList(List list, Long resumeId) {
+ Long userId = UserSecurityTool.getUserId();
+ resumeId = getOrCreateResumeId(resumeId, userId);
+ internshipMapper.delete(new LambdaQueryWrapper().eq(UserResumeInternship::getResumeId, resumeId));
+ if (!list.isEmpty()) {
+ Instant now = Instant.now();
+ Long finalResumeId = resumeId;
+ IntStream.range(0, list.size()).forEach(i -> {
+ UserResumeInternship item = list.get(i);
+ item.setUserId(userId);
+ item.setResumeId(finalResumeId);
+ item.setSortOrder(i);
+ item.setCreateTime(now);
+ item.setUpdateTime(now);
+ });
+ internshipMapper.batchInsert(list);
+ }
+ touchResumeUpdateTime(resumeId);
+ return resumeId;
+ }
+
+ // ==================== 项目经历 ====================
+
+ /** 查询简历的项目经历列表 */
+ public List listProject(Long resumeId) {
+ checkResumeOwnership(resumeId);
+ return projectMapper.selectList(new LambdaQueryWrapper()
+ .eq(UserResumeProject::getResumeId, resumeId)
+ .orderByAsc(UserResumeProject::getSortOrder));
+ }
+
+ /**
+ * 保存简历的项目经历列表(先删后插)
+ * 1. resumeId为空则自动创建新简历 2. 按resumeId删除旧数据 3. 批量插入 4. 更新主表修改时间
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public Long saveProjectList(List list, Long resumeId) {
+ Long userId = UserSecurityTool.getUserId();
+ resumeId = getOrCreateResumeId(resumeId, userId);
+ projectMapper.delete(new LambdaQueryWrapper().eq(UserResumeProject::getResumeId, resumeId));
+ if (!list.isEmpty()) {
+ Instant now = Instant.now();
+ Long finalResumeId = resumeId;
+ IntStream.range(0, list.size()).forEach(i -> {
+ UserResumeProject item = list.get(i);
+ item.setUserId(userId);
+ item.setResumeId(finalResumeId);
+ item.setSortOrder(i);
+ item.setCreateTime(now);
+ item.setUpdateTime(now);
+ });
+ projectMapper.batchInsert(list);
+ }
+ touchResumeUpdateTime(resumeId);
+ return resumeId;
+ }
+
+ // ==================== 竞赛经历 ====================
+
+ /** 查询简历的竞赛经历列表 */
+ public List listCompetition(Long resumeId) {
+ checkResumeOwnership(resumeId);
+ return competitionMapper.selectList(new LambdaQueryWrapper()
+ .eq(UserResumeCompetition::getResumeId, resumeId)
+ .orderByAsc(UserResumeCompetition::getSortOrder));
+ }
+
+ /**
+ * 保存简历的竞赛经历列表(先删后插)
+ * 1. resumeId为空则自动创建新简历 2. 按resumeId删除旧数据 3. 批量插入 4. 更新主表修改时间
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public Long saveCompetitionList(List list, Long resumeId) {
+ Long userId = UserSecurityTool.getUserId();
+ resumeId = getOrCreateResumeId(resumeId, userId);
+ competitionMapper.delete(new LambdaQueryWrapper().eq(UserResumeCompetition::getResumeId, resumeId));
+ if (!list.isEmpty()) {
+ Instant now = Instant.now();
+ Long finalResumeId = resumeId;
+ IntStream.range(0, list.size()).forEach(i -> {
+ UserResumeCompetition item = list.get(i);
+ item.setUserId(userId);
+ item.setResumeId(finalResumeId);
+ item.setSortOrder(i);
+ item.setCreateTime(now);
+ item.setUpdateTime(now);
+ });
+ competitionMapper.batchInsert(list);
+ }
+ touchResumeUpdateTime(resumeId);
+ return resumeId;
+ }
+
+ // ==================== 内部方法 ====================
+
+ /**
+ * 获取或创建简历,返回resumeId
+ * resumeId为空则创建新简历(默认名"我的简历"),不为空则校验归属后返回
+ */
+ private Long getOrCreateResumeId(Long resumeId, Long userId) {
+ if (resumeId != null) {
+ checkResumeOwnership(resumeId);
+ return resumeId;
+ }
+ UserResume resume = new UserResume();
+ resume.setUserId(userId);
+ resume.setResumeName("我的简历");
+ resume.setIsDefault(0);
+ resume.setSortOrder(0);
+ Instant now = Instant.now();
+ resume.setCreateTime(now);
+ resume.setUpdateTime(now);
+ userResumeMapper.insert(resume);
+ return resume.getId();
+ }
+
+ /**
+ * 更新简历主表的修改时间
+ * 子表保存后调用,确保主表update_time反映简历整体最后修改时间
+ */
+ private void touchResumeUpdateTime(Long resumeId) {
+ userResumeMapper.update(null, new LambdaUpdateWrapper()
+ .eq(UserResume::getId, resumeId)
+ .set(UserResume::getUpdateTime, Instant.now()));
+ }
+
+ /**
+ * 校验简历归属当前用户
+ */
+ private void checkOwnership(UserResume resume) {
+ Long userId = UserSecurityTool.getUserId();
+ if (resume == null || !resume.getUserId().equals(userId)) {
+ throw new BusinessException(BusinessExpCodeEnum.PERMISSION_DENIED, "简历不存在或无权操作");
+ }
+ }
+
+ /**
+ * 根据resumeId校验简历归属当前用户
+ */
+ private void checkResumeOwnership(Long resumeId) {
+ UserResume resume = userResumeMapper.selectById(resumeId);
+ checkOwnership(resume);
+ }
}
diff --git a/manager/src/main/java/org/jiayunet/mapper/UserResumeCompetitionMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserResumeCompetitionMapper.java
index 003c96a..29630f9 100644
--- a/manager/src/main/java/org/jiayunet/mapper/UserResumeCompetitionMapper.java
+++ b/manager/src/main/java/org/jiayunet/mapper/UserResumeCompetitionMapper.java
@@ -1,6 +1,6 @@
package org.jiayunet.mapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
import org.jiayunet.pojo.po.UserResumeCompetition;
/**
@@ -8,5 +8,6 @@ import org.jiayunet.pojo.po.UserResumeCompetition;
*
* @author zk
*/
-public interface UserResumeCompetitionMapper extends BaseMapper {
+@Mapper
+public interface UserResumeCompetitionMapper extends CommonMapper {
}
diff --git a/manager/src/main/java/org/jiayunet/mapper/UserResumeEducationMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserResumeEducationMapper.java
index b347d0a..09b81b5 100644
--- a/manager/src/main/java/org/jiayunet/mapper/UserResumeEducationMapper.java
+++ b/manager/src/main/java/org/jiayunet/mapper/UserResumeEducationMapper.java
@@ -1,6 +1,6 @@
package org.jiayunet.mapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
import org.jiayunet.pojo.po.UserResumeEducation;
/**
@@ -8,5 +8,6 @@ import org.jiayunet.pojo.po.UserResumeEducation;
*
* @author zk
*/
-public interface UserResumeEducationMapper extends BaseMapper {
+@Mapper
+public interface UserResumeEducationMapper extends CommonMapper {
}
diff --git a/manager/src/main/java/org/jiayunet/mapper/UserResumeInternshipMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserResumeInternshipMapper.java
index 30f2389..3dfb9f0 100644
--- a/manager/src/main/java/org/jiayunet/mapper/UserResumeInternshipMapper.java
+++ b/manager/src/main/java/org/jiayunet/mapper/UserResumeInternshipMapper.java
@@ -1,6 +1,6 @@
package org.jiayunet.mapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
import org.jiayunet.pojo.po.UserResumeInternship;
/**
@@ -8,5 +8,6 @@ import org.jiayunet.pojo.po.UserResumeInternship;
*
* @author zk
*/
-public interface UserResumeInternshipMapper extends BaseMapper {
+@Mapper
+public interface UserResumeInternshipMapper extends CommonMapper {
}
diff --git a/manager/src/main/java/org/jiayunet/mapper/UserResumeMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserResumeMapper.java
index a31554b..8764483 100644
--- a/manager/src/main/java/org/jiayunet/mapper/UserResumeMapper.java
+++ b/manager/src/main/java/org/jiayunet/mapper/UserResumeMapper.java
@@ -1,6 +1,6 @@
package org.jiayunet.mapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
import org.jiayunet.pojo.po.UserResume;
/**
@@ -8,5 +8,6 @@ import org.jiayunet.pojo.po.UserResume;
*
* @author zk
*/
-public interface UserResumeMapper extends BaseMapper {
+@Mapper
+public interface UserResumeMapper extends CommonMapper {
}
diff --git a/manager/src/main/java/org/jiayunet/mapper/UserResumeProjectMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserResumeProjectMapper.java
index b1b8a32..9abdde3 100644
--- a/manager/src/main/java/org/jiayunet/mapper/UserResumeProjectMapper.java
+++ b/manager/src/main/java/org/jiayunet/mapper/UserResumeProjectMapper.java
@@ -1,6 +1,6 @@
package org.jiayunet.mapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
import org.jiayunet.pojo.po.UserResumeProject;
/**
@@ -8,5 +8,6 @@ import org.jiayunet.pojo.po.UserResumeProject;
*
* @author zk
*/
-public interface UserResumeProjectMapper extends BaseMapper {
+@Mapper
+public interface UserResumeProjectMapper extends CommonMapper {
}
diff --git a/manager/src/main/java/org/jiayunet/mapper/UserResumeWorkMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserResumeWorkMapper.java
index 16e1477..309fac2 100644
--- a/manager/src/main/java/org/jiayunet/mapper/UserResumeWorkMapper.java
+++ b/manager/src/main/java/org/jiayunet/mapper/UserResumeWorkMapper.java
@@ -1,6 +1,6 @@
package org.jiayunet.mapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
import org.jiayunet.pojo.po.UserResumeWork;
/**
@@ -8,5 +8,6 @@ import org.jiayunet.pojo.po.UserResumeWork;
*
* @author zk
*/
-public interface UserResumeWorkMapper extends BaseMapper {
+@Mapper
+public interface UserResumeWorkMapper extends CommonMapper {
}