点狮HRM-OKR目标管理系统设计与实践
·
相关链接:
- 🌐 官网:http://www.dianshixinxi.com/
- 📱 演示站:http://cloud.dianshixinxi.com:90/
- 🎨 Gitee:https://gitee.com/glorylion/JFinalOA
- 💻 GitCode:https://gitcode.com/Glory_Lion/pointlion-cloud
一、业务背景与挑战
1.1 传统绩效管理的痛点
在数字化转型的浪潮中,企业越来越重视目标管理。传统的KPI(关键绩效指标)管理模式存在诸多问题:
传统KPI模式的局限性:
- 目标脱节:员工目标与企业战略脱节,各自为政
- 短期导向:过度关注短期指标,忽视长期价值创造
- 僵化执行:目标设定后难以调整,无法适应变化
- 考核至上:以考核为目的,而非以改进为目的
- 缺乏激励:员工对目标缺乏认同感,内驱力不足
- 沟通不畅:目标制定过程缺乏透明度和员工参与


1.2 OKR方法论的价值
什么是OKR:
OKR(Objectives and Key Results,目标与关键结果)是一种目标管理框架,由Intel引入Google并在硅谷科技企业中广为流行。
OKR的核心要素:
- Objective(目标):回答"我想实现什么",定性描述,鼓舞人心
- Key Result(关键结果):回答"我如何实现目标",定量衡量,可验证达成
- 周期性:按季度或月度设定,保持灵活性和适应性
- 透明公开:全员可见,促进对齐和协作
- 挑战性:设定具有挑战性的目标,激发团队潜能
OKR管理的价值:
- 战略对齐:确保个人目标与团队目标、企业战略高度对齐
- 聚焦重点:明确优先级,集中精力在重要事项上
- 透明协作:目标公开透明,促进跨部门协作
- 持续改进:定期检视和调整,保持敏捷性
- 内驱激发:自主设定目标,增强员工参与感和责任感
1.3 技术实现挑战
OKR系统的技术挑战:
- 目标对齐算法:如何实现多层目标的有效对齐和联动
- 进度跟踪机制:如何实时跟踪关键结果的完成进度
- 数据聚合计算:如何高效地聚合和计算OKR达成率
- 变更控制流程:如何管理目标的变更历史和影响分析
- 可视化展示:如何直观地展示OKR层级关系和进度状态
二、整体架构设计
2.1 系统架构全景
┌─────────────────────────────────────────────────────────────────┐
│ OKR目标管理系统 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 目标配置管理层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 目标模板管理 │ │ 目标周期管理 │ │ 权重配置管理 │ │ │
│ │ │(Template) │ │(Cycle) │ │(Weight) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 目标对齐配置 │ │ 关键结果配置 │ │ 进度跟踪配置 │ │ │
│ │ │(Alignment) │ │(Key Result) │ │(Tracking) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 目标管理引擎层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 目标创建引擎 │ │ 目标对齐引擎 │ │ 进度计算引擎 │ │ │
│ │ │(Creator) │ │(Alignment) │ │(ProgressCalc) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 变更控制引擎 │ │ 通知提醒引擎 │ │ 审批流程引擎 │ │ │
│ │ │(ChangeCtrl) │ │(Notifier) │ │(Approval) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 数据分析层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 达成率分析 │ │ 趋势分析引擎 │ │ 对比分析引擎 │ │ │
│ │ │(Achievement) │ │(Trend) │ │(Comparison) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 预测分析引擎 │ │ 异常检测引擎 │ │ 智能推荐引擎 │ │ │
│ │ │(Prediction) │ │(Anomaly) │ │(Recommend) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 可视化展示层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 目标树状图 │ │ 进度仪表盘 │ │ 达成率报表 │ │ │
│ │ │(Tree View) │ │(Dashboard) │ │(Report) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2.2 核心领域模型
OKR目标模型(ObjectiveDO):
/**
* OKR目标领域对象
* 表示一个OKR周期内的目标
*/
@Data
@TableName("hrm_okr_objective")
public class ObjectiveDO {
/**
* 目标ID
*/
@TableId(type = IdType.ASSIGN_ID)
private String id;
/**
* 目标周期ID
*/
private String cycleId;
/**
* 目标类型:COMPANY(公司), DEPARTMENT(部门), TEAM(团队), PERSONAL(个人)
*/
private ObjectiveType type;
/**
* 目标所属对象ID(部门ID、团队ID或员工ID)
*/
private String ownerId;
/**
* 目标所属对象名称
*/
private String ownerName;
/**
* 父目标ID(用于目标对齐)
*/
private String parentObjectiveId;
/**
* 目标标题(定性描述,鼓舞人心)
* 示例:"打造业界领先的移动办公体验"
*/
private String title;
/**
* 目标描述
*/
@Lob
private String description;
/**
* 目标状态:DRAFT(草稿), ACTIVE(进行中), PAUSED(暂停), COMPLETED(已完成), CANCELLED(已取消)
*/
private ObjectiveStatus status;
/**
* 目标进度(0-100)
*/
private BigDecimal progress;
/**
* 优先级:P0(最高), P1(高), P2(中), P3(低)
*/
private ObjectivePriority priority;
/**
* 目标分类:STRATEGIC(战略), GROWTH(增长), PRODUCT(产品), CULTURE(文化)
*/
private ObjectiveCategory category;
/**
* 开始时间
*/
private LocalDate startDate;
/**
* 结束时间
*/
private LocalDate endDate;
/**
* 创建人ID
*/
private String creatorId;
/**
* 创建人姓名
*/
private String creatorName;
/**
* 目标负责人ID
*/
private String ownerId;
/**
* 目标负责人姓名
*/
private String ownerName;
/**
* 参与人ID列表(JSON数组)
*/
@Lob
private String participantIds;
/**
* 观察者ID列表(JSON数组)
*/
@Lob
private String watcherIds;
/**
* 是否公开
*/
private Boolean isPublic;
/**
* 置信度评分(0-10)
*/
private Integer confidenceScore;
/**
* 难度评分(0-10)
*/
private Integer difficultyScore;
/**
* 扩展属性(JSON格式)
*/
@Lob
private String extProperties;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
/**
* 目标类型枚举
*/
public enum ObjectiveType {
/** 公司级目标 */
COMPANY,
/** 部门级目标 */
DEPARTMENT,
/** 团队级目标 */
TEAM,
/** 个人目标 */
PERSONAL
}
/**
* 目标状态枚举
*/
public enum ObjectiveStatus {
/** 草稿 */
DRAFT,
/** 进行中 */
ACTIVE,
/** 暂停 */
PAUSED,
/** 已完成 */
COMPLETED,
/** 已取消 */
CANCELLED
}
/**
* 目标优先级枚举
*/
public enum ObjectivePriority {
/** P0 - 最高优先级 */
P0(0),
/** P1 - 高优先级 */
P1(1),
/** P2 - 中优先级 */
P2(2),
/** P3 - 低优先级 */
P3(3);
private final Integer code;
ObjectivePriority(Integer code) {
this.code = code;
}
public Integer getCode() {
return code;
}
}
关键结果模型(KeyResultDO):
/**
* 关键结果领域对象
* 表示目标下的关键结果,用于量化目标达成情况
*/
@Data
@TableName("hrm_okr_key_result")
public class KeyResultDO {
/**
* 关键结果ID
*/
@TableId(type = IdType.ASSIGN_ID)
private String id;
/**
* 所属目标ID
*/
private String objectiveId;
/**
* 关键结果描述
* 示例:"移动端DAU提升50%"
*/
private String title;
/**
* 关键结果描述
*/
@Lob
private String description;
/**
* 当前值
*/
private BigDecimal currentValue;
/**
* 目标值
*/
private BigDecimal targetValue;
/**
* 单位
*/
private String unit;
/**
* 数据类型:NUMBER(数值), PERCENTAGE(百分比), CURRENCY(金额), COUNT(计数)
*/
private KeyResultDataType dataType;
/**
* 进度方向:INCREASE(增长), DECREASE(减少), MAINTAIN(维持)
*/
private ProgressDirection direction;
/**
* 权重(0-100)
*/
private BigDecimal weight;
/**
* 进度(0-100)
*/
private BigDecimal progress;
/**
* 状态:NOT_STARTED(未开始), IN_PROGRESS(进行中), ACHIEVED(已达成), AT_RISK(有风险), NOT_ACHIEVED(未达成)
*/
private KeyResultStatus status;
/**
* 截止时间
*/
private LocalDate dueDate;
/**
* 负责人ID
*/
private String ownerId;
/**
* 负责人姓名
*/
private String ownerName;
/**
* 数据源类型:MANUAL(手动录入), SYSTEM(系统同步), API(外部接口)
*/
private DataSourceType dataSourceType;
/**
* 数据源配置(JSON格式)
* 记录数据源配置,如API地址、同步频率等
*/
@Lob
private String dataSourceConfig;
/**
* 最后同步时间
*/
private LocalDateTime lastSyncTime;
/**
* 进度更新时间
*/
private LocalDateTime progressUpdateTime;
/**
* 扩展属性(JSON格式)
*/
@Lob
private String extProperties;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
/**
* 关键结果数据类型枚举
*/
public enum KeyResultDataType {
/** 数值型 */
NUMBER,
/** 百分比型 */
PERCENTAGE,
/** 金额型 */
CURRENCY,
/** 计数型 */
COUNT
}
/**
* 进度方向枚举
*/
public enum ProgressDirection {
/** 增长型:越大越好 */
INCREASE,
/** 下降型:越小越好 */
DECREASE,
/** 维持型:稳定在目标值附近 */
MAINTAIN
}
/**
* 关键结果状态枚举
*/
public enum KeyResultStatus {
/** 未开始 */
NOT_STARTED,
/** 进行中 */
IN_PROGRESS,
/** 已达成 */
ACHIEVED,
/** 有风险 */
AT_RISK,
/** 未达成 */
NOT_ACHIEVED
}
2.3 OKR周期模型
OKR周期模型(OkrCycleDO):
/**
* OKR周期领域对象
* 定义一个OKR管理周期
*/
@Data
@TableName("hrm_okr_cycle")
public class OkrCycleDO {
/**
* 周期ID
*/
@TableId(type = IdType.ASSIGN_ID)
private String id;
/**
* 周期名称
* 示例:"2024年Q1", "2024年3月"
*/
private String name;
/**
* 周期类型:QUARTERLY(季度), MONTHLY(月度), WEEKLY(周度), CUSTOM(自定义)
*/
private CycleType type;
/**
* 年份
*/
private Integer year;
/**
* 季度(1-4)
*/
private Integer quarter;
/**
* 月份(1-12)
*/
private Integer month;
/**
* 开始日期
*/
private LocalDate startDate;
/**
* 结束日期
*/
private LocalDate endDate;
/**
* 周期状态:DRAFT(草稿), ACTIVE(进行中), CLOSED(已关闭)
*/
private CycleStatus status;
/**
* 目标制定开始时间
*/
private LocalDateTime planningStartTime;
/**
* 目标制定截止时间
*/
private LocalDateTime planningEndTime;
/**
* 目标执行开始时间
*/
private LocalDateTime executionStartTime;
/**
* 目标执行截止时间
*/
private LocalDateTime executionEndTime;
/**
* 复盘开始时间
*/
private LocalDateTime reviewStartTime;
/**
* 复盘截止时间
*/
private LocalDateTime reviewEndTime;
/**
* 参与人员范围
* ALL(全员), DEPARTMENT(指定部门), TEAM(指定团队)
*/
private ParticipantScope participantScope;
/**
* 参与人员配置(JSON格式)
*/
@Lob
private String participantConfig;
/**
* OKR模板ID
*/
private String templateId;
/**
* 目标总数
*/
private Integer totalObjectives;
/**
* 进行中目标数
*/
private Integer activeObjectives;
/**
* 已完成目标数
*/
private Integer completedObjectives;
/**
* 平均达成率
*/
private BigDecimal averageAchievementRate;
/**
* 扩展配置(JSON格式)
*/
@Lob
private String extConfig;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
/**
* 周期类型枚举
*/
public enum CycleType {
/** 季度周期 */
QUARTERLY,
/** 月度周期 */
MONTHLY,
/** 周度周期 */
WEEKLY,
/** 自定义周期 */
CUSTOM
}
/**
* 周期状态枚举
*/
public enum CycleStatus {
/** 草稿 */
DRAFT,
/** 进行中 */
ACTIVE,
/** 已关闭 */
CLOSED
}
/**
* 参与人员范围枚举
*/
public enum ParticipantScope {
/** 全员 */
ALL,
/** 指定部门 */
DEPARTMENT,
/** 指定团队 */
TEAM
}
三、核心模块实现
3.1 目标对齐引擎
目标对齐算法实现:
/**
* OKR目标对齐引擎
* 实现个人目标与团队目标、团队目标与部门目标、部门目标与公司目标的对齐
*/
@Service
@Slf4j
public class ObjectiveAlignmentEngine {
@Autowired
private ObjectiveMapper objectiveMapper;
@Autowired
private KeyResultMapper keyResultMapper;
/**
* 创建对齐关系
*
* @param childObjectiveId 子目标ID
* @param parentObjectiveId 父目标ID
*/
@Transactional(rollbackFor = Exception.class)
public void createAlignment(String childObjectiveId, String parentObjectiveId) {
log.info("创建目标对齐: child={}, parent={}", childObjectiveId, parentObjectiveId);
// 1. 获取目标信息
ObjectiveDO childObjective = objectiveMapper.selectById(childObjectiveId);
ObjectiveDO parentObjective = objectiveMapper.selectById(parentObjectiveId);
if (childObjective == null || parentObjective == null) {
throw new BusinessException("目标不存在");
}
// 2. 验证对齐关系
validateAlignment(childObjective, parentObjective);
// 3. 建立对齐关系
childObjective.setParentObjectiveId(parentObjectiveId);
objectiveMapper.updateById(childObjective);
// 4. 记录对齐日志
ObjectiveAlignmentLog alignmentLog = new ObjectiveAlignmentLog();
alignmentLog.setId(IdUtil.fastUUID());
alignmentLog.setChildObjectiveId(childObjectiveId);
alignmentLog.setParentObjectiveId(parentObjectiveId);
alignmentLog.setChildObjectiveTitle(childObjective.getTitle());
alignmentLog.setParentObjectiveTitle(parentObjective.getTitle());
alignmentLog.setAlignmentTime(LocalDateTime.now());
alignmentLog.setOperator(SecurityContextHolder.getUserId());
alignmentLogMapper.insert(alignmentLog);
log.info("目标对齐创建完成: child={}, parent={}", childObjectiveId, parentObjectiveId);
}
/**
* 批量自动对齐
*
* @param cycleId 周期ID
*/
@Transactional(rollbackFor = Exception.class)
public void batchAutoAlignment(String cycleId) {
log.info("开始批量自动对齐: cycleId={}", cycleId);
// 1. 获取该周期下所有未对齐的目标
List<ObjectiveDO> unalignedObjectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
.isNull(ObjectiveDO::getParentObjectiveId)
.ne(ObjectiveDO::getStatus, ObjectiveStatus.CANCELLED)
);
log.info("找到未对齐目标: count={}", unalignedObjectives.size());
// 2. 按目标类型分组对齐
for (ObjectiveDO objective : unalignedObjectives) {
try {
alignObjectiveToParent(objective);
} catch (Exception e) {
log.error("对齐目标失败: objectiveId={}, error={}",
objective.getId(), e.getMessage());
}
}
log.info("批量自动对齐完成: cycleId={}", cycleId);
}
/**
* 对齐目标到父目标
*
* @param objective 目标
*/
private void alignObjectiveToParent(ObjectiveDO objective) {
switch (objective.getType()) {
case PERSONAL:
// 个人目标对齐到团队目标
alignPersonalToTeam(objective);
break;
case TEAM:
// 团队目标对齐到部门目标
alignTeamToDepartment(objective);
break;
case DEPARTMENT:
// 部门目标对齐到公司目标
alignDepartmentToCompany(objective);
break;
case COMPANY:
// 公司级目标无需对齐
break;
}
}
/**
* 个人目标对齐到团队目标
*/
private void alignPersonalToTeam(ObjectiveDO personalObjective) {
// 1. 获取员工的团队信息
String employeeId = personalObjective.getOwnerId();
String teamId = getTeamIdByEmployeeId(employeeId);
if (StringUtils.isBlank(teamId)) {
log.warn("员工未分配团队: employeeId={}", employeeId);
return;
}
// 2. 查找团队的活跃目标
ObjectiveDO teamObjective = objectiveMapper.selectOne(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, personalObjective.getCycleId())
.eq(ObjectiveDO::getOwnerId, teamId)
.eq(ObjectiveDO::getType, ObjectiveType.TEAM)
.eq(ObjectiveDO::getStatus, ObjectiveStatus.ACTIVE)
.orderByDesc(ObjectiveDO::getCreateTime)
.last("LIMIT 1")
);
if (teamObjective != null) {
// 创建对齐关系
createAlignment(personalObjective.getId(), teamObjective.getId());
log.info("个人目标已对齐到团队目标: personal={}, team={}",
personalObjective.getTitle(), teamObjective.getTitle());
}
}
/**
* 团队目标对齐到部门目标
*/
private void alignTeamToDepartment(ObjectiveDO teamObjective) {
// 1. 获取团队的部门信息
String teamId = teamObjective.getOwnerId();
String departmentId = getDepartmentIdByTeamId(teamId);
if (StringUtils.isBlank(departmentId)) {
log.warn("团队未分配部门: teamId={}", teamId);
return;
}
// 2. 查找部门的活跃目标
ObjectiveDO departmentObjective = objectiveMapper.selectOne(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, teamObjective.getCycleId())
.eq(ObjectiveDO::getOwnerId, departmentId)
.eq(ObjectiveDO::getType, ObjectiveType.DEPARTMENT)
.eq(ObjectiveDO::getStatus, ObjectiveStatus.ACTIVE)
.orderByDesc(ObjectiveDO::getCreateTime)
.last("LIMIT 1")
);
if (departmentObjective != null) {
// 创建对齐关系
createAlignment(teamObjective.getId(), departmentObjective.getId());
log.info("团队目标已对齐到部门目标: team={}, department={}",
teamObjective.getTitle(), departmentObjective.getTitle());
}
}
/**
* 部门目标对齐到公司目标
*/
private void alignDepartmentToCompany(ObjectiveDO departmentObjective) {
// 1. 查找公司的活跃目标
ObjectiveDO companyObjective = objectiveMapper.selectOne(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, departmentObjective.getCycleId())
.eq(ObjectiveDO::getType, ObjectiveType.COMPANY)
.eq(ObjectiveDO::getStatus, ObjectiveStatus.ACTIVE)
.orderByDesc(ObjectiveDO::getCreateTime)
.last("LIMIT 1")
);
if (companyObjective != null) {
// 创建对齐关系
createAlignment(departmentObjective.getId(), companyObjective.getId());
log.info("部门目标已对齐到公司目标: department={}, company={}",
departmentObjective.getTitle(), companyObjective.getTitle());
}
}
/**
* 验证对齐关系
*/
private void validateAlignment(ObjectiveDO child, ObjectiveDO parent) {
// 1. 检查周期是否相同
if (!child.getCycleId().equals(parent.getCycleId())) {
throw new BusinessException("父子目标的周期不一致");
}
// 2. 检查对齐路径是否合理
ObjectiveType childType = child.getType();
ObjectiveType parentType = parent.getType();
if (!isValidAlignment(childType, parentType)) {
throw new BusinessException(
String.format("无效的对齐关系: %s -> %s", childType, parentType)
);
}
// 3. 检查是否会造成循环对齐
if (wouldCreateCircularAlignment(child.getId(), parent.getId())) {
throw new BusinessException("对齐关系会造成循环依赖");
}
}
/**
* 检查对齐关系是否有效
*/
private boolean isValidAlignment(ObjectiveType childType, ObjectiveType parentType) {
// 个人 -> 团队
if (childType == ObjectiveType.PERSONAL && parentType == ObjectiveType.TEAM) {
return true;
}
// 团队 -> 部门
if (childType == ObjectiveType.TEAM && parentType == ObjectiveType.DEPARTMENT) {
return true;
}
// 部门 -> 公司
if (childType == ObjectiveType.DEPARTMENT && parentType == ObjectiveType.COMPANY) {
return true;
}
return false;
}
/**
* 检查是否会造成循环对齐
*/
private boolean wouldCreateCircularAlignment(String childId, String parentId) {
// 检查父目标是否已经对齐到子目标(直接或间接)
Set<String> ancestorIds = new HashSet<>();
String currentParentId = parentId;
while (StringUtils.isNotBlank(currentParentId)) {
if (childId.equals(currentParentId)) {
return true; // 发现循环
}
if (ancestorIds.contains(currentParentId)) {
return true; // 发现循环
}
ancestorIds.add(currentParentId);
ObjectiveDO currentParent = objectiveMapper.selectById(currentParentId);
if (currentParent == null) {
break;
}
currentParentId = currentParent.getParentObjectiveId();
}
return false;
}
/**
* 获取员工的团队ID
*/
private String getTeamIdByEmployeeId(String employeeId) {
// 从员工信息中获取团队ID
EmployeeDO employee = employeeMapper.selectById(employeeId);
return employee != null ? employee.getTeamId() : null;
}
/**
* 获取团队的部门ID
*/
private String getDepartmentIdByTeamId(String teamId) {
// 从团队信息中获取部门ID
TeamDO team = teamMapper.selectById(teamId);
return team != null ? team.getDepartmentId() : null;
}
}
3.2 进度计算引擎
OKR进度计算引擎:
/**
* OKR进度计算引擎
* 负责计算目标整体进度和关键结果进度
*/
@Service
@Slf4j
public class OkrProgressCalculationEngine {
@Autowired
private KeyResultMapper keyResultMapper;
@Autowired
private ObjectiveMapper objectiveMapper;
/**
* 计算目标进度
*
* @param objectiveId 目标ID
* @return 计算后的进度值
*/
@Transactional(rollbackFor = Exception.class)
public BigDecimal calculateObjectiveProgress(String objectiveId) {
log.info("计算目标进度: objectiveId={}", objectiveId);
// 1. 获取目标信息
ObjectiveDO objective = objectiveMapper.selectById(objectiveId);
if (objective == null) {
throw new BusinessException("目标不存在");
}
// 2. 获取目标的所有关键结果
List<KeyResultDO> keyResults = keyResultMapper.selectList(
new LambdaQueryWrapper<KeyResultDO>()
.eq(KeyResultDO::getObjectiveId, objectiveId)
.eq(KeyResultDO::getStatus, KeyResultStatus.ACTIVE)
);
if (keyResults.isEmpty()) {
log.warn("目标没有关键结果: objectiveId={}", objectiveId);
return BigDecimal.ZERO;
}
// 3. 加权计算整体进度
BigDecimal totalProgress = BigDecimal.ZERO;
BigDecimal totalWeight = BigDecimal.ZERO;
for (KeyResultDO keyResult : keyResults) {
// 计算单个关键结果的进度
BigDecimal krProgress = calculateKeyResultProgress(keyResult);
// 更新关键结果进度
keyResult.setProgress(krProgress);
keyResultMapper.updateById(keyResult);
// 加权汇总
BigDecimal weight = keyResult.getWeight() != null ?
keyResult.getWeight() : new BigDecimal("1");
totalProgress = totalProgress.add(krProgress.multiply(weight));
totalWeight = totalWeight.add(weight);
}
// 4. 计算平均进度
BigDecimal averageProgress = totalWeight.compareTo(BigDecimal.ZERO) > 0 ?
totalProgress.divide(totalWeight, 2, RoundingMode.HALF_UP) :
BigDecimal.ZERO;
// 5. 更新目标进度
objective.setProgress(averageProgress);
objectiveMapper.updateById(objective);
// 6. 根据进度更新状态
updateObjectiveStatus(objective);
log.info("目标进度计算完成: objectiveId={}, progress={}%",
objectiveId, averageProgress);
return averageProgress;
}
/**
* 计算关键结果进度
*
* @param keyResult 关键结果
* @return 进度值(0-100)
*/
private BigDecimal calculateKeyResultProgress(KeyResultDO keyResult) {
BigDecimal currentValue = keyResult.getCurrentValue();
BigDecimal targetValue = keyResult.getTargetValue();
if (currentValue == null || targetValue == null ||
targetValue.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
ProgressDirection direction = keyResult.getDirection();
if (direction == null) {
direction = ProgressDirection.INCREASE;
}
switch (direction) {
case INCREASE:
// 增长型:越大越好
return currentValue.multiply(new BigDecimal("100"))
.divide(targetValue, 2, RoundingMode.HALF_UP)
.min(new BigDecimal("100"));
case DECREASE:
// 下降型:越小越好
return currentValue.divide(targetValue, 2, RoundingMode.HALF_UP)
.multiply(new BigDecimal("100"))
.min(new BigDecimal("100"));
case MAINTAIN:
// 维持型:在目标值附近
BigDecimal deviation = currentValue.subtract(targetValue).abs();
BigDecimal tolerance = targetValue.multiply(new BigDecimal("0.1")); // 10%容差
if (deviation.compareTo(tolerance) <= 0) {
return new BigDecimal("100");
} else {
BigDecimal excess = deviation.subtract(tolerance);
BigDecimal penalty = excess.multiply(new BigDecimal("100"))
.divide(targetValue, 2, RoundingMode.HALF_UP);
return new BigDecimal("100").subtract(penalty).max(BigDecimal.ZERO);
}
default:
return BigDecimal.ZERO;
}
}
/**
* 批量计算周期内所有目标的进度
*
* @param cycleId 周期ID
*/
@Transactional(rollbackFor = Exception.class)
public void batchCalculateCycleProgress(String cycleId) {
log.info("批量计算周期进度: cycleId={}", cycleId);
// 1. 获取周期内所有活跃目标
List<ObjectiveDO> activeObjectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
.eq(ObjectiveDO::getStatus, ObjectiveStatus.ACTIVE)
);
log.info("找到活跃目标: count={}", activeObjectives.size());
// 2. 并行计算各目标进度
activeObjectives.parallelStream().forEach(objective -> {
try {
calculateObjectiveProgress(objective.getId());
} catch (Exception e) {
log.error("计算目标进度失败: objectiveId={}, error={}",
objective.getId(), e.getMessage());
}
});
// 3. 计算周期整体达成率
BigDecimal cycleAchievementRate = calculateCycleAchievementRate(cycleId);
// 4. 更新周期统计
OkrCycleDO cycle = okrCycleMapper.selectById(cycleId);
if (cycle != null) {
cycle.setAverageAchievementRate(cycleAchievementRate);
// 更新统计信息
updateCycleStatistics(cycle);
okrCycleMapper.updateById(cycle);
}
log.info("周期进度批量计算完成: cycleId={}, achievementRate={}%",
cycleId, cycleAchievementRate);
}
/**
* 计算周期整体达成率
*/
private BigDecimal calculateCycleAchievementRate(String cycleId) {
// 获取周期内所有已完成的目标
List<ObjectiveDO> completedObjectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
.eq(ObjectiveDO::getStatus, ObjectiveStatus.COMPLETED)
);
if (completedObjectives.isEmpty()) {
return BigDecimal.ZERO;
}
// 计算平均达成率
BigDecimal totalProgress = completedObjectives.stream()
.map(ObjectiveDO::getProgress)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return totalProgress.divide(
new BigDecimal(completedObjectives.size()),
2,
RoundingMode.HALF_UP
);
}
/**
* 更新周期统计信息
*/
private void updateCycleStatistics(OkrCycleDO cycle) {
Long totalCount = objectiveMapper.selectCount(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycle.getId())
);
Long activeCount = objectiveMapper.selectCount(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycle.getId())
.eq(ObjectiveDO::getStatus, ObjectiveStatus.ACTIVE)
);
Long completedCount = objectiveMapper.selectCount(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycle.getId())
.eq(ObjectiveDO::getStatus, ObjectiveStatus.COMPLETED)
);
cycle.setTotalObjectives(totalCount.intValue());
cycle.setActiveObjectives(activeCount.intValue());
cycle.setCompletedObjectives(completedCount.intValue());
}
/**
* 更新目标状态
*/
private void updateObjectiveStatus(ObjectiveDO objective) {
ObjectiveStatus currentStatus = objective.getStatus();
BigDecimal progress = objective.getProgress();
LocalDate endDate = objective.getEndDate();
// 如果已完成,无需更新
if (currentStatus == ObjectiveStatus.COMPLETED ||
currentStatus == ObjectiveStatus.CANCELLED) {
return;
}
// 检查是否应该标记为已完成
if (progress.compareTo(new BigDecimal("100")) >= 0) {
objective.setStatus(ObjectiveStatus.COMPLETED);
objectiveMapper.updateById(objective);
return;
}
// 检查是否已过期
if (endDate != null && LocalDate.now().isAfter(endDate)) {
objective.setStatus(ObjectiveStatus.COMPLETED);
objectiveMapper.updateById(objective);
return;
}
// 检查是否应该暂停
if (shouldPause(objective)) {
objective.setStatus(ObjectiveStatus.PAUSED);
objectiveMapper.updateById(objective);
}
}
/**
* 检查是否应该暂停目标
*/
private boolean shouldPause(ObjectiveDO objective) {
// 检查关键结果是否全部有风险
List<KeyResultDO> keyResults = keyResultMapper.selectList(
new LambdaQueryWrapper<KeyResultDO>()
.eq(KeyResultDO::getObjectiveId, objective.getId())
);
long atRiskCount = keyResults.stream()
.filter(kr -> KeyResultStatus.AT_RISK.equals(kr.getStatus()))
.count();
// 如果超过50%的关键结果有风险,标记为暂停
return !keyResults.isEmpty() &&
(double) atRiskCount / keyResults.size() > 0.5;
}
}
3.3 目标创建引擎
目标创建服务:
/**
* OKR目标创建服务
* 支持从模板创建、手动创建、批量创建等多种创建方式
*/
@Service
@Slf4j
public class ObjectiveCreationService {
@Autowired
private ObjectiveMapper objectiveMapper;
@Autowired
private ObjectiveTemplateMapper templateMapper;
@Autowired
private OkrProgressCalculationEngine progressCalculationEngine;
/**
* 从模板创建目标
*
* @param cycleId 周期ID
* @param templateId 模板ID
* @param ownerId 所有者ID
* @param customParams 自定义参数
* @return 创建的目标ID
*/
@Transactional(rollbackFor = Exception.class)
public String createFromTemplate(
String cycleId,
String templateId,
String ownerId,
Map<String, Object> customParams
) {
log.info("从模板创建目标: cycleId={}, templateId={}, ownerId={}",
cycleId, templateId, ownerId);
// 1. 获取模板
ObjectiveTemplateDO template = templateMapper.selectById(templateId);
if (template == null) {
throw new BusinessException("模板不存在");
}
// 2. 解析模板内容
List<ObjectiveTemplateItem> templateItems = JSON.parseArray(
template.getContent(), ObjectiveTemplateItem.class
);
if (templateItems.isEmpty()) {
throw new BusinessException("模板内容为空");
}
// 3. 创建主目标
ObjectiveTemplateItem mainItem = templateItems.get(0);
ObjectiveDO objective = new ObjectiveDO();
objective.setId(IdUtil.fastUUID());
objective.setCycleId(cycleId);
objective.setType(template.getType());
objective.setOwnerId(ownerId);
objective.setTitle(mainItem.getTitle());
objective.setDescription(mainItem.getDescription());
objective.setStatus(ObjectiveStatus.DRAFT);
objective.setPriority(ObjectivePriority.P2);
objective.setCreateTime(LocalDateTime.now());
// 应用自定义参数
applyCustomParams(objective, customParams);
objectiveMapper.insert(objective);
// 4. 创建关键结果
List<KeyResultDO> keyResults = new ArrayList<>();
for (int i = 1; i < templateItems.size(); i++) {
ObjectiveTemplateItem item = templateItems.get(i);
KeyResultDO keyResult = new KeyResultDO();
keyResult.setId(IdUtil.fastUUID());
keyResult.setObjectiveId(objective.getId());
keyResult.setTitle(item.getTitle());
keyResult.setDescription(item.getDescription());
keyResult.setTargetValue(new BigDecimal(item.getTargetValue()));
keyResult.setUnit(item.getUnit());
keyResult.setDataType(parseDataType(item.getDataType()));
keyResult.setDirection(parseDirection(item.getDirection()));
keyResult.setWeight(new BigDecimal(item.getWeight()));
keyResult.setStatus(KeyResultStatus.NOT_STARTED);
keyResult.setCreateTime(LocalDateTime.now());
keyResults.add(keyResult);
}
// 5. 批量保存关键结果
if (!keyResults.isEmpty()) {
keyResultMapper.insertBatch(keyResults);
}
// 6. 初始计算进度
progressCalculationEngine.calculateObjectiveProgress(objective.getId());
log.info("从模板创建目标完成: objectiveId={}, keyResultCount={}",
objective.getId(), keyResults.size());
return objective.getId();
}
/**
* 手动创建目标
*
* @param request 创建请求
* @return 创建的目标ID
*/
@Transactional(rollbackFor = Exception.class)
public String createManually(ObjectiveCreateRequest request) {
log.info("手动创建目标: ownerId={}, title={}",
request.getOwnerId(), request.getTitle());
// 1. 验证请求
validateCreateRequest(request);
// 2. 创建目标
ObjectiveDO objective = new ObjectiveDO();
objective.setId(IdUtil.fastUUID());
objective.setCycleId(request.getCycleId());
objective.setType(request.getType());
objective.setOwnerId(request.getOwnerId());
objective.setOwnerName(request.getOwnerName());
objective.setTitle(request.getTitle());
objective.setDescription(request.getDescription());
objective.setStatus(ObjectiveStatus.DRAFT);
objective.setPriority(request.getPriority());
objective.setCategory(request.getCategory());
objective.setStartDate(request.getStartDate());
objective.setEndDate(request.getEndDate());
objective.setIsPublic(request.getIsPublic());
objective.setCreatorId(SecurityContextHolder.getUserId());
objective.setCreateTime(LocalDateTime.now());
objectiveMapper.insert(objective);
// 3. 创建关键结果
if (request.getKeyResults() != null && !request.getKeyResults().isEmpty()) {
List<KeyResultDO> keyResults = new ArrayList<>();
for (KeyResultCreateRequest krRequest : request.getKeyResults()) {
KeyResultDO keyResult = new KeyResultDO();
keyResult.setId(IdUtil.fastUUID());
keyResult.setObjectiveId(objective.getId());
keyResult.setTitle(krRequest.getTitle());
keyResult.setDescription(krRequest.getDescription());
keyResult.setTargetValue(krRequest.getTargetValue());
keyResult.setUnit(krRequest.getUnit());
keyResult.setDataType(krRequest.getDataType());
keyResult.setDirection(krRequest.getDirection());
keyResult.setWeight(krRequest.getWeight());
keyResult.setStatus(KeyResultStatus.NOT_STARTED);
keyResult.setOwnerId(krRequest.getOwnerId());
keyResult.setOwnerName(krRequest.getOwnerName());
keyResult.setCreateTime(LocalDateTime.now());
keyResults.add(keyResult);
}
keyResultMapper.insertBatch(keyResults);
}
// 4. 发送通知给参与者和观察者
sendNotificationToParticipants(objective);
log.info("手动创建目标完成: objectiveId={}", objective.getId());
return objective.getId();
}
/**
* 应用自定义参数
*/
private void applyCustomParams(ObjectiveDO objective, Map<String, Object> customParams) {
if (customParams == null || customParams.isEmpty()) {
return;
}
// 应用标题自定义
if (customParams.containsKey("title")) {
String title = (String) customParams.get("title");
if (StringUtils.isNotBlank(title)) {
objective.setTitle(title);
}
}
// 应用描述自定义
if (customParams.containsKey("description")) {
String description = (String) customParams.get("description");
if (StringUtils.isNotBlank(description)) {
objective.setDescription(description);
}
}
// 应用优先级自定义
if (customParams.containsKey("priority")) {
String priorityStr = (String) customParams.get("priority");
if (StringUtils.isNotBlank(priorityStr)) {
objective.setPriority(ObjectivePriority.valueOf(priorityStr));
}
}
// 应用时间范围自定义
if (customParams.containsKey("startDate")) {
String startDateStr = (String) customParams.get("startDate");
if (StringUtils.isNotBlank(startDateStr)) {
objective.setStartDate(LocalDate.parse(startDateStr));
}
}
if (customParams.containsKey("endDate")) {
String endDateStr = (String) customParams.get("endDate");
if (StringUtils.isNotBlank(endDateStr)) {
objective.setEndDate(LocalDate.parse(endDateStr));
}
}
}
/**
* 发送通知给参与者和观察者
*/
private void sendNotificationToParticipants(ObjectiveDO objective) {
try {
// 构建通知消息
String message = String.format(
"【OKR通知】您被邀请参与目标 \"%s\",请及时查看并制定您的关键结果。",
objective.getTitle()
);
// 发送给参与者
if (StringUtils.isNotBlank(objective.getParticipantIds())) {
List<String> participantIds = JSON.parseArray(
objective.getParticipantIds(), String.class
);
for (String participantId : participantIds) {
notificationService.sendNotification(participantId, message);
}
}
// 发送给观察者
if (StringUtils.isNotBlank(objective.getWatcherIds())) {
List<String> watcherIds = JSON.parseArray(
objective.getWatcherIds(), String.class
);
for (String watcherId : watcherIds) {
notificationService.sendNotification(watcherId, message);
}
}
} catch (Exception e) {
log.error("发送通知失败: objectiveId={}, error={}",
objective.getId(), e.getMessage());
}
}
}
3.4 智能推荐引擎
OKR智能推荐服务:
/**
* OKR智能推荐引擎
* 基于历史数据和AI算法推荐关键结果目标和数值
*/
@Service
@Slf4j
public class OkrRecommendationEngine {
@Autowired
private ObjectiveMapper objectiveMapper;
@Autowired
private KeyResultMapper keyResultMapper;
/**
* 推荐关键结果目标值
*
* @param employeeId 员工ID
* @param historicalData 历史数据
* @return 推荐结果
*/
public RecommendationResult recommendTargetValue(
String employeeId,
Map<String, Object> historicalData
) {
log.info("推荐关键结果目标值: employeeId={}", employeeId);
RecommendationResult result = new RecommendationResult();
result.setEmployeeId(employeeId);
// 1. 分析历史数据
HistoricalPerformanceAnalysis analysis = analyzeHistoricalData(historicalData);
// 2. 应用推荐算法
BigDecimal recommendedValue = calculateRecommendedValue(analysis);
result.setRecommendedValue(recommendedValue);
result.setConfidence(calculateConfidence(analysis));
result.setReason(buildRecommendationReason(analysis));
log.info("推荐完成: employeeId={}, recommendedValue={}, confidence={}%",
employeeId, recommendedValue, result.getConfidence());
return result;
}
/**
* 分析历史绩效数据
*/
private HistoricalPerformanceAnalysis analyzeHistoricalData(
Map<String, Object> historicalData
) {
HistoricalPerformanceAnalysis analysis = new HistoricalPerformanceAnalysis();
// 1. 分析历史OKR完成率
List<BigDecimal> completionRates = (List<BigDecimal>)
historicalData.getOrDefault("completionRates", Collections.emptyList());
if (!completionRates.isEmpty()) {
BigDecimal avgRate = completionRates.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(completionRates.size()), 2, RoundingMode.HALF_UP);
analysis.setAverageCompletionRate(avgRate);
}
// 2. 分析绩效得分趋势
List<BigDecimal> performanceScores = (List<BigDecimal>)
historicalData.getOrDefault("performanceScores", Collections.emptyList());
if (!performanceScores.isEmpty()) {
BigDecimal latestScore = performanceScores.get(performanceScores.size() - 1);
analysis.setLatestPerformanceScore(latestScore);
// 计算趋势
BigDecimal trend = calculateTrend(performanceScores);
analysis.setTrend(trend);
}
// 3. 分析同级别员工表现
List<BigDecimal> peerScores = (List<BigDecimal>)
historicalData.getOrDefault("peerScores", Collections.emptyList());
if (!peerScores.isEmpty()) {
BigDecimal peerAvg = peerScores.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(peerScores.size()), 2, RoundingMode.HALF_UP);
analysis.setPeerAverageScore(peerAvg);
// 计算员工在同级中的位置
long betterThanCount = peerScores.stream()
.filter(score -> score.compareTo(latestScore) < 0)
.count();
double percentile = (double) betterThanCount / peerScores.size();
analysis.setPercentileRank(percentile);
}
return analysis;
}
/**
* 计算推荐值
*/
private BigDecimal calculateRecommendedValue(HistoricalPerformanceAnalysis analysis) {
// 基于历史表现计算推荐值
BigDecimal baseValue = analysis.getLatestPerformanceScore();
BigDecimal trend = analysis.getTrend();
// 如果趋势向上,增加目标
if (trend.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal increaseFactor = new BigDecimal("1.1"); // 增加10%
return baseValue.multiply(increaseFactor).setScale(2, RoundingMode.HALF_UP);
}
// 如果趋势向下,适当降低目标
if (trend.compareTo(BigDecimal.ZERO) < 0) {
BigDecimal decreaseFactor = new BigDecimal("0.95"); // 降低5%
return baseValue.multiply(decreaseFactor).setScale(2, RoundingMode.HALF_UP);
}
// 趋势平稳,保持目标
return baseValue;
}
/**
* 计算趋势
*/
private BigDecimal calculateTrend(List<BigDecimal> values) {
if (values.size() < 2) {
return BigDecimal.ZERO;
}
// 使用线性回归计算趋势
BigDecimal sumX = BigDecimal.ZERO;
BigDecimal sumY = BigDecimal.ZERO;
BigDecimal sumXY = BigDecimal.ZERO;
BigDecimal sumX2 = BigDecimal.ZERO;
int n = values.size();
for (int i = 0; i < n; i++) {
BigDecimal x = new BigDecimal(i);
BigDecimal y = values.get(i);
sumX = sumX.add(x);
sumY = sumY.add(y);
sumXY = sumXY.add(x.multiply(y));
sumX2 = sumX2.add(x.multiply(x));
}
BigDecimal denominator = n.multiply(sumX2).subtract(sumX.multiply(sumX));
if (denominator.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
BigDecimal slope = n.multiply(sumXY).subtract(sumX.multiply(sumY))
.divide(denominator, 4, RoundingMode.HALF_UP);
return slope;
}
/**
* 计算置信度
*/
private Double calculateConfidence(HistoricalPerformanceAnalysis analysis) {
double confidence = 0.5; // 基础置信度
// 历史数据越多,置信度越高
if (analysis.getAverageCompletionRate() != null) {
double rate = analysis.getAverageCompletionRate().doubleValue();
if (rate >= 0.8) {
confidence += 0.2; // 完成率高,增加置信度
}
}
// 趋势明显,增加置信度
if (analysis.getTrend().abs().compareTo(new BigDecimal("0.05")) > 0) {
confidence += 0.15;
}
// 数据稳定,增加置信度
if (analysis.getPercentileRank() != null) {
double rank = analysis.getPercentileRank();
if (rank >= 0.6) { // 前40%
confidence += 0.15;
}
}
return Math.min(confidence, 0.95); // 最高95%置信度
}
/**
* 构建推荐原因说明
*/
private String buildRecommendationReason(HistoricalPerformanceAnalysis analysis) {
List<String> reasons = new ArrayList<>();
if (analysis.getLatestPerformanceScore() != null) {
reasons.add(String.format("基于最新绩效得分%.2f",
analysis.getLatestPerformanceScore()));
}
if (analysis.getTrend().compareTo(BigDecimal.ZERO) > 0) {
reasons.add("考虑到近期表现上升趋势");
} else if (analysis.getTrend().compareTo(BigDecimal.ZERO) < 0) {
reasons.add("考虑到近期表现下降趋势");
}
if (analysis.getPeerAverageScore() != null) {
reasons.add(String.format("参考同级平均绩效%.2f",
analysis.getPeerAverageScore()));
}
if (analysis.getPercentileRank() != null) {
double rank = analysis.getPercentileRank() * 100;
reasons.add(String.format("历史排名位于前%.1f%%",
(1 - rank)));
}
return String.join(",", reasons);
}
}
/**
* 推荐结果
*/
@Data
@Builder
public class RecommendationResult {
private String employeeId;
private BigDecimal recommendedValue;
private Double confidence;
private String reason;
private List<String> factors;
}
/**
* 历史绩效分析
*/
@Data
public class HistoricalPerformanceAnalysis {
private BigDecimal averageCompletionRate;
private BigDecimal latestPerformanceScore;
private BigDecimal trend;
private BigDecimal peerAverageScore;
private Double percentileRank;
}
四、高级特性实现
4.1 目标树形结构可视化
目标树构建器:
/**
* OKR目标树形结构构建器
* 构建用于可视化展示的目标层级关系树
*/
@Service
@Slf4j
public class ObjectiveTreeBuilder {
@Autowired
private ObjectiveMapper objectiveMapper;
@Autowired
private KeyResultMapper keyResultMapper;
/**
* 构建目标树
*
* @param cycleId 周期ID
* @param rootObjectiveId 根目标ID(可选)
* @return 目标树
*/
public ObjectiveTree buildTree(String cycleId, String rootObjectiveId) {
log.info("构建目标树: cycleId={}, rootId={}", cycleId, rootObjectiveId);
// 1. 确定根节点
String actualRootId = StringUtils.isNotBlank(rootObjectiveId) ?
rootObjectiveId : findRootObjectiveId(cycleId);
// 2. 递归构建树
TreeNode root = buildTreeNode(actualRootId, true);
// 3. 构建目标树
ObjectiveTree tree = ObjectiveTree.builder()
.cycleId(cycleId)
.root(root)
.totalNodes(countNodes(root))
.totalDepth(calculateDepth(root))
.build();
log.info("目标树构建完成: nodes={}, depth={}",
tree.getTotalNodes(), tree.getTotalDepth());
return tree;
}
/**
* 查找根目标ID
*/
private String findRootObjectiveId(String cycleId) {
// 查找公司级目标作为根节点
ObjectiveDO companyObjective = objectiveMapper.selectOne(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
.eq(ObjectiveDO::getType, ObjectiveType.COMPANY)
.eq(ObjectiveDO::getStatus, ObjectiveStatus.ACTIVE)
.orderByDesc(ObjectiveDO::getCreateTime)
.last("LIMIT 1")
);
return companyObjective != null ? companyObjective.getId() : null;
}
/**
* 递归构建树节点
*/
private TreeNode buildTreeNode(String objectiveId, boolean isRoot) {
// 1. 获取目标信息
ObjectiveDO objective = objectiveMapper.selectById(objectiveId);
if (objective == null) {
return null;
}
// 2. 获取关键结果
List<KeyResultDO> keyResults = keyResultMapper.selectList(
new LambdaQueryWrapper<KeyResultDO>()
.eq(KeyResultDO::getObjectiveId, objectiveId)
);
// 3. 获取子目标
List<ObjectiveDO> childObjectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getParentObjectiveId, objectiveId)
.eq(ObjectiveDO::getStatus, ObjectiveStatus.ACTIVE)
.orderByAsc(ObjectiveDO::getPriority)
);
// 4. 构建节点
TreeNode.TreeNodeBuilder nodeBuilder = TreeNode.builder()
.id(objective.getId())
.type("OBJECTIVE")
.title(objective.getTitle())
.objectiveType(objective.getType().name())
.status(objective.getStatus().name())
.progress(objective.getProgress())
.priority(objective.getPriority().getCode())
.startDate(objective.getStartDate())
.endDate(objective.getEndDate())
.keyResults(convertToKeyResultNodes(keyResults))
.build();
// 5. 递归构建子节点
List<TreeNode> children = new ArrayList<>();
for (ObjectiveDO childObjective : childObjectives) {
TreeNode childNode = buildTreeNode(childObjective.getId(), false);
if (childNode != null) {
children.add(childNode);
}
}
nodeBuilder.children(children);
return nodeBuilder.build();
}
/**
* 转换关键结果为树节点
*/
private List<TreeNode.KeyResultNode> convertToKeyResultNodes(List<KeyResultDO> keyResults) {
return keyResults.stream()
.map(kr -> TreeNode.KeyResultNode.builder()
.id(kr.getId())
.title(kr.getTitle())
.currentValue(kr.getCurrentValue())
.targetValue(kr.getTargetValue())
.unit(kr.getUnit())
.progress(kr.getProgress())
.status(kr.getStatus().name())
.direction(kr.getDirection().name())
.weight(kr.getWeight())
.dueDate(kr.getDueDate())
.build())
.collect(Collectors.toList());
}
/**
* 计算树的深度
*/
private int calculateDepth(TreeNode root) {
if (root == null || root.getChildren().isEmpty()) {
return 1;
}
int maxChildDepth = 0;
for (TreeNode child : root.getChildren()) {
int childDepth = calculateDepth(child);
if (childDepth > maxChildDepth) {
maxChildDepth = childDepth;
}
}
return maxChildDepth + 1;
}
/**
* 计算树的节点数
*/
private int countNodes(TreeNode root) {
if (root == null) {
return 0;
}
int count = 1;
for (TreeNode child : root.getChildren()) {
count += countNodes(child);
}
return count;
}
}
/**
* 目标树
*/
@Data
@Builder
public class ObjectiveTree {
private String cycleId;
private TreeNode root;
private Integer totalNodes;
private Integer totalDepth;
private Map<String, Object> statistics;
}
/**
* 树节点
*/
@Data
@Builder
class TreeNode {
private String id;
private String type; // OBJECTIVE or KEY_RESULT
private String title;
private String objectiveType;
private String status;
private BigDecimal progress;
private Integer priority;
private LocalDate startDate;
private LocalDate endDate;
private List<KeyResultNode> keyResults;
private List<TreeNode> children;
@Data
@Builder
static class KeyResultNode {
private String id;
private String title;
private BigDecimal currentValue;
private BigDecimal targetValue;
private String unit;
private BigDecimal progress;
private String status;
private String direction;
private BigDecimal weight;
private LocalDate dueDate;
}
}
4.2 复盘分析引擎
OKR复盘分析服务:
/**
* OKR复盘分析引擎
* 对OKR周期结束后进行全面的复盘分析
*/
@Service
@Slf4j
public class OkrReviewAnalysisEngine {
@Autowired
private ObjectiveMapper objectiveMapper;
@Autowired
private KeyResultMapper keyResultMapper;
/**
* 生成复盘报告
*
* @param cycleId 周期ID
* @return 复盘报告
*/
public ReviewReport generateReviewReport(String cycleId) {
log.info("生成复盘报告: cycleId={}", cycleId);
// 1. 获取周期信息
OkrCycleDO cycle = okrCycleMapper.selectById(cycleId);
if (cycle == null) {
throw new BusinessException("周期不存在");
}
// 2. 构建报告框架
ReviewReport report = ReviewReport.builder()
.cycleId(cycleId)
.cycleName(cycle.getName())
.reportDate(LocalDateTime.now())
.build();
// 3. 整体达成分析
OverallAchievementAnalysis overallAnalysis = analyzeOverallAchievement(cycleId);
report.setOverallAnalysis(overallAnalysis);
// 4. 分层级达成分析
List<LevelAchievementAnalysis> levelAnalyses = analyzeLevelAchievement(cycleId);
report.setLevelAnalyses(levelAnalyses);
// 5. 关键结果分析
KeyResultAnalysis keyResultAnalysis = analyzeKeyResults(cycleId);
report.setKeyResultAnalysis(keyResultAnalysis);
// 6. 目标完成情况分析
CompletionAnalysis completionAnalysis = analyzeCompletion(cycleId);
report.setCompletionAnalysis(completionAnalysis);
// 7. 最佳实践识别
BestPracticesAnalysis bestPractices = identifyBestPractices(cycleId);
report.setBestPractices(bestPractices);
// 8. 问题与改进建议
IssuesAndRecommendations issues = identifyIssuesAndRecommendations(cycleId);
report.setIssuesAndRecommendations(issues);
// 9. 下周期建议
NextCycleRecommendation nextCycle = generateNextCycleRecommendation(cycleId);
report.setNextCycleRecommendation(nextCycle);
// 10. 保存报告
saveReviewReport(report);
log.info("复盘报告生成完成: cycleId={}", cycleId);
return report;
}
/**
* 整体达成分析
*/
private OverallAchievementAnalysis analyzeOverallAchievement(String cycleId) {
// 获取周期内所有目标
List<ObjectiveDO> objectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
);
if (objectives.isEmpty()) {
return OverallAchievementAnalysis.empty();
}
// 计算整体统计
long totalCount = objectives.size();
long completedCount = objectives.stream()
.filter(o -> ObjectiveStatus.COMPLETED.equals(o.getStatus()))
.count();
BigDecimal averageProgress = objectives.stream()
.map(ObjectiveDO::getProgress)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(totalCount), 2, RoundingMode.HALF_UP);
// 按优先级统计
Map<ObjectivePriority, Long> countByPriority = objectives.stream()
.collect(Collectors.groupingBy(ObjectiveDO::getPriority, Collectors.counting()));
// 按类型统计
Map<ObjectiveType, Long> countByType = objectives.stream()
.collect(Collectors.groupingBy(ObjectiveDO::getType, Collectors.counting()));
return OverallAchievementAnalysis.builder()
.totalObjectives((int) totalCount)
.completedObjectives((int) completedCount)
.completionRate(totalCount > 0 ?
new BigDecimal(completedCount).divide(new BigDecimal(totalCount), 2, RoundingMode.HALF_UP) :
BigDecimal.ZERO)
.averageProgress(averageProgress)
.countByPriority(countByPriority)
.countByType(countByType)
.build();
}
/**
* 分层级达成分析
*/
private List<LevelAchievementAnalysis> analyzeLevelAchievement(String cycleId) {
List<LevelAchievementAnalysis> analyses = new ArrayList<>();
// 1. 公司级分析
LevelAchievementAnalysis companyAnalysis = analyzeByLevel(cycleId, ObjectiveType.COMPANY);
if (companyAnalysis != null) {
analyses.add(companyAnalysis);
}
// 2. 部门级分析
List<ObjectiveDO> departmentObjectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
.eq(ObjectiveDO::getType, ObjectiveType.DEPARTMENT)
);
Map<String, List<ObjectiveDO>> groupedByDept = departmentObjectives.stream()
.collect(Collectors.groupingBy(ObjectiveDO::getOwnerId));
for (Map.Entry<String, List<ObjectiveDO>> entry : groupedByDept.entrySet()) {
LevelAchievementAnalysis deptAnalysis = analyzeByLevel(
cycleId, ObjectiveType.DEPARTMENT, entry.getKey()
);
if (deptAnalysis != null) {
analyses.add(deptAnalysis);
}
}
// 3. 团队级分析(类似处理)
// 4. 个人级分析(类似处理)
return analyses;
}
/**
* 按层级分析达成情况
*/
private LevelAchievementAnalysis analyzeByLevel(
String cycleId,
ObjectiveType type,
String ownerId
) {
List<ObjectiveDO> objectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
.eq(ObjectiveDO::getType, type)
.eq(ObjectiveDO::getOwnerId, ownerId)
);
if (objectives.isEmpty()) {
return null;
}
// 计算该层级达成情况
long totalCount = objectives.size();
long completedCount = objectives.stream()
.filter(o -> ObjectiveStatus.COMPLETED.equals(o.getStatus()))
.count();
BigDecimal averageProgress = objectives.stream()
.map(ObjectiveDO::getProgress)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(totalCount), 2, RoundingMode.HALF_UP);
return LevelAchievementAnalysis.builder()
.type(type)
.ownerId(ownerId)
.totalObjectives((int) totalCount)
.completedObjectives((int) completedCount)
.completionRate(totalCount > 0 ?
new BigDecimal(completedCount).divide(new BigDecimal(totalCount), 2, RoundingMode.HALF_UP) :
BigDecimal.ZERO)
.averageProgress(averageProgress)
.build();
}
/**
* 识别最佳实践
*/
private BestPracticesAnalysis identifyBestPractices(String cycleId) {
BestPracticesAnalysis analysis = new BestPracticesAnalysis();
// 1. 找出高达成率目标
List<ObjectiveDO> highAchievementObjectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
.eq(ObjectiveDO::getStatus, ObjectiveStatus.COMPLETED)
.ge(ObjectiveDO::getProgress, new BigDecimal("80"))
.orderByDesc(ObjectiveDO::getProgress)
.last("LIMIT 10")
);
List<BestPractice> bestPractices = highAchievementObjectives.stream()
.map(obj -> {
BestPractice bp = new BestPractice();
bp.setObjectiveTitle(obj.getTitle());
bp.setAchievementRate(obj.getProgress());
bp.setOwnerId(obj.getOwnerId());
bp.setOwnerName(obj.getOwnerName());
// 分析关键成功因素
List<KeyResultDO> keyResults = keyResultMapper.selectList(
new LambdaQueryWrapper<KeyResultDO>()
.eq(KeyResultDO::getObjectiveId, obj.getId())
);
List<String> successFactors = new ArrayList<>();
for (KeyResultDO kr : keyResults) {
if (kr.getProgress().compareTo(new BigDecimal("80")) >= 0) {
successFactors.add(kr.getTitle());
}
}
bp.setSuccessFactors(successFactors);
return bp;
})
.collect(Collectors.toList());
analysis.setTopAchievingObjectives(bestPractices);
// 2. 分析成功因素
Map<String, Long> factorCounts = new HashMap<>();
bestPractices.forEach(bp -> {
bp.getSuccessFactors().forEach(factor -> {
factorCounts.merge(factor, 1L, Long::sum);
});
});
List<SuccessFactor> rankedFactors = factorCounts.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.map(entry -> SuccessFactor.builder()
.factor(entry.getKey())
.count(entry.getValue())
.build())
.collect(Collectors.toList());
analysis.setCommonSuccessFactors(rankedFactors);
return analysis;
}
/**
* 识别问题与改进建议
*/
private IssuesAndRecommendations identifyIssuesAndRecommendations(String cycleId) {
IssuesAndRecommendations issues = new IssuesAndRecommendations();
// 1. 找出未达成目标
List<ObjectiveDO> notAchievedObjectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
.ne(ObjectiveDO::getStatus, ObjectiveStatus.COMPLETED)
.lt(ObjectiveDO::getProgress, new BigDecimal("80"))
);
List<Issue> issueList = notAchievedObjectives.stream()
.map(obj -> Issue.builder()
.objectiveId(obj.getId())
.objectiveTitle(obj.getTitle())
.type(obj.getType().name())
.progress(obj.getProgress())
.ownerId(obj.getOwnerId())
.ownerName(obj.getOwnerName())
.rootCause(analyzeRootCause(obj))
.recommendation(generateRecommendation(obj))
.build())
.collect(Collectors.toList());
issues.setIssues(issueList);
// 2. 汇总问题类型
Map<String, Long> issueTypes = issueList.stream()
.collect(Collectors.groupingBy(
issue -> issue.getType() != null ? issue.getType() : "OTHER",
Collectors.counting()
));
issues.setIssueTypeSummary(issueTypes);
// 3. 生成改进建议
List<Recommendation> recommendations = generateRecommendations(issueList);
issues.setRecommendations(recommendations);
return issues;
}
/**
* 分析根本原因
*/
private String analyzeRootCause(ObjectiveDO objective) {
// 获取关键结果完成情况
List<KeyResultDO> keyResults = keyResultMapper.selectList(
new LambdaQueryWrapper<KeyResultDO>()
.eq(KeyResultDO::getObjectiveId, objective.getId())
);
long notStartedCount = keyResults.stream()
.filter(kr -> KeyResultStatus.NOT_STARTED.equals(kr.getStatus()))
.count();
long atRiskCount = keyResults.stream()
.filter(kr -> KeyResultStatus.AT_RISK.equals(kr.getStatus()))
.count();
if (notStartedCount > 0) {
return "关键结果尚未启动";
}
if (atRiskCount > 0) {
return "关键结果存在风险";
}
return "进度缓慢";
}
/**
* 生成改进建议
*/
private String generateRecommendation(ObjectiveDO objective) {
return "建议:定期检视关键结果进度,及时调整策略,加强资源投入";
}
}
4.3 数据同步接口
外部数据同步服务:
/**
* OKR数据同步服务
* 从外部系统同步数据作为关键结果值
*/
@Service
@Slf4j
public class OkrDataSyncService {
@Autowired
private KeyResultMapper keyResultMapper;
@Autowired
private ObjectiveMapper objectiveMapper;
/**
* 同步关键结果数据
*
* @param keyResultId 关键结果ID
* @return 同步结果
*/
@Transactional(rollbackFor = Exception.class)
public SyncResult syncKeyResultData(String keyResultId) {
log.info("同步关键结果数据: keyResultId={}", keyResultId);
// 1. 获取关键结果信息
KeyResultDO keyResult = keyResultMapper.selectById(keyResultId);
if (keyResult == null) {
throw new BusinessException("关键结果不存在");
}
// 2. 获取数据源配置
String dataSourceConfig = keyResult.getDataSourceConfig();
if (StringUtils.isBlank(dataSourceConfig)) {
throw new BusinessException("未配置数据源");
}
try {
// 3. 解析数据源配置
DataSourceConfig config = JSON.parseObject(
dataSourceConfig, DataSourceConfig.class
);
// 4. 根据数据源类型同步数据
BigDecimal syncedValue = syncFromDataSource(config);
// 5. 更新关键结果值
keyResult.setCurrentValue(syncedValue);
keyResult.setLastSyncTime(LocalDateTime.now());
keyResultMapper.updateById(keyResult);
// 6. 重新计算目标进度
progressCalculationEngine.calculateObjectiveProgress(
keyResult.getObjectiveId()
);
log.info("关键结果数据同步完成: keyResultId={}, value={}",
keyResultId, syncedValue);
return SyncResult.success(keyResultId, syncedValue);
} catch (Exception e) {
log.error("同步关键结果数据失败: keyResultId={}, error={}",
keyResultId, e.getMessage());
// 记录同步失败日志
recordSyncFailure(keyResultId, e.getMessage());
return SyncResult.failure(keyResultId, e.getMessage());
}
}
/**
* 从数据源同步数据
*/
private BigDecimal syncFromDataSource(DataSourceConfig config) throws Exception {
switch (config.getType()) {
case "API":
return syncFromApi(config);
case "DATABASE":
return syncFromDatabase(config);
case "MANUAL":
return syncFromManual(config);
default:
throw new BusinessException("不支持的数据源类型: " + config.getType());
}
}
/**
* 从API同步数据
*/
private BigDecimal syncFromApi(DataSourceConfig config) throws Exception {
// 构建API请求
String apiUrl = config.getUrl();
Map<String, String> headers = config.getHeaders();
Map<String, Object> params = config.getParams();
// 发送HTTP请求
ApiResponse response = httpClient.get(apiUrl)
.headers(headers)
.queryParams(params)
.execute();
if (!response.isSuccess()) {
throw new BusinessException("API调用失败: " + response.getStatusText());
}
// 解析响应数据
JSONObject jsonResponse = JSON.parseObject(response.body());
return jsonResponse.getBigDecimal(config.getValueField());
}
/**
* 从数据库同步数据
*/
private BigDecimal syncFromDatabase(DataSourceConfig config) throws Exception {
// 构建SQL查询
String sql = config.getSql();
Map<String, Object> params = config.getParams();
// 执行查询
List<Map<String, Object>> results = jdbcTemplate.queryForList(sql, params);
if (results.isEmpty()) {
return BigDecimal.ZERO;
}
// 提取值
Object value = results.get(0).get(config.getValueField());
if (value == null) {
return BigDecimal.ZERO;
}
if (value instanceof Number) {
return new BigDecimal(value.toString());
}
return new BigDecimal(value.toString());
}
/**
* 批量同步周期数据
*
* @param cycleId 周期ID
*/
public void batchSyncCycleData(String cycleId) {
log.info("批量同步周期数据: cycleId={}", cycleId);
// 1. 获取需要同步的关键结果
List<KeyResultDO> keyResultsToSync = keyResultMapper.selectList(
new LambdaQueryWrapper<KeyResultDO>()
.eq(KeyResultDO::getObjectiveId,
objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
).stream()
.map(ObjectiveDO::getId)
.collect(Collectors.toList())
)
.isNotNull(KeyResultDO::getDataSourceConfig)
.isNotNull(KeyResultDO::getDataSourceConfig)
);
log.info("找到需要同步的关键结果: count={}", keyResultsToSync.size());
// 2. 批量同步
for (KeyResultDO keyResult : keyResultsToSync) {
try {
syncKeyResultData(keyResult.getId());
} catch (Exception e) {
log.error("同步失败: keyResultId={}, error={}",
keyResult.getId(), e.getMessage());
}
}
log.info("批量同步完成: cycleId={}", cycleId);
}
}
/**
* 数据源配置
*/
@Data
class DataSourceConfig {
private String type; // API, DATABASE, MANUAL
private String url;
private Map<String, String> headers;
private Map<String, Object> params;
private String sql;
private String valueField;
}
五、前端实现
5.1 OKR目标管理页面
目标列表页面(Vue3):
<template>
<div class="okr-objective-list">
<!-- 周期选择器 -->
<div class="cycle-selector">
<el-select v-model="currentCycleId" @change="handleCycleChange" placeholder="选择周期">
<el-option
v-for="cycle in cycles"
:key="cycle.id"
:label="cycle.name"
:value="cycle.id">
</el-option>
</el-select>
<el-button type="primary" @click="createObjective" icon="Plus">
创建目标
</el-button>
<el-button @click="batchAutoAlignment" :loading="aligning">
批量对齐
</el-button>
</div>
<!-- 目标树状图 -->
<div class="objective-tree" v-loading="treeLoading">
<el-tree
:data="objectiveTreeData"
:props="treeProps"
:expand-on-click-node="false"
:default-expand-all="false"
node-key="id"
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<div class="tree-node">
<div class="node-header">
<el-icon>
<component :is="getNodeIcon(node)" />
</el-icon>
<span class="node-title">{{ node.title }}</span>
<el-tag
:type="getStatusType(node.status)"
size="small"
effect="plain"
>
{{ getStatusText(node.status) }}
</el-tag>
<span class="node-progress">{{ node.progress }}%</span>
</div>
<!-- 关键结果列表 -->
<div class="key-results" v-if="node.keyResults">
<el-progress
v-for="kr in node.keyResults"
:key="kr.id"
:percentage="kr.progress"
:format="getProgressFormat"
:color="getProgressColor(kr.progress)"
>
<span class="kr-title">{{ kr.title }}</span>
<span class="kr-value">
{{ formatValue(kr.currentValue, kr.unit) }} /
{{ formatValue(kr.targetValue, kr.unit) }}
</span>
</el-progress>
</div>
</div>
</template>
</el-tree>
</div>
<!-- 目标详情抽屉 -->
<el-drawer
v-model="detailDrawerVisible"
:title="selectedObjective?.title"
size="60%"
direction="rtl"
>
<ObjectiveDetail
v-if="selectedObjective"
:objective-id="selectedObjective.id"
@refresh="loadObjectiveTree"
/>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
import {
getObjectiveTree,
createObjective,
batchAutoAlignment,
getCycles
} from '@/api/hrm/okr'
import ObjectiveDetail from './components/ObjectiveDetail.vue'
const router = useRouter()
// 状态定义
const currentCycleId = ref('')
const cycles = ref([])
const objectiveTreeData = ref([])
const treeLoading = ref(false)
const detailDrawerVisible = ref(false)
const selectedObjective = ref(null)
const aligning = ref(false)
// 树形结构配置
const treeProps = {
children: 'children',
label: 'title',
disabled: 'disabled'
}
// 周期变更
const handleCycleChange = () => {
loadObjectiveTree()
}
// 创建目标
const createObjective = () => {
router.push('/okr/objective/create', {
cycleId: currentCycleId.value
})
}
// 批量对齐
const batchAutoAlignment = async () => {
const loading = ElLoading.service({
lock: true,
text: '正在批量对齐...'
})
try {
await batchAutoAlignment({ cycleId: currentCycleId.value })
ElMessage.success('批量对齐完成')
loadObjectiveTree()
} finally {
loading.close()
}
}
// 加载目标树
const loadObjectiveTree = async () => {
if (!currentCycleId.value) {
return
}
treeLoading.value = true
try {
const { data } = await getObjectiveTree({
cycleId: currentCycleId.value
})
objectiveTreeData.value = [data.root]
} finally {
treeLoading.value = false
}
}
// 节点点击
const handleNodeClick = (node, data) => {
selectedObjective.value = node
detailDrawerVisible.value = true
}
// 辅助函数
const getNodeIcon = (node) => {
if (node.type === 'OBJECTIVE') {
return 'Objective'
} else {
return 'KeyResult'
}
}
const getStatusType = (status) => {
switch (status) {
case 'COMPLETED':
return 'success'
case 'ACTIVE':
return 'primary'
case 'PAUSED':
return 'warning'
case 'CANCELLED':
return 'danger'
default:
return 'info'
}
}
const getStatusText = (status) => {
const statusMap = {
'COMPLETED': '已完成',
'LANGUAGE': '进行中',
'PAUSED': '已暂停',
'CANCELLED': '已取消',
'DRAFT': '草稿'
}
return statusMap[status] || '未知'
}
const formatValue = (value, unit) => {
if (unit === 'PERCENTAGE') {
return `${value}%`
}
return `${value} ${unit}`
}
const getProgressFormat = (percentage) => {
return `${percentage}%`
}
const getProgressColor = (progress) => {
if (progress >= 100) {
return '#67c23a'
} else if (progress >= 80) {
return '#e6a23c'
} else if (progress >= 50) {
return '#e5e5e5'
} else if (progress >= 20) {
return '#f56c6c'
} else {
return '#909399'
}
}
// 初始化
onMounted(() => {
loadCycles()
// 加载当前周期
const currentCycle = cycles.value.find(c => c.status === 'ACTIVE')
if (currentCycle) {
currentCycleId.value = currentCycle.id
loadObjectiveTree()
}
})
// 加载周期列表
const loadCycles = async () => {
const { data } = await getCycles()
cycles.value = data || []
}
</script>
<style lang="scss" scoped>
.okr-objective-list {
padding: 20px;
.cycle-selector {
margin-bottom: 20px;
display: flex;
gap: 12px;
align-items: center;
.el-select {
width: 300px;
}
}
.objective-tree {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 20px;
min-height: 400px;
.tree-node {
width: 100%;
.node-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
.node-title {
font-weight: 500;
flex: 1;
}
.node-progress {
font-size: 12px;
color: #909399;
}
}
.key-results {
margin-top: 12px;
padding-left: 24px;
.kr-title {
font-size: 12px;
color: #606266;
margin-bottom: 4px;
}
.kr-value {
font-size: 12px;
color: #909399;
}
}
}
}
}
</style>
5.2 关键结果编辑器
关键结果编辑器(Vue3):
<template>
<div class="key-result-editor">
<!-- 进度条显示 -->
<div class="progress-display">
<el-progress
:percentage="calculatedProgress"
:format="progressFormat"
:color="getProgressColor"
/>
</div>
<!-- 数据源配置 -->
<div class="datasource-config" v-if="keyResult.dataSourceType !== 'MANUAL'">
<div class="config-header">
<span>数据源配置</span>
<el-button
type="primary"
size="small"
@click="syncNow"
:loading="syncing"
>
立即同步
</el-button>
</div>
<el-descriptions :column="1" border>
<el-descriptions-item label="数据源类型">
{{ getDataSourceTypeText() }}
</el-descriptions-item>
<el-descriptions-item label="最后同步时间">
{{ keyResult.lastSyncTime || '未同步' }}
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 快速设置进度 -->
<div class="quick-set">
<div class="set-item">
<span>快速设置进度</span>
<el-slider
v-model="quickProgress"
:marks="progressMarks"
:step="10"
show-stops
@change="handleQuickProgressChange"
/>
</div>
</div>
<!-- 手动录入 -->
<div class="manual-input">
<el-form :model="formData" :rules="formRules" ref="formRef" label-width="120px">
<el-form-item label="当前值" prop="currentValue">
<el-input-number
v-model="formData.currentValue"
:precision="2"
:controls="true"
@change="handleManualInput"
/>
<span class="unit-label">{{ keyResult.unit }}</span>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { updateKeyResult, syncKeyResultData } from '@/api/hrm/okr'
const props = defineProps({
keyResultId: {
type: String,
required: true
}
})
const emit = defineEmits(['update', 'sync'])
// 状态
const keyResult = ref({})
const syncing = ref(false)
const calculatedProgress = ref(0)
const quickProgress = ref(0)
// 表单数据
const formData = ref({
currentValue: 0,
targetValue: 0
})
const formRules = {
currentValue: [
{ required: true, message: '请输入当前值', trigger: 'blur' }
]
}
// 计算属性
const progressMarks = [0, 25, 50, 75, 100]
// 方法
const calculateProgress = () => {
if (!keyResult.value.targetValue || keyResult.value.targetValue === 0) {
calculatedProgress.value = 0
return
}
let progress = 0
switch (keyResult.value.direction) {
case 'INCREASE':
// 增长型:当前值 / 目标值
progress = keyResult.value.currentValue.divide(
keyResult.value.targetValue,
2,
MathContext.HALF_UP
).multiply(100)
break
case 'DECREASE':
// 下降型:1 - (当前值 - 目标值) / 目标值
progress = BigDecimal.ONE.subtract(
keyResult.value.currentValue.subtract(keyResult.value.targetValue)
.abs()
.divide(keyResult.value.targetValue, 2, MathContext.HALF_UP)
).multiply(100)
break
case 'MAINTAIN':
// 维持型:计算偏差
const deviation = keyResult.value.currentValue
.subtract(keyResult.value.targetValue)
.abs()
const tolerance = keyResult.value.targetValue.multiply(new BigDecimal('0.1'))
if (deviation.compareTo(tolerance) <= 0) {
progress = 100
} else {
progress = 100 - deviation
.multiply(new BigDecimal('100'))
.divide(keyResult.value.targetValue, 2, MathContext.HALF_UP)
.intValue()
}
break
}
calculatedProgress.value = progress.min(100).intValue(0)
}
const handleManualInput = () => {
emit('update', {
keyResultId: props.keyResultId,
currentValue: formData.value.currentValue
})
}
const syncNow = async () => {
syncing.value = true
try {
await syncKeyResultData(props.keyResultId)
ElMessage.success('同步成功')
emit('sync')
} finally {
syncing.value = false
}
}
const getDataSourceTypeText = () => {
const typeMap = {
'MANUAL': '手动录入',
'SYSTEM': '系统同步',
'API': '外部接口'
}
return typeMap[keyResult.value.dataSourceType] || '未知'
}
// 监听数据变化
watch(() => props.keyResultId, () => {
loadKeyResult()
})
// 初始化
onMounted(() => {
loadKeyResult()
})
// 加载关键结果数据
const loadKeyResult = async () => {
// 从API获取关键结果详情
const { data } = await getKeyResultDetail(props.keyResultId)
keyResult.value = data
formData.value.currentValue = data.currentValue
calculateProgress()
}
</script>
六、最佳实践
6.1 OKR制定最佳实践
1. 目标设定原则(SMART原则增强版)
✅ Specific(具体明确)
- 目标描述清晰,避免模糊表述
- 明确业务范围和边界条件
- 示例:❌ "提升用户体验"
✅ "将移动端页面加载时间从3s优化到1.5s"
✅ Measurable(可衡量)
- 关键结果可量化
- 有明确的数据来源
- 示例:❌ "提高销售额"
✅ "Q4销售额同比增长30%,达到5000万"
✅ Achievable(可达成)
- 目标具有挑战性但可实现
- 考虑资源限制和外部因素
- 建议:设定达成率70-80%的目标
✅ Relevant(相关联)
- 与团队目标和公司战略对齐
- 与岗位职责高度相关
- 支持上级目标的实现
✅ Time-bound(有时限)
- 明确的截止日期
- 分阶段里程碑
- 定期检视机制
2. 关键结果设计原则
✅ 关键结果数量:每个目标3-5个关键结果
- 太少:无法全面衡量目标
- 太多:分散精力,难以聚焦
✅ 权重分配:关键结果总权重应为100%
- 根据重要性分配权重
- 避免权重过于平均
✅ 进度方向明确
- 增长型:值越大越好(如DAU、GMV)
- 下降型:值越小越好(如成本、投诉率)
- 维持型:保持在目标值附近
✅ 数据来源可靠
- 优先使用系统自动同步
- 手动录入需注明数据来源
- 定期验证数据准确性
6.2 团队对齐最佳实践
1. 对齐会议流程
/**
* OKR对齐会议流程
*/
public class AlignmentMeetingProcess {
/**
* 步骤1:公司目标发布(季度初)
* - CEO发布公司级OKR
* - 全员参加目标发布会
* - 各部门负责人理解公司战略
*/
void publishCompanyObjectives(String cycleId);
/**
* 步骤2:部门目标对齐会议(季度第一周)
* - 部门负责人传达公司目标
* - 团队负责人讨论如何支撑部门目标
* - 确定部门OKR和团队OKR
*/
void alignDepartmentObjectives(String cycleId);
/**
* 步骤3:团队目标对齐会议(季度第一周)
* - 团队负责人传达部门目标
* - 个人讨论如何支撑团队目标
* - 确定个人OKR
*/
void alignTeamObjectives(String cycleId);
/**
* 步骤4:目标公示(季度第二周)
* - 所有OKR公开透明
- 定期更新进度
- 鼓励跨部门协作
*/
void publishObjectives(String cycleId);
/**
* 步骤5:定期检视(月度)
* - 月度OKR检视会议
- 识别风险和障碍
- 及时调整策略
*/
void reviewProgress(String cycleId);
}
2. 对齐关系可视化
┌────────────────────────────────────────────────────────┐
│ 公司级OKR │
│ "Q1实现GMV 1亿" │
└────────────┬───────────────────────┬─────────────────────┘
│ │
┌────────────┴──────────────┐ ┌──────────────────┴──────────────┐
│ 研发部OKR │ │ 市场部OKR │
│ "Q1提升DAU 50%" │ │ "Q1新增客户1000" │
└────────────┬──────────────┘ └──────────┬──────────────┘
│ │
┌────────────┴──────────┐ ┌──────────┴──────────────┐
│ 前端团队OKR │ │ 后端团队OKR │
│ "Q1降低首屏延迟" │ │ "Q1提升API性能" │
└────────────┬──────────┘ └──────────┬──────────────┘
│ │
┌────────────┴───────────────────────────┴─────────────┐
│ 前端开发个人OKR │
│ "Q1优化首页加载速度" │
└───────────────────────────────────────────────────────┘
6.3 进度跟踪最佳实践
1. 进度更新频率
✅ 推荐频率:
- 关键结果:每周更新一次
- 目标整体进度:每月更新一次
- 接近截止日期:每周更新一次
- 发生重大变化时:立即更新
❌ 避免的频率:
- 每天更新:过于频繁,增加负担
- 仅期末更新:无法及时发现风险
2. 进度异常预警
/**
* 进度异常预警规则
*/
@Component
public class ProgressAlertEngine {
/**
* 检查并预警异常情况
*/
public void checkAndAlert(String cycleId) {
// 1. 查找所有活跃目标
List<ObjectiveDO> activeObjectives = objectiveMapper.selectList(
new LambdaQueryWrapper<ObjectiveDO>()
.eq(ObjectiveDO::getCycleId, cycleId)
.eq(ObjectiveDO::getStatus, ObjectiveStatus.ACTIVE)
);
LocalDate now = LocalDate.now();
for (ObjectiveDO objective : activeObjectives) {
// 2. 检查关键结果风险
List<KeyResultDO> atRiskKRs = findAtRiskKeyResults(objective.getId());
if (!atRiskKRs.isEmpty()) {
sendAlert(objective, atRiskKRs);
}
// 3. 检查是否接近截止日期
LocalDate endDate = objective.getEndDate();
if (endDate != null) {
int daysUntilDue = (int) ChronoUnit.DAYS.between(now, endDate);
if (daysUntilDue <= 7 && daysUntilDue >= 0) {
sendDueDateReminder(objective, daysUntilDue);
}
}
}
}
/**
* 发送风险预警
*/
private void sendAlert(ObjectiveDO objective, List<KeyResultDO> atRiskKRs) {
String message = String.format(
"【OKR预警】目标「%s」存在风险,%d个关键结果进度落后",
objective.getTitle(),
atRiskKRs.size()
);
// 发送给目标负责人
if (StringUtils.isNotBlank(objective.getOwnerId())) {
notificationService.sendAlert(
objective.getOwnerId(),
message
);
}
// 发送给观察者
if (StringUtils.isNotBlank(objective.getWatcherIds())) {
List<String> watcherIds = JSON.parseArray(
objective.getWatcherIds(), String.class
);
for (String watcherId : watcherIds) {
notificationService.sendAlert(watcherId, message);
}
}
}
}
七、总结
本文详细介绍了OKR目标管理系统设计与实现,主要内容包括:
核心技术点
- 目标对齐算法:多层目标的自动对齐和联动机制
- 进度计算引擎:加权计算目标整体进度和关键结果进度
- 智能推荐引擎:基于历史数据和算法推荐目标值
- 复盘分析引擎:全面的周期复盘分析和问题识别
- 数据同步机制:支持API、数据库、手动等多种数据源
- 可视化展示:目标树形结构和进度仪表板
业务价值
- 战略对齐:确保个人目标与团队、部门、公司战略高度对齐
- 聚焦重点:明确优先级,集中精力在重要事项上
- 透明协作:全员可见,促进跨部门协作
- 持续改进:定期检视和调整,保持敏捷性
- 内驱激发:自主设定目标,增强员工参与感
扩展方向
- AI智能推荐:使用机器学习算法优化目标值推荐
- 协作功能:支持目标协作者和跨部门项目OKR
- OKR模板库:提供行业标准的OKR模板
- 预测分析:预测目标达成概率和风险
- 移动端优化:提供便捷的移动端OKR管理
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)