package com.ruoyi.web.controller.api;
|
|
import com.ruoyi.common.annotation.Log;
|
import com.ruoyi.common.config.RuoYiConfig;
|
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.system.domain.SysDoorDevice;
|
import com.ruoyi.system.dto.SysEmployeeAddRequest;
|
import com.ruoyi.system.dto.SysEmployeeDeleteRequest;
|
import com.ruoyi.system.dto.SysEmployeeDetailRequest;
|
import com.ruoyi.system.dto.SysEmployeeEditRequest;
|
import com.ruoyi.system.dto.SysEmployeePageRequest;
|
import com.ruoyi.system.dto.SysEmployeeStatusRequest;
|
import com.ruoyi.system.dto.SysEmployeeUnbindRequest;
|
import com.ruoyi.system.mapper.SysDoorDeviceMapper;
|
import com.ruoyi.system.service.ISysEmployeeService;
|
import com.ruoyi.web.controller.tool.HikvisionAccessControlApi;
|
import com.ruoyi.web.controller.tool.MiddlePlatformDataQuery;
|
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Row;
|
import org.apache.poi.ss.usermodel.Sheet;
|
import org.apache.poi.ss.usermodel.Workbook;
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.multipart.MultipartFile;
|
|
import javax.servlet.http.HttpServletResponse;
|
import java.io.IOException;
|
import java.net.URLEncoder;
|
import java.nio.file.Files;
|
import java.nio.file.Path;
|
import java.nio.file.Paths;
|
import java.util.ArrayList;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* 员工管理接口
|
*
|
* @author ruoyi
|
*/
|
@RestController
|
@RequestMapping("/system/employee")
|
public class SysEmployeeController extends BaseController
|
{
|
@Autowired
|
private ISysEmployeeService employeeService;
|
|
@Autowired
|
private RuoYiConfig ruoyiConfig;
|
|
@Autowired
|
private SysDoorDeviceMapper doorDeviceMapper;
|
|
/**
|
* 分页查询员工列表
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.SELECT)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:view')")
|
@PostMapping("/page")
|
public AjaxResult page(@RequestBody(required = false) SysEmployeePageRequest request)
|
{
|
// 集群版对接中台时,员工属于中台人员主数据,本地只保留查询入口兼容前端。
|
if (ruoyiConfig.isConnectMiddlePlatform())
|
{
|
try
|
{
|
return success(MiddlePlatformDataQuery.employeePage(request));
|
}
|
catch (IOException e)
|
{
|
return middlePlatformError(e);
|
}
|
}
|
return success(employeeService.pageList(request));
|
}
|
|
/**
|
* 查询员工详情
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.SELECT)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:view')")
|
@PostMapping("/detail")
|
public AjaxResult detail(@RequestBody SysEmployeeDetailRequest request)
|
{
|
// 员工详情跟随列表数据源,避免同一员工在列表和详情展示不同字段。
|
if (ruoyiConfig.isConnectMiddlePlatform())
|
{
|
try
|
{
|
return success(MiddlePlatformDataQuery.employeeDetail(request.getEmployeeId()));
|
}
|
catch (IOException e)
|
{
|
return middlePlatformError(e);
|
}
|
}
|
return success(employeeService.detail(request));
|
}
|
|
/**
|
* 新增员工
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.INSERT)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:add')")
|
@PostMapping("/add")
|
public AjaxResult add(@RequestBody SysEmployeeAddRequest request)
|
{
|
int rows = employeeService.add(request, getUsername());
|
AjaxResult ajax = toAjax(rows);
|
if (rows > 0)
|
{
|
ajax.put("hikvisionIssue", issueEmployeeToHikvision(request));
|
}
|
return ajax;
|
}
|
|
/**
|
* 编辑员工
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.UPDATE)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:edit')")
|
@PostMapping("/edit")
|
public AjaxResult edit(@RequestBody SysEmployeeEditRequest request)
|
{
|
return toAjax(employeeService.edit(request, getUsername()));
|
}
|
|
/**
|
* 启用或禁用员工
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.UPDATE)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:status')")
|
@PostMapping("/changeStatus")
|
public AjaxResult changeStatus(@RequestBody SysEmployeeStatusRequest request)
|
{
|
return toAjax(employeeService.changeStatus(request, getUsername()));
|
}
|
|
/**
|
* 卡片解绑
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.UPDATE)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:unbind-card')")
|
@PostMapping("/unbindCard")
|
public AjaxResult unbindCard(@RequestBody SysEmployeeUnbindRequest request)
|
{
|
return toAjax(employeeService.unbindCard(request, getUsername()));
|
}
|
|
/**
|
* 人脸解绑
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.UPDATE)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:unbind-face')")
|
@PostMapping("/unbindFace")
|
public AjaxResult unbindFace(@RequestBody SysEmployeeUnbindRequest request)
|
{
|
return toAjax(employeeService.unbindFace(request, getUsername()));
|
}
|
|
/**
|
* 删除员工
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.DELETE)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:remove')")
|
@PostMapping("/delete")
|
public AjaxResult delete(@RequestBody SysEmployeeDeleteRequest request)
|
{
|
return toAjax(employeeService.delete(request, getUsername()));
|
}
|
|
/**
|
* 下载员工导入模板
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.EXPORT)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:template')")
|
@PostMapping("/importTemplate")
|
public void importTemplate(HttpServletResponse response) throws IOException
|
{
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
response.setCharacterEncoding("utf-8");
|
String fileName = URLEncoder.encode("员工导入模板", "UTF-8").replaceAll("\\+", "%20");
|
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
|
try (Workbook workbook = new XSSFWorkbook())
|
{
|
Sheet sheet = workbook.createSheet("员工导入");
|
String[] headers = {"员工名称", "员工编号", "联系电话", "所属部门", "岗位", "卡片"};
|
Row headerRow = sheet.createRow(0);
|
for (int index = 0; index < headers.length; index++)
|
{
|
Cell cell = headerRow.createCell(index);
|
cell.setCellValue(headers[index]);
|
sheet.setColumnWidth(index, 20 * 256);
|
}
|
Row sampleRow = sheet.createRow(1);
|
sampleRow.createCell(0).setCellValue("张三");
|
sampleRow.createCell(1).setCellValue("SY1001");
|
sampleRow.createCell(2).setCellValue("13800000000");
|
sampleRow.createCell(3).setCellValue("综合管理部");
|
sampleRow.createCell(4).setCellValue("员工");
|
sampleRow.createCell(5).setCellValue("15151665655");
|
workbook.write(response.getOutputStream());
|
}
|
}
|
|
/**
|
* 批量导入员工
|
*/
|
@Log(title = "员工管理", businessType = BusinessType.IMPORT)
|
@PreAuthorize("@ss.hasPermi('visitor:employee:import')")
|
@PostMapping("/importData")
|
public AjaxResult importData(@RequestParam("file") MultipartFile file) throws IOException
|
{
|
if (file == null || file.isEmpty())
|
{
|
return error("请选择导入文件");
|
}
|
return success(employeeService.importData(file.getInputStream(), getUsername()));
|
}
|
|
/**
|
* 目的:中台查询异常时返回统一业务错误,避免异常栈直接暴露给前端。
|
*/
|
private Map<String, Object> issueEmployeeToHikvision(SysEmployeeAddRequest request)
|
{
|
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
if (request == null || Boolean.FALSE.equals(request.getEnabled()))
|
{
|
result.put("status", "skipped");
|
result.put("message", "员工未启用,跳过海康门禁下发");
|
return result;
|
}
|
|
List<SysDoorDevice> devices = doorDeviceMapper.selectEnabledHikvisionDevices();
|
result.put("total", devices.size());
|
if (devices.isEmpty())
|
{
|
result.put("status", "skipped");
|
result.put("message", "未配置启用的海康门禁设备");
|
return result;
|
}
|
|
String username = readHikvisionConfig("hikvision.access.username", "HIKVISION_ACCESS_USERNAME", "admin");
|
String password = readHikvisionConfig("hikvision.access.password", "HIKVISION_ACCESS_PASSWORD", "");
|
if (StringUtils.isEmpty(password))
|
{
|
result.put("status", "skipped");
|
result.put("message", "未配置海康门禁设备密码");
|
handleHikvisionIssueFailure(result);
|
return result;
|
}
|
|
List<Map<String, Object>> records = new ArrayList<Map<String, Object>>();
|
int successCount = 0;
|
for (SysDoorDevice device : devices)
|
{
|
Map<String, Object> record = issueEmployeeToHikvisionDevice(device, request, username, password);
|
records.add(record);
|
if (Boolean.TRUE.equals(record.get("success")))
|
{
|
successCount++;
|
}
|
}
|
result.put("success", successCount);
|
result.put("failed", devices.size() - successCount);
|
result.put("records", records);
|
result.put("status", successCount == devices.size() ? "success" : (successCount == 0 ? "failed" : "partial"));
|
handleHikvisionIssueFailure(result);
|
return result;
|
}
|
|
private Map<String, Object> issueEmployeeToHikvisionDevice(SysDoorDevice device, SysEmployeeAddRequest request,
|
String username, String password)
|
{
|
Map<String, Object> record = new LinkedHashMap<String, Object>();
|
record.put("doorId", device.getDoorId());
|
record.put("deviceName", device.getDeviceName());
|
record.put("deviceId", device.getDeviceId());
|
try
|
{
|
HikvisionAccessControlApi.DeviceConfig deviceConfig = HikvisionAccessControlApi.DeviceConfig
|
.http(device.getDeviceId(), username, password);
|
deviceConfig.setPort(readHikvisionIntConfig("hikvision.access.port", "HIKVISION_ACCESS_PORT", 80));
|
deviceConfig.setHttps(readHikvisionBooleanConfig("hikvision.access.https", "HIKVISION_ACCESS_HTTPS", false));
|
|
HikvisionAccessControlApi.IssueResult issueResult = HikvisionAccessControlApi.issueEmployee(
|
deviceConfig, buildHikvisionEmployee(request));
|
record.put("success", true);
|
record.put("result", issueResult);
|
}
|
catch (Exception e)
|
{
|
record.put("success", false);
|
record.put("message", e.getMessage());
|
}
|
return record;
|
}
|
|
private HikvisionAccessControlApi.EmployeeCredential buildHikvisionEmployee(SysEmployeeAddRequest request) throws IOException
|
{
|
HikvisionAccessControlApi.EmployeeCredential employee = HikvisionAccessControlApi.EmployeeCredential.of(
|
request.getEmployeeNo(), request.getEmployeeName(), request.getCardNo());
|
String faceImage = StringUtils.trim(request.getFaceImage());
|
if (StringUtils.isEmpty(faceImage))
|
{
|
return employee;
|
}
|
if (isHttpUrl(faceImage))
|
{
|
employee.setFaceImageUrl(faceImage);
|
return employee;
|
}
|
byte[] localFaceImage = readProfileFaceImage(faceImage);
|
if (localFaceImage != null)
|
{
|
employee.setFaceImage(localFaceImage);
|
return employee;
|
}
|
employee.setFaceImageBase64(faceImage);
|
return employee;
|
}
|
|
private byte[] readProfileFaceImage(String faceImage) throws IOException
|
{
|
String relativePath = normalizeProfileRelativePath(faceImage);
|
if (StringUtils.isEmpty(relativePath))
|
{
|
return null;
|
}
|
Path profileRoot = Paths.get(RuoYiConfig.getProfile()).toAbsolutePath().normalize();
|
Path facePath = profileRoot.resolve(relativePath).normalize();
|
if (!facePath.startsWith(profileRoot))
|
{
|
throw new ServiceException("人脸图片路径非法");
|
}
|
if (!Files.exists(facePath) || !Files.isRegularFile(facePath))
|
{
|
return null;
|
}
|
return Files.readAllBytes(facePath);
|
}
|
|
private String normalizeProfileRelativePath(String faceImage)
|
{
|
String value = StringUtils.trim(faceImage).replace("\\", "/");
|
if (StringUtils.isEmpty(value) || value.startsWith("data:"))
|
{
|
return "";
|
}
|
if (value.startsWith("/profile/"))
|
{
|
return value.substring("/profile/".length());
|
}
|
if (value.startsWith("profile/"))
|
{
|
return value.substring("profile/".length());
|
}
|
if (value.startsWith("/"))
|
{
|
return "";
|
}
|
return value;
|
}
|
|
private boolean isHttpUrl(String value)
|
{
|
return value != null && (value.startsWith("http://") || value.startsWith("https://"));
|
}
|
|
private String readHikvisionConfig(String propertyName, String envName, String defaultValue)
|
{
|
String value = System.getProperty(propertyName);
|
if (StringUtils.isNotEmpty(StringUtils.trim(value)))
|
{
|
return StringUtils.trim(value);
|
}
|
value = System.getenv(envName);
|
if (StringUtils.isNotEmpty(StringUtils.trim(value)))
|
{
|
return StringUtils.trim(value);
|
}
|
return defaultValue;
|
}
|
|
private int readHikvisionIntConfig(String propertyName, String envName, int defaultValue)
|
{
|
String value = readHikvisionConfig(propertyName, envName, "");
|
if (StringUtils.isEmpty(value))
|
{
|
return defaultValue;
|
}
|
try
|
{
|
return Integer.parseInt(value);
|
}
|
catch (NumberFormatException e)
|
{
|
return defaultValue;
|
}
|
}
|
|
private boolean readHikvisionBooleanConfig(String propertyName, String envName, boolean defaultValue)
|
{
|
String value = readHikvisionConfig(propertyName, envName, "");
|
return StringUtils.isEmpty(value) ? defaultValue : Boolean.parseBoolean(value);
|
}
|
|
private void handleHikvisionIssueFailure(Map<String, Object> result)
|
{
|
if (!readHikvisionBooleanConfig("hikvision.access.fail-on-error", "HIKVISION_ACCESS_FAIL_ON_ERROR", false))
|
{
|
return;
|
}
|
if ("failed".equals(result.get("status")) || "partial".equals(result.get("status")))
|
{
|
throw new ServiceException("海康门禁员工下发失败");
|
}
|
}
|
|
private AjaxResult middlePlatformError(IOException e)
|
{
|
return error("中台数据查询失败:" + e.getMessage());
|
}
|
}
|