删除过时的批处理脚本和部署Python文件

- 删除了macOS虚拟机的安装和迁移批处理脚本,因为它们已不再需要。
- 删除了macOS虚拟机的导出脚本,以简化项目流程。
- 删除了soul-admin项目的部署Python脚本,以简化代码库。
- 更新了小程序,以反映环境配置的变化并提升用户体验。
This commit is contained in:
Alex-larget
2026-03-16 16:10:30 +08:00
parent e75092eaad
commit 219ae3b843
19 changed files with 708 additions and 322 deletions

View File

@@ -54,6 +54,30 @@ var allChaptersCache struct {
const allChaptersCacheTTL = 30 * time.Second
// bookPartsCache 目录接口内存缓存30 秒 TTL减轻 DB 压力
type cachedPartRow struct {
PartID string `json:"id"`
PartTitle string `json:"title"`
Subtitle string `json:"subtitle"`
ChapterCount int `json:"chapterCount"`
MinSortOrder int `json:"minSortOrder"`
}
type cachedFixedItem struct {
ID string `json:"id"`
MID int `json:"mid"`
SectionTitle string `json:"title"`
}
var bookPartsCache struct {
mu sync.RWMutex
parts []cachedPartRow
total int64
fixed []cachedFixedItem
expires time.Time
}
const bookPartsCacheTTL = 30 * time.Second
// WarmAllChaptersCache 启动时预热缓存,避免首请求冷启动 502
func WarmAllChaptersCache() {
db := database.DB()
@@ -79,6 +103,86 @@ func WarmAllChaptersCache() {
allChaptersCache.mu.Unlock()
}
// fetchAndCacheBookParts 执行 DB 查询并更新缓存,供 BookParts 与 WarmBookPartsCache 复用
func fetchAndCacheBookParts() (parts []cachedPartRow, total int64, fixed []cachedFixedItem) {
db := database.DB()
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
conds := make([]string, len(excludeParts))
args := make([]interface{}, len(excludeParts))
for i, p := range excludeParts {
conds[i] = "part_title LIKE ?"
args[i] = "%" + p + "%"
}
where := "(" + strings.Join(conds, " OR ") + ")"
var rows []model.Chapter
if err := db.Model(&model.Chapter{}).Select("id", "mid", "section_title", "sort_order").
Where(where, args...).
Order("COALESCE(sort_order, 999999) ASC, id ASC").
Find(&rows).Error; err == nil {
sortChaptersByNaturalID(rows)
for _, r := range rows {
fixed = append(fixed, cachedFixedItem{r.ID, r.MID, r.SectionTitle})
}
}
}()
where := "1=1"
args := []interface{}{}
for _, p := range excludeParts {
where += " AND part_title NOT LIKE ?"
args = append(args, "%"+p+"%")
}
sql := `SELECT part_id, part_title, '' as subtitle,
COUNT(DISTINCT chapter_id) as chapter_count,
MIN(COALESCE(sort_order, 999999)) as min_sort
FROM chapters WHERE ` + where + `
GROUP BY part_id, part_title ORDER BY min_sort ASC, part_id ASC`
var raw []struct {
PartID string `gorm:"column:part_id"`
PartTitle string `gorm:"column:part_title"`
Subtitle string `gorm:"column:subtitle"`
ChapterCount int `gorm:"column:chapter_count"`
MinSortOrder int `gorm:"column:min_sort"`
}
go func() {
defer wg.Done()
db.Raw(sql, args...).Scan(&raw)
}()
go func() {
defer wg.Done()
db.Model(&model.Chapter{}).Count(&total)
}()
wg.Wait()
parts = make([]cachedPartRow, len(raw))
for i, r := range raw {
parts[i] = cachedPartRow{
PartID: r.PartID, PartTitle: r.PartTitle, Subtitle: r.Subtitle,
ChapterCount: r.ChapterCount, MinSortOrder: r.MinSortOrder,
}
}
bookPartsCache.mu.Lock()
bookPartsCache.parts = parts
bookPartsCache.total = total
bookPartsCache.fixed = fixed
bookPartsCache.expires = time.Now().Add(bookPartsCacheTTL)
bookPartsCache.mu.Unlock()
return parts, total, fixed
}
// WarmBookPartsCache 启动时预热目录缓存,避免首请求慢
func WarmBookPartsCache() {
fetchAndCacheBookParts()
}
// BookAllChapters GET /api/book/all-chapters 返回所有章节(列表,来自 chapters 表)
// 排序须与管理端 PUT /api/db/book action=reorder 一致:按 sort_order 升序,同序按 id
// 免费判断system_config.free_chapters / chapter_config.freeChapters 优先于 chapters.is_free
@@ -144,78 +248,30 @@ func BookChapterByID(c *gin.Context) {
// BookParts GET /api/miniprogram/book/parts 目录懒加载:仅返回篇章列表,不含章节详情
// 返回 parts排除序言/尾声/附录、totalSections、fixedSectionsid, mid, title 供序言/尾声/附录跳转用 mid
// 带 30 秒内存缓存,固定模块合并为 1 次查询,三路并行执行
func BookParts(c *gin.Context) {
db := database.DB()
// 固定模块(序言、尾声、附录)的 id、mid、title供 goToRead 传 data-mid
var fixedList []struct {
ID string `json:"id"`
MID int `json:"mid"`
SectionTitle string `json:"title"`
}
for _, p := range excludeParts {
var rows []model.Chapter
if err := db.Model(&model.Chapter{}).Select("id", "mid", "section_title", "sort_order").
Where("part_title LIKE ?", "%"+p+"%").
Order("COALESCE(sort_order, 999999) ASC, id ASC").
Find(&rows).Error; err != nil {
continue
}
sortChaptersByNaturalID(rows)
for _, r := range rows {
fixedList = append(fixedList, struct {
ID string `json:"id"`
MID int `json:"mid"`
SectionTitle string `json:"title"`
}{r.ID, r.MID, r.SectionTitle})
}
}
// 中间篇章:轻量聚合,不拉取 content
type partRow struct {
PartID string `json:"id"`
PartTitle string `json:"title"`
Subtitle string `json:"subtitle"`
ChapterCount int `json:"chapterCount"`
MinSortOrder int `json:"minSortOrder"`
}
where := "1=1"
args := []interface{}{}
for _, p := range excludeParts {
where += " AND part_title NOT LIKE ?"
args = append(args, "%"+p+"%")
}
var raw []struct {
PartID string `gorm:"column:part_id"`
PartTitle string `gorm:"column:part_title"`
Subtitle string `gorm:"column:subtitle"`
ChapterCount int `gorm:"column:chapter_count"`
MinSortOrder int `gorm:"column:min_sort"`
}
sql := `SELECT part_id, part_title, '' as subtitle,
COUNT(DISTINCT chapter_id) as chapter_count,
MIN(COALESCE(sort_order, 999999)) as min_sort
FROM chapters WHERE ` + where + `
GROUP BY part_id, part_title ORDER BY min_sort ASC, part_id ASC`
if err := db.Raw(sql, args...).Scan(&raw).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": true, "parts": []interface{}{}, "totalSections": 0, "fixedSections": fixedList})
bookPartsCache.mu.RLock()
if time.Now().Before(bookPartsCache.expires) {
parts := bookPartsCache.parts
total := bookPartsCache.total
fixed := bookPartsCache.fixed
bookPartsCache.mu.RUnlock()
c.JSON(http.StatusOK, gin.H{
"success": true,
"parts": parts,
"totalSections": total,
"fixedSections": fixed,
})
return
}
parts := make([]partRow, len(raw))
for i, r := range raw {
parts[i] = partRow{
PartID: r.PartID, PartTitle: r.PartTitle, Subtitle: r.Subtitle,
ChapterCount: r.ChapterCount, MinSortOrder: r.MinSortOrder,
}
}
var total int64
db.Model(&model.Chapter{}).Count(&total)
bookPartsCache.mu.RUnlock()
parts, total, fixed := fetchAndCacheBookParts()
c.JSON(http.StatusOK, gin.H{
"success": true,
"parts": parts,
"totalSections": total,
"fixedSections": fixedList,
"fixedSections": fixed,
})
}