feat(device): 新增远程拍照功能

- 新增设备端与家属端远程拍照接口
- 新增截图实体字段(原始文件名、MIME类型、扩展名)
- 补充截图上传信息记录
- 提取设备操作指令常量
This commit is contained in:
TongTongStudio
2026-08-21 17:12:41 +08:00
parent 32921274a3
commit 9bc37286b7
10 changed files with 388 additions and 11 deletions

View File

@@ -0,0 +1,182 @@
package com.youlai.boot.client.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.youlai.boot.client.model.entity.ClientUser;
import com.youlai.boot.client.service.ClientUserService;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.device.model.entity.SnDeviceInfo;
import com.youlai.boot.device.model.form.ContactForm;
import com.youlai.boot.device.model.vo.ContactVO;
import com.youlai.boot.device.service.ContactService;
import com.youlai.boot.device.service.DeviceService;
import com.youlai.boot.framework.security.util.SecurityUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 客户端(家属端)联系人控制层
*
* <p>将后台管理端 {@code ContactController} 的联系人管理能力移植到客户端:
* 家属端用户登录后可对已绑定设备查询、新增、修改、删除联系人,路径统一挂在
* {@code /api/v1/client/sn/contact} 下。
*
* <p>与 {@link ClientController} 一致:所有接口先通过 {@link #checkBound(String)}
* 校验当前登录的 client 用户是否绑定传入的设备 SN再执行操作。
*
* @author TTSTD
* @since 2026/08/19
*/
@Tag(name = "客户端-联系人")
@RestController
@RequestMapping("/api/v1/client/sn/contact")
@RequiredArgsConstructor
@Slf4j
public class ClientContactController {
private final ClientUserService clientUserService;
private final DeviceService deviceService;
private final ContactService contactService;
/**
* 校验当前登录的 client 用户是否绑定了传入的设备 SN。
* <p>
* 绑定关系:设备表 {@code sys_sn.snMobile} 与 client 用户 {@code app_user.mobile} 一致即视为已绑定。
* 校验未通过时抛出 {@link BusinessException},由调用方转换为失败结果返回。
*
* @param sn 设备序列号
* @return 已绑定的设备记录
*/
private SnDeviceInfo checkBound(String sn) {
if (!StringUtils.hasText(sn)) {
throw new BusinessException("设备序列号不能为空");
}
Long userId = SecurityUtils.getUserId();
ClientUser clientUser = clientUserService.getById(userId);
if (clientUser == null || !StringUtils.hasText(clientUser.getMobile())) {
throw new BusinessException("当前用户未绑定手机号,无法校验设备");
}
SnDeviceInfo deviceInfo = deviceService.getOne(
new LambdaQueryWrapper<SnDeviceInfo>().eq(SnDeviceInfo::getSerialno, sn)
);
if (deviceInfo == null) {
throw new BusinessException("设备不存在");
}
if (!StringUtils.hasText(deviceInfo.getSnMobile())
|| !deviceInfo.getSnMobile().equals(clientUser.getMobile())) {
log.warn("client用户 {} 未绑定设备 sn: {}", userId, sn);
throw new BusinessException("当前用户未绑定该设备");
}
return deviceInfo;
}
@Operation(summary = "联系人列表(家属端)")
@PreAuthorize("isAuthenticated()")
@GetMapping("/list")
public Result<List<ContactVO>> getContactList(
@Parameter(description = "设备序列号") @RequestParam("sn") String sn
) {
try {
checkBound(sn);
List<ContactVO> result = contactService.getAllContacts(sn);
return Result.success(result);
} catch (BusinessException be) {
return Result.failed(be.getMessage());
} catch (Exception e) {
log.error("getContactList error, sn: {}", sn, e);
return Result.failed("获取联系人列表失败: " + e.getMessage());
}
}
@Operation(summary = "新增联系人(家属端)")
@PreAuthorize("isAuthenticated()")
@PostMapping("/insert")
public Result<?> saveContact(
@Parameter(description = "设备序列号") @RequestParam("sn") String sn,
@Valid @RequestBody ContactForm contactForm
) {
try {
checkBound(sn);
Long contactId = contactService.saveContact(sn, contactForm);
if (contactId == null) {
return Result.failed(ResultCode.DUPLICATE_SUBMISSION, "该手机号已存在于当前设备的联系人中", contactForm);
}
return Result.success(contactId);
} catch (BusinessException be) {
return Result.failed(be.getMessage());
} catch (Exception e) {
log.error("saveContact error, sn: {}", sn, e);
return Result.failed("新增联系人失败: " + e.getMessage());
}
}
@Operation(summary = "获取联系人表单数据(家属端)")
@PreAuthorize("isAuthenticated()")
@GetMapping("/form")
public Result<ContactForm> getContactForm(
@Parameter(description = "联系人ID") @RequestParam Long id,
@Parameter(description = "设备序列号") @RequestParam("sn") String sn
) {
try {
checkBound(sn);
ContactForm formData = contactService.getContactForm(id, sn);
return Result.success(formData);
} catch (BusinessException be) {
return Result.failed(be.getMessage());
} catch (Exception e) {
log.error("getContactForm error, sn: {}, id: {}", sn, id, e);
return Result.failed("获取联系人信息失败: " + e.getMessage());
}
}
@Operation(summary = "修改联系人(家属端)")
@PreAuthorize("isAuthenticated()")
@PutMapping("/update")
public Result<?> updateContact(
@Parameter(description = "联系人ID") @RequestParam Long id,
@Parameter(description = "设备序列号") @RequestParam("sn") String sn,
@Valid @RequestBody ContactForm contactForm
) {
try {
checkBound(sn);
return Result.judge(contactService.updateContact(id, sn, contactForm));
} catch (BusinessException be) {
return Result.failed(be.getMessage());
} catch (Exception e) {
log.error("updateContact error, sn: {}, id: {}", sn, id, e);
return Result.failed("修改联系人失败: " + e.getMessage());
}
}
@Operation(summary = "删除联系人(家属端)")
@PreAuthorize("isAuthenticated()")
@DeleteMapping("/delete")
public Result<?> deleteContact(
@Parameter(description = "联系人ID") @RequestParam Long id,
@Parameter(description = "设备序列号") @RequestParam("sn") String sn
) {
try {
checkBound(sn);
return Result.judge(contactService.deleteContact(id, sn));
} catch (BusinessException be) {
return Result.failed(be.getMessage());
} catch (Exception e) {
log.error("deleteContact error, sn: {}, id: {}", sn, id, e);
return Result.failed("删除联系人失败: " + e.getMessage());
}
}
}

View File

@@ -143,6 +143,24 @@ public class ClientDeviceOpsController {
}
}
@Operation(summary = "远程拍照(家属端)")
@PreAuthorize("isAuthenticated()")
@PostMapping("/take_photo")
public Result<?> takePhoto(
@Parameter(description = "设备序列号") @RequestParam("sn") String sn
) {
try {
checkBound(sn);
boolean result = deviceService.takePhoto(sn);
return Result.judge(result);
} catch (BusinessException be) {
return Result.failed(be.getMessage());
} catch (Exception e) {
log.error("remoteCamera error, sn: {}", sn, e);
return Result.failed("设备截图失败: " + e.getMessage());
}
}
@Operation(summary = "设备重启(家属端)")
@PreAuthorize("isAuthenticated()")
@PostMapping("/reboot")