Files
ERP/oa-backend/src/main/java/com/kaidi/oa/web/DeclPushDeviceController.java
T
QiufengandClaude Opus 4.8 5e51dc3f56 SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)
恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。

== 此快照内容 ==
- 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091)
- 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static)
- 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB)
- 交接文档 go.md + go-code-reference/endpoints/entities/database.md
- 多代理建设脚本 .claude/wf-*.js

== 状态 ==
- 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板)
- 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用
- W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成)
- 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456

== 排除(gitignore, 可再生) ==
node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui)
完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules)

时间戳: 20260615-191517

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:19:15 +08:00

124 lines
5.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 移动端·政策推送/预警提醒)。
*
* 补 PARTIALmed)的「无手机 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<DeclPushDevice>> 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<DeclPushDevice> 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<DeclPushDevice> 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<DeclPushDevice> 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<DeclPushDevice> 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<DeclPushDevice> 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<Void> 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));
}
}