From 0a4b97e40eabae4f3bc4973de0ee9c1b980a5c87 Mon Sep 17 00:00:00 2001
From: zk
Date: Mon, 10 Aug 2026 18:27:48 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=AA=E4=BA=BA=E8=B5=84?=
=?UTF-8?q?=E6=96=99=E5=A4=84=E7=90=86=E8=83=BD=E5=8A=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controller/UserProfileController.java | 31 ++++-
.../pojo/dto/userProfile/UserProfileDto.java | 6 +
.../UserProfileOrganizationDto.java | 32 +++++
.../UserProfileOrganizationParam.java | 35 ++++++
.../param/userProfile/UserProfileParam.java | 8 ++
.../jiayunet/service/UserProfileService.java | 113 ++++++++++++++++--
.../mapper/UserProfileOrganizationMapper.java | 13 ++
.../org/jiayunet/pojo/po/UserProfile.java | 6 +
.../pojo/po/UserProfileOrganization.java | 56 +++++++++
9 files changed, 289 insertions(+), 11 deletions(-)
create mode 100644 client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileOrganizationDto.java
create mode 100644 client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileOrganizationParam.java
create mode 100644 manager/src/main/java/org/jiayunet/mapper/UserProfileOrganizationMapper.java
create mode 100644 manager/src/main/java/org/jiayunet/pojo/po/UserProfileOrganization.java
diff --git a/client-api/src/main/java/org/jiayunet/controller/UserProfileController.java b/client-api/src/main/java/org/jiayunet/controller/UserProfileController.java
index ef5fc7a..96a7e69 100644
--- a/client-api/src/main/java/org/jiayunet/controller/UserProfileController.java
+++ b/client-api/src/main/java/org/jiayunet/controller/UserProfileController.java
@@ -15,7 +15,7 @@ import java.util.stream.Collectors;
/**
* 用户个人资料接口
- * 提供主表及5张子表(教育/工作/实习/项目/竞赛)的查询与保存功能
+ * 提供主表及6张子表(教育/工作/实习/项目/竞赛/社团组织)的查询与保存功能
*
* @author zk
*/
@@ -54,7 +54,7 @@ public class UserProfileController {
}
/**
- * 根据简历ID同步更新个人资料(主表+5子表全量覆盖,触发一次异步AI分析)
+ * 根据简历ID同步更新个人资料(主表+6子表全量覆盖,触发一次异步AI分析)
*/
@PostMapping("/syncFromResume")
public void syncFromResume(@RequestParam Long resumeId) {
@@ -203,4 +203,31 @@ public class UserProfileController {
}).collect(Collectors.toList());
userProfileService.saveCompetitionList(list);
}
+
+ // ==================== 社团组织经历 ====================
+
+ /**
+ * 查询当前用户的社团组织经历列表
+ */
+ @GetMapping("/organization")
+ public List listOrganization() {
+ return userProfileService.listOrganization().stream().map(po -> {
+ UserProfileOrganizationDto dto = new UserProfileOrganizationDto();
+ BeanUtils.copyProperties(po, dto);
+ return dto;
+ }).collect(Collectors.toList());
+ }
+
+ /**
+ * 保存当前用户的社团组织经历列表(全量替换)
+ */
+ @PostMapping("/organization")
+ public void saveOrganization(@Validated @RequestBody List<@Valid UserProfileOrganizationParam> params) {
+ List list = params.stream().map(p -> {
+ UserProfileOrganization po = new UserProfileOrganization();
+ BeanUtils.copyProperties(p, po);
+ return po;
+ }).collect(Collectors.toList());
+ userProfileService.saveOrganizationList(list);
+ }
}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileDto.java
index 3acf183..2d63052 100644
--- a/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileDto.java
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileDto.java
@@ -46,4 +46,10 @@ public class UserProfileDto {
/** 证书标签列表 */
private List certificates;
+
+ /** 兴趣爱好 */
+ private String hobbies;
+
+ /** 语言能力 */
+ private String languageSkills;
}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileOrganizationDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileOrganizationDto.java
new file mode 100644
index 0000000..a4081ed
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileOrganizationDto.java
@@ -0,0 +1,32 @@
+package org.jiayunet.pojo.dto.userProfile;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import java.util.List;
+
+/**
+ * 社团组织经历返回
+ *
+ * @author zk
+ */
+@Data
+public class UserProfileOrganizationDto {
+
+ private Long id;
+
+ /** 社团/组织名称 */
+ private String organizationName;
+
+ /** 担任角色 */
+ private String role;
+
+ /** 开始时间 */
+ private String startDate;
+
+ /** 结束时间 */
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileOrganizationParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileOrganizationParam.java
new file mode 100644
index 0000000..f97f6e1
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileOrganizationParam.java
@@ -0,0 +1,35 @@
+package org.jiayunet.pojo.param.userProfile;
+
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import javax.validation.constraints.Size;
+import java.util.List;
+
+/**
+ * 社团组织经历保存入参
+ *
+ * @author zk
+ */
+@Data
+public class UserProfileOrganizationParam {
+
+ /** 社团/组织名称 */
+ @Size(max = 200, message = "社团/组织名称长度不能超过200")
+ private String organizationName;
+
+ /** 担任角色 */
+ @Size(max = 100, message = "担任角色长度不能超过100")
+ private String role;
+
+ /** 开始时间,格式:2023.06 */
+ @Size(max = 10, message = "开始时间长度不能超过10")
+ private String startDate;
+
+ /** 结束时间,格式:2023.09,至今则为空 */
+ @Size(max = 10, message = "结束时间长度不能超过10")
+ private String endDate;
+
+ /** 描述段落 */
+ private List description;
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileParam.java
index 3b702ad..5d1cce0 100644
--- a/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileParam.java
+++ b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileParam.java
@@ -52,4 +52,12 @@ public class UserProfileParam {
/** 证书标签列表 */
private List certificates;
+
+ /** 兴趣爱好 */
+ @Size(max = 2000, message = "兴趣爱好长度不能超过2000")
+ private String hobbies;
+
+ /** 语言能力 */
+ @Size(max = 2000, message = "语言能力长度不能超过2000")
+ private String languageSkills;
}
diff --git a/client-api/src/main/java/org/jiayunet/service/UserProfileService.java b/client-api/src/main/java/org/jiayunet/service/UserProfileService.java
index d022230..06ed1b0 100644
--- a/client-api/src/main/java/org/jiayunet/service/UserProfileService.java
+++ b/client-api/src/main/java/org/jiayunet/service/UserProfileService.java
@@ -23,7 +23,8 @@ import java.util.stream.IntStream;
* 依赖:无
* 使用表:bg_user_profile(主表CRUD)、bg_user_profile_education(教育经历)、
* bg_user_profile_work(工作经历)、bg_user_profile_internship(实习经历)、
- * bg_user_profile_project(项目经历)、bg_user_profile_competition(竞赛经历)
+ * bg_user_profile_project(项目经历)、bg_user_profile_competition(竞赛经历)、
+ * bg_user_profile_organization(社团组织经历)
*
* @author zk
*/
@@ -49,6 +50,9 @@ public class UserProfileService {
@Autowired
private UserProfileCompetitionMapper competitionMapper;
+ @Autowired
+ private UserProfileOrganizationMapper organizationMapper;
+
@Autowired
private org.jiayunet.service.UserProfileAnalyzeService userProfileAnalyzeService;
@@ -70,6 +74,9 @@ public class UserProfileService {
@Autowired
private UserResumeCompetitionMapper resumeCompetitionMapper;
+ @Autowired
+ private UserResumeOrganizationMapper resumeOrganizationMapper;
+
/** 学历文本→枚举映射 */
private static final Map DEGREE_MAP = Map.of(
"大专", 1, "本科", 2, "硕士", 3, "博士", 4
@@ -126,6 +133,8 @@ public class UserProfileService {
.set(UserProfile::getPortfolioUrl, profile.getPortfolioUrl())
.set(UserProfile::getSkills, profile.getSkills(), JSON_TYPE_HANDLER)
.set(UserProfile::getCertificates, profile.getCertificates(), JSON_TYPE_HANDLER)
+ .set(UserProfile::getHobbies, profile.getHobbies())
+ .set(UserProfile::getLanguageSkills, profile.getLanguageSkills())
.set(UserProfile::getUpdateTime, now));
} else {
profile.setUserId(userId);
@@ -320,11 +329,48 @@ public class UserProfileService {
userProfileAnalyzeService.analyzeUserProfile(userId);
}
+ // ==================== 社团组织经历 ====================
+
+ /** 查询社团组织经历列表 */
+ public List listOrganization() {
+ Long userId = UserSecurityTool.getUserId();
+ return organizationMapper.selectList(
+ new LambdaQueryWrapper()
+ .eq(UserProfileOrganization::getUserId, userId)
+ .orderByAsc(UserProfileOrganization::getSortOrder));
+ }
+
+ /**
+ * 保存社团组织经历列表(先删后插)
+ * 1. 删除该用户所有社团组织经历 2. 批量插入新数据
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public void saveOrganizationList(List list) {
+ Long userId = UserSecurityTool.getUserId();
+ Long profileId = getOrCreateProfileId(userId);
+ organizationMapper.delete(
+ new LambdaQueryWrapper().eq(UserProfileOrganization::getUserId, userId));
+ if (list.isEmpty()) {
+ return;
+ }
+ Instant now = Instant.now();
+ IntStream.range(0, list.size()).forEach(i -> {
+ UserProfileOrganization item = list.get(i);
+ item.setUserId(userId);
+ item.setProfileId(profileId);
+ item.setSortOrder(i);
+ item.setCreateTime(now);
+ item.setUpdateTime(now);
+ });
+ organizationMapper.batchInsert(list);
+ userProfileAnalyzeService.analyzeUserProfile(userId);
+ }
+
// ==================== 从简历同步到个人资料 ====================
/**
* 根据简历ID同步更新个人资料
- * 1. 校验简历归属 2. 读取简历主表+5子表 3. 事务内覆盖写入Profile主表+5子表 4. 同步执行一次AI分析
+ * 1. 校验简历归属 2. 读取简历主表+6子表 3. 事务内覆盖写入Profile主表+6子表 4. 同步执行一次AI分析
*/
@Transactional(rollbackFor = Exception.class)
public void syncProfileFromResume(Long resumeId) {
@@ -336,12 +382,13 @@ public class UserProfileService {
throw new BusinessException(BusinessExpCodeEnum.PERMISSION_DENIED, "简历不存在或无权操作");
}
- // 2. 读取简历5张子表
+ // 2. 读取简历6张子表
List resumeEducationList = resumeEducationMapper.selectList(new LambdaQueryWrapper().eq(UserResumeEducation::getResumeId, resumeId).orderByAsc(UserResumeEducation::getSortOrder));
List resumeWorkList = resumeWorkMapper.selectList(new LambdaQueryWrapper().eq(UserResumeWork::getResumeId, resumeId).orderByAsc(UserResumeWork::getSortOrder));
List resumeInternshipList = resumeInternshipMapper.selectList(new LambdaQueryWrapper().eq(UserResumeInternship::getResumeId, resumeId).orderByAsc(UserResumeInternship::getSortOrder));
List resumeProjectList = resumeProjectMapper.selectList(new LambdaQueryWrapper().eq(UserResumeProject::getResumeId, resumeId).orderByAsc(UserResumeProject::getSortOrder));
List resumeCompetitionList = resumeCompetitionMapper.selectList(new LambdaQueryWrapper().eq(UserResumeCompetition::getResumeId, resumeId).orderByAsc(UserResumeCompetition::getSortOrder));
+ List resumeOrganizationList = resumeOrganizationMapper.selectList(new LambdaQueryWrapper().eq(UserResumeOrganization::getResumeId, resumeId).orderByAsc(UserResumeOrganization::getSortOrder));
Instant now = Instant.now();
@@ -362,6 +409,8 @@ public class UserProfileService {
.set(UserProfile::getPortfolioUrl, resume.getPortfolioUrl())
.set(UserProfile::getSkills, resume.getSkills(), JSON_TYPE_HANDLER)
.set(UserProfile::getCertificates, resume.getCertificates(), JSON_TYPE_HANDLER)
+ .set(UserProfile::getHobbies, resume.getHobbies())
+ .set(UserProfile::getLanguageSkills, resume.getLanguageSkills())
.set(UserProfile::getUpdateTime, now));
} else {
UserProfile profile = new UserProfile();
@@ -373,6 +422,8 @@ public class UserProfileService {
profile.setPortfolioUrl(resume.getPortfolioUrl());
profile.setSkills(resume.getSkills());
profile.setCertificates(resume.getCertificates());
+ profile.setHobbies(resume.getHobbies());
+ profile.setLanguageSkills(resume.getLanguageSkills());
profile.setCreateTime(now);
profile.setUpdateTime(now);
userProfileMapper.insert(profile);
@@ -486,7 +537,28 @@ public class UserProfileService {
competitionMapper.batchInsert(profileCompetitionList);
}
- // 9. 事务内所有数据写入完成,同步执行AI分析并等待完成
+ // 9. 同步社团组织经历
+ organizationMapper.delete(new LambdaQueryWrapper().eq(UserProfileOrganization::getUserId, userId));
+ if (!resumeOrganizationList.isEmpty()) {
+ List profileOrganizationList = IntStream.range(0, resumeOrganizationList.size()).mapToObj(i -> {
+ UserResumeOrganization src = resumeOrganizationList.get(i);
+ UserProfileOrganization dest = new UserProfileOrganization();
+ dest.setProfileId(profileId);
+ dest.setUserId(userId);
+ dest.setOrganizationName(src.getOrganizationName());
+ dest.setRole(src.getRole());
+ dest.setStartDate(src.getStartDate());
+ dest.setEndDate(src.getEndDate());
+ dest.setDescription(src.getDescription());
+ dest.setSortOrder(i);
+ dest.setCreateTime(now);
+ dest.setUpdateTime(now);
+ return dest;
+ }).collect(Collectors.toList());
+ organizationMapper.batchInsert(profileOrganizationList);
+ }
+
+ // 10. 事务内所有数据写入完成,同步执行AI分析并等待完成
userProfileAnalyzeService.analyzeUserProfileSync(userId);
}
@@ -494,9 +566,9 @@ public class UserProfileService {
/**
* 根据当前用户的个人资料创建一份新简历
- * syncProfileFromResume 的反向操作:读取个人资料主表+5子表,新建一份简历(名"个人资料简历")及对应子表,返回新简历ID
- * 1. 校验个人资料存在 2. 读取个人资料5子表 3. 新建简历主表(复制共有字段,简历独有字段targetPosition/avatarUrl/city/summary留空)
- * 4. 转换并批量插入5张简历子表(含degree/studyType反向映射) 5. 返回新简历ID
+ * syncProfileFromResume 的反向操作:读取个人资料主表+6子表,新建一份简历(名"个人资料简历")及对应子表,返回新简历ID
+ * 1. 校验个人资料存在 2. 读取个人资料6子表 3. 新建简历主表(复制共有字段,简历独有字段targetPosition/avatarUrl/city/summary留空)
+ * 4. 转换并批量插入6张简历子表(含degree/studyType反向映射) 5. 返回新简历ID
* 注意:这是创建新简历(非覆盖),不删除已有简历;简历侧无AI分析,故不触发分析
*/
@Transactional(rollbackFor = Exception.class)
@@ -511,12 +583,13 @@ public class UserProfileService {
}
Long profileId = profile.getId();
- // 2. 读取个人资料5张子表
+ // 2. 读取个人资料6张子表
List profileEducationList = educationMapper.selectList(new LambdaQueryWrapper().eq(UserProfileEducation::getProfileId, profileId).orderByAsc(UserProfileEducation::getSortOrder));
List profileWorkList = workMapper.selectList(new LambdaQueryWrapper().eq(UserProfileWork::getProfileId, profileId).orderByAsc(UserProfileWork::getSortOrder));
List profileInternshipList = internshipMapper.selectList(new LambdaQueryWrapper().eq(UserProfileInternship::getProfileId, profileId).orderByAsc(UserProfileInternship::getSortOrder));
List profileProjectList = projectMapper.selectList(new LambdaQueryWrapper().eq(UserProfileProject::getProfileId, profileId).orderByAsc(UserProfileProject::getSortOrder));
List profileCompetitionList = competitionMapper.selectList(new LambdaQueryWrapper().eq(UserProfileCompetition::getProfileId, profileId).orderByAsc(UserProfileCompetition::getSortOrder));
+ List profileOrganizationList = organizationMapper.selectList(new LambdaQueryWrapper().eq(UserProfileOrganization::getProfileId, profileId).orderByAsc(UserProfileOrganization::getSortOrder));
Instant now = Instant.now();
@@ -531,6 +604,8 @@ public class UserProfileService {
resume.setPortfolioUrl(profile.getPortfolioUrl());
resume.setSkills(profile.getSkills());
resume.setCertificates(profile.getCertificates());
+ resume.setHobbies(profile.getHobbies());
+ resume.setLanguageSkills(profile.getLanguageSkills());
// 无简历时新建的这份自动设为默认,与 UserResumeService.saveResume 逻辑一致
boolean hasResume = userResumeMapper.selectCount(new LambdaQueryWrapper().eq(UserResume::getUserId, userId)) > 0;
resume.setIsDefault(hasResume ? 0 : 1);
@@ -642,7 +717,27 @@ public class UserProfileService {
resumeCompetitionMapper.batchInsert(resumeCompetitionList);
}
- // 9. 返回新简历ID
+ // 9. 转换并插入简历社团组织经历
+ if (!profileOrganizationList.isEmpty()) {
+ List resumeOrganizationList = IntStream.range(0, profileOrganizationList.size()).mapToObj(i -> {
+ UserProfileOrganization src = profileOrganizationList.get(i);
+ UserResumeOrganization dest = new UserResumeOrganization();
+ dest.setResumeId(resumeId);
+ dest.setUserId(userId);
+ dest.setOrganizationName(src.getOrganizationName());
+ dest.setRole(src.getRole());
+ dest.setStartDate(src.getStartDate());
+ dest.setEndDate(src.getEndDate());
+ dest.setDescription(src.getDescription());
+ dest.setSortOrder(i);
+ dest.setCreateTime(now);
+ dest.setUpdateTime(now);
+ return dest;
+ }).collect(Collectors.toList());
+ resumeOrganizationMapper.batchInsert(resumeOrganizationList);
+ }
+
+ // 10. 返回新简历ID
return resumeId;
}
diff --git a/manager/src/main/java/org/jiayunet/mapper/UserProfileOrganizationMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserProfileOrganizationMapper.java
new file mode 100644
index 0000000..130df6d
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/mapper/UserProfileOrganizationMapper.java
@@ -0,0 +1,13 @@
+package org.jiayunet.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.jiayunet.pojo.po.UserProfileOrganization;
+
+/**
+ * 用户社团组织经历Mapper
+ *
+ * @author zk
+ */
+@Mapper
+public interface UserProfileOrganizationMapper extends CommonMapper {
+}
diff --git a/manager/src/main/java/org/jiayunet/pojo/po/UserProfile.java b/manager/src/main/java/org/jiayunet/pojo/po/UserProfile.java
index f4ce6d4..a31edff 100644
--- a/manager/src/main/java/org/jiayunet/pojo/po/UserProfile.java
+++ b/manager/src/main/java/org/jiayunet/pojo/po/UserProfile.java
@@ -80,6 +80,12 @@ public class UserProfile {
@TableField(typeHandler = JacksonTypeHandler.class)
private List certificates;
+ /** 兴趣爱好 */
+ private String hobbies;
+
+ /** 语言能力 */
+ private String languageSkills;
+
/** 创建时间 */
private Instant createTime;
diff --git a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileOrganization.java b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileOrganization.java
new file mode 100644
index 0000000..a3af897
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileOrganization.java
@@ -0,0 +1,56 @@
+package org.jiayunet.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.Data;
+import org.jiayunet.pojo.vo.DescriptionParagraph;
+
+import java.time.Instant;
+import java.util.List;
+
+/**
+ * 用户社团组织经历表(bg_user_profile_organization,bg_user_profile子表)
+ *
+ * @author zk
+ */
+@Data
+@TableName(value = "bg_user_profile_organization", autoResultMap = true)
+public class UserProfileOrganization {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /** 关联bg_user_profile.id */
+ private Long profileId;
+
+ /** 用户ID(冗余,便于直接按用户查询) */
+ private Long userId;
+
+ /** 社团/组织名称 */
+ private String organizationName;
+
+ /** 担任角色 */
+ private String role;
+
+ /** 开始时间,格式:2023.06 */
+ private String startDate;
+
+ /** 结束时间,格式:2023.09,至今则为空 */
+ private String endDate;
+
+ /** 描述段落,id为前端生成的短标识,用于简历优化时精确定位段落 */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List description;
+
+ /** 排序序号,越小越靠前 */
+ private Integer sortOrder;
+
+ /** 创建时间 */
+ private Instant createTime;
+
+ /** 更新时间 */
+ private Instant updateTime;
+}