diff --git a/.kiro/steering/代码开发风格文档.md b/.kiro/steering/代码开发风格文档.md index 273f603..ee16467 100644 --- a/.kiro/steering/代码开发风格文档.md +++ b/.kiro/steering/代码开发风格文档.md @@ -70,10 +70,10 @@ inclusion: manual ## POJO 规范 -- DTO:入参对象 + 校验注解,放在 `pojo/dto/{功能模块}/` 下,功能模块一般是对应 Service 类名的简写,如 `pojo/dto/login/SmsLoginDto.java` -- VO:出参对象,放在 `pojo/vo/` 下,如 `pojo/vo/LoginVo.java` +- Param:入参对象 + 校验注解,放在 `pojo/param/{功能模块}/` 下,功能模块一般是对应 Service 类名的简写,如 `pojo/param/login/SmsLoginParam.java` +- DTO:出参对象(接口返回给前端的数据),放在 `pojo/dto/{功能模块}/` 下,如 `pojo/dto/login/SmsLoginDto.java` +- VO:多场景共享的复合对象,放在 `pojo/vo/` 下,如 `pojo/vo/RouteMenuVo.java` - PO:对应数据库表字段,统一放在 `manager` 模块的 `pojo/po/` 下 -- Param:查询参数对象,放在 `pojo/param/{功能模块}/` 下 ## 获取当前登录用户 @@ -118,4 +118,11 @@ inclusion: manual - PO 类加 `@TableName(value = "bg_xxx")`,主键加 `@TableId(type = IdType.ASSIGN_ID)` - 查询使用 `LambdaQueryWrapper` 构建条件,避免硬编码字段名 - 简单的 CRUD 直接使用 MyBatis-Plus 的 `BaseMapper` 方法和 `LambdaQueryWrapper`,只有复杂查询(多表关联、子查询等)才写 Mapper XML SQL -- Mapper 接口继承 `BaseMapper` \ No newline at end of file +- Mapper 接口继承 `BaseMapper` + +### JSON 字段规范 +- 数据库中存储 JSON 的字段,PO 中必须使用对应的 Java 类型(`List`、`List`、`List` 等),不使用 `String` +- 通过 MyBatis-Plus 的 `@TableField(typeHandler = JacksonTypeHandler.class)` 注解实现自动序列化/反序列化 +- 含有 TypeHandler 字段的 PO,`@TableName` 必须加 `autoResultMap = true`,如 `@TableName(value = "bg_xxx", autoResultMap = true)` +- JSON 数组存简单值用 `List` 或 `List`,存复杂结构则抽象为独立的 VO 类(如 `DescriptionParagraph`),放在 `manager/pojo/vo/` 下 +- Param 和 Dto 中对应字段直接使用相同的 Java 类型,Controller 层通过 `BeanUtils.copyProperties` 直接拷贝,不做手动 JSON 转换 \ No newline at end of file diff --git a/.kiro/steering/项目结构说明.md b/.kiro/steering/项目结构说明.md index f588768..0058bfd 100644 --- a/.kiro/steering/项目结构说明.md +++ b/.kiro/steering/项目结构说明.md @@ -21,16 +21,21 @@ offerpie/back-end │ │ └─ FuncPermissionAspect.java # 功能权限校验切面(拦截注解,校验+扣减+异常回退) │ ├─ controller/ │ │ ├─ LoginController.java # 登录相关接口(发送验证码、短信登录) -│ │ └─ RouteMenuController.java # 路由菜单接口(获取用户有效菜单树) +│ │ ├─ RouteMenuController.java # 路由菜单接口(获取用户有效菜单树) +│ │ └─ UserProfileController.java # 用户个人资料接口(主表+5张子表的查询与保存) │ ├─ service/ │ │ ├─ LoginService.java # 登录业务逻辑(验证码校验、自动注册、JWT生成、Cookie设置) │ │ ├─ UserRegisterService.java # 用户注册服务(注册逻辑、邀请码生成与绑定) │ │ ├─ FuncPermissionService.java # 功能权限服务(校验、扣减、查询、添加库存、回退) │ │ ├─ RouteMenuService.java # 路由菜单服务(查询、添加库存、获取用户菜单树) +│ │ ├─ UserProfileService.java # 用户个人资料服务(主表+5张子表的CRUD) │ │ └─ WxPayNotifyMessageAbstractImpl.java # 微信支付回调实现 │ └─ pojo/ +│ ├─ param/ +│ │ └─ userProfile/ # 个人资料入参(UserProfileParam、各子表Param) │ ├─ dto/ -│ │ └─ SmsLoginDto.java # 短信登录入参(mobileNumber + code + inviteCode) +│ │ ├─ SmsLoginDto.java # 短信登录入参(mobileNumber + code + inviteCode) +│ │ └─ userProfile/ # 个人资料出参(UserProfileDto、各子表Dto) │ └─ vo/ │ ├─ LoginVo.java # 登录返回(userId + nick) │ └─ RouteMenuVo.java # 路由菜单树形VO(含children子菜单) @@ -113,7 +118,7 @@ offerpie/back-end │ │ ├─ UserProfileProject.java # 用户项目经历表(bg_user_profile_project) │ │ ├─ UserProfileCompetition.java # 用户竞赛经历表(bg_user_profile_competition) │ │ └─ AppJobData.java # 爬虫岗位原始数据表(app_job_data) - │ └─ vo/ # ViewObject(OssUrlVo 等) + │ └─ vo/ # ViewObject(OssUrlVo、DescriptionParagraph 等) └─ service/ # 业务 Service(OssService、SmsService、DictCacheService、JobCleanService、JobCleanTransactionService、CompanyCleanService、CompanyCleanTransactionService 等) ``` > **设计理念** – 业务实体和 Mapper 位于 `manager`,B 端和 C 端共享;C 端特有的注解、切面、权限服务、路由菜单服务位于 `client-api`,避免 B 端误用;`common` 提供统一的技术支撑。 diff --git a/client-api/src/main/java/org/jiayunet/controller/UserProfileController.java b/client-api/src/main/java/org/jiayunet/controller/UserProfileController.java new file mode 100644 index 0000000..fb01e19 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/controller/UserProfileController.java @@ -0,0 +1,153 @@ +package org.jiayunet.controller; + +import lombok.AllArgsConstructor; +import org.jiayunet.pojo.dto.userProfile.*; +import org.jiayunet.pojo.param.userProfile.*; +import org.jiayunet.pojo.po.*; +import org.jiayunet.service.UserProfileService; +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; + +/** + * 用户个人资料控制类 + * + * @author zk + */ +@RestController +@RequestMapping("/user/profile") +@AllArgsConstructor +@Validated +public class UserProfileController { + + private UserProfileService userProfileService; + + // ==================== 主表 ==================== + + @GetMapping + public UserProfileDto getProfile() { + UserProfile po = userProfileService.getProfile(); + if (po == null) { + return null; + } + UserProfileDto dto = new UserProfileDto(); + BeanUtils.copyProperties(po, dto); + return dto; + } + + @PostMapping + public void saveProfile(@Validated @RequestBody UserProfileParam param) { + UserProfile po = new UserProfile(); + BeanUtils.copyProperties(param, po); + userProfileService.saveProfile(po); + } + + // ==================== 教育经历 ==================== + + @GetMapping("/education") + public List listEducation() { + return userProfileService.listEducation().stream().map(po -> { + UserProfileEducationDto dto = new UserProfileEducationDto(); + BeanUtils.copyProperties(po, dto); + return dto; + }).collect(Collectors.toList()); + } + + @PostMapping("/education") + public void saveEducation(@Validated @RequestBody List<@Valid UserProfileEducationParam> params) { + List list = params.stream().map(p -> { + UserProfileEducation po = new UserProfileEducation(); + BeanUtils.copyProperties(p, po); + return po; + }).collect(Collectors.toList()); + userProfileService.saveEducationList(list); + } + + // ==================== 工作经历 ==================== + + @GetMapping("/work") + public List listWork() { + return userProfileService.listWork().stream().map(po -> { + UserProfileWorkDto dto = new UserProfileWorkDto(); + BeanUtils.copyProperties(po, dto); + return dto; + }).collect(Collectors.toList()); + } + + @PostMapping("/work") + public void saveWork(@Validated @RequestBody List<@Valid UserProfileWorkParam> params) { + List list = params.stream().map(p -> { + UserProfileWork po = new UserProfileWork(); + BeanUtils.copyProperties(p, po); + return po; + }).collect(Collectors.toList()); + userProfileService.saveWorkList(list); + } + + // ==================== 实习经历 ==================== + + @GetMapping("/internship") + public List listInternship() { + return userProfileService.listInternship().stream().map(po -> { + UserProfileInternshipDto dto = new UserProfileInternshipDto(); + BeanUtils.copyProperties(po, dto); + return dto; + }).collect(Collectors.toList()); + } + + @PostMapping("/internship") + public void saveInternship(@Validated @RequestBody List<@Valid UserProfileInternshipParam> params) { + List list = params.stream().map(p -> { + UserProfileInternship po = new UserProfileInternship(); + BeanUtils.copyProperties(p, po); + return po; + }).collect(Collectors.toList()); + userProfileService.saveInternshipList(list); + } + + // ==================== 项目经历 ==================== + + @GetMapping("/project") + public List listProject() { + return userProfileService.listProject().stream().map(po -> { + UserProfileProjectDto dto = new UserProfileProjectDto(); + BeanUtils.copyProperties(po, dto); + return dto; + }).collect(Collectors.toList()); + } + + @PostMapping("/project") + public void saveProject(@Validated @RequestBody List<@Valid UserProfileProjectParam> params) { + List list = params.stream().map(p -> { + UserProfileProject po = new UserProfileProject(); + BeanUtils.copyProperties(p, po); + return po; + }).collect(Collectors.toList()); + userProfileService.saveProjectList(list); + } + + // ==================== 竞赛经历 ==================== + + @GetMapping("/competition") + public List listCompetition() { + return userProfileService.listCompetition().stream().map(po -> { + UserProfileCompetitionDto dto = new UserProfileCompetitionDto(); + BeanUtils.copyProperties(po, dto); + return dto; + }).collect(Collectors.toList()); + } + + @PostMapping("/competition") + public void saveCompetition(@Validated @RequestBody List<@Valid UserProfileCompetitionParam> params) { + List list = params.stream().map(p -> { + UserProfileCompetition po = new UserProfileCompetition(); + BeanUtils.copyProperties(p, po); + return po; + }).collect(Collectors.toList()); + userProfileService.saveCompetitionList(list); + } +} diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileCompetitionDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileCompetitionDto.java new file mode 100644 index 0000000..4c07b1f --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileCompetitionDto.java @@ -0,0 +1,29 @@ +package org.jiayunet.pojo.dto.userProfile; + +import lombok.Data; +import org.jiayunet.pojo.vo.DescriptionParagraph; + +import java.util.List; + +/** + * 竞赛经历返回 + * + * @author zk + */ +@Data +public class UserProfileCompetitionDto { + + private Long id; + + /** 竞赛名称 */ + private String competitionName; + + /** 获奖情况 */ + private String award; + + /** 获奖时间 */ + private String awardDate; + + /** 描述段落 */ + private List description; +} 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 new file mode 100644 index 0000000..3acf183 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileDto.java @@ -0,0 +1,49 @@ +package org.jiayunet.pojo.dto.userProfile; + +import lombok.Data; + +import java.util.List; + +/** + * 个人资料主表返回 + * + * @author zk + */ +@Data +public class UserProfileDto { + + private Long id; + + /** 真实姓名 */ + private String name; + + /** 邮箱 */ + private String email; + + /** 手机号码 */ + private String mobileNumber; + + /** 身份证号 */ + private String idCard; + + /** 所在城市编码 */ + private String regionCode; + + /** 微信号 */ + private String wechatNumber; + + /** 作品集链接 */ + private String portfolioUrl; + + /** 工作年限 */ + private Integer workYears; + + /** 拥有经验的行业ID列表 */ + private List experienceIndustryIds; + + /** 技能标签列表 */ + private List skills; + + /** 证书标签列表 */ + private List certificates; +} diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileEducationDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileEducationDto.java new file mode 100644 index 0000000..f7d7e08 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileEducationDto.java @@ -0,0 +1,38 @@ +package org.jiayunet.pojo.dto.userProfile; + +import lombok.Data; +import org.jiayunet.pojo.vo.DescriptionParagraph; + +import java.util.List; + +/** + * 教育经历返回 + * + * @author zk + */ +@Data +public class UserProfileEducationDto { + + private Long id; + + /** 学校名称 */ + private String school; + + /** 专业 */ + private String major; + + /** 学历 1=大专 2=本科 3=硕士 4=博士 */ + private Integer degree; + + /** 学习形式 0=全日制 1=非全日制 */ + private Integer studyType; + + /** 入学年份 */ + private Integer startYear; + + /** 毕业年份 */ + private Integer endYear; + + /** 描述段落 */ + private List description; +} diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileInternshipDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileInternshipDto.java new file mode 100644 index 0000000..a1cc0fb --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileInternshipDto.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 UserProfileInternshipDto { + + 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/userProfile/UserProfileProjectDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileProjectDto.java new file mode 100644 index 0000000..bada870 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileProjectDto.java @@ -0,0 +1,35 @@ +package org.jiayunet.pojo.dto.userProfile; + +import lombok.Data; +import org.jiayunet.pojo.vo.DescriptionParagraph; + +import java.util.List; + +/** + * 项目经历返回 + * + * @author zk + */ +@Data +public class UserProfileProjectDto { + + 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/userProfile/UserProfileWorkDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileWorkDto.java new file mode 100644 index 0000000..5de6cb2 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/dto/userProfile/UserProfileWorkDto.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 UserProfileWorkDto { + + 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/userProfile/UserProfileCompetitionParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileCompetitionParam.java new file mode 100644 index 0000000..258a2a6 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileCompetitionParam.java @@ -0,0 +1,29 @@ +package org.jiayunet.pojo.param.userProfile; + +import lombok.Data; +import org.jiayunet.pojo.vo.DescriptionParagraph; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 竞赛经历保存入参 + * + * @author zk + */ +@Data +public class UserProfileCompetitionParam { + + /** 竞赛名称 */ + @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/userProfile/UserProfileEducationParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileEducationParam.java new file mode 100644 index 0000000..b65ccdb --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileEducationParam.java @@ -0,0 +1,40 @@ +package org.jiayunet.pojo.param.userProfile; + +import lombok.Data; +import org.jiayunet.pojo.vo.DescriptionParagraph; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * 教育经历保存入参 + * + * @author zk + */ +@Data +public class UserProfileEducationParam { + + /** 学校名称 */ + @NotBlank(message = "学校名称不能为空") + private String school; + + /** 专业 */ + private String major; + + /** 学历 1=大专 2=本科 3=硕士 4=博士 */ + @NotNull(message = "学历不能为空") + private Integer degree; + + /** 学习形式 0=全日制 1=非全日制 */ + private Integer studyType; + + /** 入学年份 */ + private Integer startYear; + + /** 毕业年份 */ + private Integer endYear; + + /** 描述段落 */ + private List description; +} diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileInternshipParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileInternshipParam.java new file mode 100644 index 0000000..05c0065 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileInternshipParam.java @@ -0,0 +1,34 @@ +package org.jiayunet.pojo.param.userProfile; + +import lombok.Data; +import org.jiayunet.pojo.vo.DescriptionParagraph; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 实习经历保存入参 + * + * @author zk + */ +@Data +public class UserProfileInternshipParam { + + /** 公司名称 */ + @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/userProfile/UserProfileParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileParam.java new file mode 100644 index 0000000..c275db9 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileParam.java @@ -0,0 +1,49 @@ +package org.jiayunet.pojo.param.userProfile; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 个人资料主表保存入参 + * + * @author zk + */ +@Data +public class UserProfileParam { + + /** 真实姓名 */ + private String name; + + /** 邮箱 */ + private String email; + + /** 手机号码 */ + private String mobileNumber; + + /** 身份证号 */ + private String idCard; + + /** 所在城市编码 */ + @NotBlank(message = "所在城市不能为空") + private String regionCode; + + /** 微信号 */ + private String wechatNumber; + + /** 作品集链接 */ + private String portfolioUrl; + + /** 工作年限 */ + private Integer workYears; + + /** 拥有经验的行业ID列表 */ + private List experienceIndustryIds; + + /** 技能标签列表 */ + private List skills; + + /** 证书标签列表 */ + private List certificates; +} diff --git a/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileProjectParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileProjectParam.java new file mode 100644 index 0000000..e55b479 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileProjectParam.java @@ -0,0 +1,36 @@ +package org.jiayunet.pojo.param.userProfile; + +import lombok.Data; +import org.jiayunet.pojo.vo.DescriptionParagraph; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 项目经历保存入参 + * + * @author zk + */ +@Data +public class UserProfileProjectParam { + + /** 所属公司 */ + 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/userProfile/UserProfileWorkParam.java b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileWorkParam.java new file mode 100644 index 0000000..8518b28 --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/param/userProfile/UserProfileWorkParam.java @@ -0,0 +1,34 @@ +package org.jiayunet.pojo.param.userProfile; + +import lombok.Data; +import org.jiayunet.pojo.vo.DescriptionParagraph; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 工作经历保存入参 + * + * @author zk + */ +@Data +public class UserProfileWorkParam { + + /** 公司名称 */ + @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/UserProfileService.java b/client-api/src/main/java/org/jiayunet/service/UserProfileService.java new file mode 100644 index 0000000..ecbb55e --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/service/UserProfileService.java @@ -0,0 +1,279 @@ +package org.jiayunet.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +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_profile(主表CRUD)、bg_user_profile_education(教育经历)、 + * bg_user_profile_work(工作经历)、bg_user_profile_internship(实习经历)、 + * bg_user_profile_project(项目经历)、bg_user_profile_competition(竞赛经历)

+ * + * @author zk + */ +@Service +@Slf4j +public class UserProfileService { + + @Autowired + private UserProfileMapper userProfileMapper; + + @Autowired + private UserProfileEducationMapper educationMapper; + + @Autowired + private UserProfileWorkMapper workMapper; + + @Autowired + private UserProfileInternshipMapper internshipMapper; + + @Autowired + private UserProfileProjectMapper projectMapper; + + @Autowired + private UserProfileCompetitionMapper competitionMapper; + + // ==================== 主表 ==================== + + /** 查询当前用户个人资料 */ + public UserProfile getProfile() { + Long userId = UserSecurityTool.getUserId(); + return userProfileMapper.selectOne( + new LambdaQueryWrapper().eq(UserProfile::getUserId, userId)); + } + + /** + * 保存个人资料主表(upsert) + *

1. 按userId查询是否存在 2. 存在则更新,不存在则插入

+ */ + @Transactional(rollbackFor = Exception.class) + public void saveProfile(UserProfile profile) { + Long userId = UserSecurityTool.getUserId(); + UserProfile existing = userProfileMapper.selectOne( + new LambdaQueryWrapper().eq(UserProfile::getUserId, userId)); + Instant now = Instant.now(); + if (existing != null) { + profile.setId(existing.getId()); + profile.setUserId(userId); + profile.setCreateTime(existing.getCreateTime()); + profile.setUpdateTime(now); + userProfileMapper.updateById(profile); + } else { + profile.setUserId(userId); + profile.setCreateTime(now); + profile.setUpdateTime(now); + userProfileMapper.insert(profile); + } + } + + // ==================== 教育经历 ==================== + + /** 查询教育经历列表 */ + public List listEducation() { + Long userId = UserSecurityTool.getUserId(); + return educationMapper.selectList( + new LambdaQueryWrapper() + .eq(UserProfileEducation::getUserId, userId) + .orderByAsc(UserProfileEducation::getSortOrder)); + } + + /** + * 保存教育经历列表(先删后插) + *

1. 删除该用户所有教育经历 2. 批量插入新数据,自动填充userId/profileId/sortOrder/时间

+ */ + @Transactional(rollbackFor = Exception.class) + public void saveEducationList(List list) { + Long userId = UserSecurityTool.getUserId(); + Long profileId = getOrCreateProfileId(userId); + educationMapper.delete(new LambdaQueryWrapper().eq(UserProfileEducation::getUserId, userId)); + if (list.isEmpty()) { + return; + } + Instant now = Instant.now(); + IntStream.range(0, list.size()).forEach(i -> { + UserProfileEducation item = list.get(i); + item.setUserId(userId); + item.setProfileId(profileId); + item.setSortOrder(i); + item.setCreateTime(now); + item.setUpdateTime(now); + }); + educationMapper.batchInsert(list); + } + + // ==================== 工作经历 ==================== + + /** 查询工作经历列表 */ + public List listWork() { + Long userId = UserSecurityTool.getUserId(); + return workMapper.selectList( + new LambdaQueryWrapper() + .eq(UserProfileWork::getUserId, userId) + .orderByAsc(UserProfileWork::getSortOrder)); + } + + /** + * 保存工作经历列表(先删后插) + *

1. 删除该用户所有工作经历 2. 批量插入新数据

+ */ + @Transactional(rollbackFor = Exception.class) + public void saveWorkList(List list) { + Long userId = UserSecurityTool.getUserId(); + Long profileId = getOrCreateProfileId(userId); + workMapper.delete( + new LambdaQueryWrapper().eq(UserProfileWork::getUserId, userId)); + if (list.isEmpty()) { + return; + } + Instant now = Instant.now(); + IntStream.range(0, list.size()).forEach(i -> { + UserProfileWork item = list.get(i); + item.setUserId(userId); + item.setProfileId(profileId); + item.setSortOrder(i); + item.setCreateTime(now); + item.setUpdateTime(now); + }); + workMapper.batchInsert(list); + } + + // ==================== 实习经历 ==================== + + /** 查询实习经历列表 */ + public List listInternship() { + Long userId = UserSecurityTool.getUserId(); + return internshipMapper.selectList( + new LambdaQueryWrapper() + .eq(UserProfileInternship::getUserId, userId) + .orderByAsc(UserProfileInternship::getSortOrder)); + } + + /** + * 保存实习经历列表(先删后插) + *

1. 删除该用户所有实习经历 2. 批量插入新数据

+ */ + @Transactional(rollbackFor = Exception.class) + public void saveInternshipList(List list) { + Long userId = UserSecurityTool.getUserId(); + Long profileId = getOrCreateProfileId(userId); + internshipMapper.delete( + new LambdaQueryWrapper().eq(UserProfileInternship::getUserId, userId)); + if (list.isEmpty()) { + return; + } + Instant now = Instant.now(); + IntStream.range(0, list.size()).forEach(i -> { + UserProfileInternship item = list.get(i); + item.setUserId(userId); + item.setProfileId(profileId); + item.setSortOrder(i); + item.setCreateTime(now); + item.setUpdateTime(now); + }); + internshipMapper.batchInsert(list); + } + + // ==================== 项目经历 ==================== + + /** 查询项目经历列表 */ + public List listProject() { + Long userId = UserSecurityTool.getUserId(); + return projectMapper.selectList( + new LambdaQueryWrapper() + .eq(UserProfileProject::getUserId, userId) + .orderByAsc(UserProfileProject::getSortOrder)); + } + + /** + * 保存项目经历列表(先删后插) + *

1. 删除该用户所有项目经历 2. 批量插入新数据

+ */ + @Transactional(rollbackFor = Exception.class) + public void saveProjectList(List list) { + Long userId = UserSecurityTool.getUserId(); + Long profileId = getOrCreateProfileId(userId); + projectMapper.delete( + new LambdaQueryWrapper().eq(UserProfileProject::getUserId, userId)); + if (list.isEmpty()) { + return; + } + Instant now = Instant.now(); + IntStream.range(0, list.size()).forEach(i -> { + UserProfileProject item = list.get(i); + item.setUserId(userId); + item.setProfileId(profileId); + item.setSortOrder(i); + item.setCreateTime(now); + item.setUpdateTime(now); + }); + projectMapper.batchInsert(list); + } + + // ==================== 竞赛经历 ==================== + + /** 查询竞赛经历列表 */ + public List listCompetition() { + Long userId = UserSecurityTool.getUserId(); + return competitionMapper.selectList( + new LambdaQueryWrapper() + .eq(UserProfileCompetition::getUserId, userId) + .orderByAsc(UserProfileCompetition::getSortOrder)); + } + + /** + * 保存竞赛经历列表(先删后插) + *

1. 删除该用户所有竞赛经历 2. 批量插入新数据

+ */ + @Transactional(rollbackFor = Exception.class) + public void saveCompetitionList(List list) { + Long userId = UserSecurityTool.getUserId(); + Long profileId = getOrCreateProfileId(userId); + competitionMapper.delete( + new LambdaQueryWrapper().eq(UserProfileCompetition::getUserId, userId)); + if (list.isEmpty()) { + return; + } + Instant now = Instant.now(); + IntStream.range(0, list.size()).forEach(i -> { + UserProfileCompetition item = list.get(i); + item.setUserId(userId); + item.setProfileId(profileId); + item.setSortOrder(i); + item.setCreateTime(now); + item.setUpdateTime(now); + }); + competitionMapper.batchInsert(list); + } + + // ==================== 内部方法 ==================== + + /** + * 获取用户的profileId,不存在则自动创建空主表记录 + *

子表保存时需要profileId,如果用户还没创建过主表,先插入一条空记录

+ */ + private Long getOrCreateProfileId(Long userId) { + UserProfile profile = userProfileMapper.selectOne( + new LambdaQueryWrapper().eq(UserProfile::getUserId, userId)); + if (profile != null) { + return profile.getId(); + } + profile = new UserProfile(); + profile.setUserId(userId); + Instant now = Instant.now(); + profile.setCreateTime(now); + profile.setUpdateTime(now); + userProfileMapper.insert(profile); + return profile.getId(); + } +} diff --git a/manager/src/main/java/org/jiayunet/mapper/UserProfileCompetitionMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserProfileCompetitionMapper.java index 71fff90..9824e6a 100644 --- a/manager/src/main/java/org/jiayunet/mapper/UserProfileCompetitionMapper.java +++ b/manager/src/main/java/org/jiayunet/mapper/UserProfileCompetitionMapper.java @@ -1,6 +1,5 @@ package org.jiayunet.mapper; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.jiayunet.pojo.po.UserProfileCompetition; @@ -10,5 +9,5 @@ import org.jiayunet.pojo.po.UserProfileCompetition; * @author zk */ @Mapper -public interface UserProfileCompetitionMapper extends BaseMapper { +public interface UserProfileCompetitionMapper extends CommonMapper { } diff --git a/manager/src/main/java/org/jiayunet/mapper/UserProfileEducationMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserProfileEducationMapper.java index ba8c5e5..7e85e5e 100644 --- a/manager/src/main/java/org/jiayunet/mapper/UserProfileEducationMapper.java +++ b/manager/src/main/java/org/jiayunet/mapper/UserProfileEducationMapper.java @@ -1,6 +1,5 @@ package org.jiayunet.mapper; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.jiayunet.pojo.po.UserProfileEducation; @@ -10,5 +9,5 @@ import org.jiayunet.pojo.po.UserProfileEducation; * @author zk */ @Mapper -public interface UserProfileEducationMapper extends BaseMapper { +public interface UserProfileEducationMapper extends CommonMapper { } diff --git a/manager/src/main/java/org/jiayunet/mapper/UserProfileInternshipMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserProfileInternshipMapper.java index b9b867e..e60859d 100644 --- a/manager/src/main/java/org/jiayunet/mapper/UserProfileInternshipMapper.java +++ b/manager/src/main/java/org/jiayunet/mapper/UserProfileInternshipMapper.java @@ -1,6 +1,5 @@ package org.jiayunet.mapper; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.jiayunet.pojo.po.UserProfileInternship; @@ -10,5 +9,5 @@ import org.jiayunet.pojo.po.UserProfileInternship; * @author zk */ @Mapper -public interface UserProfileInternshipMapper extends BaseMapper { +public interface UserProfileInternshipMapper extends CommonMapper { } diff --git a/manager/src/main/java/org/jiayunet/mapper/UserProfileProjectMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserProfileProjectMapper.java index 51f0484..5aa9b9a 100644 --- a/manager/src/main/java/org/jiayunet/mapper/UserProfileProjectMapper.java +++ b/manager/src/main/java/org/jiayunet/mapper/UserProfileProjectMapper.java @@ -1,6 +1,5 @@ package org.jiayunet.mapper; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.jiayunet.pojo.po.UserProfileProject; @@ -10,5 +9,5 @@ import org.jiayunet.pojo.po.UserProfileProject; * @author zk */ @Mapper -public interface UserProfileProjectMapper extends BaseMapper { +public interface UserProfileProjectMapper extends CommonMapper { } diff --git a/manager/src/main/java/org/jiayunet/mapper/UserProfileWorkMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserProfileWorkMapper.java index 5740e49..0cca254 100644 --- a/manager/src/main/java/org/jiayunet/mapper/UserProfileWorkMapper.java +++ b/manager/src/main/java/org/jiayunet/mapper/UserProfileWorkMapper.java @@ -1,6 +1,5 @@ package org.jiayunet.mapper; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.jiayunet.pojo.po.UserProfileWork; @@ -10,5 +9,5 @@ import org.jiayunet.pojo.po.UserProfileWork; * @author zk */ @Mapper -public interface UserProfileWorkMapper extends BaseMapper { +public interface UserProfileWorkMapper 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 3e4fc2a..f658e43 100644 --- a/manager/src/main/java/org/jiayunet/pojo/po/UserProfile.java +++ b/manager/src/main/java/org/jiayunet/pojo/po/UserProfile.java @@ -1,11 +1,14 @@ 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 java.time.Instant; +import java.util.List; /** * 用户个人资料表(bg_user_profile) @@ -13,7 +16,7 @@ import java.time.Instant; * @author zk */ @Data -@TableName(value = "bg_user_profile") +@TableName(value = "bg_user_profile", autoResultMap = true) public class UserProfile { @TableId(type = IdType.ASSIGN_ID) @@ -22,6 +25,15 @@ public class UserProfile { /** 用户ID */ private Long userId; + /** 真实姓名 */ + private String name; + + /** 邮箱 */ + private String email; + + /** 手机号码 */ + private String mobileNumber; + /** 身份证号 */ private String idCard; @@ -37,14 +49,17 @@ public class UserProfile { /** 工作年限(不展示,用于适配度计算) */ private Integer workYears; - /** 拥有经验的行业ID列表(不展示,用于适配度计算),格式:[1, 3, 7] */ - private String experienceIndustryIds; + /** 拥有经验的行业ID列表(不展示,用于适配度计算) */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List experienceIndustryIds; - /** 技能标签列表,格式:["CET 6", "Photoshop", "Python"] */ - private String skills; + /** 技能标签列表 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List skills; - /** 证书标签列表,格式:["CFA", "CMA"] */ - private String certificates; + /** 证书标签列表 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List certificates; /** 创建时间 */ private Instant createTime; diff --git a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileCompetition.java b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileCompetition.java index bea792f..ee02ce5 100644 --- a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileCompetition.java +++ b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileCompetition.java @@ -1,11 +1,15 @@ 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_competition,bg_user_profile子表) @@ -13,7 +17,7 @@ import java.time.Instant; * @author zk */ @Data -@TableName(value = "bg_user_profile_competition") +@TableName(value = "bg_user_profile_competition", autoResultMap = true) public class UserProfileCompetition { @TableId(type = IdType.ASSIGN_ID) @@ -34,8 +38,9 @@ public class UserProfileCompetition { /** 获奖时间,格式:2023.07 */ private String awardDate; - /** 描述段落,格式:[{"id":"a1b2","text":"协助分析师开展TMT行业研究..."}],id为前端生成的短标识,用于简历优化时精确定位段落 */ - private String description; + /** 描述段落,id为前端生成的短标识,用于简历优化时精确定位段落 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List description; /** 排序序号,越小越靠前 */ private Integer sortOrder; diff --git a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileEducation.java b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileEducation.java index c7ad0b7..2acd353 100644 --- a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileEducation.java +++ b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileEducation.java @@ -1,11 +1,15 @@ 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_education,bg_user_profile子表) @@ -13,7 +17,7 @@ import java.time.Instant; * @author zk */ @Data -@TableName(value = "bg_user_profile_education") +@TableName(value = "bg_user_profile_education", autoResultMap = true) public class UserProfileEducation { @TableId(type = IdType.ASSIGN_ID) @@ -43,8 +47,9 @@ public class UserProfileEducation { /** 毕业年份 */ private Integer endYear; - /** 描述段落,格式:[{"id":"a1b2","text":"GPA: 3.8/4.0..."}],id为前端生成的短标识,用于简历优化时精确定位段落 */ - private String description; + /** 描述段落,id为前端生成的短标识,用于简历优化时精确定位段落 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List description; /** 排序序号,越小越靠前 */ private Integer sortOrder; diff --git a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileInternship.java b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileInternship.java index 96d3aeb..e033555 100644 --- a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileInternship.java +++ b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileInternship.java @@ -1,11 +1,15 @@ 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_internship,bg_user_profile子表) @@ -13,7 +17,7 @@ import java.time.Instant; * @author zk */ @Data -@TableName(value = "bg_user_profile_internship") +@TableName(value = "bg_user_profile_internship", autoResultMap = true) public class UserProfileInternship { @TableId(type = IdType.ASSIGN_ID) @@ -37,8 +41,9 @@ public class UserProfileInternship { /** 结束时间,格式:2023.09,至今则为空 */ private String endDate; - /** 描述段落,格式:[{"id":"a1b2","text":"参与传统行业投资项目分析..."}],id为前端生成的短标识,用于简历优化时精确定位段落 */ - private String description; + /** 描述段落,id为前端生成的短标识,用于简历优化时精确定位段落 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List description; /** 排序序号,越小越靠前 */ private Integer sortOrder; diff --git a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileProject.java b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileProject.java index 8c0d066..c9bf77b 100644 --- a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileProject.java +++ b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileProject.java @@ -1,11 +1,15 @@ 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_project,bg_user_profile子表) @@ -13,7 +17,7 @@ import java.time.Instant; * @author zk */ @Data -@TableName(value = "bg_user_profile_project") +@TableName(value = "bg_user_profile_project", autoResultMap = true) public class UserProfileProject { @TableId(type = IdType.ASSIGN_ID) @@ -40,8 +44,9 @@ public class UserProfileProject { /** 结束时间,格式:2023.09,至今则为空 */ private String endDate; - /** 描述段落,格式:[{"id":"a1b2","text":"围绕新能源行业投资研究..."}],id为前端生成的短标识,用于简历优化时精确定位段落 */ - private String description; + /** 描述段落,id为前端生成的短标识,用于简历优化时精确定位段落 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List description; /** 排序序号,越小越靠前 */ private Integer sortOrder; diff --git a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileWork.java b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileWork.java index a102338..cece1a1 100644 --- a/manager/src/main/java/org/jiayunet/pojo/po/UserProfileWork.java +++ b/manager/src/main/java/org/jiayunet/pojo/po/UserProfileWork.java @@ -1,11 +1,15 @@ 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_work,bg_user_profile子表) @@ -13,7 +17,7 @@ import java.time.Instant; * @author zk */ @Data -@TableName(value = "bg_user_profile_work") +@TableName(value = "bg_user_profile_work", autoResultMap = true) public class UserProfileWork { @TableId(type = IdType.ASSIGN_ID) @@ -37,8 +41,9 @@ public class UserProfileWork { /** 结束时间,格式:2023.09,至今则为空 */ private String endDate; - /** 描述段落,格式:[{"id":"a1b2","text":"参与传统行业投资项目分析..."}],id为前端生成的短标识,用于简历优化时精确定位段落 */ - private String description; + /** 描述段落,id为前端生成的短标识,用于简历优化时精确定位段落 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List description; /** 排序序号,越小越靠前 */ private Integer sortOrder; diff --git a/manager/src/main/java/org/jiayunet/pojo/vo/DescriptionParagraph.java b/manager/src/main/java/org/jiayunet/pojo/vo/DescriptionParagraph.java new file mode 100644 index 0000000..0df2944 --- /dev/null +++ b/manager/src/main/java/org/jiayunet/pojo/vo/DescriptionParagraph.java @@ -0,0 +1,19 @@ +package org.jiayunet.pojo.vo; + +import lombok.Data; + +/** + * 描述段落对象(多场景共享) + *

用于个人资料各子表的描述字段,前端按段落编辑,id为前端生成的短标识,用于简历优化时精确定位段落

+ * + * @author zk + */ +@Data +public class DescriptionParagraph { + + /** 段落标识,前端生成的短ID */ + private String id; + + /** 段落文本内容 */ + private String text; +}