Compare commits
31
Commits
5972f3b7e1
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
695ba1530e | ||
|
|
c2f6f42a3c | ||
|
|
2b9389fcb8 | ||
|
|
44d3cd0326 | ||
|
|
3ff9b48a19 | ||
|
|
20325bed00 | ||
|
|
0efde94a74 | ||
|
|
4211b72041 | ||
|
|
e9e1c1722d | ||
|
|
583d06f3d0 | ||
|
|
83182cb538 | ||
|
|
bbcb865055 | ||
|
|
cf8d6da5b5 | ||
|
|
ae1bd4cdb0 | ||
|
|
f1059fd92b | ||
|
|
5785a2c10e | ||
|
|
10c791d034 | ||
|
|
04e708c4fe | ||
|
|
d658ab5b6a | ||
|
|
ae8327807e | ||
|
|
74ba80ddf9 | ||
|
|
6ff1d07f31 | ||
|
|
7e945d310d | ||
|
|
3e9be2bd54 | ||
|
|
7112b7ed40 | ||
|
|
f711f1c5e5 | ||
|
|
6d5df72d2f | ||
|
|
0225df144c | ||
|
|
461847e4ed | ||
|
|
951ed3f00a | ||
|
|
7471dbe66f |
@@ -26,7 +26,10 @@ jobs:
|
||||
options: --network infra-infra-net
|
||||
steps:
|
||||
- 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
|
||||
- name: 拉取源码(内网无 GitHub,手动 clone 本仓库)
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -53,14 +56,20 @@ jobs:
|
||||
- name: 构建 publish 产物(dotnet sdk 容器,复用宿主 nuget 缓存)
|
||||
run: |
|
||||
set -e
|
||||
docker rm -f dotnetbuild 2>/dev/null || true
|
||||
docker run -d --name dotnetbuild \
|
||||
# 构建容器名必须全局唯一:runner 挂的是宿主 docker.sock,容器名在整个 daemon 上共享
|
||||
# 命名空间。固定名会在多仓/多条 workflow 并发时互相 docker rm -f 掉对方正在构建的
|
||||
# 容器,表现为无编译报错的 exit 137(SIGKILL,2026-10-21 实测复现)。
|
||||
# GITHUB_RUN_ID 实例级唯一,再拼时间戳兜底。
|
||||
BUILD_CONTAINER="dotnetbuild-${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 \
|
||||
-v /opt/docker/projman-dotnet-nuget:/root/.nuget/packages \
|
||||
-w /build mcr.microsoft.com/dotnet/sdk:5.0 sleep 3600
|
||||
docker cp "${PWD}/." dotnetbuild:/build/
|
||||
docker exec dotnetbuild sh -c "find /build -maxdepth 2 -iname nuget.config -exec sed -i 's|//nexus.yunhaixingtong.com|//nexus:8081|g' {} +"
|
||||
docker exec dotnetbuild sh -c '
|
||||
docker cp "${PWD}/." "$BUILD_CONTAINER":/build/
|
||||
docker exec "$BUILD_CONTAINER" sh -c "find /build -maxdepth 2 -iname nuget.config -exec sed -i 's|//nexus.yunhaixingtong.com|//nexus:8081|g' {} +"
|
||||
docker exec "$BUILD_CONTAINER" sh -c '
|
||||
set -e
|
||||
root_sln=$(ls /build/*.sln 2>/dev/null | head -1 || true)
|
||||
root_csproj=$(ls /build/*.csproj 2>/dev/null | head -1 || true)
|
||||
@@ -88,8 +97,8 @@ jobs:
|
||||
echo "publish target: $target"
|
||||
'
|
||||
mkdir -p "${PWD}/publish"
|
||||
docker cp dotnetbuild:/app/. "${PWD}/publish/"
|
||||
docker rm -f dotnetbuild
|
||||
docker cp "$BUILD_CONTAINER":/app/. "${PWD}/publish/"
|
||||
docker rm -f "$BUILD_CONTAINER"
|
||||
# 入口脚本:runtimeconfig.json 是可执行项目唯一标志(X.runtimeconfig.json <-> X.dll),
|
||||
# 类库 deps.json 无 runtimeconfig 兄弟文件,天然不会误选
|
||||
entry=$(ls "${PWD}"/publish/*.runtimeconfig.json | head -1)
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
# ============ 多阶段构建:.NET 5.0 ============
|
||||
# 构建阶段
|
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# 复制解决方案与全部项目文件(利用层缓存)
|
||||
COPY YHDocPrintCore.sln* ./
|
||||
COPY src/*/*.csproj ./
|
||||
RUN for f in src/*/*.csproj; do \
|
||||
mkdir -p "$(dirname "$f")" && mv "$f" "$(dirname "$f")/"; \
|
||||
done \
|
||||
&& dotnet restore YHDocPrintCore.sln
|
||||
|
||||
# 复制源码并发布
|
||||
COPY src/ src/
|
||||
RUN dotnet publish src/YHDocPrintCore.WebApi/YHDocPrintCore.WebApi.csproj \
|
||||
-c Release -o /app/publish --no-restore
|
||||
|
||||
# 运行阶段
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
# 日志目录(接口访问日志唯一输出,挂载卷便于采集)
|
||||
RUN mkdir -p /app/logs
|
||||
VOLUME ["/app/logs"]
|
||||
|
||||
EXPOSE 80
|
||||
ENV ASPNETCORE_URLS=http://+:80
|
||||
|
||||
# 生产配置通过环境变量注入(ConnectionStrings__SqlServer / Redis:ConnectionString 等)
|
||||
ENTRYPOINT ["dotnet", "YHDocPrintCore.WebApi.dll"]
|
||||
@@ -1,53 +0,0 @@
|
||||
# DocprintService
|
||||
|
||||
> 本仓库由 projman 自动创建并注入 CI(`.gitea/workflows/build.yml`)与 Dockerfile(`docker/Dockerfile`)。
|
||||
> 推送代码到 main 分支即自动构建镜像并推送到 Nexus,projman 收到通知后建档待部署,由运维在 projman「后端发布」手动点部署。
|
||||
|
||||
## 首次推送本地代码到本仓库
|
||||
|
||||
> 目标:把本地已有代码推上去,同时**保留** projman 注入的文件(`build.yml`、`Dockerfile`)。
|
||||
|
||||
```bash
|
||||
# 1. 进入本地代码目录
|
||||
cd /your/local/code/dir
|
||||
|
||||
# 2. 初始化 git(目录已有 .git 也安全,git init 幂等无副作用)
|
||||
git init
|
||||
git branch -M main
|
||||
|
||||
# 3. 先把本地代码提交成一个 commit(关键!不提交直接 merge 会丢失本地代码)
|
||||
git add -A
|
||||
git commit -m "init local code"
|
||||
|
||||
# 4. 关联远程仓库(幂等:已存在 origin 则改地址,不报错)
|
||||
git remote add origin http://gitea.yunhaixingtong.com/docprint/DocprintService.git 2>/dev/null \
|
||||
|| git remote set-url origin http://gitea.yunhaixingtong.com/docprint/DocprintService.git
|
||||
|
||||
# 5. 拉取远程信息(只下载,不合并)
|
||||
git fetch origin
|
||||
|
||||
# 6. 合并远程 main(关键步骤)
|
||||
# 本地与远程是两条独立历史,需允许合并不相关历史。
|
||||
# 此步会把 projman 注入的 build.yml 与 Dockerfile 合并进你的本地工作区。
|
||||
git merge origin/main --allow-unrelated-histories -m "merge projman injected files"
|
||||
|
||||
# 7. 查看状态(确认 .gitea/workflows/build.yml、docker/Dockerfile 已在本地)
|
||||
git status
|
||||
|
||||
# 8. 提交并推送
|
||||
git add -A
|
||||
git commit -m "sync local code" 2>/dev/null || true
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## 推送后验证
|
||||
|
||||
push 触发 CI 构建,前往 Gitea 仓库 → **Actions** 查看日志;构建成功后镜像推送到 Nexus,projman 建档待部署,在 projman 应用详情「后端发布」手动点部署。
|
||||
|
||||
## 禁止写法(会导致注入文件被删)
|
||||
|
||||
`git reset origin/main` + `git add -A` 会把远程已注入、但你本地没有的文件(`build.yml`、`Dockerfile`)当作"删除"提交并推送,导致这些文件被删、CI 失败。务必用 `git merge` 而非 `git reset`,且 merge 前先提交本地代码。
|
||||
|
||||
## 关于 Dockerfile
|
||||
|
||||
projman 注入的 Dockerfile 仅在仓库不存在时写入;你自备的 Dockerfile 优先,不会被覆盖。
|
||||
+3
-1
@@ -8,10 +8,12 @@ FROM mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION}
|
||||
|
||||
# .NET 5 基础镜像基于 Debian 10(已 EOL),apt 源须切 archive.debian.org 才能装包
|
||||
# curl 供 projman compose healthcheck(test: CMD curl -f ...)使用;tzdata 设上海时区
|
||||
# wkhtmltopdf 供审批单 PDF 接口(DwonWYSQD)本地转换 HTML(Debian 源为 0.12.5,无需 --enable-local-file-access);
|
||||
# fonts-wqy-zenhei 提供中文字体(缺失则 PDF 中文显示方块)
|
||||
RUN sed -i 's|deb.debian.org|archive.debian.org|g; s|security.debian.org|archive.debian.org|g' /etc/apt/sources.list \
|
||||
&& echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99no-valid-until \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends curl tzdata \
|
||||
&& apt-get install -y --no-install-recommends curl tzdata wkhtmltopdf fonts-wqy-zenhei \
|
||||
&& ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -21,5 +21,8 @@ namespace YhCore.Common.Config
|
||||
|
||||
/// <summary>文件访问 URL 前缀(对应 Java FILE_WEB_PATH,由 Startup 注入)</summary>
|
||||
public static string FileWebPath { get; set; }
|
||||
|
||||
/// <summary>wkhtmltopdf 可执行文件路径(审批单 PDF 用;空则用 PATH 中的 wkhtmltopdf,由 Startup 注入)</summary>
|
||||
public static string WkHtmlToPdfPath { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
using Newtonsoft.Json;
|
||||
using Yh.Platform.Abstractions;
|
||||
|
||||
namespace YhCore.Common.Result
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一响应(对应 Java yh-api-std:com.yunhai.platform.apistd.ApiResponse)。
|
||||
/// 字段与 Java 对齐:code/msg/data(Newtonsoft CamelCase 序列化输出小写)。
|
||||
/// 信封字段 code/msg/data 以 [JsonProperty] 显式钉死小写——全局序列化已改为
|
||||
/// DefaultContractResolver 原样输出(data 内容对齐旧项目混合大小写字段),信封不受影响。
|
||||
/// 注意:Controller 返回值由全局过滤器 GlobalWrapResultFilter 自动包装;
|
||||
/// 不需要包装的接口用 [NoWrapResult] 特性显式排除(CAS 协议端点、文件下载等)。
|
||||
/// </summary>
|
||||
public class ApiResult<T>
|
||||
{
|
||||
/// <summary>0=成功,非 0=失败(对齐 Java ErrorCode)</summary>
|
||||
/// <summary>0=成功,非 0=失败(对齐 Java ErrorCode )</summary>
|
||||
[JsonProperty("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>提示消息(对齐 Java msg)</summary>
|
||||
[JsonProperty("msg")]
|
||||
public string Msg { get; set; }
|
||||
|
||||
[JsonProperty("data")]
|
||||
public T Data { get; set; }
|
||||
|
||||
public static ApiResult<T> Ok(T data) => new() { Code = ErrorCode.Success.Code, Msg = "", Data = data };
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- 平台模型与信封(DException/ErrorCode/BoaUser 等):从 Nexus nuget-group 引用(原为本地源项目引用) -->
|
||||
<PackageReference Include="Yh.Platform.Abstractions" Version="1.0.0-preview.1" />
|
||||
<PackageReference Include="Yh.Platform.Abstractions" Version="1.0.0-preview.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -68,22 +68,10 @@ namespace YhCore.Model.Dto
|
||||
/// <summary>全部字典类别(typeID, typeName),对齐旧 EnumConfig.Dictionary 的 Description。</summary>
|
||||
public static readonly (string Id, string Name)[] AllTypes =
|
||||
{
|
||||
("DEPT", "部门类别"),
|
||||
("BXBM", "报修部门"),
|
||||
("WYFL", "文印分类"),
|
||||
("YZBM", "印制版面"),
|
||||
("YZFL", "印制分类"),
|
||||
("LeaveType", "请假类别"),
|
||||
("FoodType", "物品类别"),
|
||||
("Unit", "单位"),
|
||||
("VenueType", "场馆类别"),
|
||||
("Vefacilities", "场馆设施"),
|
||||
("Time", "场馆时间"),
|
||||
("Repeat", "场馆重复"),
|
||||
("LevelOrder", "奖次"),
|
||||
("filetype", "文件类型"),
|
||||
("doclevel", "公文级别"),
|
||||
("XKCategory", "选课类型"),
|
||||
("filetype", "文件类型")
|
||||
};
|
||||
|
||||
/// <summary>GetSysDic 的 Type(int) → typeID;越界返回 null。</summary>
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace YhCore.Model.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件信息(旧 BestSchool.Entity.FileObj,GetEnclosure/FileSave/FileSaveImage/FileMergeSave 共用)。
|
||||
/// fileurl 为相对路径(如 /UploadFile/docprint/20260101/{guid}.jpg,与旧库数据格式一致);
|
||||
/// fileurl 为相对路径(如 /upload/docprint/20260101/{guid}.jpg,与旧库数据格式一致);
|
||||
/// host 为文件访问主机前缀(旧 AuthConfig.weboa_url,新 Config.FileWebPath)。
|
||||
/// </summary>
|
||||
public class FileObj
|
||||
@@ -19,7 +19,7 @@ namespace YhCore.Model.Dto
|
||||
/// <summary>文件真实名称(含扩展名)。</summary>
|
||||
public string filename { get; set; }
|
||||
|
||||
/// <summary>相对访问路径(如 /UploadFile/docprint/20260101/{guid}.jpg)。</summary>
|
||||
/// <summary>相对访问路径(如 /upload/docprint/20260101/{guid}.jpg)。</summary>
|
||||
public string fileurl { get; set; }
|
||||
|
||||
/// <summary>文件主机前缀(Config.FileWebPath)。</summary>
|
||||
|
||||
@@ -19,6 +19,13 @@ namespace YhCore.Repository
|
||||
Task<bool> AnyAsync(Expression<Func<T, bool>> predicate);
|
||||
Task<long> CountAsync(Expression<Func<T, bool>> predicate = null);
|
||||
|
||||
// ---- 读取(追踪版)----
|
||||
// 查出的实体受变更追踪器管理,修改属性后直接 SaveChangesAsync 即可落库。
|
||||
// 仅供写场景使用,纯查询请用上面的 NoTracking 版本。
|
||||
Task<List<T>> FindTrackedAsync(Expression<Func<T, bool>> predicate);
|
||||
Task<List<T>> GetAllTrackedAsync();
|
||||
Task<T> FirstOrDefaultTrackedAsync(Expression<Func<T, bool>> predicate);
|
||||
|
||||
/// <summary>分页查询(页码从 0 开始),对应 PageUtil + PageVO</summary>
|
||||
Task<PageVO<T>> PageAsync(Expression<Func<T, bool>> predicate, int page, int size,
|
||||
Expression<Func<T, object>> orderBy = null, bool asc = true);
|
||||
|
||||
@@ -43,6 +43,21 @@ namespace YhCore.Repository
|
||||
return await DbSet.AsNoTracking().FirstOrDefaultAsync(predicate);
|
||||
}
|
||||
|
||||
public virtual async Task<List<T>> FindTrackedAsync(Expression<Func<T, bool>> predicate)
|
||||
{
|
||||
return await DbSet.Where(predicate).ToListAsync();
|
||||
}
|
||||
|
||||
public virtual async Task<List<T>> GetAllTrackedAsync()
|
||||
{
|
||||
return await DbSet.ToListAsync();
|
||||
}
|
||||
|
||||
public virtual async Task<T> FirstOrDefaultTrackedAsync(Expression<Func<T, bool>> predicate)
|
||||
{
|
||||
return await DbSet.FirstOrDefaultAsync(predicate);
|
||||
}
|
||||
|
||||
public virtual async Task<bool> AnyAsync(Expression<Func<T, bool>> predicate)
|
||||
{
|
||||
return await DbSet.AnyAsync(predicate);
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using OfficeOpenXml;
|
||||
using OfficeOpenXml.Style;
|
||||
|
||||
namespace YhCore.Service.Excel
|
||||
{
|
||||
/// <summary>
|
||||
/// EPPlus 多 Sheet 导出(移植自旧 Bsch.Common/DocPrint/ExcelHelper,
|
||||
/// 保留 RenderToExcelBytes + ExportDynamicSheet:动态列、大标题、自定义列宽、打印优化)。
|
||||
/// 注意:EPPlus 实际产出 xlsx 字节流,旧系统文件名沿用 .xls,Excel 可正常打开,保持原契约。
|
||||
/// </summary>
|
||||
public class ExcelHelper
|
||||
{
|
||||
/// <summary>导出多 Sheet Excel 字节流(listdt/listsheetname/listtitle 按下标一一对应)</summary>
|
||||
public static byte[] RenderToExcelBytes(string fileName, List<DataTable> listdt, List<string> listsheetname, List<string> listtitle, List<double> listcolwidth = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var package = new ExcelPackage();
|
||||
int n = 0;
|
||||
foreach (var dt in listdt)
|
||||
{
|
||||
string sheetname = "Sheet" + (n + 1).ToString();
|
||||
if (n < listsheetname.Count) sheetname = listsheetname[n];
|
||||
string title = "";
|
||||
if (n < listtitle.Count) title = listtitle[n];
|
||||
var ws1 = package.Workbook.Worksheets.Add(sheetname);
|
||||
ExportDynamicSheet(ws1, dt, title, "A", listcolwidth);
|
||||
n++;
|
||||
}
|
||||
return package.GetAsByteArray();
|
||||
}
|
||||
catch (Exception) { return Array.Empty<byte>(); }
|
||||
}
|
||||
|
||||
#region 动态列 Sheet 导出(通用方法)
|
||||
|
||||
/// <summary>
|
||||
/// 导出 动态列 + 自定义表头 + 自定义页码 + 打印优化
|
||||
/// </summary>
|
||||
public static void ExportDynamicSheet(ExcelWorksheet ws, DataTable dt, string sheetTitle, string printMark, List<double> listcolwidth = null)
|
||||
{
|
||||
// -------------------------- 行定义 --------------------------
|
||||
int titleRow = 1; // 大标题行
|
||||
int headerRow = 2; // 列标题行
|
||||
int dataStartRow = 3; // 数据开始行
|
||||
|
||||
int colCount = dt.Columns.Count;
|
||||
int rowCount = dt.Rows.Count;
|
||||
|
||||
// -------------------------- 1. 自定义大标题(合并行) --------------------------
|
||||
if (sheetTitle != "")
|
||||
{
|
||||
ws.Cells[titleRow, 1].Value = sheetTitle;
|
||||
ws.Cells[titleRow, 1, titleRow, colCount].Merge = true; // 合并单元格
|
||||
|
||||
// 标题样式
|
||||
var titleStyle = ws.Cells[titleRow, 1].Style;
|
||||
titleStyle.Font.Size = 12;
|
||||
titleStyle.Font.Bold = true;
|
||||
titleStyle.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||
titleStyle.VerticalAlignment = ExcelVerticalAlignment.Center;
|
||||
ws.Row(titleRow).Height = 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerRow = 1; // 列标题行
|
||||
dataStartRow = 2; // 数据开始行
|
||||
}
|
||||
|
||||
// -------------------------- 2. 动态列 自动填充表头 --------------------------
|
||||
for (int c = 0; c < colCount; c++)
|
||||
{
|
||||
ws.Cells[headerRow, c + 1].Value = dt.Columns[c].ColumnName;
|
||||
}
|
||||
|
||||
// -------------------------- 3. 动态列 自动填充数据 --------------------------
|
||||
for (int r = 0; r < rowCount; r++)
|
||||
{
|
||||
for (int c = 0; c < colCount; c++)
|
||||
{
|
||||
ws.Cells[dataStartRow + r, c + 1].Value = dt.Rows[r][c];
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------- 4. 自动换行 + 行高自适应(关键) --------------------------
|
||||
var dataRange = ws.Cells[headerRow, 1, dataStartRow + rowCount - 1, colCount];
|
||||
dataRange.Style.Font.Size = 10;
|
||||
dataRange.Style.WrapText = true;
|
||||
dataRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
||||
dataRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||
// 加细边框
|
||||
dataRange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
|
||||
dataRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
|
||||
dataRange.Style.Border.Left.Style = ExcelBorderStyle.Thin;
|
||||
dataRange.Style.Border.Right.Style = ExcelBorderStyle.Thin;
|
||||
|
||||
// 自动计算:所有列平均撑满 A4 宽度
|
||||
if (listcolwidth != null)
|
||||
{
|
||||
int colnum = listcolwidth.Count > colCount ? colCount : listcolwidth.Count;
|
||||
for (int i = 1; i <= colnum; i++)
|
||||
{
|
||||
ws.Column(i).Width = listcolwidth[i - 1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double totalWidth = 58;
|
||||
double perColWidth = (double)totalWidth / colCount;
|
||||
for (int i = 1; i <= colCount; i++)
|
||||
{
|
||||
ws.Column(i).Width = perColWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// 行高自适应(EPPlus 4.x 兼容)
|
||||
for (int r = headerRow; r <= headerRow + rowCount - 1; r++)
|
||||
{
|
||||
ws.Row(r).CustomHeight = true;
|
||||
}
|
||||
|
||||
// -------------------------- 5. 表头样式 --------------------------
|
||||
var headerRange = ws.Cells[headerRow, 1, headerRow, colCount];
|
||||
headerRange.Style.Font.Bold = true;
|
||||
ws.Row(headerRow).Height = 25;
|
||||
|
||||
// -------------------------- 6. 打印:每页重复标题行 --------------------------
|
||||
ws.PrinterSettings.RepeatRows = new ExcelAddress($"{headerRow}:{headerRow}");
|
||||
|
||||
// -------------------------- 7. 自定义页码(中文样式 + 字体大小) --------------------------
|
||||
// &P=当前页 &N=总页 12号字体 居中
|
||||
ws.HeaderFooter.OddFooter.CenteredText = "&14 第 &P 页 / 共 &N 页";
|
||||
|
||||
// -------------------------- 8. 打印格式优化 --------------------------
|
||||
ws.PrinterSettings.PaperSize = ePaperSize.A4;
|
||||
ws.PrinterSettings.Orientation = eOrientation.Portrait;
|
||||
|
||||
ws.PrinterSettings.LeftMargin = 0.4m;
|
||||
ws.PrinterSettings.RightMargin = 0.4m;
|
||||
ws.PrinterSettings.TopMargin = 0.8m;
|
||||
ws.PrinterSettings.BottomMargin = 1.2m;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using NPOI.HSSF.UserModel;
|
||||
using NPOI.SS.Formula.Eval;
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.XSSF.UserModel;
|
||||
|
||||
namespace YhCore.Service.Excel
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 NPOI 操作 Excel,无需 Office COM 组件(移植自旧 Bsch.Common/DocPrint/ExcelRender,
|
||||
/// 仅保留 6 个 WYSHRole Excel 接口用到的导出/导入成员;控制器用 File() 包装下载)。
|
||||
/// </summary>
|
||||
public class ExcelRender
|
||||
{
|
||||
static ExcelRender()
|
||||
{
|
||||
// ExportDT 列宽按 GB2312 字节长计算:net5 需注册 CodePages 提供程序后才能 GetEncoding(936)
|
||||
Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
|
||||
}
|
||||
|
||||
#region DataTable 导出为 .xls 字节流
|
||||
|
||||
/// <summary>
|
||||
/// DataTable 导出为 Excel 的 MemoryStream(HSSF .xls;65535 行自动分 sheet;列宽按 GB2312 字节长,上限 50)
|
||||
/// </summary>
|
||||
static MemoryStream ExportDT(DataTable dtSource, string strHeaderText)
|
||||
{
|
||||
HSSFWorkbook workbook = new HSSFWorkbook();
|
||||
HSSFSheet sheet = workbook.CreateSheet() as HSSFSheet;
|
||||
|
||||
HSSFCellStyle dateStyle = workbook.CreateCellStyle() as HSSFCellStyle;
|
||||
HSSFDataFormat format = workbook.CreateDataFormat() as HSSFDataFormat;
|
||||
dateStyle.DataFormat = format.GetFormat("yyyy-mm-dd");
|
||||
|
||||
//取得列宽
|
||||
int[] arrColWidth = new int[dtSource.Columns.Count];
|
||||
foreach (DataColumn item in dtSource.Columns)
|
||||
{
|
||||
arrColWidth[item.Ordinal] = Encoding.GetEncoding(936).GetBytes(item.ColumnName.ToString()).Length;
|
||||
}
|
||||
for (int i = 0; i < dtSource.Rows.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < dtSource.Columns.Count; j++)
|
||||
{
|
||||
int intTemp = Encoding.GetEncoding(936).GetBytes(dtSource.Rows[i][j].ToString()).Length;
|
||||
if (intTemp > arrColWidth[j])
|
||||
{
|
||||
arrColWidth[j] = intTemp;
|
||||
if (intTemp >= 50)
|
||||
arrColWidth[j] = 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
int rowIndex = 0;
|
||||
|
||||
foreach (DataRow row in dtSource.Rows)
|
||||
{
|
||||
#region 新建表,填充表头,填充列头,样式
|
||||
|
||||
if (rowIndex == 65535 || rowIndex == 0)
|
||||
{
|
||||
if (rowIndex != 0)
|
||||
{
|
||||
sheet = workbook.CreateSheet() as HSSFSheet;
|
||||
}
|
||||
|
||||
#region 列头及样式
|
||||
|
||||
{
|
||||
HSSFRow headerRow = sheet.CreateRow(0) as HSSFRow;
|
||||
|
||||
HSSFCellStyle headStyle = workbook.CreateCellStyle() as HSSFCellStyle;
|
||||
headStyle.Alignment = NPOI.SS.UserModel.HorizontalAlignment.Center;
|
||||
HSSFFont font = workbook.CreateFont() as HSSFFont;
|
||||
font.FontHeightInPoints = 10;
|
||||
font.Boldweight = 700;
|
||||
headStyle.SetFont(font);
|
||||
|
||||
foreach (DataColumn column in dtSource.Columns)
|
||||
{
|
||||
headerRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
|
||||
headerRow.GetCell(column.Ordinal).CellStyle = headStyle;
|
||||
|
||||
//设置列宽
|
||||
sheet.SetColumnWidth(column.Ordinal, (arrColWidth[column.Ordinal] + 1) * 256);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
rowIndex = 1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 填充内容
|
||||
|
||||
HSSFRow dataRow = sheet.CreateRow(rowIndex) as HSSFRow;
|
||||
foreach (DataColumn column in dtSource.Columns)
|
||||
{
|
||||
HSSFCell newCell = dataRow.CreateCell(column.Ordinal) as HSSFCell;
|
||||
|
||||
string drValue = row[column].ToString();
|
||||
|
||||
switch (column.DataType.ToString())
|
||||
{
|
||||
case "System.String": //字符串类型
|
||||
newCell.SetCellValue(drValue);
|
||||
break;
|
||||
case "System.DateTime": //日期类型
|
||||
DateTime dateV;
|
||||
DateTime.TryParse(drValue, out dateV);
|
||||
newCell.SetCellValue(dateV);
|
||||
newCell.CellStyle = dateStyle; //格式化显示
|
||||
break;
|
||||
case "System.Boolean": //布尔型
|
||||
bool boolV = false;
|
||||
bool.TryParse(drValue, out boolV);
|
||||
newCell.SetCellValue(boolV);
|
||||
break;
|
||||
case "System.Int16": //整型
|
||||
case "System.Int32":
|
||||
case "System.Int64":
|
||||
case "System.Byte":
|
||||
int intV = 0;
|
||||
int.TryParse(drValue, out intV);
|
||||
newCell.SetCellValue(intV);
|
||||
break;
|
||||
case "System.Decimal": //浮点型
|
||||
case "System.Double":
|
||||
double doubV = 0;
|
||||
double.TryParse(drValue, out doubV);
|
||||
newCell.SetCellValue(doubV);
|
||||
break;
|
||||
case "System.DBNull": //空值处理
|
||||
newCell.SetCellValue("");
|
||||
break;
|
||||
default:
|
||||
newCell.SetCellValue("");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
workbook.Write(ms);
|
||||
ms.Flush();
|
||||
ms.Position = 0;
|
||||
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>导出 Excel 字节流(供 Service 层返回 byte[],控制器包 File 下载)</summary>
|
||||
public static byte[] RenderToExcelBytes(DataTable table, string fileName)
|
||||
{
|
||||
try { return ExportDT(table, null).ToArray(); }
|
||||
catch (Exception) { return Array.Empty<byte>(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 读取 excel 到 DataTable
|
||||
|
||||
/// <summary>Excel 文档流是否有数据(默认第一个 sheet)</summary>
|
||||
public static bool HasData(Stream excelFileStream, string fileExt)
|
||||
{
|
||||
return HasData(excelFileStream, 0, fileExt);
|
||||
}
|
||||
|
||||
public static bool HasData(Stream excelFileStream, int sheetIndex, string fileExt)
|
||||
{
|
||||
using (excelFileStream)
|
||||
{
|
||||
IWorkbook workbook = null;
|
||||
if (fileExt == ".xls")
|
||||
{
|
||||
workbook = new HSSFWorkbook(excelFileStream);
|
||||
}
|
||||
else if (fileExt == ".xlsx")
|
||||
{
|
||||
workbook = new XSSFWorkbook(excelFileStream);
|
||||
}
|
||||
|
||||
{
|
||||
if (workbook.NumberOfSheets > 0)
|
||||
{
|
||||
if (sheetIndex < workbook.NumberOfSheets)
|
||||
{
|
||||
ISheet sheet = workbook.GetSheetAt(sheetIndex);
|
||||
{
|
||||
return sheet.PhysicalNumberOfRows > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Excel 文档流转换成 DataTable
|
||||
/// </summary>
|
||||
/// <param name="excelFileStream">Excel 文档流</param>
|
||||
/// <param name="sheetIndex">表索引号,如第一个表为 0</param>
|
||||
/// <param name="headerRowIndex">标题行索引号,如第一行为 0</param>
|
||||
public static DataTable RenderFromExcel(Stream excelFileStream, int sheetIndex, int headerRowIndex, string fileExt)
|
||||
{
|
||||
DataTable table = null;
|
||||
|
||||
using (excelFileStream)
|
||||
{
|
||||
IWorkbook workbook = null;
|
||||
if (fileExt == ".xls")
|
||||
{
|
||||
workbook = new HSSFWorkbook(excelFileStream);
|
||||
}
|
||||
else if (fileExt == ".xlsx")
|
||||
{
|
||||
workbook = new XSSFWorkbook(excelFileStream);
|
||||
}
|
||||
|
||||
ISheet sheet = workbook.GetSheetAt(sheetIndex);
|
||||
{
|
||||
table = ImportDt(sheet, headerRowIndex, true);
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/// <summary>将指定 sheet 中的数据导出到 datatable 中(第一行为列头)</summary>
|
||||
static DataTable ImportDt(ISheet sheet, int HeaderRowIndex, bool needHeader)
|
||||
{
|
||||
DataTable table = new DataTable();
|
||||
IRow headerRow;
|
||||
int cellCount;
|
||||
try
|
||||
{
|
||||
if (HeaderRowIndex < 0 || !needHeader)
|
||||
{
|
||||
headerRow = sheet.GetRow(0);
|
||||
cellCount = headerRow.LastCellNum;
|
||||
|
||||
for (int i = headerRow.FirstCellNum; i <= cellCount; i++)
|
||||
{
|
||||
DataColumn column = new DataColumn(Convert.ToString(i));
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headerRow = sheet.GetRow(HeaderRowIndex);
|
||||
cellCount = headerRow.LastCellNum;
|
||||
|
||||
for (int i = headerRow.FirstCellNum; i < cellCount; i++)
|
||||
{
|
||||
if (headerRow.GetCell(i) == null)
|
||||
{
|
||||
if (table.Columns.IndexOf(Convert.ToString(i)) > 0)
|
||||
{
|
||||
DataColumn column = new DataColumn(Convert.ToString("重复列名" + i));
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataColumn column = new DataColumn(Convert.ToString(i));
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
else if (table.Columns.IndexOf(headerRow.GetCell(i).ToString()) > 0)
|
||||
{
|
||||
DataColumn column = new DataColumn(Convert.ToString("重复列名" + i));
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataColumn column = new DataColumn(headerRow.GetCell(i).ToString());
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
int rowCount = sheet.LastRowNum;
|
||||
for (int i = (HeaderRowIndex + 1); i <= sheet.LastRowNum; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
IRow row;
|
||||
if (sheet.GetRow(i) == null)
|
||||
{
|
||||
row = sheet.CreateRow(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
row = sheet.GetRow(i);
|
||||
}
|
||||
|
||||
DataRow dataRow = table.NewRow();
|
||||
|
||||
for (int j = row.FirstCellNum; j < cellCount; j++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (row.GetCell(j) != null)
|
||||
{
|
||||
switch (row.GetCell(j).CellType)
|
||||
{
|
||||
case CellType.String:
|
||||
string str = row.GetCell(j).StringCellValue;
|
||||
if (str != null && str.Length > 0)
|
||||
{
|
||||
dataRow[j] = str.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
dataRow[j] = null;
|
||||
}
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
if (DateUtil.IsCellDateFormatted(row.GetCell(j)))
|
||||
{
|
||||
dataRow[j] = DateTime.FromOADate(row.GetCell(j).NumericCellValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
dataRow[j] = Convert.ToDouble(row.GetCell(j).NumericCellValue);
|
||||
}
|
||||
break;
|
||||
case CellType.Boolean:
|
||||
dataRow[j] = Convert.ToString(row.GetCell(j).BooleanCellValue);
|
||||
break;
|
||||
case CellType.Error:
|
||||
dataRow[j] = ErrorEval.GetText(row.GetCell(j).ErrorCellValue);
|
||||
break;
|
||||
case CellType.Formula:
|
||||
switch (row.GetCell(j).CachedFormulaResultType)
|
||||
{
|
||||
case CellType.String:
|
||||
string strFORMULA = row.GetCell(j).StringCellValue;
|
||||
if (strFORMULA != null && strFORMULA.Length > 0)
|
||||
{
|
||||
dataRow[j] = strFORMULA.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
dataRow[j] = null;
|
||||
}
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
dataRow[j] = Convert.ToString(row.GetCell(j).NumericCellValue);
|
||||
break;
|
||||
case CellType.Boolean:
|
||||
dataRow[j] = Convert.ToString(row.GetCell(j).BooleanCellValue);
|
||||
break;
|
||||
case CellType.Error:
|
||||
dataRow[j] = ErrorEval.GetText(row.GetCell(j).ErrorCellValue);
|
||||
break;
|
||||
default:
|
||||
dataRow[j] = "";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dataRow[j] = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
table.Rows.Add(dataRow);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static byte[] StreamToBytes(Stream stream)
|
||||
{
|
||||
byte[] bytes = new byte[stream.Length];
|
||||
stream.Read(bytes, 0, bytes.Length);
|
||||
// 设置当前流的位置为流的开始
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ namespace YhCore.Service.Impl
|
||||
throw new DException("已存在该分类,请重新填写!");
|
||||
}
|
||||
|
||||
var obj = await _dic.FirstOrDefaultAsync(x => x.SerID == model.SerID);
|
||||
var obj = await _dic.FirstOrDefaultTrackedAsync(x => x.SerID == model.SerID);
|
||||
if (obj == null) throw new DException("未找到要更新的分类!");
|
||||
|
||||
obj.Name = model.Name;
|
||||
@@ -141,7 +141,7 @@ namespace YhCore.Service.Impl
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
|
||||
var current = await _dic.FirstOrDefaultAsync(m => m.SerID == model.SerID);
|
||||
var current = await _dic.FirstOrDefaultTrackedAsync(m => m.SerID == model.SerID);
|
||||
if (current == null) throw new DException("未找到要编辑的分类!");
|
||||
|
||||
var dup = await _dic.AnyAsync(m =>
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace YhCore.Service.Impl
|
||||
{
|
||||
if (ids == null || ids.Length == 0) throw new DException("ids is not null");
|
||||
|
||||
var items = await _dic.FindAsync(m => ids.Contains(m.SerID));
|
||||
var items = await _dic.FindTrackedAsync(m => ids.Contains(m.SerID));
|
||||
if (items.Count == 0) throw new DException("未找到要删除的分类!");
|
||||
|
||||
foreach (var item in items)
|
||||
@@ -189,7 +189,7 @@ namespace YhCore.Service.Impl
|
||||
{
|
||||
if (ids == null || ids.Length == 0) throw new DException("ids is not null");
|
||||
|
||||
var items = await _dic.FindAsync(m => ids.Contains(m.SerID.ToString()));
|
||||
var items = await _dic.FindTrackedAsync(m => ids.Contains(m.SerID.ToString()));
|
||||
foreach (var item in items)
|
||||
{
|
||||
item.Enable = 3;
|
||||
@@ -200,7 +200,7 @@ namespace YhCore.Service.Impl
|
||||
|
||||
public async Task SetDictionaryOrdAsync(string schSerId, int dicSerId, bool show)
|
||||
{
|
||||
var obj = await _ord.FirstOrDefaultAsync(m => m.DicSerID == dicSerId && m.SchSerID == schSerId);
|
||||
var obj = await _ord.FirstOrDefaultTrackedAsync(m => m.DicSerID == dicSerId && m.SchSerID == schSerId);
|
||||
if (obj == null)
|
||||
{
|
||||
await _ord.AddAsync(new Common_DictionaryOrd
|
||||
@@ -224,7 +224,7 @@ namespace YhCore.Service.Impl
|
||||
if (string.IsNullOrEmpty(ids)) throw new DException("ids is not null");
|
||||
|
||||
var ssids = ids.Split(',').Select(a => int.Parse(a)).ToArray();
|
||||
var list = await _ord.FindAsync(m => m.SchSerID == schSerId);
|
||||
var list = await _ord.FindTrackedAsync(m => m.SchSerID == schSerId);
|
||||
|
||||
var i = 0;
|
||||
foreach (var id in ssids)
|
||||
@@ -274,7 +274,7 @@ namespace YhCore.Service.Impl
|
||||
|
||||
public async Task SetSchZyBitAsync(string schSerId, bool zyBit)
|
||||
{
|
||||
var obj = await _zybit.FirstOrDefaultAsync(a => a.SchSerID == schSerId);
|
||||
var obj = await _zybit.FirstOrDefaultTrackedAsync(a => a.SchSerID == schSerId);
|
||||
if (obj == null)
|
||||
{
|
||||
await _zybit.AddAsync(new WY_SchZyBit { SchSerID = schSerId, ziyuanbit = zyBit });
|
||||
|
||||
@@ -68,10 +68,9 @@ namespace YhCore.Service.Impl
|
||||
if (file == null || file.Length == 0)
|
||||
throw new DException("上传失败!");
|
||||
|
||||
if (appType.ToLower() == "noticemsg") appType = "notice";
|
||||
string dtStr = DateTime.Now.ToString("yyyyMMdd");
|
||||
string physicalDir = Path.Combine(StorageRoot(), appType, dtStr);
|
||||
string outFilePath = "/UploadFile/" + appType + "/" + dtStr + "/";
|
||||
string outFilePath = appType + "/" + dtStr + "/";
|
||||
|
||||
if (!Directory.Exists(physicalDir))
|
||||
Directory.CreateDirectory(physicalDir);
|
||||
@@ -109,7 +108,7 @@ namespace YhCore.Service.Impl
|
||||
if (appType.ToLower() == "noticemsg") appType = "notice";
|
||||
string dtStr = DateTime.Now.ToString("yyyyMMdd");
|
||||
string physicalDir = Path.Combine(StorageRoot(), appType, dtStr);
|
||||
string outFilePath = "/UploadFile/" + appType + "/" + dtStr + "/";
|
||||
string outFilePath = appType + "/" + dtStr + "/";
|
||||
|
||||
if (!Directory.Exists(physicalDir))
|
||||
Directory.CreateDirectory(physicalDir);
|
||||
@@ -169,7 +168,7 @@ namespace YhCore.Service.Impl
|
||||
if (appType.ToLower() == "noticemsg") appType = "notice";
|
||||
string dtStr = DateTime.Now.ToString("yyyyMMdd");
|
||||
string physicalDir = Path.Combine(StorageRoot(), appType, dtStr);
|
||||
string outFilePath = "/UploadFile/" + appType + "/" + dtStr + "/";
|
||||
string outFilePath = appType + "/" + dtStr + "/";
|
||||
if (!Directory.Exists(physicalDir))
|
||||
Directory.CreateDirectory(physicalDir);
|
||||
|
||||
@@ -214,22 +213,29 @@ namespace YhCore.Service.Impl
|
||||
if (string.IsNullOrEmpty(fileUrl))
|
||||
return "";
|
||||
|
||||
// 相对路径如 /UploadFile/docprint/20260101/{guid}.jpg → Config.FilePath + 子路径
|
||||
// 新数据:fileurl 以 FILE_PATH 开头(如 /data/upload/docprint/20260101/{guid}.jpg)→ 剥前缀映射回 FILE_PATH。
|
||||
// 注意必须用未 TrimStart 的原始 fileUrl 比较(FILE_PATH 本身以 / 开头)
|
||||
string fp = Config.FilePath;
|
||||
if (!string.IsNullOrEmpty(fp) && fileUrl.StartsWith(fp, StringComparison.OrdinalIgnoreCase))
|
||||
return Path.Combine(fp, fileUrl.Substring(fp.Length).TrimStart('/').Replace('/', Path.DirectorySeparatorChar));
|
||||
|
||||
// 旧数据:相对路径如 /upload/docprint/20260101/{guid}.jpg(不含 FILE_PATH 前缀)→ StorageRoot + 子路径
|
||||
var relative = fileUrl.TrimStart('/');
|
||||
const string prefix = "UploadFile/";
|
||||
string prefix = Config.FilePath;
|
||||
if (relative.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
relative = relative.Substring(prefix.Length);
|
||||
|
||||
return Path.Combine(StorageRoot(), relative.Replace('/', Path.DirectorySeparatorChar));
|
||||
}
|
||||
|
||||
/// <summary>存储根目录(旧 Server.MapPath("~/UploadFile/");Config.FilePath 缺省时退回进程目录/UploadFile)。</summary>
|
||||
private static string StorageRoot()
|
||||
/// <summary>存储根目录(旧 Server.MapPath("~/upload/");Config.FilePath 缺省时退回进程目录/upload)。</summary>
|
||||
public static string StorageRoot()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Config.FilePath))
|
||||
return Config.FilePath;
|
||||
|
||||
return Path.Combine(AppContext.BaseDirectory, "UploadFile");
|
||||
// 此分支仅在 FILE_PATH 未配置时执行(此时 Config.FilePath 为 null,不可再参与拼接)
|
||||
return Path.Combine(AppContext.BaseDirectory, Config.FilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace YhCore.Service.Impl
|
||||
a.flowname != null && a.flowname.Trim() == flowname && a.enable != 3)).Count;
|
||||
if (dup > 1) throw new DException("流程名不能重名!");
|
||||
|
||||
var info = await _flowinfo.FirstOrDefaultAsync(a => a.SerID == model.SerID);
|
||||
var info = await _flowinfo.FirstOrDefaultTrackedAsync(a => a.SerID == model.SerID);
|
||||
if (info == null) throw new DException("未找到要修改的流程!");
|
||||
|
||||
info.flowname = model.flowname;
|
||||
@@ -198,7 +198,7 @@ namespace YhCore.Service.Impl
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
|
||||
var info = await _flowstep.FirstOrDefaultAsync(a => a.SerID == model.SerID);
|
||||
var info = await _flowstep.FirstOrDefaultTrackedAsync(a => a.SerID == model.SerID);
|
||||
if (info == null) throw new DException("未找到要修改的步骤!");
|
||||
|
||||
info.flowid = model.flowid;
|
||||
@@ -207,7 +207,7 @@ namespace YhCore.Service.Impl
|
||||
await _flowstep.UpdateAsync(info);
|
||||
|
||||
// 重写该步骤的人员绑定(先清后加)
|
||||
var existing = await _groupuser.FindAsync(a => a.usergroup == info.SerID);
|
||||
var existing = await _groupuser.FindTrackedAsync(a => a.usergroup == info.SerID);
|
||||
foreach (var itt in existing)
|
||||
{
|
||||
await _groupuser.RemoveAsync(itt);
|
||||
@@ -243,7 +243,7 @@ namespace YhCore.Service.Impl
|
||||
if (string.IsNullOrWhiteSpace(ids)) throw new DException("ids is not null");
|
||||
|
||||
var lids = ids.Split(',').Select(a => int.Parse(a)).ToArray();
|
||||
var flows = await _flowinfo.FindAsync(a => lids.Contains(a.SerID));
|
||||
var flows = await _flowinfo.FindTrackedAsync(a => lids.Contains(a.SerID));
|
||||
foreach (var item in flows)
|
||||
{
|
||||
item.enable = 3;
|
||||
@@ -256,7 +256,7 @@ namespace YhCore.Service.Impl
|
||||
if (ids == null || ids.Length == 0) throw new DException("ids is not null");
|
||||
|
||||
var intIds = ids.Select(d => (int)d).ToArray();
|
||||
var objlist = await _flowstep.FindAsync(m => intIds.Contains(m.SerID));
|
||||
var objlist = await _flowstep.FindTrackedAsync(m => intIds.Contains(m.SerID));
|
||||
foreach (var item in objlist)
|
||||
{
|
||||
await _flowstep.RemoveAsync(item);
|
||||
|
||||
@@ -7,6 +7,7 @@ using Yh.Platform.Abstractions;
|
||||
using Yh.SchClass.OpenApi;
|
||||
using YhCore.Entity;
|
||||
using YhCore.Model.Dto;
|
||||
using YhCore.Model.Vo;
|
||||
using YhCore.Repository;
|
||||
using YhCore.Service.Interfaces;
|
||||
|
||||
@@ -72,7 +73,7 @@ namespace YhCore.Service.Impl
|
||||
#region 抄送
|
||||
|
||||
/// <summary>抄送列表(旧 SendRdList:DocStatus 1/11,SendUserID 含本人;旧为教师内连接)。</summary>
|
||||
public async Task<List<DocPrintListItem>> SendRdListAsync(string name, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<DocPrintListItem>> SendRdListAsync(string name, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
var docs = (await _doc.FindAsync(m => m.SchSerID == schSerId)).ToList();
|
||||
|
||||
@@ -88,6 +89,7 @@ namespace YhCore.Service.Impl
|
||||
docs = docs.Where(m => m.TakeDate <= date2).ToList();
|
||||
}
|
||||
|
||||
var total = docs.Count(m => m.DocStatus == "1" || m.DocStatus == "11");
|
||||
docs = docs.Where(m => m.DocStatus == "1" || m.DocStatus == "11")
|
||||
.OrderByDescending(m => m.SerID)
|
||||
.Skip(pageSize * pageIndex).Take(pageSize).ToList();
|
||||
@@ -101,7 +103,7 @@ namespace YhCore.Service.Impl
|
||||
if (!TryGetTea(teaMap, item.UserID, out var tea)) continue;
|
||||
result.Add(await BuildListItemAsync(item, tea, dictMap, "yyyy-MM-dd"));
|
||||
}
|
||||
return result;
|
||||
return PageVO<DocPrintListItem>.Of(result, total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>查看抄送人员详情(旧 SendDetailsList)。</summary>
|
||||
@@ -138,7 +140,7 @@ namespace YhCore.Service.Impl
|
||||
{
|
||||
await SetChaoSendAsync(int.Parse(uid), schSerId, mysenduserid);
|
||||
|
||||
var model = await _doc.FirstOrDefaultAsync(m => m.SerID == serId);
|
||||
var model = await _doc.FirstOrDefaultTrackedAsync(m => m.SerID == serId);
|
||||
if (model != null)
|
||||
{
|
||||
model.SendUserID = senduserid;
|
||||
@@ -249,7 +251,7 @@ namespace YhCore.Service.Impl
|
||||
/// </summary>
|
||||
public async Task<int> EditAsync(WYDocPrintSaveDto model)
|
||||
{
|
||||
var info = await _doc.FirstOrDefaultAsync(x => x.SerID == model.SerID);
|
||||
var info = await _doc.FirstOrDefaultTrackedAsync(x => x.SerID == model.SerID);
|
||||
if (info == null) throw new DException("未找到该文印记录!");
|
||||
|
||||
info.DocType = model.DocType;
|
||||
@@ -303,7 +305,7 @@ namespace YhCore.Service.Impl
|
||||
var idArr = ids.Split(',')
|
||||
.Where(a => !string.IsNullOrEmpty(a) && decimal.TryParse(a, out _))
|
||||
.Select(decimal.Parse).ToArray();
|
||||
var objlist = await _doc.FindAsync(m => idArr.Contains(m.SerID));
|
||||
var objlist = await _doc.FindTrackedAsync(m => idArr.Contains(m.SerID));
|
||||
foreach (var item in objlist)
|
||||
{
|
||||
await _doc.RemoveAsync(item);
|
||||
@@ -334,7 +336,7 @@ namespace YhCore.Service.Impl
|
||||
await _flowtranct.AddAsync(tranct);
|
||||
await _flowtranct.SaveChangesAsync();
|
||||
|
||||
var docModel = await _doc.FirstOrDefaultAsync(m => m.SerID == model.dataid);
|
||||
var docModel = await _doc.FirstOrDefaultTrackedAsync(m => m.SerID == model.dataid);
|
||||
if (docModel != null)
|
||||
{
|
||||
docModel.DocStatus = model.actionstatus.ToString();
|
||||
@@ -349,7 +351,7 @@ namespace YhCore.Service.Impl
|
||||
#region 列表
|
||||
|
||||
/// <summary>我的文印列表(旧 MyList;旧 DocTypeName 误用 YZBM 字典,此处保持旧行为)。</summary>
|
||||
public async Task<List<DocPrintListItem>> MyListAsync(string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<DocPrintListItem>> MyListAsync(string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
var docs = (await _doc.FindAsync(m => m.UserID == userId && m.SchSerID == schSerId)).ToList();
|
||||
|
||||
@@ -365,6 +367,7 @@ namespace YhCore.Service.Impl
|
||||
docs = docs.Where(m => m.TakeDate <= date2).ToList();
|
||||
}
|
||||
|
||||
var total = docs.Count;
|
||||
docs = docs.OrderBy(m => m.DocStatus).ThenByDescending(m => m.TakeDate).ThenByDescending(m => m.EditDate)
|
||||
.Skip(pageSize * pageIndex).Take(pageSize).ToList();
|
||||
|
||||
@@ -376,14 +379,14 @@ namespace YhCore.Service.Impl
|
||||
TryGetTea(teaMap, item.UserID, out var tea); // 旧为左连接,缺失补空
|
||||
result.Add(await BuildListItemAsync(item, tea, dictMap, "yyyy-MM-dd HH:mm", "YZBM"));
|
||||
}
|
||||
return result;
|
||||
return PageVO<DocPrintListItem>.Of(result, total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全部文印列表(旧 AllList 原生 SQL 四分类:x=待我审(0) y=我已审(1) z=抄送我(2) m=我发起(3),按优先级去重)。
|
||||
/// 旧 Name 过滤的 SQL 拼接存在运算符优先级缺陷,此处按语义实现:内容/教师名/字典名任一命中。
|
||||
/// </summary>
|
||||
public async Task<List<DocPrintListItem>> AllListAsync(string name, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<DocPrintListItem>> AllListAsync(string name, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
int uid = int.Parse(userId);
|
||||
|
||||
@@ -423,6 +426,7 @@ namespace YhCore.Service.Impl
|
||||
|| DictNameContains(dictMap, "YZFL", t.doc.DPType, name));
|
||||
}
|
||||
|
||||
var total = query.Count();
|
||||
var pageItems = query.OrderByDescending(t => t.doc.SerID).ThenByDescending(t => t.doc.TakeDate)
|
||||
.Skip(pageSize * pageIndex).Take(pageSize).ToList();
|
||||
|
||||
@@ -434,16 +438,17 @@ namespace YhCore.Service.Impl
|
||||
item.ctype = ctype;
|
||||
result.Add(item);
|
||||
}
|
||||
return result;
|
||||
return PageVO<DocPrintListItem>.Of(result, total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文印审核列表(旧 ShenheList:固定流程 + 指派 + 自由流程三路并集,HashSet 去重)。
|
||||
/// 旧实现的 Name 过滤作用在未被使用的查询上(死代码),此处保持 Name 不生效的旧行为。
|
||||
/// </summary>
|
||||
public async Task<List<DocPrintListItem>> ShenheListAsync(string userId, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<DocPrintListItem>> ShenheListAsync(string userId, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
var docs = await GetPendingShenheDocsAsync(userId, schSerId, requireFlowIdNonZero: false);
|
||||
var total = docs.Count;
|
||||
docs = docs.OrderBy(m => m.DocStatus).ThenByDescending(m => m.TakeDate)
|
||||
.Skip(pageSize * pageIndex).Take(pageSize).ToList();
|
||||
|
||||
@@ -455,13 +460,13 @@ namespace YhCore.Service.Impl
|
||||
TryGetTea(teaMap, item.UserID, out var tea);
|
||||
result.Add(await BuildListItemAsync(item, tea, dictMap, "yyyy-MM-dd HH:mm"));
|
||||
}
|
||||
return result;
|
||||
return PageVO<DocPrintListItem>.Of(result, total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 审批列表(旧 SPList:待审(DS,zt=false)∪ 我已审(YS,zt=true,排除 DS),_disabled 即 zt)。
|
||||
/// </summary>
|
||||
public async Task<List<DocPrintListItem>> SPListAsync(string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<DocPrintListItem>> SPListAsync(string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
var userid = int.Parse(userId);
|
||||
|
||||
@@ -493,6 +498,7 @@ namespace YhCore.Service.Impl
|
||||
query = query.Where(m => m.doc.DocContent != null && m.doc.DocContent.IndexOf(name) >= 0);
|
||||
|
||||
var ordered = query.OrderBy(m => m.doc.DocStatus).ThenByDescending(m => m.doc.EditDate).ToList();
|
||||
var total = ordered.Count;
|
||||
|
||||
var teaMap = await GetTeaMapAsync(schSerId);
|
||||
var dictMap = await GetDictMapAsync(schSerId);
|
||||
@@ -504,11 +510,11 @@ namespace YhCore.Service.Impl
|
||||
item._disabled = zt; // 我已审的记录不可再编辑(覆盖通用 _disabled 语义,与旧一致)
|
||||
result.Add(item);
|
||||
}
|
||||
return result;
|
||||
return PageVO<DocPrintListItem>.Of(result, total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>文印已审列表(旧 YishenList:本人审核流水关联,排除撤销与已领取)。</summary>
|
||||
public async Task<List<DocPrintListItem>> YishenListAsync(string name, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<DocPrintListItem>> YishenListAsync(string name, string userId, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
int uid = int.Parse(userId);
|
||||
var tranctDataIds = (await _flowtranct.FindAsync(b => b.actionuserid == uid && b.datatype == DocPrintConstants.AppCode.ToString() && b.actionstatus != 11))
|
||||
@@ -521,6 +527,7 @@ namespace YhCore.Service.Impl
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
docs = docs.Where(m => m.DocContent != null && m.DocContent.Contains(name)).ToList();
|
||||
|
||||
var total = docs.Count;
|
||||
docs = docs.OrderByDescending(m => m.SerID).ThenByDescending(m => m.TakeDate)
|
||||
.Skip(pageSize * pageIndex).Take(pageSize).ToList();
|
||||
|
||||
@@ -532,11 +539,11 @@ namespace YhCore.Service.Impl
|
||||
TryGetTea(teaMap, item.UserID, out var tea);
|
||||
result.Add(await BuildListItemAsync(item, tea, dictMap, "yyyy-MM-dd HH:mm"));
|
||||
}
|
||||
return result;
|
||||
return PageVO<DocPrintListItem>.Of(result, total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>全部文印列表(旧 totalList,管理员视角,按状态/时间过滤)。</summary>
|
||||
public async Task<List<DocPrintListItem>> TotalListAsync(string name, string stateid, string starttime, string endtime, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<DocPrintListItem>> TotalListAsync(string name, string stateid, string starttime, string endtime, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
var docs = (await _doc.FindAsync(m => m.SchSerID == schSerId)).ToList();
|
||||
|
||||
@@ -552,6 +559,7 @@ namespace YhCore.Service.Impl
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
docs = docs.Where(m => m.DocContent != null && m.DocContent.IndexOf(name) >= 0).ToList();
|
||||
|
||||
var total = docs.Count;
|
||||
docs = docs.OrderBy(m => m.DocStatus).ThenByDescending(m => m.TakeDate)
|
||||
.Skip(pageSize * pageIndex).Take(pageSize).ToList();
|
||||
|
||||
@@ -563,7 +571,7 @@ namespace YhCore.Service.Impl
|
||||
TryGetTea(teaMap, item.UserID, out var tea);
|
||||
result.Add(await BuildListItemAsync(item, tea, dictMap, "yyyy-MM-dd"));
|
||||
}
|
||||
return result;
|
||||
return PageVO<DocPrintListItem>.Of(result, total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -665,7 +673,7 @@ namespace YhCore.Service.Impl
|
||||
using var tx = await _db.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var rdmodel = await _doc.FirstOrDefaultAsync(m => m.SerID == model.dataid);
|
||||
var rdmodel = await _doc.FirstOrDefaultTrackedAsync(m => m.SerID == model.dataid);
|
||||
if (rdmodel == null) throw new DException("未找到该文印记录!");
|
||||
|
||||
if (rdmodel.DocStatus != "0")
|
||||
@@ -766,7 +774,7 @@ namespace YhCore.Service.Impl
|
||||
/// </summary>
|
||||
public async Task<string> AffirmApplyforOkAsync(FlowTranctDto model)
|
||||
{
|
||||
var itt = await _doc.FirstOrDefaultAsync(m => m.SerID == model.dataid);
|
||||
var itt = await _doc.FirstOrDefaultTrackedAsync(m => m.SerID == model.dataid);
|
||||
if (itt == null) throw new DException("未找到该文印记录!");
|
||||
|
||||
var claimed = await _flowtranct.AnyAsync(a => a.dataid == model.dataid
|
||||
@@ -817,7 +825,7 @@ namespace YhCore.Service.Impl
|
||||
{
|
||||
if (flowid <= 0) return;
|
||||
|
||||
var list = await _zhipai.FirstOrDefaultAsync(a => a.UserID == uid && a.MSerID == dataid
|
||||
var list = await _zhipai.FirstOrDefaultTrackedAsync(a => a.UserID == uid && a.MSerID == dataid
|
||||
&& a.SchSerID == schSerId && a.FlowID == flowid && a.FlowStepID == flowstepid && a.type == type);
|
||||
if (list == null)
|
||||
{
|
||||
@@ -895,7 +903,7 @@ namespace YhCore.Service.Impl
|
||||
}
|
||||
|
||||
/// <summary>文印详情统计列表(旧 List2:非待审单据按申请时间倒序分页)。</summary>
|
||||
public async Task<List<DocDetailStatItem>> List2Async(string starttime, string endtime, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<DocDetailStatItem>> List2Async(string starttime, string endtime, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
var docs = (await _doc.FindAsync(m => m.SchSerID == schSerId && m.DocStatus != "0")).ToList();
|
||||
if (DateTime.TryParse(starttime, out var date1))
|
||||
@@ -933,13 +941,16 @@ namespace YhCore.Service.Impl
|
||||
});
|
||||
}
|
||||
|
||||
return full.OrderByDescending(a => a.EditDate)
|
||||
var total = full.Count;
|
||||
return PageVO<DocDetailStatItem>.Of(
|
||||
full.OrderByDescending(a => a.EditDate)
|
||||
.Skip(pageSize * pageIndex).Take(pageSize)
|
||||
.ToList();
|
||||
.ToList(),
|
||||
total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>文印数量按用户排名(旧 List3)。</summary>
|
||||
public async Task<List<UserRankItem>> List3Async(string starttime, string endtime, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<UserRankItem>> List3Async(string starttime, string endtime, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
var docs = (await _doc.FindAsync(m => m.SchSerID == schSerId && m.DocStatus == "1")).ToList();
|
||||
if (DateTime.TryParse(starttime, out var date1))
|
||||
@@ -965,23 +976,27 @@ namespace YhCore.Service.Impl
|
||||
.OrderByDescending(a => a.num)
|
||||
.ToList();
|
||||
|
||||
return grouped.Skip(pageSize * pageIndex).Take(pageSize).ToList();
|
||||
var total = grouped.Count;
|
||||
return PageVO<UserRankItem>.Of(
|
||||
grouped.Skip(pageSize * pageIndex).Take(pageSize).ToList(),
|
||||
total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>按月份统计明细(旧 List4:指定年月已印好单据,按申请时间倒序分页)。</summary>
|
||||
public async Task<List<StatListItem>> List4Async(int year, int month, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<StatListItem>> List4Async(int year, int month, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
var docs = (await _doc.FindAsync(m => m.SchSerID == schSerId
|
||||
&& m.DocStatus == "1" && m.EditDate.Year == year && m.EditDate.Month == month))
|
||||
.OrderByDescending(a => a.EditDate)
|
||||
var all = (await _doc.FindAsync(m => m.SchSerID == schSerId
|
||||
&& m.DocStatus == "1" && m.EditDate.Year == year && m.EditDate.Month == month)).ToList();
|
||||
var total = all.Count;
|
||||
var docs = all.OrderByDescending(a => a.EditDate)
|
||||
.Skip(pageSize * pageIndex).Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
return await BuildStatListAsync(docs, schSerId);
|
||||
return PageVO<StatListItem>.Of(await BuildStatListAsync(docs, schSerId), total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
/// <summary>按用户统计明细(旧 List5:指定用户已印好单据,按申请时间倒序分页)。</summary>
|
||||
public async Task<List<StatListItem>> List5Async(string userid, string starttime, string endtime, string schSerId, int pageSize, int pageIndex)
|
||||
public async Task<PageVO<StatListItem>> List5Async(string userid, string starttime, string endtime, string schSerId, int pageSize, int pageIndex)
|
||||
{
|
||||
var docs = (await _doc.FindAsync(m => m.SchSerID == schSerId && m.UserID == userid && m.DocStatus == "1")).ToList();
|
||||
if (DateTime.TryParse(starttime, out var date1))
|
||||
@@ -992,11 +1007,12 @@ namespace YhCore.Service.Impl
|
||||
docs = docs.Where(m => m.EditDate <= date2).ToList();
|
||||
}
|
||||
|
||||
var total = docs.Count;
|
||||
docs = docs.OrderByDescending(a => a.EditDate)
|
||||
.Skip(pageSize * pageIndex).Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
return await BuildStatListAsync(docs, schSerId);
|
||||
return PageVO<StatListItem>.Of(await BuildStatListAsync(docs, schSerId), total, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1240,7 +1256,7 @@ namespace YhCore.Service.Impl
|
||||
/// <summary>自由流程挂链(旧 ApproveFlow.SetPrimarySteptID:从 stepid 起沿 laststep 链回写 dataid)。</summary>
|
||||
private async Task SetPrimarySteptIDAsync(int dataid, int stepid)
|
||||
{
|
||||
var node = await _freeflowinfo.FirstOrDefaultAsync(a => a.SerID == stepid);
|
||||
var node = await _freeflowinfo.FirstOrDefaultTrackedAsync(a => a.SerID == stepid);
|
||||
if (node != null)
|
||||
{
|
||||
node.dataid = dataid;
|
||||
@@ -1250,7 +1266,7 @@ namespace YhCore.Service.Impl
|
||||
var nextSer = stepid;
|
||||
while (true)
|
||||
{
|
||||
var next = await _freeflowinfo.FirstOrDefaultAsync(a => a.laststep == nextSer);
|
||||
var next = await _freeflowinfo.FirstOrDefaultTrackedAsync(a => a.laststep == nextSer);
|
||||
if (next == null) break;
|
||||
next.dataid = dataid;
|
||||
await _freeflowinfo.SaveChangesAsync();
|
||||
@@ -1261,7 +1277,7 @@ namespace YhCore.Service.Impl
|
||||
/// <summary>抄送人落库(旧 ApproveFlow.SetChaoSend:按用户+flowType 全删再插)。</summary>
|
||||
private async Task SetChaoSendAsync(int uid, string schSerId, string senduserid)
|
||||
{
|
||||
var olds = await _senduser.FindAsync(a => a.flowType == DocPrintConstants.AppCode && a.userid == uid);
|
||||
var olds = await _senduser.FindTrackedAsync(a => a.flowType == DocPrintConstants.AppCode && a.userid == uid);
|
||||
foreach (var old in olds)
|
||||
{
|
||||
await _senduser.RemoveAsync(old);
|
||||
@@ -1287,7 +1303,7 @@ namespace YhCore.Service.Impl
|
||||
var fids = ParseIds(upFile);
|
||||
foreach (var fid in fids)
|
||||
{
|
||||
var file = await _files.FirstOrDefaultAsync(x => x.SerID == fid);
|
||||
var file = await _files.FirstOrDefaultTrackedAsync(x => x.SerID == fid);
|
||||
if (file != null)
|
||||
{
|
||||
file.TopicID = topicId;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ namespace YhCore.Service.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 公共文件服务(移植旧 BestSchool.Service.Common.GetEnclosure/FileSaveEnclosure +
|
||||
/// BestSchool.Common.UpLoadFile.FileSave/FileSaveImage/FileChunkSave/FileMergeSave)。
|
||||
/// 存储约定:物理根目录 = Config.FilePath(≈旧 ~/UploadFile/),相对路径仍存 /UploadFile/{AppType}/{yyyyMMdd}/{guid}{ext}
|
||||
/// BestSchool.Common.upload.FileSave/FileSaveImage/FileChunkSave/FileMergeSave)。
|
||||
/// 存储约定:物理根目录 = Config.FilePath(≈旧 ~/upload/),相对路径仍存 /upload/{AppType}/{yyyyMMdd}/{guid}{ext}
|
||||
/// (与旧库数据格式一致,兼容历史记录);host 取 Config.FileWebPath(≈旧 AuthConfig.weboa_url)。
|
||||
/// 旧 ToOfficeHelper.OfficeConvertToPDF(Office COM 转 PDF)新项目缺失,降级:上传不做转 PDF,
|
||||
/// 预览时本地无 .pdf 则降级 officeapps.live.com 在线预览。
|
||||
@@ -21,10 +21,10 @@ namespace YhCore.Service.Interfaces
|
||||
/// <summary>存储附件信息(旧 FileSaveEnclosure:Common_UpFiles 落库,返回 SerID;文件名非法字符替换为 _)。</summary>
|
||||
Task<int> SaveEnclosureAsync(FileObj file, string appType);
|
||||
|
||||
/// <summary>单文件上传(旧 UpLoadFile.FileSave:扩展名白名单 + 保存 + 返回 FileObj)。</summary>
|
||||
/// <summary>单文件上传(旧 upload.FileSave:扩展名白名单 + 保存 + 返回 FileObj)。</summary>
|
||||
Task<FileObj> SaveFileAsync(string appType, IFormFile file);
|
||||
|
||||
/// <summary>图片多文件上传(旧 UpLoadFile.FileSaveImage:jpg/gif/png/jpeg)。</summary>
|
||||
/// <summary>图片多文件上传(旧 upload.FileSaveImage:jpg/gif/png/jpeg)。</summary>
|
||||
Task<List<FileObj>> SaveFileImageAsync(string appType, IFormFileCollection files);
|
||||
|
||||
/// <summary>保存分片(旧 FileChunkSave:临时目录 {guid}/ 下按索引存块)。</summary>
|
||||
@@ -33,7 +33,7 @@ namespace YhCore.Service.Interfaces
|
||||
/// <summary>合并分片(旧 FileMergeSave:按块合并 → 正式目录 + FileObj)。</summary>
|
||||
Task<FileObj> MergeSaveAsync(string appType, string guid, string fileName);
|
||||
|
||||
/// <summary>相对路径 → 物理路径(fileurl 以 /UploadFile/ 开头 → Config.FilePath + 子路径)。</summary>
|
||||
/// <summary>相对路径 → 物理路径(fileurl 以 /upload/ 开头 → Config.FilePath + 子路径)。</summary>
|
||||
string MapPhysicalPath(string fileUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using YhCore.Model.Dto;
|
||||
using YhCore.Model.Vo;
|
||||
|
||||
namespace YhCore.Service.Interfaces
|
||||
{
|
||||
@@ -11,7 +12,7 @@ namespace YhCore.Service.Interfaces
|
||||
public interface IWYDocPrintService
|
||||
{
|
||||
// ---- 抄送 ----
|
||||
Task<List<DocPrintListItem>> SendRdListAsync(string name, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<DocPrintListItem>> SendRdListAsync(string name, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<SendDetailItem>> SendDetailsListAsync(int serId);
|
||||
Task<string> EditSendAsync(int serId, string senduserid, string mysenduserid, string schSerId, string uid);
|
||||
|
||||
@@ -25,12 +26,12 @@ namespace YhCore.Service.Interfaces
|
||||
Task<string> RevokeAsync(FlowTranctDto model);
|
||||
|
||||
// ---- 列表 ----
|
||||
Task<List<DocPrintListItem>> MyListAsync(string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<DocPrintListItem>> AllListAsync(string name, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<DocPrintListItem>> ShenheListAsync(string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<DocPrintListItem>> SPListAsync(string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<DocPrintListItem>> YishenListAsync(string name, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<DocPrintListItem>> TotalListAsync(string name, string stateid, string starttime, string endtime, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<DocPrintListItem>> MyListAsync(string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<DocPrintListItem>> AllListAsync(string name, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<DocPrintListItem>> ShenheListAsync(string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<DocPrintListItem>> SPListAsync(string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<DocPrintListItem>> YishenListAsync(string name, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<DocPrintListItem>> TotalListAsync(string name, string stateid, string starttime, string endtime, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<DocPrintDetailItem>> MyListtoOneAsync(int serId, string schSerId);
|
||||
Task<List<BindFlowOption>> BindflowinfoAsync(string schSerId, int flowType);
|
||||
|
||||
@@ -43,9 +44,9 @@ namespace YhCore.Service.Interfaces
|
||||
|
||||
// ---- 统计 ----
|
||||
Task<List<MonthStatItem>> List1Async(string starttime, string endtime, string schSerId);
|
||||
Task<List<DocDetailStatItem>> List2Async(string starttime, string endtime, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<UserRankItem>> List3Async(string starttime, string endtime, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<StatListItem>> List4Async(int year, int month, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<StatListItem>> List5Async(string userid, string starttime, string endtime, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<DocDetailStatItem>> List2Async(string starttime, string endtime, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<UserRankItem>> List3Async(string starttime, string endtime, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<StatListItem>> List4Async(int year, int month, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<StatListItem>> List5Async(string userid, string starttime, string endtime, string schSerId, int pageSize, int pageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using YhCore.Entity;
|
||||
using YhCore.Model.Dto;
|
||||
using YhCore.Model.Vo;
|
||||
@@ -14,7 +16,9 @@ namespace YhCore.Service.Interfaces
|
||||
/// 落库 WY_SHRole_SHTea.SHTeaID / WY_SHRole_BSHTea.BSHTeaID 仍为 decimal 用户 id;
|
||||
/// 3. 签名存文件系统(DES 加密),非数据库表;
|
||||
/// 4. 旧 ServiceResult 的 Data/OtherData/Page 三段合并为单个 Result 对象(getshenheFlow / getStepInfo / getStepByJE / getdocOftable)。
|
||||
/// 延后:Excel 导入导出(UpfileExcelSHRole/downMBOfSHRole/downUpfileSHRoleFK/ExceltotalListSHRole/WYRDExceldaochu)、审批单 PDF(DwonWYSQD)。
|
||||
/// Excel 导入导出(UpfileExcelSHRole/downMBOfSHRole/downUpfileSHRoleFK/ExceltotalListSHRole/WYRDExceldaochu)
|
||||
/// 与审批单 PDF(DwonWYSQD):下载类接口返回 (byte[] Content, string FileName),
|
||||
/// 控制器按扩展名包装 File();导入反馈表存进程内 IMemoryCache(单实例)。
|
||||
/// </summary>
|
||||
public interface IWYSHRoleService
|
||||
{
|
||||
@@ -50,10 +54,10 @@ namespace YhCore.Service.Interfaces
|
||||
// ---- 用户记录查看和审核 ----
|
||||
Task<SHRoleShenheFlowResult> GetshenheFlowAsync(string schSerId, decimal docprintId, int flowid);
|
||||
Task<List<SHRoleDocPrintListItem>> MyListtoOneSHRoleAsync(int serId, string schSerId);
|
||||
Task<List<SHRoleDocPrintListItem>> MyListSHRoleAsync(string name, string status, string startDate, string endDate, int pageSize, int pageIndex, string userId, string schSerId);
|
||||
Task<List<SHRoleDocPrintListItem>> TotalListSHRoleAsync(int flowid, int dwid, string name, string stateid, string starttime, string endtime, int pageSize, int pageIndex, string schSerId);
|
||||
Task<List<SHRoleDocPrintListItem>> SendRdListSHRoleAsync(int flowid, int dwid, string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<List<SHRoleDocPrintListItem>> SPListSHRoleAsync(int flowid, int dwid, string name, string status, string startDate, string endDate, string userId, string userName, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<SHRoleDocPrintListItem>> MyListSHRoleAsync(string name, string status, string startDate, string endDate, int pageSize, int pageIndex, string userId, string schSerId);
|
||||
Task<PageVO<SHRoleDocPrintListItem>> TotalListSHRoleAsync(int flowid, int dwid, string name, string stateid, string starttime, string endtime, int pageSize, int pageIndex, string schSerId);
|
||||
Task<PageVO<SHRoleDocPrintListItem>> SendRdListSHRoleAsync(int flowid, int dwid, string name, string status, string startDate, string endDate, string userId, string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<SHRoleDocPrintListItem>> SPListSHRoleAsync(int flowid, int dwid, string name, string status, string startDate, string endDate, string userId, string userName, string schSerId, int pageSize, int pageIndex);
|
||||
Task<string> ShenheOkSHRoleAsync(FlowTranctDto model);
|
||||
|
||||
// ---- 规格表 / 签名 / 退回 / 金额 ----
|
||||
@@ -64,7 +68,26 @@ namespace YhCore.Service.Interfaces
|
||||
Task<string> TuiHuiAsync(string userId, string cont, decimal docId);
|
||||
Task<string> UnTuiHuiAsync(string userId, decimal docId);
|
||||
Task<string> RegetJEAsync(string schSerId, string userId, List<decimal> listDocPrintId, string type);
|
||||
Task<List<SHRoleRegetJERdItem>> GetRegetJERdAsync(string schSerId, int pageSize, int pageIndex);
|
||||
Task<PageVO<SHRoleRegetJERdItem>> GetRegetJERdAsync(string schSerId, int pageSize, int pageIndex);
|
||||
Task<int> SetDownFileTimesAsync(int serId);
|
||||
|
||||
// ---- Excel 导入导出 / 审批单 PDF(延后批次补齐)----
|
||||
/// <summary>下载导入模板(旧 downMBOfSHRole,代码生成)。</summary>
|
||||
Task<byte[]> DownMBOfSHRoleAsync();
|
||||
|
||||
/// <summary>Excel 批量导入审核角色(旧 UpfileExcelSHRole,multipart 第一个文件),返回反馈消息。</summary>
|
||||
Task<string> UpfileExcelSHRoleAsync(string schSerId, string userId, IFormFile file);
|
||||
|
||||
/// <summary>下载最近一次导入的反馈 Excel(旧 downUpfileSHRoleFK)。</summary>
|
||||
Task<byte[]> DownUpfileSHRoleFKAsync(string userId);
|
||||
|
||||
/// <summary>全部文印导出(旧 ExceltotalListSHRole)。</summary>
|
||||
Task<(byte[] Content, string FileName)> ExceltotalListSHRoleAsync(string schSerId, string name, string stateid, string starttime, string endtime);
|
||||
|
||||
/// <summary>文印记录导出(旧 WYRDExceldaochu,flag=page 简版 / 其余完整版)。</summary>
|
||||
Task<(byte[] Content, string FileName)> WYRDExceldaochuAsync(string schSerId, int flowid, int dwid, string name, string stateid, string starttime, string endtime, string flag);
|
||||
|
||||
/// <summary>审批单 PDF 下载(旧 DwonWYSQD,IDS 逗号分隔的 WY_DocPrint.SerID)。</summary>
|
||||
Task<(byte[] Content, string FileName)> DwonWYSQDAsync(string schSerId, string userId, string ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
|
||||
namespace YhCore.Service.Pdf
|
||||
{
|
||||
/// <summary>数字转中文大写金额(移植自旧 Bsch.Common/DocPrint/NumberToChineseConverter,审批单合计金额用)。</summary>
|
||||
public class NumberToChineseConverter
|
||||
{
|
||||
// 中文大写数字
|
||||
private static readonly char[] ChineseNumbers = { '零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖' };
|
||||
// 中文大写单位
|
||||
private static readonly string[] ChineseUnits = { "分", "角", "元", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "兆" };
|
||||
|
||||
/// <summary>
|
||||
/// 将数字转换为中文大写金额
|
||||
/// </summary>
|
||||
/// <param name="number">需要转换的数字(支持整数和小数,建议保留两位小数)</param>
|
||||
/// <returns>中文大写金额字符串</returns>
|
||||
public static string ToChineseUppercase(decimal number)
|
||||
{
|
||||
// 处理负数
|
||||
bool isNegative = number < 0;
|
||||
if (isNegative)
|
||||
{
|
||||
number = Math.Abs(number);
|
||||
}
|
||||
|
||||
// 四舍五入保留两位小数
|
||||
number = Math.Round(number, 2);
|
||||
long integerPart = (long)number; // 整数部分
|
||||
int decimalPart = (int)((number - integerPart) * 100); // 小数部分(角分)
|
||||
|
||||
// 处理零值
|
||||
if (integerPart == 0 && decimalPart == 0)
|
||||
{
|
||||
return "零元整";
|
||||
}
|
||||
|
||||
string result = string.Empty;
|
||||
|
||||
// 处理整数部分
|
||||
if (integerPart > 0)
|
||||
{
|
||||
string integerStr = integerPart.ToString();
|
||||
int length = integerStr.Length;
|
||||
|
||||
// 标记是否需要加零
|
||||
bool needZero = false;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int digit = int.Parse(integerStr[i].ToString());
|
||||
int unitIndex = length - i - 1 + 2; // 对应到ChineseUnits的索引(从元开始)
|
||||
|
||||
if (digit == 0)
|
||||
{
|
||||
needZero = true;
|
||||
// 处理万、亿等单位,避免漏单位
|
||||
if (unitIndex == 6 || unitIndex == 10)
|
||||
{
|
||||
result += ChineseUnits[unitIndex];
|
||||
needZero = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (needZero)
|
||||
{
|
||||
result += ChineseNumbers[0];
|
||||
needZero = false;
|
||||
}
|
||||
result += ChineseNumbers[digit] + ChineseUnits[unitIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理小数部分(角分)
|
||||
if (decimalPart > 0)
|
||||
{
|
||||
int jiao = decimalPart / 10; // 角
|
||||
int fen = decimalPart % 10; // 分
|
||||
|
||||
if (jiao > 0)
|
||||
{
|
||||
result += ChineseNumbers[jiao] + ChineseUnits[1];
|
||||
}
|
||||
else if (integerPart > 0) // 有整数部分但角为0,需要加零
|
||||
{
|
||||
result += ChineseNumbers[0];
|
||||
}
|
||||
|
||||
if (fen > 0)
|
||||
{
|
||||
result += ChineseNumbers[fen] + ChineseUnits[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无小数部分,加"整"
|
||||
result += "整";
|
||||
}
|
||||
|
||||
// 处理负数
|
||||
if (isNegative)
|
||||
{
|
||||
result = "负" + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 重载方法,支持double类型
|
||||
public static string ToChineseUppercase(double number)
|
||||
{
|
||||
return ToChineseUppercase((decimal)number);
|
||||
}
|
||||
|
||||
// 重载方法,支持int类型
|
||||
public static string ToChineseUppercase(int number)
|
||||
{
|
||||
return ToChineseUppercase((decimal)number);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// PDF helper(移植自旧 Bsch.Common/DocPrint/PdfHelper):
|
||||
// HTML→PDF 由 wkhtmltopdf 外部进程完成(原 iTextSharp XMLWorker 无 Core 版),
|
||||
// 多页合并用 iTextSharp.LGPLv2.Core;Base64→PNG 直接落盘。
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using iTextSharp.text;
|
||||
using iTextSharp.text.pdf;
|
||||
using Yh.Platform.Abstractions;
|
||||
using YhCore.Common.Config;
|
||||
|
||||
namespace YhCore.Service.Pdf
|
||||
{
|
||||
public class PdfHelper
|
||||
{
|
||||
private static string WkHtmlToPdfPath =>
|
||||
string.IsNullOrEmpty(Config.WkHtmlToPdfPath) ? "wkhtmltopdf" : Config.WkHtmlToPdfPath;
|
||||
|
||||
// --enable-local-file-access 为 wkhtmltopdf 0.12.6 新增开关(签名图片用本地绝对路径 <img> 必需);
|
||||
// 0.12.5 及以下(如 Debian 10 源)不认识该参数会报 Unknown long argument,按版本探测决定是否追加
|
||||
private static readonly Lazy<bool> NeedsLocalFileAccessFlag = new Lazy<bool>(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var probe = Process.Start(new ProcessStartInfo(WkHtmlToPdfPath, "--version")
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
});
|
||||
if (probe == null) return true;
|
||||
string output = probe.StandardOutput.ReadToEnd();
|
||||
probe.WaitForExit(10000);
|
||||
var m = Regex.Match(output, @"(\d+)\.(\d+)\.(\d+)");
|
||||
if (!m.Success) return true;
|
||||
var version = new Version(int.Parse(m.Groups[1].Value), int.Parse(m.Groups[2].Value), int.Parse(m.Groups[3].Value));
|
||||
return version >= new Version(0, 12, 6);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>逐页 HTML→PDF 后合并为单个 PDF 字节流(DwonWYSQD 用)</summary>
|
||||
public static byte[] ConvertHtmlTextToPdfByList(List<string> htmlText, string HorV = "V")
|
||||
=> MergePdf(htmlText.Select(HtmlToPdf).ToList());
|
||||
|
||||
private static byte[] HtmlToPdf(string html)
|
||||
{
|
||||
var tmpIn = Path.GetTempFileName() + ".html";
|
||||
var tmpOut = Path.GetTempFileName() + ".pdf";
|
||||
try
|
||||
{
|
||||
File.WriteAllText(tmpIn, html, new UTF8Encoding(false));
|
||||
string localFlag = NeedsLocalFileAccessFlag.Value ? " --enable-local-file-access" : "";
|
||||
var psi = new ProcessStartInfo(WkHtmlToPdfPath,
|
||||
$" --encoding utf-8{localFlag} --disable-smart-shrinking \"{tmpIn}\" \"{tmpOut}\"")
|
||||
{ CreateNoWindow = true, UseShellExecute = false };
|
||||
using var proc = Process.Start(psi);
|
||||
if (proc == null || !proc.WaitForExit(60000) || !File.Exists(tmpOut))
|
||||
throw new DException("审批单 PDF 生成失败,请检查服务器是否已安装 wkhtmltopdf(或配置 wkhtmltopdf_path 指向其可执行文件)!");
|
||||
return File.ReadAllBytes(tmpOut);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(tmpIn)) File.Delete(tmpIn);
|
||||
if (File.Exists(tmpOut)) File.Delete(tmpOut);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] MergePdf(List<byte[]> pdfBytes)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
var doc = new Document();
|
||||
var copy = new PdfCopy(doc, ms);
|
||||
doc.Open();
|
||||
foreach (var b in pdfBytes)
|
||||
{
|
||||
if (b == null || b.Length == 0) continue;
|
||||
var reader = new PdfReader(b);
|
||||
for (int i = 1; i <= reader.NumberOfPages; i++)
|
||||
copy.AddPage(copy.GetImportedPage(reader, i));
|
||||
copy.FreeReader(reader);
|
||||
reader.Close();
|
||||
}
|
||||
doc.Close();
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>签名 base64 → PNG 落盘(审批单 HTML 内嵌本地路径图片用)</summary>
|
||||
public static void ConvertBase64ToPng(string base64String, string outputFilePath)
|
||||
{
|
||||
var bytes = Convert.FromBase64String(base64String);
|
||||
File.WriteAllBytes(outputFilePath, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,16 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- 业务异常 DException / ErrorCode / IErrorCode:从 Nexus nuget-group 引用(原为本地源项目引用) -->
|
||||
<PackageReference Include="Yh.Platform.Abstractions" Version="1.0.0-preview.1" />
|
||||
<PackageReference Include="Yh.Platform.Abstractions" Version="1.0.0-preview.2" />
|
||||
<!-- 教务(SchClass)开放接口 SDK:教师/学校查询(C1 reviewprocess 教师类接口):从 Nexus nuget-group 引用(原为本地源项目引用) -->
|
||||
<PackageReference Include="Yh.SchClass.OpenApi" Version="1.0.0-preview.1" />
|
||||
<PackageReference Include="Yh.SchClass.OpenApi" Version="1.0.0-preview.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Excel 导入导出(NPOI .xls 读写 / EPPlus 多 Sheet 导出)与 PDF 合并:版本对齐旧 DocPrintCore/Bsch.Common -->
|
||||
<PackageReference Include="NPOI" Version="2.6.2" />
|
||||
<PackageReference Include="EPPlus" Version="4.5.3.3" />
|
||||
<PackageReference Include="iTextSharp.LGPLv2.Core" Version="3.4.20" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace YhCore.Tasks.Jobs
|
||||
|
||||
try
|
||||
{
|
||||
var oldList = await repo.FindAsync(l => l.CreateTime < cutoff);
|
||||
var oldList = await repo.FindTrackedAsync(l => l.CreateTime < cutoff);
|
||||
if (oldList.Count == 0)
|
||||
{
|
||||
logger.LogInformation("审计日志清理完成:无 30 天前的数据");
|
||||
|
||||
@@ -44,7 +44,12 @@ namespace YhCore.WebApi.Auth
|
||||
var auth = configuration.GetSection(YhAuthOptions.SectionName).Get<YhAuthOptions>() ?? new YhAuthOptions();
|
||||
options.Cookie.Name = auth.CookieName;
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
|
||||
// SameSite=Lax:登录/回调全部是顶层导航,Lax 足够。
|
||||
// 【勿改 None】HTTP 部署下无 Secure 属性的 SameSite=None Cookie 会被浏览器直接丢弃
|
||||
// (Chrome/Edge 强制 None 必须 Secure),导致登录 Cookie 永远种不上 → 无限重定向循环
|
||||
// (2026-09-07 线上复现:验票成功但下一请求无 Cookie → ERR_TOO_MANY_REDIRECTS)。
|
||||
// 跨 host 登录态需求由 DebugUser 头方案解决(见 docs/本地前端联调线上后端SSO方案.md)。
|
||||
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax;
|
||||
// 会话票据:TicketDataFormat 把票据存服务端,Cookie 只写 32 位 key;
|
||||
// IsPersistent=false(CasAuthenticationHandler)会话 Cookie,关闭浏览器即失效;
|
||||
// SlidingExpiration=false 固定过期,不滑动续期。
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Yh.Platform.Abstractions;
|
||||
@@ -49,6 +50,20 @@ namespace YhCore.WebApi.Controllers.Api
|
||||
return BoaUserContext.Get();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取初始化地址(对齐老 UserCenterApiController.GetInitUrl:读 Session["AppInitUrl"])。
|
||||
/// 前端 main.js 首次进入时调用:resp.Data 有值则 router.replace(initUrl),否则回 /docPrint/。
|
||||
/// 响应形状为混排兼容体 {"code":0,"msg":"","State":true,"Data":...}:
|
||||
/// code 小写保证前端 api/index.js 的 success 回调触发(response.code==0),
|
||||
/// Data/State 大写保证 main.js 的 resp.Data 取到值——故用 ContentResult 手写 JSON
|
||||
/// (ContentResult 不参与全局包装,也不经 camelCase ContractResolver)。
|
||||
/// </summary>
|
||||
[HttpGet("GetInitUrl")]
|
||||
public string GetInitUrl()
|
||||
{
|
||||
return HttpContext.Session.GetString("AppInitUrl");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登出(对应 Java GET /api/base/logout:session.invalidate())。
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Yh.Platform.Abstractions;
|
||||
using Yh.SchClass.OpenApi;
|
||||
using Yh.SchClass.OpenApi.Model;
|
||||
using YhCore.Common;
|
||||
using YhCore.Common.Config;
|
||||
using YhCore.Common.UserContext;
|
||||
@@ -28,13 +30,24 @@ namespace YhCore.WebApi.Controllers.Api.Common
|
||||
{
|
||||
private readonly IDictionaryService _dict;
|
||||
private readonly IFileService _file;
|
||||
private readonly SchClassOpenApi _sch;
|
||||
|
||||
public CommonController(IDictionaryService dict, IFileService file)
|
||||
public CommonController(IDictionaryService dict, IFileService file, SchClassOpenApi sch)
|
||||
{
|
||||
_dict = dict;
|
||||
_file = file;
|
||||
_sch = sch;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("GetSchoolAll")]
|
||||
public async Task<List<TeaTreeVo>> GetSchoolAll([FromQuery] int type = 0)
|
||||
{
|
||||
return await _sch.SchTree.GetTeaTreeAsync(SchSerID(), null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>获取指定字典数据(Type 1~10)。</summary>
|
||||
[HttpGet("GetSysDic")]
|
||||
public async Task<List<DicOption>> GetSysDic([FromQuery] int type = 0)
|
||||
@@ -222,42 +235,58 @@ namespace YhCore.WebApi.Controllers.Api.Common
|
||||
|
||||
/// <summary>
|
||||
/// 公用附件上传(旧 FileUpload,[NoAuth])。
|
||||
/// chunk 参数 → 分片保存返回 {code:1,type:"chunk",msg};fileName 参数 → 合并分片;否则单文件上传。
|
||||
/// 分片(chunk/chunkIndex + totalChunks>1)→ 分片保存返回 {code:1,type:"chunk",msg};
|
||||
/// 只带 fileName 不带文件 → 合并分片;带文件 → 单文件直传
|
||||
/// (前端单文件直传也会带 guid/chunkIndex=0/totalChunks=0/fileName,不得误入合并分支)。
|
||||
/// guid/chunk/fileName 兼容 query 与 form 两种形态(旧版读 Query)。
|
||||
/// 落库 Common_UpFiles(TopicType=AppCode),返回 {fileurl,host,fileid,name,type}。
|
||||
/// </summary>
|
||||
[HttpPost("FileUpload")]
|
||||
public async Task<object> Upload()
|
||||
{
|
||||
string apptype = "docprint"; // 旧 WebHelper.AppType 固定 docprint
|
||||
string guid = Request.Form["guid"];
|
||||
string chunk = Request.Form["chunk"];
|
||||
string fileName = Request.Form["fileName"];
|
||||
string guid = FormOrQuery("guid");
|
||||
string chunk = FormOrQuery("chunk"); // 旧契约分片序号
|
||||
string chunkIndex = FormOrQuery("chunkIndex"); // 前端现用分片序号
|
||||
string totalChunks = FormOrQuery("totalChunks");
|
||||
string fileName = FormOrQuery("fileName");
|
||||
|
||||
if (!string.IsNullOrEmpty(chunk))
|
||||
var file = Request.Form.Files.FirstOrDefault(f => f.Name == "file") ?? Request.Form.Files.FirstOrDefault();
|
||||
bool multiChunk = int.TryParse(totalChunks, out var total) && total > 1;
|
||||
bool isChunkReq = file != null
|
||||
&& (!string.IsNullOrEmpty(chunk) || (!string.IsNullOrEmpty(chunkIndex) && multiChunk));
|
||||
|
||||
if (isChunkReq)
|
||||
{
|
||||
var chunkFile = Request.Form.Files.FirstOrDefault(f => f.Name == "file") ?? Request.Form.Files.FirstOrDefault();
|
||||
await _file.SaveChunkAsync(guid, chunk, chunkFile);
|
||||
await _file.SaveChunkAsync(guid, string.IsNullOrEmpty(chunk) ? chunkIndex : chunk, file);
|
||||
return new { code = 1, type = "chunk", msg = "分片上传成功!" };
|
||||
}
|
||||
|
||||
FileObj url;
|
||||
string errMsg = "上传失败!";
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
if (!string.IsNullOrEmpty(fileName) && file == null)
|
||||
{
|
||||
url = await _file.SaveFileAsync(apptype, Request.Form.Files.FirstOrDefault());
|
||||
url = await _file.MergeSaveAsync(apptype, guid, fileName); // 合并分片
|
||||
}
|
||||
else
|
||||
{
|
||||
url = await _file.MergeSaveAsync(apptype, guid, fileName);
|
||||
url = await _file.SaveFileAsync(apptype, file); // 单文件直传
|
||||
}
|
||||
if (url == null)
|
||||
throw new DException(errMsg);
|
||||
throw new DException("上传失败!");
|
||||
|
||||
// 旧 WebHelper.AppCode == "1102" ? "1002" : AppCode → docprint 固定 1003
|
||||
int serid = await _file.SaveEnclosureAsync(url, DocPrintConstants.AppCode.ToString());
|
||||
return new { fileurl = url.fileurl, host = url.host, fileid = serid, name = url.filename, type = url.filetype };
|
||||
}
|
||||
|
||||
/// <summary>取表单/URL 参数(旧版读 Request["x"] 同时覆盖 Query+Form)。</summary>
|
||||
private string FormOrQuery(string name)
|
||||
{
|
||||
var v = Request.Form[name].ToString();
|
||||
if (!string.IsNullOrEmpty(v)) return v;
|
||||
return Request.Query[name].ToString();
|
||||
}
|
||||
|
||||
/// <summary>公用图片上传(旧 FileUploadImage,[NoAuth];多图,返回数组)。</summary>
|
||||
[HttpPost("FileUploadImage")]
|
||||
public async Task<object> UploadImg()
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace YhCore.WebApi.Controllers.Api.Common
|
||||
|
||||
/// <summary>添加审核流程。</summary>
|
||||
[HttpPost("Create")]
|
||||
public async Task Create([FromBody] Common_flowinfo model)
|
||||
public async Task Create([FromForm] Common_flowinfo model)
|
||||
{
|
||||
model.schSerID = SchSerID();
|
||||
await _svc.CreateAsync(model);
|
||||
@@ -57,7 +57,7 @@ namespace YhCore.WebApi.Controllers.Api.Common
|
||||
|
||||
/// <summary>修改审核流程。</summary>
|
||||
[HttpPost("Edit")]
|
||||
public async Task Edit([FromBody] Common_flowinfo model)
|
||||
public async Task Edit([FromForm] Common_flowinfo model)
|
||||
{
|
||||
model.schSerID = SchSerID();
|
||||
await _svc.EditAsync(model);
|
||||
@@ -65,12 +65,12 @@ namespace YhCore.WebApi.Controllers.Api.Common
|
||||
|
||||
/// <summary>添加步骤流程。</summary>
|
||||
[HttpPost("StepCreate")]
|
||||
public Task StepCreate([FromBody] Common_flowstep model)
|
||||
public Task StepCreate([FromForm] Common_flowstep model)
|
||||
=> _svc.StepCreateAsync(model);
|
||||
|
||||
/// <summary>修改步骤流程。</summary>
|
||||
[HttpPost("StepEdit")]
|
||||
public Task StepEdit([FromBody] Common_flowstep model)
|
||||
public Task StepEdit([FromForm] Common_flowstep model)
|
||||
=> _svc.StepEditAsync(model);
|
||||
|
||||
/// <summary>删除审核流程(ids 逗号分隔)。</summary>
|
||||
@@ -80,7 +80,7 @@ namespace YhCore.WebApi.Controllers.Api.Common
|
||||
|
||||
/// <summary>删除步骤。</summary>
|
||||
[HttpPost("StepDelete")]
|
||||
public Task StepDelete([FromBody] decimal[] ids)
|
||||
public Task StepDelete([FromForm] decimal[] ids)
|
||||
=> _svc.StepDeleteAsync(ids);
|
||||
|
||||
/// <summary>流程绑定的步骤列表。</summary>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Yh.Platform.Abstractions;
|
||||
using YhCore.Common;
|
||||
using YhCore.Common.UserContext;
|
||||
@@ -15,8 +17,10 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
{
|
||||
/// <summary>
|
||||
/// 审核角色 + 流程配置控制器(对应旧 Areas/DocPrint/WYSHRoleController,路由 api/docprint/wyshrole)。
|
||||
/// 45 action 中本批落地 39 个;Excel 导入导出(ExceltotalListSHRole/WYRDExceldaochu/UpfileExcelSHRole/
|
||||
/// downMBOfSHRole/downUpfileSHRoleFK)与审批单 PDF(DwonWYSQD)依赖 NPOI/PdfHelper,延后迁移。
|
||||
/// 45 action 全部落地(Excel 导入导出 5 个走 NPOI/EPPlus,审批单 PDF 走 wkhtmltopdf)。
|
||||
/// POST 动作一律 [FromForm]:前端 api/index.js 固定 x-www-form-urlencoded 提交([FromForm] 会 415);
|
||||
/// 老契约"表单字段塞 JSON"的动作(WYSHRole/SHTeaTxt/listXxxTxt/data/docdata/docnumdata)
|
||||
/// 以 [FromForm] string 接收后手动反序列化。
|
||||
/// </summary>
|
||||
[EnableCors("default")]
|
||||
[ApiController]
|
||||
@@ -30,6 +34,14 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
_role = role;
|
||||
}
|
||||
|
||||
/// <summary>解析老契约"表单字段塞 JSON"的值;空串安全,非法 JSON 返回 null 由调用方判参。</summary>
|
||||
private static T ParseFormJson<T>(string txt) where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(txt)) return null;
|
||||
try { return JsonConvert.DeserializeObject<T>(txt); }
|
||||
catch (JsonException) { return null; }
|
||||
}
|
||||
|
||||
#region 审核角色管理
|
||||
|
||||
/// <summary>学校是否启用审核角色(旧 getIsSHRole)。</summary>
|
||||
@@ -49,18 +61,20 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
public async Task<string> UpdateSHRoleName([FromQuery] decimal serID, [FromQuery] string sHRoleName)
|
||||
=> await _role.UpdateSHRoleNameAsync(SchSerID(), UserId(), serID, sHRoleName);
|
||||
|
||||
/// <summary>新增/编辑角色(旧 EditSHRole,form: WYSHRole=JSON → [FromBody])。</summary>
|
||||
/// <summary>新增/编辑角色(旧 EditSHRole,form: WYSHRole=JSON 字符串)。</summary>
|
||||
[HttpPost("EditSHRole")]
|
||||
public async Task<string> EditSHRole([FromBody] WYSHRoleExt model)
|
||||
public async Task<string> EditSHRole([FromForm] string WYSHRole)
|
||||
{
|
||||
var model = ParseFormJson<WYSHRoleExt>(WYSHRole);
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
return await _role.EditSHRoleAsync(SchSerID(), UserId(), model);
|
||||
}
|
||||
|
||||
/// <summary>编辑成员行(旧 editSHTeaRow,form: SHTeaTxt → [FromBody])。</summary>
|
||||
/// <summary>编辑成员行(旧 editSHTeaRow,form: SHTeaTxt=JSON 字符串)。</summary>
|
||||
[HttpPost("editSHTeaRow")]
|
||||
public async Task<string> EditSHTeaRow([FromBody] WY_SHRole_SHTea model)
|
||||
public async Task<string> EditSHTeaRow([FromForm] string SHTeaTxt)
|
||||
{
|
||||
var model = ParseFormJson<WY_SHRole_SHTea>(SHTeaTxt);
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
return await _role.EditSHTeaRowAsync(SchSerID(), UserId(), model);
|
||||
}
|
||||
@@ -70,35 +84,41 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
public async Task<string> DelSHTeaRow([FromQuery] decimal serID)
|
||||
=> await _role.DelSHTeaRowAsync(SchSerID(), serID);
|
||||
|
||||
/// <summary>批量保存审核人(旧 editSHTea,form: SHRoleID + listSHTeaTxt → [FromBody])。</summary>
|
||||
/// <summary>批量保存审核人(旧 editSHTea,form: SHRoleID + listSHTeaTxt=JSON 数组字符串)。</summary>
|
||||
[HttpPost("editSHTea")]
|
||||
public async Task<string> EditSHTea([FromBody] EditSHTeaDto model)
|
||||
public async Task<string> EditSHTea([FromForm] decimal SHRoleID, [FromForm] string listSHTeaTxt)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
return await _role.EditSHTeaAsync(SchSerID(), UserId(), model.SHRoleID, model.listSHTea);
|
||||
var list = ParseFormJson<List<WY_SHRole_SHTea>>(listSHTeaTxt) ?? new List<WY_SHRole_SHTea>();
|
||||
return await _role.EditSHTeaAsync(SchSerID(), UserId(), SHRoleID, list);
|
||||
}
|
||||
|
||||
/// <summary>批量保存被审核人(旧 editBSHTea,form: SHRoleID + listBSHTeaTxt → [FromBody])。</summary>
|
||||
/// <summary>批量保存被审核人(旧 editBSHTea,form: SHRoleID + listBSHTeaTxt=JSON 数组字符串)。</summary>
|
||||
[HttpPost("editBSHTea")]
|
||||
public async Task<string> EditBSHTea([FromBody] EditBSHTeaDto model)
|
||||
public async Task<string> EditBSHTea([FromForm] decimal SHRoleID, [FromForm] string listBSHTeaTxt)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
return await _role.EditBSHTeaAsync(SchSerID(), UserId(), model.SHRoleID, model.listBSHTea);
|
||||
var list = ParseFormJson<List<WY_SHRole_BSHTea>>(listBSHTeaTxt) ?? new List<WY_SHRole_BSHTea>();
|
||||
return await _role.EditBSHTeaAsync(SchSerID(), UserId(), SHRoleID, list);
|
||||
}
|
||||
|
||||
/// <summary>删除角色(旧 delSHRole,form: listSHRoleIDTxt → [FromBody])。</summary>
|
||||
/// <summary>删除角色(旧 delSHRole,form: listSHRoleIDTxt=JSON 数组字符串,兼容逗号分隔)。</summary>
|
||||
[HttpPost("delSHRole")]
|
||||
public async Task<string> DelSHRole([FromBody] DelSHRoleDto model)
|
||||
public async Task<string> DelSHRole([FromForm] string listSHRoleIDTxt)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
return await _role.DelSHRoleAsync(SchSerID(), model.listSHRoleID);
|
||||
var list = ParseFormJson<List<decimal?>>(listSHRoleIDTxt);
|
||||
if (list == null)
|
||||
{
|
||||
list = (listSHRoleIDTxt ?? "").Split(',')
|
||||
.Where(a => !string.IsNullOrWhiteSpace(a) && decimal.TryParse(a.Trim(), out _))
|
||||
.Select(a => (decimal?)decimal.Parse(a.Trim())).ToList();
|
||||
}
|
||||
return await _role.DelSHRoleAsync(SchSerID(), list);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 审核角色流程
|
||||
|
||||
/// <summary>审核流程列表(旧 ListFlow)。</summary>
|
||||
/// <summary>审核流程列表(旧 ListFlow;分页返回 PageVO,Content 内对象字段与旧 Data 对齐)。</summary>
|
||||
[HttpGet("ListFlow")]
|
||||
public async Task<PageVO<SHRoleFlowListItem>> ListFlow(
|
||||
[FromQuery] string name = null,
|
||||
@@ -110,7 +130,7 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>新增/编辑流程(旧 EditFlow;schSerID 取当前用户)。</summary>
|
||||
[HttpPost("EditFlow")]
|
||||
public async Task<string> EditFlow([FromBody] Common_flowinfo model)
|
||||
public async Task<string> EditFlow([FromForm] Common_flowinfo model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
model.schSerID = SchSerID();
|
||||
@@ -119,7 +139,7 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>新增步骤(旧 StepCreateOfSHRole)。</summary>
|
||||
[HttpPost("StepCreateOfSHRole")]
|
||||
public async Task<string> StepCreateOfSHRole([FromBody] Common_flowstep model)
|
||||
public async Task<string> StepCreateOfSHRole([FromForm] Common_flowstep model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
return await _role.StepCreateOfSHRoleAsync(model);
|
||||
@@ -180,11 +200,22 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
public async Task<List<Common_Dictionary>> GetDicInfo([FromQuery] string typeID)
|
||||
=> await _role.GetDicInfoAsync(SchSerID(), typeID);
|
||||
|
||||
/// <summary>编辑文印(审核角色视角;旧 editDocPrint,form 三段 JSON → [FromBody])。</summary>
|
||||
/// <summary>编辑文印(审核角色视角;旧 editDocPrint,form: isedit + data/docdata/docnumdata 三段 JSON 字符串)。</summary>
|
||||
[HttpPost("editDocPrint")]
|
||||
public async Task<int> EditDocPrint([FromBody] WYSHRoleEditDocPrintDto model)
|
||||
public async Task<int> EditDocPrint(
|
||||
[FromForm] int isedit,
|
||||
[FromForm] string data,
|
||||
[FromForm] string docdata,
|
||||
[FromForm] string docnumdata)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
var model = new WYSHRoleEditDocPrintDto
|
||||
{
|
||||
isedit = isedit,
|
||||
data = ParseFormJson<WYFlowForUser>(data),
|
||||
docdata = ParseFormJson<WYDocPrintSaveDto>(docdata),
|
||||
docnumdata = ParseFormJson<List<WY_DocPrint_Num>>(docnumdata) ?? new List<WY_DocPrint_Num>()
|
||||
};
|
||||
if (model.data == null || model.docdata == null) throw new DException("参数错误!");
|
||||
return await _role.EditDocPrintAsync(SchSerID(), UserId(), model);
|
||||
}
|
||||
|
||||
@@ -207,9 +238,9 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
public async Task<List<SHRoleDocPrintListItem>> MyListtoOneSHRole([FromQuery] int serID)
|
||||
=> await _role.MyListtoOneSHRoleAsync(serID, SchSerID());
|
||||
|
||||
/// <summary>我的文印(旧 MyListSHRole)。</summary>
|
||||
/// <summary>我的文印(旧 MyListSHRole,分页返回 PageVO)。</summary>
|
||||
[HttpGet("MyListSHRole")]
|
||||
public async Task<List<SHRoleDocPrintListItem>> MyListSHRole(
|
||||
public async Task<PageVO<SHRoleDocPrintListItem>> MyListSHRole(
|
||||
[FromQuery] string name = null,
|
||||
[FromQuery] string status = null,
|
||||
[FromQuery] string startDate = null,
|
||||
@@ -218,9 +249,9 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
[FromQuery] int pageIndex = 0)
|
||||
=> await _role.MyListSHRoleAsync(name, status, startDate, endDate, pageSize, pageIndex, UserId(), SchSerID());
|
||||
|
||||
/// <summary>全部文印(旧 totalListSHRole)。</summary>
|
||||
/// <summary>全部文印(旧 totalListSHRole,分页返回 PageVO)。</summary>
|
||||
[HttpGet("totalListSHRole")]
|
||||
public async Task<List<SHRoleDocPrintListItem>> TotalListSHRole(
|
||||
public async Task<PageVO<SHRoleDocPrintListItem>> TotalListSHRole(
|
||||
[FromQuery] int flowid = 0,
|
||||
[FromQuery] int dwid = 0,
|
||||
[FromQuery] string name = null,
|
||||
@@ -231,9 +262,9 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
[FromQuery] int pageIndex = 0)
|
||||
=> await _role.TotalListSHRoleAsync(flowid, dwid, name, stateid, starttime, endtime, pageSize, pageIndex, SchSerID());
|
||||
|
||||
/// <summary>抄送列表(旧 SendRdListSHRole)。</summary>
|
||||
/// <summary>抄送列表(旧 SendRdListSHRole,分页返回 PageVO)。</summary>
|
||||
[HttpGet("SendRdListSHRole")]
|
||||
public async Task<List<SHRoleDocPrintListItem>> SendRdListSHRole(
|
||||
public async Task<PageVO<SHRoleDocPrintListItem>> SendRdListSHRole(
|
||||
[FromQuery] int flowid = 0,
|
||||
[FromQuery] int dwid = 0,
|
||||
[FromQuery] string name = null,
|
||||
@@ -244,9 +275,9 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
[FromQuery] int pageIndex = 0)
|
||||
=> await _role.SendRdListSHRoleAsync(flowid, dwid, name, status, startDate, endDate, UserId(), SchSerID(), pageSize, pageIndex);
|
||||
|
||||
/// <summary>审批列表(旧 SPListSHRole)。</summary>
|
||||
/// <summary>审批列表(旧 SPListSHRole,分页返回 PageVO)。</summary>
|
||||
[HttpGet("SPListSHRole")]
|
||||
public async Task<List<SHRoleDocPrintListItem>> SPListSHRole(
|
||||
public async Task<PageVO<SHRoleDocPrintListItem>> SPListSHRole(
|
||||
[FromQuery] int flowid = 0,
|
||||
[FromQuery] int dwid = 0,
|
||||
[FromQuery] string name = null,
|
||||
@@ -259,23 +290,89 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>审核确认(旧 shenheokSHRole)。</summary>
|
||||
[HttpPost("shenheokSHRole")]
|
||||
public async Task<string> ShenheOkSHRole([FromBody] FlowTranctDto model)
|
||||
public async Task<string> ShenheOkSHRole([FromForm] FlowTranctDto model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
model.actionuserid = UserContextHolder.Current?.Id ?? 0;
|
||||
return await _role.ShenheOkSHRoleAsync(model);
|
||||
}
|
||||
|
||||
// 延后:ExceltotalListSHRole / WYRDExceldaochu / UpfileExcelSHRole / downMBOfSHRole / downUpfileSHRoleFK(Excel,NPOI)
|
||||
// 延后:DwonWYSQD(审批单 PDF,PdfHelper + Server.MapPath)
|
||||
#region Excel 导入导出 / 审批单 PDF
|
||||
|
||||
/// <summary>全部文印导出(旧 ExceltotalListSHRole,NPOI .xls)。</summary>
|
||||
[HttpGet("ExceltotalListSHRole")]
|
||||
public async Task<IActionResult> ExceltotalListSHRole(
|
||||
[FromQuery] string Name,
|
||||
[FromQuery] string stateid,
|
||||
[FromQuery] string starttime,
|
||||
[FromQuery] string endtime)
|
||||
{
|
||||
var (content, fileName) = await _role.ExceltotalListSHRoleAsync(SchSerID(), Name, stateid, starttime, endtime);
|
||||
return File(content, ContentType(fileName), fileName);
|
||||
}
|
||||
|
||||
/// <summary>文印记录导出(旧 WYRDExceldaochu,flag=page 简版 / 完整版,EPPlus)。</summary>
|
||||
[HttpGet("WYRDExceldaochu")]
|
||||
public async Task<IActionResult> WYRDExceldaochu(
|
||||
[FromQuery] int flowid,
|
||||
[FromQuery] int DWid,
|
||||
[FromQuery] string Name,
|
||||
[FromQuery] string stateid,
|
||||
[FromQuery] string starttime,
|
||||
[FromQuery] string endtime,
|
||||
[FromQuery] string flag)
|
||||
{
|
||||
var (content, fileName) = await _role.WYRDExceldaochuAsync(SchSerID(), flowid, DWid, Name, stateid, starttime, endtime, flag);
|
||||
return File(content, ContentType(fileName), fileName);
|
||||
}
|
||||
|
||||
/// <summary>Excel 批量导入审核角色(旧 UpfileExcelSHRole,multipart 文件字段名不敏感取第一个)。</summary>
|
||||
[HttpPost("UpfileExcelSHRole")]
|
||||
public async Task<string> UpfileExcelSHRole()
|
||||
{
|
||||
var file = Request.Form.Files.Count > 0 ? Request.Form.Files[0] : null;
|
||||
return await _role.UpfileExcelSHRoleAsync(SchSerID(), UserId(), file);
|
||||
}
|
||||
|
||||
/// <summary>下载导入模板(旧 downMBOfSHRole,代码生成)。</summary>
|
||||
[HttpGet("downMBOfSHRole")]
|
||||
public async Task<IActionResult> DownMBOfSHRole()
|
||||
{
|
||||
var content = await _role.DownMBOfSHRoleAsync();
|
||||
return File(content, "application/vnd.ms-excel", "批量导入审核角色.xls");
|
||||
}
|
||||
|
||||
/// <summary>下载最近一次导入的反馈 Excel(旧 downUpfileSHRoleFK)。</summary>
|
||||
[HttpGet("downUpfileSHRoleFK")]
|
||||
public async Task<IActionResult> DownUpfileSHRoleFK()
|
||||
{
|
||||
var content = await _role.DownUpfileSHRoleFKAsync(UserId());
|
||||
return File(content, "application/vnd.ms-excel", "批量导入审核角色反馈.xls");
|
||||
}
|
||||
|
||||
/// <summary>审批单 PDF 下载(旧 DwonWYSQD,IDS 逗号分隔的 WY_DocPrint.SerID;依赖服务器安装 wkhtmltopdf)。</summary>
|
||||
[HttpGet("DwonWYSQD")]
|
||||
public async Task<IActionResult> DwonWYSQD([FromQuery] string IDS)
|
||||
{
|
||||
var (content, fileName) = await _role.DwonWYSQDAsync(SchSerID(), UserId(), IDS);
|
||||
return File(content, "application/pdf", fileName);
|
||||
}
|
||||
|
||||
/// <summary>按扩展名取下载 Content-Type(对齐 CommonController.GetContentType 约定)。</summary>
|
||||
private static string ContentType(string fileName)
|
||||
=> fileName != null && fileName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase)
|
||||
? "application/pdf"
|
||||
: "application/vnd.ms-excel";
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region 签名 / 退回 / 金额重提
|
||||
|
||||
/// <summary>保存签名(旧 SaveSignature,form: pic → [FromBody])。</summary>
|
||||
/// <summary>保存签名(旧 SaveSignature,form: pic → [FromForm])。</summary>
|
||||
[HttpPost("SaveSignature")]
|
||||
public async Task<string> SaveSignature([FromBody] SaveSignatureDto model)
|
||||
public async Task<string> SaveSignature([FromForm] SaveSignatureDto model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
return await _role.SaveSignatureAsync(SchSerID(), UserId(), model.pic);
|
||||
@@ -289,9 +386,9 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
[HttpGet("DelSignature")]
|
||||
public async Task<string> DelSignature() => await _role.DelSignatureAsync(SchSerID(), UserId());
|
||||
|
||||
/// <summary>退回(旧 TuiHui,form: DocID + cont → [FromBody])。</summary>
|
||||
/// <summary>退回(旧 TuiHui,form: DocID + cont → [FromForm])。</summary>
|
||||
[HttpPost("TuiHui")]
|
||||
public async Task<string> TuiHui([FromBody] TuiHuiDto model)
|
||||
public async Task<string> TuiHui([FromForm] TuiHuiDto model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
return await _role.TuiHuiAsync(UserId(), model.cont, model.DocID);
|
||||
@@ -302,9 +399,9 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
public async Task<string> UnTuiHui([FromQuery] decimal docID)
|
||||
=> await _role.UnTuiHuiAsync(UserId(), docID);
|
||||
|
||||
/// <summary>金额重提(旧 regetJE,form: DocID 逗号分隔 + type → [FromBody])。</summary>
|
||||
/// <summary>金额重提(旧 regetJE,form: DocID 逗号分隔 + type → [FromForm])。</summary>
|
||||
[HttpPost("regetJE")]
|
||||
public async Task<string> RegetJE([FromBody] RegetJEDto model)
|
||||
public async Task<string> RegetJE([FromForm] RegetJEDto model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
var ids = (model.DocID ?? "").Split(',')
|
||||
@@ -313,9 +410,9 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
return await _role.RegetJEAsync(SchSerID(), UserId(), ids, model.type);
|
||||
}
|
||||
|
||||
/// <summary>重提记录(旧 getRegetJERd)。</summary>
|
||||
/// <summary>重提记录(旧 getRegetJERd,分页返回 PageVO)。</summary>
|
||||
[HttpGet("getRegetJERd")]
|
||||
public async Task<List<SHRoleRegetJERdItem>> GetRegetJERd(
|
||||
public async Task<PageVO<SHRoleRegetJERdItem>> GetRegetJERd(
|
||||
[FromQuery] int pageSize = 10,
|
||||
[FromQuery] int pageIndex = 0)
|
||||
=> await _role.GetRegetJERdAsync(SchSerID(), pageSize, pageIndex);
|
||||
|
||||
@@ -8,6 +8,7 @@ using YhCore.Common;
|
||||
using YhCore.Common.UserContext;
|
||||
using YhCore.Entity;
|
||||
using YhCore.Model.Dto;
|
||||
using YhCore.Model.Vo;
|
||||
using YhCore.Service.Interfaces;
|
||||
|
||||
namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
@@ -37,7 +38,7 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>添加文印分类。</summary>
|
||||
[HttpPost("AddDic")]
|
||||
public async Task AddDic([FromBody] Common_Dictionary model)
|
||||
public async Task AddDic([FromForm] Common_Dictionary model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
model.Schserid = SchSerID();
|
||||
@@ -51,7 +52,7 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>添加印制版面。</summary>
|
||||
[HttpPost("AddYZBM")]
|
||||
public async Task AddYZBM([FromBody] Common_Dictionary model)
|
||||
public async Task AddYZBM([FromForm] Common_Dictionary model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
model.Schserid = SchSerID();
|
||||
@@ -80,20 +81,27 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
[FromQuery] bool showAll = false)
|
||||
=> await _classify.DictionaryTypeListAsync(SchSerID(), name, showAll, typeID);
|
||||
|
||||
/// <summary>添加公共类别(复用字典添加逻辑)。</summary>
|
||||
/// <summary>添加公共类别(复用字典添加逻辑)。前端 api/index.js 固定以 x-www-form-urlencoded 提交
|
||||
/// (TypeId/Name/typeName 表单字段,大小写不敏感绑定到 typeID/Name/typeName),故用 [FromForm];
|
||||
/// 若误用 [FromBody],Content-Type 不符会返回 415。</summary>
|
||||
[HttpPost("AddDictionaryType")]
|
||||
public async Task AddDictionaryType([FromBody] Common_Dictionary model)
|
||||
public async Task AddDictionaryType([FromForm] Common_Dictionary model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
// 表单绑定实例不会为 null,但字段名拼错(如 TypelD≠TypeId)时必填列会为 null,
|
||||
// SaveChanges 才抛"不能将 NULL 插入列"(code:6 无从排查)——在入口显式校验给出可读错误。
|
||||
if (model == null || string.IsNullOrWhiteSpace(model.typeID)
|
||||
|| string.IsNullOrWhiteSpace(model.typeName) || string.IsNullOrWhiteSpace(model.Name))
|
||||
throw new DException("参数错误:typeID、typeName、Name 均不能为空(注意表单字段名拼写,TypeId≠TypelD)");
|
||||
model.Schserid = SchSerID();
|
||||
await _dict.AddDicAsync(model);
|
||||
}
|
||||
|
||||
/// <summary>编辑公共类别(旧调 GetSysDict.EditDic)。</summary>
|
||||
/// <summary>编辑公共类别(旧调 GetSysDict.EditDic)。同 AddDictionaryType:前端表单编码提交,[FromForm] 绑定。</summary>
|
||||
[HttpPost("EditDictionaryType")]
|
||||
public async Task EditDictionaryType([FromBody] Common_Dictionary model)
|
||||
public async Task EditDictionaryType([FromForm] Common_Dictionary model)
|
||||
{
|
||||
if (model == null) throw new DException("参数有误!");
|
||||
if (model == null || model.SerID <= 0 || string.IsNullOrWhiteSpace(model.Name))
|
||||
throw new DException("参数错误:SerID、Name 均不能为空(注意表单字段名拼写,TypeId≠TypelD)");
|
||||
model.Schserid = SchSerID();
|
||||
await _dict.EditDicAsync(model);
|
||||
}
|
||||
@@ -142,9 +150,9 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
#region 文印核心(第三批 D1:抄送 / 附件 / CRUD / 列表 / 审核 / 统计 / 指派)
|
||||
|
||||
/// <summary>抄送列表(旧 SendRdList)。</summary>
|
||||
/// <summary>抄送列表(旧 SendRdList,分页返回 PageVO)。</summary>
|
||||
[HttpGet("SendRdList")]
|
||||
public async Task<System.Collections.Generic.List<DocPrintListItem>> SendRdList(
|
||||
public async Task<PageVO<DocPrintListItem>> SendRdList(
|
||||
[FromQuery] string name = null,
|
||||
[FromQuery] string startDate = null,
|
||||
[FromQuery] string endDate = null,
|
||||
@@ -169,7 +177,7 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>添加文印(旧 Create;短信/待办推送已降级为纯落库)。</summary>
|
||||
[HttpPost("Create")]
|
||||
public async Task<int> Create([FromBody] WYDocPrintSaveDto model)
|
||||
public async Task<int> Create([FromForm] WYDocPrintSaveDto model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
model.UserID = UserId();
|
||||
@@ -179,7 +187,7 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>修改文印(旧 Edit)。</summary>
|
||||
[HttpPost("Edit")]
|
||||
public async Task<int> Edit([FromBody] WYDocPrintSaveDto model)
|
||||
public async Task<int> Edit([FromForm] WYDocPrintSaveDto model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
model.UserID = UserId();
|
||||
@@ -189,16 +197,16 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>撤销(旧 Revoke)。</summary>
|
||||
[HttpPost("Revoke")]
|
||||
public async Task<string> Revoke([FromBody] FlowTranctDto model)
|
||||
public async Task<string> Revoke([FromForm] FlowTranctDto model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
model.actionuserid = UserContextHolder.Current?.Id;
|
||||
return await _wy.RevokeAsync(model);
|
||||
}
|
||||
|
||||
/// <summary>我的文印列表(旧 MyList)。</summary>
|
||||
/// <summary>我的文印列表(旧 MyList,分页返回 PageVO)。</summary>
|
||||
[HttpGet("MyList")]
|
||||
public async Task<System.Collections.Generic.List<DocPrintListItem>> MyList(
|
||||
public async Task<PageVO<DocPrintListItem>> MyList(
|
||||
[FromQuery] string name = null,
|
||||
[FromQuery] string status = null,
|
||||
[FromQuery] string startDate = null,
|
||||
@@ -207,24 +215,24 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
[FromQuery] int pageIndex = 0)
|
||||
=> await _wy.MyListAsync(name, status, startDate, endDate, UserId(), SchSerID(), pageSize, pageIndex);
|
||||
|
||||
/// <summary>全部文印列表(旧 AllList 四分类,返回 ctype)。</summary>
|
||||
/// <summary>全部文印列表(旧 AllList 四分类,返回 ctype;分页返回 PageVO)。</summary>
|
||||
[HttpGet("AllList")]
|
||||
public async Task<System.Collections.Generic.List<DocPrintListItem>> AllList(
|
||||
public async Task<PageVO<DocPrintListItem>> AllList(
|
||||
[FromQuery] string name = null,
|
||||
[FromQuery] int pageSize = 10,
|
||||
[FromQuery] int pageIndex = 0)
|
||||
=> await _wy.AllListAsync(name, UserId(), SchSerID(), pageSize, pageIndex);
|
||||
|
||||
/// <summary>文印审核列表(旧 ShenheList;旧 Name 过滤为死代码,保持不生效)。</summary>
|
||||
/// <summary>文印审核列表(旧 ShenheList;旧 Name 过滤为死代码,保持不生效;分页返回 PageVO)。</summary>
|
||||
[HttpGet("ShenheList")]
|
||||
public async Task<System.Collections.Generic.List<DocPrintListItem>> ShenheList(
|
||||
public async Task<PageVO<DocPrintListItem>> ShenheList(
|
||||
[FromQuery] int pageSize = 10,
|
||||
[FromQuery] int pageIndex = 0)
|
||||
=> await _wy.ShenheListAsync(UserId(), SchSerID(), pageSize, pageIndex);
|
||||
|
||||
/// <summary>审批列表(旧 SPList)。</summary>
|
||||
/// <summary>审批列表(旧 SPList,分页返回 PageVO)。</summary>
|
||||
[HttpGet("SPList")]
|
||||
public async Task<System.Collections.Generic.List<DocPrintListItem>> SPList(
|
||||
public async Task<PageVO<DocPrintListItem>> SPList(
|
||||
[FromQuery] string name = null,
|
||||
[FromQuery] string status = null,
|
||||
[FromQuery] string startDate = null,
|
||||
@@ -233,17 +241,17 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
[FromQuery] int pageIndex = 0)
|
||||
=> await _wy.SPListAsync(name, status, startDate, endDate, UserId(), SchSerID(), pageSize, pageIndex);
|
||||
|
||||
/// <summary>文印已审列表(旧 YishenList)。</summary>
|
||||
/// <summary>文印已审列表(旧 YishenList,分页返回 PageVO)。</summary>
|
||||
[HttpGet("YishenList")]
|
||||
public async Task<System.Collections.Generic.List<DocPrintListItem>> YishenList(
|
||||
public async Task<PageVO<DocPrintListItem>> YishenList(
|
||||
[FromQuery] string name = null,
|
||||
[FromQuery] int pageSize = 10,
|
||||
[FromQuery] int pageIndex = 0)
|
||||
=> await _wy.YishenListAsync(name, UserId(), SchSerID(), pageSize, pageIndex);
|
||||
|
||||
/// <summary>全部文印列表(旧 totalList)。</summary>
|
||||
/// <summary>全部文印列表(旧 totalList,分页返回 PageVO)。</summary>
|
||||
[HttpGet("totalList")]
|
||||
public async Task<System.Collections.Generic.List<DocPrintListItem>> TotalList(
|
||||
public async Task<PageVO<DocPrintListItem>> TotalList(
|
||||
[FromQuery] string name = null,
|
||||
[FromQuery] string stateid = null,
|
||||
[FromQuery] string starttime = null,
|
||||
@@ -259,7 +267,7 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>审核确认(旧 shenheok)。</summary>
|
||||
[HttpPost("shenheok")]
|
||||
public async Task<string> ShenheOk([FromBody] FlowTranctDto model)
|
||||
public async Task<string> ShenheOk([FromForm] FlowTranctDto model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
model.actionuserid = UserContextHolder.Current?.Id ?? 0;
|
||||
@@ -268,7 +276,7 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
|
||||
/// <summary>领取确认(旧 AffirmApplyforOk)。</summary>
|
||||
[HttpPost("AffirmApplyforOk")]
|
||||
public async Task<string> AffirmApplyforOk([FromBody] FlowTranctDto model)
|
||||
public async Task<string> AffirmApplyforOk([FromForm] FlowTranctDto model)
|
||||
{
|
||||
if (model == null) throw new DException("参数错误!");
|
||||
model.actionuserid = UserContextHolder.Current?.Id ?? 0;
|
||||
@@ -298,30 +306,30 @@ namespace YhCore.WebApi.Controllers.Api.DocPrint
|
||||
public async Task<System.Collections.Generic.List<MonthStatItem>> List1([FromQuery] string starttime = null, [FromQuery] string endtime = null)
|
||||
=> await _wy.List1Async(starttime, endtime, SchSerID());
|
||||
|
||||
/// <summary>文印详情统计列表(旧 List2)。</summary>
|
||||
/// <summary>文印详情统计列表(旧 List2,分页返回 PageVO)。</summary>
|
||||
[HttpGet("List2")]
|
||||
public async Task<System.Collections.Generic.List<DocDetailStatItem>> List2(
|
||||
public async Task<PageVO<DocDetailStatItem>> List2(
|
||||
[FromQuery] string starttime = null, [FromQuery] string endtime = null,
|
||||
[FromQuery] int pageSize = 10, [FromQuery] int pageIndex = 0)
|
||||
=> await _wy.List2Async(starttime, endtime, SchSerID(), pageSize, pageIndex);
|
||||
|
||||
/// <summary>文印数量按用户排名(旧 List3)。</summary>
|
||||
/// <summary>文印数量按用户排名(旧 List3,分页返回 PageVO)。</summary>
|
||||
[HttpGet("List3")]
|
||||
public async Task<System.Collections.Generic.List<UserRankItem>> List3(
|
||||
public async Task<PageVO<UserRankItem>> List3(
|
||||
[FromQuery] string starttime = null, [FromQuery] string endtime = null,
|
||||
[FromQuery] int pageSize = 10, [FromQuery] int pageIndex = 0)
|
||||
=> await _wy.List3Async(starttime, endtime, SchSerID(), pageSize, pageIndex);
|
||||
|
||||
/// <summary>按月份统计明细(旧 List4)。</summary>
|
||||
/// <summary>按月份统计明细(旧 List4,分页返回 PageVO)。</summary>
|
||||
[HttpGet("List4")]
|
||||
public async Task<System.Collections.Generic.List<StatListItem>> List4(
|
||||
public async Task<PageVO<StatListItem>> List4(
|
||||
[FromQuery] int year, [FromQuery] int month,
|
||||
[FromQuery] int pageSize = 10, [FromQuery] int pageIndex = 0)
|
||||
=> await _wy.List4Async(year, month, SchSerID(), pageSize, pageIndex);
|
||||
|
||||
/// <summary>按用户统计明细(旧 List5)。</summary>
|
||||
/// <summary>按用户统计明细(旧 List5,分页返回 PageVO)。</summary>
|
||||
[HttpGet("List5")]
|
||||
public async Task<System.Collections.Generic.List<StatListItem>> List5(
|
||||
public async Task<PageVO<StatListItem>> List5(
|
||||
[FromQuery] string userid = null, [FromQuery] string starttime = null, [FromQuery] string endtime = null,
|
||||
[FromQuery] int pageSize = 10, [FromQuery] int pageIndex = 0)
|
||||
=> await _wy.List5Async(userid, starttime, endtime, SchSerID(), pageSize, pageIndex);
|
||||
|
||||
@@ -81,6 +81,12 @@ namespace YhCore.WebApi.Middleware
|
||||
{
|
||||
sb.Append(" | exception=").Append(exceptionSummary);
|
||||
}
|
||||
else if (context.Items.TryGetValue("ExceptionSummary", out var inner)
|
||||
&& !string.IsNullOrEmpty(inner as string))
|
||||
{
|
||||
// GlobalExceptionMiddleware 在内侧把异常转成了 200 响应,摘要经 HttpContext.Items 递出
|
||||
sb.Append(" | exception=").Append(inner);
|
||||
}
|
||||
|
||||
Log.Info(sb.ToString());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using log4net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
@@ -18,6 +19,8 @@ namespace YhCore.WebApi.Middleware
|
||||
/// </summary>
|
||||
public class GlobalExceptionMiddleware
|
||||
{
|
||||
private static readonly ILog Log = LogManager.GetLogger(typeof(GlobalExceptionMiddleware));
|
||||
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public GlobalExceptionMiddleware(RequestDelegate next)
|
||||
@@ -53,9 +56,13 @@ namespace YhCore.WebApi.Middleware
|
||||
timeNow = TimeNow()
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 未捕获异常 → 平台兜底:200 + code:6 内部服务错误(对齐 Java GlobalExceptionHandler.handleException)
|
||||
// 本中间件位于 ApiAccessLogMiddleware 内侧,异常不会穿透到它的 catch,
|
||||
// 必须在此落日志,否则 code:6 无迹可寻(摘要随接口访问日志一并输出)
|
||||
context.Items["ExceptionSummary"] = ex.Message;
|
||||
Log.Error($"未捕获异常 {context.Request.Method} {context.Request.Path}{context.Request.QueryString}", ex);
|
||||
await WriteJsonAsync(context, StatusCodes.Status200OK, new
|
||||
{
|
||||
code = ErrorCode.InternalServerError.Code,
|
||||
|
||||
@@ -80,11 +80,14 @@ namespace YhCore.WebApi
|
||||
});
|
||||
};
|
||||
})
|
||||
// 对齐 Java Jackson:日期格式 yyyy-MM-dd HH:mm:ss + 字段 camelCase(code/msg/data)
|
||||
// 对齐旧项目 DocPrint WebApiConfig:属性名原样输出(DefaultContractResolver),
|
||||
// data 内容字段与旧接口逐字段一致(SerID/DocContent/teaname 等混合大小写 as-is);
|
||||
// 日期对齐旧 IsoDateTimeConverter "yyyy/MM/dd HH:mm:ss"。
|
||||
// 信封 code/msg/data 由 ApiResult 的 [JsonProperty] 显式固定小写,不受 as-is 影响。
|
||||
.AddNewtonsoftJson(o =>
|
||||
{
|
||||
o.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
||||
o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
||||
o.SerializerSettings.DateFormatString = "yyyy/MM/dd HH:mm:ss";
|
||||
o.SerializerSettings.ContractResolver = new DefaultContractResolver();
|
||||
})
|
||||
// 对应 Java Hibernate Validator
|
||||
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());
|
||||
@@ -119,6 +122,10 @@ namespace YhCore.WebApi
|
||||
// Redis 缓存(RedisUtil 单例,连接串取 Redis:ConnectionString)
|
||||
services.AddRedisCache(Configuration);
|
||||
|
||||
// 进程内内存缓存:WYSHRole Excel 导入的反馈表(updateSHRole_{userId},单实例;
|
||||
// 多实例部署需改 Redis 支撑的分布式缓存)
|
||||
services.AddMemoryCache();
|
||||
|
||||
// ===== 会话状态(Session)=====
|
||||
// 对应 Java HttpSession / session.invalidate();旧 DocPrintCore 同款配置。
|
||||
// 注意:YhAuth 的登录态走自定义短 Cookie(SessionTicketDataFormat,票据存进程内存),
|
||||
@@ -168,9 +175,19 @@ namespace YhCore.WebApi
|
||||
.Bind(Configuration.GetSection("Swagger"));
|
||||
services.AddOptions<YhCore.Common.Config.FileConfig>()
|
||||
.Bind(Configuration);
|
||||
// 静态门面(对齐 Java Config):文件路径由配置注入
|
||||
YhCore.Common.Config.Config.FilePath = Configuration["FILE_PATH"];
|
||||
YhCore.Common.Config.Config.FileWebPath = Configuration["FILE_WEB_PATH"];
|
||||
// 静态门面(对齐 Java Config):文件路径由配置注入。
|
||||
// FILE_PATH 必须以 / 结尾(旧约定:FileService 多处 FilePath + "{appType}/..." 直接拼接,缺斜杠会拼出坏路径);
|
||||
// FILE_WEB_PATH 不带尾 /(拼接目标 fileurl 均以 / 开头,带尾斜杠会产生 //)
|
||||
var configFilePath = Configuration["FILE_PATH"];
|
||||
YhCore.Common.Config.Config.FilePath = string.IsNullOrEmpty(configFilePath)
|
||||
? configFilePath
|
||||
: configFilePath.TrimEnd('/').Replace('\\', '/') + "/";
|
||||
var configFileWebPath = Configuration["FILE_WEB_PATH"];
|
||||
YhCore.Common.Config.Config.FileWebPath = string.IsNullOrEmpty(configFileWebPath)
|
||||
? configFileWebPath
|
||||
: configFileWebPath.TrimEnd('/');
|
||||
// 审批单 PDF:wkhtmltopdf 可执行文件路径(空则用 PATH)
|
||||
YhCore.Common.Config.Config.WkHtmlToPdfPath = Configuration["wkhtmltopdf_path"];
|
||||
|
||||
// 接口文档:对应 Java SpringDoc,按渠道分三组(api / mobiapi / oapi)
|
||||
services.AddSwaggerGen(c =>
|
||||
@@ -185,6 +202,12 @@ namespace YhCore.WebApi
|
||||
In = ParameterLocation.Header,
|
||||
Description = "登录后携带的令牌(对接 CAS SSO 后使用)"
|
||||
});
|
||||
// 加载 XML 注释:控制器/动作的 summary 中文描述展示到 Swagger(csproj GenerateDocumentationFile)
|
||||
var xml = $"{System.AppContext.BaseDirectory}{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
if (System.IO.File.Exists(xml))
|
||||
{
|
||||
c.IncludeXmlComments(xml, includeControllerXmlComments: true);
|
||||
}
|
||||
});
|
||||
|
||||
// 任务调度:Quartz 宿主 + Job 模板(对应 Java scheduler 包)
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<AssemblyName>YhCore.WebApi</AssemblyName>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<Nullable>disable</Nullable>
|
||||
<!-- 生成 XML 注释文件供 Swagger 展示接口描述;1591=无 XML 注释的公共成员告警(大量 DTO 属性无注释,忽略) -->
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -33,11 +36,11 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- 平台基础(Abstractions/AppAuth/Auth/OpenApi + 教务 SDK):从 Nexus nuget-group 引用(原为本地源项目引用) -->
|
||||
<PackageReference Include="Yh.Platform.Abstractions" Version="1.0.0-preview.1" />
|
||||
<PackageReference Include="Yh.Platform.AppAuth" Version="1.0.0-preview.1" />
|
||||
<PackageReference Include="Yh.Platform.Auth" Version="1.0.0-preview.1" />
|
||||
<PackageReference Include="Yh.Platform.OpenApi" Version="1.0.0-preview.1" />
|
||||
<PackageReference Include="Yh.SchClass.OpenApi" Version="1.0.0-preview.1" />
|
||||
<PackageReference Include="Yh.Platform.Abstractions" Version="1.0.0-preview.2" />
|
||||
<PackageReference Include="Yh.Platform.AppAuth" Version="1.0.0-preview.2" />
|
||||
<PackageReference Include="Yh.Platform.Auth" Version="1.0.0-preview.2" />
|
||||
<PackageReference Include="Yh.Platform.OpenApi" Version="1.0.0-preview.2" />
|
||||
<PackageReference Include="Yh.SchClass.OpenApi" Version="1.0.0-preview.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
"AutoCreateMissingTables": true
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"SqlServer": "Server=localhost;Database=EduCloudDocPrint;User Id=sa;Password=Password01!;MultipleActiveResultSets=true",
|
||||
"SqlServer": "Server=localhost;Database=EduCloudDocPrint2;User Id=sa;Password=Password01!;MultipleActiveResultSets=true",
|
||||
"Mysql": "Server=localhost;Port=3306;Database=EduCloudDocPrint;User Id=root;Password=123123;CharSet=utf8mb4"
|
||||
},
|
||||
"Redis": {
|
||||
"ConnectionString": "localhost:6379,defaultDatabase=0"
|
||||
},
|
||||
"FILE_PATH": "/data/upload/",
|
||||
"FILE_WEB_PATH": "",
|
||||
"wkhtmltopdf_path": "",
|
||||
"yh": {
|
||||
"auth": {
|
||||
"Enabled": true,
|
||||
@@ -36,7 +39,7 @@
|
||||
"Enabled": true,
|
||||
"BaseHost": "http://base.yunhaixingtong.com",
|
||||
"BasePath": "/userbase",
|
||||
"SchClassPath": "/bschclass",
|
||||
"SchClassPath": "/userclass",
|
||||
"AppKey": "100002",
|
||||
"AppSecret": "2314a3b158b24a838b2b9b69fd3db118"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user