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; +}