Compare commits
4
Commits
2c8125a549
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1ac64c306 | ||
|
|
1532fd4318 | ||
|
|
e20ba5f38b | ||
|
|
3016a97f1b |
+21
-12
@@ -25,7 +25,10 @@ jobs:
|
|||||||
options: --network infra-infra-net
|
options: --network infra-infra-net
|
||||||
steps:
|
steps:
|
||||||
- name: 安装 git / curl(alpine)
|
- name: 安装 git / curl(alpine)
|
||||||
run: apk add --no-cache git curl
|
run: |
|
||||||
|
# 统一走 aliyun 源:dl-cdn 时段性被拦会卡满 60s×2(2026-10-20 事故)
|
||||||
|
sed -i 's#dl-cdn.alpinelinux.org#mirrors.aliyun.com#g' /etc/apk/repositories
|
||||||
|
apk add --no-cache git curl
|
||||||
|
|
||||||
# 内网 Gitea 无法访问 GitHub 拉 actions/checkout,手动 clone 本仓库
|
# 内网 Gitea 无法访问 GitHub 拉 actions/checkout,手动 clone 本仓库
|
||||||
- name: 拉取源码(内网无 GitHub,手动 clone 本仓库)
|
- name: 拉取源码(内网无 GitHub,手动 clone 本仓库)
|
||||||
@@ -55,26 +58,32 @@ jobs:
|
|||||||
- name: 构建 jar(maven 容器,复用宿主 m2 缓存)
|
- name: 构建 jar(maven 容器,复用宿主 m2 缓存)
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
docker rm -f mavenbuild 2>/dev/null || true
|
# 构建容器名必须全局唯一:runner 挂的是宿主 docker.sock,容器名在整个 daemon 上共享
|
||||||
docker run -d --name mavenbuild \
|
# 命名空间。固定名会在多仓/多条 workflow 并发时互相 docker rm -f 掉对方正在构建的
|
||||||
|
# 容器,表现为无编译报错的 exit 137(SIGKILL,2026-10-21 实测复现)。
|
||||||
|
# GITHUB_RUN_ID 实例级唯一,再拼时间戳兜底。
|
||||||
|
BUILD_CONTAINER="mavenbuild-${GITHUB_RUN_ID:-0}-$(date +%s)"
|
||||||
|
echo "BUILD_CONTAINER=${BUILD_CONTAINER}"
|
||||||
|
docker rm -f "$BUILD_CONTAINER" 2>/dev/null || true
|
||||||
|
docker run -d --name "$BUILD_CONTAINER" \
|
||||||
--network infra-infra-net \
|
--network infra-infra-net \
|
||||||
-v /opt/docker/projman-m2:/root/.m2 \
|
-v /opt/docker/projman-m2:/root/.m2 \
|
||||||
-w /build maven:3.6.3-jdk-8 sleep 3600
|
-w /build maven:3.6.3-jdk-8 sleep 3600
|
||||||
docker cp "${PWD}/." mavenbuild:/build/
|
docker cp "${PWD}/." "$BUILD_CONTAINER":/build/
|
||||||
docker exec mavenbuild sh -c "printf '%s' '<settings><profiles><profile><id>nexus-repo</id><repositories><repository><id>nexus-internal</id><url>http://nexus:8081/repository/maven-public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories></profile></profiles><activeProfiles><activeProfile>nexus-repo</activeProfile></activeProfiles></settings>' > /root/.m2/settings.xml"
|
docker exec "$BUILD_CONTAINER" sh -c "printf '%s' '<settings><profiles><profile><id>nexus-repo</id><repositories><repository><id>nexus-internal</id><url>http://nexus:8081/repository/maven-public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories></profile></profiles><activeProfiles><activeProfile>nexus-repo</activeProfile></activeProfiles></settings>' > /root/.m2/settings.xml"
|
||||||
docker exec mavenbuild sh -c 'cd /build && mvn -B -DskipTests clean package'
|
docker exec "$BUILD_CONTAINER" sh -c 'cd /build && mvn -B -DskipTests clean package'
|
||||||
docker exec mavenbuild sh -c 'cd /build/target && cp *.jar app.jar'
|
docker exec "$BUILD_CONTAINER" sh -c 'cd /build/target && cp *.jar app.jar'
|
||||||
mkdir -p "${PWD}/target"
|
mkdir -p "${PWD}/target"
|
||||||
docker cp mavenbuild:/build/target/app.jar "${PWD}/target/app.jar"
|
docker cp "$BUILD_CONTAINER":/build/target/app.jar "${PWD}/target/app.jar"
|
||||||
docker rm -f mavenbuild
|
docker rm -f "$BUILD_CONTAINER"
|
||||||
|
|
||||||
- name: 构建并推送镜像
|
- name: 构建并推送镜像
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
TAG=$(echo "$GITHUB_SHA" | cut -c1-8)
|
TAG=$(echo "$GITHUB_SHA" | cut -c1-8)
|
||||||
echo "${{ secrets.NEXUS_PASSWORD }}" | docker login nexus:8083 -u "${{ secrets.NEXUS_USER }}" --password-stdin
|
echo "${{ secrets.NEXUS_PASSWORD }}" | docker login nexus:8083 -u "${{ secrets.NEXUS_USER }}" --password-stdin
|
||||||
docker build -t "nexus:8083/docker-hosted/notice-service:${TAG}" -f docker/Dockerfile .
|
docker build -t "nexus:8083/docker-hosted/campusnotice:${TAG}" -f docker/Dockerfile .
|
||||||
docker push "nexus:8083/docker-hosted/notice-service:${TAG}"
|
docker push "nexus:8083/docker-hosted/campusnotice:${TAG}"
|
||||||
echo "TAG=${TAG}" >> "$GITHUB_ENV"
|
echo "TAG=${TAG}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
# 通知 projman:webhook 收到后只建档 Release(status=pending),部署由运维在 DeployTab 手动触发(App.deployMode=manual)
|
# 通知 projman:webhook 收到后只建档 Release(status=pending),部署由运维在 DeployTab 手动触发(App.deployMode=manual)
|
||||||
@@ -83,4 +92,4 @@ jobs:
|
|||||||
curl -sS -X POST "http://192.168.10.160:8090/projman/api/deploy/webhook" \
|
curl -sS -X POST "http://192.168.10.160:8090/projman/api/deploy/webhook" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-H "X-Deploy-Token: ${{ secrets.DEPLOY_TOKEN }}" \
|
-H "X-Deploy-Token: ${{ secrets.DEPLOY_TOKEN }}" \
|
||||||
-d "{\"app\":\"notice\",\"tag\":\"${TAG}\",\"sha\":\"${GITHUB_SHA}\",\"message\":\"${COMMIT_MSG}\"}"
|
-d "{\"app\":\"campusnotice\",\"tag\":\"${TAG}\",\"sha\":\"${GITHUB_SHA}\",\"message\":\"${COMMIT_MSG}\"}"
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# CampusNotice 接口 curl 测试脚本 v2
|
||||||
|
# 用法: ./api-curl-test.sh [BASE] [PLATFORM]
|
||||||
|
# BASE 默认 http://localhost:8101
|
||||||
|
# PLATFORM 默认 manage(CAS 仅拦 /api/* 与 /mobiapi/*,manage 为匿名通道;生产环境应封死,见安全提醒)
|
||||||
|
# 注意:中文 JSON 体必须走 UTF-8 文件(--data-binary @file),
|
||||||
|
# 直接 -d '中文' 会被 Git Bash→curl.exe 的 argv 转换变成 GBK,服务端报
|
||||||
|
# Invalid UTF-8 start byte(HttpMessageNotReadable → code=6),属测试工具问题。
|
||||||
|
BASE="${1:-http://localhost:8101}"
|
||||||
|
PF="${2:-manage}"
|
||||||
|
U="$BASE/$PF"
|
||||||
|
TMPB="$(dirname "$0")/.curl-body.json"
|
||||||
|
PASS=0; BIZ=0; FAIL=0
|
||||||
|
CREATED_IDS=()
|
||||||
|
|
||||||
|
req() { # req 名称 方法 路径 [json体]
|
||||||
|
local name="$1" method="$2" path="$3" body="$4"
|
||||||
|
local args=(-s --max-time 15 -w "\n%{http_code}" -H "Accept: application/json")
|
||||||
|
if [ -n "$body" ]; then
|
||||||
|
printf '%s' "$body" > "$TMPB" # UTF-8 原样字节落盘
|
||||||
|
args+=(-X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")")
|
||||||
|
elif [ "$method" = "POST" ]; then
|
||||||
|
args+=(-X POST)
|
||||||
|
fi
|
||||||
|
local out; out=$(curl "${args[@]}" "$U$path" 2>/dev/null)
|
||||||
|
local http="${out##*$'\n'}"; local json="${out%$'\n'*}"
|
||||||
|
local parsed; parsed=$(node -e "
|
||||||
|
try{const j=JSON.parse(process.argv[1]);
|
||||||
|
const d=j.data;
|
||||||
|
const ds=d===null||d===undefined?'null':Array.isArray(d)?'array['+d.length+']':typeof d==='object'?'{'+Object.keys(d).slice(0,4).join(',')+'}':String(d).slice(0,30);
|
||||||
|
console.log([j.code,(j.msg||'').slice(0,46),ds].join('\u0001'));
|
||||||
|
}catch(e){console.log(['-','-','-'].join('\u0001'))}" "$json" 2>/dev/null)
|
||||||
|
local code msg data
|
||||||
|
code=$(echo "$parsed" | cut -d$'\001' -f1)
|
||||||
|
msg=$(echo "$parsed" | cut -d$'\001' -f2)
|
||||||
|
data=$(echo "$parsed" | cut -d$'\001' -f3)
|
||||||
|
local st
|
||||||
|
if [ "$code" = "0" ]; then PASS=$((PASS+1)); st="✅PASS";
|
||||||
|
elif [ "$code" = "-" ] || [ "$http" != "200" ]; then FAIL=$((FAIL+1)); st="❌FAIL";
|
||||||
|
else BIZ=$((BIZ+1)); st="🟡BIZ"; fi
|
||||||
|
printf "%-26s | HTTP %s | code=%-3s | msg=%-46s | data=%s\n" "$name" "$http" "$code" "$msg" "$data"
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_id() { # 从最近一次输出无法跨函数取值,改为调用方直接用 node 从 curl 结果提取
|
||||||
|
node -e "try{console.log(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).data ?? '')}catch(e){console.log('')}" "$1" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "================ CampusNotice curl 矩阵 (BASE=$BASE PF=$PF) ================"
|
||||||
|
|
||||||
|
echo "---- A. 静态(直连 BASE,不经 platform 前缀) ----"
|
||||||
|
for p in "/" "/api-test.html"; do
|
||||||
|
http=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$BASE$p")
|
||||||
|
if [ "$http" = "200" ]; then PASS=$((PASS+1)); printf "%-26s | HTTP %s\n" "静态 $p" "$http"; else FAIL=$((FAIL+1)); printf "%-26s | HTTP %s ❌\n" "静态 $p" "$http"; fi
|
||||||
|
done
|
||||||
|
http=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$BASE/api/notice/Statis")
|
||||||
|
if [ "$http" = "302" ]; then PASS=$((PASS+1)); printf "%-26s | HTTP 302 → CAS 登录墙(符合预期)\n" "/api 登录墙对照"; else FAIL=$((FAIL+1)); printf "%-26s | HTTP $http ❌(预期302)\n" "/api 登录墙对照"; fi
|
||||||
|
|
||||||
|
echo "---- B. 平台/基础(匿名语义) ----"
|
||||||
|
req "GetUserInfo" GET "/base/GetUserInfo"
|
||||||
|
req "GetUserMod(匿名预期拒)" GET "/base/GetUserMod"
|
||||||
|
req "GetSchoolAll" GET "/base/GetSchoolAll"
|
||||||
|
req "GetGroupUser(未接入桩)" GET "/base/GetGroupUser?gid=1&role=2"
|
||||||
|
|
||||||
|
echo "---- C. 通知查询 ----"
|
||||||
|
req "notice/List" GET "/notice/List?pageIndex=1&pageSize=3"
|
||||||
|
req "notice/SendList" GET "/notice/SendList?pageIndex=1&pageSize=3&status=1&scope=user"
|
||||||
|
req "notice/Statis" GET "/notice/Statis"
|
||||||
|
req "notice/GetNextPrev" GET "/notice/GetNextPrev?serid=1&type=0"
|
||||||
|
req "notice/DraftList" GET "/notice/DraftList?pageIndex=1&pageSize=3"
|
||||||
|
req "notice/ApproverSuggest" GET "/notice/ApproverSuggest"
|
||||||
|
req "ApprovalList(pending)" GET "/notice/ApprovalList?auditState=pending&pageIndex=1&pageSize=3"
|
||||||
|
req "ApprovalList(reviewed)" GET "/notice/ApprovalList?auditState=reviewed&pageIndex=1&pageSize=3"
|
||||||
|
|
||||||
|
echo "---- D. 推送范围(匿名预期拒) ----"
|
||||||
|
req "notice/ScopeTree" GET "/notice/ScopeTree"
|
||||||
|
req "notice/ScopePreview" GET "/notice/ScopePreview?scopes=%5B%7B%22scopeType%22%3A1%2C%22scopeId%22%3A0%2C%22scopeName%22%3A%22%E5%85%A8%E6%A0%A1%22%7D%5D"
|
||||||
|
|
||||||
|
echo "---- E. 写链路(UTF-8 文件体,测后自清理) ----"
|
||||||
|
printf '%s' '{"title":"【测试】curl直发","content":"<p>curl</p>","type":1,"deptId":"1","deptName":"校长室","topmost":0,"receiveList":"ALL","fileIds":"","timingSendTime":null,"smsFlag":0,"receiptFlag":1,"publishType":"direct"}' > "$TMPB"
|
||||||
|
resp=$(curl -s --max-time 15 -X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")" "$U/notice/Create")
|
||||||
|
echo "$resp" > "$TMPB.out"
|
||||||
|
ID_DIRECT=$(extract_id "$TMPB.out")
|
||||||
|
if [ -n "$ID_DIRECT" ]; then PASS=$((PASS+1)); printf "%-26s | code=0 | id=%s ✅\n" "Create(direct)" "$ID_DIRECT"; else FAIL=$((FAIL+1)); printf "%-26s | ❌ 未返回id\n" "Create(direct)"; fi
|
||||||
|
CREATED_IDS+=("$ID_DIRECT")
|
||||||
|
|
||||||
|
printf '%s' '{"title":"【测试】curl草稿","content":"<p>curl</p>","type":1,"deptId":"1","deptName":"校长室","topmost":0,"receiveList":"ALL","fileIds":"","timingSendTime":null,"smsFlag":0,"receiptFlag":1}' > "$TMPB"
|
||||||
|
resp=$(curl -s --max-time 15 -X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")" "$U/notice/SaveDraft")
|
||||||
|
echo "$resp" > "$TMPB.out"
|
||||||
|
ID_DRAFT=$(extract_id "$TMPB.out")
|
||||||
|
if [ -n "$ID_DRAFT" ]; then PASS=$((PASS+1)); printf "%-26s | code=0 | id=%s ✅\n" "SaveDraft" "$ID_DRAFT"; else FAIL=$((FAIL+1)); printf "%-26s | ❌ 未返回id\n" "SaveDraft"; fi
|
||||||
|
CREATED_IDS+=("$ID_DRAFT")
|
||||||
|
|
||||||
|
if [ -n "$ID_DRAFT" ]; then
|
||||||
|
printf '%s' "{\"serid\":$ID_DRAFT,\"approverAccounts\":[\"lijing\"],\"opinion\":\"请审批\"}" > "$TMPB"
|
||||||
|
resp=$(curl -s --max-time 15 -X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")" "$U/notice/Submit")
|
||||||
|
code=$(node -e "try{console.log(JSON.parse(process.argv[1]).code)}catch(e){console.log('-')}" "$resp")
|
||||||
|
if [ "$code" = "0" ]; then PASS=$((PASS+1)); printf "%-26s | code=0 | 草稿→待审批 ✅\n" "Submit"; else BIZ=$((BIZ+1)); printf "%-26s | code=%s 🟡\n" "Submit" "$code"; fi
|
||||||
|
fi
|
||||||
|
req "Approve(匿名预期拒)" POST "/notice/Approve?serid=${ID_DRAFT:-0}&opinion=x"
|
||||||
|
req "Reject(匿名预期拒)" POST "/notice/Reject?serid=${ID_DRAFT:-0}&opinion=x"
|
||||||
|
|
||||||
|
echo "---- F. 其他模块 ----"
|
||||||
|
req "dict/ManageList" GET "/dict/ManageList"
|
||||||
|
req "template/Typelist" GET "/template/Typelist"
|
||||||
|
req "contacts/GetGroup" GET "/contacts/GetGroup"
|
||||||
|
req "sms/GetSchool" GET "/sms/GetSchool"
|
||||||
|
|
||||||
|
echo "---- G. 附件 ----"
|
||||||
|
f="$(dirname "$0")/.curl-upload-sample.txt"; echo "curl upload sample $(date +%s)" > "$f"
|
||||||
|
req "base/FileUpload" POST "/base/FileUpload" "" "file=@$(cygpath -w "$f" 2>/dev/null || echo "$f")"
|
||||||
|
rm -f "$f"
|
||||||
|
|
||||||
|
echo "---- H. 清理测试数据 ----"
|
||||||
|
if [ "${#CREATED_IDS[@]}" -gt 0 ]; then
|
||||||
|
ids=$(IFS=,; echo "${CREATED_IDS[*]}")
|
||||||
|
printf '%s' "[$ids]" > "$TMPB"
|
||||||
|
resp=$(curl -s --max-time 15 -X POST -H "Content-Type: application/json; charset=utf-8" --data-binary "@$(cygpath -w "$TMPB" 2>/dev/null || echo "$TMPB")" "$U/notice/Delete")
|
||||||
|
code=$(node -e "try{console.log(JSON.parse(process.argv[1]).code)}catch(e){console.log('-')}" "$resp")
|
||||||
|
if [ "$code" = "0" ]; then PASS=$((PASS+1)); printf "%-26s | code=0 | ids=[$ids] 已清理 ✅\n" "Delete清理"; else FAIL=$((FAIL+1)); printf "%-26s | code=%s ❌\n" "Delete清理" "$code"; fi
|
||||||
|
fi
|
||||||
|
rm -f "$TMPB" "$TMPB.out"
|
||||||
|
|
||||||
|
echo "================ 统计: PASS=$PASS BIZ(业务拒绝/环境预期)=$BIZ FAIL=$FAIL ================"
|
||||||
|
echo "提醒: /manage 匿名可访问全部业务接口——CAS 仅拦 /api/* 与 /mobiapi/*,生产环境务必封死(安全项)。"
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
# Notice 生产配置(线上以 nacos 的 notice.yaml 为准,本文件是 nacos 不可达时的兜底)
|
# CampusNotice 生产配置(线上以 nacos 的 campusnotice.yaml 为准,本文件是 nacos 不可达时的兜底)
|
||||||
# 真正上线请在 Nacos 新建 dataId=notice.yaml、group=NOTICE 的配置覆盖以下项
|
# 真正上线请在 Nacos 新建 dataId=campusnotice.yaml、group=OA_APP 的配置覆盖以下项
|
||||||
# (尤其 datasource 的库名/账号、yh.sso.client-host-url)。
|
# (尤其 datasource 的库名/账号、yh.sso.client-host-url)。
|
||||||
server:
|
server:
|
||||||
port: 8070
|
port: 8070
|
||||||
servlet:
|
servlet:
|
||||||
# 对外经 caddy 暴露为 http://base.yunhaixingtong.com/notice
|
# 对外经 caddy 暴露为 http://base.yunhaixingtong.com/campusnotice
|
||||||
context-path: /notice
|
context-path: /campusnotice
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
mvc:
|
mvc:
|
||||||
@@ -35,7 +35,7 @@ spring:
|
|||||||
yh:
|
yh:
|
||||||
sso:
|
sso:
|
||||||
server-url-prefix: http://sso.yunhaixingtong.com
|
server-url-prefix: http://sso.yunhaixingtong.com
|
||||||
client-host-url: http://base.yunhaixingtong.com/notice
|
client-host-url: http://base.yunhaixingtong.com/campusnotice
|
||||||
allow-headers: all
|
allow-headers: all
|
||||||
allow-origins: all
|
allow-origins: all
|
||||||
url-pattern: /api/*
|
url-pattern: /api/*
|
||||||
|
|||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Notice 启动入口:配置统一由 Nacos 配置中心在 bootstrap 阶段下发(见 bootstrap.yml)
|
# CampusNotice 启动入口:配置统一由 Nacos 配置中心在 bootstrap 阶段下发(见 bootstrap.yml)
|
||||||
# 不再需要手动 curl 拉取;若 nacos 不可达,bootstrap 中 fail-fast=false 会自动回退到
|
# 不再需要手动 curl 拉取;若 nacos 不可达,bootstrap 中 fail-fast=false 会自动回退到
|
||||||
# 镜像内烤好的 application-prod.yml 兜底默认。
|
# 镜像内烤好的 application-prod.yml 兜底默认。
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "[run-app] starting Notice (profile=prod, nacos=${NACOS_ADDR:-nacos.yunhaixingtong.com:8848}) ..."
|
echo "[run-app] starting CampusNotice (profile=prod, nacos=${NACOS_ADDR:-nacos.yunhaixingtong.com:8848}) ..."
|
||||||
exec java ${JAVA_OPTS:--Xms512m -Xmx1024m} -Dfile.encoding=UTF-8 -jar /app/notice.jar \
|
exec java ${JAVA_OPTS:--Xms512m -Xmx1024m} -Dfile.encoding=UTF-8 -jar /app/app.jar \
|
||||||
--spring.profiles.active=prod
|
--spring.profiles.active=prod
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.yunhai</groupId>
|
<groupId>com.yunhai</groupId>
|
||||||
<artifactId>notice</artifactId>
|
<artifactId>campusnotice</artifactId>
|
||||||
<version>0.0.1</version>
|
<version>0.0.1</version>
|
||||||
<name>Notice</name>
|
<name>CampusNotice</name>
|
||||||
<description>通知公告子应用</description>
|
<description>通知公告子应用</description>
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<finalName>notice</finalName>
|
<finalName>campusnotice</finalName>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<mainClass>com.yunhai.notice.NoticeApplication</mainClass>
|
<mainClass>com.yunhai.campusnotice.CampusNoticeApplication</mainClass>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-- =====================================================================
|
||||||
|
-- 修复:campus_notice 库字符集 latin1 → utf8mb4(2026-09-09 接口检测发现)
|
||||||
|
-- 现象:所有含中文的写入(notice Create/SaveDraft/Forward/Edit、审批人姓名、
|
||||||
|
-- 审批意见等)报 Incorrect string value → 全局兜底为「内部服务错误」。
|
||||||
|
-- 根因:建库未指定字符集(latin1_swedish_ci),Hibernate ddl-auto=update
|
||||||
|
-- 建表继承库默认,全部 14 张表均为 latin1。
|
||||||
|
-- 修复:在线转换(存量数据仅 ASCII,转换无损);应用无需重启。
|
||||||
|
-- DBA 建议:生产建库统一 utf8mb4_general_ci;新表继承库默认后即为 utf8mb4。
|
||||||
|
-- =====================================================================
|
||||||
|
|
||||||
|
ALTER DATABASE campus_notice CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci;
|
||||||
|
|
||||||
|
ALTER TABLE campus_notice.notice CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.notice_read CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.notice_receiver CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.notice_push_scope CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.notice_approval CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.notice_approver_memory CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.sys_dict CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.template_type CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.notice_template CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.up_file CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.contacts_group CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.contacts_group_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.sms_module CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
ALTER TABLE campus_notice.sms_school_config CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- 核查(预期全部 utf8mb4):
|
||||||
|
-- SELECT table_name, table_collation FROM information_schema.TABLES
|
||||||
|
-- WHERE table_schema='campus_notice' ORDER BY table_name;
|
||||||
+6
-4
@@ -1,18 +1,20 @@
|
|||||||
package com.yunhai.notice;
|
package com.yunhai.campusnotice;
|
||||||
|
|
||||||
import com.alibaba.fastjson.parser.ParserConfig;
|
import com.alibaba.fastjson.parser.ParserConfig;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
@SpringBootApplication(scanBasePackages = "com.yunhai.notice")
|
@SpringBootApplication(scanBasePackages = "com.yunhai.campusnotice")
|
||||||
@EnableTransactionManagement
|
@EnableTransactionManagement
|
||||||
public class NoticeApplication {
|
@EnableScheduling
|
||||||
|
public class CampusNoticeApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// 开启 fastjson 安全模式
|
// 开启 fastjson 安全模式
|
||||||
ParserConfig.getGlobalInstance().setSafeMode(true);
|
ParserConfig.getGlobalInstance().setSafeMode(true);
|
||||||
SpringApplication.run(NoticeApplication.class, args);
|
SpringApplication.run(CampusNoticeApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.common.listener;
|
package com.yunhai.campusnotice.common.listener;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.BaseEntity;
|
import com.yunhai.campusnotice.entity.BaseEntity;
|
||||||
import com.yunhai.platform.auth.LoginUser;
|
import com.yunhai.platform.auth.LoginUser;
|
||||||
import com.yunhai.platform.auth.UserContextHolder;
|
import com.yunhai.platform.auth.UserContextHolder;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.common.validation;
|
package com.yunhai.campusnotice.common.validation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSR-303 分组校验标记接口(对齐 UserBase 规范)。
|
* JSR-303 分组校验标记接口(对齐 UserBase 规范)。
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.config;
|
package com.yunhai.campusnotice.config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局路径常量。与 UserBase 一致:API 统一挂在 /{platform}/ 之下(platform = api / manage / mobiapi)。
|
* 全局路径常量。与 UserBase 一致:API 统一挂在 /{platform}/ 之下(platform = api / manage / mobiapi)。
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.config;
|
package com.yunhai.campusnotice.config;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.config;
|
package com.yunhai.campusnotice.config;
|
||||||
|
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
+18
-6
@@ -1,9 +1,11 @@
|
|||||||
package com.yunhai.notice.controller.api;
|
package com.yunhai.campusnotice.controller.api;
|
||||||
|
|
||||||
import com.yunhai.notice.config.Config;
|
import com.yunhai.campusnotice.config.Config;
|
||||||
import com.yunhai.notice.model.notice.UpFileResultVo;
|
import com.yunhai.campusnotice.model.bsch.SchTreeNodeDto;
|
||||||
import com.yunhai.notice.service.IFileService;
|
import com.yunhai.campusnotice.model.notice.UpFileResultVo;
|
||||||
import com.yunhai.notice.service.IUserCenterService;
|
import com.yunhai.campusnotice.service.IClassService;
|
||||||
|
import com.yunhai.campusnotice.service.IFileService;
|
||||||
|
import com.yunhai.campusnotice.service.IUserCenterService;
|
||||||
import com.yunhai.platform.auth.LoginUser;
|
import com.yunhai.platform.auth.LoginUser;
|
||||||
import com.yunhai.platform.auth.UserContextHolder;
|
import com.yunhai.platform.auth.UserContextHolder;
|
||||||
import com.yunhai.platform.utils.model.BoaUser;
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
@@ -32,6 +34,9 @@ public class BaseApiController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IUserCenterService userCenterService;
|
private IUserCenterService userCenterService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IClassService classService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IFileService fileService;
|
private IFileService fileService;
|
||||||
|
|
||||||
@@ -73,9 +78,16 @@ public class BaseApiController {
|
|||||||
// ============================ GetSchoolAll ============================
|
// ============================ GetSchoolAll ============================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前用户学校组及用户(树形结构)。
|
* 获取当前用户学校学生树及学生
|
||||||
* 组树组装/教师挂组/递归建树逻辑已下沉 IUserCenterService。
|
* 组树组装/教师挂组/递归建树逻辑已下沉 IUserCenterService。
|
||||||
*/
|
*/
|
||||||
|
@GetMapping("getStuTree")
|
||||||
|
public List<SchTreeNodeDto> getStuTree(BoaUser authUserWithDept) {
|
||||||
|
Integer schId = authUserWithDept.getCurrentRole() != null
|
||||||
|
? authUserWithDept.getCurrentRole().getSchId() : null;
|
||||||
|
return classService.getStuTree(schId);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("GetSchoolAll")
|
@GetMapping("GetSchoolAll")
|
||||||
public List<Map<String, Object>> getSchoolAll(BoaUser authUserWithDept) {
|
public List<Map<String, Object>> getSchoolAll(BoaUser authUserWithDept) {
|
||||||
Integer schId = authUserWithDept.getCurrentRole() != null
|
Integer schId = authUserWithDept.getCurrentRole() != null
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
package com.yunhai.notice.controller.api;
|
package com.yunhai.campusnotice.controller.api;
|
||||||
|
|
||||||
import com.yunhai.notice.config.Config;
|
import com.yunhai.campusnotice.config.Config;
|
||||||
import com.yunhai.notice.service.IContactsService;
|
import com.yunhai.campusnotice.service.IContactsService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
package com.yunhai.notice.controller.api;
|
package com.yunhai.campusnotice.controller.api;
|
||||||
|
|
||||||
import com.yunhai.notice.config.Config;
|
import com.yunhai.campusnotice.config.Config;
|
||||||
import com.yunhai.notice.service.IDictService;
|
import com.yunhai.campusnotice.service.IDictService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
+5
-5
@@ -1,9 +1,9 @@
|
|||||||
package com.yunhai.notice.controller.api;
|
package com.yunhai.campusnotice.controller.api;
|
||||||
|
|
||||||
import com.yunhai.notice.config.Config;
|
import com.yunhai.campusnotice.config.Config;
|
||||||
import com.yunhai.notice.model.notice.UpFileResultVo;
|
import com.yunhai.campusnotice.model.notice.UpFileResultVo;
|
||||||
import com.yunhai.notice.model.notice.UpFileVo;
|
import com.yunhai.campusnotice.model.notice.UpFileVo;
|
||||||
import com.yunhai.notice.service.IFileService;
|
import com.yunhai.campusnotice.service.IFileService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
package com.yunhai.campusnotice.controller.api;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.yunhai.campusnotice.config.Config;
|
||||||
|
import com.yunhai.campusnotice.common.validation.ValidationGroups;
|
||||||
|
import com.yunhai.campusnotice.model.BasePageResult;
|
||||||
|
import com.yunhai.campusnotice.model.notice.*;
|
||||||
|
import com.yunhai.campusnotice.service.INoticeService;
|
||||||
|
import com.yunhai.campusnotice.utils.PageUtils;
|
||||||
|
import com.yunhai.platform.apistd.BizException;
|
||||||
|
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||||
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.web.SortDefault;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知公告接口(合并 NoticeApiController + 旧 NoticeController)。
|
||||||
|
* <p>路径前缀:{@code Config.API_PATH + "/notice/"}(platform = api / manage / mobiapi)。
|
||||||
|
* 方法动词式 PascalCase(对齐 UserBase);写操作一律 POST。
|
||||||
|
* 返回裸对象,由 ApiResponseAdvice 自动包装为 {code, msg, data}。
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(value = Config.API_PATH + "/notice/")
|
||||||
|
public class NoticeController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private INoticeService noticeService;
|
||||||
|
|
||||||
|
// ============================ 收件箱列表 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已收通知列表(用户端)。
|
||||||
|
*/
|
||||||
|
@GetMapping("List")
|
||||||
|
public NoticePageResult<NoticeVo> list(NoticeQuery query,
|
||||||
|
@SortDefault(sort = {"topmost", "createTime"},
|
||||||
|
direction = Sort.Direction.DESC) Sort sort) {
|
||||||
|
return noticeService.userList(query, PageUtils.toPageable(query, sort));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 已发/全部通知列表 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已发/全部通知列表(管理端)。type=user 只看自己。
|
||||||
|
*/
|
||||||
|
@GetMapping("SendList")
|
||||||
|
public BasePageResult<NoticeVo> sendList(NoticeQuery query,
|
||||||
|
@SortDefault(sort = "createTime",
|
||||||
|
direction = Sort.Direction.DESC) Sort sort) {
|
||||||
|
return noticeService.adminList(query, PageUtils.toPageable(query, sort));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 查看详情(标读) ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查看通知详情(type=1 普通查看标记已读, type=0 编辑模式不标记已读)。
|
||||||
|
*/
|
||||||
|
@GetMapping("Detail")
|
||||||
|
public NoticeDetailVo detail(@RequestParam("serid") Integer serid,
|
||||||
|
@RequestParam(value = "type", defaultValue = "1") Integer type) {
|
||||||
|
return noticeService.detailMap(serid, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 编辑回显(不标读) ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取通知详情(编辑/转发用,不标记已读)。
|
||||||
|
*/
|
||||||
|
@GetMapping("GetDetail")
|
||||||
|
public NoticeDetailVo getDetail(@RequestParam("serid") Integer serid) {
|
||||||
|
return noticeService.getDetailMap(serid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 新建 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建通知(JSON body)。fileIds 为附件ID逗号串,smsFlag 为短信标记。
|
||||||
|
*/
|
||||||
|
@PostMapping("Create")
|
||||||
|
public Integer create(@RequestBody @Validated(ValidationGroups.Create.class) NoticeRequest request) {
|
||||||
|
return noticeService.publish(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 编辑 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑通知(JSON body,含 id)。
|
||||||
|
*/
|
||||||
|
@PostMapping("Edit")
|
||||||
|
public void edit(@RequestBody @Validated(ValidationGroups.Update.class) NoticeRequest request) {
|
||||||
|
noticeService.edit(request.getSourceId() != null ? request.getSourceId() : 0, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 批量删除 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除通知(ids 数组)。
|
||||||
|
*/
|
||||||
|
@PostMapping("Delete")
|
||||||
|
public void delete(@RequestBody List<Integer> ids) {
|
||||||
|
noticeService.deleteBatch(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 撤回 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回通知。
|
||||||
|
*/
|
||||||
|
@PostMapping("UserPull")
|
||||||
|
public void userPull(@RequestParam("serid") Integer serid) {
|
||||||
|
noticeService.pull(serid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 置顶 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 置顶/取消置顶。
|
||||||
|
*/
|
||||||
|
@PostMapping("SetTopmost")
|
||||||
|
public void setTopmost(@RequestParam("serid") Integer serid,
|
||||||
|
@RequestParam(value = "topmost", defaultValue = "1") Integer topmost) {
|
||||||
|
noticeService.setTopmost(serid, topmost);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 追加接收人 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追加接收人。
|
||||||
|
*/
|
||||||
|
@PostMapping("ReceiveUsers")
|
||||||
|
public void receiveUsers(@RequestParam("serid") Integer serid,
|
||||||
|
@RequestParam("users") String users) {
|
||||||
|
noticeService.receiveUsers(serid, users);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 催收 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 催收未读。
|
||||||
|
*/
|
||||||
|
@PostMapping("Expedite")
|
||||||
|
public void expedite(@RequestParam("serid") Integer serid) {
|
||||||
|
noticeService.expedite(serid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 一键已读 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一键全部已读(POST 化,原为 GET 写操作)。
|
||||||
|
*/
|
||||||
|
@PostMapping("InitializeRed")
|
||||||
|
public InitializeRedVo initializeRed() {
|
||||||
|
return noticeService.initializeRedResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 阅读情况 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阅读情况(按已读/未读分组)。
|
||||||
|
*/
|
||||||
|
@GetMapping("ReadDetail")
|
||||||
|
public List<Map<String, Object>> readDetail(@RequestParam("serid") Integer serid) {
|
||||||
|
return noticeService.getReadDetailGrouped(serid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 上一篇/下一篇 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上一篇/下一篇通知ID。
|
||||||
|
*/
|
||||||
|
@GetMapping("GetNextPrev")
|
||||||
|
public Map<String, Object> getNextPrev(@RequestParam("serid") Integer serid,
|
||||||
|
@RequestParam("type") String type) {
|
||||||
|
return noticeService.getNextPrev(serid, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 附件打包下载 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量下载通知全部附件(打包 zip)。
|
||||||
|
*/
|
||||||
|
@GetMapping("AllFileDown")
|
||||||
|
public void allFileDown(@RequestParam("dataid") Integer dataid,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
noticeService.downloadAllFiles(dataid, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 统计 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知统计。
|
||||||
|
*/
|
||||||
|
@GetMapping("Statis")
|
||||||
|
public NoticeStatisticsVo statis(
|
||||||
|
@RequestParam(value = "starttime", required = false)
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start,
|
||||||
|
@RequestParam(value = "endtime", required = false)
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end) {
|
||||||
|
return noticeService.statistics(new NoticeQuery(), start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 转发(合并旧 REST 壳 forward) ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转发通知(基于源通知创建新通知)。sourceId 在 request body 中传递。
|
||||||
|
*/
|
||||||
|
@PostMapping("Forward")
|
||||||
|
public Integer forward(@RequestBody @Validated(ValidationGroups.Create.class) NoticeRequest request) {
|
||||||
|
return noticeService.forward(request.getSourceId(), request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 推送范围树(新版发布表单,改造方案 3.1) ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前学校学员推送范围树:全校根 + 学段→年级→班级(数据源 bschclass),
|
||||||
|
* 供表单「推送范围」胶囊按钮渲染。节点结构:{scopeType, scopeId, name, children}。
|
||||||
|
*/
|
||||||
|
@GetMapping("ScopeTree")
|
||||||
|
public List<Map<String, Object>> scopeTree() {
|
||||||
|
return noticeService.scopeTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 推送范围预估 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按已选范围预估接收人数(学员 = 范围展开去重账号数 + 教师 = receiveList 定向名单数)。
|
||||||
|
* scopes 为 JSON 数组:[{"scopeType":3,"scopeId":5,"scopeName":"初一"}]
|
||||||
|
*/
|
||||||
|
@GetMapping("ScopePreview")
|
||||||
|
public ScopePreviewVo scopePreview(@RequestParam(value = "scopes", required = false) String scopes,
|
||||||
|
@RequestParam(value = "receiveList", required = false) String receiveList) {
|
||||||
|
return noticeService.scopePreview(parseScopes(scopes), receiveList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 存为草稿 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存为草稿(status=0):不圈人、不发短信、不落接收人,仅存内容 + 范围快照 + 附件。
|
||||||
|
*/
|
||||||
|
@PostMapping("SaveDraft")
|
||||||
|
public Integer saveDraft(@RequestBody NoticeRequest request) {
|
||||||
|
return noticeService.saveDraft(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 草稿箱列表 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 草稿箱列表(status=0 本人草稿):重要程度 type/typeDesc、定时标注与 timingSendTime。
|
||||||
|
*/
|
||||||
|
@GetMapping("DraftList")
|
||||||
|
public BasePageResult<NoticeVo> draftList(NoticeQuery query,
|
||||||
|
@SortDefault(sort = "updateTime",
|
||||||
|
direction = Sort.Direction.DESC) Sort sort) {
|
||||||
|
return noticeService.draftList(query, PageUtils.toPageable(query, sort));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 已发送阅读回执 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已发送通知阅读回执:按 年级→班级→学生 分组返回(身份快照),
|
||||||
|
* 学生带已读/未读状态与阅读时间(前端着色),教师接收行归「教师」组。
|
||||||
|
*/
|
||||||
|
@GetMapping("ReadReceipt")
|
||||||
|
public ReadReceiptVo readReceipt(@RequestParam("serid") Integer serid) {
|
||||||
|
return noticeService.readReceipt(serid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 审批人记忆回显 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人记忆回显:当前发布人最近一次所选审批人组合(无记忆返回空)。
|
||||||
|
*/
|
||||||
|
@GetMapping("ApproverSuggest")
|
||||||
|
public ApproverSuggestVo approverSuggest() {
|
||||||
|
return noticeService.approverSuggest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 审批人解析(教师树ID↔账号) ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人解析:教师树(base/GetSchoolAll 叶子 id=教师用户ID)与登录账号互转。
|
||||||
|
* ids=1,2,3 解析账号(选人提交前);accounts=a,b 解析回ID(编辑回显勾树)。
|
||||||
|
* 两参数至少传其一;解析失败的项保留原值、缺目标字段,由前端提示。
|
||||||
|
*/
|
||||||
|
@GetMapping("ApproverResolve")
|
||||||
|
public List<ApproverResolveVo> approverResolve(
|
||||||
|
@RequestParam(value = "ids", required = false) String ids,
|
||||||
|
@RequestParam(value = "accounts", required = false) String accounts) {
|
||||||
|
if ((ids == null || ids.trim().isEmpty()) && (accounts == null || accounts.trim().isEmpty())) {
|
||||||
|
throw new BizException(ErrorCode.PARAM_ERROR, "ids 与 accounts 至少传其一");
|
||||||
|
}
|
||||||
|
return noticeService.approverResolve(
|
||||||
|
parseCsvInts(ids), parseCsvStrings(accounts));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 逗号串 → Integer 列表(忽略空段与非数字) */
|
||||||
|
private List<Integer> parseCsvInts(String csv) {
|
||||||
|
if (csv == null || csv.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<Integer> result = new ArrayList<>();
|
||||||
|
for (String s : csv.split(",")) {
|
||||||
|
try {
|
||||||
|
result.add(Integer.valueOf(s.trim()));
|
||||||
|
} catch (NumberFormatException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 逗号串 → 字符串列表(忽略空段,兼容中文逗号/分号) */
|
||||||
|
private List<String> parseCsvStrings(String csv) {
|
||||||
|
if (csv == null || csv.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new ArrayList<>(java.util.Arrays.asList(csv.split("[,,;;]")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 提交审批 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交审批(status: 0/3→2):发布人选定审批人(可多选,或签),
|
||||||
|
* 写 notice_approval 提交行并 upsert 审批人记忆表。
|
||||||
|
*/
|
||||||
|
@PostMapping("Submit")
|
||||||
|
public void submit(@RequestBody SubmitRequest request) {
|
||||||
|
noticeService.submit(request.getSerid(), request.getApproverAccounts(), request.getOpinion());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 审批通过 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批通过(或签):任一审批人通过即 status→1 并触发真实发布;
|
||||||
|
* 被他人先审时返回带实际处理人/时间的冲突提示。
|
||||||
|
*/
|
||||||
|
@PostMapping("Approve")
|
||||||
|
public void approve(@RequestParam("serid") Integer serid,
|
||||||
|
@RequestParam(value = "opinion", required = false) String opinion) {
|
||||||
|
noticeService.approve(serid, opinion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 审批驳回 ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批驳回(或签):任一审批人驳回即 status→3,意见必填;发布人可改后重新提交。
|
||||||
|
*/
|
||||||
|
@PostMapping("Reject")
|
||||||
|
public void reject(@RequestParam("serid") Integer serid,
|
||||||
|
@RequestParam("opinion") String opinion) {
|
||||||
|
noticeService.reject(serid, opinion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 审批列表(待审/已审合一) ============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批列表:按审批人快照含当前账号过滤(schId 隔离)。
|
||||||
|
* auditState=pending(默认) 待审 / reviewed 已审(含或签下他人已处理,带实际处理人)。
|
||||||
|
*/
|
||||||
|
@GetMapping("ApprovalList")
|
||||||
|
public BasePageResult<ApprovalListVo> approvalList(
|
||||||
|
@RequestParam(value = "auditState", defaultValue = "pending") String auditState,
|
||||||
|
NoticeQuery query,
|
||||||
|
@SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) {
|
||||||
|
return noticeService.approvalList(auditState, query, PageUtils.toPageable(query, sort));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析 scopes JSON 参数为范围列表(空/非法返回 null = 无范围) */
|
||||||
|
private List<PushScopeItem> parseScopes(String scopes) {
|
||||||
|
if (scopes == null || scopes.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new ObjectMapper().readValue(scopes, new TypeReference<List<PushScopeItem>>() {
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new BizException(ErrorCode.PARAM_ERROR, "scopes 参数格式错误,应为 JSON 数组:[{\"scopeType\":3,\"scopeId\":5}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
package com.yunhai.notice.controller.api;
|
package com.yunhai.campusnotice.controller.api;
|
||||||
|
|
||||||
import com.yunhai.notice.config.Config;
|
import com.yunhai.campusnotice.config.Config;
|
||||||
import com.yunhai.notice.service.ISmsService;
|
import com.yunhai.campusnotice.service.ISmsService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
package com.yunhai.notice.controller.api;
|
package com.yunhai.campusnotice.controller.api;
|
||||||
|
|
||||||
import com.yunhai.notice.config.Config;
|
import com.yunhai.campusnotice.config.Config;
|
||||||
import com.yunhai.notice.service.ITemplateService;
|
import com.yunhai.campusnotice.service.ITemplateService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import com.yunhai.notice.common.listener.NoticeAuditListener;
|
import com.yunhai.campusnotice.common.listener.NoticeAuditListener;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
+33
-7
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -10,7 +10,7 @@ import javax.persistence.Table;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知公告主表。对应原 .NET Notify_Message(已剔除短信/模板/门户等无关字段)。
|
* 校园公告主表。对应原 .NET Notify_Message(已剔除短信/模板/门户等无关字段)。
|
||||||
* <p>
|
* <p>
|
||||||
* 阅读状态不落主表:采用「懒标记」模型——读者首次查看时写入 {@link NoticeRead},
|
* 阅读状态不落主表:采用「懒标记」模型——读者首次查看时写入 {@link NoticeRead},
|
||||||
* 不存在阅读记录即视为未读,从而无需在发通知时枚举全校用户预插接收行。
|
* 不存在阅读记录即视为未读,从而无需在发通知时枚举全校用户预插接收行。
|
||||||
@@ -41,7 +41,7 @@ public class Notice extends BaseEntity {
|
|||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知类型:1 普通 / 2 重要(见 {@link com.yunhai.notice.enums.NoticeTypeEnum})
|
* 通知类型:1 普通 / 2 重要(见 {@link com.yunhai.campusnotice.enums.NoticeTypeEnum})
|
||||||
*/
|
*/
|
||||||
@Column(name = "type", nullable = false)
|
@Column(name = "type", nullable = false)
|
||||||
private Integer type = 1;
|
private Integer type = 1;
|
||||||
@@ -59,22 +59,23 @@ public class Notice extends BaseEntity {
|
|||||||
private String publisherName;
|
private String publisherName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发布部门ID(原 DeptID)
|
* 所属分类ID(原 DeptID)
|
||||||
*/
|
*/
|
||||||
@Column(name = "dept_id", length = 64)
|
@Column(name = "dept_id", length = 64)
|
||||||
private String deptId;
|
private String deptId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发布部门名称
|
* 所属分类名称
|
||||||
*/
|
*/
|
||||||
@Column(name = "dept_name", length = 128)
|
@Column(name = "dept_name", length = 128)
|
||||||
private String deptName;
|
private String deptName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态:1 已发布(无草稿态,见 {@link com.yunhai.notice.enums.NoticeStatusEnum})
|
* 状态机值见 {@link com.yunhai.campusnotice.enums.NoticeStatusEnum}:
|
||||||
|
* 0 草稿 / 1 已发布 / 2 待审批 / 3 已驳回。
|
||||||
*/
|
*/
|
||||||
@Column(name = "status", nullable = false)
|
@Column(name = "status", nullable = false)
|
||||||
private Integer status = 1;
|
private Integer status = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否置顶:0 否 / 1 是(原 topmost)
|
* 是否置顶:0 否 / 1 是(原 topmost)
|
||||||
@@ -145,4 +146,29 @@ public class Notice extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
@Column(name = "sms_flag")
|
@Column(name = "sms_flag")
|
||||||
private Integer smsFlag = 0;
|
private Integer smsFlag = 0;
|
||||||
|
|
||||||
|
// ==================== 新版发布表单扩展字段(改造方案 2.1) ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要学员阅读回执:0 否 / 1 是。
|
||||||
|
* =1 时管理端展示已读/未读统计与「催收未读」入口,学员端展示回执提示;
|
||||||
|
* =0 时阅读仍照常记录(notice_read),但不出催收与回执提示(改动面最小)。
|
||||||
|
*/
|
||||||
|
@Column(name = "receipt_flag")
|
||||||
|
private Integer receiptFlag = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人快照(JSON 账号数组,如 ["account1","account2"]):提交审批时由发布人选定(可多选)。
|
||||||
|
* ApprovalList 据此过滤「待我审批」;或签任一处理后状态即离开待审批,快照留作历史追溯。
|
||||||
|
* 驳回重提并更换审批人后,该快照被新一轮提交覆盖(已知边界,见改造方案 4.4)。
|
||||||
|
*/
|
||||||
|
@Column(name = "approver_accounts", columnDefinition = "LONGTEXT")
|
||||||
|
private String approverAccounts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时发送已真正发布标记:到点调度后回写为 1,避免重复触发(改造方案 4.2)。
|
||||||
|
* 立即发布的通知在真实发布时一并置 1。
|
||||||
|
*/
|
||||||
|
@Column(name = "timing_published")
|
||||||
|
private Integer timingPublished = 0;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Index;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知审批记录表 notice_approval(改造方案 2.4)。
|
||||||
|
* <p>
|
||||||
|
* 审批轨迹全量落本表(提交行含审批人快照,通过/驳回行记录实际处理人),
|
||||||
|
* 驳回重提产生新一轮提交行,历史轮次永久保留(详情页按时间线分组展示)。
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Table(name = "notice_approval",
|
||||||
|
indexes = @Index(name = "idx_approval_notice", columnList = "notice_id"))
|
||||||
|
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
||||||
|
public class NoticeApproval extends BaseEntity {
|
||||||
|
|
||||||
|
/** 审批动作:1 提交审批 / 2 通过 / 3 驳回(见 ApprovalActionEnum) */
|
||||||
|
public static final int ACTION_SUBMIT = 1;
|
||||||
|
public static final int ACTION_APPROVE = 2;
|
||||||
|
public static final int ACTION_REJECT = 3;
|
||||||
|
|
||||||
|
/** 通知ID(关联 notice.id) */
|
||||||
|
@Column(name = "notice_id", nullable = false)
|
||||||
|
private Integer noticeId;
|
||||||
|
|
||||||
|
/** 审批动作:1 提交审批 / 2 通过 / 3 驳回 */
|
||||||
|
@Column(name = "action", nullable = false)
|
||||||
|
private Integer action;
|
||||||
|
|
||||||
|
/** 操作人账号 */
|
||||||
|
@Column(name = "operator_account", length = 64)
|
||||||
|
private String operatorAccount;
|
||||||
|
|
||||||
|
/** 操作人姓名 */
|
||||||
|
@Column(name = "operator_name", length = 64)
|
||||||
|
private String operatorName;
|
||||||
|
|
||||||
|
/** 审批意见 */
|
||||||
|
@Column(name = "opinion", length = 1000)
|
||||||
|
private String opinion;
|
||||||
|
|
||||||
|
/** 仅 action=1 提交行使用:选定审批人快照 JSON [{account,name}] */
|
||||||
|
@Column(name = "assignees", columnDefinition = "LONGTEXT")
|
||||||
|
private String assignees;
|
||||||
|
|
||||||
|
/** 操作时间 */
|
||||||
|
@Column(name = "action_time")
|
||||||
|
private LocalDateTime actionTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人记忆表 notice_approver_memory(改造方案 2.5)。
|
||||||
|
* <p>
|
||||||
|
* 记住发布人最近一次提交所选的审批人组合,表单再次进入审批流程时
|
||||||
|
* 经 ApproverSuggest 默认回显,免去重复选人。记忆仅是默认值:
|
||||||
|
* Submit 时后端仍逐个校验被选人资格,防止记忆中的人被撤销角色后带病提交。
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Table(name = "notice_approver_memory",
|
||||||
|
uniqueConstraints = @UniqueConstraint(
|
||||||
|
name = "uk_approver_memory_sch_publisher",
|
||||||
|
columnNames = {"sch_id", "publisher_account"}))
|
||||||
|
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
||||||
|
public class NoticeApproverMemory extends BaseEntity {
|
||||||
|
|
||||||
|
/** 学校ID(多租户隔离) */
|
||||||
|
@Column(name = "sch_id", nullable = false)
|
||||||
|
private Integer schId;
|
||||||
|
|
||||||
|
/** 发布人账号(按人记忆) */
|
||||||
|
@Column(name = "publisher_account", nullable = false, length = 64)
|
||||||
|
private String publisherAccount;
|
||||||
|
|
||||||
|
/** 最近一次所选审批人账号 JSON ["a","b"] */
|
||||||
|
@Column(name = "approver_accounts", columnDefinition = "LONGTEXT")
|
||||||
|
private String approverAccounts;
|
||||||
|
|
||||||
|
/** 对应姓名快照 JSON(回显免二次查询) */
|
||||||
|
@Column(name = "approver_names", columnDefinition = "LONGTEXT")
|
||||||
|
private String approverNames;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Index;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知推送范围表 notice_push_scope(改造方案 2.2)。
|
||||||
|
* <p>
|
||||||
|
* 范围选择本质是圈定一组学校组织节点,随通知快照保存(防止后续组织调整影响历史通知):
|
||||||
|
* 判定用 scopeId,展示用 scopeName。推送范围不在主表冗余 JSON 字段,统一以本表为准。
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Table(name = "notice_push_scope",
|
||||||
|
indexes = @Index(name = "idx_push_scope_notice", columnList = "notice_id"))
|
||||||
|
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
||||||
|
public class NoticePushScope extends BaseEntity {
|
||||||
|
|
||||||
|
/** 通知ID(关联 notice.id) */
|
||||||
|
@Column(name = "notice_id", nullable = false)
|
||||||
|
private Integer noticeId;
|
||||||
|
|
||||||
|
/** 范围类型:1 全校 / 2 学部 / 3 年级 / 4 班级 / 5 训练队 / 6 指定学员(见 PushScopeTypeEnum) */
|
||||||
|
@Column(name = "scope_type", nullable = false)
|
||||||
|
private Integer scopeType;
|
||||||
|
|
||||||
|
/** 对应组织节点ID:学部=学制ID、年级=gradeId、班级=class_id、学员=stuId、全校= schId(仅语义占位) */
|
||||||
|
@Column(name = "scope_id")
|
||||||
|
private Integer scopeId;
|
||||||
|
|
||||||
|
/** 快照名称(如"初二年级"),组织改名不影响历史通知展示 */
|
||||||
|
@Column(name = "scope_name", length = 128)
|
||||||
|
private String scopeName;
|
||||||
|
}
|
||||||
+23
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -46,4 +46,26 @@ public class NoticeRead extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
@Column(name = "read_time")
|
@Column(name = "read_time")
|
||||||
private LocalDateTime readTime;
|
private LocalDateTime readTime;
|
||||||
|
|
||||||
|
// ==================== 阅读身份快照(改造方案 2.3,学员阅读时带出) ====================
|
||||||
|
|
||||||
|
/** 学校ID(取通知 sch_id) */
|
||||||
|
@Column(name = "sch_id")
|
||||||
|
private Integer schId;
|
||||||
|
|
||||||
|
/** 年级ID快照 */
|
||||||
|
@Column(name = "grade_id")
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
/** 年级名称快照 */
|
||||||
|
@Column(name = "grade_name", length = 128)
|
||||||
|
private String gradeName;
|
||||||
|
|
||||||
|
/** 班级ID快照 */
|
||||||
|
@Column(name = "class_id")
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 班级名称快照 */
|
||||||
|
@Column(name = "class_name", length = 128)
|
||||||
|
private String className;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Index;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知接收人关联表(快照语义:真实发布时一次写入,后续组织调整不影响历史通知)。
|
||||||
|
* <p>
|
||||||
|
* 教师定向:全员通知(receive_list="ALL")不写明细行,读取时按 receive_list="ALL" 标识走旧逻辑兜底。
|
||||||
|
* 学员圈人(新版发布表单):推送范围按 bschclass 展开为学员账号集合批量写入,
|
||||||
|
* 并携带年级/班级身份快照,供学员端可见性过滤、回执分组(年级→班级→学生)、催收取数。
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Table(name = "notice_receiver",
|
||||||
|
indexes = {
|
||||||
|
@Index(name = "idx_notice_id", columnList = "notice_id"),
|
||||||
|
@Index(name = "idx_account", columnList = "account")
|
||||||
|
})
|
||||||
|
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
||||||
|
public class NoticeReceiver extends BaseEntity {
|
||||||
|
|
||||||
|
/** 接收类型:1 教师(现状语义,存量数据全为 1) / 2 学员(推送范围解析快照) */
|
||||||
|
public static final int TYPE_TEACHER = 1;
|
||||||
|
public static final int TYPE_STUDENT = 2;
|
||||||
|
|
||||||
|
/** 通知ID(关联 notice.id) */
|
||||||
|
@Column(name = "notice_id", nullable = false)
|
||||||
|
private Integer noticeId;
|
||||||
|
|
||||||
|
/** 接收人登录账号(学员=SchStudent.account) */
|
||||||
|
@Column(name = "account", nullable = false, length = 64)
|
||||||
|
private String account;
|
||||||
|
|
||||||
|
/** 接收类型:1 教师 / 2 学员(原「2 抄送」从未写入过数据,语义重定义,存量无需迁移) */
|
||||||
|
@Column(name = "receive_type", nullable = false)
|
||||||
|
private Integer receiveType = TYPE_TEACHER;
|
||||||
|
|
||||||
|
// ==================== 学员身份快照(发布时从 bschclass 带出,仅 receive_type=2 使用) ====================
|
||||||
|
|
||||||
|
/** 学员姓名快照 */
|
||||||
|
@Column(name = "stu_name", length = 64)
|
||||||
|
private String stuName;
|
||||||
|
|
||||||
|
/** 年级ID快照 */
|
||||||
|
@Column(name = "grade_id")
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
/** 年级名称快照(如「初二年级」) */
|
||||||
|
@Column(name = "grade_name", length = 128)
|
||||||
|
private String gradeName;
|
||||||
|
|
||||||
|
/** 班级ID快照(sch_class.class_id) */
|
||||||
|
@Column(name = "class_id")
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 班级名称快照 */
|
||||||
|
@Column(name = "class_name", length = 128)
|
||||||
|
private String className;
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -18,7 +18,7 @@ import javax.persistence.Table;
|
|||||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
||||||
public class SysDict extends BaseEntity {
|
public class SysDict extends BaseEntity {
|
||||||
|
|
||||||
/** 字典类型(如 "dept"=发文部门) */
|
/** 字典类型(如 "dept"=所属分类) */
|
||||||
@Column(name = "dict_type", length = 50)
|
@Column(name = "dict_type", length = 50)
|
||||||
private String dictType;
|
private String dictType;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.entity;
|
package com.yunhai.campusnotice.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.yunhai.campusnotice.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知审批动作(notice_approval.action)。
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum ApprovalActionEnum {
|
||||||
|
|
||||||
|
SUBMIT(1, "提交审批"),
|
||||||
|
APPROVE(2, "审批通过"),
|
||||||
|
REJECT(3, "审批驳回");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String desc;
|
||||||
|
|
||||||
|
ApprovalActionEnum(int code, String desc) {
|
||||||
|
this.code = code;
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApprovalActionEnum of(Integer code) {
|
||||||
|
if (code == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (ApprovalActionEnum e : values()) {
|
||||||
|
if (e.code == code) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.yunhai.campusnotice.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知状态机(新版发布表单:直接发布 / 提交审批 / 存为草稿 / 定时发送)。
|
||||||
|
* <pre>
|
||||||
|
* 0 DRAFT 草稿(存为草稿)
|
||||||
|
* 1 PUBLISHED 已发布(直接发布 / 审批通过 / 定时到点)
|
||||||
|
* 2 PENDING 待审批(提交校办审批)
|
||||||
|
* 3 REJECTED 已驳回(可改后重新提交)
|
||||||
|
* </pre>
|
||||||
|
* 兼容性:.NET 原系统「已发布=2」迁移时已归一化为 1,存量数据无 0/2/3;
|
||||||
|
* 实体历史注释「2 定时发送」从未真实写入(定时靠 timingSendTime 字段表达)。
|
||||||
|
* {@link #of(Integer)} 未知值回落 PUBLISHED 的兜底保留。
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum NoticeStatusEnum {
|
||||||
|
|
||||||
|
DRAFT(0, "草稿"),
|
||||||
|
PUBLISHED(1, "已发布"),
|
||||||
|
PENDING(2, "待审批"),
|
||||||
|
REJECTED(3, "已驳回");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String desc;
|
||||||
|
|
||||||
|
NoticeStatusEnum(int code, String desc) {
|
||||||
|
this.code = code;
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NoticeStatusEnum of(Integer code) {
|
||||||
|
if (code == null) {
|
||||||
|
return PUBLISHED;
|
||||||
|
}
|
||||||
|
for (NoticeStatusEnum e : values()) {
|
||||||
|
if (e.code == code) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PUBLISHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.enums;
|
package com.yunhai.campusnotice.enums;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ import lombok.Getter;
|
|||||||
@Getter
|
@Getter
|
||||||
public enum NoticeTypeEnum {
|
public enum NoticeTypeEnum {
|
||||||
|
|
||||||
NORMAL(1, "普通通知"),
|
NORMAL(1, "普通公告"),
|
||||||
IMPORTANT(2, "重要通知");
|
IMPORTANT(2, "重要公告");
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
private final String desc;
|
private final String desc;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.yunhai.campusnotice.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送范围节点类型(notice_push_scope.scope_type)。
|
||||||
|
* scopeId 对应 bschclass 组织节点:学段=学制ID、年级=gradeId、班级=class_id;
|
||||||
|
* 训练队数据源待确认(改造方案 5.1-#3),暂不支持圈人展开。
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum PushScopeTypeEnum {
|
||||||
|
|
||||||
|
SCHOOL(1, "全校"),
|
||||||
|
SUB_SCHOOL(2, "学部"),
|
||||||
|
GRADE(3, "年级"),
|
||||||
|
CLAZZ(4, "班级"),
|
||||||
|
TEAM(5, "训练队"),
|
||||||
|
STUDENT(6, "指定学员");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String desc;
|
||||||
|
|
||||||
|
PushScopeTypeEnum(int code, String desc) {
|
||||||
|
this.code = code;
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PushScopeTypeEnum of(Integer code) {
|
||||||
|
if (code == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (PushScopeTypeEnum e : values()) {
|
||||||
|
if (e.code == code) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.yunhai.campusnotice.job;
|
||||||
|
|
||||||
|
import com.yunhai.campusnotice.service.INoticeService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时发送调度任务(改造方案 4.2)。
|
||||||
|
* <p>每分钟扫描 status=1 且 timingSendTime<=now 且 timing_published=0 的通知,
|
||||||
|
* 逐条 CAS 抢占(timing_published 0→1)后执行真实发布(圈人/落学员快照/发短信/统计口径对齐)。
|
||||||
|
* 幂等由 CAS 保证:重复扫描同一通知只触发一次真实发布。</p>
|
||||||
|
* <p>注意:当前按单实例部署实现(无分布式锁);若后续多实例部署,
|
||||||
|
* 需引入分布式锁(DB 行锁/Redis)或保留 CAS 抢占语义由数据库串行化。</p>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class TimingPublishJob {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(TimingPublishJob.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private INoticeService noticeService;
|
||||||
|
|
||||||
|
@Scheduled(fixedDelay = 60_000, initialDelay = 30_000)
|
||||||
|
public void publishDue() {
|
||||||
|
try {
|
||||||
|
int count = noticeService.timingPublishDue();
|
||||||
|
if (count > 0) {
|
||||||
|
log.info("[TimingPublishJob] 本轮定时发布 {} 条通知", count);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 单条失败不影响下一轮扫描(事务内单条异常会整体回滚该条,由日志暴露)
|
||||||
|
log.error("[TimingPublishJob] 定时发布调度异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model;
|
package com.yunhai.campusnotice.model;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model;
|
package com.yunhai.campusnotice.model;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model;
|
package com.yunhai.campusnotice.model;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.yunhai.campusnotice.model.bsch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bschclass 班级 DTO(/oapi/VSchClass/GetVClassList,SchClass 视图)。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SchClassDto {
|
||||||
|
|
||||||
|
/** 班级业务ID(sch_class.class_id,scopeType=班级 的 scopeId;学生 ClassID 指向它) */
|
||||||
|
@JsonProperty("ClassID")
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 班级名称 */
|
||||||
|
@JsonProperty("ClassName")
|
||||||
|
private String className;
|
||||||
|
|
||||||
|
/** 年级ID(= gradeId) */
|
||||||
|
@JsonProperty("Levell")
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
/** 排序号 */
|
||||||
|
@JsonProperty("sx")
|
||||||
|
private Integer sort;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.yunhai.campusnotice.model.bsch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bschclass 年级 DTO(/oapi/Schoolen/GetSchLenLevellist,BaseSchsystemGrade)。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SchGradeDto {
|
||||||
|
|
||||||
|
/** 年级ID(=gradeId,scopeType=年级 的 scopeId;班级 Levell 指向它) */
|
||||||
|
@JsonProperty("Id")
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 年级名称(gradeNickname,如「初一」) */
|
||||||
|
@JsonProperty("Name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 所属学段(学制)ID */
|
||||||
|
@JsonProperty("OwnerId")
|
||||||
|
private Integer ownerId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.yunhai.campusnotice.model.bsch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bschclass 学校信息 DTO(/oapi/SchInfo/GetUserSchFirst)。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SchInfoDto {
|
||||||
|
|
||||||
|
/** 学校 GUID(bschclass 侧 SchSerId) */
|
||||||
|
@JsonProperty("SchSerId")
|
||||||
|
private String schSerId;
|
||||||
|
|
||||||
|
/** 用户中心学校组ID(与 CampusNotice schId 同域) */
|
||||||
|
@JsonProperty("UserSchSerId")
|
||||||
|
private Integer userSchSerId;
|
||||||
|
|
||||||
|
/** 学校名称 */
|
||||||
|
@JsonProperty("SchName")
|
||||||
|
private String schName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.yunhai.campusnotice.model.bsch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bschclass 学生 DTO(/oapi/OAStu/*,SchStudent 视图)。
|
||||||
|
* 登录账号口径:学员端账号 = SchStudent.account(改造方案 5.1-#1)。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SchStudentDto {
|
||||||
|
|
||||||
|
/** 学员登录账号 */
|
||||||
|
@JsonProperty("Account")
|
||||||
|
private String account;
|
||||||
|
|
||||||
|
/** 学生ID */
|
||||||
|
@JsonProperty("UserStuSerID")
|
||||||
|
private Integer stuId;
|
||||||
|
|
||||||
|
/** 学生姓名 */
|
||||||
|
@JsonProperty("StuName")
|
||||||
|
private String stuName;
|
||||||
|
|
||||||
|
/** 所在班级ID(sch_class.class_id) */
|
||||||
|
@JsonProperty("ClassID")
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 所在组ID */
|
||||||
|
@JsonProperty("GroupID")
|
||||||
|
private Integer groupId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.yunhai.campusnotice.model.bsch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bschclass 学段(学制)DTO(/oapi/Schoolen/GetSchLenlist,BaseSchsystem)。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SchSystemDto {
|
||||||
|
|
||||||
|
/** 学制ID(scopeType=学部 的 scopeId) */
|
||||||
|
@JsonProperty("Id")
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 学段名称(如「小学部」「初中部」) */
|
||||||
|
@JsonProperty("Name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 学制类型 */
|
||||||
|
@JsonProperty("Type")
|
||||||
|
private Integer type;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.yunhai.campusnotice.model.bsch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bschclass 组织树节点 DTO(/oapi/SchGroup/GetTeaTree、GetStuTree,TeaTreeVO 视图)。
|
||||||
|
* type=1 组节点(学校/学段/年级/班级、教师组),type=2 人员叶子(学生/教师)。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SchTreeNodeDto {
|
||||||
|
|
||||||
|
/** 节点ID(学校/学段/年级/班级为对应业务ID,叶子为学生 stuId 或教师 teaId) */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 节点名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 父节点ID(根节点为 0) */
|
||||||
|
private Integer pid;
|
||||||
|
|
||||||
|
/** 排序号(组取 sort,叶子固定 0) */
|
||||||
|
private Integer no;
|
||||||
|
|
||||||
|
/** 1=组节点,2=人员叶子 */
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
/** 是否父节点(组 true,叶子 false) */
|
||||||
|
@JsonProperty("isParent")
|
||||||
|
private Boolean isParent;
|
||||||
|
|
||||||
|
/** 头像(当前固定空串) */
|
||||||
|
private String photo;
|
||||||
|
|
||||||
|
/** 子节点(叶子为 null,组节点非 null) */
|
||||||
|
private List<SchTreeNodeDto> children;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批列表行 VO(notice/ApprovalList,待审/已审合一)。
|
||||||
|
* <p>已审行携带最新处理信息:或签下含「他人已处理」(handledByMe=false),
|
||||||
|
* 实际处理人/动作/时间/意见取 notice_approval 最新通过/驳回行。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ApprovalListVo {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/** 通知类型:1 普通 / 2 重要 */
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
private String typeDesc;
|
||||||
|
|
||||||
|
/** 状态:2 待审批 / 1 已发布 / 3 已驳回 */
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
private String statusDesc;
|
||||||
|
|
||||||
|
/** 发布人账号 */
|
||||||
|
private String publisherAccount;
|
||||||
|
|
||||||
|
/** 发布人姓名 */
|
||||||
|
private String publisherName;
|
||||||
|
|
||||||
|
/** 本轮审批人姓名(来自提交行 assignees 快照,列表页免查用户中心) */
|
||||||
|
private List<String> approverNames = new ArrayList<>();
|
||||||
|
|
||||||
|
/** 最新一轮提交时间 */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime submitTime;
|
||||||
|
|
||||||
|
/** 是否需要学员阅读回执 */
|
||||||
|
private Integer receiptFlag;
|
||||||
|
|
||||||
|
/** 定时发送时间(审批通过后到点真实发布) */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime timingSendTime;
|
||||||
|
|
||||||
|
// ==================== 已审 Tab 字段(待审 Tab 为空) ====================
|
||||||
|
|
||||||
|
/** 是否已有处理结果(已审 Tab 恒 true,冗余字段便于前端判断) */
|
||||||
|
private boolean handled;
|
||||||
|
|
||||||
|
/** 是否由当前登录人处理(false=或签下他人已处理) */
|
||||||
|
private boolean handledByMe;
|
||||||
|
|
||||||
|
/** 实际处理人账号 */
|
||||||
|
private String handledAccount;
|
||||||
|
|
||||||
|
/** 实际处理人姓名 */
|
||||||
|
private String handledName;
|
||||||
|
|
||||||
|
/** 实际处理动作:2 通过 / 3 驳回 */
|
||||||
|
private Integer handledAction;
|
||||||
|
|
||||||
|
/** 实际处理动作描述 */
|
||||||
|
private String handledActionDesc;
|
||||||
|
|
||||||
|
/** 实际处理时间 */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime handledTime;
|
||||||
|
|
||||||
|
/** 处理意见(长文本建议进详情查看) */
|
||||||
|
private String handledOpinion;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批轨迹记录 VO(详情页全量时间线,按轮次分组由前端按提交行切分)。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ApprovalRecordVo {
|
||||||
|
|
||||||
|
/** 审批动作:1 提交审批 / 2 通过 / 3 驳回 */
|
||||||
|
private Integer action;
|
||||||
|
|
||||||
|
/** 动作描述 */
|
||||||
|
private String actionDesc;
|
||||||
|
|
||||||
|
/** 操作人账号 */
|
||||||
|
private String operatorAccount;
|
||||||
|
|
||||||
|
/** 操作人姓名 */
|
||||||
|
private String operatorName;
|
||||||
|
|
||||||
|
/** 审批意见(提交行=提交说明) */
|
||||||
|
private String opinion;
|
||||||
|
|
||||||
|
/** 仅提交行:该轮审批人快照 */
|
||||||
|
private List<Assignee> assignees = new ArrayList<>();
|
||||||
|
|
||||||
|
/** 操作时间 */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime actionTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人快照项。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class Assignee {
|
||||||
|
private String account;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人解析项:教师树节点ID(用户中心用户ID)与登录账号互转。
|
||||||
|
* <p>教师树(base/GetSchoolAll)叶子仅携带教师用户ID与姓名,审批快照按登录账号存储;
|
||||||
|
* 前端选人后经 ApproverResolve 换算账号提交,编辑回显时按账号换回节点ID勾树。
|
||||||
|
* 按账号解析失败时 id 为 null,按ID解析失败时 account 为 null(调用方据此提示)。</p>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ApproverResolveVo {
|
||||||
|
|
||||||
|
/** 教师用户ID(教师树叶子节点 id) */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 登录账号 */
|
||||||
|
private String account;
|
||||||
|
|
||||||
|
/** 姓名 */
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人记忆回显 VO(notice/ApproverSuggest)。
|
||||||
|
* 无记忆时返回空列表,前端走通用选人。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ApproverSuggestVo {
|
||||||
|
|
||||||
|
/** 审批人账号列表(与 names 下标对齐) */
|
||||||
|
private List<String> accounts = new ArrayList<>();
|
||||||
|
|
||||||
|
/** 审批人姓名快照列表 */
|
||||||
|
private List<String> names = new ArrayList<>();
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
+43
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -55,6 +55,48 @@ public class NoticeDetailVo {
|
|||||||
@JsonProperty("tlist")
|
@JsonProperty("tlist")
|
||||||
private Object tlist;
|
private Object tlist;
|
||||||
|
|
||||||
|
// ==================== 新版发布表单扩展(改造方案 3.2 / 4.6) ====================
|
||||||
|
|
||||||
|
/** 重要程度:1 普通 / 2 重要(编辑回显) */
|
||||||
|
@JsonProperty("type")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
/** 是否置顶:0 否 / 1 是(编辑回显) */
|
||||||
|
@JsonProperty("topmost")
|
||||||
|
private Integer topmost;
|
||||||
|
|
||||||
|
/** 短信提醒标记:0 否 / 1 是(编辑回显) */
|
||||||
|
@JsonProperty("smsFlag")
|
||||||
|
private Integer smsFlag;
|
||||||
|
|
||||||
|
/** 状态:0 草稿 / 1 已发布 / 2 待审批 / 3 已驳回 */
|
||||||
|
@JsonProperty("status")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/** 状态描述 */
|
||||||
|
@JsonProperty("statusDesc")
|
||||||
|
private String statusDesc;
|
||||||
|
|
||||||
|
/** 是否需要学员阅读回执:0 否 / 1 是 */
|
||||||
|
@JsonProperty("receiptFlag")
|
||||||
|
private Integer receiptFlag;
|
||||||
|
|
||||||
|
/** 推送范围快照(编辑回显) */
|
||||||
|
@JsonProperty("pushScopes")
|
||||||
|
private List<PushScopeItem> pushScopes;
|
||||||
|
|
||||||
|
/** 审批轨迹(管理端详情展示;学员端 mobiapi 不返回) */
|
||||||
|
@JsonProperty("approvalRecords")
|
||||||
|
private List<ApprovalRecordVo> approvalRecords;
|
||||||
|
|
||||||
|
/** 当前绑定的审批人账号快照(待审批/已驳回通知的编辑回显,按账号勾选教师树) */
|
||||||
|
@JsonProperty("approverAccounts")
|
||||||
|
private List<String> approverAccounts;
|
||||||
|
|
||||||
|
/** 审批人姓名(与 approverAccounts 一一对应,取自最新提交行 assignees 快照) */
|
||||||
|
@JsonProperty("approverNames")
|
||||||
|
private List<String> approverNames;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 附件项(旧 ChaoSend 数组元素)。
|
* 附件项(旧 ChaoSend 数组元素)。
|
||||||
*/
|
*/
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import com.yunhai.notice.model.BasePageResult;
|
import com.yunhai.campusnotice.model.BasePageResult;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import com.yunhai.notice.model.BasePageQuery;
|
import com.yunhai.campusnotice.model.BasePageQuery;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
+17
-2
@@ -1,12 +1,13 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
import com.yunhai.notice.common.validation.ValidationGroups;
|
import com.yunhai.campusnotice.common.validation.ValidationGroups;
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。
|
* 通知发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。
|
||||||
@@ -50,4 +51,18 @@ public class NoticeRequest {
|
|||||||
|
|
||||||
/** 转发源通知ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */
|
/** 转发源通知ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */
|
||||||
private Integer sourceId;
|
private Integer sourceId;
|
||||||
|
|
||||||
|
// ==================== 新版发布表单扩展(改造方案 3.2) ====================
|
||||||
|
|
||||||
|
/** 推送范围列表(与 receiveList 可并存:pushScopes 解析学员,receiveList 作为教师定向名单) */
|
||||||
|
private List<PushScopeItem> pushScopes;
|
||||||
|
|
||||||
|
/** 是否需要学员阅读回执:0 否 / 1 是 */
|
||||||
|
private Integer receiptFlag = 0;
|
||||||
|
|
||||||
|
/** 发布方式:direct 直接发布(默认,兼容旧链路)/ draft 存为草稿 / approve 提交审批(需携带 approverAccounts) */
|
||||||
|
private String publishType;
|
||||||
|
|
||||||
|
/** 提交审批时所选审批人账号(publishType=approve 时必填;独立 Submit 接口走 SubmitRequest) */
|
||||||
|
private List<String> approverAccounts;
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
+12
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -47,6 +47,17 @@ public class NoticeVo {
|
|||||||
/** 接收人列表(JSON) */
|
/** 接收人列表(JSON) */
|
||||||
private String receiveList;
|
private String receiveList;
|
||||||
|
|
||||||
|
// ==================== 新版发布表单扩展(改造方案 3.2) ====================
|
||||||
|
|
||||||
|
/** 是否需要学员阅读回执:0 否 / 1 是 */
|
||||||
|
private Integer receiptFlag;
|
||||||
|
|
||||||
|
/** 定时发送已真正发布标记:0 未触发 / 1 已真实发布(配合 timingSendTime 区分「定时中/已发布」) */
|
||||||
|
private Integer timingPublished;
|
||||||
|
|
||||||
|
/** 审批人账号快照(JSON 数组串,发布流程展示用) */
|
||||||
|
private String approverAccounts;
|
||||||
|
|
||||||
/** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */
|
/** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */
|
||||||
private String UpFile;
|
private String UpFile;
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送范围项(发布/编辑入参与详情回显)。
|
||||||
|
* scopeType 见 {@link com.yunhai.campusnotice.enums.PushScopeTypeEnum}。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PushScopeItem {
|
||||||
|
|
||||||
|
/** 范围类型:1 全校 / 2 学部 / 3 年级 / 4 班级 / 5 训练队 / 6 指定学员 */
|
||||||
|
private Integer scopeType;
|
||||||
|
|
||||||
|
/** 组织节点ID:学部=学制ID、年级=gradeId、班级=class_id、学员=stuId */
|
||||||
|
private Integer scopeId;
|
||||||
|
|
||||||
|
/** 节点名称快照 */
|
||||||
|
private String scopeName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已发送通知阅读回执 VO(notice/ReadReceipt)。
|
||||||
|
* 按身份快照分组:年级 → 班级 → 学生(前端已读/未读着色),教师接收行归「教师」组。
|
||||||
|
* 数据源=notice_receiver 学员快照 + notice_read 懒标记,不实时调用 bschclass。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ReadReceiptVo {
|
||||||
|
|
||||||
|
/** 通知ID */
|
||||||
|
private Integer serid;
|
||||||
|
|
||||||
|
/** 应读人数(notice.allow_count) */
|
||||||
|
private Integer allowCount;
|
||||||
|
|
||||||
|
/** 已读人数(notice_read 实时统计) */
|
||||||
|
private Long readCount;
|
||||||
|
|
||||||
|
/** 年级分组 */
|
||||||
|
private List<GradeGroup> grades = new ArrayList<>();
|
||||||
|
|
||||||
|
/** 教师接收行(无年级/班级维度) */
|
||||||
|
private List<ReaderItem> teachers = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 年级分组。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class GradeGroup {
|
||||||
|
private Integer gradeId;
|
||||||
|
private String gradeName;
|
||||||
|
private List<ClassGroup> classes = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级分组。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class ClassGroup {
|
||||||
|
private Integer classId;
|
||||||
|
private String className;
|
||||||
|
private List<ReaderItem> students = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生/教师阅读项。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class ReaderItem {
|
||||||
|
private String account;
|
||||||
|
private String name;
|
||||||
|
/** 是否已读 */
|
||||||
|
private boolean read;
|
||||||
|
/** 阅读时间(未读为 null) */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime readTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送范围预估 VO(notice/ScopePreview)。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ScopePreviewVo {
|
||||||
|
|
||||||
|
/** 范围展开学员数(按账号去重) */
|
||||||
|
private int studentCount;
|
||||||
|
|
||||||
|
/** 教师定向名单数(receiveList 解析) */
|
||||||
|
private int teacherCount;
|
||||||
|
|
||||||
|
/** 合计预估接收人数 */
|
||||||
|
private int totalCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交审批入参(notice/Submit)。
|
||||||
|
* 审批人可多选,或签语义:任一审批人通过即发布、任一驳回即驳回。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SubmitRequest {
|
||||||
|
|
||||||
|
/** 通知ID */
|
||||||
|
private Integer serid;
|
||||||
|
|
||||||
|
/** 选定审批人账号列表(至少一人) */
|
||||||
|
private List<String> approverAccounts;
|
||||||
|
|
||||||
|
/** 提交说明(可选) */
|
||||||
|
private String opinion;
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.model.notice;
|
package com.yunhai.campusnotice.model.notice;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.ContactsGroup;
|
import com.yunhai.campusnotice.entity.ContactsGroup;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.ContactsGroupUser;
|
import com.yunhai.campusnotice.entity.ContactsGroupUser;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
|
import com.yunhai.campusnotice.entity.NoticeApproval;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知审批记录仓储。
|
||||||
|
*/
|
||||||
|
public interface NoticeApprovalRepository extends JpaRepository<NoticeApproval, Integer> {
|
||||||
|
|
||||||
|
/** 某通知全量审批轨迹(按落库顺序,即轮次时间线) */
|
||||||
|
@Query("select a from NoticeApproval a where a.noticeId = :noticeId order by a.id asc")
|
||||||
|
List<NoticeApproval> findByNoticeIdOrderByIdAsc(@Param("noticeId") Integer noticeId);
|
||||||
|
|
||||||
|
/** 批量查 N 个通知的审批记录(审批列表/详情批量装配) */
|
||||||
|
@Query("select a from NoticeApproval a where a.noticeId in :noticeIds order by a.id asc")
|
||||||
|
List<NoticeApproval> findByNoticeIds(@Param("noticeIds") Collection<Integer> noticeIds);
|
||||||
|
|
||||||
|
/** 某通知最新一条指定动作的记录(或签「他人已处理」摘要 / 驳回意见回显) */
|
||||||
|
@Query("select a from NoticeApproval a where a.noticeId = :noticeId and a.action in :actions " +
|
||||||
|
"order by a.id desc")
|
||||||
|
List<NoticeApproval> findLatestByNoticeIdAndActions(@Param("noticeId") Integer noticeId,
|
||||||
|
@Param("actions") Collection<Integer> actions);
|
||||||
|
|
||||||
|
/** 按通知ID删除全部审批记录(通知删除时级联清理) */
|
||||||
|
void deleteByNoticeId(Integer noticeId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
|
import com.yunhai.campusnotice.entity.NoticeApproverMemory;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人记忆仓储(唯一约束 sch_id + publisher_account,upsert 由 Service 完成)。
|
||||||
|
*/
|
||||||
|
public interface NoticeApproverMemoryRepository extends JpaRepository<NoticeApproverMemory, Integer> {
|
||||||
|
|
||||||
|
/** 按学校 + 发布人查最近一次所选审批人组合 */
|
||||||
|
Optional<NoticeApproverMemory> findBySchIdAndPublisherAccount(Integer schId, String publisherAccount);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
|
import com.yunhai.campusnotice.entity.NoticePushScope;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知推送范围仓储。
|
||||||
|
*/
|
||||||
|
public interface NoticePushScopeRepository extends JpaRepository<NoticePushScope, Integer> {
|
||||||
|
|
||||||
|
/** 按通知ID查询全部范围快照 */
|
||||||
|
List<NoticePushScope> findByNoticeId(Integer noticeId);
|
||||||
|
|
||||||
|
/** 批量查 N 个通知的范围(编辑回显/列表展示避免 N+1) */
|
||||||
|
@Query("select s from NoticePushScope s where s.noticeId in :noticeIds")
|
||||||
|
List<NoticePushScope> findByNoticeIds(@Param("noticeIds") List<Integer> noticeIds);
|
||||||
|
|
||||||
|
/** 通知是否存在推送范围(学员端可见性过滤仅对范围通知启用) */
|
||||||
|
boolean existsByNoticeId(Integer noticeId);
|
||||||
|
|
||||||
|
/** 按通知ID删除全部范围(编辑全量替换) */
|
||||||
|
void deleteByNoticeId(Integer noticeId);
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.NoticeRead;
|
import com.yunhai.campusnotice.entity.NoticeRead;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
|
import com.yunhai.campusnotice.entity.NoticeReceiver;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知接收人关联表仓储。
|
||||||
|
*/
|
||||||
|
public interface NoticeReceiverRepository extends JpaRepository<NoticeReceiver, Integer> {
|
||||||
|
|
||||||
|
/** 按通知ID查询全部接收人 */
|
||||||
|
List<NoticeReceiver> findByNoticeId(Integer noticeId);
|
||||||
|
|
||||||
|
/** 按通知ID删除全部接收人 */
|
||||||
|
void deleteByNoticeId(Integer noticeId);
|
||||||
|
|
||||||
|
/** 按通知ID和接收类型查询接收人 */
|
||||||
|
List<NoticeReceiver> findByNoticeIdAndReceiveType(Integer noticeId, Integer receiveType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查 N 个通知的接收人,用于列表展示(避免 N+1)。
|
||||||
|
* @param noticeIds 通知ID列表
|
||||||
|
* @return 接收人关联记录列表
|
||||||
|
*/
|
||||||
|
@Query("select r from NoticeReceiver r where r.noticeId in :noticeIds")
|
||||||
|
List<NoticeReceiver> findByNoticeIds(@Param("noticeIds") List<Integer> noticeIds);
|
||||||
|
|
||||||
|
// ==================== 新版发布流程(改造方案 2.3 / 3.2) ====================
|
||||||
|
|
||||||
|
/** 当前账号命中的通知ID集合(学员端列表可见性过滤:仅对存在范围的通知启用) */
|
||||||
|
@Query("select distinct r.noticeId from NoticeReceiver r where r.account = :account")
|
||||||
|
List<Integer> findNoticeIdsByAccount(@Param("account") String account);
|
||||||
|
|
||||||
|
/** 某人某通知的接收快照行(markRead 身份快照取数:学员行优先) */
|
||||||
|
Optional<NoticeReceiver> findFirstByNoticeIdAndAccountOrderByReceiveTypeDescIdDesc(
|
||||||
|
Integer noticeId, String account);
|
||||||
|
|
||||||
|
/** 按通知ID + 接收类型删除(学员圈人重新展开时清理旧快照) */
|
||||||
|
void deleteByNoticeIdAndReceiveType(Integer noticeId, Integer receiveType);
|
||||||
|
|
||||||
|
/** 按通知ID + 接收类型计数(allowCount = 学员数 + 教师数) */
|
||||||
|
long countByNoticeIdAndReceiveType(Integer noticeId, Integer receiveType);
|
||||||
|
}
|
||||||
+49
-2
@@ -1,8 +1,9 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.Notice;
|
import com.yunhai.campusnotice.entity.Notice;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
@@ -56,6 +57,16 @@ public interface NoticeRepository extends JpaRepository<Notice, Integer>,
|
|||||||
*/
|
*/
|
||||||
long countBySchId(Integer schId);
|
long countBySchId(Integer schId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排除指定状态计数(统计"累计发布"时剔除草稿)。
|
||||||
|
*/
|
||||||
|
long countByStatusNot(Integer status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按学校排除指定状态计数(多租户隔离统计,剔除草稿)。
|
||||||
|
*/
|
||||||
|
long countBySchIdAndStatusNot(Integer schId, Integer status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 已发布且已到点(定时发送未到点不计入)且属于指定学校的通知数(用户端未读数统计,多租户隔离)。
|
* 已发布且已到点(定时发送未到点不计入)且属于指定学校的通知数(用户端未读数统计,多租户隔离)。
|
||||||
*/
|
*/
|
||||||
@@ -86,4 +97,40 @@ public interface NoticeRepository extends JpaRepository<Notice, Integer>,
|
|||||||
@Param("status") Integer status,
|
@Param("status") Integer status,
|
||||||
@Param("start") LocalDateTime start,
|
@Param("start") LocalDateTime start,
|
||||||
@Param("end") LocalDateTime end);
|
@Param("end") LocalDateTime end);
|
||||||
|
|
||||||
|
// ==================== 新版发布流程(改造方案 4.2 / 4.3) ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时发送待触发列表:已发布、已到点、尚未真正发布(timing_published=0)。
|
||||||
|
* TimingPublishJob 每分钟扫描,逐条 CAS 抢占后执行真实发布。
|
||||||
|
*/
|
||||||
|
@Query("select n from Notice n where n.status = 1 " +
|
||||||
|
"and n.timingSendTime is not null and n.timingSendTime <= :now " +
|
||||||
|
"and n.timingPublished = 0")
|
||||||
|
List<Notice> findDueTimingPublish(@Param("now") LocalDateTime now);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时发布幂等抢占:仅当 timing_published 仍为 0 时置 1,返回影响行数(0=已被其他线程/实例处理)。
|
||||||
|
*/
|
||||||
|
@Modifying
|
||||||
|
@Query("update Notice n set n.timingPublished = 1, n.updateTime = :now " +
|
||||||
|
"where n.id = :id and n.timingPublished = 0")
|
||||||
|
int casMarkTimingPublished(@Param("id") Integer id, @Param("now") LocalDateTime now);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批通过 CAS:仅当仍处于待审批(status=2)时置为已发布,返回影响行数。
|
||||||
|
* 或签并发场景下先提交者生效,后提交者影响行数为 0,由 Service 转为冲突提示。
|
||||||
|
*/
|
||||||
|
@Modifying
|
||||||
|
@Query("update Notice n set n.status = 1, n.updateTime = :now " +
|
||||||
|
"where n.id = :id and n.status = 2")
|
||||||
|
int casApprove(@Param("id") Integer id, @Param("now") LocalDateTime now);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批驳回 CAS:仅当仍处于待审批(status=2)时置为已驳回。
|
||||||
|
*/
|
||||||
|
@Modifying
|
||||||
|
@Query("update Notice n set n.status = 3, n.updateTime = :now " +
|
||||||
|
"where n.id = :id and n.status = 2")
|
||||||
|
int casReject(@Param("id") Integer id, @Param("now") LocalDateTime now);
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.NoticeTemplate;
|
import com.yunhai.campusnotice.entity.NoticeTemplate;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.SmsModule;
|
import com.yunhai.campusnotice.entity.SmsModule;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.SmsSchoolConfig;
|
import com.yunhai.campusnotice.entity.SmsSchoolConfig;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.SysDict;
|
import com.yunhai.campusnotice.entity.SysDict;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.TemplateType;
|
import com.yunhai.campusnotice.entity.TemplateType;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.yunhai.notice.repository;
|
package com.yunhai.campusnotice.repository;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.UpFile;
|
import com.yunhai.campusnotice.entity.UpFile;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.yunhai.campusnotice.service;
|
||||||
|
|
||||||
|
import com.yunhai.campusnotice.model.SchTeaList;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchClassDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchGradeDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchInfoDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchStudentDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchSystemDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchTreeNodeDto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级管理(bschclass)开放接口客户端门面。
|
||||||
|
* <p>基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。
|
||||||
|
* 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。
|
||||||
|
*/
|
||||||
|
public interface IClassService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定学校全部教师(含所属组 GroupIds)。
|
||||||
|
* 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}}
|
||||||
|
*
|
||||||
|
* @param schId 学校ID(即用户当前身份的 schId)
|
||||||
|
*/
|
||||||
|
List<SchTeaList> getSchTeaList(int schId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}}
|
||||||
|
*/
|
||||||
|
SchInfoDto getSchFirst(int schId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校学段(学制)列表:小学部 / 初中部…。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=}
|
||||||
|
*/
|
||||||
|
List<SchSystemDto> getSchLenList(String schSerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=}
|
||||||
|
*/
|
||||||
|
List<SchGradeDto> getSchGradeList(String schSerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=}
|
||||||
|
*/
|
||||||
|
List<SchClassDto> getSchClassList(String schSerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按校全部学生(圈人:全校范围展开)。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=}
|
||||||
|
*/
|
||||||
|
List<SchStudentDto> getSchStuList(String schSerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=}
|
||||||
|
*/
|
||||||
|
List<SchStudentDto> getGradeStuList(String schSerId, Integer gradeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=}
|
||||||
|
*/
|
||||||
|
List<SchStudentDto> getSubStuList(String schSerId, Integer schSystemId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按班级批量查学生(圈人:班级范围一次多班)。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3}
|
||||||
|
*/
|
||||||
|
List<SchStudentDto> getClaArrStuList(List<Integer> classIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按学生ID批量查(圈人:指定学员范围)。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3}
|
||||||
|
*/
|
||||||
|
List<SchStudentDto> getStuIdsList(List<Integer> stuIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。
|
||||||
|
* 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。
|
||||||
|
* 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=}
|
||||||
|
*/
|
||||||
|
List<SchTreeNodeDto> getStuTree(Integer schId);
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.service;
|
package com.yunhai.campusnotice.service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.service;
|
package com.yunhai.campusnotice.service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
package com.yunhai.notice.service;
|
package com.yunhai.campusnotice.service;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.UpFile;
|
import com.yunhai.campusnotice.entity.UpFile;
|
||||||
import com.yunhai.notice.model.notice.UpFileVo;
|
import com.yunhai.campusnotice.model.notice.UpFileVo;
|
||||||
import com.yunhai.notice.model.notice.UpFileResultVo;
|
import com.yunhai.campusnotice.model.notice.UpFileResultVo;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
package com.yunhai.campusnotice.service;
|
||||||
|
|
||||||
|
import com.yunhai.campusnotice.entity.Notice;
|
||||||
|
import com.yunhai.campusnotice.model.BasePageResult;
|
||||||
|
import com.yunhai.campusnotice.model.notice.*;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知公告业务接口。
|
||||||
|
*/
|
||||||
|
public interface INoticeService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发通知:以当前登录用户为发布人入库。
|
||||||
|
* 按 request.publishType 分流(改造方案 4.1):
|
||||||
|
* direct/空 = 直接发布(兼容旧 Create 链路;携带未来 timingSendTime 时置 status=1 待调度);
|
||||||
|
* draft = 存为草稿;approve = 提交审批(request.approverAccounts 选人,或签)。
|
||||||
|
*
|
||||||
|
* @return 新建通知 ID(用于后续回填附件 TopicID 等)
|
||||||
|
*/
|
||||||
|
Integer publish(NoticeRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存为草稿(status=0):仅存内容 + 范围快照 + 附件,不圈人、不发短信、不落接收人。
|
||||||
|
*/
|
||||||
|
Integer saveDraft(NoticeRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 草稿箱列表(status=0,本人草稿):返回重要程度 type/typeDesc、定时标注与 timingSendTime。
|
||||||
|
*/
|
||||||
|
BasePageResult<NoticeVo> draftList(NoticeQuery query, Pageable pageable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交审批(status: 0/3 → 2):发布人选定审批人(可多人,或签),
|
||||||
|
* 后端逐个校验审批人资格;写 notice_approval 提交行并 upsert 审批人记忆表。
|
||||||
|
*/
|
||||||
|
void submit(Integer serid, List<String> approverAccounts, String opinion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批通过(或签):任一审批人通过即 status: 2→1(CAS),并触发真实发布;
|
||||||
|
* 已被他人先行处理时抛业务冲突提示(带实际处理人与时间)。
|
||||||
|
*/
|
||||||
|
void approve(Integer serid, String opinion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批驳回(或签):任一审批人驳回即 status: 2→3(CAS),记录意见;发布人可改后重新提交。
|
||||||
|
*/
|
||||||
|
void reject(Integer serid, String opinion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批列表(待审/已审合一):按 schId + 审批人快照含当前账号过滤。
|
||||||
|
*
|
||||||
|
* @param auditState pending(默认)=待审 status=2;reviewed=已审 status∈{1,3}(含或签下他人已处理)
|
||||||
|
*/
|
||||||
|
BasePageResult<ApprovalListVo> approvalList(String auditState, NoticeQuery query, Pageable pageable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人记忆回显:当前发布人最近一次所选审批人组合,无记忆返回空。
|
||||||
|
*/
|
||||||
|
ApproverSuggestVo approverSuggest();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人解析:教师树节点ID(用户中心用户ID)与登录账号互转。
|
||||||
|
* ids 按用户ID解析出账号,accounts 按账号解析回ID;单项解析失败跳过不中断。
|
||||||
|
*/
|
||||||
|
List<ApproverResolveVo> approverResolve(List<Integer> ids, List<String> accounts);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑通知:草稿/已驳回/已发布(旧链路,无推送范围)可编辑;
|
||||||
|
* 待审批禁止编辑;新版表单已发布(存在范围快照)禁止编辑(先撤回后重发)。
|
||||||
|
*/
|
||||||
|
void edit(Integer id, NoticeRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除通知:仅草稿、已驳回、已发布(本人)可删,待审批不可删;
|
||||||
|
* 级联清理阅读记录、接收人、推送范围、审批记录、附件关联。
|
||||||
|
*/
|
||||||
|
void deleteBatch(List<Integer> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回通知:仅已发布且未撤回的通知可撤回,置 isPulled=1。
|
||||||
|
*/
|
||||||
|
void pull(Integer id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 置顶/取消置顶。
|
||||||
|
*/
|
||||||
|
void setTopmost(Integer id, Integer topmost);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转发通知:基于源通知创建新通知(已发布),parentId 关联源通知。
|
||||||
|
*/
|
||||||
|
Integer forward(Integer sourceId, NoticeRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户端列表:仅展示已发布、已到点、且对当前账号可见的通知。
|
||||||
|
* 可见性(改造方案 3.2):无推送范围维持「全校广播」现状;
|
||||||
|
* 存在范围的通知须 notice_receiver 快照命中当前账号(学员 receive_type=2 / 教师 receive_type=1)。
|
||||||
|
*/
|
||||||
|
NoticePageResult<NoticeVo> userList(NoticeQuery query, Pageable pageable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理端列表:按条件查询全部通知(管理员查看全部)。
|
||||||
|
*/
|
||||||
|
BasePageResult<NoticeVo> adminList(NoticeQuery query, Pageable pageable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学员推送范围树(学段→年级→班级 + 全校根,数据源 bschclass),
|
||||||
|
* 供表单「推送范围」胶囊按钮渲染。
|
||||||
|
*/
|
||||||
|
List<Map<String, Object>> scopeTree();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按已选范围预估接收人数(学员=范围展开去重账号数 + 教师=receiveList 定向名单数)。
|
||||||
|
*/
|
||||||
|
ScopePreviewVo scopePreview(List<PushScopeItem> scopes, String receiveList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已发送通知阅读回执:按 年级→班级→学生 分组返回(身份快照),
|
||||||
|
* 学生带已读/未读状态与阅读时间(前端着色),教师接收行归「教师」组。
|
||||||
|
*/
|
||||||
|
ReadReceiptVo readReceipt(Integer serid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。
|
||||||
|
* 草稿/待审批/已驳回态仅发布人本人或该轮审批人可打开(改造方案 3.2 访问校验补口)。
|
||||||
|
*/
|
||||||
|
NoticeVo detail(Integer id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑回显详情(不标记已读,访问校验同 {@link #detail})。
|
||||||
|
*/
|
||||||
|
NoticeVo getDetail(Integer id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记已读:为当前用户写入(幂等)该通知的阅读记录,
|
||||||
|
* 学员阅读时按接收快照(缺失时 bschclass 实时兜底)写入学校/年级/班级身份快照。
|
||||||
|
*/
|
||||||
|
void markRead(Integer id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一键全部已读(批量化):当前用户所有可见的已发布通知批量插入阅读记录。
|
||||||
|
*
|
||||||
|
* @return 已读条数
|
||||||
|
*/
|
||||||
|
int initializeRed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追加接收人(事务化):并入接收人列表并重算 allowcount(范围通知含学员数)。
|
||||||
|
*/
|
||||||
|
void receiveUsers(Integer noticeId, String users);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上一篇/下一篇通知ID(SQL 相邻查询)。
|
||||||
|
*
|
||||||
|
* @param serid 当前通知ID
|
||||||
|
* @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列)
|
||||||
|
* @return {prevId, nextId}
|
||||||
|
*/
|
||||||
|
Map<String, Object> getNextPrev(Integer serid, String type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取某通知的阅读明细列表。
|
||||||
|
*/
|
||||||
|
List<NoticeVo.ReadDetail> getReadDetail(Integer noticeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阅读情况分组(已读/未读,旧接口保留过渡;新版表单走 {@link #readReceipt})。
|
||||||
|
*/
|
||||||
|
List<Map<String, Object>> getReadDetailGrouped(Integer serid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 催收未读:仅 receiptFlag=1 的通知可用;
|
||||||
|
* 催收对象优先 notice_receiver 快照(含学员),回退 receiveList JSON(历史数据)。
|
||||||
|
*/
|
||||||
|
void expedite(Integer serid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时发送调度(TimingPublishJob 每分钟触发):
|
||||||
|
* 扫描 status=1 且 timingSendTime<=now 且 timing_published=0 的通知,
|
||||||
|
* 逐条 CAS 抢占(幂等)后执行真实发布。
|
||||||
|
*
|
||||||
|
* @return 本次真实发布条数
|
||||||
|
*/
|
||||||
|
int timingPublishDue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。
|
||||||
|
*/
|
||||||
|
NoticeStatisticsVo statistics(NoticeQuery query, LocalDateTime start, LocalDateTime end);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知详情视图(含附件、接收人、推送范围、审批轨迹),供 Controller 返回。
|
||||||
|
*/
|
||||||
|
NoticeDetailVo getDetailMap(Integer serid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已发列表附件批量查询(消除 N+1)。
|
||||||
|
*/
|
||||||
|
Map<Integer, List<Integer>> batchFileIds(List<Integer> noticeIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查看通知详情视图(含附件、接收人)。
|
||||||
|
* type=1 时先标记已读再返回,type=0 时直接返回不标记。
|
||||||
|
*/
|
||||||
|
NoticeDetailVo detailMap(Integer serid, Integer type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一键全部已读并返回结果(count = 已读条数)。
|
||||||
|
*/
|
||||||
|
InitializeRedVo initializeRedResult();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量下载通知全部附件(打包 zip):查通知标题后调文件域打包下载。
|
||||||
|
*/
|
||||||
|
void downloadAllFiles(Integer dataid, HttpServletResponse response);
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.service;
|
package com.yunhai.campusnotice.service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.service;
|
package com.yunhai.campusnotice.service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.service;
|
package com.yunhai.campusnotice.service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package com.yunhai.campusnotice.service.impl;
|
||||||
|
|
||||||
|
import com.yunhai.campusnotice.model.SchTeaList;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchClassDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchGradeDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchInfoDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchStudentDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchSystemDto;
|
||||||
|
import com.yunhai.campusnotice.model.bsch.SchTreeNodeDto;
|
||||||
|
import com.yunhai.campusnotice.service.IClassService;
|
||||||
|
import com.yunhai.platform.apistd.BizException;
|
||||||
|
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.client.RestClientException;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级管理(bschclass)开放接口实现。
|
||||||
|
* bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。
|
||||||
|
* <p>推送范围树/圈人/身份快照统一经本门面(改造方案 2.0):
|
||||||
|
* 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、
|
||||||
|
* 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、
|
||||||
|
* 学生树 SchGroup/GetStuTree。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ClassService implements IClassService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ClassService.class);
|
||||||
|
|
||||||
|
/** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */
|
||||||
|
@Value("${yh.open.bschclass:}")
|
||||||
|
private String bschClassHost;
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
public ClassService() {
|
||||||
|
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||||
|
factory.setConnectTimeout(5000);
|
||||||
|
factory.setReadTimeout(10000);
|
||||||
|
this.restTemplate = new RestTemplate(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchTeaList> getSchTeaList(int schId) {
|
||||||
|
return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId,
|
||||||
|
new ParameterizedTypeReference<List<SchTeaList>>() {
|
||||||
|
}, "获取教师数据", "schId=" + schId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SchInfoDto getSchFirst(int schId) {
|
||||||
|
requireHost();
|
||||||
|
String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId;
|
||||||
|
try {
|
||||||
|
ResponseEntity<SchInfoDto> resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class);
|
||||||
|
return resp.getBody();
|
||||||
|
} catch (RestClientException e) {
|
||||||
|
log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e);
|
||||||
|
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchSystemDto> getSchLenList(String schSerId) {
|
||||||
|
return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId,
|
||||||
|
new ParameterizedTypeReference<List<SchSystemDto>>() {
|
||||||
|
}, "获取学段列表", "schSerId=" + schSerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchGradeDto> getSchGradeList(String schSerId) {
|
||||||
|
return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId,
|
||||||
|
new ParameterizedTypeReference<List<SchGradeDto>>() {
|
||||||
|
}, "获取年级列表", "schSerId=" + schSerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchClassDto> getSchClassList(String schSerId) {
|
||||||
|
return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId,
|
||||||
|
new ParameterizedTypeReference<List<SchClassDto>>() {
|
||||||
|
}, "获取班级列表", "schSerId=" + schSerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchStudentDto> getSchStuList(String schSerId) {
|
||||||
|
return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId,
|
||||||
|
new ParameterizedTypeReference<List<SchStudentDto>>() {
|
||||||
|
}, "获取学校学生", "schSerId=" + schSerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchStudentDto> getGradeStuList(String schSerId, Integer gradeId) {
|
||||||
|
return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId,
|
||||||
|
new ParameterizedTypeReference<List<SchStudentDto>>() {
|
||||||
|
}, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchStudentDto> getSubStuList(String schSerId, Integer schSystemId) {
|
||||||
|
return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId,
|
||||||
|
new ParameterizedTypeReference<List<SchStudentDto>>() {
|
||||||
|
}, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchStudentDto> getClaArrStuList(List<Integer> classIds) {
|
||||||
|
String csv = joinIds(classIds);
|
||||||
|
return exchangeList(bschClassHost + "/oapi/OAStu/GetClaArrStulist?ClassIDs=" + csv,
|
||||||
|
new ParameterizedTypeReference<List<SchStudentDto>>() {
|
||||||
|
}, "获取班级学生", "classIds=" + csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchStudentDto> getStuIdsList(List<Integer> stuIds) {
|
||||||
|
String csv = joinIds(stuIds);
|
||||||
|
return exchangeList(bschClassHost + "/oapi/OAStu/GetStuIdslist?stuids=" + csv,
|
||||||
|
new ParameterizedTypeReference<List<SchStudentDto>>() {
|
||||||
|
}, "获取指定学生", "stuIds=" + csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchTreeNodeDto> getStuTree(Integer schId) {
|
||||||
|
return exchangeList(bschClassHost + "/oapi/SchGroup/GetStuTree?schId=" + schId,
|
||||||
|
new ParameterizedTypeReference<List<SchTreeNodeDto>>() {
|
||||||
|
}, "获取学生树", "schId=" + schId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================ 通用调用 ============================
|
||||||
|
|
||||||
|
private <T> List<T> exchangeList(String url, ParameterizedTypeReference<List<T>> type,
|
||||||
|
String action, String arg) {
|
||||||
|
requireHost();
|
||||||
|
try {
|
||||||
|
// GET + Accept: application/json,保持与既有 GetSchteaList 调用一致
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||||
|
ResponseEntity<List<T>> resp = restTemplate.exchange(
|
||||||
|
url, HttpMethod.GET, new HttpEntity<>(headers), type);
|
||||||
|
List<T> list = resp.getBody();
|
||||||
|
log.info("[bschclass] {} 完成, {} 返回 {} 条", action, arg, list == null ? 0 : list.size());
|
||||||
|
return list != null ? list : Collections.emptyList();
|
||||||
|
} catch (RestClientException e) {
|
||||||
|
log.error("[bschclass] {} 失败, {}, url={}", action, arg, url, e);
|
||||||
|
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, action + "失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireHost() {
|
||||||
|
if (!StringUtils.hasText(bschClassHost)) {
|
||||||
|
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "班级管理接口未配置(yh.open.bschclass)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String joinIds(List<Integer> ids) {
|
||||||
|
if (ids == null || ids.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return ids.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-6
@@ -1,10 +1,10 @@
|
|||||||
package com.yunhai.notice.service.impl;
|
package com.yunhai.campusnotice.service.impl;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.ContactsGroup;
|
import com.yunhai.campusnotice.entity.ContactsGroup;
|
||||||
import com.yunhai.notice.entity.ContactsGroupUser;
|
import com.yunhai.campusnotice.entity.ContactsGroupUser;
|
||||||
import com.yunhai.notice.repository.ContactsGroupRepository;
|
import com.yunhai.campusnotice.repository.ContactsGroupRepository;
|
||||||
import com.yunhai.notice.repository.ContactsGroupUserRepository;
|
import com.yunhai.campusnotice.repository.ContactsGroupUserRepository;
|
||||||
import com.yunhai.notice.service.IContactsService;
|
import com.yunhai.campusnotice.service.IContactsService;
|
||||||
import com.yunhai.platform.auth.LoginUser;
|
import com.yunhai.platform.auth.LoginUser;
|
||||||
import com.yunhai.platform.auth.UserContextHolder;
|
import com.yunhai.platform.auth.UserContextHolder;
|
||||||
import com.yunhai.platform.apistd.BizException;
|
import com.yunhai.platform.apistd.BizException;
|
||||||
+6
-6
@@ -1,10 +1,10 @@
|
|||||||
package com.yunhai.notice.service.impl;
|
package com.yunhai.campusnotice.service.impl;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.Notice;
|
import com.yunhai.campusnotice.entity.Notice;
|
||||||
import com.yunhai.notice.entity.SysDict;
|
import com.yunhai.campusnotice.entity.SysDict;
|
||||||
import com.yunhai.notice.repository.NoticeRepository;
|
import com.yunhai.campusnotice.repository.NoticeRepository;
|
||||||
import com.yunhai.notice.repository.SysDictRepository;
|
import com.yunhai.campusnotice.repository.SysDictRepository;
|
||||||
import com.yunhai.notice.service.IDictService;
|
import com.yunhai.campusnotice.service.IDictService;
|
||||||
import com.yunhai.platform.auth.LoginUser;
|
import com.yunhai.platform.auth.LoginUser;
|
||||||
import com.yunhai.platform.auth.UserContextHolder;
|
import com.yunhai.platform.auth.UserContextHolder;
|
||||||
import com.yunhai.platform.apistd.BizException;
|
import com.yunhai.platform.apistd.BizException;
|
||||||
+6
-6
@@ -1,10 +1,10 @@
|
|||||||
package com.yunhai.notice.service.impl;
|
package com.yunhai.campusnotice.service.impl;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.UpFile;
|
import com.yunhai.campusnotice.entity.UpFile;
|
||||||
import com.yunhai.notice.model.notice.UpFileVo;
|
import com.yunhai.campusnotice.model.notice.UpFileVo;
|
||||||
import com.yunhai.notice.model.notice.UpFileResultVo;
|
import com.yunhai.campusnotice.model.notice.UpFileResultVo;
|
||||||
import com.yunhai.notice.repository.UpFileRepository;
|
import com.yunhai.campusnotice.repository.UpFileRepository;
|
||||||
import com.yunhai.notice.service.IFileService;
|
import com.yunhai.campusnotice.service.IFileService;
|
||||||
import com.yunhai.platform.apistd.BizException;
|
import com.yunhai.platform.apistd.BizException;
|
||||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -1,10 +1,10 @@
|
|||||||
package com.yunhai.notice.service.impl;
|
package com.yunhai.campusnotice.service.impl;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.SmsModule;
|
import com.yunhai.campusnotice.entity.SmsModule;
|
||||||
import com.yunhai.notice.entity.SmsSchoolConfig;
|
import com.yunhai.campusnotice.entity.SmsSchoolConfig;
|
||||||
import com.yunhai.notice.repository.SmsModuleRepository;
|
import com.yunhai.campusnotice.repository.SmsModuleRepository;
|
||||||
import com.yunhai.notice.repository.SmsSchoolConfigRepository;
|
import com.yunhai.campusnotice.repository.SmsSchoolConfigRepository;
|
||||||
import com.yunhai.notice.service.ISmsService;
|
import com.yunhai.campusnotice.service.ISmsService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
+8
-8
@@ -1,12 +1,12 @@
|
|||||||
package com.yunhai.notice.service.impl;
|
package com.yunhai.campusnotice.service.impl;
|
||||||
|
|
||||||
import com.yunhai.notice.entity.NoticeTemplate;
|
import com.yunhai.campusnotice.entity.NoticeTemplate;
|
||||||
import com.yunhai.notice.entity.TemplateType;
|
import com.yunhai.campusnotice.entity.TemplateType;
|
||||||
import com.yunhai.notice.entity.UpFile;
|
import com.yunhai.campusnotice.entity.UpFile;
|
||||||
import com.yunhai.notice.repository.NoticeTemplateRepository;
|
import com.yunhai.campusnotice.repository.NoticeTemplateRepository;
|
||||||
import com.yunhai.notice.repository.TemplateTypeRepository;
|
import com.yunhai.campusnotice.repository.TemplateTypeRepository;
|
||||||
import com.yunhai.notice.repository.UpFileRepository;
|
import com.yunhai.campusnotice.repository.UpFileRepository;
|
||||||
import com.yunhai.notice.service.ITemplateService;
|
import com.yunhai.campusnotice.service.ITemplateService;
|
||||||
import com.yunhai.platform.apistd.BizException;
|
import com.yunhai.platform.apistd.BizException;
|
||||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
package com.yunhai.notice.service.impl;
|
package com.yunhai.campusnotice.service.impl;
|
||||||
|
|
||||||
import com.yunhai.notice.model.SchTeaList;
|
import com.yunhai.campusnotice.model.SchTeaList;
|
||||||
import com.yunhai.notice.service.IClassService;
|
import com.yunhai.campusnotice.service.IClassService;
|
||||||
import com.yunhai.notice.service.IUserCenterService;
|
import com.yunhai.campusnotice.service.IUserCenterService;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
||||||
import com.yunhai.platform.apistd.BizException;
|
import com.yunhai.platform.apistd.BizException;
|
||||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.service.sms;
|
package com.yunhai.campusnotice.service.sms;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.yunhai.notice.service.sms;
|
package com.yunhai.campusnotice.service.sms;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
package com.yunhai.notice.utils;
|
package com.yunhai.campusnotice.utils;
|
||||||
|
|
||||||
import com.yunhai.notice.model.BasePageQuery;
|
import com.yunhai.campusnotice.model.BasePageQuery;
|
||||||
import com.yunhai.notice.model.BasePageResult;
|
import com.yunhai.campusnotice.model.BasePageResult;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user