package com.kaidi.oa.web; import com.kaidi.oa.common.ApiException; import com.kaidi.oa.common.ApiResp; import com.kaidi.oa.common.NotFoundException; import com.kaidi.oa.domain.DeclPushDevice; import com.kaidi.oa.repository.DeclPushDeviceRepository; import jakarta.servlet.http.HttpServletRequest; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.time.Instant; import java.util.List; /** * 移动端推送通道登记(创新研发中心·申报服务部,需求 §8 移动端·政策推送/预警提醒)。 * * 补 PARTIAL(med)的「无手机 push 通道」缺口:用户登记移动设备 deviceToken 与通道后,政策推送、 * 截止/到期维护提醒等可主动下发到设备(演示口径记为已入队 + 累计投递计数,真实网关对接留扩展点)。 * /{id}/test-push 用于自检通道连通。同一 deviceToken 重复注册按更新处理(幂等登记)。 */ @RestController @RequestMapping("/api/oa/decl-push-devices") public class DeclPushDeviceController { private final DeclPushDeviceRepository repo; private final CurrentUserResolver currentUser; public DeclPushDeviceController(DeclPushDeviceRepository repo, CurrentUserResolver currentUser) { this.repo = repo; this.currentUser = currentUser; } @GetMapping public ApiResp> list(@RequestParam(required = false) String owner) { if (owner != null && !owner.isBlank()) { return ApiResp.ok(repo.findByOwner(owner)); } return ApiResp.ok(repo.findAll()); } @GetMapping("/{id}") public ApiResp get(@PathVariable Long id) { return ApiResp.ok(load(id)); } public record RegisterRequest(String owner, String deviceToken, String channel, String platform) { } /** 登记/更新推送设备。deviceToken 必填;已存在同 token 则更新通道/平台并启用(幂等)。 */ @PostMapping @Transactional public ApiResp register(@RequestBody RegisterRequest req, HttpServletRequest request) { if (req.deviceToken() == null || req.deviceToken().isBlank()) { throw new ApiException(400, "设备推送令牌(deviceToken) 不能为空"); } DeclPushDevice d = repo.findByDeviceToken(req.deviceToken().trim()); if (d == null) { d = new DeclPushDevice(); d.setDeviceToken(req.deviceToken().trim()); d.setDeliveredCount(0); d.setCreatedAt(Instant.now()); } d.setOwner(req.owner() == null || req.owner().isBlank() ? currentUser.resolveLabel(request) : req.owner()); d.setChannel(req.channel() == null || req.channel().isBlank() ? "jpush" : req.channel()); d.setPlatform(req.platform() == null || req.platform().isBlank() ? "Android" : req.platform()); d.setActive(Boolean.TRUE); return ApiResp.ok(repo.save(d)); } @PatchMapping("/{id}") @Transactional public ApiResp update(@PathVariable Long id, @RequestBody RegisterRequest req) { DeclPushDevice d = load(id); if (req.channel() != null && !req.channel().isBlank()) d.setChannel(req.channel()); if (req.platform() != null && !req.platform().isBlank()) d.setPlatform(req.platform()); return ApiResp.ok(repo.save(d)); } @PostMapping("/{id}/toggle") @Transactional public ApiResp toggle(@PathVariable Long id) { DeclPushDevice d = load(id); d.setActive(!Boolean.TRUE.equals(d.getActive())); return ApiResp.ok(repo.save(d)); } /** 自检:向本设备投递一条测试 push(记为已入队 + 计数)。 */ @PostMapping("/{id}/test-push") @Transactional public ApiResp testPush(@PathVariable Long id) { DeclPushDevice d = load(id); if (!Boolean.TRUE.equals(d.getActive())) { throw new ApiException(400, "该设备已停用,无法投递"); } d.setDeliveredCount((d.getDeliveredCount() == null ? 0 : d.getDeliveredCount()) + 1); d.setLastPushAt(Instant.now()); d.setLastPushTitle("测试推送:通道连通正常"); return ApiResp.ok(repo.save(d)); } @DeleteMapping("/{id}") public ApiResp delete(@PathVariable Long id) { if (!repo.existsById(id)) { throw new NotFoundException("push device not found: " + id); } repo.deleteById(id); return ApiResp.ok(null); } private DeclPushDevice load(Long id) { return repo.findById(id) .orElseThrow(() -> new NotFoundException("push device not found: " + id)); } }