444 lines
20 KiB
C#
444 lines
20 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Data;
|
|||
|
|
using System.Data.SqlClient;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Text;
|
|||
|
|
using System.Text.RegularExpressions;
|
|||
|
|
|
|||
|
|
namespace BLL
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// PDF 书籍模块 WebAPI 数据服务(移动端 / 第三方系统集成用)。
|
|||
|
|
/// 对应 PC 页面 HSSE/PDFDOC/PdfBookView.aspx 的完整功能:书籍、多级章节目录、章节正文(富文本 HTML)的
|
|||
|
|
/// 查询与维护(新增书籍 / 新增·编辑·删除章节 / 保存章节正文),并额外提供章节关键词检索。
|
|||
|
|
/// 数据表:Sys_PdfBook / Sys_PdfChapter(正文存 [Text] 列,为 UMeditor 生成的 HTML)。
|
|||
|
|
/// 说明:目录结构用 ParentChapterId + Level(1章 2节 3小节) 表达多级层级,SortNo 控制同级顺序。
|
|||
|
|
/// </summary>
|
|||
|
|
public static class APIPdfBookService
|
|||
|
|
{
|
|||
|
|
#region 返回模型
|
|||
|
|
/// <summary>书籍列表项。</summary>
|
|||
|
|
public class BookItem
|
|||
|
|
{
|
|||
|
|
/// <summary>书籍主键。</summary>
|
|||
|
|
public string BookId { get; set; }
|
|||
|
|
/// <summary>书名。</summary>
|
|||
|
|
public string Title { get; set; }
|
|||
|
|
/// <summary>章节总数。</summary>
|
|||
|
|
public int ChapterCount { get; set; }
|
|||
|
|
/// <summary>登记时间。</summary>
|
|||
|
|
public DateTime? CreateTime { get; set; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>章节目录树节点(多级嵌套)。</summary>
|
|||
|
|
public class ChapterTreeNode
|
|||
|
|
{
|
|||
|
|
/// <summary>章节主键。</summary>
|
|||
|
|
public string ChapterId { get; set; }
|
|||
|
|
/// <summary>章节名称。</summary>
|
|||
|
|
public string ChapterTitle { get; set; }
|
|||
|
|
/// <summary>层级:1章 2节 3小节。</summary>
|
|||
|
|
public int Level { get; set; }
|
|||
|
|
/// <summary>起始页码(无则为0)。</summary>
|
|||
|
|
public int PageStart { get; set; }
|
|||
|
|
/// <summary>结束页码(无则为0)。</summary>
|
|||
|
|
public int PageEnd { get; set; }
|
|||
|
|
/// <summary>是否已有正文内容(用于客户端标识可读章节)。</summary>
|
|||
|
|
public bool HasContent { get; set; }
|
|||
|
|
/// <summary>子章节列表(叶子章节为空数组)。</summary>
|
|||
|
|
public List<ChapterTreeNode> Children { get; set; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>章节正文详情。</summary>
|
|||
|
|
public class ChapterDetailItem
|
|||
|
|
{
|
|||
|
|
/// <summary>章节主键。</summary>
|
|||
|
|
public string ChapterId { get; set; }
|
|||
|
|
/// <summary>所属书籍主键。</summary>
|
|||
|
|
public string BookId { get; set; }
|
|||
|
|
/// <summary>所属书籍名称。</summary>
|
|||
|
|
public string BookTitle { get; set; }
|
|||
|
|
/// <summary>章节名称。</summary>
|
|||
|
|
public string ChapterTitle { get; set; }
|
|||
|
|
/// <summary>父章节主键(顶级为 null)。</summary>
|
|||
|
|
public string ParentChapterId { get; set; }
|
|||
|
|
/// <summary>层级:1章 2节 3小节。</summary>
|
|||
|
|
public int Level { get; set; }
|
|||
|
|
/// <summary>起始页码(无则为0)。</summary>
|
|||
|
|
public int PageStart { get; set; }
|
|||
|
|
/// <summary>结束页码(无则为0)。</summary>
|
|||
|
|
public int PageEnd { get; set; }
|
|||
|
|
/// <summary>正文(富文本 HTML,可直接用 WebView 渲染)。</summary>
|
|||
|
|
public string Content { get; set; }
|
|||
|
|
/// <summary>正文纯文本(去掉 HTML 标签,便于全文展示/搜索)。</summary>
|
|||
|
|
public string ContentText { get; set; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>章节关键词检索命中项。</summary>
|
|||
|
|
public class SearchHitItem
|
|||
|
|
{
|
|||
|
|
/// <summary>书籍主键。</summary>
|
|||
|
|
public string BookId { get; set; }
|
|||
|
|
/// <summary>书籍名称。</summary>
|
|||
|
|
public string BookTitle { get; set; }
|
|||
|
|
/// <summary>章节主键。</summary>
|
|||
|
|
public string ChapterId { get; set; }
|
|||
|
|
/// <summary>章节名称。</summary>
|
|||
|
|
public string ChapterTitle { get; set; }
|
|||
|
|
/// <summary>起始页码(无则为0)。</summary>
|
|||
|
|
public int PageStart { get; set; }
|
|||
|
|
/// <summary>结束页码(无则为0)。</summary>
|
|||
|
|
public int PageEnd { get; set; }
|
|||
|
|
/// <summary>命中片段(关键词前后各约40字)。</summary>
|
|||
|
|
public string Snippet { get; set; }
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 查询:书籍列表
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取全部书籍(按登记时间升序,与页面下拉一致)。
|
|||
|
|
/// </summary>
|
|||
|
|
public static List<BookItem> GetBooks()
|
|||
|
|
{
|
|||
|
|
var list = new List<BookItem>();
|
|||
|
|
DataTable dt = SQLHelper.GetDataTableRunText(
|
|||
|
|
"SELECT b.BookId, b.Title, b.CreateTime, " +
|
|||
|
|
"(SELECT COUNT(*) FROM dbo.Sys_PdfChapter c WHERE c.BookId = b.BookId) AS ChapterCount " +
|
|||
|
|
"FROM dbo.Sys_PdfBook b ORDER BY b.CreateTime", new SqlParameter[] { });
|
|||
|
|
if (dt == null)
|
|||
|
|
return list;
|
|||
|
|
foreach (DataRow r in dt.Rows)
|
|||
|
|
{
|
|||
|
|
list.Add(new BookItem
|
|||
|
|
{
|
|||
|
|
BookId = r["BookId"] == null ? "" : r["BookId"].ToString(),
|
|||
|
|
Title = r["Title"] == null ? "" : r["Title"].ToString(),
|
|||
|
|
ChapterCount = r["ChapterCount"] == DBNull.Value ? 0 : Convert.ToInt32(r["ChapterCount"]),
|
|||
|
|
CreateTime = r["CreateTime"] == DBNull.Value ? (DateTime?)null : Convert.ToDateTime(r["CreateTime"])
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
return list;
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 查询:多级章节目录树
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取某本书的多级章节目录树(嵌套 Children,顶级为根的数组;按 SortNo 排序)。
|
|||
|
|
/// </summary>
|
|||
|
|
public static List<ChapterTreeNode> GetChapterTree(string bookId)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(bookId))
|
|||
|
|
throw new Exception("bookId 不能为空");
|
|||
|
|
|
|||
|
|
var chapters = PdfBookService.GetChapters(bookId);
|
|||
|
|
var result = new List<ChapterTreeNode>();
|
|||
|
|
if (chapters == null || chapters.Count == 0)
|
|||
|
|
return result;
|
|||
|
|
|
|||
|
|
// 有正文标记(一次查询)
|
|||
|
|
var hasContentSet = new HashSet<string>();
|
|||
|
|
DataTable dt = SQLHelper.GetDataTableRunText(
|
|||
|
|
"SELECT ChapterId FROM dbo.Sys_PdfChapter " +
|
|||
|
|
"WHERE BookId = @BookId AND [Text] IS NOT NULL AND LTRIM(RTRIM([Text])) <> N''",
|
|||
|
|
new[] { new SqlParameter("@BookId", bookId) });
|
|||
|
|
if (dt != null)
|
|||
|
|
foreach (DataRow r in dt.Rows)
|
|||
|
|
hasContentSet.Add(r["ChapterId"] == null ? "" : r["ChapterId"].ToString());
|
|||
|
|
|
|||
|
|
// 1) 先为每个章节建节点
|
|||
|
|
var nodeMap = new Dictionary<string, ChapterTreeNode>();
|
|||
|
|
foreach (PdfChapter c in chapters)
|
|||
|
|
{
|
|||
|
|
nodeMap[c.ChapterId] = new ChapterTreeNode
|
|||
|
|
{
|
|||
|
|
ChapterId = c.ChapterId,
|
|||
|
|
ChapterTitle = c.Title,
|
|||
|
|
Level = c.Level,
|
|||
|
|
PageStart = c.PageStart,
|
|||
|
|
PageEnd = c.PageEnd,
|
|||
|
|
HasContent = hasContentSet.Contains(c.ChapterId),
|
|||
|
|
Children = new List<ChapterTreeNode>()
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2) 按父章节挂接(父不在列表中的作为顶级)
|
|||
|
|
foreach (PdfChapter c in chapters)
|
|||
|
|
{
|
|||
|
|
var node = nodeMap[c.ChapterId];
|
|||
|
|
if (!string.IsNullOrWhiteSpace(c.ParentChapterId) && nodeMap.ContainsKey(c.ParentChapterId))
|
|||
|
|
nodeMap[c.ParentChapterId].Children.Add(node);
|
|||
|
|
else
|
|||
|
|
result.Add(node);
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 查询:章节正文
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取章节正文详情(含 HTML 与纯文本);章节不存在返回 null。
|
|||
|
|
/// </summary>
|
|||
|
|
public static ChapterDetailItem GetChapterDetail(string chapterId)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(chapterId))
|
|||
|
|
throw new Exception("chapterId 不能为空");
|
|||
|
|
|
|||
|
|
DataTable dt = SQLHelper.GetDataTableRunText(
|
|||
|
|
"SELECT c.ChapterId, c.BookId, b.Title AS BookTitle, c.ParentChapterId, c.[Level], " +
|
|||
|
|
"c.ChapterTitle, c.PageStart, c.PageEnd, c.[Text] " +
|
|||
|
|
"FROM dbo.Sys_PdfChapter c INNER JOIN dbo.Sys_PdfBook b ON c.BookId = b.BookId " +
|
|||
|
|
"WHERE c.ChapterId = @ChapterId",
|
|||
|
|
new[] { new SqlParameter("@ChapterId", chapterId) });
|
|||
|
|
if (dt == null || dt.Rows.Count == 0)
|
|||
|
|
return null;
|
|||
|
|
|
|||
|
|
DataRow r = dt.Rows[0];
|
|||
|
|
string html = r["Text"] == DBNull.Value ? "" : r["Text"].ToString();
|
|||
|
|
var item = new ChapterDetailItem
|
|||
|
|
{
|
|||
|
|
ChapterId = r["ChapterId"] == null ? "" : r["ChapterId"].ToString(),
|
|||
|
|
BookId = r["BookId"] == null ? "" : r["BookId"].ToString(),
|
|||
|
|
BookTitle = r["BookTitle"] == null ? "" : r["BookTitle"].ToString(),
|
|||
|
|
ParentChapterId = r["ParentChapterId"] == DBNull.Value ? null : r["ParentChapterId"].ToString(),
|
|||
|
|
Level = r["Level"] == DBNull.Value ? 1 : Convert.ToInt32(r["Level"]),
|
|||
|
|
ChapterTitle = r["ChapterTitle"] == null ? "" : r["ChapterTitle"].ToString(),
|
|||
|
|
PageStart = r["PageStart"] == DBNull.Value ? 0 : Convert.ToInt32(r["PageStart"]),
|
|||
|
|
PageEnd = r["PageEnd"] == DBNull.Value ? 0 : Convert.ToInt32(r["PageEnd"]),
|
|||
|
|
Content = html,
|
|||
|
|
ContentText = HtmlToPlainText(html)
|
|||
|
|
};
|
|||
|
|
return item;
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 查询:章节关键词检索
|
|||
|
|
/// <summary>
|
|||
|
|
/// 章节关键词检索:在章节正文中按关键词做字面匹配(正文为 HTML,匹配前先剔除标签)。
|
|||
|
|
/// bookId 可空:不传则全库检索。
|
|||
|
|
/// </summary>
|
|||
|
|
public static List<SearchHitItem> SearchChapter(string keyword, string bookId)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(keyword))
|
|||
|
|
throw new Exception("keyword 不能为空");
|
|||
|
|
|
|||
|
|
// LIKE 通配符转义(ESCAPE 字符为反斜杠)
|
|||
|
|
string esc = keyword.Replace("\\", "\\\\").Replace("%", "\\%").Replace("_", "\\_");
|
|||
|
|
var ps = new List<SqlParameter>
|
|||
|
|
{
|
|||
|
|
new SqlParameter("@Keyword", "%" + esc + "%")
|
|||
|
|
};
|
|||
|
|
string where = "c.[Text] LIKE @Keyword ESCAPE '\\'";
|
|||
|
|
if (!string.IsNullOrWhiteSpace(bookId))
|
|||
|
|
{
|
|||
|
|
where += " AND c.BookId = @BookId";
|
|||
|
|
ps.Add(new SqlParameter("@BookId", bookId));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
DataTable dt = SQLHelper.GetDataTableRunText(
|
|||
|
|
"SELECT c.ChapterId, c.ChapterTitle, c.BookId, b.Title AS BookTitle, " +
|
|||
|
|
"c.PageStart, c.PageEnd, c.[Text] " +
|
|||
|
|
"FROM dbo.Sys_PdfChapter c INNER JOIN dbo.Sys_PdfBook b ON c.BookId = b.BookId " +
|
|||
|
|
"WHERE " + where + " ORDER BY b.Title, c.SortNo", ps.ToArray());
|
|||
|
|
|
|||
|
|
var list = new List<SearchHitItem>();
|
|||
|
|
if (dt == null)
|
|||
|
|
return list;
|
|||
|
|
foreach (DataRow r in dt.Rows)
|
|||
|
|
{
|
|||
|
|
string html = r["Text"] == DBNull.Value ? "" : r["Text"].ToString();
|
|||
|
|
string plain = HtmlToPlainText(html);
|
|||
|
|
list.Add(new SearchHitItem
|
|||
|
|
{
|
|||
|
|
BookId = r["BookId"] == null ? "" : r["BookId"].ToString(),
|
|||
|
|
BookTitle = r["BookTitle"] == null ? "" : r["BookTitle"].ToString(),
|
|||
|
|
ChapterId = r["ChapterId"] == null ? "" : r["ChapterId"].ToString(),
|
|||
|
|
ChapterTitle = r["ChapterTitle"] == null ? "" : r["ChapterTitle"].ToString(),
|
|||
|
|
PageStart = r["PageStart"] == DBNull.Value ? 0 : Convert.ToInt32(r["PageStart"]),
|
|||
|
|
PageEnd = r["PageEnd"] == DBNull.Value ? 0 : Convert.ToInt32(r["PageEnd"]),
|
|||
|
|
Snippet = BuildSnippet(plain, keyword)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
return list;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>取关键词命中片段(前后各约40字)。</summary>
|
|||
|
|
private static string BuildSnippet(string plain, string keyword)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrEmpty(plain))
|
|||
|
|
return "";
|
|||
|
|
int idx = plain.IndexOf(keyword, StringComparison.OrdinalIgnoreCase);
|
|||
|
|
if (idx < 0)
|
|||
|
|
return plain.Length > 120 ? plain.Substring(0, 120) + "……" : plain;
|
|||
|
|
int start = Math.Max(0, idx - 40);
|
|||
|
|
int len = Math.Min(plain.Length - start, keyword.Length + 80);
|
|||
|
|
return (start > 0 ? "……" : "") + plain.Substring(start, len) + (start + len < plain.Length ? "……" : "");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>HTML 转纯文本(去标签、解码实体、压缩空白)。</summary>
|
|||
|
|
private static string HtmlToPlainText(string html)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrEmpty(html))
|
|||
|
|
return "";
|
|||
|
|
string s = Regex.Replace(html, "(?i)<(br|/p|/div|/li|/tr|/h[1-6])[^>]*>", "\n");
|
|||
|
|
s = Regex.Replace(s, "<[^>]+>", "");
|
|||
|
|
s = System.Net.WebUtility.HtmlDecode(s);
|
|||
|
|
s = Regex.Replace(s, "[\r\n]+", "\n");
|
|||
|
|
s = Regex.Replace(s, "[ \t]+", " ");
|
|||
|
|
return s.Trim();
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 维护:书籍
|
|||
|
|
/// <summary>
|
|||
|
|
/// 新增书籍(仅书名;FilePath/TotalPages/IndexStatus 与页面一致:NULL/0/2)。
|
|||
|
|
/// </summary>
|
|||
|
|
public static string AddBook(string title, string createUser)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(title))
|
|||
|
|
throw new Exception("书名不能为空");
|
|||
|
|
if (string.IsNullOrWhiteSpace(createUser))
|
|||
|
|
createUser = "api";
|
|||
|
|
|
|||
|
|
string bookId = NewId("PB");
|
|||
|
|
SQLHelper.ExecutSql(
|
|||
|
|
"INSERT INTO dbo.Sys_PdfBook (BookId, Title, FilePath, TotalPages, IndexStatus, CreateUser, CreateTime) VALUES ('"
|
|||
|
|
+ SqlEsc(bookId) + "',N'" + SqlEsc(title.Trim()) + "',NULL,0,2,N'"
|
|||
|
|
+ SqlEsc(createUser) + "',GETDATE())");
|
|||
|
|
return bookId;
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 维护:章节(新增 / 改名 / 删除 / 保存正文)
|
|||
|
|
/// <summary>
|
|||
|
|
/// 新增章节:parentChapterId 为空则为顶级章节,否则挂到指定父章节下(层级=父级+1)。
|
|||
|
|
/// SortNo 取全书当前最大值+1(与页面逻辑一致)。
|
|||
|
|
/// </summary>
|
|||
|
|
public static string AddChapter(string bookId, string parentChapterId, string chapterTitle, string createUser)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(bookId))
|
|||
|
|
throw new Exception("bookId 不能为空");
|
|||
|
|
if (string.IsNullOrWhiteSpace(chapterTitle))
|
|||
|
|
throw new Exception("章节名称不能为空");
|
|||
|
|
|
|||
|
|
int level = 1;
|
|||
|
|
if (!string.IsNullOrWhiteSpace(parentChapterId))
|
|||
|
|
{
|
|||
|
|
int parentLevel = GetInt(
|
|||
|
|
"SELECT ISNULL([Level],1) FROM dbo.Sys_PdfChapter WHERE ChapterId='" + SqlEsc(parentChapterId) + "'");
|
|||
|
|
if (parentLevel <= 0)
|
|||
|
|
throw new Exception("父章节不存在:" + parentChapterId);
|
|||
|
|
level = parentLevel + 1;
|
|||
|
|
}
|
|||
|
|
int sort = GetInt(
|
|||
|
|
"SELECT ISNULL(MAX(SortNo),0) FROM dbo.Sys_PdfChapter WHERE BookId='" + SqlEsc(bookId) + "'") + 1;
|
|||
|
|
|
|||
|
|
string chapterId = NewId("CH");
|
|||
|
|
SQLHelper.ExecutSql(
|
|||
|
|
"INSERT INTO dbo.Sys_PdfChapter (ChapterId, BookId, ParentChapterId, [Level], ChapterTitle, PageStart, PageEnd, [Text], SortNo) VALUES ('"
|
|||
|
|
+ SqlEsc(chapterId) + "','" + SqlEsc(bookId) + "',"
|
|||
|
|
+ (string.IsNullOrWhiteSpace(parentChapterId) ? "NULL" : "'" + SqlEsc(parentChapterId) + "'")
|
|||
|
|
+ "," + level + ",N'" + SqlEsc(chapterTitle.Trim()) + "',NULL,NULL,N''," + sort + ")");
|
|||
|
|
return chapterId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 编辑章节:仅修改章节名称。
|
|||
|
|
/// </summary>
|
|||
|
|
public static void EditChapter(string chapterId, string chapterTitle)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(chapterId))
|
|||
|
|
throw new Exception("chapterId 不能为空");
|
|||
|
|
if (string.IsNullOrWhiteSpace(chapterTitle))
|
|||
|
|
throw new Exception("章节名称不能为空");
|
|||
|
|
if (GetInt("SELECT COUNT(*) FROM dbo.Sys_PdfChapter WHERE ChapterId='" + SqlEsc(chapterId) + "'") == 0)
|
|||
|
|
throw new Exception("章节不存在:" + chapterId);
|
|||
|
|
|
|||
|
|
SQLHelper.ExecutSql("UPDATE dbo.Sys_PdfChapter SET ChapterTitle=N'" + SqlEsc(chapterTitle.Trim())
|
|||
|
|
+ "' WHERE ChapterId='" + SqlEsc(chapterId) + "'");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 删除章节及其全部子章节(递归收集自身+后代后一次性删除)。
|
|||
|
|
/// </summary>
|
|||
|
|
public static int DeleteChapter(string chapterId)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(chapterId))
|
|||
|
|
throw new Exception("chapterId 不能为空");
|
|||
|
|
|
|||
|
|
DataTable dt = SQLHelper.GetDataTableRunText(
|
|||
|
|
"SELECT BookId FROM dbo.Sys_PdfChapter WHERE ChapterId=@ChapterId",
|
|||
|
|
new[] { new SqlParameter("@ChapterId", chapterId) });
|
|||
|
|
if (dt == null || dt.Rows.Count == 0)
|
|||
|
|
throw new Exception("章节不存在:" + chapterId);
|
|||
|
|
string bookId = dt.Rows[0]["BookId"].ToString();
|
|||
|
|
|
|||
|
|
var all = PdfBookService.GetChapters(bookId);
|
|||
|
|
var ids = new List<string> { chapterId };
|
|||
|
|
bool changed = true;
|
|||
|
|
while (changed)
|
|||
|
|
{
|
|||
|
|
changed = false;
|
|||
|
|
foreach (PdfChapter c in all)
|
|||
|
|
{
|
|||
|
|
if (!ids.Contains(c.ChapterId) && ids.Contains(c.ParentChapterId ?? ""))
|
|||
|
|
{
|
|||
|
|
ids.Add(c.ChapterId);
|
|||
|
|
changed = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
foreach (string id in ids)
|
|||
|
|
{
|
|||
|
|
if (sb.Length > 0)
|
|||
|
|
sb.Append(",");
|
|||
|
|
sb.Append("'").Append(id.Replace("'", "''")).Append("'");
|
|||
|
|
}
|
|||
|
|
SQLHelper.ExecutSql("DELETE FROM dbo.Sys_PdfChapter WHERE ChapterId IN (" + sb + ")");
|
|||
|
|
return ids.Count;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 保存章节正文(富文本 HTML,与页面"保存内容"一致)。
|
|||
|
|
/// </summary>
|
|||
|
|
public static void SaveContent(string chapterId, string content)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(chapterId))
|
|||
|
|
throw new Exception("chapterId 不能为空");
|
|||
|
|
if (GetInt("SELECT COUNT(*) FROM dbo.Sys_PdfChapter WHERE ChapterId='" + SqlEsc(chapterId) + "'") == 0)
|
|||
|
|
throw new Exception("章节不存在:" + chapterId);
|
|||
|
|
|
|||
|
|
SQLHelper.ExecutSql("UPDATE dbo.Sys_PdfChapter SET [Text]=N'" + SqlEsc(content ?? "")
|
|||
|
|
+ "' WHERE ChapterId='" + SqlEsc(chapterId) + "'");
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 工具
|
|||
|
|
/// <summary>SQL 字符串转义。</summary>
|
|||
|
|
private static string SqlEsc(string s)
|
|||
|
|
{
|
|||
|
|
return (s ?? "").Replace("'", "''");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>执行 SELECT 首行首列并转 int(查无返回0)。</summary>
|
|||
|
|
private static int GetInt(string sql)
|
|||
|
|
{
|
|||
|
|
DataTable dt = SQLHelper.GetDataTableRunText(sql, new SqlParameter[] { });
|
|||
|
|
if (dt == null || dt.Rows.Count == 0)
|
|||
|
|
return 0;
|
|||
|
|
object v = dt.Rows[0][0];
|
|||
|
|
return v == DBNull.Value || v == null ? 0 : Convert.ToInt32(v);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>生成业务主键(前缀+时间+随机,与页面一致)。</summary>
|
|||
|
|
private static string NewId(string prefix)
|
|||
|
|
{
|
|||
|
|
return prefix + DateTime.Now.ToString("yyyyMMddHHmmss")
|
|||
|
|
+ Guid.NewGuid().ToString("N").Substring(0, 4);
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
}
|
|||
|
|
}
|