feat: 优化书籍和章节的导出
This commit is contained in:
36
src/main/kotlin/com/lanyuanxiaoyao/bookstore/Exporter.kt
Normal file
36
src/main/kotlin/com/lanyuanxiaoyao/bookstore/Exporter.kt
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.lanyuanxiaoyao.bookstore
|
||||
|
||||
data class ExporterItem(val name: String, val content: String)
|
||||
|
||||
interface ExporterTemplate {
|
||||
fun exportBook(book: Book): ExporterItem
|
||||
fun exportChapter(chapter: Chapter): ExporterItem
|
||||
}
|
||||
|
||||
val defaultExporterTemplate = object : ExporterTemplate {
|
||||
override fun exportBook(book: Book): ExporterItem {
|
||||
return ExporterItem(
|
||||
name = book.name,
|
||||
content = StringBuilder().apply {
|
||||
for (chapter in book.chapters.sortedBy { it.sequence }) {
|
||||
val (chapterName, chapterContent) = exportChapter(chapter)
|
||||
appendLine(chapterName)
|
||||
appendLine()
|
||||
appendLine(chapterContent)
|
||||
}
|
||||
appendLine()
|
||||
}.toString()
|
||||
)
|
||||
}
|
||||
|
||||
override fun exportChapter(chapter: Chapter): ExporterItem {
|
||||
return ExporterItem(
|
||||
name = "第${chapter.sequence}章${if (chapter.name.isNullOrBlank()) "" else "-${chapter.name}"}",
|
||||
content = StringBuilder().apply {
|
||||
for (line in chapter.content.sortedBy { it.sequence }) {
|
||||
appendLine(line.text)
|
||||
}
|
||||
}.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.lanyuanxiaoyao.bookstore
|
||||
|
||||
import cn.hutool.core.convert.Convert
|
||||
|
||||
// @Configuration
|
||||
// class ProcessorConfiguration {
|
||||
// @Bean
|
||||
@@ -22,39 +24,29 @@ interface Optimization {
|
||||
fun handle(line: String): String
|
||||
}
|
||||
|
||||
class TrimOptimization : Optimization {
|
||||
override fun handle(line: String): String {
|
||||
if (line.isBlank()) return line
|
||||
return line.trim()
|
||||
}
|
||||
}
|
||||
|
||||
class CharWidthOptimization : Optimization {
|
||||
override fun handle(line: String): String {
|
||||
if (line.isBlank()) return line
|
||||
return Convert.toDBC(line)
|
||||
}
|
||||
}
|
||||
|
||||
class QuoteCovertChineseOptimization : Optimization {
|
||||
override fun handle(line: String): String {
|
||||
return line
|
||||
.map { char ->
|
||||
if (char in '!'..'~')
|
||||
char.code - 65248
|
||||
else
|
||||
char
|
||||
}
|
||||
.joinToString("")
|
||||
.replace(",", ",")
|
||||
.replace("?", "?")
|
||||
.replace("!", "!")
|
||||
.replace(";", ";")
|
||||
.replace(":", ":")
|
||||
.replace("(", "(")
|
||||
.replace(")", ")")
|
||||
}
|
||||
}
|
||||
|
||||
interface ProcessTemplate {
|
||||
fun process(text: String): List<String>
|
||||
}
|
||||
|
||||
open class AbstractProcessTemplate(
|
||||
private val slicer: Slicer,
|
||||
private val optimizations: List<Optimization>,
|
||||
) : ProcessTemplate {
|
||||
override fun process(text: String): List<String> {
|
||||
return slicer.slice(text)
|
||||
.map { line ->
|
||||
optimizations.fold(line) { result, optimization -> optimization.handle(result) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SexInSexProcessTemplate : AbstractProcessTemplate(
|
||||
RegexSlicer(Regex("")),
|
||||
listOf(
|
||||
CharWidthOptimization(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil
|
||||
import com.lanyuanxiaoyao.bookstore.Book
|
||||
import com.lanyuanxiaoyao.bookstore.BookRepository
|
||||
import com.lanyuanxiaoyao.bookstore.PageResponse
|
||||
import com.lanyuanxiaoyao.bookstore.defaultExporterTemplate
|
||||
import jakarta.annotation.Resource
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import jakarta.transaction.Transactional
|
||||
@@ -75,24 +76,12 @@ class BookController {
|
||||
@GetMapping("export/{bookId}")
|
||||
fun export(@PathVariable("bookId") bookId: String, response: HttpServletResponse) {
|
||||
val book = bookRepository.findById(bookId).orElseThrow()
|
||||
val builder = StringBuilder()
|
||||
for (chapter in book.chapters.sortedBy { it.sequence }) {
|
||||
if (chapter.name.isNullOrBlank()) {
|
||||
builder.appendLine("第${chapter.sequence}章")
|
||||
} else {
|
||||
builder.appendLine("第${chapter.sequence}章 ${chapter.name}")
|
||||
}
|
||||
builder.appendLine()
|
||||
for (line in chapter.content.sortedBy { it.sequence }) {
|
||||
builder.appendLine(line.text)
|
||||
}
|
||||
builder.appendLine()
|
||||
}
|
||||
val result = defaultExporterTemplate.exportBook(book)
|
||||
response.setHeader("Access-Control-Expose-Headers", "Content-Type")
|
||||
response.setHeader("Content-Type", "text/plain")
|
||||
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
response.setHeader("Content-Disposition", StrUtil.format("attachment; filename={}", URLUtil.encodeAll("${book.name}.txt")))
|
||||
IoUtil.copy(builder.toString().byteInputStream(), response.outputStream)
|
||||
response.setHeader("Content-Disposition", StrUtil.format("attachment; filename={}", URLUtil.encodeAll("${result.name}.txt")))
|
||||
IoUtil.copy(result.content.byteInputStream(), response.outputStream)
|
||||
}
|
||||
|
||||
data class ViewItem(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.lanyuanxiaoyao.bookstore.controller
|
||||
|
||||
import cn.hutool.core.io.IoUtil
|
||||
import cn.hutool.core.util.IdUtil
|
||||
import cn.hutool.core.util.StrUtil
|
||||
import cn.hutool.core.util.URLUtil
|
||||
import com.lanyuanxiaoyao.bookstore.Book
|
||||
import com.lanyuanxiaoyao.bookstore.BookRepository
|
||||
import com.lanyuanxiaoyao.bookstore.Chapter
|
||||
@@ -9,7 +12,9 @@ import com.lanyuanxiaoyao.bookstore.Line
|
||||
import com.lanyuanxiaoyao.bookstore.LineRepository
|
||||
import com.lanyuanxiaoyao.bookstore.PageResponse
|
||||
import com.lanyuanxiaoyao.bookstore.SingleResponse
|
||||
import com.lanyuanxiaoyao.bookstore.defaultExporterTemplate
|
||||
import jakarta.annotation.Resource
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import jakarta.transaction.Transactional
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.data.domain.PageRequest
|
||||
@@ -40,7 +45,7 @@ class ChapterController {
|
||||
fun list(
|
||||
@PathVariable("bookId") bookId: String,
|
||||
@RequestParam("page", defaultValue = "1") page: Int,
|
||||
@RequestParam("size", defaultValue = "10") size: Int
|
||||
@RequestParam("size", defaultValue = "10") size: Int,
|
||||
): PageResponse<ViewItem> {
|
||||
val pageable = chapterRepository.findAll({ root, _, builder ->
|
||||
builder.equal(root.get<Book>("book").get<String>("bookId"), bookId)
|
||||
@@ -101,11 +106,26 @@ class ChapterController {
|
||||
lineRepository
|
||||
.findAll({ root, _, builder ->
|
||||
builder.equal(root.get<Chapter>("chapter").get<String>("chapterId"), chapterId)
|
||||
}, Sort.by(Sort.Direction.DESC, "sequence"))
|
||||
}, Sort.by(Sort.Direction.ASC, "sequence"))
|
||||
.joinToString("\n") { "<p>${it.text}</p>" }
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@GetMapping("export/{chapterId}")
|
||||
fun export(@PathVariable("chapterId") chapterId: String, response: HttpServletResponse) {
|
||||
val chapter = chapterRepository.findById(chapterId).orElseThrow()
|
||||
val result = defaultExporterTemplate.exportChapter(chapter)
|
||||
response.setHeader("Access-Control-Expose-Headers", "Content-Type")
|
||||
response.setHeader("Content-Type", "text/plain")
|
||||
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
response.setHeader(
|
||||
"Content-Disposition",
|
||||
StrUtil.format("attachment; filename={}", URLUtil.encodeAll("${result.name}.txt"))
|
||||
)
|
||||
IoUtil.copy(result.content.byteInputStream(), response.outputStream)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@GetMapping("remove/{chapterId}")
|
||||
fun remove(@PathVariable("chapterId") chapterId: String) {
|
||||
@@ -128,6 +148,6 @@ class ChapterController {
|
||||
|
||||
data class ImportItem(
|
||||
val override: Boolean = false,
|
||||
val text: String
|
||||
val text: String,
|
||||
)
|
||||
}
|
||||
@@ -208,6 +208,12 @@
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
label: '导出',
|
||||
actionType: 'download',
|
||||
api: '${base}/chapter/export/${chapterId}',
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
label: '编辑正文',
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.lanyuanxiaoyao.bookstore
|
||||
|
||||
import java.io.File
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ChapterOptimization {
|
||||
@Test
|
||||
fun run() {
|
||||
val optimizations = listOf(
|
||||
TrimOptimization(),
|
||||
CharWidthOptimization(),
|
||||
QuoteCovertChineseOptimization(),
|
||||
)
|
||||
val content = File("C:\\Users\\lanyuanxiaoyao\\Downloads\\第141章-反攻云岚宗.txt")
|
||||
.readLines()
|
||||
.joinToString("\n") { optimizations.fold(it) { line, optimization -> optimization.handle(line) } }
|
||||
println(content)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user