develop: 开发员工管理模块(端到端验证)

This commit is contained in:
Pipeline Agent 2026-08-14 11:43:58 +08:00
parent 6e03322ef0
commit 1793685601
64 changed files with 388 additions and 8 deletions

View File

@ -32,6 +32,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
class StaffApiIntegrationTest {
private static final AtomicInteger SEQ = new AtomicInteger(1);
private static final AtomicInteger ID_SEQ = new AtomicInteger(1);
@Autowired
MockMvc mockMvc;
@ -50,11 +51,26 @@ class StaffApiIntegrationTest {
return "EMP" + String.format("%08d", SEQ.incrementAndGet());
}
private Map<String, Object> createBody(String employeeNo) {
/**
* 生成符合身份证号格式且每次调用都不重复的 18 位身份证号
* 结构地区码(6) + 出生日期(8) + 顺序码(3) + 校验位(1)
*/
private String uniqueIdNumber() {
int n = ID_SEQ.getAndIncrement() % 1000;
return String.format("41012319900101%03d4", n);
}
private String maskedId(String idNumber) {
return idNumber.substring(0, 3)
+ "*".repeat(idNumber.length() - 6)
+ idNumber.substring(idNumber.length() - 3);
}
private Map<String, Object> createBody(String employeeNo, String idNumber) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("employeeNo", employeeNo);
body.put("name", "张三");
body.put("idNumber", "410123199001011234");
body.put("idNumber", idNumber);
body.put("gender", 1);
body.put("departmentId", 1001L);
body.put("position", "Java开发工程师");
@ -73,10 +89,14 @@ class StaffApiIntegrationTest {
}
private long create(String token, String employeeNo) throws Exception {
return create(token, employeeNo, uniqueIdNumber());
}
private long create(String token, String employeeNo, String idNumber) throws Exception {
MvcResult result = mockMvc.perform(post("/api/v1/staff")
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createBody(employeeNo))))
.content(objectMapper.writeValueAsString(createBody(employeeNo, idNumber))))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.code").value(201))
.andExpect(jsonPath("$.data.employeeNo").value(employeeNo))
@ -89,7 +109,8 @@ class StaffApiIntegrationTest {
void createListDetailFlow() throws Exception {
String admin = token("ADMIN", null);
String employeeNo = uniqueNo();
long id = create(admin, employeeNo);
String idNumber = uniqueIdNumber();
long id = create(admin, employeeNo, idNumber);
mockMvc.perform(get("/api/v1/staff")
.header("Authorization", "Bearer " + admin)
@ -101,7 +122,7 @@ class StaffApiIntegrationTest {
mockMvc.perform(get("/api/v1/staff/{id}", id)
.header("Authorization", "Bearer " + admin))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.idNumber").value("410************234"))
.andExpect(jsonPath("$.data.idNumber").value(maskedId(idNumber)))
.andExpect(jsonPath("$.data.extra.technicalLevel").value("P6"))
.andExpect(jsonPath("$.data.emergencyPhone").value("13900139000"));
}
@ -115,7 +136,7 @@ class StaffApiIntegrationTest {
mockMvc.perform(post("/api/v1/staff")
.header("Authorization", "Bearer " + admin)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createBody(employeeNo))))
.content(objectMapper.writeValueAsString(createBody(employeeNo, uniqueIdNumber()))))
.andExpect(status().isConflict())
.andExpect(jsonPath("$.code").value(409));
}
@ -124,14 +145,15 @@ class StaffApiIntegrationTest {
void employeeMaskingAndDataScope() throws Exception {
String admin = token("ADMIN", null);
String employeeNo = uniqueNo();
long id = create(admin, employeeNo);
String idNumber = uniqueIdNumber();
long id = create(admin, employeeNo, idNumber);
// 同部门员工脱敏敏感字段为 null
String sameDeptEmp = token("EMPLOYEE", 1001L);
mockMvc.perform(get("/api/v1/staff/{id}", id)
.header("Authorization", "Bearer " + sameDeptEmp))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.idNumber").value("410************234"))
.andExpect(jsonPath("$.data.idNumber").value(maskedId(idNumber)))
.andExpect(jsonPath("$.data.extra.technicalLevel").isEmpty())
.andExpect(jsonPath("$.data.emergencyPhone").value("139****9000"));

View File

@ -0,0 +1,17 @@
spring:
datasource:
url: jdbc:h2:mem:staff_mgr;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: create-drop
open-in-view: false
properties:
hibernate:
format_sql: true
h2:
console:
enabled: true
path: /h2-console

View File

@ -0,0 +1,13 @@
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/hr_main_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${MYSQL_USER:root}
password: ${MYSQL_PASSWORD:root}
jpa:
hibernate:
ddl-auto: none
open-in-view: false
sql:
init:
mode: never

View File

@ -0,0 +1,17 @@
spring:
application:
name: staff-mgr
profiles:
active: dev
server:
port: 8080
app:
jwt:
secret: ${JWT_SECRET:staff-mgr-dev-secret-key-must-be-at-least-32-bytes}
expiration-ms: 86400000
springdoc:
swagger-ui:
path: /swagger-ui.html

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,101 @@
-- 员工管理模块 MySQL 8.0 DDL生产库手动执行
CREATE TABLE IF NOT EXISTS staff_employee (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
employee_no VARCHAR(32) NOT NULL COMMENT '工号,唯一标识',
name VARCHAR(64) NOT NULL COMMENT '员工姓名',
id_number VARCHAR(64) NOT NULL COMMENT '身份证号,应用层加密存储',
gender TINYINT DEFAULT 0 COMMENT '性别0-未知1-男2-女',
department_id BIGINT NOT NULL COMMENT '所属部门ID',
department_name VARCHAR(128) DEFAULT '' COMMENT '部门名称(冗余,加快查询)',
position VARCHAR(128) NOT NULL COMMENT '岗位名称',
hire_date DATE NOT NULL COMMENT '入职日期',
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
email VARCHAR(128) DEFAULT NULL COMMENT '邮箱',
emergency_contact VARCHAR(64) DEFAULT NULL COMMENT '紧急联系人姓名',
emergency_phone VARCHAR(20) DEFAULT NULL COMMENT '紧急联系人电话',
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态active-在职inactive-离职probation-试用期',
created_by BIGINT DEFAULT NULL COMMENT '创建人ID',
updated_by BIGINT DEFAULT NULL COMMENT '最后更新人ID',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_employee_no (employee_no),
UNIQUE KEY uk_id_number (id_number),
KEY idx_name (name),
KEY idx_department_id (department_id),
KEY idx_status (status),
KEY idx_hire_date (hire_date),
KEY idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='员工主表';
CREATE TABLE IF NOT EXISTS staff_employee_extra (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
employee_id BIGINT NOT NULL COMMENT '员工ID',
education VARCHAR(32) DEFAULT NULL COMMENT '学历',
major VARCHAR(128) DEFAULT NULL COMMENT '专业',
school VARCHAR(128) DEFAULT NULL COMMENT '毕业院校',
graduation_date DATE DEFAULT NULL COMMENT '毕业时间',
previous_company VARCHAR(128) DEFAULT NULL COMMENT '前工作单位',
work_years INT DEFAULT 0 COMMENT '总工作年限',
technical_level VARCHAR(32) DEFAULT NULL COMMENT '技术级别,仅 Admin/HR 可见',
salary_grade VARCHAR(32) DEFAULT NULL COMMENT '薪资等级,仅 Admin/HR 可见',
contract_type VARCHAR(32) DEFAULT NULL COMMENT '合同类型',
contract_start DATE DEFAULT NULL COMMENT '合同开始日期',
contract_end DATE DEFAULT NULL COMMENT '合同结束日期',
probation_end DATE DEFAULT NULL COMMENT '试用期截止日',
address VARCHAR(256) DEFAULT NULL COMMENT '现住址',
remark TEXT DEFAULT NULL COMMENT '备注',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_employee_id (employee_id),
KEY idx_technical_level (technical_level),
KEY idx_contract_end (contract_end)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='员工扩展信息表';
CREATE TABLE IF NOT EXISTS staff_change_log (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
employee_id BIGINT NOT NULL COMMENT '员工ID',
change_type VARCHAR(32) NOT NULL COMMENT '变更类型',
old_value VARCHAR(256) DEFAULT NULL COMMENT '原值',
new_value VARCHAR(256) DEFAULT NULL COMMENT '新值',
operator_id BIGINT NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) DEFAULT NULL COMMENT '操作人姓名',
change_reason VARCHAR(512) DEFAULT NULL COMMENT '变更原因',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '变更时间',
PRIMARY KEY (id),
KEY idx_employee_id (employee_id),
KEY idx_change_type (change_type),
KEY idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='员工变更记录表';
CREATE TABLE IF NOT EXISTS staff_audit_log (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
target_type VARCHAR(32) NOT NULL COMMENT '操作目标类型',
target_id BIGINT NOT NULL COMMENT '操作目标ID',
operation VARCHAR(32) NOT NULL COMMENT '操作类型CREATE/UPDATE/DELETE/VIEW',
operator_id BIGINT NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) DEFAULT '' COMMENT '操作人姓名',
field_name VARCHAR(64) DEFAULT NULL COMMENT '变更字段名',
before_value TEXT DEFAULT NULL COMMENT '变更前值',
after_value TEXT DEFAULT NULL COMMENT '变更后值',
request_ip VARCHAR(64) DEFAULT NULL COMMENT '请求IP',
user_agent VARCHAR(512) DEFAULT NULL COMMENT '浏览器UA',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
PRIMARY KEY (id),
KEY idx_target (target_type, target_id),
KEY idx_operator_id (operator_id),
KEY idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='操作审计日志表';
CREATE TABLE IF NOT EXISTS staff_department_cache (
id BIGINT NOT NULL COMMENT '部门ID来自 org-mgr',
name VARCHAR(128) NOT NULL COMMENT '部门名称',
parent_id BIGINT DEFAULT NULL COMMENT '上级部门ID',
level INT DEFAULT 0 COMMENT '层级深度',
employee_count INT DEFAULT 0 COMMENT '部门人数',
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态',
sync_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后同步时间',
PRIMARY KEY (id),
KEY idx_parent_id (parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门信息本地缓存表';

View File

@ -0,0 +1,3 @@
artifactId=staff-mgr
groupId=com.hr
version=0.1.0-SNAPSHOT

View File

@ -0,0 +1,49 @@
com/hr/staff/entity/StaffEmployee.class
com/hr/staff/common/MaskUtil.class
com/hr/staff/entity/StaffChangeLog.class
com/hr/staff/dto/DepartmentItem.class
com/hr/staff/dto/BatchDeleteResult$FailedItem$FailedItemBuilder.class
com/hr/staff/dto/StaffUpdateRequest.class
com/hr/staff/dto/StaffListItem.class
com/hr/staff/dto/CheckEmployeeNoResult$CheckEmployeeNoResultBuilder.class
com/hr/staff/repository/StaffEmployeeExtraRepository.class
com/hr/staff/dto/ExtraDto$ExtraDtoBuilder.class
com/hr/staff/common/ApiResponse.class
com/hr/staff/service/StaffService.class
com/hr/staff/dto/StaffDetailResponse$StaffDetailResponseBuilder.class
com/hr/staff/dto/BatchDeleteResult$FailedItem.class
com/hr/staff/config/JwtAuthenticationFilter.class
com/hr/staff/repository/StaffAuditLogRepository.class
com/hr/staff/security/JwtService.class
com/hr/staff/dto/StaffListItem$StaffListItemBuilder.class
com/hr/staff/dto/AuditLogItem.class
com/hr/staff/dto/ChangeLogItem$ChangeLogItemBuilder.class
com/hr/staff/dto/StaffDetailResponse.class
com/hr/staff/entity/StaffAuditLog.class
com/hr/staff/config/SecurityConfig.class
com/hr/staff/dto/CheckEmployeeNoResult.class
com/hr/staff/security/AuthUser.class
com/hr/staff/entity/StaffDepartmentCache.class
com/hr/staff/dto/StaffBrief$StaffBriefBuilder.class
com/hr/staff/dto/BatchDeleteResult.class
com/hr/staff/dto/BatchDeleteResult$BatchDeleteResultBuilder.class
com/hr/staff/service/DataInitializer.class
com/hr/staff/StaffMgrApplication.class
com/hr/staff/common/PageResult.class
com/hr/staff/dto/StaffCreateRequest.class
com/hr/staff/dto/DepartmentItem$DepartmentItemBuilder.class
com/hr/staff/repository/StaffDepartmentCacheRepository.class
com/hr/staff/dto/ExtraDto.class
com/hr/staff/security/SecurityUtils.class
com/hr/staff/entity/StaffEmployeeExtra.class
com/hr/staff/repository/StaffChangeLogRepository.class
com/hr/staff/repository/StaffEmployeeRepository.class
com/hr/staff/security/AesCipher.class
com/hr/staff/dto/StaffBrief.class
com/hr/staff/controller/StaffController.class
com/hr/staff/common/BusinessException.class
com/hr/staff/service/ExternalProcessClient.class
com/hr/staff/common/GlobalExceptionHandler.class
com/hr/staff/dto/ChangeLogItem.class
com/hr/staff/dto/AuditLogItem$AuditLogItemBuilder.class
com/hr/staff/dto/BatchDeleteRequest.class

View File

@ -0,0 +1,38 @@
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/config/JwtAuthenticationFilter.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/service/StaffService.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/repository/StaffChangeLogRepository.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/ChangeLogItem.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/common/PageResult.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/StaffCreateRequest.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/common/GlobalExceptionHandler.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/repository/StaffEmployeeExtraRepository.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/StaffMgrApplication.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/common/BusinessException.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/CheckEmployeeNoResult.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/ExtraDto.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/config/SecurityConfig.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/service/ExternalProcessClient.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/security/JwtService.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/repository/StaffDepartmentCacheRepository.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/entity/StaffDepartmentCache.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/StaffListItem.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/StaffDetailResponse.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/common/MaskUtil.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/DepartmentItem.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/repository/StaffEmployeeRepository.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/BatchDeleteResult.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/service/DataInitializer.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/StaffUpdateRequest.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/controller/StaffController.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/StaffBrief.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/security/AuthUser.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/entity/StaffAuditLog.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/repository/StaffAuditLogRepository.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/entity/StaffChangeLog.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/AuditLogItem.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/security/SecurityUtils.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/common/ApiResponse.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/entity/StaffEmployeeExtra.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/security/AesCipher.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/entity/StaffEmployee.java
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/main/java/com/hr/staff/dto/BatchDeleteRequest.java

View File

@ -0,0 +1 @@
com/hr/staff/StaffApiIntegrationTest.class

View File

@ -0,0 +1 @@
/d/pipeline/pipeline_ws/web版本的人事系统/repos/staff-mgr/src/test/java/com/hr/staff/StaffApiIntegrationTest.java

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.hr.staff.StaffApiIntegrationTest
-------------------------------------------------------------------------------
Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.882 s -- in com.hr.staff.StaffApiIntegrationTest