tuqin
2026-10-28 f4af3fbde83f305f4f1c96154aaa95944ecd8276
feat(hrm): 新增「我的薪资」,员工可自助查看本人工资条

背景:系统原先只有薪酬核算/薪资发放两个管理端页面,均需 hrm:salary:query
权限。普通员工既看不到菜单,也没有任何只看自己的接口,工资完全不可自助查询。

改动:
- Controller 新增两个接口,均不校验薪酬查询权限,登录即可访问:
* GET /hrm/salary/calculation/my-page 我的薪资分页
* GET /hrm/salary/calculation/my-get 我的薪资详情
- Service 新增对应方法:my-page 强制把查询条件中的 userId 覆盖为登录人、
并清空 deptId;my-get 校验记录归属,非本人记录一律返回 null
- 新增菜单脚本 sql/mysql/hrm_my_payslip_init.sql:顶级菜单「我的薪资」
(id=1065900),授权给所有角色,可重复执行

安全设计(已实测):
- 不传 userId 也能保证只查本人;传了也会被服务端覆盖,无法越权
- my-get 越权查他人 id 统一返回记录不存在,不泄露记录是否存在
- 原有 /page /get 管理端接口保持 hrm:salary:query 权限不变

联调结果(普通角色 ly101 / ly102):
- ly101 登录 => my-page total=1(17858, userId=234)
- ly102 登录 => my-page total=1(13418, userId=235)
- ly102 查他人记录 => 返回薪酬核算记录不存在
- 普通角色访问 /page => 403 没有该操作权限
已添加1个文件
已修改3个文件
111 ■■■■■ 文件已修改
sql/mysql/hrm_my_payslip_init.sql 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_my_payslip_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
-- =====================================================================
-- æˆ‘的薪资(员工自助查看工资条)初始化脚本
--
-- èƒŒæ™¯ï¼šç³»ç»ŸåŽŸå…ˆåªæœ‰ã€Œè–ªé…¬æ ¸ç®— / è–ªèµ„发放」两个管理端页面,且需 hrm:salary:query
--       æƒé™æ‰èƒ½è®¿é—®ï¼Œæ™®é€šå‘˜å·¥çœ‹ä¸åˆ°è‡ªå·±çš„工资。本脚本新增一个对所有人可见的
--       ã€Œæˆ‘的薪资」菜单,后端对应 /hrm/salary/calculation/my-page æŽ¥å£ï¼Œ
--       è¯¥æŽ¥å£ä¸æ ¡éªŒè–ªé…¬æŸ¥è¯¢æƒé™ï¼Œç”±æœåŠ¡ç«¯å¼ºåˆ¶æŒ‰ç™»å½•äººè¿‡æ»¤ï¼Œåªèƒ½çœ‹åˆ°æœ¬äººè®°å½•ã€‚
--
-- ç‰¹æ€§ï¼šå¯é‡å¤æ‰§è¡Œï¼ˆå…ˆæŒ‰ä¸»é”®åˆ é™¤å†æ’入)
-- =====================================================================
-- ---------------------------------------------------------------------
-- 1. èœå•:我的薪资(顶级菜单,对所有登录用户可见)
-- ---------------------------------------------------------------------
DELETE FROM system_role_menu WHERE menu_id = 1065900;
DELETE FROM system_menu WHERE id = 1065900;
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon,
                         component, component_name, status, visible, keep_alive, always_show,
                         creator, create_time, updater, update_time, deleted)
VALUES (1065900, '我的薪资', '', 2, 60, 0, '/my-payslip', 'ep:money',
        'hrm/salary/my-payslip/index', 'MyPayslip', 0, b'1', b'1', b'1',
        '1', NOW(), '1', NOW(), b'0');
-- ---------------------------------------------------------------------
-- 2. æŽˆæƒç»™æ‰€æœ‰è§’色(含后续新增角色需手动授权,或在角色管理中勾选)
-- ---------------------------------------------------------------------
INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
SELECT r.id, 1065900, '1', NOW(), '1', NOW(), b'0'
FROM system_role r
WHERE r.deleted = b'0';
-- ---------------------------------------------------------------------
-- 3. éªŒè¯
-- ---------------------------------------------------------------------
SELECT m.id, m.name, m.path, m.component, m.type
FROM system_menu m
WHERE m.id = 1065900;
SELECT r.id AS role_id, r.name AS role_name
FROM system_role r
JOIN system_role_menu rm ON rm.role_id = r.id AND rm.menu_id = 1065900
WHERE r.deleted = b'0'
ORDER BY r.id;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryCalculationController.java
@@ -16,6 +16,7 @@
import cn.iocoder.yudao.module.hrm.service.salary.HrmSalaryCalculationService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -31,7 +32,9 @@
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SALARY_CALCULATION_NOT_EXISTS;
@Tag(name = "管理后台 - HRM è–ªé…¬æ ¸ç®—")
@RestController
@@ -46,6 +49,29 @@
    @Resource
    private DeptApi deptApi;
    @GetMapping("/my-page")
    @Operation(summary = "获得我的薪资核算分页")
    public CommonResult<PageResult<HrmSalaryCalculationRespVO>> getMySalaryCalculationPage(@Valid HrmSalaryCalculationPageReqVO pageReqVO) {
        // åªæŸ¥å½“前登录人自己的薪资,无需薪酬查询权限,登录即可访问
        PageResult<HrmSalaryCalculationDO> pageResult = salaryCalculationService
                .getMySalaryCalculationPage(SecurityFrameworkUtils.getLoginUserId(), pageReqVO);
        List<HrmSalaryCalculationRespVO> list = BeanUtils.toBean(pageResult.getList(), HrmSalaryCalculationRespVO.class);
        return success(new PageResult<>(list, pageResult.getTotal()));
    }
    @GetMapping("/my-get")
    @Operation(summary = "获得我的薪资核算")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    public CommonResult<HrmSalaryCalculationRespVO> getMySalaryCalculation(@RequestParam("id") Long id) {
        HrmSalaryCalculationDO calculation = salaryCalculationService
                .getMySalaryCalculation(id, SecurityFrameworkUtils.getLoginUserId());
        // è®°å½•不存在、或不属于当前登录人时统一返回“不存在”,避免泄露他人薪资是否存在
        if (calculation == null) {
            throw exception(SALARY_CALCULATION_NOT_EXISTS);
        }
        return success(BeanUtils.toBean(calculation, HrmSalaryCalculationRespVO.class));
    }
    @GetMapping("/page")
    @Operation(summary = "获得薪酬核算分页")
    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationService.java
@@ -19,6 +19,25 @@
    PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO);
    /**
     * èŽ·å¾—ã€Œæˆ‘çš„ã€è–ªé…¬æ ¸ç®—åˆ†é¡µ
     *
     * åªè¿”回当前登录人自己的薪资记录,忽略请求参数中的 userId、deptId,避免越权查看他人工资
     *
     * @param userId     å½“前登录人ID
     * @param pageReqVO  æŸ¥è¯¢æ¡ä»¶ï¼ˆä»… period、status ç­‰ç”Ÿæ•ˆï¼‰
     */
    PageResult<HrmSalaryCalculationDO> getMySalaryCalculationPage(Long userId, HrmSalaryCalculationPageReqVO pageReqVO);
    /**
     * èŽ·å¾—ã€Œæˆ‘çš„ã€è–ªé…¬æ ¸ç®—
     *
     * @param id     æ ¸ç®—ID
     * @param userId å½“前登录人ID
     * @return æ ¸ç®—记录;记录不存在或不属于该用户时返回 null
     */
    HrmSalaryCalculationDO getMySalaryCalculation(Long id, Long userId);
    /**
     * èŽ·å¾—è–ªé…¬æ ¸ç®—
     */
    HrmSalaryCalculationDO getSalaryCalculation(Long id);
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -114,6 +114,27 @@
    }
    @Override
    public PageResult<HrmSalaryCalculationDO> getMySalaryCalculationPage(Long userId, HrmSalaryCalculationPageReqVO pageReqVO) {
        // å¼ºåˆ¶é™å®šä¸ºå½“前登录人:忽略前端传入的 userId / deptId,防止越权查看他人工资
        pageReqVO.setUserId(userId);
        pageReqVO.setDeptId(null);
        return salaryCalculationMapper.selectPage(pageReqVO);
    }
    @Override
    public HrmSalaryCalculationDO getMySalaryCalculation(Long id, Long userId) {
        HrmSalaryCalculationDO calculation = salaryCalculationMapper.selectById(id);
        if (calculation == null) {
            return null;
        }
        // ä¸å±žäºŽæœ¬äººçš„记录一律视为不存在,避免通过猜 id è¶ŠæƒæŸ¥çœ‹ä»–人工资
        if (!java.util.Objects.equals(calculation.getUserId(), userId)) {
            return null;
        }
        return calculation;
    }
    @Override
    public HrmSalaryCalculationDO getSalaryCalculation(Long id) {
        return salaryCalculationMapper.selectById(id);
    }