点狮HRM-360度绩效评估系统设计与实现
·
一、业务背景与挑战
1.1 绩效管理的痛点
在企业人力资源管理中,绩效管理是最具挑战性的核心业务之一。传统的绩效管理模式存在诸多问题:
传统绩效评估的局限性:
- 单一视角:仅由上级评估,缺乏全面性
- 主观偏差:评估者个人偏好影响评估结果
- 反馈延迟:年度或季度评估,反馈周期过长
- 缺乏数据:凭印象评估,缺乏客观数据支撑
- 发展导向弱:重考核轻发展,难以促进员工成长
- 形式主义:评估流于形式,难以反映真实绩效
相关链接:
- 🌐 官网:http://www.dianshixinxi.com/
- 📱 演示站:http://cloud.dianshixinxi.com:90/
- 🎨 Gitee:https://gitee.com/glorylion/JFinalOA
- 💻 GitCode:https://gitcode.com/Glory_Lion/pointlion-cloud

1.2 360度评估的价值
什么是360度绩效评估:
360度绩效评估(360-Degree Feedback)是一种全方位的绩效评估方法,通过被评估者的上级、下级、同事、自评、客户等多维度角色进行评估,全面、客观地反映员工的工作表现和能力素质。
360度评估的价值:
- 全面性:多视角评估,全方位了解员工表现
- 客观性:多人评估,减少个人偏见影响
- 发展性:帮助员工识别优势与改进空间
- 参与性:促进团队沟通,增强组织凝聚力
- 数据化:量化评估结果,便于分析和决策
- 持续性:支持持续评估,及时反馈改进
1.3 技术挑战
实现360度评估系统面临的技术挑战:
-
评估模型设计复杂
- 如何设计科学合理的评估维度?
- 如何平衡不同评估者的权重?
- 如何处理评估者之间的评分差异?
-
数据计算量大
- 评估者数量多:一个员工可能有10+个评估者
- 评估指标多:几十个评估指标
- 计算复杂:权重计算、异常值处理、数据聚合
-
实时性要求高
- 评估流程实时推进
- 评估进度实时跟踪
- 评估结果实时计算
-
数据隐私敏感
- 评估结果涉及个人隐私
- 需要严格的权限控制
- 匿名评估的技术实现
-
业务流程复杂
- 评估周期管理
- 评估者选择策略
- 异常情况处理(评估者离职、请假等)
二、整体架构设计
2.1 系统架构全景
┌─────────────────────────────────────────────────────────────────┐
│ 360度绩效评估系统 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 评估配置管理层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 评估项目配置 │ │ 评估维度配置 │ │ 评估指标配置 │ │ │
│ │ │(Project) │ │(Dimension) │ │(Indicator) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 评估流程配置 │ │ 权重规则配置 │ │ 评估者选择规则 │ │ │
│ │ │(Workflow) │ │(Weight Rule) │ │(Selector Rule) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 评估执行引擎层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 评估任务调度 │ │ 评估流程引擎 │ │ 进度监控引擎 │ │ │
│ │ │(Scheduler) │ │(Workflow) │ │(Monitor) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 提醒通知引擎 │ │ 异常处理引擎 │ │ 权限控制引擎 │ │ │
│ │ │(Notifier) │ │(Handler) │ │(Permission) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 数据计算分析层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 评估数据聚合 │ │ 权重计算引擎 │ │ 异常值检测引擎 │ │ │
│ │ │(Aggregator) │ │(Weight Calc) │ │(Outlier Detect) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 评分计算引擎 │ │ 结果排名引擎 │ │ 报表生成引擎 │ │ │
│ │ │(Score Calc) │ │(Ranking) │ │(Report) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 结果应用层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 结果反馈查看 │ │ 绩效改进计划 │ │ 人才盘点分析 │ │ │
│ │ │(Feedback) │ │(Improve Plan)│ │(Talent Review) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 数据可视化 │ │ 趋势分析 │ │ 对比分析 │ │ │
│ │ │(Dashboard) │ │(Trend) │ │(Comparison) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

2.2 核心领域模型
评估项目模型(EvaluationProject):
/**
* 360度评估项目
* 表示一次完整的360度评估活动
*/
@Data
@Entity
@Table(name = "hrm_evaluation_project")
public class EvaluationProject {
/**
* 项目ID
*/
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
/**
* 项目名称
*/
private String name;
/**
* 项目编码
*/
@Column(unique = true, nullable = false)
private String code;
/**
* 项目描述
*/
@Lob
private String description;
/**
* 评估周期类型:ANNUAL(年度), QUARTERLY(季度),
* MONTHLY(月度), ON_DEMAND(按需)
*/
@Enumerated(EnumType.STRING)
private PeriodType periodType;
/**
* 评估开始时间
*/
private LocalDateTime startTime;
/**
* 评估结束时间
*/
private LocalDateTime endTime;
/**
* 项目状态:DRAFT(草稿), PENDING(待开始),
* IN_PROGRESS(进行中), COMPLETED(已完成), ARCHIVED(已归档)
*/
@Enumerated(EnumType.STRING)
private ProjectStatus status;
/**
* 适用范围类型:ALL(全员), DEPARTMENT(部门),
* POSITION(岗位), EMPLOYEE(指定员工)
*/
@Enumerated(EnumType.STRING)
private ScopeType scopeType;
/**
* 适用范围配置(JSON格式)
* 示例:{"departmentIds": ["dept001", "dept002"]}
*/
@Lob
private String scopeConfig;
/**
* 评估模板ID
*/
private String templateId;
/**
* 是否匿名评估
*/
private Boolean anonymous;
/**
* 是否启用自评
*/
private Boolean enableSelfEvaluation;
/**
* 是否启用上级评估
*/
private Boolean enableSuperiorEvaluation;
/**
* 是否启用下级评估
*/
private Boolean enableSubordinateEvaluation;
/**
* 是否启用同事评估
*/
private Boolean enablePeerEvaluation;
/**
* 是否启用客户评估
*/
private Boolean enableCustomerEvaluation;
/**
* 评估者最小数量
*/
private Integer minRaterCount;
/**
* 评估者最大数量
*/
private Integer maxRaterCount;
/**
* 创建人
*/
private String creator;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 扩展配置(JSON格式)
*/
@Lob
private String extConfig;
}
/**
* 周期类型枚举
*/
public enum PeriodType {
/** 年度评估 */
ANNUAL,
/** 季度评估 */
QUARTERLY,
/** 月度评估 */
MONTHLY,
/** 按需评估 */
ON_DEMAND
}
/**
* 项目状态枚举
*/
public enum ProjectStatus {
/** 草稿 */
DRAFT,
/** 待开始 */
PENDING,
/** 进行中 */
IN_PROGRESS,
/** 已完成 */
COMPLETED,
/** 已归档 */
ARCHIVED
}
/**
* 范围类型枚举
*/
public enum ScopeType {
/** 全员 */
ALL,
/** 部门 */
DEPARTMENT,
/** 岗位 */
POSITION,
/** 指定员工 */
EMPLOYEE
}
评估维度模型(EvaluationDimension):
/**
* 评估维度
* 表示评估的一级分类,如:工作业绩、工作能力、工作态度等
*/
@Data
@Entity
@Table(name = "hrm_evaluation_dimension")
public class EvaluationDimension {
/**
* 维度ID
*/
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
/**
* 维度编码
*/
@Column(unique = true, nullable = false)
private String code;
/**
* 维度名称
*/
private String name;
/**
* 维度描述
*/
@Lob
private String description;
/**
* 所属模板ID
*/
private String templateId;
/**
* 父维度ID(支持多级维度)
*/
private String parentId;
/**
* 维度类型:CORE(核心维度), OPTIONAL(可选维度), CUSTOM(自定义维度)
*/
@Enumerated(EnumType.STRING)
private DimensionType type;
/**
* 维度权重(0-100)
*/
private BigDecimal weight;
/**
* 最小分值
*/
private Integer minScore;
/**
* 最大分值
*/
private Integer maxScore;
/**
* 显示顺序
*/
private Integer sortOrder;
/**
* 是否启用
*/
private Boolean enabled;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 扩展配置(JSON格式)
*/
@Lob
private String extConfig;
}
/**
* 维度类型枚举
*/
public enum DimensionType {
/** 核心维度 */
CORE,
/** 可选维度 */
OPTIONAL,
/** 自定义维度 */
CUSTOM
}
评估指标模型(EvaluationIndicator):
/**
* 评估指标
* 表示具体的评估项,如:工作完成质量、团队协作能力等
*/
@Data
@Entity
@Table(name = "hrm_evaluation_indicator")
public class EvaluationIndicator {
/**
* 指标ID
*/
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
/**
* 指标编码
*/
@Column(unique = true, nullable = false)
private String code;
/**
* 指标名称
*/
private String name;
/**
* 指标描述
*/
@Lob
private String description;
/**
* 所属维度ID
*/
private String dimensionId;
/**
* 指标类型:QUANTITATIVE(定量), QUALITATIVE(定性)
*/
@Enumerated(EnumType.STRING)
private IndicatorType type;
/**
* 评分方式:SCORE(打分制), RANKING(等级制),
* CHOICE(选择制), TEXT(文本制)
*/
@Enumerated(EnumType.STRING)
private ScoringType scoringType;
/**
* 指标权重(0-100)
*/
private BigDecimal weight;
/**
* 最小分值
*/
private Integer minScore;
/**
* 最大分值
*/
private Integer maxScore;
/**
* 评分等级定义(JSON格式)
* 示例:[{"level":5,"name":"优秀","description":"表现卓越"}]
*/
@Lob
private String scoreLevels;
/**
* 是否必填
*/
private Boolean required;
/**
* 显示顺序
*/
private Integer sortOrder;
/**
* 是否启用
*/
private Boolean enabled;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 扩展配置(JSON格式)
*/
@Lob
private String extConfig;
}
/**
* 指标类型枚举
*/
public enum IndicatorType {
/** 定量指标 */
QUANTITATIVE,
/** 定性指标 */
QUALITATIVE
}
/**
* 评分方式枚举
*/
public enum ScoringType {
/** 打分制 */
SCORE,
/** 等级制 */
RANKING,
/** 选择制 */
CHOICE,
/** 文本制 */
TEXT
}
评估记录模型(EvaluationRecord):
/**
* 评估记录
* 存储一次评估的完整数据
*/
@Data
@Entity
@Table(name = "hrm_evaluation_record")
public class EvaluationRecord {
/**
* 记录ID
*/
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
/**
* 评估项目ID
*/
private String projectId;
/**
* 被评估者ID
*/
private String evaluateeId;
/**
* 被评估者姓名
*/
private String evaluateeName;
/**
* 评估者ID
*/
private String raterId;
/**
* 评估者姓名
*/
private String raterName;
/**
* 评估关系类型:SELF(自评), SUPERIOR(上级),
* SUBORDINATE(下级), PEER(同事), CUSTOMER(客户)
*/
@Enumerated(EnumType.STRING)
private RaterType raterType;
/**
* 是否匿名
*/
private Boolean anonymous;
/**
* 评估状态:PENDING(待评估), IN_PROGRESS(评估中),
* COMPLETED(已完成), SKIPPED(已跳过)
*/
@Enumerated(EnumType.STRING)
private EvaluationStatus status;
/**
* 评估开始时间
*/
private LocalDateTime startTime;
/**
* 评估完成时间
*/
private LocalDateTime completeTime;
/**
* 评估用时(秒)
*/
private Long duration;
/**
* 总分
*/
private BigDecimal totalScore;
/**
* 各维度得分(JSON格式)
*/
@Lob
private String dimensionScores;
/**
* 评估备注
*/
@Lob
private String remark;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 扩展配置(JSON格式)
*/
@Lob
private String extConfig;
/**
* 评估明细列表
*/
@OneToMany(mappedBy = "evaluationRecord", cascade = CascadeType.ALL)
private List<EvaluationDetail> details;
}
/**
* 评估关系类型枚举
*/
public enum RaterType {
/** 自评 */
SELF,
/** 上级 */
SUPERIOR,
/** 下级 */
SUBORDINATE,
/** 同事 */
PEER,
/** 客户 */
CUSTOMER
}
/**
* 评估状态枚举
*/
public enum EvaluationStatus {
/** 待评估 */
PENDING,
/** 评估中 */
IN_PROGRESS,
/** 已完成 */
COMPLETED,
/** 已跳过 */
SKIPPED
}
评估明细模型(EvaluationDetail):
/**
* 评估明细
* 存储对单个指标的评分
*/
@Data
@Entity
@Table(name = "hrm_evaluation_detail")
public class EvaluationDetail {
/**
* 明细ID
*/
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
/**
* 评估记录ID
*/
private String evaluationRecordId;
/**
* 关联的评估记录
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "evaluation_record_id")
private EvaluationRecord evaluationRecord;
/**
* 指标ID
*/
private String indicatorId;
/**
* 维度ID
*/
private String dimensionId;
/**
* 指标名称
*/
private String indicatorName;
/**
* 维度名称
*/
private String dimensionName;
/**
* 评分
*/
private BigDecimal score;
/**
* 评语
*/
@Lob
private String comment;
/**
* 附件列表(JSON格式)
*/
@Lob
private String attachments;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
2.3 数据模型关系图
┌────────────────────────────────────────────────────────────────┐
│ 评估数据模型关系 │
├────────────────────────────────────────────────────────────────┤
│ │
│ EvaluationProject(评估项目) │
│ │ │
│ ├──► EvaluationTemplate(评估模板) │
│ │ │ │
│ │ ├──► EvaluationDimension(评估维度) │
│ │ │ │ │
│ │ │ └──► EvaluationIndicator(评估指标) │
│ │ │
│ └──► EvaluationRecord(评估记录) │
│ │ │
│ ├──► EvaluationDetail(评估明细) │
│ │ │ │
│ │ └──► Indicator, Dimension │
│ │ │
│ └──► EvaluationResult(评估结果) │
│ │ │
│ ├──► ResultDimension(维度结果) │
│ └──► ImprovementPlan(改进计划) │
│ │
└────────────────────────────────────────────────────────────────┘
三、核心模块实现
3.1 评估任务调度引擎
评估任务调度器:
/**
* 评估任务调度引擎
* 负责创建和分配评估任务
*/
@Component
@Slf4j
public class EvaluationTaskScheduler {
@Autowired
private EvaluationProjectRepository projectRepository;
@Autowired
private EvaluationRecordRepository recordRepository;
@Autowired
private RaterSelectorEngine raterSelectorEngine;
@Autowired
private ApplicationEventPublisher eventPublisher;
/**
* 启动评估项目
*
* @param projectId 项目ID
* @return 启动结果
*/
@Transactional(rollbackFor = Exception.class)
public LaunchResult launchProject(String projectId) {
log.info("启动评估项目: projectId={}", projectId);
try {
// 1. 获取项目信息
EvaluationProject project = projectRepository.findById(projectId)
.orElseThrow(() -> new BusinessException("评估项目不存在"));
// 2. 验证项目状态
if (project.getStatus() != ProjectStatus.PENDING) {
throw new BusinessException("项目状态不允许启动");
}
// 3. 获取被评估者列表
List<Employee> evaluatees = getEvaluatees(project);
log.info("获取被评估者列表: count={}", evaluatees.size());
// 4. 为每个被评估者创建评估任务
List<EvaluationRecord> allRecords = new ArrayList<>();
int successCount = 0;
int failCount = 0;
for (Employee evaluatee : evaluatees) {
try {
// 选择评估者
List<RaterSelectionResult> raterSelections =
raterSelectorEngine.selectRaters(project, evaluatee);
// 验证评估者数量
if (raterSelections.size() < project.getMinRaterCount()) {
log.warn("评估者数量不足: evaluateeId={}, raterCount={}",
evaluatee.getId(), raterSelections.size());
failCount++;
continue;
}
// 创建评估记录
for (RaterSelectionResult selection : raterSelections) {
EvaluationRecord record = createEvaluationRecord(
project, evaluatee, selection
);
allRecords.add(record);
}
successCount++;
} catch (Exception e) {
log.error("创建评估任务失败: evaluateeId={}, error={}",
evaluatee.getId(), e.getMessage());
failCount++;
}
}
// 5. 批量保存评估记录
if (!allRecords.isEmpty()) {
recordRepository.saveAll(allRecords);
}
// 6. 更新项目状态
project.setStatus(ProjectStatus.IN_PROGRESS);
project.setUpdateTime(LocalDateTime.now());
projectRepository.save(project);
// 7. 发布项目启动事件
eventPublisher.publishEvent(new ProjectLaunchedEvent(
projectId, evaluatees.size(), allRecords.size()
));
log.info("评估项目启动成功: projectId={}, successCount={}, failCount={}",
projectId, successCount, failCount);
return LaunchResult.builder()
.projectId(projectId)
.success(successCount)
.failed(failCount)
.totalRecords(allRecords.size())
.build();
} catch (Exception e) {
log.error("启动评估项目失败: projectId={}, error={}", projectId, e.getMessage());
throw new BusinessException("启动评估项目失败", e);
}
}
/**
* 获取被评估者列表
*/
private List<Employee> getEvaluatees(EvaluationProject project) {
ScopeType scopeType = project.getScopeType();
String scopeConfig = project.getScopeConfig();
// 解析范围配置
JSONObject scopeJson = JSON.parseObject(scopeConfig);
switch (scopeType) {
case ALL:
// 获取所有在职员工
return employeeRepository.findAllActiveEmployees();
case DEPARTMENT:
// 获取指定部门的员工
List<String> departmentIds = scopeJson.getJSONArray("departmentIds")
.toJavaList(String.class);
return employeeRepository.findByDepartmentIds(departmentIds);
case POSITION:
// 获取指定岗位的员工
List<String> positionIds = scopeJson.getJSONArray("positionIds")
.toJavaList(String.class);
return employeeRepository.findByPositionIds(positionIds);
case EMPLOYEE:
// 获取指定员工
List<String> employeeIds = scopeJson.getJSONArray("employeeIds")
.toJavaList(String.class);
return employeeRepository.findByEmployeeIds(employeeIds);
default:
throw new BusinessException("不支持的评估范围类型: " + scopeType);
}
}
/**
* 创建评估记录
*/
private EvaluationRecord createEvaluationRecord(
EvaluationProject project,
Employee evaluatee,
RaterSelectionResult selection
) {
EvaluationRecord record = new EvaluationRecord();
record.setProjectId(project.getId());
record.setEvaluateeId(evaluatee.getId());
record.setEvaluateeName(evaluatee.getName());
record.setRaterId(selection.getRater().getId());
record.setRaterName(selection.getRater().getName());
record.setRaterType(selection.getRaterType());
record.setAnonymous(project.getAnonymous() && selection.isAnonymous());
record.setStatus(EvaluationStatus.PENDING);
record.setCreateTime(LocalDateTime.now());
return record;
}
/**
* 发送评估提醒
*
* @param recordId 评估记录ID
*/
public void sendEvaluationReminder(String recordId) {
EvaluationRecord record = recordRepository.findById(recordId)
.orElseThrow(() -> new BusinessException("评估记录不存在"));
// 发送提醒通知
notificationService.sendEvaluationReminder(record);
// 记录提醒日志
reminderLogRepository.save(ReminderLog.builder()
.recordId(recordId)
.raterId(record.getRaterId())
.reminderTime(LocalDateTime.now())
.build());
}
}
/**
* 评估者选择结果
*/
@Data
@Builder
public class RaterSelectionResult {
/**
* 评估者
*/
private Employee rater;
/**
* 评估关系类型
*/
private RaterType raterType;
/**
* 是否匿名
*/
private boolean anonymous;
/**
* 权重
*/
private BigDecimal weight;
}
评估者选择引擎:
/**
* 评估者选择引擎
* 根据配置规则选择合适的评估者
*/
@Component
@Slf4j
public class RaterSelectorEngine {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private OrganizationService organizationService;
/**
* 选择评估者
*
* @param project 评估项目
* @param evaluatee 被评估者
* @return 评估者选择结果列表
*/
public List<RaterSelectionResult> selectRaters(
EvaluationProject project,
Employee evaluatee
) {
List<RaterSelectionResult> results = new ArrayList<>();
// 1. 自评
if (project.getEnableSelfEvaluation()) {
results.add(RaterSelectionResult.builder()
.rater(evaluatee)
.raterType(RaterType.SELF)
.anonymous(false)
.weight(BigDecimal.ZERO) // 自评不参与加权计算
.build());
}
// 2. 上级评估
if (project.getEnableSuperiorEvaluation()) {
List<Employee> superiors = getSuperiors(evaluatee);
for (Employee superior : superiors) {
results.add(RaterSelectionResult.builder()
.rater(superior)
.raterType(RaterType.SUPERIOR)
.anonymous(false) // 上级评估不匿名
.weight(new BigDecimal("0.4")) // 上级权重40%
.build());
}
}
// 3. 下级评估
if (project.getEnableSubordinateEvaluation()) {
List<Employee> subordinates = getSubordinates(evaluatee);
int maxCount = Math.min(subordinates.size(), project.getMaxRaterCount() / 3);
for (int i = 0; i < maxCount; i++) {
results.add(RaterSelectionResult.builder()
.rater(subordinates.get(i))
.raterType(RaterType.SUBORDINATE)
.anonymous(true) // 下级评估匿名
.weight(new BigDecimal("0.2")) // 下级权重20%
.build());
}
}
// 4. 同事评估
if (project.getEnablePeerEvaluation()) {
List<Employee> peers = getPeers(evaluatee);
int maxCount = Math.min(peers.size(), project.getMaxRaterCount() / 3);
// 随机选择同事
Collections.shuffle(peers);
for (int i = 0; i < maxCount; i++) {
results.add(RaterSelectionResult.builder()
.rater(peers.get(i))
.raterType(RaterType.PEER)
.anonymous(true) // 同事评估匿名
.weight(new BigDecimal("0.2")) // 同事权重20%
.build());
}
}
// 5. 客户评估
if (project.getEnableCustomerEvaluation()) {
List<Employee> customers = getCustomers(evaluatee);
for (Employee customer : customers) {
results.add(RaterSelectionResult.builder()
.rater(customer)
.raterType(RaterType.CUSTOMER)
.anonymous(true) // 客户评估匿名
.weight(new BigDecimal("0.2")) // 客户权重20%
.build());
}
}
// 验证评估者数量
if (results.size() < project.getMinRaterCount()) {
log.warn("评估者数量不足: evaluateeId={}, count={}, min={}",
evaluatee.getId(), results.size(), project.getMinRaterCount());
}
return results;
}
/**
* 获取上级
*/
private List<Employee> getSuperiors(Employee employee) {
// 获取直接上级
Employee directSuperior = employeeRepository.findById(employee.getSuperiorId())
.orElse(null);
if (directSuperior == null) {
return Collections.emptyList();
}
List<Employee> superiors = new ArrayList<>();
superiors.add(directSuperior);
// 可选:获取更高级别的上级
if (directSuperior.getSuperiorId() != null) {
Employee grandSuperior = employeeRepository.findById(directSuperior.getSuperiorId())
.orElse(null);
if (grandSuperior != null) {
superiors.add(grandSuperior);
}
}
return superiors;
}
/**
* 获取下级
*/
private List<Employee> getSubordinates(Employee employee) {
return employeeRepository.findBySuperiorId(employee.getId());
}
/**
* 获取同事
*/
private List<Employee> getPeers(Employee employee) {
// 获取同一部门的其他同事
List<Employee> departmentEmployees = employeeRepository
.findByDepartmentId(employee.getDepartmentId());
// 排除自己和上级下级关系
return departmentEmployees.stream()
.filter(e -> !e.getId().equals(employee.getId()))
.filter(e -> !e.getSuperiorId().equals(employee.getId()))
.filter(e -> !employee.getId().equals(e.getSuperiorId()))
.collect(Collectors.toList());
}
/**
* 获取客户
*/
private List<Employee> getCustomers(Employee employee) {
// 根据业务场景获取客户
// 这里简化处理,实际可能需要从客户管理系统获取
return Collections.emptyList();
}
}
3.2 评估数据聚合引擎
评估数据聚合器:
/**
* 评估数据聚合引擎
* 负责聚合多个评估者的评分,计算最终得分
*/
@Component
@Slf4j
public class EvaluationDataAggregator {
@Autowired
private EvaluationRecordRepository recordRepository;
@Autowired
private EvaluationDetailRepository detailRepository;
/**
* 聚合评估数据
*
* @param projectId 项目ID
* @param evaluateeId 被评估者ID
* @return 聚合结果
*/
@Transactional(rollbackFor = Exception.class)
public EvaluationAggregationResult aggregateEvaluation(
String projectId,
String evaluateeId
) {
log.info("开始聚合评估数据: projectId={}, evaluateeId={}", projectId, evaluateeId);
try {
// 1. 获取所有已完成的评估记录
List<EvaluationRecord> records = recordRepository
.findByProjectIdAndEvaluateeIdAndStatus(
projectId, evaluateeId, EvaluationStatus.COMPLETED
);
if (records.isEmpty()) {
log.warn("没有已完成的评估记录: projectId={}, evaluateeId={}",
projectId, evaluateeId);
return EvaluationAggregationResult.empty();
}
// 2. 聚合维度得分
Map<String, DimensionAggregation> dimensionAggregations =
aggregateDimensionScores(records);
// 3. 聚合指标得分
Map<String, IndicatorAggregation> indicatorAggregations =
aggregateIndicatorScores(records);
// 4. 按评估关系类型聚合
Map<RaterType, RaterTypeAggregation> raterTypeAggregations =
aggregateByRaterType(records);
// 5. 计算总分
BigDecimal totalScore = calculateTotalScore(dimensionAggregations);
// 6. 聚合评语
List<String> comments = aggregateComments(records);
// 7. 构建聚合结果
EvaluationAggregationResult result = EvaluationAggregationResult.builder()
.projectId(projectId)
.evaluateeId(evaluateeId)
.totalScore(totalScore)
.dimensionAggregations(new ArrayList<>(dimensionAggregations.values()))
.indicatorAggregations(new ArrayList<>(indicatorAggregations.values()))
.raterTypeAggregations(new ArrayList<>(raterTypeAggregations.values()))
.comments(comments)
.raterCount(records.size())
.aggregateTime(LocalDateTime.now())
.build();
log.info("评估数据聚合完成: projectId={}, evaluateeId={}, totalScore={}",
projectId, evaluateeId, totalScore);
return result;
} catch (Exception e) {
log.error("聚合评估数据失败: projectId={}, evaluateeId={}, error={}",
projectId, evaluateeId, e.getMessage());
throw new AggregationException("聚合评估数据失败", e);
}
}
/**
* 聚合维度得分
*/
private Map<String, DimensionAggregation> aggregateDimensionScores(
List<EvaluationRecord> records
) {
Map<String, DimensionAggregation> aggregations = new LinkedHashMap<>();
// 按维度分组聚合
for (EvaluationRecord record : records) {
String dimensionScores = record.getDimensionScores();
if (StringUtils.isBlank(dimensionScores)) {
continue;
}
// 解析维度得分
JSONObject scoresJson = JSON.parseObject(dimensionScores);
for (String dimensionId : scoresJson.keySet()) {
BigDecimal score = scoresJson.getBigDecimal(dimensionId);
DimensionAggregation aggregation = aggregations.computeIfAbsent(
dimensionId,
k -> DimensionAggregation.builder()
.dimensionId(dimensionId)
.scores(new ArrayList<>())
.build()
);
aggregation.getScores().add(score);
}
}
// 计算统计值
for (DimensionAggregation aggregation : aggregations.values()) {
List<BigDecimal> scores = aggregation.getScores();
if (!scores.isEmpty()) {
// 计算平均分(加权平均)
BigDecimal avgScore = calculateWeightedAverage(scores);
aggregation.setAverageScore(avgScore);
// 计算最高分
BigDecimal maxScore = scores.stream().max(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
aggregation.setMaxScore(maxScore);
// 计算最低分
BigDecimal minScore = scores.stream().min(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
aggregation.setMinScore(minScore);
// 计算标准差
BigDecimal stdDev = calculateStandardDeviation(scores, avgScore);
aggregation.setStandardDeviation(stdDev);
}
}
return aggregations;
}
/**
* 聚合指标得分
*/
private Map<String, IndicatorAggregation> aggregateIndicatorScores(
List<EvaluationRecord> records
) {
Map<String, IndicatorAggregation> aggregations = new LinkedHashMap<>();
// 获取所有评估明细
List<String> recordIds = records.stream()
.map(EvaluationRecord::getId)
.collect(Collectors.toList());
List<EvaluationDetail> details = detailRepository.findByRecordIds(recordIds);
// 按指标分组聚合
Map<String, List<EvaluationDetail>> groupedDetails = details.stream()
.collect(Collectors.groupingBy(EvaluationDetail::getIndicatorId));
for (Map.Entry<String, List<EvaluationDetail>> entry : groupedDetails.entrySet()) {
String indicatorId = entry.getKey();
List<EvaluationDetail> indicatorDetails = entry.getValue();
// 提取评分
List<BigDecimal> scores = indicatorDetails.stream()
.map(EvaluationDetail::getScore)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (scores.isEmpty()) {
continue;
}
IndicatorAggregation aggregation = IndicatorAggregation.builder()
.indicatorId(indicatorId)
.indicatorName(indicatorDetails.get(0).getIndicatorName())
.dimensionId(indicatorDetails.get(0).getDimensionId())
.dimensionName(indicatorDetails.get(0).getDimensionName())
.scores(scores)
.build();
// 计算统计值
BigDecimal avgScore = calculateWeightedAverage(scores);
aggregation.setAverageScore(avgScore);
BigDecimal maxScore = scores.stream().max(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
aggregation.setMaxScore(maxScore);
BigDecimal minScore = scores.stream().min(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
aggregation.setMinScore(minScore);
aggregations.put(indicatorId, aggregation);
}
return aggregations;
}
/**
* 按评估关系类型聚合
*/
private Map<RaterType, RaterTypeAggregation> aggregateByRaterType(
List<EvaluationRecord> records
) {
Map<RaterType, RaterTypeAggregation> aggregations = new LinkedHashMap<>();
// 按评估关系类型分组
Map<RaterType, List<EvaluationRecord>> groupedRecords = records.stream()
.collect(Collectors.groupingBy(EvaluationRecord::getRaterType));
for (Map.Entry<RaterType, List<EvaluationRecord>> entry : groupedRecords.entrySet()) {
RaterType raterType = entry.getKey();
List<EvaluationRecord> typeRecords = entry.getValue();
// 提取总分
List<BigDecimal> scores = typeRecords.stream()
.map(EvaluationRecord::getTotalScore)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (scores.isEmpty()) {
continue;
}
// 计算平均分
BigDecimal avgScore = scores.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(scores.size()), 2, RoundingMode.HALF_UP);
RaterTypeAggregation aggregation = RaterTypeAggregation.builder()
.raterType(raterType)
.raterCount(typeRecords.size())
.averageScore(avgScore)
.scores(scores)
.build();
aggregations.put(raterType, aggregation);
}
return aggregations;
}
/**
* 计算总分
*/
private BigDecimal calculateTotalScore(
Map<String, DimensionAggregation> dimensionAggregations
) {
// 获取维度权重配置
// 这里简化处理,实际应从配置获取
Map<String, BigDecimal> dimensionWeights = getDimensionWeights();
BigDecimal totalScore = BigDecimal.ZERO;
BigDecimal totalWeight = BigDecimal.ZERO;
for (DimensionAggregation aggregation : dimensionAggregations.values()) {
BigDecimal weight = dimensionWeights.getOrDefault(
aggregation.getDimensionId(),
new BigDecimal("1.0")
);
totalScore = totalScore.add(
aggregation.getAverageScore().multiply(weight)
);
totalWeight = totalWeight.add(weight);
}
// 归一化
if (totalWeight.compareTo(BigDecimal.ZERO) > 0) {
totalScore = totalScore.divide(totalWeight, 2, RoundingMode.HALF_UP);
}
return totalScore;
}
/**
* 聚合评语
*/
private List<String> aggregateComments(List<EvaluationRecord> records) {
List<String> comments = new ArrayList<>();
// 获取所有评估明细
List<String> recordIds = records.stream()
.map(EvaluationRecord::getId)
.collect(Collectors.toList());
List<EvaluationDetail> details = detailRepository.findByRecordIds(recordIds);
// 提取评语
for (EvaluationDetail detail : details) {
if (StringUtils.isNotBlank(detail.getComment())) {
comments.add(detail.getComment());
}
}
return comments;
}
/**
* 计算加权平均
*/
private BigDecimal calculateWeightedAverage(List<BigDecimal> values) {
if (values.isEmpty()) {
return BigDecimal.ZERO;
}
BigDecimal sum = values.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
return sum.divide(
new BigDecimal(values.size()),
2,
RoundingMode.HALF_UP
);
}
/**
* 计算标准差
*/
private BigDecimal calculateStandardDeviation(
List<BigDecimal> values,
BigDecimal mean
) {
if (values.isEmpty()) {
return BigDecimal.ZERO;
}
// 计算方差
BigDecimal variance = values.stream()
.map(value -> value.subtract(mean).pow(2))
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(values.size()), 2, RoundingMode.HALF_UP);
// 计算标准差(方差开方)
return new BigDecimal(Math.sqrt(variance.doubleValue()))
.setScale(2, RoundingMode.HALF_UP);
}
/**
* 获取维度权重
*/
private Map<String, BigDecimal> getDimensionWeights() {
// 从配置或数据库获取
// 这里返回默认权重
Map<String, BigDecimal> weights = new HashMap<>();
weights.put("WORK_PERFORMANCE", new BigDecimal("0.4")); // 工作业绩40%
weights.put("WORK_ABILITY", new BigDecimal("0.3")); // 工作能力30%
weights.put("WORK_ATTITUDE", new BigDecimal("0.2")); // 工作态度20%
weights.put("TEAM_COLLABORATION", new BigDecimal("0.1")); // 团队协作10%
return weights;
}
}
/**
* 评估聚合结果
*/
@Data
@Builder
public class EvaluationAggregationResult {
/**
* 项目ID
*/
private String projectId;
/**
* 被评估者ID
*/
private String evaluateeId;
/**
* 总分
*/
private BigDecimal totalScore;
/**
* 维度聚合结果列表
*/
private List<DimensionAggregation> dimensionAggregations;
/**
* 指标聚合结果列表
*/
private List<IndicatorAggregation> indicatorAggregations;
/**
* 评估关系类型聚合结果列表
*/
private List<RaterTypeAggregation> raterTypeAggregations;
/**
* 评语列表
*/
private List<String> comments;
/**
* 评估者数量
*/
private Integer raterCount;
/**
* 聚合时间
*/
private LocalDateTime aggregateTime;
public static EvaluationAggregationResult empty() {
return EvaluationAggregationResult.builder()
.totalScore(BigDecimal.ZERO)
.dimensionAggregations(Collections.emptyList())
.indicatorAggregations(Collections.emptyList())
.raterTypeAggregations(Collections.emptyList())
.comments(Collections.emptyList())
.raterCount(0)
.build();
}
}
/**
* 维度聚合结果
*/
@Data
@Builder
public class DimensionAggregation {
/**
* 维度ID
*/
private String dimensionId;
/**
* 维度名称
*/
private String dimensionName;
/**
* 评分列表
*/
private List<BigDecimal> scores;
/**
* 平均分
*/
private BigDecimal averageScore;
/**
* 最高分
*/
private BigDecimal maxScore;
/**
* 最低分
*/
private BigDecimal minScore;
/**
* 标准差
*/
private BigDecimal standardDeviation;
}
/**
* 指标聚合结果
*/
@Data
@Builder
public class IndicatorAggregation {
/**
* 指标ID
*/
private String indicatorId;
/**
* 指标名称
*/
private String indicatorName;
/**
* 维度ID
*/
private String dimensionId;
/**
* 维度名称
*/
private String dimensionName;
/**
* 评分列表
*/
private List<BigDecimal> scores;
/**
* 平均分
*/
private BigDecimal averageScore;
/**
* 最高分
*/
private BigDecimal maxScore;
/**
* 最低分
*/
private BigDecimal minScore;
}
/**
* 评估关系类型聚合结果
*/
@Data
@Builder
public class RaterTypeAggregation {
/**
* 评估关系类型
*/
private RaterType raterType;
/**
* 评估者数量
*/
private Integer raterCount;
/**
* 平均分
*/
private BigDecimal averageScore;
/**
* 评分列表
*/
private List<BigDecimal> scores;
}
3.3 异常值检测与处理
异常值检测引擎:
/**
* 评估异常值检测引擎
* 检测和处理异常的评估数据
*/
@Component
@Slf4j
public class EvaluationOutlierDetector {
/**
* 检测异常值
*
* @param values 数值列表
* @return 异常值检测结果
*/
public OutlierDetectionResult detectOutliers(List<BigDecimal> values) {
if (values == null || values.isEmpty()) {
return OutlierDetectionResult.empty();
}
OutlierDetectionResult result = new OutlierDetectionResult();
result.setTotalCount(values.size());
// 1. 使用IQR方法检测异常值
List<BigDecimal> iqrOutliers = detectByIQR(values);
result.setIqrOutliers(iqrOutliers);
// 2. 使用Z-Score方法检测异常值
List<BigDecimal> zScoreOutliers = detectByZScore(values);
result.setZScoreOutliers(zScoreOutliers);
// 3. 合并异常值
Set<BigDecimal> allOutliers = new HashSet<>();
allOutliers.addAll(iqrOutliers);
allOutliers.addAll(zScoreOutliers);
result.setAllOutliers(new ArrayList<>(allOutliers));
// 4. 计算异常值比例
double outlierRatio = (double) allOutliers.size() / values.size();
result.setOutlierRatio(outlierRatio);
return result;
}
/**
* 使用IQR(四分位距)方法检测异常值
*
* @param values 数值列表
* @return 异常值列表
*/
private List<BigDecimal> detectByIQR(List<BigDecimal> values) {
List<BigDecimal> sortedValues = new ArrayList<>(values);
Collections.sort(sortedValues);
// 计算四分位数
int n = sortedValues.size();
BigDecimal q1 = sortedValues.get(n / 4);
BigDecimal q3 = sortedValues.get(3 * n / 4);
// 计算IQR
BigDecimal iqr = q3.subtract(q1);
// 计算上下限
BigDecimal lowerBound = q1.subtract(iqr.multiply(new BigDecimal("1.5")));
BigDecimal upperBound = q3.add(iqr.multiply(new BigDecimal("1.5")));
// 找出异常值
List<BigDecimal> outliers = sortedValues.stream()
.filter(value -> value.compareTo(lowerBound) < 0 ||
value.compareTo(upperBound) > 0)
.collect(Collectors.toList());
log.debug("IQR异常值检测: count={}, outliers={}", n, outliers.size());
return outliers;
}
/**
* 使用Z-Score方法检测异常值
*
* @param values 数值列表
* @return 异常值列表
*/
private List<BigDecimal> detectByZScore(List<BigDecimal> values) {
// 计算平均值
BigDecimal mean = values.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(values.size()), 2, RoundingMode.HALF_UP);
// 计算标准差
BigDecimal variance = values.stream()
.map(value -> value.subtract(mean).pow(2))
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(values.size()), 2, RoundingMode.HALF_UP);
BigDecimal stdDev = new BigDecimal(Math.sqrt(variance.doubleValue()));
// 计算Z-Score阈值(通常使用3作为阈值)
BigDecimal threshold = new BigDecimal("3.0");
// 找出异常值
List<BigDecimal> outliers = new ArrayList<>();
for (BigDecimal value : values) {
BigDecimal zScore = value.subtract(mean)
.divide(stdDev, 2, RoundingMode.HALF_UP);
if (zScore.abs().compareTo(threshold) > 0) {
outliers.add(value);
}
}
log.debug("Z-Score异常值检测: count={}, outliers={}", values.size(), outliers.size());
return outliers;
}
/**
* 处理异常值
*
* @param values 原始数值列表
* @param strategy 处理策略
* @return 处理后的数值列表
*/
public List<BigDecimal> handleOutliers(
List<BigDecimal> values,
OutlierHandlingStrategy strategy
) {
OutlierDetectionResult detectionResult = detectOutliers(values);
List<BigDecimal> outliers = detectionResult.getAllOutliers();
if (outliers.isEmpty()) {
return values;
}
switch (strategy) {
case REMOVE:
// 移除异常值
return values.stream()
.filter(value -> !outliers.contains(value))
.collect(Collectors.toList());
case REPLACE_WITH_MEAN:
// 用平均值替换异常值
BigDecimal mean = values.stream()
.filter(value -> !outliers.contains(value))
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(
new BigDecimal(values.size() - outliers.size()),
2,
RoundingMode.HALF_UP
);
return values.stream()
.map(value -> outliers.contains(value) ? mean : value)
.collect(Collectors.toList());
case REPLACE_WITH_MEDIAN:
// 用中位数替换异常值
List<BigDecimal> normalValues = values.stream()
.filter(value -> !outliers.contains(value))
.sorted()
.collect(Collectors.toList());
BigDecimal median = normalValues.get(normalValues.size() / 2);
return values.stream()
.map(value -> outliers.contains(value) ? median : value)
.collect(Collectors.toList());
case CAP:
// 用上下限替换异常值
List<BigDecimal> sortedValues = new ArrayList<>(values);
Collections.sort(sortedValues);
BigDecimal lowerBound = sortedValues.get(1); // 使用次小值作为下限
BigDecimal upperBound = sortedValues.get(sortedValues.size() - 2); // 使用次大值作为上限
return values.stream()
.map(value -> {
if (value.compareTo(lowerBound) < 0) {
return lowerBound;
} else if (value.compareTo(upperBound) > 0) {
return upperBound;
} else {
return value;
}
})
.collect(Collectors.toList());
default:
return values;
}
}
}
/**
* 异常值检测结果
*/
@Data
public class OutlierDetectionResult {
/**
* 总数量
*/
private Integer totalCount;
/**
* IQR方法检测出的异常值
*/
private List<BigDecimal> iqrOutliers;
/**
* Z-Score方法检测出的异常值
*/
private List<BigDecimal> zScoreOutliers;
/**
* 所有异常值
*/
private List<BigDecimal> allOutliers;
/**
* 异常值比例
*/
private Double outlierRatio;
public static OutlierDetectionResult empty() {
OutlierDetectionResult result = new OutlierDetectionResult();
result.setTotalCount(0);
result.setIqrOutliers(Collections.emptyList());
result.setZScoreOutliers(Collections.emptyList());
result.setAllOutliers(Collections.emptyList());
result.setOutlierRatio(0.0);
return result;
}
}
/**
* 异常值处理策略枚举
*/
public enum OutlierHandlingStrategy {
/** 移除异常值 */
REMOVE,
/** 用平均值替换异常值 */
REPLACE_WITH_MEAN,
/** 用中位数替换异常值 */
REPLACE_WITH_MEDIAN,
/** 用上下限替换异常值 */
CAP,
/** 不处理 */
NONE
}
3.4 评估结果排名与分析
评估结果排名引擎:
/**
* 评估结果排名引擎
* 对评估结果进行排名和对比分析
*/
@Component
@Slf4j
public class EvaluationRankingEngine {
@Autowired
private EvaluationResultRepository resultRepository;
/**
* 计算排名
*
* @param projectId 项目ID
* @return 排名结果
*/
public RankingResult calculateRanking(String projectId) {
log.info("开始计算评估排名: projectId={}", projectId);
// 1. 获取所有评估结果
List<EvaluationResult> results = resultRepository.findByProjectId(projectId);
if (results.isEmpty()) {
return RankingResult.empty();
}
// 2. 按总分排序
List<EvaluationResult> sortedResults = results.stream()
.sorted(Comparator.comparing(EvaluationResult::getTotalScore).reversed())
.collect(Collectors.toList());
// 3. 计算排名
List<RankingItem> rankingItems = new ArrayList<>();
int rank = 1;
for (int i = 0; i < sortedResults.size(); i++) {
EvaluationResult result = sortedResults.get(i);
// 处理并列排名
if (i > 0) {
EvaluationResult prevResult = sortedResults.get(i - 1);
if (result.getTotalScore().compareTo(prevResult.getTotalScore()) == 0) {
// 分数相同,排名相同
// rank保持不变
} else {
// 分数不同,排名更新
rank = i + 1;
}
}
RankingItem item = RankingItem.builder()
.rank(rank)
.evaluateeId(result.getEvaluateeId())
.evaluateeName(result.getEvaluateeName())
.departmentId(result.getDepartmentId())
.departmentName(result.getDepartmentName())
.positionId(result.getPositionId())
.positionName(result.getPositionName())
.totalScore(result.getTotalScore())
.dimensionScores(result.getDimensionScores())
.build();
rankingItems.add(item);
}
// 4. 计算百分位排名
for (RankingItem item : rankingItems) {
int percentile = calculatePercentile(rank, sortedResults.size());
item.setPercentile(percentile);
}
// 5. 构建排名结果
RankingResult rankingResult = RankingResult.builder()
.projectId(projectId)
.rankingItems(rankingItems)
.totalCount(sortedResults.size())
.averageScore(calculateAverageScore(sortedResults))
.medianScore(calculateMedianScore(sortedResults))
.maxScore(sortedResults.get(0).getTotalScore())
.minScore(sortedResults.get(sortedResults.size() - 1).getTotalScore())
.build();
log.info("评估排名计算完成: projectId={}, count={}", projectId, sortedResults.size());
return rankingResult;
}
/**
* 计算百分位
*/
private int calculatePercentile(int rank, int totalCount) {
if (totalCount == 0) {
return 0;
}
// 百分位 = (1 - rank/totalCount) * 100
double percentile = (1.0 - (double) rank / totalCount) * 100;
return (int) Math.round(percentile);
}
/**
* 计算平均分
*/
private BigDecimal calculateAverageScore(List<EvaluationResult> results) {
BigDecimal sum = results.stream()
.map(EvaluationResult::getTotalScore)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return sum.divide(
new BigDecimal(results.size()),
2,
RoundingMode.HALF_UP
);
}
/**
* 计算中位数
*/
private BigDecimal calculateMedianScore(List<EvaluationResult> results) {
int size = results.size();
if (size == 0) {
return BigDecimal.ZERO;
}
if (size % 2 == 0) {
// 偶数个,取中间两个数的平均值
BigDecimal score1 = results.get(size / 2 - 1).getTotalScore();
BigDecimal score2 = results.get(size / 2).getTotalScore();
return score1.add(score2)
.divide(new BigDecimal("2"), 2, RoundingMode.HALF_UP);
} else {
// 奇数个,取中间数
return results.get(size / 2).getTotalScore();
}
}
/**
* 部门排名对比
*
* @param projectId 项目ID
* @return 部门排名对比结果
*/
public DepartmentRankingComparison compareDepartmentRanking(String projectId) {
log.info("开始部门排名对比: projectId={}", projectId);
// 1. 获取所有评估结果
List<EvaluationResult> results = resultRepository.findByProjectId(projectId);
// 2. 按部门分组
Map<String, List<EvaluationResult>> departmentGroups = results.stream()
.collect(Collectors.groupingBy(EvaluationResult::getDepartmentId));
// 3. 计算各部门的平均分
List<DepartmentRankingItem> departmentRankings = new ArrayList<>();
for (Map.Entry<String, List<EvaluationResult>> entry : departmentGroups.entrySet()) {
String departmentId = entry.getKey();
List<EvaluationResult> departmentResults = entry.getValue();
BigDecimal avgScore = calculateAverageScore(departmentResults);
BigDecimal maxScore = departmentResults.stream()
.map(EvaluationResult::getTotalScore)
.max(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
BigDecimal minScore = departmentResults.stream()
.map(EvaluationResult::getTotalScore)
.min(BigDecimal::compareTo)
.orElse(BigDecimal.ZERO);
DepartmentRankingItem item = DepartmentRankingItem.builder()
.departmentId(departmentId)
.departmentName(departmentResults.get(0).getDepartmentName())
.averageScore(avgScore)
.maxScore(maxScore)
.minScore(minScore)
.participantCount(departmentResults.size())
.build();
departmentRankings.add(item);
}
// 4. 按平均分排序
departmentRankings.sort(
Comparator.comparing(DepartmentRankingItem::getAverageScore).reversed()
);
// 5. 构建对比结果
return DepartmentRankingComparison.builder()
.projectId(projectId)
.departmentRankings(departmentRankings)
.build();
}
}
/**
* 排名结果
*/
@Data
@Builder
public class RankingResult {
/**
* 项目ID
*/
private String projectId;
/**
* 排名列表
*/
private List<RankingItem> rankingItems;
/**
* 总人数
*/
private Integer totalCount;
/**
* 平均分
*/
private BigDecimal averageScore;
/**
* 中位数
*/
private BigDecimal medianScore;
/**
* 最高分
*/
private BigDecimal maxScore;
/**
* 最低分
*/
private BigDecimal minScore;
public static RankingResult empty() {
return RankingResult.builder()
.rankingItems(Collections.emptyList())
.totalCount(0)
.averageScore(BigDecimal.ZERO)
.medianScore(BigDecimal.ZERO)
.maxScore(BigDecimal.ZERO)
.minScore(BigDecimal.ZERO)
.build();
}
}
/**
* 排名项
*/
@Data
@Builder
public class RankingItem {
/**
* 排名
*/
private Integer rank;
/**
* 百分位
*/
private Integer percentile;
/**
* 被评估者ID
*/
private String evaluateeId;
/**
* 被评估者姓名
*/
private String evaluateeName;
/**
* 部门ID
*/
private String departmentId;
/**
* 部门名称
*/
private String departmentName;
/**
* 岗位ID
*/
private String positionId;
/**
* 岗位名称
*/
private String positionName;
/**
* 总分
*/
private BigDecimal totalScore;
/**
* 各维度得分(JSON格式)
*/
private String dimensionScores;
}
/**
* 部门排名对比结果
*/
@Data
@Builder
public class DepartmentRankingComparison {
/**
* 项目ID
*/
private String projectId;
/**
* 部门排名列表
*/
private List<DepartmentRankingItem> departmentRankings;
}
/**
* 部门排名项
*/
@Data
@Builder
public class DepartmentRankingItem {
/**
* 部门ID
*/
private String departmentId;
/**
* 部门名称
*/
private String departmentName;
/**
* 平均分
*/
private BigDecimal averageScore;
/**
* 最高分
*/
private BigDecimal maxScore;
/**
* 最低分
*/
private BigDecimal minScore;
/**
* 参与人数
*/
private Integer participantCount;
}
3.5 绩效改进计划生成
改进计划生成引擎:
/**
* 绩效改进计划生成引擎
* 根据评估结果自动生成改进建议
*/
@Component
@Slf4j
public class ImprovementPlanGenerator {
@Autowired
private EvaluationResultRepository resultRepository;
@Autowired
private ImprovementAdviceRepository adviceRepository;
/**
* 生成改进计划
*
* @param projectId 项目ID
* @param evaluateeId 被评估者ID
* @return 改进计划
*/
@Transactional(rollbackFor = Exception.class)
public ImprovementPlan generateImprovementPlan(
String projectId,
String evaluateeId
) {
log.info("生成改进计划: projectId={}, evaluateeId={}", projectId, evaluateeId);
// 1. 获取评估结果
EvaluationResult result = resultRepository.findByProjectIdAndEvaluateeId(
projectId, evaluateeId
).orElseThrow(() -> new BusinessException("评估结果不存在"));
// 2. 分析优势和不足
List<String> strengths = analyzeStrengths(result);
List<String> weaknesses = analyzeWeaknesses(result);
// 3. 生成改进建议
List<ImprovementAdvice> advices = generateAdvices(result, weaknesses);
// 4. 构建改进计划
ImprovementPlan plan = ImprovementPlan.builder()
.projectId(projectId)
.evaluateeId(evaluateeId)
.evaluateeName(result.getEvaluateeName())
.totalScore(result.getTotalScore())
.strengths(strengths)
.weaknesses(weaknesses)
.advices(advices)
.planStatus(PlanStatus.DRAFT)
.createTime(LocalDateTime.now())
.build();
// 5. 保存改进计划
improvementPlanRepository.save(plan);
log.info("改进计划生成完成: projectId={}, evaluateeId={}, adviceCount={}",
projectId, evaluateeId, advices.size());
return plan;
}
/**
* 分析优势
*/
private List<String> analyzeStrengths(EvaluationResult result) {
List<String> strengths = new ArrayList<>();
// 解析维度得分
List<DimensionScore> dimensionScores = parseDimensionScores(
result.getDimensionScores()
);
// 找出得分较高的维度(高于平均分)
BigDecimal totalScore = result.getTotalScore();
for (DimensionScore dimensionScore : dimensionScores) {
if (dimensionScore.getScore().compareTo(totalScore) > 0) {
// 根据维度ID生成优势描述
String strength = generateStrengthDescription(dimensionScore);
strengths.add(strength);
}
}
return strengths;
}
/**
* 分析不足
*/
private List<String> analyzeWeaknesses(EvaluationResult result) {
List<String> weaknesses = new ArrayList<>();
// 解析维度得分
List<DimensionScore> dimensionScores = parseDimensionScores(
result.getDimensionScores()
);
// 找出得分较低的维度(低于平均分)
BigDecimal totalScore = result.getTotalScore();
for (DimensionScore dimensionScore : dimensionScores) {
if (dimensionScore.getScore().compareTo(totalScore) < 0) {
// 根据维度ID生成不足描述
String weakness = generateWeaknessDescription(dimensionScore);
weaknesses.add(weakness);
}
}
return weaknesses;
}
/**
* 生成改进建议
*/
private List<ImprovementAdvice> generateAdvices(
EvaluationResult result,
List<String> weaknesses
) {
List<ImprovementAdvice> advices = new ArrayList<>();
// 为每个不足生成改进建议
for (String weakness : weaknesses) {
// 根据不足类型获取改进建议模板
List<AdviceTemplate> templates = adviceRepository
.findByWeaknessType(weakness);
if (templates.isEmpty()) {
// 使用通用建议
templates = adviceRepository.findGenericAdvices();
}
// 选择合适的建议模板
for (AdviceTemplate template : templates) {
ImprovementAdvice advice = ImprovementAdvice.builder()
.adviceType(template.getAdviceType())
.title(template.getTitle())
.description(template.getDescription())
.actionSteps(parseActionSteps(template.getActionSteps()))
.expectedOutcome(template.getExpectedOutcome())
.priority(calculateAdvicePriority(result, weakness))
.estimatedDuration(template.getEstimatedDuration())
.resources(template.getResources())
.build();
advices.add(advice);
}
}
return advices;
}
/**
* 解析维度得分
*/
private List<DimensionScore> parseDimensionScores(String dimensionScoresJson) {
if (StringUtils.isBlank(dimensionScoresJson)) {
return Collections.emptyList();
}
JSONObject jsonObject = JSON.parseObject(dimensionScoresJson);
List<DimensionScore> dimensionScores = new ArrayList<>();
for (String key : jsonObject.keySet()) {
JSONObject dimensionObj = jsonObject.getJSONObject(key);
DimensionScore dimensionScore = DimensionScore.builder()
.dimensionId(key)
.dimensionName(dimensionObj.getString("name"))
.score(dimensionObj.getBigDecimal("score"))
.build();
dimensionScores.add(dimensionScore);
}
return dimensionScores;
}
/**
* 生成优势描述
*/
private String generateStrengthDescription(DimensionScore dimensionScore) {
String dimensionId = dimensionScore.getDimensionId();
BigDecimal score = dimensionScore.getScore();
switch (dimensionId) {
case "WORK_PERFORMANCE":
return String.format("工作业绩表现优秀,得分为%.2f分,展现出较强的业务能力", score);
case "WORK_ABILITY":
return String.format("工作能力突出,得分为%.2f分,具备良好的专业素养", score);
case "WORK_ATTITUDE":
return String.format("工作态度积极,得分为%.2f分,表现出高度的责任心", score);
case "TEAM_COLLABORATION":
return String.format("团队协作能力强,得分为%.2f分,善于与同事合作", score);
default:
return String.format("%s表现优秀,得分为%.2f分",
dimensionScore.getDimensionName(), score);
}
}
/**
* 生成不足描述
*/
private String generateWeaknessDescription(DimensionScore dimensionScore) {
String dimensionId = dimensionScore.getDimensionId();
BigDecimal score = dimensionScore.getScore();
switch (dimensionId) {
case "WORK_PERFORMANCE":
return String.format("工作业绩有待提升,当前得分为%.2f分,建议加强业务能力", score);
case "WORK_ABILITY":
return String.format("工作能力需要改进,当前得分为%.2f分,建议参加相关培训", score);
case "WORK_ATTITUDE":
return String.format("工作态度需要调整,当前得分为%.2f分,建议增强责任心", score);
case "TEAM_COLLABORATION":
return String.format("团队协作需要加强,当前得分为%.2f分,建议多与同事沟通", score);
default:
return String.format("%s需要改进,当前得分为%.2f分",
dimensionScore.getDimensionName(), score);
}
}
/**
* 解析行动步骤
*/
private List<String> parseActionSteps(String actionStepsJson) {
if (StringUtils.isBlank(actionStepsJson)) {
return Collections.emptyList();
}
return JSON.parseArray(actionStepsJson, String.class);
}
/**
* 计算建议优先级
*/
private Priority calculateAdvicePriority(EvaluationResult result, String weakness) {
// 根据得分差异决定优先级
BigDecimal scoreGap = result.getTotalScore().subtract(new BigDecimal("60"));
if (scoreGap.compareTo(new BigDecimal("20")) < 0) {
return Priority.HIGH;
} else if (scoreGap.compareTo(new BigDecimal("10")) < 0) {
return Priority.MEDIUM;
} else {
return Priority.LOW;
}
}
}
/**
* 改进计划
*/
@Data
@Builder
@Entity
@Table(name = "hrm_improvement_plan")
public class ImprovementPlan {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
/**
* 项目ID
*/
private String projectId;
/**
* 被评估者ID
*/
private String evaluateeId;
/**
* 被评估者姓名
*/
private String evaluateeName;
/**
* 总分
*/
private BigDecimal totalScore;
/**
* 优势列表
*/
@Lob
private String strengths;
/**
* 不足列表
*/
@Lob
private String weaknesses;
/**
* 改进建议列表
*/
@OneToMany(mappedBy = "improvementPlan", cascade = CascadeType.ALL)
private List<ImprovementAdvice> advices;
/**
* 计划状态
*/
@Enumerated(EnumType.STRING)
private PlanStatus planStatus;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
/**
* 计划状态枚举
*/
public enum PlanStatus {
/** 草稿 */
DRAFT,
/** 进行中 */
IN_PROGRESS,
/** 已完成 */
COMPLETED,
/** 已归档 */
ARCHIVED
}
/**
* 改进建议
*/
@Data
@Builder
@Entity
@Table(name = "hrm_improvement_advice")
public class ImprovementAdvice {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
/**
* 改进计划ID
*/
private String improvementPlanId;
/**
* 关联的改进计划
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "improvement_plan_id")
private ImprovementPlan improvementPlan;
/**
* 建议类型:TRAINING(培训), COACHING(辅导), PROJECT(项目),
* READING(阅读), PRACTICE(练习)
*/
@Enumerated(EnumType.STRING)
private AdviceType adviceType;
/**
* 标题
*/
private String title;
/**
* 描述
*/
@Lob
private String description;
/**
* 行动步骤(JSON格式)
*/
@Lob
private String actionSteps;
/**
* 预期成果
*/
private String expectedOutcome;
/**
* 优先级
*/
@Enumerated(EnumType.STRING)
private Priority priority;
/**
* 预计时长(天)
*/
private Integer estimatedDuration;
/**
* 资源(JSON格式)
*/
@Lob
private String resources;
/**
* 完成状态
*/
@Enumerated(EnumType.STRING)
private CompletionStatus completionStatus;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
/**
* 建议类型枚举
*/
public enum AdviceType {
/** 培训 */
TRAINING,
/** 辅导 */
COACHING,
/** 项目 */
PROJECT,
/** 阅读 */
READING,
/** 练习 */
PRACTICE
}
/**
* 优先级枚举
*/
public enum Priority {
/** 高 */
HIGH,
/** 中 */
MEDIUM,
/** 低 */
LOW
}
/**
* 完成状态枚举
*/
public enum CompletionStatus {
/** 待开始 */
PENDING,
/** 进行中 */
IN_PROGRESS,
/** 已完成 */
COMPLETED,
/** 已跳过 */
SKIPPED
}
/**
* 维度得分
*/
@Data
@Builder
public class DimensionScore {
private String dimensionId;
private String dimensionName;
private BigDecimal score;
}
四、高级特性实现
4.1 匿名评估实现
匿名评估处理:
/**
* 匿名评估处理器
* 确保评估者的匿名性
*/
@Component
@Slf4j
public class AnonymousEvaluationHandler {
@Autowired
private EvaluationRecordRepository recordRepository;
/**
* 脱敏处理评估记录
*
* @param record 评估记录
* @return 脱敏后的评估记录
*/
public EvaluationRecord maskEvaluationRecord(EvaluationRecord record) {
// 如果不是匿名评估,直接返回
if (!record.getAnonymous()) {
return record;
}
// 创建脱敏副本
EvaluationRecord maskedRecord = new EvaluationRecord();
BeanUtils.copyProperties(record, maskedRecord);
// 脱敏评估者信息
maskedRecord.setRaterId(generateAnonymousId(record.getRaterId()));
maskedRecord.setRaterName("匿名评估者");
return maskedRecord;
}
/**
* 批量脱敏处理评估记录
*
* @param records 评估记录列表
* @return 脱敏后的评估记录列表
*/
public List<EvaluationRecord> maskEvaluationRecords(List<EvaluationRecord> records) {
return records.stream()
.map(this::maskEvaluationRecord)
.collect(Collectors.toList());
}
/**
* 生成匿名ID
*
* @param originalId 原始ID
* @return 匿名ID
*/
private String generateAnonymousId(String originalId) {
// 使用哈希算法生成匿名ID
return DigestUtils.md5Hex(originalId + "_anonymous");
}
/**
* 验证匿名权限
*
* @param userId 用户ID
* @param recordId 评估记录ID
* @return 是否有权限查看
*/
public boolean validateAnonymousPermission(String userId, String recordId) {
EvaluationRecord record = recordRepository.findById(recordId)
.orElseThrow(() -> new BusinessException("评估记录不存在"));
// 如果不是匿名评估,所有人都可以查看
if (!record.getAnonymous()) {
return true;
}
// 匿名评估只有以下人员可以查看真实身份:
// 1. 被评估者本人
// 2. 系统管理员
// 3. HR管理员
return userId.equals(record.getEvaluateeId()) ||
hasAdminRole(userId) ||
hasHrAdminRole(userId);
}
/**
* 判断是否为管理员
*/
private boolean hasAdminRole(String userId) {
User user = userService.getById(userId);
return user != null && user.getRoles().contains("ADMIN");
}
/**
* 判断是否为HR管理员
*/
private boolean hasHrAdminRole(String userId) {
User user = userService.getById(userId);
return user != null && user.getRoles().contains("HR_ADMIN");
}
}
4.2 评估进度监控
评估进度监控器:
/**
* 评估进度监控器
* 实时监控评估进度和异常情况
*/
@Component
@Slf4j
public class EvaluationProgressMonitor {
@Autowired
private EvaluationRecordRepository recordRepository;
@Autowired
private NotificationService notificationService;
/**
* 获取项目评估进度
*
* @param projectId 项目ID
* @return 评估进度
*/
public ProjectProgress getProjectProgress(String projectId) {
// 获取所有评估记录
List<EvaluationRecord> allRecords = recordRepository.findByProjectId(projectId);
if (allRecords.isEmpty()) {
return ProjectProgress.empty();
}
// 统计各状态的评估记录数量
Map<EvaluationStatus, Long> statusCounts = allRecords.stream()
.collect(Collectors.groupingBy(
EvaluationRecord::getStatus,
Collectors.counting()
));
// 获取被评估者数量
long evaluateeCount = allRecords.stream()
.map(EvaluationRecord::getEvaluateeId)
.distinct()
.count();
// 计算完成率
long completedCount = statusCounts.getOrDefault(EvaluationStatus.COMPLETED, 0L);
double completionRate = (double) completedCount / allRecords.size();
// 构建进度信息
return ProjectProgress.builder()
.projectId(projectId)
.totalRecords(allRecords.size())
.evaluateeCount((int) evaluateeCount)
.pendingCount(statusCounts.getOrDefault(EvaluationStatus.PENDING, 0L).intValue())
.inProgressCount(statusCounts.getOrDefault(EvaluationStatus.IN_PROGRESS, 0L).intValue())
.completedCount(completedCount)
.skippedCount(statusCounts.getOrDefault(EvaluationStatus.SKIPPED, 0L).intValue())
.completionRate(completionRate)
.build();
}
/**
* 获取被评估者的评估进度
*
* @param projectId 项目ID
* @param evaluateeId 被评估者ID
* @return 评估进度
*/
public EvaluateeProgress getEvaluateeProgress(String projectId, String evaluateeId) {
// 获取该被评估者的所有评估记录
List<EvaluationRecord> records = recordRepository
.findByProjectIdAndEvaluateeId(projectId, evaluateeId);
if (records.isEmpty()) {
return EvaluateeProgress.empty();
}
// 统计各状态的评估记录数量
Map<EvaluationStatus, Long> statusCounts = records.stream()
.collect(Collectors.groupingBy(
EvaluationRecord::getStatus,
Collectors.counting()
));
// 计算完成率
long completedCount = statusCounts.getOrDefault(EvaluationStatus.COMPLETED, 0L);
double completionRate = (double) completedCount / records.size();
// 构建进度信息
return EvaluateeProgress.builder()
.projectId(projectId)
.evaluateeId(evaluateeId)
.totalRaters(records.size())
.completedRaters(completedCount)
.completionRate(completionRate)
.statusCounts(statusCounts)
.build();
}
/**
* 监控异常情况
*
* @param projectId 项目ID
* @return 异常情况列表
*/
public List<ProgressAlert> checkProgressAlerts(String projectId) {
List<ProgressAlert> alerts = new ArrayList<>();
// 获取项目进度
ProjectProgress progress = getProjectProgress(projectId);
// 1. 检查是否有评估即将过期
List<EvaluationRecord> expiringRecords = findExpiringRecords(projectId);
if (!expiringRecords.isEmpty()) {
alerts.add(ProgressAlert.builder()
.alertType(AlertType.EXPIRING_SOON)
.severity(AlertSeverity.MEDIUM)
.message(String.format("有%d条评估即将过期", expiringRecords.size()))
.count(expiringRecords.size())
.build());
}
// 2. 检查完成率是否过低
if (progress.getCompletionRate() < 0.5) {
alerts.add(ProgressAlert.builder()
.alertType(AlertType.LOW_COMPLETION_RATE)
.severity(AlertSeverity.HIGH)
.message(String.format("评估完成率仅为%.2f%%,低于50%%",
progress.getCompletionRate() * 100))
.build());
}
// 3. 检查是否有被评估者缺少足够的评估者
List<String> insufficientRaters = findEvaluateesWithInsufficientRaters(projectId);
if (!insufficientRaters.isEmpty()) {
alerts.add(ProgressAlert.builder()
.alertType(AlertType.INSUFFICIENT_RATERS)
.severity(AlertSeverity.HIGH)
.message(String.format("有%d名被评估者缺少足够的评估者",
insufficientRaters.size()))
.count(insufficientRaters.size())
.build());
}
// 4. 检查是否有异常评分
List<String> outlierEvaluatees = findEvaluateesWithOutlierScores(projectId);
if (!outlierEvaluatees.isEmpty()) {
alerts.add(ProgressAlert.builder()
.alertType(AlertType.OUTLIER_SCORES)
.severity(AlertSeverity.LOW)
.message(String.format("有%d名被评估者的评分存在异常",
outlierEvaluatees.size()))
.count(outlierEvaluatees.size())
.build());
}
return alerts;
}
/**
* 查找即将过期的评估记录
*/
private List<EvaluationRecord> findExpiringRecords(String projectId) {
LocalDateTime threshold = LocalDateTime.now().plusDays(3);
return recordRepository.findByProjectIdAndStatusAndEndTimeBefore(
projectId,
EvaluationStatus.PENDING,
threshold
);
}
/**
* 查找评估者数量不足的被评估者
*/
private List<String> findEvaluateesWithInsufficientRaters(String projectId) {
// 获取项目配置
EvaluationProject project = projectRepository.findById(projectId).orElse(null);
if (project == null) {
return Collections.emptyList();
}
int minRaterCount = project.getMinRaterCount();
// 按被评估者分组
Map<String, List<EvaluationRecord>> groupedRecords = recordRepository
.findByProjectId(projectId).stream()
.collect(Collectors.groupingBy(EvaluationRecord::getEvaluateeId));
// 找出评估者数量不足的被评估者
List<String> insufficientRaters = new ArrayList<>();
for (Map.Entry<String, List<EvaluationRecord>> entry : groupedRecords.entrySet()) {
if (entry.getValue().size() < minRaterCount) {
insufficientRaters.add(entry.getKey());
}
}
return insufficientRaters;
}
/**
* 查找评分存在异常的被评估者
*/
private List<String> findEvaluateesWithOutlierScores(String projectId) {
// 获取所有已完成的评估记录
List<EvaluationRecord> completedRecords = recordRepository
.findByProjectIdAndStatus(projectId, EvaluationStatus.COMPLETED);
// 提取所有评分
List<BigDecimal> allScores = completedRecords.stream()
.map(EvaluationRecord::getTotalScore)
.collect(Collectors.toList());
// 检测异常值
OutlierDetectionResult detectionResult = outlierDetector.detectOutliers(allScores);
if (detectionResult.getAllOutliers().isEmpty()) {
return Collections.emptyList();
}
// 找出有异常值的被评估者
List<String> outlierEvaluatees = completedRecords.stream()
.filter(record -> detectionResult.getAllOutliers().contains(record.getTotalScore()))
.map(EvaluationRecord::getEvaluateeId)
.distinct()
.collect(Collectors.toList());
return outlierEvaluatees;
}
}
/**
* 项目评估进度
*/
@Data
@Builder
public class ProjectProgress {
/**
* 项目ID
*/
private String projectId;
/**
* 总评估记录数
*/
private Integer totalRecords;
/**
* 被评估者数量
*/
private Integer evaluateeCount;
/**
* 待评估数量
*/
private Integer pendingCount;
/**
* 评估中数量
*/
private Integer inProgressCount;
/**
* 已完成数量
*/
private Integer completedCount;
/**
* 已跳过数量
*/
private Integer skippedCount;
/**
* 完成率
*/
private Double completionRate;
public static ProjectProgress empty() {
return ProjectProgress.builder()
.totalRecords(0)
.evaluateeCount(0)
.pendingCount(0)
.inProgressCount(0)
.completedCount(0)
.skippedCount(0)
.completionRate(0.0)
.build();
}
}
/**
* 被评估者评估进度
*/
@Data
@Builder
public class EvaluateeProgress {
/**
* 项目ID
*/
private String projectId;
/**
* 被评估者ID
*/
private String evaluateeId;
/**
* 评估者总数
*/
private Integer totalRaters;
/**
* 已完成评估者数量
*/
private Integer completedRaters;
/**
* 完成率
*/
private Double completionRate;
/**
* 各状态数量
*/
private Map<EvaluationStatus, Long> statusCounts;
public static EvaluateeProgress empty() {
return EvaluateeProgress.builder()
.totalRaters(0)
.completedRaters(0)
.completionRate(0.0)
.statusCounts(Collections.emptyMap())
.build();
}
}
/**
* 进度告警
*/
@Data
@Builder
public class ProgressAlert {
/**
* 告警类型
*/
private AlertType alertType;
/**
* 告警级别
*/
private AlertSeverity severity;
/**
* 告警消息
*/
private String message;
/**
* 数量
*/
private Integer count;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
/**
* 告警类型枚举
*/
public enum AlertType {
/** 即将过期 */
EXPIRING_SOON,
/** 完成率低 */
LOW_COMPLETION_RATE,
/** 评估者不足 */
INSUFFICIENT_RATERS,
/** 异常评分 */
OUTLIER_SCORES
}
/**
* 告警级别枚举
*/
public enum AlertSeverity {
/** 低 */
LOW,
/** 中 */
MEDIUM,
/** 高 */
HIGH
}
4.3 数据可视化
评估结果可视化:
/**
* 评估数据可视化服务
* 生成各种图表和报表
*/
@Service
@Slf4j
public class EvaluationVisualizationService {
@Autowired
private EvaluationResultRepository resultRepository;
/**
* 生成评分分布图数据
*
* @param projectId 项目ID
* @return 图表数据
*/
public ChartData generateScoreDistributionChart(String projectId) {
// 获取所有评估结果
List<EvaluationResult> results = resultRepository.findByProjectId(projectId);
// 按分数段分组
Map<String, Long> distribution = new LinkedHashMap<>();
distribution.put("90-100", 0L);
distribution.put("80-89", 0L);
distribution.put("70-79", 0L);
distribution.put("60-69", 0L);
distribution.put("0-59", 0L);
for (EvaluationResult result : results) {
BigDecimal score = result.getTotalScore();
if (score.compareTo(new BigDecimal("90")) >= 0) {
distribution.put("90-100", distribution.get("90-100") + 1);
} else if (score.compareTo(new BigDecimal("80")) >= 0) {
distribution.put("80-89", distribution.get("80-89") + 1);
} else if (score.compareTo(new BigDecimal("70")) >= 0) {
distribution.put("70-79", distribution.get("70-79") + 1);
} else if (score.compareTo(new BigDecimal("60")) >= 0) {
distribution.put("60-69", distribution.get("60-69") + 1);
} else {
distribution.put("0-59", distribution.get("0-59") + 1);
}
}
// 构建图表数据
List<String> categories = new ArrayList<>(distribution.keySet());
List<Long> values = new ArrayList<>(distribution.values());
return ChartData.builder()
.chartType(ChartType.BAR)
.title("评分分布")
.categories(categories)
.series(List.of(
ChartSeries.builder()
.name("人数")
.data(values)
.build()
))
.build();
}
/**
* 生成维度对比雷达图数据
*
* @param projectId 项目ID
* @param evaluateeIds 被评估者ID列表
* @return 图表数据
*/
public ChartData generateDimensionRadarChart(
String projectId,
List<String> evaluateeIds
) {
// 获取评估结果
List<EvaluationResult> results = evaluateeIds.stream()
.map(evaluateeId -> resultRepository.findByProjectIdAndEvaluateeId(projectId, evaluateeId))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
// 提取维度名称
Set<String> dimensionNames = new LinkedHashSet<>();
for (EvaluationResult result : results) {
List<DimensionScore> dimensionScores = parseDimensionScores(result.getDimensionScores());
dimensionScores.forEach(ds -> dimensionNames.add(ds.getDimensionName()));
}
// 构建图表数据
List<String> categories = new ArrayList<>(dimensionNames);
List<ChartSeries> series = new ArrayList<>();
for (EvaluationResult result : results) {
List<DimensionScore> dimensionScores = parseDimensionScores(result.getDimensionScores());
// 按维度名称顺序提取分数
List<BigDecimal> scores = dimensionNames.stream()
.map(dimName -> {
return dimensionScores.stream()
.filter(ds -> ds.getDimensionName().equals(dimName))
.map(DimensionScore::getScore)
.findFirst()
.orElse(BigDecimal.ZERO);
})
.collect(Collectors.toList());
series.add(ChartSeries.builder()
.name(result.getEvaluateeName())
.data(scores.stream().map(BigDecimal::doubleValue).collect(Collectors.toList()))
.build());
}
return ChartData.builder()
.chartType(ChartType.RADAR)
.title("维度能力对比")
.categories(categories)
.series(series)
.build();
}
/**
* 生成趋势图数据
*
* @param evaluateeId 被评估者ID
* @param projectIds 项目ID列表
* @return 图表数据
*/
public ChartData generateTrendChart(
String evaluateeId,
List<String> projectIds
) {
// 获取历史评估结果
List<EvaluationResult> results = projectIds.stream()
.map(projectId -> resultRepository.findByProjectIdAndEvaluateeId(projectId, evaluateeId))
.filter(Optional::isPresent)
.map(Optional::get)
.sorted(Comparator.comparing(EvaluationResult::getCreateTime))
.collect(Collectors.toList());
// 提取数据
List<String> projectNames = results.stream()
.map(EvaluationResult::getProjectName)
.collect(Collectors.toList());
List<BigDecimal> totalScores = results.stream()
.map(EvaluationResult::getTotalScore)
.collect(Collectors.toList());
// 按维度提取趋势
Map<String, List<BigDecimal>> dimensionTrends = new LinkedHashMap<>();
for (EvaluationResult result : results) {
List<DimensionScore> dimensionScores = parseDimensionScores(result.getDimensionScores());
for (DimensionScore ds : dimensionScores) {
dimensionTrends.computeIfAbsent(ds.getDimensionName(), k -> new ArrayList<>())
.add(ds.getScore());
}
}
// 构建图表数据
List<ChartSeries> series = new ArrayList<>();
// 添加总分趋势
series.add(ChartSeries.builder()
.name("总分")
.data(totalScores.stream().map(BigDecimal::doubleValue).collect(Collectors.toList()))
.build());
// 添加各维度趋势
for (Map.Entry<String, List<BigDecimal>> entry : dimensionTrends.entrySet()) {
series.add(ChartSeries.builder()
.name(entry.getKey())
.data(entry.getValue().stream().map(BigDecimal::doubleValue).collect(Collectors.toList()))
.build());
}
return ChartData.builder()
.chartType(ChartType.LINE)
.title("绩效趋势")
.categories(projectNames)
.series(series)
.build();
}
/**
* 解析维度得分
*/
private List<DimensionScore> parseDimensionScores(String dimensionScoresJson) {
if (StringUtils.isBlank(dimensionScoresJson)) {
return Collections.emptyList();
}
JSONObject jsonObject = JSON.parseObject(dimensionScoresJson);
List<DimensionScore> dimensionScores = new ArrayList<>();
for (String key : jsonObject.keySet()) {
JSONObject dimensionObj = jsonObject.getJSONObject(key);
DimensionScore dimensionScore = DimensionScore.builder()
.dimensionId(key)
.dimensionName(dimensionObj.getString("name"))
.score(dimensionObj.getBigDecimal("score"))
.build();
dimensionScores.add(dimensionScore);
}
return dimensionScores;
}
}
/**
* 图表数据
*/
@Data
@Builder
public class ChartData {
/**
* 图表类型
*/
private ChartType chartType;
/**
* 图表标题
*/
private String title;
/**
* 类别标签
*/
private List<String> categories;
/**
* 系列数据
*/
private List<ChartSeries> series;
/**
* 图表配置(JSON格式)
*/
private String options;
}
/**
* 图表类型枚举
*/
public enum ChartType {
/** 柱状图 */
BAR,
/** 折线图 */
LINE,
/** 饼图 */
PIE,
/** 雷达图 */
RADAR,
/** 散点图 */
SCATTER
}
/**
* 图表系列
*/
@Data
@Builder
public class ChartSeries {
/**
* 系列名称
*/
private String name;
/**
* 数据
*/
private List<Double> data;
/**
* 颜色
*/
private String color;
}
五、性能优化
5.1 批量评估处理
批量评估任务处理器:
/**
* 批量评估任务处理器
* 使用异步处理和消息队列优化大规模评估任务
*/
@Component
@Slf4j
public class BatchEvaluationProcessor {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private EvaluationDataAggregator aggregator;
/**
* 批量聚合评估数据
*
* @param projectId 项目ID
* @param evaluateeIds 被评估者ID列表
*/
public void batchAggregateEvaluation(
String projectId,
List<String> evaluateeIds
) {
log.info("开始批量聚合评估数据: projectId={}, count={}",
projectId, evaluateeIds.size());
// 分批发送消息到MQ
int batchSize = 50;
List<List<String>> batches = Lists.partition(evaluateeIds, batchSize);
for (int i = 0; i < batches.size(); i++) {
List<String> batch = batches.get(i);
EvaluationAggregationMessage message = EvaluationAggregationMessage.builder()
.projectId(projectId)
.evaluateeIds(batch)
.batchNumber(i + 1)
.totalBatches(batches.size())
.build();
rocketMQTemplate.asyncSend(
"EVALUATION_AGGREGATION_TOPIC",
message,
new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("批量聚合消息发送成功: batchNumber={}", i + 1);
}
@Override
public void onException(Throwable e) {
log.error("批量聚合消息发送失败: batchNumber={}", i + 1, e);
}
}
);
}
}
}
/**
* 评估聚合消息监听器
*/
@Component
@RocketMQMessageListener(
topic = "EVALUATION_AGGREGATION_TOPIC",
consumerGroup = "EVALUATION_AGGREGATION_CONSUMER_GROUP"
)
@Slf4j
public class EvaluationAggregationConsumer implements
RocketMQListener<EvaluationAggregationMessage> {
@Autowired
private EvaluationDataAggregator aggregator;
@Autowired
private EvaluationResultRepository resultRepository;
@Override
public void onMessage(EvaluationAggregationMessage message) {
String projectId = message.getProjectId();
int batchNumber = message.getBatchNumber();
log.info("处理评估聚合批次: projectId={}, batchNumber={}", projectId, batchNumber);
try {
// 并行处理本批次的所有被评估者
List<EvaluationAggregationResult> results = message.getEvaluateeIds()
.parallelStream()
.map(evaluateeId -> aggregator.aggregateEvaluation(projectId, evaluateeId))
.collect(Collectors.toList());
// 批量保存聚合结果
List<EvaluationResult> evaluationResults = results.stream()
.map(this::convertToEvaluationResult)
.collect(Collectors.toList());
resultRepository.saveAll(evaluationResults);
log.info("评估聚合批次处理完成: projectId={}, batchNumber={}, count={}",
projectId, batchNumber, evaluationResults.size());
} catch (Exception e) {
log.error("评估聚合批次处理失败: projectId={}, batchNumber={}, error={}",
projectId, batchNumber, e.getMessage());
}
}
/**
* 转换为评估结果
*/
private EvaluationResult convertToEvaluationResult(
EvaluationAggregationResult aggregationResult
) {
EvaluationResult result = new EvaluationResult();
result.setProjectId(aggregationResult.getProjectId());
result.setEvaluateeId(aggregationResult.getEvaluateeId());
result.setTotalScore(aggregationResult.getTotalScore());
// 转换维度得分
JSONObject dimensionScores = new JSONObject();
for (DimensionAggregation dimension : aggregationResult.getDimensionAggregations()) {
JSONObject dimensionObj = new JSONObject();
dimensionObj.put("name", dimension.getDimensionName());
dimensionObj.put("score", dimension.getAverageScore());
dimensionScores.put(dimension.getDimensionId(), dimensionObj);
}
result.setDimensionScores(dimensionScores.toJSONString());
return result;
}
}
/**
* 评估聚合消息
*/
@Data
@Builder
public class EvaluationAggregationMessage implements Serializable {
/**
* 项目ID
*/
private String projectId;
/**
* 被评估者ID列表
*/
private List<String> evaluateeIds;
/**
* 批次号
*/
private Integer batchNumber;
/**
* 总批次数
*/
private Integer totalBatches;
}
5.2 缓存策略
评估数据缓存管理:
/**
* 评估数据缓存管理器
* 使用多级缓存提升查询性能
*/
@Component
@Slf4j
public class EvaluationCacheManager {
/**
* L1缓存:本地缓存(Caffeine)
*/
private final Cache<String, Object> localCache;
/**
* L2缓存:分布式缓存(Redis)
*/
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public EvaluationCacheManager() {
this.localCache = Caffeine.newBuilder()
.maximumSize(50_000)
.expireAfterWrite(1, TimeUnit.HOURS)
.recordStats()
.build();
}
/**
* 获取项目进度缓存
*
* @param projectId 项目ID
* @return 项目进度
*/
public ProjectProgress getProjectProgress(String projectId) {
String cacheKey = buildProjectProgressKey(projectId);
// 先查L1缓存
Object cached = localCache.getIfPresent(cacheKey);
if (cached != null) {
return (ProjectProgress) cached;
}
// 再查L2缓存
cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
// 回写L1缓存
localCache.put(cacheKey, cached);
return (ProjectProgress) cached;
}
return null;
}
/**
* 缓存项目进度
*
* @param projectId 项目ID
* @param progress 项目进度
*/
public void cacheProjectProgress(String projectId, ProjectProgress progress) {
String cacheKey = buildProjectProgressKey(projectId);
// 写入L1缓存
localCache.put(cacheKey, progress);
// 写入L2缓存
redisTemplate.opsForValue().set(cacheKey, progress, 30, TimeUnit.MINUTES);
}
/**
* 获取评估结果缓存
*
* @param projectId 项目ID
* @param evaluateeId 被评估者ID
* @return 评估结果
*/
public EvaluationResult getEvaluationResult(String projectId, String evaluateeId) {
String cacheKey = buildEvaluationResultKey(projectId, evaluateeId);
// 先查L1缓存
Object cached = localCache.getIfPresent(cacheKey);
if (cached != null) {
return (EvaluationResult) cached;
}
// 再查L2缓存
cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
// 回写L1缓存
localCache.put(cacheKey, cached);
return (EvaluationResult) cached;
}
return null;
}
/**
* 缓存评估结果
*
* @param projectId 项目ID
* @param evaluateeId 被评估者ID
* @param result 评估结果
*/
public void cacheEvaluationResult(
String projectId,
String evaluateeId,
EvaluationResult result
) {
String cacheKey = buildEvaluationResultKey(projectId, evaluateeId);
// 写入L1缓存
localCache.put(cacheKey, result);
// 写入L2缓存
redisTemplate.opsForValue().set(cacheKey, result, 1, TimeUnit.HOURS);
}
/**
* 清除项目相关缓存
*
* @param projectId 项目ID
*/
public void invalidateProjectCache(String projectId) {
// 清除L1缓存
Set<String> keys = localCache.asMap().keySet().stream()
.filter(key -> key.startsWith(projectId))
.collect(Collectors.toSet());
localCache.invalidateAll(keys);
// 清除L2缓存
Set<String> redisKeys = redisTemplate.keys(projectId + ":*");
if (redisKeys != null && !redisKeys.isEmpty()) {
redisTemplate.delete(redisKeys);
}
log.info("清除项目缓存: projectId={}", projectId);
}
/**
* 构建项目进度缓存键
*/
private String buildProjectProgressKey(String projectId) {
return String.format("evaluation:project:progress:%s", projectId);
}
/**
* 构建评估结果缓存键
*/
private String buildEvaluationResultKey(String projectId, String evaluateeId) {
return String.format("evaluation:result:%s:%s", projectId, evaluateeId);
}
}
六、最佳实践
6.1 评估设计最佳实践
1. 评估维度设计原则
- 全面性:覆盖工作业绩、工作能力、工作态度、团队协作等维度
- 独立性:各维度之间相对独立,避免重复评价
- 可衡量性:每个维度都可以用具体指标量化
- 相关性:维度设置与企业战略和岗位要求相关
- 数量适中:建议4-6个维度,避免评估疲劳
2. 评估指标设计原则
- SMART原则:具体、可衡量、可达成、相关性、有时限
- 行为化:用具体行为描述,而非抽象概念
- 权重合理:根据指标重要性分配权重
- 分层设计:核心指标、重要指标、一般指标
- 可观察性:指标应该是可以直接观察到的
3. 评估者选择策略
- 上级评估:1-2人,权重30-40%
- 同事评估:3-5人,权重20-30%
- 下级评估:2-3人,权重15-20%
- 自评:1人,权重5-10%(仅供参考)
- 客户评估:根据实际情况,权重10-20%
- 评估者数量:建议5-10人
- 匿名原则:同事、下级、客户评估匿名
- 多样性:选择不同背景、不同合作深度的评估者
6.2 数据分析最佳实践
1. 异常值处理策略
// 示例:处理评估异常值
public class OutlierHandlingExample {
public void handleOutliers(List<EvaluationRecord> records) {
// 1. 检测异常值
EvaluationOutlierDetector detector = new EvaluationOutlierDetector();
List<BigDecimal> scores = records.stream()
.map(EvaluationRecord::getTotalScore)
.collect(Collectors.toList());
OutlierDetectionResult result = detector.detectOutliers(scores);
// 2. 根据异常值比例选择处理策略
if (result.getOutlierRatio() > 0.3) {
// 异常值比例过高,可能存在系统性问题
log.warn("异常值比例过高: ratio={}", result.getOutlierRatio());
// 需要人工复核
return;
}
// 3. 处理异常值
List<BigDecimal> processedScores = detector.handleOutliers(
scores,
OutlierHandlingStrategy.REPLACE_WITH_MEDIAN
);
// 4. 使用处理后的数据
// ...
}
}
2. 趋势分析方法
// 示例:分析绩效趋势
public class TrendAnalysisExample {
public TrendAnalysisResult analyzeTrend(String employeeId) {
// 1. 获取历史评估结果
List<EvaluationResult> historyResults = getHistoryResults(employeeId);
// 2. 计算移动平均
List<BigDecimal> movingAverages = calculateMovingAverage(
historyResults,
3 // 3期移动平均
);
// 3. 判断趋势
TrendType trend = determineTrend(movingAverages);
// 4. 计算趋势强度
BigDecimal trendStrength = calculateTrendStrength(movingAverages);
// 5. 预测未来表现
BigDecimal predictedScore = predictNextScore(movingAverages);
return TrendAnalysisResult.builder()
.trendType(trend)
.trendStrength(trendStrength)
.predictedScore(predictedScore)
.build();
}
}
6.3 系统监控最佳实践
关键监控指标:
/**
* 360度评估系统监控指标
*/
public class EvaluationSystemMetrics {
/**
* 核心指标
*/
public enum CoreMetric {
/** 评估参与率 */
EVALUATION_PARTICIPATION_RATE,
/** 评估完成率 */
EVALUATION_COMPLETION_RATE,
/** 平均评估时长 */
AVERAGE_EVALUATION_DURATION,
/** 异常评分比例 */
OUTLIER_SCORE_RATIO,
/** 缓存命中率 */
CACHE_HIT_RATE
}
/**
* 业务指标
*/
public enum BusinessMetric {
/** 评估项目执行周期 */
PROJECT_EXECUTION_CYCLE,
/** 改进计划完成率 */
IMPROVEMENT_PLAN_COMPLETION_RATE,
/** 绩效提升率 */
PERFORMANCE_IMPROVEMENT_RATE,
/** 员工满意度 */
EMPLOYEE_SATISFACTION_SCORE
}
}
七、总结
本文详细介绍了360度绩效评估系统的设计与实现,主要内容包括:
核心技术点
- 多维度评估模型:支持工作业绩、工作能力、工作态度、团队协作等多维度评估
- 智能评估者选择:根据组织结构自动选择合适的评估者
- 数据聚合算法:加权平均、异常值检测、数据清洗等算法
- 结果分析引擎:排名计算、趋势分析、对比分析
- 改进计划生成:基于评估结果自动生成改进建议
业务价值
- 全面性:多视角评估,全面了解员工表现
- 客观性:多人评估减少个人偏见
- 发展性:帮助员工识别优势和改进空间
- 数据化:量化评估结果,便于决策
- 自动化:自动化流程降低管理成本
扩展方向
- AI辅助评估:使用NLP技术分析评语,提取关键信息
- 智能推荐:根据历史数据推荐改进措施
- 预测分析:预测员工绩效发展趋势
- 实时反馈:支持实时评估和即时反馈
- 移动端优化:优化移动端评估体验
参考资料
- 360度反馈评估方法论
- 绩效管理最佳实践
- 《绩效管理:理论与实践》(第四版)
- 《组织行为学》(第18版)
- 《数据化运营:系统方法与实践案例》
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)