From e493c85d08df335efdf1dea8d2f69f2d4cb300aa Mon Sep 17 00:00:00 2001
From: glz <694770232@qq.com>
Date: Thu, 11 Jun 2026 17:01:24 +0800
Subject: [PATCH] =?UTF-8?q?refactor,feat:=20=E6=89=B9=E9=87=8F=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81=E9=87=8D=E6=9E=84=E4=B8=8E=E6=96=B0=E5=A2=9E=E4=B8=9A?=
=?UTF-8?q?=E5=8A=A1=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 重命名枚举项与实体字段,修正类型引用
2. 新增IsNull扩展方法与多项字符串处理扩展
3. 新增大量业务DTO、服务接口与枚举定义
4. 重构RabbitMQ服务实现,替换旧版消息队列组件
5. 优化签到服务的宠物喂养事务逻辑
6. 移除冗余的项目引用与旧版消息队列代码
7. 新增Excel导出、导入模板相关工具方法
---
.../Extensions/Extension.Convert.cs | 441 ++++++++++++++
.../Extensions/Extension.Validate.cs | 44 ++
.../Extensions/ObjectExtension.cs | 5 +
.../Extensions/StringExtension.cs | 50 ++
.../QYZH.InteractiveMagazine.Common.csproj | 4 -
.../IJournalCatalogService.cs | 45 ++
.../IJournalPageService.cs | 32 +
.../IJournalPageTaskService.cs | 17 +
.../IJournalService.cs | 48 ++
.../IPetService.cs | 8 +
.../Autofac/AutofacExtension.cs | 25 -
.../MessageQueue/RabbitMQConsumer.cs | 72 ---
.../MessageQueue/RabbitMQPublisher.cs | 47 --
.../OSS/OssService.cs | 4 +-
.../RabbitMQ/IRabbitMQService.cs | 14 +
.../RabbitMQ/RabbitMQConnection.cs | 62 ++
.../RabbitMQ/RabbitMQOptions.cs | 13 +
.../RabbitMQ/RabbitMQSendParam.cs | 25 +
.../RabbitMQ/RabbitMQService.cs | 157 +++++
.../RabbitMQ/RabbiteMQExtensions.cs | 56 ++
.../Common/BusinessException.cs | 19 +
.../DotMatrix/DotMatrixNotebookReportInput.cs | 19 +
.../Dto/DotMatrix/DotMatrixOutput.cs | 73 +++
.../Dto/Journal/BindJournalDto.cs | 81 +++
.../Dto/Journal/JournalCatalogAddInput.cs | 112 ++++
.../Dto/Journal/JournalCatalogTreeListDto.cs | 71 +++
.../Dto/Journal/JournalCatalogUpdateInput.cs | 15 +
.../Dto/Journal/JournalDto.cs | 214 ++++---
.../Dto/Journal/JournalEditDto.cs | 116 ++++
.../Dto/Journal/JournalPageAddV2Input.cs | 28 +
.../Dto/Journal/JournalPageTaskAddInput.cs | 62 ++
.../Journal/JournalPageTaskKeywordAnalysis.cs | 21 +
.../Dto/Journal/JournalPageTaskOutput.cs | 158 +++++
.../Dto/Journal/JournalPageTaskUpdateInput.cs | 126 ++++
.../Dto/Journal/JournalPageV2Output.cs | 100 ++++
.../Dto/Journal/JournalQueryDto.cs | 22 +
.../Dto/Journal/JournalQuestionOutput.cs | 35 ++
.../Dto/Journal/PageLayoutInput.cs | 21 +
.../Dto/MoveInput.cs | 29 +
.../Entity/Journal.cs | 146 +++--
.../Entity/JournalPageTask.cs | 2 +-
.../Enum/JournalPageTaskTypeEnum.cs | 2 +-
.../Enum/JournalStatusEnum.cs | 52 +-
.../Enum/TaskBankTypeEnum.cs | 77 +++
.../Enum/TaskTypeEnum.cs | 24 +
.../QYZH.InteractiveMagazine.Models.csproj | 5 +
.../CheckInService.cs | 84 ++-
.../JournalCatalogService.cs | 526 ++++++++++++++++
.../JournalPageService.cs | 211 +++++++
.../JournalPageTaskService.cs | 176 ++++++
.../JournalService.cs | 313 ++++++++++
.../PetService.cs | 140 +++--
.../UsersService.cs | 2 +-
.../Controllers/BaseController.cs | 57 ++
.../Controllers/JournalController.cs | 559 ++++++++++++++++++
QYZH.InteractiveMagazine.WebApi/Program.cs | 4 +
.../appsettings.json | 5 +-
57 files changed, 4477 insertions(+), 399 deletions(-)
create mode 100644 QYZH.InteractiveMagazine.Common/Extensions/Extension.Convert.cs
create mode 100644 QYZH.InteractiveMagazine.Common/Extensions/Extension.Validate.cs
create mode 100644 QYZH.InteractiveMagazine.IService/IJournalCatalogService.cs
create mode 100644 QYZH.InteractiveMagazine.IService/IJournalPageService.cs
create mode 100644 QYZH.InteractiveMagazine.IService/IJournalPageTaskService.cs
create mode 100644 QYZH.InteractiveMagazine.IService/IJournalService.cs
delete mode 100644 QYZH.InteractiveMagazine.Infrastructure/MessageQueue/RabbitMQConsumer.cs
delete mode 100644 QYZH.InteractiveMagazine.Infrastructure/MessageQueue/RabbitMQPublisher.cs
create mode 100644 QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/IRabbitMQService.cs
create mode 100644 QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQConnection.cs
create mode 100644 QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQOptions.cs
create mode 100644 QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQSendParam.cs
create mode 100644 QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQService.cs
create mode 100644 QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbiteMQExtensions.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/DotMatrix/DotMatrixNotebookReportInput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/DotMatrix/DotMatrixOutput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/BindJournalDto.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogAddInput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogTreeListDto.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogUpdateInput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalEditDto.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageAddV2Input.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskAddInput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskKeywordAnalysis.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskOutput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskUpdateInput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageV2Output.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalQueryDto.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/JournalQuestionOutput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Journal/PageLayoutInput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/MoveInput.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Enum/TaskBankTypeEnum.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Enum/TaskTypeEnum.cs
create mode 100644 QYZH.InteractiveMagazine.Service/JournalCatalogService.cs
create mode 100644 QYZH.InteractiveMagazine.Service/JournalPageService.cs
create mode 100644 QYZH.InteractiveMagazine.Service/JournalPageTaskService.cs
create mode 100644 QYZH.InteractiveMagazine.Service/JournalService.cs
create mode 100644 QYZH.InteractiveMagazine.WebApi/Controllers/JournalController.cs
diff --git a/QYZH.InteractiveMagazine.Common/Extensions/Extension.Convert.cs b/QYZH.InteractiveMagazine.Common/Extensions/Extension.Convert.cs
new file mode 100644
index 0000000..2544edf
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Common/Extensions/Extension.Convert.cs
@@ -0,0 +1,441 @@
+using System.Collections;
+
+namespace QYZH.InteractiveMagazine.Common.Extensions
+{
+ public static partial class Extensions
+ {
+ #region 转换为long
+ ///
+ /// 将object转换为long,若转换失败,则返回0。不抛出异常。
+ ///
+ ///
+ ///
+ public static long ParseToLong(this object obj)
+ {
+ try
+ {
+ return long.Parse(obj.ToString());
+ }
+ catch
+ {
+ return 0L;
+ }
+ }
+
+ ///
+ /// 将object转换为long,若转换失败,则返回指定值。不抛出异常。
+ ///
+ ///
+ ///
+ ///
+ public static long ParseToLong(this string str, long defaultValue)
+ {
+ try
+ {
+ return long.Parse(str);
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+ #endregion
+
+ #region 转换为int
+ ///
+ /// 将object转换为int,若转换失败,则返回0。不抛出异常。
+ ///
+ ///
+ ///
+ public static int ParseToInt(this object str)
+ {
+ try
+ {
+ return Convert.ToInt32(str);
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+
+ ///
+ /// 将object转换为int,若转换失败,则返回指定值。不抛出异常。
+ /// null返回默认值
+ ///
+ ///
+ ///
+ ///
+ public static int ParseToInt(this object str, int defaultValue)
+ {
+ if (str == null)
+ {
+ return defaultValue;
+ }
+ try
+ {
+ return Convert.ToInt32(str);
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+ #endregion
+
+ #region 转换为short
+ ///
+ /// 将object转换为short,若转换失败,则返回0。不抛出异常。
+ ///
+ ///
+ ///
+ public static short ParseToShort(this object obj)
+ {
+ try
+ {
+ return short.Parse(obj.ToString());
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+
+ ///
+ /// 将object转换为short,若转换失败,则返回指定值。不抛出异常。
+ ///
+ ///
+ ///
+ public static short ParseToShort(this object str, short defaultValue)
+ {
+ try
+ {
+ return short.Parse(str.ToString());
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+ #endregion
+
+ #region 转换为demical
+ ///
+ /// 将object转换为demical,若转换失败,则返回指定值。不抛出异常。
+ ///
+ ///
+ ///
+ public static decimal ParseToDecimal(this object str, decimal defaultValue)
+ {
+ try
+ {
+ return decimal.Parse(str.ToString());
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+
+ ///
+ /// 将object转换为demical,若转换失败,则返回0。不抛出异常。
+ ///
+ ///
+ ///
+ public static decimal ParseToDecimal(this object str)
+ {
+ try
+ {
+ return decimal.Parse(str.ToString());
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+ #endregion
+
+ #region 转化为bool
+ ///
+ /// 将object转换为bool,若转换失败,则返回false。不抛出异常。
+ ///
+ ///
+ ///
+ public static bool ParseToBool(this object str)
+ {
+ try
+ {
+ return bool.Parse(str.ToString());
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// 将object转换为bool,若转换失败,则返回指定值。不抛出异常。
+ ///
+ ///
+ ///
+ public static bool ParseToBool(this object str, bool result)
+ {
+ try
+ {
+ return bool.Parse(str.ToString());
+ }
+ catch
+ {
+ return result;
+ }
+ }
+ #endregion
+
+ #region 转换为float
+ ///
+ /// 将object转换为float,若转换失败,则返回0。不抛出异常。
+ ///
+ ///
+ ///
+ public static float ParseToFloat(this object str)
+ {
+ try
+ {
+ return float.Parse(str.ToString());
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+
+ ///
+ /// 将object转换为float,若转换失败,则返回指定值。不抛出异常。
+ ///
+ ///
+ ///
+ public static float ParseToFloat(this object str, float result)
+ {
+ try
+ {
+ return float.Parse(str.ToString());
+ }
+ catch
+ {
+ return result;
+ }
+ }
+ #endregion
+
+ #region 转换为Guid
+ ///
+ /// 将string转换为Guid,若转换失败,则返回Guid.Empty。不抛出异常。
+ ///
+ ///
+ ///
+ public static Guid ParseToGuid(this string str)
+ {
+ try
+ {
+ return new Guid(str);
+ }
+ catch
+ {
+ return Guid.Empty;
+ }
+ }
+ #endregion
+
+ #region 转换为DateTime
+ ///
+ /// 将string转换为DateTime,若转换失败,则返回日期最小值。不抛出异常。
+ ///
+ ///
+ ///
+ public static DateTime ParseToDateTime(this string str)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(str))
+ {
+ return DateTime.MinValue;
+ }
+ if (str.Contains("-") || str.Contains("/"))
+ {
+ return DateTime.Parse(str);
+ }
+ else
+ {
+ int length = str.Length;
+ switch (length)
+ {
+ case 4:
+ return DateTime.ParseExact(str, "yyyy", System.Globalization.CultureInfo.CurrentCulture);
+ case 6:
+ return DateTime.ParseExact(str, "yyyyMM", System.Globalization.CultureInfo.CurrentCulture);
+ case 8:
+ return DateTime.ParseExact(str, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture);
+ case 10:
+ return DateTime.ParseExact(str, "yyyyMMddHH", System.Globalization.CultureInfo.CurrentCulture);
+ case 12:
+ return DateTime.ParseExact(str, "yyyyMMddHHmm", System.Globalization.CultureInfo.CurrentCulture);
+ case 14:
+ return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
+ default:
+ return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
+ }
+ }
+ }
+ catch
+ {
+ return DateTime.MinValue;
+ }
+ }
+
+ ///
+ /// 将string转换为DateTime,若转换失败,则返回默认值。
+ ///
+ ///
+ ///
+ ///
+ public static DateTime ParseToDateTime(this string str, DateTime? defaultValue)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(str))
+ {
+ return defaultValue.GetValueOrDefault();
+ }
+ if (str.Contains("-") || str.Contains("/"))
+ {
+ return DateTime.Parse(str);
+ }
+ else
+ {
+ int length = str.Length;
+ switch (length)
+ {
+ case 4:
+ return DateTime.ParseExact(str, "yyyy", System.Globalization.CultureInfo.CurrentCulture);
+ case 6:
+ return DateTime.ParseExact(str, "yyyyMM", System.Globalization.CultureInfo.CurrentCulture);
+ case 8:
+ return DateTime.ParseExact(str, "yyyyMMdd", System.Globalization.CultureInfo.CurrentCulture);
+ case 10:
+ return DateTime.ParseExact(str, "yyyyMMddHH", System.Globalization.CultureInfo.CurrentCulture);
+ case 12:
+ return DateTime.ParseExact(str, "yyyyMMddHHmm", System.Globalization.CultureInfo.CurrentCulture);
+ case 14:
+ return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
+ default:
+ return DateTime.ParseExact(str, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
+ }
+ }
+ }
+ catch
+ {
+ return defaultValue.GetValueOrDefault();
+ }
+ }
+ #endregion
+
+ #region 转换为string
+ ///
+ /// 将object转换为string,若转换失败,则返回""。不抛出异常。
+ ///
+ ///
+ ///
+ public static string ParseToString(this object obj)
+ {
+ try
+ {
+ if (obj == null)
+ {
+ return string.Empty;
+ }
+ else
+ {
+ return obj.ToString();
+ }
+ }
+ catch
+ {
+ return string.Empty;
+ }
+ }
+ public static string ParseToStrings(this object obj)
+ {
+ try
+ {
+ var list = obj as IEnumerable;
+ if (list != null)
+ {
+ return string.Join(",", list);
+ }
+ else
+ {
+ return obj.ToString();
+ }
+ }
+ catch
+ {
+ return string.Empty;
+ }
+
+ }
+ #endregion
+
+ #region 转换为double
+ ///
+ /// 将object转换为double,若转换失败,则返回0。不抛出异常。
+ ///
+ ///
+ ///
+ public static double ParseToDouble(this object obj)
+ {
+ try
+ {
+ return double.Parse(obj.ToString());
+ }
+ catch
+ {
+ return 0;
+ }
+ }
+
+ ///
+ /// 将object转换为double,若转换失败,则返回指定值。不抛出异常。
+ ///
+ ///
+ ///
+ ///
+ public static double ParseToDouble(this object str, double defaultValue)
+ {
+ try
+ {
+ return double.Parse(str.ToString());
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+ #endregion
+
+ #region 强制转换类型
+ ///
+ /// 强制转换类型
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable CastSuper(this IEnumerable source)
+ {
+ foreach (object item in source)
+ {
+ yield return (TResult)Convert.ChangeType(item, typeof(TResult));
+ }
+ }
+ #endregion
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Common/Extensions/Extension.Validate.cs b/QYZH.InteractiveMagazine.Common/Extensions/Extension.Validate.cs
new file mode 100644
index 0000000..67a79e6
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Common/Extensions/Extension.Validate.cs
@@ -0,0 +1,44 @@
+//using Microsoft.AspNetCore.Http;
+
+namespace QYZH.InteractiveMagazine.Common.Extensions
+{
+ public static partial class Extensions
+ {
+ public static bool IsEmpty(this object value)
+ {
+ if (value != null && !string.IsNullOrEmpty(value.ParseToString()))
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ public static bool IsNotEmpty(this object value)
+ {
+ return !IsEmpty(value);
+ }
+ public static bool IsNullOrZero(this object value)
+ {
+ if (value == null || value.ParseToString().Trim() == "0")
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ //public static bool IsAjaxRequest(this HttpRequest request)
+ //{
+ // if (request == null)
+ // throw new ArgumentNullException("request");
+
+ // if (request.Headers != null)
+ // return request.Headers["X-Requested-With"] == "XMLHttpRequest";
+ // return false;
+ //}
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Common/Extensions/ObjectExtension.cs b/QYZH.InteractiveMagazine.Common/Extensions/ObjectExtension.cs
index 303b882..b576ec8 100644
--- a/QYZH.InteractiveMagazine.Common/Extensions/ObjectExtension.cs
+++ b/QYZH.InteractiveMagazine.Common/Extensions/ObjectExtension.cs
@@ -95,5 +95,10 @@ namespace QYZH.InteractiveMagazine.Common.Extensions
{
return s == null || s?.Count() < 1;
}
+
+ public static bool IsNull(this object? s)
+ {
+ return s == null;
+ }
}
}
diff --git a/QYZH.InteractiveMagazine.Common/Extensions/StringExtension.cs b/QYZH.InteractiveMagazine.Common/Extensions/StringExtension.cs
index 985dbc0..0fd9aa6 100644
--- a/QYZH.InteractiveMagazine.Common/Extensions/StringExtension.cs
+++ b/QYZH.InteractiveMagazine.Common/Extensions/StringExtension.cs
@@ -163,5 +163,55 @@ namespace QYZH.InteractiveMagazine.Common.Extensions
// 匹配域名后的路径部分(包括第一个/)
return Regex.Replace(str, @"https?://[^/]+/([^""\s<>]*)", "$1");
}
+
+ public static string AddDomain(this string str, string domainUrl)
+ {
+ if (string.IsNullOrEmpty(str) || string.IsNullOrEmpty(domainUrl)) return str;
+
+ string pattern = @"src=""([^""]+)""";
+ return Regex.Replace(str, pattern, match =>
+ {
+ string originalUrl = match.Groups[1].Value;
+ // 忽略base64图片
+ if (originalUrl.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) return match.Value; // 返回原样
+ // 如果已经是完整URL则不做处理
+ if (Uri.IsWellFormedUriString(originalUrl, UriKind.Absolute)) return match.Value; // 返回原样
+
+
+ // 处理URL开头可能存在的斜杠
+ var trimmedUrl = originalUrl.StartsWith("/") ? originalUrl.Substring(1) : originalUrl;
+ var trimmedDomain = domainUrl.EndsWith("/") ? domainUrl : domainUrl + "/";
+
+ string newUrl = $"{trimmedDomain}{trimmedUrl}";
+ return $"src=\"{newUrl}\""; // 返回替换后的属性
+ });
+ }
+
+ public static string ToExtension(this string str)
+ {
+ return Regex.Match(str, @"(?<=\.)[a-zA-Z0-9]+$").Value;
+ }
+
+ public static bool NotNull(this string s)
+ {
+ return !string.IsNullOrWhiteSpace(s);
+ }
+
+ public static List AllUrl(this string str, string url = null)
+ {
+ if (string.IsNullOrEmpty(str)) return new List();
+
+ string pattern = @"src=""([^""]+)""";
+
+ var matches = Regex.Matches(str, pattern, RegexOptions.IgnoreCase);
+
+ return matches
+ .Cast()
+ .Select(m => m.Groups[1].Value)
+ .Where(u => !string.IsNullOrEmpty(u) && !u.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) // 排除base64
+ .Where(url => !string.IsNullOrEmpty(url) && url.StartsWith(url, StringComparison.OrdinalIgnoreCase))
+ //.Where(url => url.StartsWith("https://example.com")) // 二次验证
+ .ToList();
+ }
}
}
diff --git a/QYZH.InteractiveMagazine.Common/QYZH.InteractiveMagazine.Common.csproj b/QYZH.InteractiveMagazine.Common/QYZH.InteractiveMagazine.Common.csproj
index b44c444..46a33fc 100644
--- a/QYZH.InteractiveMagazine.Common/QYZH.InteractiveMagazine.Common.csproj
+++ b/QYZH.InteractiveMagazine.Common/QYZH.InteractiveMagazine.Common.csproj
@@ -1,9 +1,5 @@
-
-
-
-
diff --git a/QYZH.InteractiveMagazine.IService/IJournalCatalogService.cs b/QYZH.InteractiveMagazine.IService/IJournalCatalogService.cs
new file mode 100644
index 0000000..092453f
--- /dev/null
+++ b/QYZH.InteractiveMagazine.IService/IJournalCatalogService.cs
@@ -0,0 +1,45 @@
+using QYZH.InteractiveMagazine.Models;
+using QYZH.InteractiveMagazine.Models.Base;
+using QYZH.InteractiveMagazine.Models.Dto.Journal;
+using QYZH.InteractiveMagazine.Models.Entity;
+
+namespace QYZH.InteractiveMagazine.IService;
+
+public interface IJournalCatalogService : IBaseService
+{
+ Task> DetailAsync(long JournalId);
+
+ Task InsertAsync(JournalCatalogInput input);
+
+ Task ImportAsync(JournalImportDto input);
+
+ Task UpdateAsync(JournalCatalogUpdateInput input);
+
+ Task DeleteAsync(long id);
+
+ Task MoveAsync(MoveInput input);
+
+ //Task CopyAsync(CopyInput input);
+
+ ///
+ /// 获取书分类及页码
+ ///
+ ///
+ ///
+ Task> GetJournalCatalogListAsync(long JournalId);
+
+ ///
+ /// 获取书分类及页码
+ ///
+ ///
+ ///
+ Task> GetJournalCataloTreeAsync(long JournalId);
+
+ ///
+ /// 导入书籍目录
+ ///
+ ///
+ ///
+ ///
+ Task ImportCatalogAsync(long JournalId, List dtos);
+}
diff --git a/QYZH.InteractiveMagazine.IService/IJournalPageService.cs b/QYZH.InteractiveMagazine.IService/IJournalPageService.cs
new file mode 100644
index 0000000..7b1e1bc
--- /dev/null
+++ b/QYZH.InteractiveMagazine.IService/IJournalPageService.cs
@@ -0,0 +1,32 @@
+
+
+using QYZH.InteractiveMagazine.Models.Dto.Journal;
+using QYZH.InteractiveMagazine.Models.Entity;
+
+namespace QYZH.InteractiveMagazine.IService;
+
+public interface IJournalPageService: IBaseService
+{
+ Task InsertAsync(JournalAddV2Input input);
+
+ Task UpdateAsync(PageLayoutInput input);
+
+ ///
+ /// 修改书页的点阵码
+ ///
+ ///
+ ///
+ Task UpdatePageNoAsync(long JournalId);
+
+ ///
+ /// 打印书页(全部)
+ ///
+ ///
+ ///
+ Task PrintJournalPageAsync(long JournalId);
+
+ Task DetailAsync(long id);
+ Task DeleteAsync(long id);
+
+ //Task> PageNoArticleAsync(long id);
+}
diff --git a/QYZH.InteractiveMagazine.IService/IJournalPageTaskService.cs b/QYZH.InteractiveMagazine.IService/IJournalPageTaskService.cs
new file mode 100644
index 0000000..24faa58
--- /dev/null
+++ b/QYZH.InteractiveMagazine.IService/IJournalPageTaskService.cs
@@ -0,0 +1,17 @@
+
+
+using QYZH.InteractiveMagazine.Models.Dto.Journal;
+using QYZH.InteractiveMagazine.Models.Entity;
+
+namespace QYZH.InteractiveMagazine.IService;
+
+public interface IJournalPageTaskService : IBaseService
+{
+ Task InsertAsync(JournalPageTaskAddInput input);
+ Task UpdateAsync(JournalPageTaskUpdateInput input);
+ Task KeywordAnalysisAsync(JournalPageTaskKeywordAnalysis input);
+ Task DetailAsync(long id);
+
+ Task DeleteAsync(long id);
+ Task ComplementAsync(JournalPageTaskComplementInput input);
+}
diff --git a/QYZH.InteractiveMagazine.IService/IJournalService.cs b/QYZH.InteractiveMagazine.IService/IJournalService.cs
new file mode 100644
index 0000000..eed5155
--- /dev/null
+++ b/QYZH.InteractiveMagazine.IService/IJournalService.cs
@@ -0,0 +1,48 @@
+
+using QYZH.InteractiveMagazine.Models.Dto.Journal;
+using QYZH.InteractiveMagazine.Models.Dto.DotMatrix;
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.Models.Dto;
+using QYZH.InteractiveMagazine.Models.Entity;
+
+namespace QYZH.InteractiveMagazine.IService;
+
+public interface IJournalService : IBaseService
+{
+ ///
+ /// 查询List
+ ///
+ ///
+ ///
+ Task> GetListAsync(JournalQueryDto dto);
+
+ ///
+ /// 分页查询
+ ///
+ ///
+ ///
+ Task> GetPageListAsync(PageQueryModel search);
+
+
+ ///
+ /// 编辑
+ ///
+ ///
+ ///
+ Task> EditAsync(JournalEditDto input);
+
+ Task> Tasks(long id);
+
+ Task DetailAsync(long id);
+
+ Task DeleteAsync(List id);
+
+
+ Task StartPageAsync(long Id, int Index);
+
+ Task StatusAsync(long id, JournalStatusEnum status);
+
+ Task PrintCodeAsync(long id);
+
+ Task ResultReportAsync(DotMatrixNoteJournalReportInput input);
+}
diff --git a/QYZH.InteractiveMagazine.IService/IPetService.cs b/QYZH.InteractiveMagazine.IService/IPetService.cs
index a611d8c..c3a491a 100644
--- a/QYZH.InteractiveMagazine.IService/IPetService.cs
+++ b/QYZH.InteractiveMagazine.IService/IPetService.cs
@@ -37,6 +37,14 @@ public interface IPetService : IBaseService
/// 喂养结果
Task FeedPetAsync(long userId, FeedPetInput input);
+ ///
+ /// 喂养宠物(无事务,需在外部事务中调用)
+ ///
+ /// 用户Id
+ /// 喂养输入
+ /// 喂养结果
+ Task FeedPetInTranAsync(long userId, FeedPetInput input);
+
///
/// 获取宠物喂养记录列表
///
diff --git a/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs b/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs
index 74c17ce..2d7fa51 100644
--- a/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs
+++ b/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs
@@ -2,7 +2,6 @@
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
-using QYZH.InteractiveMagazine.Infrastructure.MessageQueue;
using QYZH.InteractiveMagazine.Models.Settings;
using RabbitMQ.Client;
using StackExchange.Redis;
@@ -25,31 +24,7 @@ namespace QYZH.InteractiveMagazine.Infrastructure.Autofacs
var source = friendlyName.Split('.');
var assemblyNames = string.Join(".", source.Take(source.Length - 1));
containerBuilder.RegisterModule(new AutofacModuleRegister(assemblyNames));
-
- InitializeRabbitMQ(c.Configuration, containerBuilder);
});
}
-
-
- private static void InitializeRabbitMQ(IConfiguration configuration, ContainerBuilder containerBuilder)
- {
- var rabbitMQSettings = configuration.GetSection("RabbitMQSettings").Get();
- if (rabbitMQSettings != null && !string.IsNullOrWhiteSpace(rabbitMQSettings.HostName))
- {
- var factory = new ConnectionFactory
- {
- HostName = rabbitMQSettings.HostName,
- Port = rabbitMQSettings.Port,
- UserName = rabbitMQSettings.UserName ?? string.Empty,
- Password = rabbitMQSettings.Password ?? string.Empty,
- VirtualHost = rabbitMQSettings.VirtualHost ?? string.Empty
- };
-
- var connection = factory.CreateConnectionAsync().GetAwaiter().GetResult();
-
- containerBuilder.RegisterInstance(connection).As().SingleInstance();
- containerBuilder.RegisterType().InstancePerDependency();
- }
- }
}
}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/MessageQueue/RabbitMQConsumer.cs b/QYZH.InteractiveMagazine.Infrastructure/MessageQueue/RabbitMQConsumer.cs
deleted file mode 100644
index 18021c8..0000000
--- a/QYZH.InteractiveMagazine.Infrastructure/MessageQueue/RabbitMQConsumer.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-using Microsoft.Extensions.Logging;
-using RabbitMQ.Client;
-using RabbitMQ.Client.Events;
-using System.Text;
-
-namespace QYZH.InteractiveMagazine.Infrastructure.MessageQueue;
-
-///
-/// RabbitMQ消息消费者基类
-///
-public abstract class RabbitMQConsumer : IDisposable
-{
- private readonly IConnection _connection;
- private readonly ILogger _logger;
- private IChannel? _channel;
- private AsyncEventingBasicConsumer? _consumer;
-
- ///
- /// 构造函数
- ///
- /// RabbitMQ连接
- /// 日志记录器
- protected RabbitMQConsumer(IConnection connection, ILogger logger)
- {
- _connection = connection;
- _logger = logger;
- }
-
- ///
- /// 启动消费
- ///
- /// 队列名称
- /// 消息处理委托
- public async Task StartConsume(string queueName, Func handleMessage)
- {
- _channel = await _connection.CreateChannelAsync();
-
- _consumer = new AsyncEventingBasicConsumer(_channel);
- _consumer.ReceivedAsync += async (model, ea) =>
- {
- try
- {
- var body = ea.Body.ToArray();
- var message = Encoding.UTF8.GetString(body);
-
- await handleMessage(message);
-
- await _channel.BasicAckAsync(ea.DeliveryTag, false);
-
- _logger.LogInformation("消息消费成功 | 队列: {QueueName} | 消息: {Message}", queueName, message);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "消息消费失败 | 队列: {QueueName}", queueName);
- await _channel.BasicNackAsync(ea.DeliveryTag, false, true);
- }
- };
-
- await _channel.BasicConsumeAsync(queue: queueName, autoAck: false, consumer: _consumer);
-
- _logger.LogInformation("开始消费消息 | 队列: {QueueName}", queueName);
- }
-
- ///
- /// 释放资源
- ///
- public void Dispose()
- {
- _channel?.DisposeAsync().GetAwaiter().GetResult();
- GC.SuppressFinalize(this);
- }
-}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/MessageQueue/RabbitMQPublisher.cs b/QYZH.InteractiveMagazine.Infrastructure/MessageQueue/RabbitMQPublisher.cs
deleted file mode 100644
index a84f184..0000000
--- a/QYZH.InteractiveMagazine.Infrastructure/MessageQueue/RabbitMQPublisher.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using Microsoft.Extensions.Logging;
-using RabbitMQ.Client;
-using System.Text;
-
-namespace QYZH.InteractiveMagazine.Infrastructure.MessageQueue;
-
-///
-/// RabbitMQ消息发布器
-///
-public class RabbitMQPublisher
-{
- private readonly IConnection _connection;
- private readonly ILogger _logger;
-
- ///
- /// 构造函数
- ///
- /// RabbitMQ连接
- /// 日志记录器
- public RabbitMQPublisher(IConnection connection, ILogger logger)
- {
- _connection = connection;
- _logger = logger;
- }
-
- ///
- /// 发布消息
- ///
- /// 交换机名称
- /// 路由键
- /// 消息内容
- public async Task PublishMessage(string exchange, string routingKey, string message)
- {
- await using var channel = await _connection.CreateChannelAsync();
-
- var body = Encoding.UTF8.GetBytes(message);
-
- await channel.BasicPublishAsync(
- exchange: exchange,
- routingKey: routingKey,
- body: body
- );
-
- _logger.LogInformation("消息已发布 | 交换机: {Exchange} | 路由键: {RoutingKey} | 消息: {Message}",
- exchange, routingKey, message);
- }
-}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/OSS/OssService.cs b/QYZH.InteractiveMagazine.Infrastructure/OSS/OssService.cs
index e62f38f..8cdfe25 100644
--- a/QYZH.InteractiveMagazine.Infrastructure/OSS/OssService.cs
+++ b/QYZH.InteractiveMagazine.Infrastructure/OSS/OssService.cs
@@ -217,7 +217,7 @@ namespace QYZH.InteractiveMagazine.Infrastructure.OSS
//var del = _ossResourceRepository.Delete(w => keys.Contains(w.Path));
- //AppException.ThrowIf(!del, "删除资源失败");
+ //BusinessException.ThrowIf(!del, "删除资源失败");
return result.Keys.Count() == keys.Count;
@@ -286,7 +286,7 @@ namespace QYZH.InteractiveMagazine.Infrastructure.OSS
// Path = targetObject,
//});
- //AppException.ThrowIf(data.IsNull(), "资源添加失败");
+ //BusinessException.ThrowIf(data.IsNull(), "资源添加失败");
var req = new CopyObjectRequest(_ossOption.BucketName, sourceObject, _ossOption.BucketName, targetObject)
{
diff --git a/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/IRabbitMQService.cs b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/IRabbitMQService.cs
new file mode 100644
index 0000000..c60c127
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/IRabbitMQService.cs
@@ -0,0 +1,14 @@
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+
+namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
+{
+ public interface IRabbitMQService
+ {
+ Task SendAsync(RabbitMQSendParam param, CancellationToken cancellationToken = default);
+
+ Task SendBatchAsync(IEnumerable @params, CancellationToken cancellationToken = default);
+
+ Task ReceiveAsync(string queueName, Func callback, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQConnection.cs b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQConnection.cs
new file mode 100644
index 0000000..bd4cc46
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQConnection.cs
@@ -0,0 +1,62 @@
+using RabbitMQ.Client;
+
+namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
+{
+ public interface IRabbitMQConnection : IDisposable
+ {
+ Task CreateChannel();
+ }
+
+ public class RabbitMQConnection : IRabbitMQConnection
+ {
+ private readonly ConnectionFactory _factory;
+ private readonly IConnection _connection;
+ private bool _isDisposed;
+
+ public RabbitMQConnection(ConnectionFactory factory)
+ {
+ _factory = factory ?? throw new ArgumentNullException(nameof(factory));
+ _connection = factory.CreateConnectionAsync().Result;
+ }
+
+ public async Task CreateChannel()
+ {
+ EnsureNotDisposed();
+ return await _connection.CreateChannelAsync();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_isDisposed) return;
+
+ if (disposing)
+ {
+ // Free any other managed objects here.
+ }
+
+ // Free any unmanaged objects here.
+ _connection.Dispose();
+
+ _isDisposed = true;
+ }
+
+ ~RabbitMQConnection()
+ {
+ Dispose(false);
+ }
+
+ private void EnsureNotDisposed()
+ {
+ if (_isDisposed)
+ {
+ throw new ObjectDisposedException(nameof(RabbitMQConnection));
+ }
+ }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQOptions.cs b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQOptions.cs
new file mode 100644
index 0000000..1682f83
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQOptions.cs
@@ -0,0 +1,13 @@
+namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
+{
+ public class RabbitMQOptions
+ {
+ public string UserName { get; set; }
+ public string Password { get; set; }
+ public string HostName { get; set; }
+ public int Port { get; set; }
+ public string ExchangeName { get; set; } = string.Empty;
+
+ public string VirtualHost { get; set; } = "/";
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQSendParam.cs b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQSendParam.cs
new file mode 100644
index 0000000..68d4711
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQSendParam.cs
@@ -0,0 +1,25 @@
+namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
+{
+ public class RabbitMQSendParam
+ {
+ ///
+ /// 交换机 默认空
+ ///
+ public string Exchange { get; set; } = "";
+ public string Queue { get; set; }
+ ///
+ /// 路由键
+ ///
+ public string RoutingKey { get; set; }
+
+ ///
+ /// 消息数据
+ ///
+ public object Data { get; set; }
+
+ ///
+ /// 是否清空队列
+ ///
+ public bool Purge { get; set; } = false;
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQService.cs b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQService.cs
new file mode 100644
index 0000000..51023e8
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbitMQService.cs
@@ -0,0 +1,157 @@
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+
+namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
+{
+ public class RabbitMQService : IRabbitMQService
+ {
+ private readonly IRabbitMQConnection _connection;
+ private readonly JsonSerializerOptions options = new JsonSerializerOptions
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+ };
+ public RabbitMQService(IRabbitMQConnection connection)
+ {
+ _connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ }
+
+
+ public async Task SendAsync(RabbitMQSendParam param, CancellationToken cancellationToken = default)
+ {
+
+ try
+ {
+ using var channel = await _connection.CreateChannel();
+
+ // 启用发布者确认
+ //var channelOpts = new CreateChannelOptions
+ //(
+ // publisherConfirmationsEnabled: true,
+ // publisherConfirmationTrackingEnabled: true,
+ // outstandingPublisherConfirmationsRateLimiter: new ThrottlingRateLimiter(MAX_OUTSTANDING_CONFIRMS)
+ //);
+
+ // 声明队列(持久化)
+ await channel.QueueDeclareAsync(queue: param.RoutingKey, durable: true, exclusive: false, autoDelete: false, arguments: null);
+ // 清空队列
+ if (param.Purge) await channel.QueuePurgeAsync(param.RoutingKey);
+ // 消息序列化
+ var mesjson = JsonSerializer.Serialize(param.Data, options);
+
+ var body = Encoding.UTF8.GetBytes(mesjson);
+ var properties = new BasicProperties
+ {
+ Persistent = true // 设置消息持久化
+ };
+ await channel.BasicPublishAsync(param.Exchange, param.RoutingKey, false, properties, body, cancellationToken);
+
+ return true;
+
+ }
+ catch (OperationCanceledException ex)
+ {
+ Console.WriteLine($"Operation was canceled: {ex.Message}");
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"An error occurred: {ex.Message}");
+ return false;
+ }
+ }
+
+
+ public async Task SendBatchAsync(IEnumerable @params, CancellationToken cancellationToken = default)
+ {
+ IChannel channel = null;
+ try
+ {
+ channel = await _connection.CreateChannel();
+
+ // 开启事务
+ await channel.TxSelectAsync();
+
+ var properties = new BasicProperties
+ {
+ Persistent = true // 设置消息持久化
+ };
+
+ // 批量发送消息到不同的 routingKey
+ var declaredExchanges = new HashSet();
+ foreach (var param in @params)
+ {
+ // 声明 Exchange(持久化)
+ if (declaredExchanges.Add(param.Exchange))
+ {
+ await channel.ExchangeDeclareAsync(exchange: param.Exchange, type: "direct", durable: true, autoDelete: false, arguments: null);
+ }
+
+ // 声明队列(持久化)
+ await channel.QueueDeclareAsync(queue: param.Queue, durable: true, exclusive: false, autoDelete: false, arguments: null);
+
+ // 绑定队列到 Exchange
+ await channel.QueueBindAsync(queue: param.Queue, exchange: param.Exchange, routingKey: param.RoutingKey, arguments: null);
+
+ // 清空队列
+ if (param.Purge) await channel.QueuePurgeAsync(param.Queue);
+
+ // 消息序列化
+ var mesjson = JsonSerializer.Serialize(param.Data, options);
+ var body = Encoding.UTF8.GetBytes(mesjson);
+
+ // 发布消息
+ await channel.BasicPublishAsync(param.Exchange, param.RoutingKey, false, properties, body, cancellationToken);
+ }
+
+ // 提交事务 - 确保所有消息都发送成功
+ await channel.TxCommitAsync();
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"An error occurred: {ex.Message}");
+ // 回滚事务
+ try { await channel?.TxRollbackAsync(); } catch { /* 忽略回滚异常 */ }
+ return false;
+ }
+ finally
+ {
+ if (channel != null && !channel.IsClosed)
+ {
+ await channel.CloseAsync();
+ }
+ }
+ }
+
+
+ public async Task ReceiveAsync(string queueName, Func callback, CancellationToken cancellationToken = default)
+ {
+ var channel = await _connection.CreateChannel();
+ //await channel.BasicQosAsync(0, 10, false); // 一次最多接收10条未确认的消息
+ await channel.QueueDeclareAsync(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);
+
+ var consumer = new AsyncEventingBasicConsumer(channel);
+ consumer.ReceivedAsync += async (model, ea) =>
+ {
+ //var body = ea.Body.ToArray();
+ try
+ {
+ // 直接传递 model 和 body 给 callback,不需要转换
+ await callback(channel, ea);
+ }
+
+ finally
+ {
+ //await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);
+ }
+ };
+ await channel.BasicConsumeAsync(queue: queueName, autoAck: false, consumer: consumer, cancellationToken: cancellationToken);
+ // Prevent the method from returning immediately
+ await Task.Delay(-1, cancellationToken);
+ }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbiteMQExtensions.cs b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbiteMQExtensions.cs
new file mode 100644
index 0000000..35e6bda
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/RabbitMQ/RabbiteMQExtensions.cs
@@ -0,0 +1,56 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using RabbitMQ.Client;
+
+namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
+{
+ public static class RabbiteMQExtensions
+ {
+ ///
+ /// 初始化消息队列,并添加Publisher到IoC容器
+ ///
+ /// 从Configuration读取"RabbbitMQOptions配置项"
+ public static IServiceCollection AddRabbitMQ(this IServiceCollection services, IConfiguration configuration)
+ {
+ var rabbitMqSection = configuration.GetSection("RabbitMq");
+
+ if (rabbitMqSection.Exists())
+ {
+ // 绑定RabbitMQ配置
+ services.Configure(rabbitMqSection);
+ // 注册RabbitMQ连接工厂
+ services.AddSingleton(sp =>
+ {
+ var options = sp.GetRequiredService>().Value;
+ var factory = new ConnectionFactory()
+ {
+ HostName = options.HostName,
+ Port = options.Port,
+ UserName = options.UserName,
+ Password = options.Password,
+ VirtualHost = options.VirtualHost,
+
+ // 自动恢复配置
+ AutomaticRecoveryEnabled = true, // 启用自动恢复
+ NetworkRecoveryInterval = TimeSpan.FromSeconds(10), // 每10秒尝试重连
+ // 心跳检测
+ RequestedHeartbeat = TimeSpan.FromSeconds(10), // 60秒心跳
+ // 其他重要配置
+ TopologyRecoveryEnabled = true, // 恢复交换机、队列等拓扑结构
+ RequestedConnectionTimeout = TimeSpan.FromSeconds(30), // 连接超时
+ SocketReadTimeout = TimeSpan.FromSeconds(30), // 读取超时
+ SocketWriteTimeout = TimeSpan.FromSeconds(30) // 写入超时
+ };
+ return new RabbitMQConnection(factory);
+ });
+
+ // 添加RabbitMQService的服务注册
+ services.AddSingleton();
+ //services.AddHostedService();
+ }
+
+ return services;
+ }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Common/BusinessException.cs b/QYZH.InteractiveMagazine.Models/Common/BusinessException.cs
index 39b7b20..93e17e5 100644
--- a/QYZH.InteractiveMagazine.Models/Common/BusinessException.cs
+++ b/QYZH.InteractiveMagazine.Models/Common/BusinessException.cs
@@ -43,4 +43,23 @@ public class BusinessException : Exception
public BusinessException(string message, Exception innerException) : base(message, innerException)
{
}
+
+ public static void ThrowIf(bool isTrue, string message)
+ {
+ if (isTrue) throw new BusinessException(message);
+ }
+
+ public static void ThrowIf(bool isTrue, Func action)
+ {
+ if (isTrue) throw new BusinessException(action.Invoke());
+ }
+
+ public static void ThrowIf(bool isTrue, string message, Action action)
+ {
+ if (isTrue)
+ {
+ action();
+ throw new BusinessException(message);
+ }
+ }
}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/DotMatrix/DotMatrixNotebookReportInput.cs b/QYZH.InteractiveMagazine.Models/Dto/DotMatrix/DotMatrixNotebookReportInput.cs
new file mode 100644
index 0000000..c858c40
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/DotMatrix/DotMatrixNotebookReportInput.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.DotMatrix
+{
+ public class DotMatrixNoteJournalReportInput
+ {
+ public long Id { get; set; }
+
+ public bool Success { get; set; }
+
+ public string FileKey { get; set; }
+
+ public string FileUrl { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/DotMatrix/DotMatrixOutput.cs b/QYZH.InteractiveMagazine.Models/Dto/DotMatrix/DotMatrixOutput.cs
new file mode 100644
index 0000000..01679cb
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/DotMatrix/DotMatrixOutput.cs
@@ -0,0 +1,73 @@
+
+using QYZH.InteractiveMagazine.Common.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.DotMatrix
+{
+ public class DotMatrixOutput
+ {
+ ///
+ /// 方法
+ ///
+ public string Method { get; set; }
+
+ ///
+ /// 名称
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// id
+ ///
+ public long NoteId { get; set; }
+
+ private string _pdfUrl;
+ ///
+ /// pdf地址
+ ///
+ public string PdfUrl
+ {
+ get => DomainHelper.OssFullUrl(_pdfUrl);
+ set => _pdfUrl = value;
+ }
+
+ public List DotMatrixs { get; set; } = new List();
+ }
+
+ public class DotMatrixMqOutput
+ {
+ public long DotId { get; set; }
+
+ private string _fileAddress;
+ ///
+ /// 点阵xml地址
+ ///
+ public string FileAddress
+ {
+ get => DomainHelper.OssFullUrl(_fileAddress);
+ set => _fileAddress = value;
+ }
+
+ ///
+ /// 页
+ ///
+ public List Pages { get; set; } = new List();
+ }
+
+ public class DotMatrixPageDto
+ {
+ ///
+ /// 页码
+ ///
+ public string PageNo { get; set; }
+
+ ///
+ /// 连续数量
+ ///
+ public int PageNum { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/BindJournalDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/BindJournalDto.cs
new file mode 100644
index 0000000..12ef5ad
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/BindJournalDto.cs
@@ -0,0 +1,81 @@
+using QYZH.InteractiveMagazine.Models.Enum;
+
+namespace QYZH.InteractiveMagazine.Models.Dto;
+
+///
+/// 绑定期刊输入DTO
+///
+public class BindJournalInput
+{
+ ///
+ /// 期刊模板Id(扫码解析的期刊定义Id)
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// 实例化期刊Id(扫码解析的具体期刊实例Id,可选)
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 关联类型: Read(已读), Favorite(收藏), Subscribe(订阅),默认 Subscribe
+ ///
+ public string Type { get; set; } = UserJournalTypeEnum.Subscribe.ToString();
+}
+
+///
+/// 绑定期刊输出DTO
+///
+public class BindJournalOutput
+{
+ ///
+ /// 绑定记录Id
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 用户Id
+ ///
+ public long UserId { get; set; }
+
+ ///
+ /// 期刊模板Id
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// 关联类型
+ ///
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// 状态
+ ///
+ public string Status { get; set; } = string.Empty;
+
+ ///
+ /// 绑定时间
+ ///
+ public DateTime CreatedAt { get; set; }
+}
+
+///
+/// 用户期刊关联查询输入DTO
+///
+public class UserJournalQueryInput : PageQueryModel
+{
+ ///
+ /// 期刊模板Id
+ ///
+ public long? JournalId { get; set; }
+
+ ///
+ /// 实例化期刊Id
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 关联类型: Read, Favorite, Subscribe
+ ///
+ public string? Type { get; set; }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogAddInput.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogAddInput.cs
new file mode 100644
index 0000000..139e73e
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogAddInput.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+
+ public class JournalImportDto
+ {
+ ///
+ /// 书Id
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// pdf地址
+ ///
+ public string PdfUrl { get; set; }
+
+ ///
+ /// 从多少页开始
+ ///
+ public int Index { get; set; }
+
+ public List JournalCatalogs { get; set; }
+ }
+
+ ///
+ /// 目录导入
+ ///
+ public class JournalCatalogImportDto
+ {
+ ///
+ /// 一级目录
+ ///
+ public string ParentName { get; set; }
+
+ ///
+ /// 二级目录
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// 页码
+ ///
+ public int PageNum { get; set; }
+
+ [JsonIgnore]
+ public long PagePageId { get; set; }
+ }
+
+ public class JournalCatalogAddInput
+ {
+ ///
+ /// 目录名称
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// 级别
+ ///
+ public int Level { get; set; }
+
+ ///
+ /// 类型0-目录 1-页
+ ///
+ public int Type { get; set; }
+
+ ///
+ /// url
+ ///
+ public string Url { get; set; }
+ }
+
+ public class JournalCatalogInput
+ {
+ ///
+ /// 书Id
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// 目录名称
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// 父Id;无限级别
+ ///
+ public long ParentId { get; set; }
+
+ ///
+ /// 等级
+ ///
+ public int Level { get; set; }
+
+ ///
+ /// 指定插入位置的索引(从0开始)不传或传null时默认追加到末尾
+ ///
+ public int? Position { get; set; }
+
+ ///
+ /// 类型0-目录 1-页
+ ///
+ public int Type { get; set; }
+
+ ///
+ /// url
+ ///
+ public string Url { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogTreeListDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogTreeListDto.cs
new file mode 100644
index 0000000..efafae8
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogTreeListDto.cs
@@ -0,0 +1,71 @@
+using MiniExcelLibs.Attributes;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public partial class JournalCatalogTreeListDto
+ {
+ ///
+ /// Desc:主键id
+ ///
+ [ExcelIgnore]
+ public long Id { get; set; }
+
+ ///
+ /// Desc:书id
+ ///
+ [ExcelIgnore]
+ public long JournalId { get; set; }
+
+ ///
+ /// Desc:一级目录名称
+ ///
+ [DisplayName("目录名称")]
+ [Required]
+ public string? ParentName { get; set; }
+
+ ///
+ /// Desc:目录名称
+ ///
+ [DisplayName("二级目录")]
+ [Required]
+ public string Name { get; set; }
+
+ [ExcelIgnore]
+ public long PageId { get; set; }
+
+ [DisplayName("页码")]
+ [Required]
+ public int PageNum { get; set; }
+
+ [ExcelIgnore]
+ public long? ParentId { get; set; }
+ }
+
+
+ public partial class IcrJournalCatalogTreeDto
+ {
+ ///
+ /// Desc:主键id
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 名称
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// 层级
+ ///
+ public int Level { get; set; }
+
+ public List Child { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogUpdateInput.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogUpdateInput.cs
new file mode 100644
index 0000000..1d21a2d
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalCatalogUpdateInput.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Linq;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class JournalCatalogUpdateInput
+ {
+ public long Id { get; set; }
+
+ ///
+ /// 目录名称
+ ///
+ public string Name { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs
index 12ef5ad..3fd81b2 100644
--- a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs
@@ -1,81 +1,145 @@
+using QYZH.InteractiveMagazine.Common.Extensions;
+using QYZH.InteractiveMagazine.Common.Helpers;
using QYZH.InteractiveMagazine.Models.Enum;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
-namespace QYZH.InteractiveMagazine.Models.Dto;
-
-///
-/// 绑定期刊输入DTO
-///
-public class BindJournalInput
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
{
- ///
- /// 期刊模板Id(扫码解析的期刊定义Id)
- ///
- public long JournalId { get; set; }
+ public class JournalDto
+ {
+ ///
+ /// Desc:主键id
+ ///
+ public long Id { get; set; }
- ///
- /// 实例化期刊Id(扫码解析的具体期刊实例Id,可选)
- ///
- public long Id { get; set; }
+ ///
+ /// Desc:书籍名称
+ ///
+ public string Name { get; set; }
- ///
- /// 关联类型: Read(已读), Favorite(收藏), Subscribe(订阅),默认 Subscribe
- ///
- public string Type { get; set; } = UserJournalTypeEnum.Subscribe.ToString();
-}
-
-///
-/// 绑定期刊输出DTO
-///
-public class BindJournalOutput
-{
- ///
- /// 绑定记录Id
- ///
- public long Id { get; set; }
-
- ///
- /// 用户Id
- ///
- public long UserId { get; set; }
-
- ///
- /// 期刊模板Id
- ///
- public long JournalId { get; set; }
-
- ///
- /// 关联类型
- ///
- public string Type { get; set; } = string.Empty;
-
- ///
- /// 状态
- ///
- public string Status { get; set; } = string.Empty;
-
- ///
- /// 绑定时间
- ///
- public DateTime CreatedAt { get; set; }
-}
-
-///
-/// 用户期刊关联查询输入DTO
-///
-public class UserJournalQueryInput : PageQueryModel
-{
- ///
- /// 期刊模板Id
- ///
- public long? JournalId { get; set; }
-
- ///
- /// 实例化期刊Id
- ///
- public long Id { get; set; }
-
- ///
- /// 关联类型: Read, Favorite, Subscribe
- ///
- public string? Type { get; set; }
+ ///
+ /// Desc:总页数
+ ///
+ public int TotalPage { get; set; }
+
+ ///
+ /// Desc:校验页数
+ ///
+ public int VerifyPage { get; set; }
+
+
+
+ ///
+ /// Desc:状态
+ ///
+ public JournalStatusEnum Status { get; set; }
+
+ ///
+ /// 状态
+ ///
+ public string ShowStatus => Status.GetDescription();
+
+ private string _pdfUrl;
+ ///
+ /// pdf预览地址
+ ///
+ public string PdfUrl
+ {
+ get => DomainHelper.OssFullUrl(_pdfUrl);
+ set => _pdfUrl = value;
+ }
+
+ ///
+ /// Desc:宽度
+ ///
+ public float Width { get; set; }
+
+ ///
+ /// Desc:高度
+ ///
+ public float Height { get; set; }
+
+ ///
+ /// Desc:乐观锁
+ ///
+ public int Revision { get; set; }
+
+ ///
+ /// Desc:审查结果(默认0 1通过 -1驳回)
+ ///
+ public int Result { get; set; }
+
+ private string _cover;
+ ///
+ /// 封面
+ ///
+ public string Cover
+ {
+ get => DomainHelper.OssFullUrl(_cover);
+ set => _cover = value;
+ }
+
+ private string _backCover;
+ ///
+ /// 封底
+ ///
+ public string BackCover
+ {
+ get => DomainHelper.OssFullUrl(_backCover);
+ set => _backCover = value;
+ }
+
+
+ private string _pdfPreviewUrl;
+
+ ///
+ /// pdf预览地址
+ ///
+ public string PdfPreviewUrl
+ {
+ get => DomainHelper.OssFullUrl(_pdfPreviewUrl);
+ set => _pdfPreviewUrl = value;
+ }
+
+ ///
+ /// Desc:结论json
+ ///
+ public object? Conclusion { get; set; }
+
+ ///
+ /// Desc:思路导读
+ ///
+ public string? Guide { get; set; }
+
+ ///
+ /// Desc:家长指导
+ ///
+ public string? Tutelage { get; set; }
+
+ ///
+ /// Desc:所属机构列表
+ ///
+ public Dictionary? Organization { get; set; }
+
+ //public List> Organizations => Organization.Select(c => new Dictionary() { { c.Key, c.Value } }).ToList();
+
+ ///
+ /// Desc:副标题
+ ///
+ public string? Title { get; set; }
+
+ ///
+ /// 年级
+ ///
+ public int Grade { get; set; }
+
+ ///
+ /// Desc:下载书籍页码点阵码PDF文件名称
+ ///
+ public string? DownloadJournalPagePdfName { get; set; }
+ }
}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalEditDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalEditDto.cs
new file mode 100644
index 0000000..3e9fb77
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalEditDto.cs
@@ -0,0 +1,116 @@
+using QYZH.InteractiveMagazine.Models.Enum;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public partial class JournalEditDto
+ {
+ ///
+ /// Desc:主键id
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// Desc:书籍名称
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Desc:总页数
+ ///
+ public int TotalPage { get; set; }
+
+ ///
+ /// Desc:校验页数
+ ///
+ public int VerifyPage { get; set; }
+
+
+ ///
+ /// Desc:状态
+ ///
+ public JournalStatusEnum Status { get; set; } = JournalStatusEnum.Created;
+
+ ///
+ /// Desc:pdf地址
+ ///
+ public string PdfUrl { get; set; }
+
+ ///
+ /// Desc:宽度
+ ///
+ public float Width { get; set; }
+
+ ///
+ /// Desc:高度
+ ///
+ public float Height { get; set; }
+
+ ///
+ /// Desc:乐观锁
+ ///
+ [JsonIgnore]
+ public int Revision { get; set; }
+
+ ///
+ /// Desc:审查结果(默认0 1通过 -1驳回)
+ ///
+ public int Result { get; set; }
+
+ ///
+ /// Desc:封面
+ ///
+ public string Cover { get; set; }
+
+ ///
+ /// Desc:封底
+ ///
+ public string BackCover { get; set; }
+
+ ///
+ /// Desc:pdf预览地址
+ ///
+ public string PdfPreviewUrl { get; set; }
+
+ ///
+ /// Desc:结论json
+ ///
+ public object Conclusion { get; set; }
+
+ ///
+ /// Desc:思路导读
+ ///
+ public string Guide { get; set; }
+
+ ///
+ /// Desc:家长指导
+ ///
+ [JsonIgnore]
+ public string Tutelage { get; set; }
+
+ ///
+ /// Desc:所属机构
+ ///
+ public long[] OrganizationIds { get; set; }
+
+ /////
+ ///// Desc:所属机构
+ /////
+ //public string OrganizationName { get; set; }
+
+ ///
+ /// Desc:副标题
+ ///
+ public string Title { get; set; }
+
+ ///
+ /// 年级
+ ///
+ public int Grade { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageAddV2Input.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageAddV2Input.cs
new file mode 100644
index 0000000..7a3d8da
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageAddV2Input.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Linq;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class JournalAddV2Input
+ {
+ ///
+ /// 书Id
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// 书目录id
+ ///
+ public long JournalCatalogId { get; set; }
+
+ ///
+ /// 页码
+ ///
+ public int PageNum { get; set; }
+
+ ///
+ /// 页图片
+ ///
+ public string Url { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskAddInput.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskAddInput.cs
new file mode 100644
index 0000000..269f64b
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskAddInput.cs
@@ -0,0 +1,62 @@
+using QYZH.InteractiveMagazine.Models.Enum;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class JournalPageTaskAddInput
+ {
+ ///
+ /// 书Id
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// 书页id
+ ///
+ public long JournalPageId { get; set; }
+
+ ///
+ /// 题号
+ ///
+ public string No { get; set; }
+
+ ///
+ /// 题型
+ ///
+ public TaskBankTypeEnum Type { get; set; }
+
+ ///
+ /// 学科Id
+ ///
+ public long SubjectId { get; set; }
+
+ ///
+ /// 分数
+ ///
+ public float Score { get; set; }
+
+ ///
+ /// 回答时间(秒)
+ ///
+ public int AnswerTime { get; set; }
+
+ ///
+ /// 关联的知识点ids
+ ///
+ public string KnowledgePointIds { get; set; }
+
+ ///
+ /// 关键解析
+ ///
+ public string KeywordAnalysis { get; set; }
+
+ ///
+ /// 问题内容
+ ///
+ public string Task { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskKeywordAnalysis.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskKeywordAnalysis.cs
new file mode 100644
index 0000000..704a327
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskKeywordAnalysis.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class JournalPageTaskKeywordAnalysis
+ {
+ ///
+ /// 问题Id
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 单项解析
+ ///
+ public string KeywordAnalysis { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskOutput.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskOutput.cs
new file mode 100644
index 0000000..bff386a
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskOutput.cs
@@ -0,0 +1,158 @@
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.Common.Extensions;
+using QYZH.InteractiveMagazine.Common.Helpers;
+using QYZH.InteractiveMagazine.Models.Enum;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class JournalPageTaskOutput
+ {
+ public long Id { get; set; }
+
+ ///
+ /// 书Id
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// 书页Id
+ ///
+ public long JournalPageId { get; set; }
+
+ ///
+ /// 学科Id
+ ///
+ public long SubjectId { get; set; }
+
+ ///
+ /// 题号
+ ///
+ public string No { get; set; }
+
+ ///
+ /// 题型
+ ///
+ public TaskBankTypeEnum Type { get; set; }
+
+ public string ShowType => Type.GetDescription();
+
+ private string _task;
+ ///
+ /// 问题
+ ///
+ public string Task
+ {
+ get { return _task.AddDomain(DomainHelper.OssDomain); }
+ set { _task = value; }
+ }
+
+ ///
+ /// 选项
+ ///
+ public string Options { get; set; }
+
+ private string _answer;
+ ///
+ /// 答案
+ ///
+ public string Answer
+ {
+ get { return _answer.AddDomain(DomainHelper.OssDomain); }
+ set { _answer = value; }
+ }
+
+ private string _analysis;
+ ///
+ /// 解析
+ ///
+ public string Analysis
+ {
+ get { return _analysis.AddDomain(DomainHelper.OssDomain); }
+ set { _analysis = value; }
+ }
+
+ public string AnalysisUrl { get; set; }
+
+ ///
+ /// 问题分数
+ ///
+ public float TaskScore { get; set; }
+
+ private string _audioUrl;
+ ///
+ /// 音频地址
+ ///
+ public string AudioUrl
+ {
+ get { return DomainHelper.OssFullUrl(_audioUrl); }
+ set { _audioUrl = value; }
+ }
+
+ private string _videoUrl;
+ ///
+ /// 视频地址
+ ///
+ public string VideoUrl
+ {
+ get { return DomainHelper.OssFullUrl(_videoUrl); }
+ set { _videoUrl = value; }
+ }
+
+ private string _pointsUrl;
+ ///
+ /// 点位数据地址
+ ///
+ public string PointsUrl
+ {
+ get { return DomainHelper.OssFullUrl(_pointsUrl); }
+ set { _pointsUrl = value; }
+ }
+
+ private string _imageUrl;
+ ///
+ /// 图片地址
+ ///
+ public string ImageUrl
+ {
+ get { return DomainHelper.OssFullUrl(_imageUrl); }
+ set { _imageUrl = value; }
+ }
+
+ public List ImageUrls { get; set; }
+
+ ///
+ /// 问题分数
+ ///
+ public float Score { get; set; }
+
+ ///
+ /// 回答时间(秒)
+ ///
+ public int AnswerTime { get; set; }
+
+ ///
+ /// 音频开始时间
+ ///
+ public long AudioStartTime { get; set; }
+
+ ///
+ /// 音频结束时间
+ ///
+ public long AudioEndTime { get; set; }
+
+ ///
+ /// 知识点
+ ///
+ public string KnowledgePointIds { get; set; }
+
+ ///
+ /// 回答项解析
+ ///
+ public string KeywordAnalysis { get; set; }
+
+ ///
+ /// 年级
+ ///
+ public int Grade { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskUpdateInput.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskUpdateInput.cs
new file mode 100644
index 0000000..8420e79
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskUpdateInput.cs
@@ -0,0 +1,126 @@
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.Models.Enum;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class JournalPageTaskUpdateInput
+ {
+ public long Id { get; set; }
+
+ ///
+ /// 书Id
+ ///
+ public long JournalId { get; set; }
+
+
+ ///
+ /// 书页id
+ ///
+ public long JournalPageId { get; set; }
+
+ ///
+ /// 题号
+ ///
+ public string No { get; set; }
+
+ ///
+ /// 题型
+ ///
+ public TaskBankTypeEnum Type { get; set; }
+
+ ///
+ /// 问题
+ ///
+ public string Task { get; set; }
+
+ ///
+ /// 选项
+ ///
+ public string Options { get; set; }
+
+ ///
+ /// 答案
+ ///
+ public string Answer { get; set; }
+
+ ///
+ /// 答案图片
+ ///
+ public string AnswerUrl { get; set; }
+
+ ///
+ /// 解析
+ ///
+ public string Analysis { get; set; }
+
+ ///
+ /// 解析图片地址
+ ///
+ public string AnalysisUrl { get; set; }
+
+ ///
+ /// 问题分数
+ ///
+ public float Score { get; set; }
+
+ ///
+ /// 回答时间秒
+ ///
+ public int AnswerTime { get; set; }
+
+ ///
+ /// 图片
+ ///
+ public string ImageUrl { get; set; }
+
+ ///
+ /// 视频
+ ///
+ public string VideoUrl { get; set; }
+
+ ///
+ /// 学科Id
+ ///
+ public long SubjectId { get; set; }
+
+ ///
+ /// 关联的知识点ids
+ ///
+ public string KnowledgepointIds { get; set; }
+ }
+
+ public class JournalPageTaskComplementInput
+ {
+ public long Id { get; set; }
+
+ ///
+ /// 任务id
+ ///
+ public long AssignTaskId { get; set; }
+
+ ///
+ /// 音频地址
+ ///
+ public string AudioUrl { get; set; }
+
+ ///
+ /// 音频开始时间
+ ///
+ public long AudioStartTime { get; set; }
+
+ ///
+ /// 音频结束时间
+ ///
+ public long AudioEndTime { get; set; }
+
+ ///
+ /// 点位数据地址
+ ///
+ public string PointsUrl { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageV2Output.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageV2Output.cs
new file mode 100644
index 0000000..06de1c9
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageV2Output.cs
@@ -0,0 +1,100 @@
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.Common.Extensions;
+using QYZH.InteractiveMagazine.Common.Helpers;
+using QYZH.InteractiveMagazine.Models.Enum;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class JournalPageV2Output
+ {
+
+
+ public long JournalId { get; set; }
+
+ public long JournalCatalogId { get; set; }
+
+ public long JournalPageId { get; set; }
+
+ public string Layout { get; set; }
+
+ ///
+ /// 页码
+ ///
+ public int PageNum { get; set; }
+
+ private string _url;
+ ///
+ /// 图片
+ ///
+ public string Url
+ {
+ get { return DomainHelper.OssFullUrl(_url); }
+ set { _url = value; }
+ }
+
+ ///
+ /// 问题
+ ///
+ public List? Tasks { get; set; }
+
+ public List? Areas { get; set; }
+ }
+
+ public class JournalPageTaskV2Output
+ {
+ public long Id { get; set; }
+ public long GroupId { get; set; }
+
+ ///
+ /// 题号
+ ///
+ public string No { get; set; }
+
+
+ //public long test => long.Parse(No.Replace("-", string.Empty));
+
+ ///
+ /// 题型
+ ///
+ public TaskBankTypeEnum Type { get; set; }
+
+ public string ShowType => Type.GetDescription();
+
+ ///
+ /// 选项
+ ///
+ public string Options { get; set; }
+
+ ///
+ /// 答案
+ ///
+ public string Answers { get; set; }
+
+ ///
+ /// 解析
+ ///
+ public string Analysis { get; set; }
+
+ ///
+ /// 是否分配
+ ///
+ public bool Assign { get; set; }
+ }
+
+ public class JournalPageOtherOutput
+ {
+ public long Id { get; set; }
+
+ ///
+ /// 题号
+ ///
+ public string No { get; set; }
+
+ ///
+ /// 题型
+ ///
+ public TaskBankTypeEnum Type { get; set; }
+
+ public string ShowType => Type.GetDescription();
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalQueryDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalQueryDto.cs
new file mode 100644
index 0000000..e57fbcb
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalQueryDto.cs
@@ -0,0 +1,22 @@
+using QYZH.InteractiveMagazine.Models.Enum;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class JournalQueryDto
+ {
+ ///
+ /// 书籍名称
+ ///
+ public string? Name { get; set; }
+
+ ///
+ /// 状态
+ ///
+ public JournalStatusEnum? Status { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalQuestionOutput.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalQuestionOutput.cs
new file mode 100644
index 0000000..4891702
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalQuestionOutput.cs
@@ -0,0 +1,35 @@
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.Models.Enum;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class JournalTaskOutput
+ {
+ public long TaskId { get; set; }
+ public string TaskNo { get; set; }
+
+ public TaskTypeEnum TaskType
+ {
+ get
+ {
+ var type = new TaskBankType();
+ if (type.ObjectiveTypes.Contains(TaskSubType))
+ return TaskTypeEnum.Objective;
+ else if (type.SubjectiveTypes.Contains(TaskSubType))
+ return TaskTypeEnum.Subjective;
+ else
+ throw new InvalidOperationException($"Unknown task sub type: {TaskSubType}");
+ }
+ }
+
+ ///
+ /// 详细类型
+ ///
+ public TaskBankTypeEnum TaskSubType { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/PageLayoutInput.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/PageLayoutInput.cs
new file mode 100644
index 0000000..2323efe
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/PageLayoutInput.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Linq;
+
+namespace QYZH.InteractiveMagazine.Models.Dto.Journal
+{
+ public class PageLayoutInput
+ {
+ public long Id { get; set; }
+
+ public string Layout { get; set; }
+
+ public List TasksImages { get; set; }
+
+ }
+ public class TasksImages
+ {
+ public long TaskId { get; set; }
+
+ public string Url { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/MoveInput.cs b/QYZH.InteractiveMagazine.Models/Dto/MoveInput.cs
new file mode 100644
index 0000000..cbb1abd
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/MoveInput.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Base
+{
+ public class MoveInput
+ {
+ ///
+ /// 要移动节点ID (必须)
+ ///
+ [Required]
+ public long SourceId { get; set; }
+
+ ///
+ /// 目标父节点ID (0表示移动到根节点)
+ ///
+ public long TargetParentId { get; set; }
+
+ ///
+ /// 在目标父节点下的插入位置 (可选,从0开始,null表示追加到末尾)
+ ///
+ [Range(0, int.MaxValue)]
+ public int? Position { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Entity/Journal.cs b/QYZH.InteractiveMagazine.Models/Entity/Journal.cs
index cfe6ebd..2b8a678 100644
--- a/QYZH.InteractiveMagazine.Models/Entity/Journal.cs
+++ b/QYZH.InteractiveMagazine.Models/Entity/Journal.cs
@@ -14,73 +14,107 @@ namespace QYZH.InteractiveMagazine.Models.Entity
}
///
- /// Desc:期刊号
+ /// Desc:书籍名称
+ /// Default:
+ /// Nullable:True
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Desc:总页数
/// Default:
/// Nullable:False
///
- public int IssueNumber {get;set;}
+ public int TotalPage { get; set; }
- ///
- /// Desc:期刊标题
- /// Default:
- /// Nullable:False
- ///
- public string Title {get;set;}
+ ///
+ /// Desc:校验页数
+ /// Default:
+ /// Nullable:False
+ ///
+ public int VerifyPage { get; set; }
- ///
- /// Desc:封面图地址
- /// Default:
- /// Nullable:True
- ///
- public string CoverImageUrl {get;set;}
- ///
- /// Desc:摘要
- /// Default:
- /// Nullable:True
- ///
- public string Summary {get;set;}
+ ///
+ /// Desc:pdf地址
+ /// Default:
+ /// Nullable:True
+ ///
+ public string PdfUrl { get; set; }
- ///
- /// Desc:发布时间
- /// Default:
- /// Nullable:True
- ///
- public DateTime? PublishDate {get;set;}
+ ///
+ /// Desc:宽度
+ /// Default:
+ /// Nullable:False
+ ///
+ public float Width { get; set; }
- ///
- /// Desc:是否启用
- /// Default:b'1'
- /// Nullable:False
- ///
- public bool IsActive {get;set;}
+ ///
+ /// Desc:高度
+ /// Default:
+ /// Nullable:False
+ ///
+ public float Height { get; set; }
- ///
- /// Desc:排序权重
- /// Default:0
- /// Nullable:False
- ///
- public int SortOrder {get;set;}
+ ///
+ /// Desc:乐观锁
+ /// Default:
+ /// Nullable:False
+ ///
+ public int Revision { get; set; }
- ///
- /// Desc:编者寄语
- /// Default:
- /// Nullable:True
- ///
- public string EditorNote {get;set;}
+ ///
+ /// Desc:审查结果(默认0 1通过 -1驳回)
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int Result { get; set; }
- ///
- /// Desc:主题色
- /// Default:
- /// Nullable:True
- ///
- public string ThemeColor {get;set;}
+ ///
+ /// Desc:封面
+ /// Default:
+ /// Nullable:True
+ ///
+ public string Cover { get; set; }
+
+ ///
+ /// Desc:封底
+ /// Default:
+ /// Nullable:True
+ ///
+ public string BackCover { get; set; }
+
+ ///
+ /// Desc:pdf预览地址
+ /// Default:
+ /// Nullable:True
+ ///
+ public string PdfPreviewUrl { get; set; }
+
+
+ public string Title { get; set; }
+
+ ///
+ /// Desc:下载书籍页码点阵码PDF文件名称
+ /// Default:
+ /// Nullable:True
+ ///
+ public string DownloadJournalPagePdfName { get; set; }
+
+
+ ///
+ /// Desc:书籍描述/简介
+ /// Default:
+ /// Nullable:True
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Desc:期刊类型: Normal, Special
+ /// Default:Normal
+ /// Nullable:False
+ ///
+ public JournalTypeEnum Type {get;set;}
- ///
- /// Desc:期刊类型: Normal, Special
- /// Default:Normal
- /// Nullable:False
- ///
- public JournalTypeEnum Type {get;set;}
}
}
diff --git a/QYZH.InteractiveMagazine.Models/Entity/JournalPageTask.cs b/QYZH.InteractiveMagazine.Models/Entity/JournalPageTask.cs
index 0457435..2a465f4 100644
--- a/QYZH.InteractiveMagazine.Models/Entity/JournalPageTask.cs
+++ b/QYZH.InteractiveMagazine.Models/Entity/JournalPageTask.cs
@@ -43,7 +43,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
/// Default:
/// Nullable:False
///
- public JournalPageTaskTypeEnum Type {get;set;}
+ public TaskBankTypeEnum Type {get;set;}
///
/// Desc:任务
diff --git a/QYZH.InteractiveMagazine.Models/Enum/JournalPageTaskTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/JournalPageTaskTypeEnum.cs
index 0d21078..b03a565 100644
--- a/QYZH.InteractiveMagazine.Models/Enum/JournalPageTaskTypeEnum.cs
+++ b/QYZH.InteractiveMagazine.Models/Enum/JournalPageTaskTypeEnum.cs
@@ -26,5 +26,5 @@ public enum JournalPageTaskTypeEnum
/// 问答题
///
[Description("问答题")]
- QuestionAnswer = 4
+ TaskAnswer = 4
}
diff --git a/QYZH.InteractiveMagazine.Models/Enum/JournalStatusEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/JournalStatusEnum.cs
index ea02ab3..3efc38c 100644
--- a/QYZH.InteractiveMagazine.Models/Enum/JournalStatusEnum.cs
+++ b/QYZH.InteractiveMagazine.Models/Enum/JournalStatusEnum.cs
@@ -8,20 +8,58 @@ namespace QYZH.InteractiveMagazine.Models.Enum;
public enum JournalStatusEnum
{
///
- /// 草稿
+ /// 缺失目录
///
- [Description("草稿")]
- Draft = 0,
+ [Description("缺失目录")]
+ MissCatalog = -1,
+ ///
+ /// 已创建
+ ///
+ [Description("已创建")]
+ Created = 0,
+
+ [Description("编辑")]
+ Editor = 1,
///
- /// 已发布
+ /// 已校验
///
- [Description("已发布")]
- Published = 1,
+ [Description("已校验")]
+ Verify = 3,
+
+ ///
+ /// 铺码中
+ ///
+ [Description("铺码中")]
+ Codeing = 4,
+
+ ///
+ /// 铺码成功
+ ///
+ [Description("铺码成功")]
+ CodeSuccess = 5,
+
+ ///
+ /// 铺码失败
+ ///
+ [Description("铺码失败")]
+ CodeFail = -5,
///
/// 已归档
///
[Description("已归档")]
- Archived = 2
+ Archive = 9,
+
+ ///
+ /// 已废弃
+ ///
+ [Description("已废弃")]
+ Abandoned = -9,
+
+ ///
+ /// 已发布
+ ///
+ [Description("已发布")]
+ Published = 999
}
diff --git a/QYZH.InteractiveMagazine.Models/Enum/TaskBankTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/TaskBankTypeEnum.cs
new file mode 100644
index 0000000..0e62bb4
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Enum/TaskBankTypeEnum.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Enum
+{
+ public enum TaskBankTypeEnum
+ {
+ ///
+ /// 无效题目
+ ///
+ [Description("无效")]
+ Default = 0,
+ ///
+ /// 单选题
+ ///
+ [Description("单选题")]
+ Single = 1,
+
+ ///
+ /// 多选题
+ ///
+ [Description("多选题")]
+ Multiple = 2,
+
+ ///
+ /// 判断题
+ ///
+ [Description("判断题")]
+ Whether = 3,
+
+ ///
+ /// 填空题
+ ///
+ [Description("填空题")]
+ FillIn = 4,
+
+ ///
+ /// 应用题
+ ///
+ [Description("应用题")]
+ Problem = 5,
+
+ ///
+ /// 简答题
+ ///
+ [Description("简答题")]
+ ShortAnswer = 6,
+
+ }
+
+ public class TaskBankType
+ {
+ ///
+ /// 客观题
+ ///
+ public TaskBankTypeEnum[] ObjectiveTypes => new[] { TaskBankTypeEnum.Single, TaskBankTypeEnum.Multiple, TaskBankTypeEnum.Whether };
+
+ ///
+ /// 获取客观题类型的整数值
+ ///
+ public List ObjectiveTypeValues => ObjectiveTypes.Select(t => (int)t).ToList();
+
+ ///
+ /// 主观题
+ ///
+ public TaskBankTypeEnum[] SubjectiveTypes => new[] { TaskBankTypeEnum.FillIn, TaskBankTypeEnum.Problem, TaskBankTypeEnum.ShortAnswer };
+
+ ///
+ /// 获取主观题类型的整数值
+ ///
+ public List SubjectiveTypeValues => SubjectiveTypes.Select(t => (int)t).ToList();
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Enum/TaskTypeEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/TaskTypeEnum.cs
new file mode 100644
index 0000000..ea32178
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Enum/TaskTypeEnum.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QYZH.InteractiveMagazine.Models.Enum
+{
+ public enum TaskTypeEnum
+ {
+ ///
+ /// 客观题
+ ///
+ [Description("客观题")]
+ Objective = 1,
+
+ ///
+ /// 主观题
+ ///
+ [Description("主观题")]
+ Subjective = 2
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/QYZH.InteractiveMagazine.Models.csproj b/QYZH.InteractiveMagazine.Models/QYZH.InteractiveMagazine.Models.csproj
index e339258..8df9756 100644
--- a/QYZH.InteractiveMagazine.Models/QYZH.InteractiveMagazine.Models.csproj
+++ b/QYZH.InteractiveMagazine.Models/QYZH.InteractiveMagazine.Models.csproj
@@ -9,9 +9,14 @@
+
+
+
+
+
diff --git a/QYZH.InteractiveMagazine.Service/CheckInService.cs b/QYZH.InteractiveMagazine.Service/CheckInService.cs
index daee141..ffdde75 100644
--- a/QYZH.InteractiveMagazine.Service/CheckInService.cs
+++ b/QYZH.InteractiveMagazine.Service/CheckInService.cs
@@ -68,7 +68,7 @@ public class CheckInService(
.Where(p => p.UserId == userId && !p.IsDeleted)
.FirstAsync();
- // 6. 事务执行签到相关写操作
+ // 6. 事务执行签到相关写操作(含宠物喂养,统一事务)
var result = new CheckInOutput();
await checkInRecordRepository.UseTranAsync(async () =>
@@ -107,6 +107,17 @@ public class CheckInService(
Description = $"签到奖励(连续{consecutiveDays}天)"
});
+ // 6d. 如果有活跃宠物,喂养宠物(同一事务内)
+ FeedPetOutput? feedResult = null;
+ if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
+ {
+ feedResult = await petService.FeedPetInTranAsync(userId, new FeedPetInput
+ {
+ PetId = pet.Id,
+ GrowthPoints = growthReward
+ });
+ }
+
// 构建返回结果
result.RecordId = (long)recordId;
result.CheckInDate = today;
@@ -116,41 +127,17 @@ public class CheckInService(
result.PointsBalance = pointsResult.NewBalance;
result.GrowthPointsBalance = newGrowthBalance;
result.HasPet = pet != null;
- });
-
- // 7. 如果用户有活跃宠物,调用 PetService 喂养(含进化检查),独立事务
- if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
- {
- try
+ if (feedResult != null)
{
- var feedResult = await petService.FeedPetAsync(userId, new FeedPetInput
- {
- PetId = pet.Id,
- GrowthPoints = growthReward
- });
-
result.HasEvolved = feedResult.HasEvolved;
result.EvolvedStageName = feedResult.EvolvedStageName;
-
- logger.LogInformation("签到成长值已喂养宠物,PetId: {PetId}, 进化: {HasEvolved}",
- pet.Id, feedResult.HasEvolved);
}
- catch (Exception ex)
- {
- logger.LogWarning(ex, "签到后喂养宠物失败,PetId: {PetId},将创建补偿任务", pet.Id);
+ });
- await compensationTaskService.CreateTaskAsync(new CreateCompensationTaskInput
- {
- TaskType = CompensationTaskTypeEnum.PetFeeding,
- BusinessSource = "CheckIn",
- BusinessId = result.RecordId.ToString(),
- UserId = userId,
- Payload = new { PetId = pet.Id, GrowthPoints = growthReward },
- ErrorMessage = ex.Message,
- ErrorSource = "CheckInService.CheckInAsync → PetService.FeedPetAsync",
- MaxRetries = 3
- });
- }
+ if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
+ {
+ logger.LogInformation("签到成长值已喂养宠物,PetId: {PetId}, 进化: {HasEvolved}",
+ pet.Id, result.HasEvolved);
}
logger.LogInformation("用户签到成功,UserId: {UserId}, 连续{Days}天, 积分+{Points}, 成长值+{Growth}",
@@ -339,6 +326,17 @@ public class CheckInService(
.ExecuteCommandAsync();
}
+ // 如果有活跃宠物,喂养宠物(同一事务内)
+ FeedPetOutput? feedResult = null;
+ if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
+ {
+ feedResult = await petService.FeedPetInTranAsync(userId, new Models.Dto.Pet.FeedPetInput
+ {
+ PetId = pet.Id,
+ GrowthPoints = growthReward
+ });
+ }
+
result.RecordId = (long)recordId;
result.CheckInDate = targetDate;
result.ConsecutiveDays = 0;
@@ -347,27 +345,17 @@ public class CheckInService(
result.PointsBalance = pointsResult.NewBalance;
result.GrowthPointsBalance = newGrowthBalance;
result.HasPet = pet != null;
- });
-
- // 如果有活跃宠物,喂养成长值
- if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
- {
- try
+ if (feedResult != null)
{
- var feedResult = await petService.FeedPetAsync(userId, new Models.Dto.Pet.FeedPetInput
- {
- PetId = pet.Id,
- GrowthPoints = growthReward
- });
-
result.HasEvolved = feedResult.HasEvolved;
result.EvolvedStageName = feedResult.EvolvedStageName;
}
- catch (Exception ex)
- {
- logger.LogWarning(ex, "补签后喂养宠物失败,PetId: {PetId}", pet.Id);
- // 补偿机制:如需可在此创建补偿任务
- }
+ });
+
+ if (pet != null && pet.Status == (int)UserPetStatusEnum.Active && growthReward > 0)
+ {
+ logger.LogInformation("补签成长值已喂养宠物,PetId: {PetId}, 进化: {HasEvolved}",
+ pet.Id, result.HasEvolved);
}
logger.LogInformation("补签成功,UserId: {UserId}, Date: {Date}, 积分+{Points}, 成长值+{Growth}",
diff --git a/QYZH.InteractiveMagazine.Service/JournalCatalogService.cs b/QYZH.InteractiveMagazine.Service/JournalCatalogService.cs
new file mode 100644
index 0000000..6faf02e
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Service/JournalCatalogService.cs
@@ -0,0 +1,526 @@
+
+using Mapster;
+using QYZH.InteractiveMagazine.Common.Extensions;
+using QYZH.InteractiveMagazine.Infrastructure.OSS;
+using QYZH.InteractiveMagazine.IService;
+using QYZH.InteractiveMagazine.Models.Base;
+using QYZH.InteractiveMagazine.Models.Common;
+using QYZH.InteractiveMagazine.Models.Dto.Journal;
+using QYZH.InteractiveMagazine.Models.Entity;
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.Repository;
+using SqlSugar;
+using Yitter.IdGenerator;
+
+namespace QYZH.InteractiveMagazine.Service;
+
+public class JournalCatalogService : BaseRepository, IJournalCatalogService
+{
+ private readonly BaseRepository _JournalPageRepository;
+ private readonly BaseRepository _JournalPageTaskRepository;
+ private readonly OssService _ossService;
+
+ public JournalCatalogService(BaseRepository JournalPageRepository, BaseRepository JournalPageTaskRepository, OssService ossService)
+ {
+ //_JournalRepository = JournalRepository;
+ _JournalPageRepository = JournalPageRepository;
+ _ossService = ossService;
+ }
+
+ public async Task> DetailAsync(long JournalId)
+ {
+ return await base.Queryable().Where(w => w.ParentId == 0 && w.JournalId == JournalId).OrderBy(o => o.Sort).ToChildListAsync(it => it.ParentId, 0) ?? [];
+ }
+
+ public async Task ImportAsync(JournalImportDto input)
+ {
+ var Journals = await Queryable().Where(w => w.Id == input.JournalId).FirstAsync();
+ BusinessException.ThrowIf(Journals.IsNull(), "未找到书本");
+
+ var pageNum = await _JournalPageRepository.Queryable().Where(w => w.JournalId == input.JournalId).MaxAsync(m => m.PageNum);
+ BusinessException.ThrowIf(pageNum != 0, "已经存在书页");
+
+
+ var JournalCatalog = new JournalCatalog()
+ {
+ Id = YitIdHelper.NextId(),
+ JournalId = input.JournalId,
+ Name = "目录",
+ ParentId = 0,
+ Level = 0,
+ Sort = 1,
+ Type = JournalCatalogTypeEnum.Page,
+ };
+
+ int sort = 1;
+ var result = input.JournalCatalogs.Where(w => w.Type == 1).Select(s =>
+ {
+ var JournalPage = new JournalPage
+ {
+ Id = YitIdHelper.NextId(),
+ JournalId = input.JournalId,
+ JournalCatalogId = JournalCatalog.Id,
+ Sort = sort,
+ PageNum = sort < input.Index ? 0 : sort - (input.Index - 1)
+ };
+ var key = $"Journal/{input.JournalId}/{JournalPage.Id}/page.{s.Url.ToExtension()}";
+ _ossService.CopyObject(s.Url.RemoveDomain(), key);
+
+ JournalPage.Url = key;
+ //dotMatrixPage.PreviewUrl = key;
+
+ sort = sort + 1;
+ return new
+ {
+ JournalPage = JournalPage
+ };
+ }).ToList();
+
+ await UseTranAsync(async () =>
+ {
+
+ var data = await base.InsertAsync(JournalCatalog);
+
+ var JournalPages = result.Select(x => x.JournalPage).ToList();
+ await _JournalPageRepository.InsertRangeAsync(JournalPages);
+
+ var key = $"Journal/{input.JournalId}/Journal.pdf";
+ _ossService.CopyObject(input.PdfUrl.RemoveDomain(), key);
+
+ await Updateable().SetColumns(s => s.PdfUrl, key)
+ .SetColumns(s => s.Status, JournalStatusEnum.Created)
+ .SetColumns(s => s.TotalPage, JournalPages.Count).Where(w => w.Id == input.JournalId)
+ .ExecuteCommandAsync();
+ });
+
+ return result.Count;
+ }
+
+
+
+ ///
+ /// 获取书分类及页码
+ ///
+ ///
+ ///
+ public async Task> GetJournalCatalogListAsync(long JournalId)
+ {
+ var Journal = await Queryable().Where(w => w.Id == JournalId).FirstAsync();
+ BusinessException.ThrowIf(Journal.IsNull(), "未找到书本");
+
+ var pageNumList = await _JournalPageRepository.Queryable()
+ .InnerJoin((a, b) => a.JournalCatalogId == b.Id)
+ .Where((a, b) => a.JournalId == JournalId)
+ .OrderBy((a, b) => a.PageNum)
+ .Select((a, b) => new JournalCatalogTreeListDto
+ {
+ JournalId = a.JournalId,
+ Id = b.Id,
+ Name = b.Name,
+ PageNum = a.PageNum,
+ ParentName = SqlFunc.Subqueryable().Where(c => c.Id == b.ParentId).Select(c => c.Name),
+ ParentId = b.ParentId,
+ PageId = a.Id,
+ })
+ .ToListAsync();
+
+ return pageNumList;
+ }
+
+
+ ///
+ /// 获取书分类及页码
+ ///
+ ///
+ ///
+ public async Task> GetJournalCataloTreeAsync(long JournalId)
+ {
+ var Journal = await Queryable().Where(w => w.Id == JournalId).FirstAsync();
+ BusinessException.ThrowIf(Journal.IsNull(), "未找到书本");
+
+ // 先查询所有目录
+ var allCatalogs = await base.Queryable()
+ .Where(w => w.JournalId == JournalId)
+ .OrderBy(o => o.Sort)
+ .ToListAsync();
+
+ if (!allCatalogs.Any())
+ return new List();
+
+ // 再查询有页面对应的目录及页面信息
+ var pageNumList = await _JournalPageRepository.Queryable()
+ .InnerJoin((a, b) => a.JournalCatalogId == b.Id)
+ .Where((a, b) => a.JournalId == JournalId)
+ .OrderBy((a, b) => a.PageNum)
+ .Select((a, b) => new JournalCatalogTreeListDto
+ {
+ JournalId = a.JournalId,
+ Id = b.Id,
+ Name = b.Name,
+ PageNum = a.PageNum,
+ ParentName = SqlFunc.Subqueryable().Where(c => c.Id == b.ParentId).Select(c => c.Name),
+ ParentId = b.ParentId,
+ PageId = a.Id,
+ })
+ .ToListAsync();
+
+ var tree = BuildCatalogTree(allCatalogs, pageNumList);
+
+ return tree;
+ }
+
+ ///
+ /// 构建目录树形结构(支持任意层级)
+ ///
+ /// 所有目录
+ /// 有页面对应的目录信息
+ ///
+ private List BuildCatalogTree(List allCatalogs, List pageNumList)
+ {
+ // 构建有页面对应的目录到页面列表的映射
+ var catalogPagesMap = pageNumList
+ .GroupBy(x => x.Id)
+ .ToDictionary(
+ g => g.Key,
+ g => g.Select(p => new IcrJournalCatalogTreeDto
+ {
+ Id = p.PageId,
+ Name = $"第{p.PageNum}页",
+ Level = 0,
+ Child = new List()
+ }).ToList()
+ );
+
+ // 构建所有目录节点字典
+ var catalogDict = new Dictionary();
+ var rootCatalogIds = new HashSet();
+
+ foreach (var catalog in allCatalogs)
+ {
+ var catalogId = catalog.Id;
+ var parentId = catalog.ParentId;
+
+ if (catalogDict.ContainsKey(catalogId))
+ continue;
+
+ var node = new IcrJournalCatalogTreeDto
+ {
+ Id = catalogId,
+ Name = catalog.Name,
+ Level = 0,
+ Child = catalogPagesMap.GetValueOrDefault(catalogId, new List())
+ };
+
+ catalogDict[catalogId] = node;
+
+ if (parentId == 0)
+ {
+ rootCatalogIds.Add(catalogId);
+ }
+ }
+
+ // 构建树形结构:将子节点挂载到父节点
+ foreach (var catalog in allCatalogs)
+ {
+ var catalogId = catalog.Id;
+ var parentId = catalog.ParentId;
+
+ if (parentId > 0 && catalogDict.ContainsKey(parentId) && catalogDict.ContainsKey(catalogId))
+ {
+ var parentNode = catalogDict[parentId];
+ var childNode = catalogDict[catalogId];
+
+ if (!parentNode.Child.Any(c => c.Id == childNode.Id))
+ {
+ parentNode.Child.Add(childNode);
+ }
+ }
+ }
+
+ // 计算每个节点的层级并返回根节点列表
+ var result = rootCatalogIds
+ .Where(id => catalogDict.ContainsKey(id))
+ .Select(id =>
+ {
+ var node = catalogDict[id];
+ node.Level = 1;
+ CalculateChildLevels(node.Child, 2);
+ return node;
+ })
+ .ToList();
+
+ return result;
+ }
+
+ ///
+ /// 递归计算子节点层级
+ ///
+ private void CalculateChildLevels(List children, int level)
+ {
+ foreach (var child in children)
+ {
+ child.Level = level;
+ if (child.Child != null && child.Child.Count > 0)
+ {
+ CalculateChildLevels(child.Child, level + 1);
+ }
+ }
+ }
+
+ ///
+ /// 导入书籍目录
+ ///
+ ///
+ ///
+ ///
+ public async Task ImportCatalogAsync(long JournalId, List dtos)
+ {
+ dtos = dtos.Where(c => !string.IsNullOrWhiteSpace(c.Name)).ToList();
+ BusinessException.ThrowIf(dtos.Select(c => c.PageNum).Distinct().Count() != dtos.Count(), "存在重复的页码");
+
+ var Journal = await Queryable().Where(w => w.Id == JournalId).FirstAsync();
+ BusinessException.ThrowIf(Journal.IsNull(), "未找到书本");
+ var pageNums = dtos.Select(c => c.PageNum).ToList();
+ var pageNumNotExists = await _JournalPageRepository.Queryable().AnyAsync(w => w.JournalId == JournalId && !pageNums.Contains(w.PageNum));
+ BusinessException.ThrowIf(pageNumNotExists, "不存在的书页");
+
+ var firstCatelogGroup = dtos.Select(c => c.ParentName).Distinct().Select(c => new JournalCatalog
+ {
+ JournalId = JournalId,
+ Id = YitIdHelper.NextId(),
+ Level = 1,
+ Name = c,
+ }).ToList();
+ var subCatelogGroup = dtos.Select(c => new { c.Name, c.ParentName }).Distinct().Select(c => new
+ {
+ JournalId = JournalId,
+ Id = YitIdHelper.NextId(),
+ Level = 2,
+ Name = c.Name,
+ ParentId = firstCatelogGroup.FirstOrDefault(m => m.Name == c.ParentName)?.Id,
+ ParentName = c.ParentName
+ }).ToList();
+
+ var result = await UseTranAsync(async () =>
+ {
+ await base.DeleteAsync(c => c.JournalId == JournalId);
+ await base.InsertRangeAsync(firstCatelogGroup);
+ await base.InsertRangeAsync(subCatelogGroup.Adapt>());
+ var JournalPages = await Queryable().Where(c => c.JournalId == JournalId).ToListAsync();
+ JournalPages.ForEach(c =>
+ {
+ var cate = dtos.FirstOrDefault(m => m.PageNum == c.PageNum);
+ c.JournalCatalogId = subCatelogGroup?.FirstOrDefault(m => m.Name == cate?.Name && m.ParentName == cate?.ParentName)?.Id ?? 0;
+ });
+ await base.Context.Updateable(JournalPages).UpdateColumns(c => c.JournalCatalogId).ExecuteCommandAsync();
+ return true;
+ });
+
+ return result;
+ }
+
+ public async Task InsertAsync(JournalCatalogInput input)
+ {
+ var map = input.Adapt();
+ map.Id = YitIdHelper.NextId();
+ await UseTranAsync(async () =>
+ {
+ // 获取同级所有节点
+ var siblings = await base.Queryable().Where(w => w.JournalId == input.JournalId).Where(w => w.ParentId == input.ParentId).OrderBy(o => o.Sort).ToListAsync();
+
+ if (input.Position.HasValue && input.Position > 0 && input.Position <= siblings.Count)
+ {
+ // 调换位置,前面不变,后续加1即可
+ int validPosition = Math.Min(input.Position.Value, siblings.Count);
+ map.Sort = validPosition;
+
+ // 调整后续节点的SortOrder (+1)
+ await base.Updateable()
+ .SetColumns(x => x.Sort == x.Sort + 1)
+ .Where(x => x.ParentId == input.ParentId && x.Sort >= validPosition)
+ .ExecuteCommandAsync();
+ }
+ else
+ {
+ // 默认追加到末尾
+ map.Sort = siblings.Count > 0 ? siblings.Max(x => x.Sort) + 1 : 0;
+ }
+
+
+ var data = await base.InsertAsync(map);
+
+ if (input.Type == 1)
+ {
+ var Journal = await Queryable().Where(w => w.Id == input.JournalId).FirstAsync();
+ BusinessException.ThrowIf(Journal.IsNull(), "未找到书本");
+
+ //var dotMatrixPage = new DotMatrixPage()
+ //{
+ // Id = YitIdHelper.NextId(),
+ // DotMatrixNoteJournalId = input.JournalId,
+ // WidthMilliMeter = Journal.Width,
+ // HightMilliMeter = Journal.Height,
+ //};
+ var JournalPage = new JournalPage
+ {
+ JournalId = input.JournalId,
+ JournalCatalogId = map.Id,
+ //DotMatrixPageId = dotMatrixPage.Id,
+ PageNum = map.Sort,
+ };
+ }
+ });
+ return map.Id;
+
+ }
+
+ public async Task UpdateAsync(JournalCatalogUpdateInput input)
+ {
+ var result = await base.Updateable(input.Adapt()).Where(w => w.Id == input.Id).ExecuteCommandAsync() > 0;
+
+ return result;
+ }
+
+ public async Task DeleteAsync(long id)
+ {
+ var catas = await base.Queryable().ToChildListAsync(it => it.ParentId, id);
+ var ids = catas.Select(s => s.Id).ToList();
+
+ await UseTranAsync(async () =>
+ {
+ // 删除目录
+ var cata = await base.Deleteable().Where(d => ids.Contains(d.Id)).ExecuteCommandAsync() > 0;
+ BusinessException.ThrowIf(!cata, $"删除目录失败");
+ // 删除所有页/问题
+ var pages = await _JournalPageRepository.DeleteAsync(w => ids.Contains(w.JournalCatalogId));
+ BusinessException.ThrowIf(pages.IsNull(), $"删除数据失败");
+
+
+ //var x = await _JournalPageRepository.DeleteAsync(w => ids.Contains(w.JournalCatalogId));
+ //var y = await _JournalPageTaskRepository.DeleteAsync(w => ids.Contains(w.JournalCatalogId));
+ });
+ return true;
+ }
+
+ public async Task MoveAsync(MoveInput input)
+ {
+ // 1. 获取源节点并校验
+ var sourceNode = await base.Queryable().Where(x => x.Id == input.SourceId).FirstAsync();
+ BusinessException.ThrowIf(sourceNode.IsNull(), $"要移动的节点{input.SourceId}不存在");
+
+
+ // 2. 校验目标父节点(如果指定)
+ if (input.TargetParentId > 0)
+ {
+ var targetParentExists = await base.Queryable().Where(x => x.Id == input.TargetParentId).AnyAsync();
+ BusinessException.ThrowIf(!targetParentExists, $"目标父节点{input.TargetParentId}不存在");
+ }
+
+ await base.UseTranAsync(async () =>
+ {
+ //var tree = await base.Queryable().Where(x => x.Id == input.SourceId).AsTreeCte().OrderBy(x => x.Level).ToTreeListAsync();
+
+ if (input.Position.HasValue)
+ {
+ await base.Updateable()
+ .SetColumns(x => x.Sort == x.Sort + 1)
+ .Where(x => x.ParentId == input.TargetParentId && x.Sort >= input.Position.Value)
+ .ExecuteCommandAsync();
+ }
+
+ // 3. 更新源节点的父节点
+ var updateCount = await base.Updateable()
+ .SetColumns(x => x.ParentId, input.TargetParentId)
+ .SetColumns(x => x.Sort, input.Position ?? 0)
+ .Where(x => x.Id == input.SourceId)
+ .ExecuteCommandAsync() > 0;
+
+ BusinessException.ThrowIf(!updateCount, $"节点移动失败");
+ });
+
+ return true;
+ }
+
+
+ //public async Task CopyAsync(CopyInput input)
+ //{
+
+ // // 1. 获取源节点并校验
+ // var sourceNode = await base.Queryable().Where(x => x.Id == input.SourceId).FirstAsync();
+ // BusinessException.ThrowIf(sourceNode.IsNull(), $"要移动的节点{input.SourceId}不存在");
+
+
+ // // 2. 校验目标父节点(如果指定)
+ // if (input.TargetParentId > 0)
+ // {
+ // var targetParentExists = await base.Queryable().Where(x => x.Id == input.TargetParentId).AnyAsync();
+ // BusinessException.ThrowIf(!targetParentExists, $"目标父节点{input.TargetParentId}不存在");
+ // }
+
+ // using var uow = _unitOfWorkManager.Begin();
+ // var tree = await base.Queryable().IncludeMany(c => c.Pages, then => then.IncludeMany(x => x.PageTasks))
+ // .Where(x => x.Id == input.SourceId)
+ // .AsTreeCte()
+ // .OrderBy(x => x.Level)
+ // .ToTreeListAsync();
+
+ // if (input.Position.HasValue)
+ // {
+ // await base.Updateable()
+ // .Set(x => x.Sort + 1)
+ // .Where(x => x.ParentId == input.TargetParentId && x.Sort >= input.Position.Value)
+ // .ExecuteCommandAsync();
+ // }
+
+ // var copy = await DeepCopyAsync(tree.First(), input.TargetParentId);
+ // BusinessException.ThrowIf(copy.IsNull(), $"复制节点失败");
+
+ // var data = await base.InsertAsync(copy);
+ // BusinessException.ThrowIf(data.IsNull(), $"复制节点失败");
+
+ // var pages = copy.Where(s => s.Pages.NotNull()).Queryable()Many(s => s.Pages);
+ // var tempPage = await _JournalPageRepository.InsertAsync(pages);
+ // BusinessException.ThrowIf(tempPage.IsNull(), $"复制页面数据失败");
+
+ // //var task = pages.Where(w => w.PageTasks.NotNull()).Queryable()Many(s => s.PageTasks);
+ // //var tempPageTask = await _JournalPageTaskRepository.InsertAsync(task);
+ // //BusinessException.ThrowIf(tempPageTask.IsNull(), $"复制页面问题数据失败");
+
+ // uow.Commit();
+ // return true;
+ //}
+ //private async Task> DeepCopyAsync(IcrJournalCatalog source, long targetParentId, List? list = null)
+ //{
+ // list ??= new List();
+
+ // var copy = new IcrJournalCatalog
+ // {
+ // Id = YitIdHelper.NextId(),// 设置新ID
+ // Name = !list.Any() ? $"{source.Name} 副本" : source.Name,
+ // ParentId = targetParentId,
+ // Level = source.Level,
+ // Sort = source.Sort,
+ // };
+ // copy.Pages?.ForEach(page =>
+ // {
+ // page.Id = YitIdHelper.NextId(); // 设置新页面ID
+ // page.JournalCatalogId = copy.Id; // 设置为新目录的ID
+ // page.PageTasks?.ForEach(task =>
+ // {
+ // task.Id = YitIdHelper.NextId(); // 设置新问题ID
+ // task.JournalPageId = page.Id; // 设置为新页面的ID
+ // });
+ // });
+ // list.Add(copy);
+
+ // // 递归复制子节点
+ // if (source.Childs != null && source.Childs.Any())
+ // {
+ // foreach (var child in source.Childs)
+ // {
+ // await DeepCopyAsync(child, copy.Id, list);
+ // }
+ // }
+ // return list;
+ //}
+}
diff --git a/QYZH.InteractiveMagazine.Service/JournalPageService.cs b/QYZH.InteractiveMagazine.Service/JournalPageService.cs
new file mode 100644
index 0000000..189cc41
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Service/JournalPageService.cs
@@ -0,0 +1,211 @@
+
+using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
+using QYZH.InteractiveMagazine.Common.Extensions;
+using QYZH.InteractiveMagazine.Infrastructure.OSS;
+using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
+using QYZH.InteractiveMagazine.IService;
+using QYZH.InteractiveMagazine.Models.Common;
+using QYZH.InteractiveMagazine.Models.Dto.Journal;
+using QYZH.InteractiveMagazine.Models.Entity;
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.Repository;
+using System.Diagnostics;
+using Yitter.IdGenerator;
+
+namespace QYZH.InteractiveMagazine.Service;
+
+public class JournalPageService(BaseRepository JournalRepository,
+ OssService ossService,
+ IHttpClientFactory httpClientFactory,
+ IConfiguration configuration,
+ IRabbitMQService rabbitMqService,
+ BaseRepository JournalPageTaskRepository) : BaseRepository, IJournalPageService
+{
+
+ public async Task InsertAsync(JournalAddV2Input input)
+ {
+
+ var Journal = await JournalRepository.Queryable().Where(w => w.Id == input.JournalId).FirstAsync();
+ BusinessException.ThrowIf(Journal.IsNull(), "未找到书本");
+
+ BusinessException.ThrowIf(Journal?.Status == (int)JournalStatusEnum.Archive, "书籍已归档不能修改");
+
+ var pages = new List();
+ //var dotMatrixpages = new List();
+
+ //var dotMatrixpage = new DotMatrixPage()
+ //{
+ // Id = YitIdHelper.NextId(),
+ // DotMatrixNoteJournalId = input.JournalId,
+ // WidthMilliMeter = Journal.Width,
+ // HightMilliMeter = Journal.Height,
+ //};
+
+ var pageTemp = new JournalPage()
+ {
+ JournalId = input.JournalId,
+ JournalCatalogId = input.JournalCatalogId,
+ //DotMatrixPageId = dotMatrixpage.Id,
+ Url = input.Url,
+ PageNum = input.PageNum,
+ };
+
+ //dotMatrixpages.Add(dotMatrixpage);
+ pages.Add(pageTemp);
+
+ await UseTranAsync(async () =>
+ {
+ //using var uow = Context.Ado.BeginTran();
+ var pageData = await base.InsertRangeAsync(pages);
+ BusinessException.ThrowIf(pageData.IsNull(), "创建页失败");
+
+ //var result = await dotMatrixPageRepository.InsertRangeAsync(dotMatrixpages);
+ //BusinessException.ThrowIf(result, "创建点阵页失败");
+ });
+
+ return pages.Count; // 返回主目录ID
+ }
+
+ public async Task UpdateAsync(PageLayoutInput input)
+ {
+ var page = await Queryable().Where(w => w.Id == input.Id).FirstAsync();
+ BusinessException.ThrowIf(page == null, "不存在此页");
+
+ var Journal = await JournalRepository.GetByIdAsync(page.JournalId);
+ BusinessException.ThrowIf(Journal == null, "不存在此书");
+
+ BusinessException.ThrowIf(Journal?.Status == (int)JournalStatusEnum.Archive, "书籍已归档不能修改");
+ var transResult = await UseTranAsync(async () =>
+ {
+ //await dotMatrixPageRepository.Updateable().SetColumns(s => s.Area == input.Layout)
+ // .SetColumns(s => s.AreaPoints == dotMatrixPage.Area)
+ // .SetColumns(s => s.WidthMilliMeter == dotMatrixPage.WidthMilliMeter)
+ // .SetColumns(s => s.HightMilliMeter == dotMatrixPage.HightMilliMeter)
+ // .SetColumns(s => s.WidthDotMatrix == dotMatrixPage.WidthDotMatrix)
+ // .SetColumns(s => s.HightDotMatrix == dotMatrixPage.HightDotMatrix)
+ // .Where(w => w.Id == page.DotMatrixPageId).ExecuteCommandAsync();
+
+ page.Layout = input.Layout;
+ base.Update(page);
+
+ input.TasksImages?.ForEach(it =>
+ {
+ var key = $"Journal/{Journal.Id}/{input.Id}/{it.TaskId}/qustion.{it.Url.ToExtension()}";
+ ossService.CopyObject(it.Url.RemoveDomain(), key);
+ JournalPageTaskRepository.Updateable().SetColumns(s => s.TaskUrl, key).Where(w => w.Id == it.TaskId).ExecuteCommand();
+ });
+ return true;
+ });
+ return transResult;
+ }
+
+ ///
+ /// 修改书页的点阵码
+ ///
+ ///
+ ///
+ public async Task UpdatePageNoAsync(long JournalId)
+ {
+ //这里不能修改 Exchange = "icr.direct" 否则会报错
+ var msRes = await rabbitMqService.SendAsync(new RabbitMQSendParam { Exchange = "icr.direct", Queue = "mq.Journal.updatePageNo.queue", RoutingKey = "mq.Journal.updatePageNo", Data = JournalId });//发生消息
+ return msRes;
+ }
+
+ ///
+ /// 打印书页(全部)
+ ///
+ ///
+ ///
+ public async Task PrintJournalPageAsync(long JournalId)
+ {
+ //这里不能修改 Exchange = "icr.direct" 否则会报错
+ var msRes = await rabbitMqService.SendAsync(new RabbitMQSendParam { Exchange = "icr.direct", Queue = "mq.Journal.printJournalPage.queue", RoutingKey = "mq.Journal.printJournalPage", Data = JournalId });//发生消息
+ return msRes;
+ }
+
+ public async Task DetailAsync(long id)
+ {
+ var output = await Queryable()
+ //.LeftJoin((a, b) => a.DotMatrixPageId == b.Id)
+ .Where(a => a.Id == id)
+ .Select(a => new JournalPageV2Output
+ {
+ JournalId = a.JournalId,
+ JournalCatalogId = a.JournalCatalogId,
+ JournalPageId = a.Id,
+ Url = a.Url,
+ Layout = a.Layout,
+ PageNum = a.PageNum,
+ })
+ .FirstAsync();
+
+ if (output != null)
+ {
+ output.Tasks = await JournalPageTaskRepository.Queryable()//.Select()
+ .Where(a => a.JournalPageId == output.JournalPageId)
+ .Select(a => new JournalPageTaskV2Output
+ {
+ Id = a.Id,
+ No = a.No,
+ Type = (TaskBankTypeEnum)a.Type,
+ Options = a.Options,
+ Answers = a.Answer,
+ Analysis = a.Analysis,
+ //Assign = SqlFunc.Subqueryable().Where(m => a.Id == m.JournalPageTaskId).Any()
+ })
+ .ToListAsync();
+ }
+ //if (output?.Areas != null)
+ // output.Areas = await _JournalPageOtherRepository.Queryable().Where(w => w.JournalPageId == output.JournalPageId).Select().ToListAsync();
+
+ return output;
+ }
+
+ public async Task DeleteAsync(long id)
+ {
+ return await Deleteable().Where(d => d.Id == id).ExecuteCommandAsync() > 0;
+ }
+ //public async Task> PageNoArticleAsync(long JournalId)
+ //{
+ // return await Queryable().Where(d => d.JournalId == JournalId)
+ // .Where(w => w.JournalArticleId == null)
+ // .ToListAsync(x => new JournalPageNoArticleOutput()
+ // {
+ // Id = x.Id,
+ // PageUrl = x.Url,
+ // PageNum = x.PageNum
+ // });
+ //}
+
+ private int MillimeterToDotMatrixUnit(float millimeterValue)
+ {
+ return (int)(millimeterValue * 8 / 0.3);
+ }
+}
+
+public class JournalDotPage_Task
+{
+ public int X { get; set; }
+ public int Y { get; set; }
+ public int W { get; set; }
+ public int H { get; set; }
+}
+
+public class JournalDotPage_Answer
+{
+ public int X { get; set; }
+ public int Y { get; set; }
+ public int W { get; set; }
+ public int H { get; set; }
+}
+
+public class JournalDotPage_Area
+{
+ public JournalDotPage_Task Task { get; set; }
+
+ public List AnswerList { get; set; }
+
+ public long TaskId { get; set; }
+
+}
diff --git a/QYZH.InteractiveMagazine.Service/JournalPageTaskService.cs b/QYZH.InteractiveMagazine.Service/JournalPageTaskService.cs
new file mode 100644
index 0000000..c6532ac
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Service/JournalPageTaskService.cs
@@ -0,0 +1,176 @@
+using Mapster;
+using QYZH.InteractiveMagazine.Common.Extensions;
+using QYZH.InteractiveMagazine.Infrastructure.OSS;
+using QYZH.InteractiveMagazine.IService;
+using QYZH.InteractiveMagazine.Models.Common;
+using QYZH.InteractiveMagazine.Models.Dto.Journal;
+using QYZH.InteractiveMagazine.Models.Entity;
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.Repository;
+using SqlSugar;
+using Yitter.IdGenerator;
+
+
+namespace QYZH.InteractiveMagazine.Service;
+
+public class JournalPageTaskService(BaseRepository journalRepository, OssService ossService) : BaseRepository, IJournalPageTaskService
+{
+ public async Task InsertAsync(JournalPageTaskAddInput input)
+ {
+ var JournalStatus = await journalRepository.Queryable().Where(w => w.Id == input.JournalId).Select(s => s.Status).FirstAsync();
+ BusinessException.ThrowIf(JournalStatus == (int)JournalStatusEnum.Archive, "书籍已归档不能添加题目");
+
+ var map = input.Adapt();
+ map.Id = YitIdHelper.NextId();
+ map.GroupId = map.Id;
+ var no = input.No.Split('-').Select(int.Parse).ToArray();
+ if (no.Last() >= 2)
+ {
+ var startNo = $"{no[0]}-{no[1]}-{no[2]}-1";
+ var groupTask = await Queryable().Where(w => w.No == startNo && w.JournalId == input.JournalId).FirstAsync();
+ BusinessException.ThrowIf(groupTask.IsNull(), "未找到关联跨页的第一部分");
+ map.GroupId = groupTask.GroupId;
+ map.Score = groupTask.Score;
+ map.AnswerTime = groupTask.AnswerTime;
+ map.Type = groupTask.Type;
+ }
+
+ var data = await base.InsertAsync(map);
+ return map?.Id;
+ }
+
+
+ public async Task UpdateAsync(JournalPageTaskUpdateInput input)
+ {
+ var JournalStatus = await journalRepository.Queryable().Where(w => w.Id == input.JournalId).Select(s => s.Status).FirstAsync();
+ BusinessException.ThrowIf(JournalStatus == (int)JournalStatusEnum.Archive, "书籍已归档不能修改题目");
+
+ var task = await base.Queryable().Where(w => w.Id == input.Id).FirstAsync();
+ BusinessException.ThrowIf(task.IsNull(), "未找到关联的题号");
+
+ var sameTask = await base.Queryable().Where(w => w.No == input.No && w.JournalId == input.JournalId).FirstAsync();
+ BusinessException.ThrowIf(sameTask.IsNotEmpty(), "已经存在相同题号题目");
+
+
+ var key = $"Journal/{task.JournalId}/{task.JournalPageId}/{task.GroupId}";
+ //if (task.AnswerUrl != input.AnswerUrl && input.AnswerUrl.NotNull())
+ //{
+ // ossService.CopyObject(input.AnswerUrl.RemoveDomain(), $"{key}/answer.{input.AnswerUrl.ToExtension()}");
+ // input.AnswerUrl = $"{key}/answer.{input.AnswerUrl.ToExtension()}";
+ //}
+ //if (task.AnalysisUrl != input.AnalysisUrl && input.AnalysisUrl.NotNull())
+ //{
+ // ossService.CopyObject(input.AnalysisUrl.RemoveDomain(), $"{key}/analysis.{input.AnalysisUrl.ToExtension()}");
+ // input.AnalysisUrl = $"{key}/analysis.{input.AnalysisUrl.ToExtension()}";
+ //}
+ //if (task.VideoUrl != input.VideoUrl && input.VideoUrl.NotNull())
+ //{
+ // ossService.CopyObject(input.VideoUrl.RemoveDomain(), $"{key}/video.{input.VideoUrl.ToExtension()}");
+ // input.VideoUrl = $"{key}/video.{input.VideoUrl.ToExtension()}";
+ //}
+
+ input.Task = Deal(task.Task, input.Task, key, "task");
+ input.Answer = Deal(task.Answer, input.Answer, key, "answer");
+ input.Analysis = Deal(task.Analysis, input.Analysis, key, "analysis");
+
+ task.Type = input.Type;
+ task.Task = input.Task;
+ task.Options = input.Options;
+ task.Answer = input.Answer;
+ //task.AnswerUrl = input.AnswerUrl;
+ task.Analysis = input.Analysis;
+ //task.AnalysisUrl = input.AnalysisUrl;
+ task.Score = input.Score;
+ task.AnswerTime = input.AnswerTime;
+ // task.VideoUrl = input.VideoUrl;
+ task.No = input.No;
+ var no = input.No.Split('-').Select(int.Parse).ToArray();
+ if (no.Last() >= 2)
+ {
+ var startNo = $"{no[0]}-{no[1]}-{no[2]}-1";
+ var groupTask = await Queryable().Where(w => w.No == startNo && w.JournalId == input.JournalId).FirstAsync();
+ BusinessException.ThrowIf(groupTask.IsNull(), "未找到关联跨页的第一部分");
+ task.GroupId = groupTask.GroupId;
+ task.Score = groupTask.Score;
+ task.AnswerTime = groupTask.AnswerTime;
+ task.Type = groupTask.Type;
+ }
+ //task.JournalId = input.JournalId;
+ //task.JournalPageId = input.JournalPageId;
+ // 这里不能直接用Set,因为jsonmap无法赋值 不知道为啥
+ return await base.Updateable(task).ExecuteCommandAsync() > 0;
+ }
+
+ public async Task KeywordAnalysisAsync(JournalPageTaskKeywordAnalysis input)
+ {
+ return await base.Updateable().SetColumns(s => s.KeywordAnalysis, input.KeywordAnalysis).Where(w => w.GroupId == input.Id).ExecuteCommandAsync() > 0;
+ }
+
+ public async Task DetailAsync(long id)
+ {
+ var data = await base.Queryable()
+ .Where(w => w.Id == id)
+ .Select(w => new JournalPageTaskOutput()
+ , true).FirstAsync();
+ return data;
+
+ }
+
+ public async Task DeleteAsync(long id)
+ {
+ var task = await base.Queryable().Where(w => w.Id == id).FirstAsync();
+ BusinessException.ThrowIf(task.IsNull(), "问题不存在");
+
+ var key = $"Journal/{task.JournalId}/{task.JournalPageId}/{id}";
+
+ ossService.DeleteObject(key);
+
+ return await base.DeleteAsync(d => d.Id == id);
+ }
+
+ public async Task ComplementAsync(JournalPageTaskComplementInput input)
+ {
+ var task = await base.Queryable().Where(w => w.Id == input.Id).FirstAsync();
+ BusinessException.ThrowIf(task.IsNull(), "问题不存在");
+
+ if (input.AudioUrl.NotNull())
+ {
+ var key = $"Journal/{task.JournalId}/{task.JournalPageId}/{task.Id}/audio.{input.AudioUrl.ToExtension()}";
+ ossService.CopyObject(input.AudioUrl, key);
+ input.AudioUrl = key;
+ }
+ if (input.PointsUrl.NotNull())
+ {
+ var key = $"Journal/{task.JournalId}/{task.JournalPageId}/{task.Id}/points.json";
+ ossService.CopyObject(input.PointsUrl, key);
+ input.PointsUrl = key;
+ }
+ return await Updateable(input.Adapt()).Where(w => w.Id == input.Id).ExecuteCommandAsync() > 0;
+ }
+
+ private string Deal(string oldUrls, string newUrls, string key, string type)
+ {
+ // 如果 newUrls 为空,则直接返回
+ if (newUrls.IsNull()) return null;
+
+ // 处理 oldUrls 为 null 的情况,将其视为空字符串
+ var oldTaskUrls = oldUrls.AllUrl().Distinct();
+ var taskUrls = newUrls.AllUrl().Distinct().Select(s => s.RemoveDomain());
+
+ // 删除不存在的(存在于旧集合但不在新集合中)
+ var removedUrls = oldTaskUrls.Except(taskUrls).ToList();
+ ossService.DeleteObjects(removedUrls);
+
+ // 添加新增的(存在于新集合但不在旧集合中)
+ var addedUrls = taskUrls.Except(oldTaskUrls).ToList();
+ foreach (var item in addedUrls)
+ {
+ var value = $"{key}/{type}/{YitIdHelper.NextId()}.{item.ToExtension()}";
+ ossService.CopyObject(item, value);
+
+ newUrls = newUrls.Replace(item, value);
+
+ }
+ return newUrls;
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Service/JournalService.cs b/QYZH.InteractiveMagazine.Service/JournalService.cs
new file mode 100644
index 0000000..594e38b
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Service/JournalService.cs
@@ -0,0 +1,313 @@
+using Mapster;
+using Microsoft.Extensions.Logging;
+using QYZH.InteractiveMagazine.Common.Extensions;
+using QYZH.InteractiveMagazine.Infrastructure.OSS;
+using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
+using QYZH.InteractiveMagazine.IService;
+using QYZH.InteractiveMagazine.Models.Common;
+using QYZH.InteractiveMagazine.Models.Dto;
+using QYZH.InteractiveMagazine.Models.Dto.DotMatrix;
+using QYZH.InteractiveMagazine.Models.Dto.Journal;
+using QYZH.InteractiveMagazine.Models.Entity;
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.Repository;
+using SqlSugar;
+using Yitter.IdGenerator;
+
+namespace QYZH.InteractiveMagazine.Service;
+
+public class JournalService(BaseRepository JournalPageRepository,
+ BaseRepository JournalPageTaskRepository,
+ BaseRepository dotFileRepository,
+ BaseRepository dotFileDetailRepository, OssService ossService, ILogger logger) : BaseRepository, IJournalService
+{
+ ///
+ /// 查询List
+ ///
+ ///
+ ///
+ public async Task> GetListAsync(JournalQueryDto dto)
+ {
+ var query = await Queryable()
+ .WhereIF(!string.IsNullOrWhiteSpace(dto.Name), a => a.Name.Contains(dto.Name))
+ .WhereIF(dto.Status.HasValue, a => a.Status == (int)dto.Status)
+ .Select(a => new JournalDto(), true)
+ .ToListAsync();
+
+ return query;
+ }
+
+ ///
+ /// 分页查询
+ ///
+ ///
+ ///
+ public async Task> GetPageListAsync(PageQueryModel search)
+ {
+ RefAsync totalNumber = 0;
+
+ var dataList = await Queryable()
+ //.InnerJoin((a, b) => a.Id == b.JournalId)
+ .WhereIF(!string.IsNullOrWhiteSpace(search.Params.Name), a => a.Name.Contains(search.Params.Name))
+ .WhereIF(search.Params.Status.HasValue, a => a.Status == (int)search.Params.Status)
+ .WhereIF(!string.IsNullOrWhiteSpace(search.Params.Name), a => a.Name.Contains(search.Params.Name))
+ .OrderByDescending(a => a.CreatedAt)
+ .Select(a => new JournalDto(), true)
+
+ .ToPageListAsync(search.PageIndex, search.PageSize, totalNumber);
+
+ return new PageListModel(dataList, search.PageIndex, search.PageSize, totalNumber);
+ }
+
+ ///
+ /// 编辑
+ ///
+ ///
+ ///
+ public async Task> EditAsync(JournalEditDto input)
+ {
+
+ var Journal = await base.GetByIdAsync(input.Id);
+ BusinessException.ThrowIf(Journal.IsNull(), "不存在的Id");
+ BusinessException.ThrowIf(Journal.Status == (int)JournalStatusEnum.Archive, "已归档不可编辑");
+ if (string.IsNullOrWhiteSpace(input.Cover))
+ {
+ Journal.Cover = null;
+ }
+ else if (input.Cover.RemoveDomain() != Journal.Cover)
+ {
+ var key = $"Journal/{Journal.Id}/cover.{input.Cover.ToExtension()}";
+ ossService.CopyObject(input.Cover.RemoveDomain(), key);
+ Journal.Cover = key;
+ }
+
+ if (string.IsNullOrWhiteSpace(input.BackCover))
+ {
+ Journal.BackCover = null;
+ }
+ else if (input.BackCover.RemoveDomain() != Journal.BackCover)
+ {
+ var backCoverkey = $"Journal/{Journal.Id}/backCover.{input.BackCover.ToExtension()}";
+ ossService.CopyObject(input.BackCover.RemoveDomain(), backCoverkey);
+ Journal.BackCover = backCoverkey;
+ }
+
+ if (string.IsNullOrWhiteSpace(input.PdfUrl))
+ {
+ Journal.PdfUrl = null;
+ }
+ else if (input.PdfUrl.RemoveDomain() != Journal.PdfUrl)
+ {
+ var pdfUrlkey = $"Journal/{Journal.Id}/Journal.{input.PdfUrl.ToExtension()}";
+ ossService.CopyObject(input.PdfUrl.RemoveDomain(), pdfUrlkey);
+ Journal.PdfUrl = pdfUrlkey;
+ }
+ Journal.Width = input.Width;
+ Journal.Height = input.Height;
+ Journal.Name = input.Name;
+ Journal.Title = input.Title;
+
+ var res = await UseTranAsync(async () =>
+ {
+ var result = await Context.Updateable(Journal).IgnoreColumns(c => c.Status).ExecuteCommandAsync();
+ return result > 0;
+ });
+
+
+
+ return BaseResponse.Success(res);
+ }
+
+
+ public async Task> Tasks(long id)
+ {
+ var data = await JournalPageTaskRepository.Queryable().Where(w => w.JournalId == id)
+ .Select(x => new JournalTaskOutput()
+ {
+ TaskId = x.Id,
+ TaskNo = x.No,
+ TaskSubType = x.Type
+ }).ToListAsync();
+ return data.OrderBy(x =>
+ {
+ var parts = x.TaskNo.Split('-').Select(int.Parse).ToArray();
+ return (parts[0], parts[1], parts[2], parts[3]); // 元组
+ }).ToList();
+ }
+
+ public async Task DetailAsync(long id)
+ {
+ var Journal = await base.Queryable().Where(w => w.Id == id).Select().FirstAsync();
+ return Journal;
+ }
+
+ public async Task DeleteAsync(List ids)
+ {
+ BusinessException.ThrowIf(!base.Queryable().Any(w => ids.Contains(w.Id)), "ID不存在");
+ BusinessException.ThrowIf(base.Queryable().Any(w => ids.Contains(w.Id) && w.Status == (int)JournalStatusEnum.Archive), "已归档不可删除");
+ var result = await UseTranAsync(async () =>
+ {
+ await base.DeleteAsync(d => ids.Contains(d.Id));
+ await JournalPageRepository.DeleteAsync(d => ids.Contains(d.JournalId));
+ await JournalPageTaskRepository.DeleteAsync(d => ids.Contains(d.JournalId));
+ return true;
+ });
+
+ //删除OSS上的Journal/{JournalId}文件夹
+ if (result)
+ {
+ foreach (var JournalId in ids)
+ {
+ var prefix = $"Journal/{JournalId}/";
+ var keys = ossService.ListObjects(prefix);
+ if (keys?.Count > 0)
+ {
+ ossService.DeleteObjects(keys);
+ }
+ }
+ }
+ return result;
+ }
+
+ ///
+ ///
+ ///
+ /// 书id
+ /// 起始页码
+ ///
+ public async Task StartPageAsync(long id, int index)
+ {
+ var oldIndex = await JournalPageRepository.Queryable().Where(w => w.JournalId == id && w.PageNum == 1).Select(x => x.Sort).FirstAsync();
+
+ // 如果位置没有变化,直接返回
+ if (index == oldIndex) return true;
+
+ if (index > oldIndex)
+ {
+ // 向后移动的情况
+ var x = index - oldIndex;
+ await JournalPageRepository.Updateable().SetColumns(s => s.PageNum, 0).Where(w => w.Sort < index).Where(w => w.JournalId == id).ExecuteCommandAsync();
+ await JournalPageRepository.Updateable().SetColumns(s => s.PageNum == s.PageNum - x).Where(w => w.Sort >= index).Where(w => w.JournalId == id).ExecuteCommandAsync();
+ }
+ else
+ {
+ // 向前移动的情况
+ //var x = oldIndex - input.Index;
+ await JournalPageRepository.Updateable().SetColumns(s => s.PageNum, 0).Where(w => w.Sort < index).Where(w => w.JournalId == id).ExecuteCommandAsync();
+
+ // 将目标页面设置为第一页
+ await JournalPageRepository.Updateable().SetColumns(s => s.PageNum == s.Sort - (index - 1)).Where(w => w.Sort >= index).Where(w => w.JournalId == id).ExecuteCommandAsync();
+ }
+
+ // await JournalPageRepository.Updateable().SetColumns(s => s.PageNum == s.Sort).Where(w => w.JournalId == input.Id).ExecuteCommandAsync();
+ return true;
+ }
+
+ public async Task PrintCodeAsync(long id)
+ {
+ var Journal = await base.Queryable().Where(w => w.Id == id).FirstAsync();
+ BusinessException.ThrowIf(Journal.IsNull(), "不存在书");
+ BusinessException.ThrowIf(Journal.Status == (int)JournalStatusEnum.Codeing, "正在生成中...");
+
+ var pages = await JournalPageRepository.Queryable().Where(w => w.JournalId == id).ToListAsync();
+ BusinessException.ThrowIf(pages.IsNull(), "不存在页");
+
+ var output = new DotMatrixOutput()
+ {
+ Method = "Journal",
+ Name = Journal.Name,
+ NoteId = id,
+ PdfUrl = Journal.PdfUrl
+ };
+
+
+ //铺码业务后续调整
+
+ //var exist = pages.All(c => c.PageNo.IsNull());
+ //if (!exist)
+ //{
+ // var dotIds = pages.Select(s => s.DotId).Distinct();
+ // var dots = await dotFileRepository.Queryable().Where(w => dotIds.Contains(w.Id)).ToListAsync();
+ // output.DotMatrixs = dots.Select(s => new DotMatrixMqOutput() { DotId = s.Id, FileAddress = s.FileAddress }).ToList();
+ // foreach (var page in pages)
+ // {
+ // var find = output.DotMatrixs.Find(f => f.DotId == page.DotId);
+ // find!.Pages.Add(new DotMatrixPageDto()
+ // {
+ // PageNo = page.PageNo,
+ // PageNum = 1
+ // });
+ // }
+
+ // var ok = await producingService.SendAsync(new RabbitMQSendParam() { Exchange = "icr.direct", RoutingKey = "mq.dotmatrix", Data = output });
+ // BusinessException.ThrowIf(!ok, "消息发送失败,生成失败");
+
+ // await base.Updateable().SetColumns(s => s.Status, JournalStatusEnum.Codeing).Where(w => w.Id == id).ExecuteCommandAsync();
+ //}
+ //else
+ //{
+ // //var first = await dotFileDetailRepository.Queryable().Where(w => w.IsUse == false).GroupBy(g => g.DotId).Having(h => h.Count() >= pages.Count).FirstAsync(a => new { DotId = a.Key, Count = a.Count() });
+ // var first = await dotFileDetailRepository.Queryable().Where(w => w.IsUse == false).GroupBy(g => g.DotId).Having(h => SqlFunc.AggregateCount(h.Id) >= pages.Count).Select(a => new { DotId = a.DotId, Count = SqlFunc.AggregateCount(a.Id) }).FirstAsync();
+ // BusinessException.ThrowIf(first.IsNull(), "点阵页码不足,生成失败");
+ // var dotFile = await dotFileRepository.Queryable().Where(w => w.Id == first.DotId).FirstAsync();
+ // if (dotFile != null)
+ // dotFile.Details = dotFileDetailRepository.Queryable().Where(w => !w.IsUse && w.DotId == dotFile.Id).Take(pages.Count()).ToList();
+ // var dotMatrix = new DotMatrixMqOutput()
+ // {
+ // DotId = dotFile.Id,
+ // FileAddress = dotFile.FileAddress,
+ // };
+ // output.DotMatrixs.Add(dotMatrix);
+
+ // foreach (var item in dotFile.Details)
+ // {
+ // var index = dotFile.Details.IndexOf(item);
+ // pages[index].PageNo = item.PageName;
+ // pages[index].DotId = dotFile.Id;
+
+ // dotMatrix.Pages.Add(new DotMatrixPageDto()
+ // {
+ // PageNo = item.PageName,
+ // PageNum = 1
+ // });
+ // }
+ // await UseTranAsync(async () =>
+ // {
+ // // 更新使用数量
+ // //await dotFileRepository.Updateable().SetColumns(s => s.TotalUse, dotFile.TotalUse + pages.Count).Where(w => w.Id == dotFile.Id).ExecuteCommandAsync();
+ // // 更新使用
+ // await dotFileDetailRepository.Updateable().SetColumns(s => s.IsUse, true).Where(w => dotFile.Details.Select(s => s.Id).Contains(w.Id)).ExecuteCommandAsync();
+ // // 更新页码
+ // await dotMatrixPageRepository.UpdateRangeAsync(pages);
+ // await Updateable().SetColumns(s => s.Status, JournalStatusEnum.CodeSuccess).Where(w => w.Id == id).ExecuteCommandAsync();
+ // //发送消息
+ // //var ok = await producingService.SendAsync(new RabbitMQSendParam() { Exchange = "icr.direct", RoutingKey = "mq.dotmatrix", Data = output });
+ // //BusinessException.ThrowIf(!ok, "消息发送失败,生成失败");
+ // });
+ //}
+
+ return output;
+ }
+
+ public async Task ResultReportAsync(DotMatrixNoteJournalReportInput input)
+ {
+ if (input.FileUrl.IsNull() && input.FileKey.IsNull())
+ {
+ return await Updateable().SetColumns(s => s.Status, input.Success ? JournalStatusEnum.CodeSuccess : JournalStatusEnum.CodeFail)
+ .SetColumns(s => s.PdfPreviewUrl, input.FileKey)
+ .Where(w => w.Id == input.Id)
+ .ExecuteCommandAsync() > 0;
+ }
+ return await Updateable().SetColumns(s => s.Status, JournalStatusEnum.CodeFail).Where(w => w.Id == input.Id).ExecuteCommandAsync() > 0;
+ }
+
+ public async Task StatusAsync(long id, JournalStatusEnum status)
+ {
+ var book = await base.GetByIdAsync(id);
+ var tasks = await Context.Queryable().Where(w => w.JournalId == id).ToListAsync();
+ BusinessException.ThrowIf(tasks.Count == 0 && status == JournalStatusEnum.Archive, "未添加任何题目,无法归档");
+ BusinessException.ThrowIf(tasks.Any(a => string.IsNullOrWhiteSpace(a.TaskUrl)) && status == JournalStatusEnum.Archive, $"{string.Join(',', tasks.Where(a => string.IsNullOrWhiteSpace(a.TaskUrl)).Select(a => a.No).ToList())}未保存,无法归档");
+ var res = await base.Updateable().SetColumns(s => s.Status, status).Where(w => w.Id == id).ExecuteCommandAsync() > 0;
+ return res;
+ }
+}
\ No newline at end of file
diff --git a/QYZH.InteractiveMagazine.Service/PetService.cs b/QYZH.InteractiveMagazine.Service/PetService.cs
index 94889d1..bc6f01c 100644
--- a/QYZH.InteractiveMagazine.Service/PetService.cs
+++ b/QYZH.InteractiveMagazine.Service/PetService.cs
@@ -253,79 +253,103 @@ public class PetService(
throw new BusinessException("宠物未激活,无法喂养", 400);
}
+ // 事务保证一致性
+ await UseTranAsync(async () =>
+ {
+ result = await FeedPetInTranAsync(userId, input);
+ });
+
+ logger.LogInformation("喂养宠物成功,PetId: {PetId}, 成长值: {Before} -> {After}, 进化: {HasEvolved}",
+ input.PetId, result.GrowthBefore, result.GrowthAfter, result.HasEvolved);
+
+ return result;
+ }
+
+ ///
+ /// 喂养宠物(无事务,需在外部事务中调用)
+ ///
+ public async Task FeedPetInTranAsync(long userId, FeedPetInput input)
+ {
+ // 查询宠物
+ var pet = await petRepository.GetByIdAsync(input.PetId);
+ if (pet == null || pet.IsDeleted)
+ throw new BusinessException("宠物不存在", 404);
+
+ if (pet.UserId != userId)
+ throw new BusinessException("无权操作该宠物", 403);
+
+ if (pet.Status != (int)UserPetStatusEnum.Active)
+ throw new BusinessException("宠物未激活,无法喂养", 400);
+
var growthBefore = pet.GrowthPoints;
var growthAfter = growthBefore + input.GrowthPoints;
var hasEvolved = false;
string? evolvedStageName = null;
- // 事务保证一致性
- await UseTranAsync(async () =>
+ // 累加成长值和喂养次数
+ var updateResult = await petRepository.Context.Updateable()
+ .SetColumns(p => p.GrowthPoints == growthAfter)
+ .SetColumns(p => p.FeedingCount == p.FeedingCount + 1)
+ .SetColumns(p => p.UpdatedAt == DateTime.Now)
+ .SetColumns(p => p.UpdatedBy == userId.ToString())
+ .Where(p => p.Id == input.PetId)
+ .ExecuteCommandAsync();
+
+ if (updateResult <= 0)
{
- // 累加成长值和喂养次数
- var updateResult = await petRepository.Context.Updateable()
- .SetColumns(p => p.GrowthPoints == growthAfter)
- .SetColumns(p => p.FeedingCount == p.FeedingCount + 1)
+ throw new BusinessException("更新宠物成长值失败", 500);
+ }
+
+ // 进化检查:查找下一阶段进化形态(PreviousEvolutionId 类型为 long?)
+ var nextEvolution = await petEvolutionRepository.Queryable()
+ .Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
+ && e.RequiredGrowth <= growthAfter
+ && e.Status == (int)DefaultStatusEnum.Active)
+ .OrderBy(e => e.RequiredGrowth, OrderByType.Desc)
+ .FirstAsync();
+
+ if (nextEvolution != null)
+ {
+ // 触发进化
+ var evolveResult = await petRepository.Context.Updateable()
+ .SetColumns(p => p.CurrentEvolutionId == nextEvolution.Id)
.SetColumns(p => p.UpdatedAt == DateTime.Now)
.SetColumns(p => p.UpdatedBy == userId.ToString())
.Where(p => p.Id == input.PetId)
.ExecuteCommandAsync();
- if (updateResult <= 0)
+ if (evolveResult > 0)
{
- throw new BusinessException("更新宠物成长值失败", 500);
+ hasEvolved = true;
+ evolvedStageName = nextEvolution.StageName;
+ logger.LogInformation("宠物进化成功,PetId: {PetId}, 新形态: {StageName} (Level {StageLevel})",
+ input.PetId, nextEvolution.StageName, nextEvolution.StageLevel);
}
+ }
- // 进化检查:查找下一阶段进化形态(PreviousEvolutionId 类型为 long?)
- var nextEvolution = await petEvolutionRepository.Queryable()
- .Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
- && e.RequiredGrowth <= growthAfter
- && e.Status == (int)DefaultStatusEnum.Active)
- .OrderBy(e => e.RequiredGrowth, OrderByType.Desc)
- .FirstAsync();
+ // 写入喂养记录
+ var record = new PetFeedingRecord
+ {
+ PetId = input.PetId,
+ UserId = userId,
+ PointsUsed = 0, // 预留:后期可扩展为消耗积分喂养
+ GrowthChange = input.GrowthPoints,
+ GrowthBefore = growthBefore,
+ GrowthAfter = growthAfter,
+ Type = PetFeedingRecordTypeEnum.Normal,
+ Status = (int)PetFeedingRecordStatusEnum.Success,
+ IsDeleted = false,
+ CreatedBy = userId.ToString(),
+ CreatedAt = DateTime.Now,
+ UpdatedBy = userId.ToString(),
+ UpdatedAt = DateTime.Now
+ };
- if (nextEvolution != null)
- {
- // 触发进化
- var evolveResult = await petRepository.Context.Updateable()
- .SetColumns(p => p.CurrentEvolutionId == nextEvolution.Id)
- .SetColumns(p => p.UpdatedAt == DateTime.Now)
- .SetColumns(p => p.UpdatedBy == userId.ToString())
- .Where(p => p.Id == input.PetId)
- .ExecuteCommandAsync();
-
- if (evolveResult > 0)
- {
- hasEvolved = true;
- evolvedStageName = nextEvolution.StageName;
- logger.LogInformation("宠物进化成功,PetId: {PetId}, 新形态: {StageName} (Level {StageLevel})",
- input.PetId, nextEvolution.StageName, nextEvolution.StageLevel);
- }
- }
-
- // 写入喂养记录
- var record = new PetFeedingRecord
- {
- PetId = input.PetId,
- UserId = userId,
- PointsUsed = 0, // 预留:后期可扩展为消耗积分喂养
- GrowthChange = input.GrowthPoints,
- GrowthBefore = growthBefore,
- GrowthAfter = growthAfter,
- Type = PetFeedingRecordTypeEnum.Normal,
- Status = (int)PetFeedingRecordStatusEnum.Success,
- IsDeleted = false,
- CreatedBy = userId.ToString(),
- CreatedAt = DateTime.Now,
- UpdatedBy = userId.ToString(),
- UpdatedAt = DateTime.Now
- };
-
- var insertResult = await feedingRecordRepository.InsertAsync(record);
- if (!insertResult)
- {
- throw new BusinessException("写入喂养记录失败", 500);
- }
- });
+ var insertResult = await feedingRecordRepository.InsertAsync(record);
+ if (!insertResult)
+ {
+ throw new BusinessException("写入喂养记录失败", 500);
+ }
logger.LogInformation("喂养宠物成功,PetId: {PetId}, 成长值: {Before} -> {After}, 进化: {HasEvolved}",
input.PetId, growthBefore, growthAfter, hasEvolved);
diff --git a/QYZH.InteractiveMagazine.Service/UsersService.cs b/QYZH.InteractiveMagazine.Service/UsersService.cs
index 445ea48..8059902 100644
--- a/QYZH.InteractiveMagazine.Service/UsersService.cs
+++ b/QYZH.InteractiveMagazine.Service/UsersService.cs
@@ -158,7 +158,7 @@ public class UsersService(
if (journalDict.TryGetValue(item.JournalId, out var journal))
{
item.JournalTitle = journal.Title;
- item.CoverImageUrl = journal.CoverImageUrl;
+ item.CoverImageUrl = journal.Cover;
}
}
}
diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs
index 734a48c..100eaf0 100644
--- a/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs
+++ b/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs
@@ -1,8 +1,12 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using MiniExcelLibs;
+using QYZH.InteractiveMagazine.Infrastructure.Context;
+using QYZH.InteractiveMagazine.Models.Common;
using QYZH.InteractiveMagazine.Models.Dto;
using QYZH.InteractiveMagazine.Models.Enum;
using System.Security.Claims;
+using System.Web;
namespace QYZH.InteractiveMagazine.WebApi.Controllers;
@@ -75,4 +79,57 @@ public abstract class BaseController : ControllerBase
{
return Ok(BaseResponse.Fail(msg));
}
+
+ ///
+ /// 导出Excel
+ ///
+ /// 完整文件路径
+ /// 带扩展文件名
+ ///
+ protected IActionResult ExportExcel(string path, string fileName)
+ {
+ //var webHostEnvironment = App.WebHostEnvironment;
+ if (!Path.Exists(path))
+ {
+ throw new BusinessException(fileName + "文件不存在");
+ }
+ var stream = System.IO.File.OpenRead(path); //创建文件流
+
+ Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition");
+ return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", HttpUtility.UrlEncode(fileName));
+ }
+
+ protected (string, string) DownloadImportTemplate(List list, string fileName)
+ {
+ IWebHostEnvironment webHostEnvironment = ServiceContext.GetService();
+ string sFileName = $"{fileName}.xlsx";
+ string fullPath = Path.Combine(webHostEnvironment.WebRootPath, "ImportTemplate", sFileName);
+
+ //不存在模板创建模板
+ if (!Directory.Exists(fullPath))
+ {
+ var directoryPath = Path.GetDirectoryName(fullPath);
+ if (directoryPath != null)
+ {
+ Directory.CreateDirectory(directoryPath);
+ }
+ }
+ if (!Path.Exists(fullPath))
+ {
+ MiniExcel.SaveAs(fullPath, list, overwriteFile: true);
+ }
+ return (sFileName, fullPath);
+ }
+
+ protected (string, string) ExportExcelMini(List list, string sheetName, string fileName)
+ {
+ IWebHostEnvironment webHostEnvironment = ServiceContext.GetService();
+ string sFileName = $"{fileName}_{DateTime.Now:MMdd_HHmmss}.xlsx";
+ string fullPath = Path.Combine(webHostEnvironment.WebRootPath, "export", sFileName);
+
+ Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
+
+ MiniExcel.SaveAs(fullPath, list, sheetName: sheetName);
+ return (sFileName, fullPath);
+ }
}
diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/JournalController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/JournalController.cs
new file mode 100644
index 0000000..c37c484
--- /dev/null
+++ b/QYZH.InteractiveMagazine.WebApi/Controllers/JournalController.cs
@@ -0,0 +1,559 @@
+
+using QYZH.InteractiveMagazine.Models.Base;
+using QYZH.InteractiveMagazine.Models.Dto.Journal;
+using QYZH.InteractiveMagazine.Models.Dto.DotMatrix;
+using QYZH.InteractiveMagazine.Models.Enum;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using MiniExcelLibs;
+using QYZH.InteractiveMagazine.Infrastructure.OSS;
+using QYZH.InteractiveMagazine.IService;
+using QYZH.InteractiveMagazine.Models.Common;
+using QYZH.InteractiveMagazine.Models.Dto;
+using QYZH.InteractiveMagazine.Models.Enum;
+using QYZH.InteractiveMagazine.WebApi.Controllers;
+using System.ComponentModel.DataAnnotations;
+using System.Web;
+
+
+namespace QYZH.InteractiveMagazine.WebApi.Controllers
+{
+ ///
+ /// 期刊管理
+ ///
+ [ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))]
+ [Route("api/icr")]
+ public class JournalController(
+ OssService ossService,
+ IJournalCatalogService JournalCatalogService,
+ IJournalService JournalService,
+ IJournalPageService JournalPageService,
+ IJournalPageTaskService JournalPageTaskService,
+ IHttpClientFactory httpClientFactory,
+ Microsoft.Extensions.Configuration.IConfiguration configuration) : BaseController
+ {
+
+ #region 制书
+
+ ///
+ /// 根据ID查询书籍数据
+ ///
+ ///
+ ///
+ [HttpGet, Route("Journal/detail/{id:long}")]
+ public async Task> GetByIdAsync([Required] long id)
+ {
+ var result = await JournalService.GetByExpressionAsync(c => c.Id == id);
+ return BaseResponse.Success(result);
+ }
+
+ ///
+ /// 分页查询书籍列表
+ ///
+ /// 查询条件
+ ///
+ [HttpPost, Route("Journal/pagelist")]
+ public async Task>> GetPageListAsync([FromBody] PageQueryModel search)
+ {
+ var result = await JournalService.GetPageListAsync(search);
+ return BaseResponse>.Success(result);
+ }
+
+ ///
+ /// 查询书籍列表
+ ///
+ ///
+ ///
+ [HttpPost, Route("Journal/list")]
+ public async Task>> GetListAsync([FromQuery] JournalQueryDto dto)
+ {
+ var result = await JournalService.GetListAsync(dto);
+ return BaseResponse>.Success(result);
+ }
+
+ /////
+ ///// 创建书籍
+ /////
+ ///// 书籍对象
+ /////
+ //[HttpPost, Route("Journal/add")]
+ //public async Task> AddAsync([FromBody] JournalEditDto dto)
+ //{
+ // var result = await JournalService.AddAsync(dto);
+ // return result;
+ //}
+
+ ///
+ /// 编辑书籍
+ ///
+ /// 书籍对象
+ ///
+ [HttpPost, Route("Journal/update")]
+ public async Task Edit([FromBody] JournalEditDto dto)
+ {
+ var result = await JournalService.EditAsync(dto);
+ return result;
+ }
+
+ ///
+ /// 批量删除
+ ///
+ ///
+ ///
+ [HttpPost, Route("Journal/delete")]
+ public async Task> DeleteAsync([Required][FromBody] List ids)
+ {
+ var result = await JournalService.DeleteAsync(ids);
+ return BaseResponse.Success(result);
+ }
+
+ ///
+ /// 书籍起始页
+ ///
+ ///
+ [HttpPost, Route("Journal/startpage")]
+ public async Task> StartPageAsync([FromQuery]long id, [FromQuery] int index)
+ {
+ var data = await JournalService.StartPageAsync(id, index);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 书籍归档
+ ///
+ ///
+ [HttpPost, Route("Journal/rchive/{id:long}")]
+ public async Task> Archive(long id)
+ {
+ var data = await JournalService.StatusAsync(id, JournalStatusEnum.Archive);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 书籍废弃
+ ///
+ ///
+ [HttpPost]
+ [HttpPost, Route("Journal/abandon/{id:long}")]
+ public async Task> Abandon(long id)
+ {
+ var data = await JournalService.StatusAsync(id, JournalStatusEnum.Abandoned);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 书籍铺码
+ ///
+ ///
+ [HttpPost, Route("Journal/printcode/{id:long}")]
+ public async Task> PrintCodeAsync(long id)
+ {
+ var data = await JournalService.PrintCodeAsync(id);
+ return BaseResponse.Success(data);
+ }
+ ///
+ /// 上报铺码结果
+ ///
+ ///
+ ///
+ ///
+ [HttpPost, Route("Journal/resultreport")]
+ public async Task> GenerateAsync([FromBody] DotMatrixNoteJournalReportInput input, [FromForm] IFormFile? file)
+ {
+ if (file != null)
+ {
+ input.FileKey = $"Journal/{input.Id}/dotMatrixJournal.pdf";
+ input.FileUrl = ossService.PutObject(input.FileKey, file.OpenReadStream());
+ }
+ var data = await JournalService.ResultReportAsync(input);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 书籍导入
+ ///
+ ///
+ ///
+ [HttpPost, Route("Journal/import")]
+ public async Task> JournalImport(JournalImportDto input)
+ {
+ var data = await JournalCatalogService.ImportAsync(input);
+ return BaseResponse.Success(data);
+ }
+
+ /////
+ ///// 工单分页列表
+ /////
+ /////
+ /////
+ //[HttpPost, Route("tasklibrary/workorder/pagelist")]
+ //public async Task>> WorkOrderPageListAsync([FromBody] PageQueryModel search)
+ //{
+ // var data = await JournalWorkOrderService.WorkOrderPageListAsync(search);
+ // return BaseResponse>.Success(data);
+ //}
+
+ ///
+ /// 书籍发起工单
+ ///
+ ///
+ ///
+ //[HttpPost, Route("tasklibrary/workorder/create")]
+ //public async Task> CreateWorkOrderAsync([FromBody] IcrWorkOrderCreateDto dto)
+ //{
+ // var result = await JournalWorkOrderService.CreateWorkOrderAsync(dto);
+ // return result;
+ //}
+
+ /////
+ ///// 更新工单状态
+ /////
+ /////
+ /////
+ //[HttpPost, Route("tasklibrary/workorder/setstatus/{JournalId:long}")]
+ //public async Task> UpdateWorkOrderAsync(long JournalId)
+ //{
+ // var result = await JournalWorkOrderService.UpdateWorkOrderAsync(JournalId);
+ // return BaseResponse.Success(result);
+ //}
+
+ ///
+ /// 题库代理请求接口
+ ///
+ ///
+ [HttpGet, Route("tasklibrary/proxyget")]
+ public async Task GetTaskLibraryData(string apiUrl = "")
+ {
+ var client = httpClientFactory.CreateClient();
+ var response = await client.GetAsync(configuration["TaskLibraryApi"] + apiUrl);
+ // 确保请求成功(状态码 200)
+ response.EnsureSuccessStatusCode();
+ string result = await response.Content.ReadAsStringAsync();
+ return Content(result, "application/json");
+ }
+ #endregion
+
+
+ #region 书籍目录
+
+ /////
+ ///// 书籍目录详情
+ /////
+ /////
+ //[HttpGet, Route("catalog/tree/{JournalId:long}")]
+ //public async Task>> CatalogDetailAsync(long JournalId)
+ //{
+ // var data = await _JournalCatalogService.DetailAsync(JournalId);
+ // return BaseResponse>.Success(data);
+ //}
+
+ ///
+ /// 书籍目录详情
+ ///
+ ///
+ [HttpGet, Route("catalog/tree/{JournalId:long}")]
+ public async Task>> CatalogDetailAsync(long JournalId)
+ {
+ var data = await JournalCatalogService.GetJournalCataloTreeAsync(JournalId);
+ return BaseResponse>.Success(data);
+ }
+
+ ///
+ /// 获取书分类及页码
+ ///
+ ///
+ ///
+ [HttpGet, Route("catalog/list")]
+ public async Task>> GetJournalCatelogListAsync(long JournalId)
+ {
+ var data = await JournalCatalogService.GetJournalCatalogListAsync(JournalId);
+ return BaseResponse>.Success(data);
+ }
+
+ ///
+ /// 下载书籍目录导入模板
+ ///
+ ///
+ [HttpGet, Route("catalog/template")]
+ [AllowAnonymous]
+ public IActionResult ImportCatalogTemplateExcel()
+ {
+ var result = DownloadImportTemplate(new List() { }, "书籍目录导入模板");
+ return ExportExcel(result.Item2, result.Item1);
+ }
+
+ ///
+ /// 书籍目录导出
+ ///
+ ///
+ ///
+ [HttpGet, Route("catalog/export/{JournalId}")]
+ public async Task CatalogExport(long JournalId)
+ {
+ var list = await JournalCatalogService.GetJournalCatalogListAsync(JournalId);
+
+ var result = ExportExcelMini(list, "sheet1", "书籍目录导出");
+ return ExportExcel(result.Item2, result.Item1);
+ }
+
+ ///
+ /// 书籍目录导入
+ ///
+ /// file
+ /// file
+ ///
+ [HttpPost, Route("catalog/import/{JournalId}")]
+ public async Task> ImportAsync(long JournalId, [FromForm(Name = "file")] IFormFile file)
+ {
+ List dto = new();
+ using (var stream = file.OpenReadStream())
+ {
+ dto = stream.Query().ToList();
+ }
+ var result = await JournalCatalogService.ImportCatalogAsync(JournalId, dto);
+ return BaseResponse.Success(result);
+ }
+
+ ///
+ /// 书籍目录新增
+ ///
+ ///
+ ///
+ [HttpPost, Route("catalog/add")]
+ public async Task> CatalogAdd(JournalCatalogInput input)
+ {
+ var data = await JournalCatalogService.InsertAsync(input);
+ return BaseResponse.Success(data);
+ }
+ ///
+ /// 书籍目录修改
+ ///
+ ///
+ ///
+ [HttpPost, Route("catalog/update")]
+ public async Task> CatalogUpdate(JournalCatalogUpdateInput input)
+ {
+ var data = await JournalCatalogService.UpdateAsync(input);
+ return BaseResponse.Success(data);
+ }
+ ///
+ /// 书籍目录删除
+ ///
+ ///
+ ///
+ [HttpPost, Route("catalog/delete/{id:long}")]
+ public async Task> CatalogDelete(long id)
+ {
+ var data = await JournalCatalogService.DeleteAsync(id);
+ return BaseResponse.Success(data);
+ }
+
+ /////
+ ///// 书籍目录复制
+ /////
+ /////
+ /////
+ //[HttpPost, Route("catalog/copy")]
+ //[ProducesResponseType(typeof(BaseResponse), 200)]
+ //public async Task CatalogCopy(CopyInput input)
+ //{
+ // var data = await _JournalCatalogServices.CopyAsync(input);
+ // return ApiResult(data, "书籍目录删除失败!");
+ //}
+
+ ///
+ /// 书籍目录移动
+ ///
+ ///
+ ///
+ [HttpPost, Route("catalog/move")]
+ public async Task> CatalogMove(MoveInput input)
+ {
+ var data = await JournalCatalogService.MoveAsync(input);
+ return BaseResponse.Success(data);
+ }
+
+ #endregion
+
+
+ #region 书页
+ ///
+ /// 书页新增
+ ///
+ ///
+ [HttpPost, Route("page/add")]
+ public async Task> PageAdd(JournalAddV2Input input)
+ {
+ var data = await JournalPageService.InsertAsync(input);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 书页修改
+ ///
+ ///
+ [HttpPost, Route("page/update")]
+ public async Task> PageUpdate(PageLayoutInput input)
+ {
+ var data = await JournalPageService.UpdateAsync(input);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 自动铺码
+ ///
+ ///
+ [HttpPost, Route("page/updatepageno/{JournalId:long}")]
+ public async Task> PageUpdatePageNo([Required(ErrorMessage = "书籍编号不允许为空")] long JournalId)
+ {
+ var data = await JournalPageService.UpdatePageNoAsync(JournalId);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 下载书籍页码点阵码PDF文件名称
+ ///
+ ///
+ [HttpGet, Route("page/downloadJournalpagePdf/{JournalId:long}")]
+ public async Task DownloadJournalPagePdf([Required(ErrorMessage = "书籍编号不允许为空")] long JournalId)
+ {
+ var entity = await JournalService.GetFirstAsync(x => x.Id == JournalId);
+ if (entity == null)
+ throw new BusinessException("书籍不存在");
+
+ var downloadUrl = entity.DownloadJournalPagePdfName;
+ if (string.IsNullOrWhiteSpace(downloadUrl))
+ throw new BusinessException("下载点阵码PDF文件为空,请先铺码");
+
+ var stream = await httpClientFactory.CreateClient().GetStreamAsync(downloadUrl); //创建文件流
+ if (stream == null)
+ throw new BusinessException("下载点阵码PDF文件失败");
+
+ Response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition");
+
+ return File(stream, "application/pdf", HttpUtility.UrlEncode(Path.GetFileName(downloadUrl)));
+ }
+
+
+ /////
+ ///// 打印书页(全部) 暂时废弃这个方,直接使用 Adobe Reader X 软件打印就可以了
+ /////
+ /////
+ //[HttpPost, Route("page/printJournalPage/{JournalId:long}")]
+ //public async Task> PrintJournalPage([Required(ErrorMessage = "书籍编号不允许为空")] long JournalId)
+ //{
+ // var data = await JournalPageService.PrintJournalPageAsync(JournalId);
+ // return BaseResponse.Success(data);
+ //}
+
+
+ /////
+ ///// 书页删除
+ /////
+ /////
+ //[HttpPost, Route("page/delete/{id:long}")]
+ //[ProducesResponseType(typeof(BaseResponse), 200)]
+ //public async Task PageDelete(long id)
+ //{
+ // var data = await _JournalPageServices.DeleteAsync(id);
+ // return ApiResult(data, "更新书页失败!");
+ //}
+
+ ///
+ /// 书页详情
+ ///
+ ///
+ [HttpGet, Route("page/detail/{id:long}")]
+ public async Task> PageDetail(long id)
+ {
+ var data = await JournalPageService.DetailAsync(id);
+ return BaseResponse.Success(data);
+ }
+
+ /////
+ ///// 不包含有文章的书页
+ /////
+ /////
+ //[HttpGet, Route("page/no-article/{JournalId:long}")]
+ //[ProducesResponseType(typeof(BaseResponse>), 200)]
+ //public async Task PageNoArticleAsync(long JournalId)
+ //{
+ // var data = await _JournalPageService.PageNoArticleAsync(JournalId);
+ // return BaseResponse>(data, "查询书页失败!");
+ //}
+
+ #endregion
+
+ #region 书页问题
+ ///
+ /// 书页问题新增
+ ///
+ ///
+ [HttpPost, Route("page/task/add")]
+ [ProducesResponseType(typeof(BaseResponse), 200)]
+ public async Task> PageTaskAdd(JournalPageTaskAddInput input)
+ {
+ var data = await JournalPageTaskService.InsertAsync(input);
+ return BaseResponse.Success(data.Value);
+ }
+
+ ///
+ /// 书页问题单项解析
+ ///
+ ///
+ [HttpPost, Route("page/task/keyword-analysis")]
+ [ProducesResponseType(typeof(BaseResponse), 200)]
+ public async Task> KeywordAnalysisAsync(JournalPageTaskKeywordAnalysis input)
+ {
+ var data = await JournalPageTaskService.KeywordAnalysisAsync(input);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 书页问题更新
+ ///
+ ///
+ [HttpPost, Route("page/task/update")]
+ [ProducesResponseType(typeof(BaseResponse), 200)]
+ public async Task> PageTaskUpdate(JournalPageTaskUpdateInput input)
+ {
+ var data = await JournalPageTaskService.UpdateAsync(input);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 书页问题详情 + 题库那边需要用
+ ///
+ ///
+ [AllowAnonymous]
+ [HttpGet, Route("page/task/detail/{id:long}")]
+ public async Task> PageTaskDetail(long id)
+ {
+ var data = await JournalPageTaskService.DetailAsync(id);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 书页问题删除
+ ///
+ ///
+ [HttpPost, Route("page/task/delete/{id:long}")]
+ public async Task> PageTaskDelete(long id)
+ {
+ var data = await JournalPageTaskService.DeleteAsync(id);
+ return BaseResponse.Success(data);
+ }
+
+ ///
+ /// 书页问题补充
+ ///
+ ///
+ [HttpPost, Route("page/task/complement")]
+ public async Task> ComplementAnsync(JournalPageTaskComplementInput input)
+ {
+ var data = await JournalPageTaskService.ComplementAsync(input);
+ return BaseResponse.Success(data);
+ }
+ #endregion
+ }
+}
diff --git a/QYZH.InteractiveMagazine.WebApi/Program.cs b/QYZH.InteractiveMagazine.WebApi/Program.cs
index 2622f55..02d1683 100644
--- a/QYZH.InteractiveMagazine.WebApi/Program.cs
+++ b/QYZH.InteractiveMagazine.WebApi/Program.cs
@@ -25,6 +25,7 @@ using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerUI;
using System.Text.Json.Serialization;
using Yitter.IdGenerator;
+using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
var builder = WebApplication.CreateBuilder(args);
@@ -51,6 +52,9 @@ builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "DataProtection"));
//redis
builder.Services.AddCSRedisCacheExtension(builder.Configuration.GetSection("RedisSettings"));
+
+//MQ
+builder.Services.AddRabbitMQ(builder.Configuration);
// 配置Serilog
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
diff --git a/QYZH.InteractiveMagazine.WebApi/appsettings.json b/QYZH.InteractiveMagazine.WebApi/appsettings.json
index 8669a56..daf9305 100644
--- a/QYZH.InteractiveMagazine.WebApi/appsettings.json
+++ b/QYZH.InteractiveMagazine.WebApi/appsettings.json
@@ -13,12 +13,13 @@
"Sentinels": [],
"ExpireSecondRange": [ 3600, 7200 ]
},
- "RabbitMQSettings": {
+ "RabbitMq": {
"HostName": "192.168.20.150",
"Port": 5672,
"UserName": "smartschool",
"Password": "@ss%&*otz%d*pq2S",
- "VirtualHost": "InteractiveMagazine"
+ "VirtualHost": "InteractiveMagazine",
+ "ClientProvidedName": "Custom connection name"
},
"WeChatSettings": {
"AppId": "wx7922cc9b6023f3ac",