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
|
package com.lanyuanxiaoyao.bookstore
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert
|
||||||
|
|
||||||
// @Configuration
|
// @Configuration
|
||||||
// class ProcessorConfiguration {
|
// class ProcessorConfiguration {
|
||||||
// @Bean
|
// @Bean
|
||||||
@@ -22,39 +24,29 @@ interface Optimization {
|
|||||||
fun handle(line: String): String
|
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 {
|
class CharWidthOptimization : Optimization {
|
||||||
override fun handle(line: String): String {
|
override fun handle(line: String): String {
|
||||||
if (line.isBlank()) return line
|
if (line.isBlank()) return line
|
||||||
|
return Convert.toDBC(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuoteCovertChineseOptimization : Optimization {
|
||||||
|
override fun handle(line: String): String {
|
||||||
return line
|
return line
|
||||||
.map { char ->
|
.replace(",", ",")
|
||||||
if (char in '!'..'~')
|
.replace("?", "?")
|
||||||
char.code - 65248
|
.replace("!", "!")
|
||||||
else
|
.replace(";", ";")
|
||||||
char
|
.replace(":", ":")
|
||||||
}
|
.replace("(", "(")
|
||||||
.joinToString("")
|
.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.Book
|
||||||
import com.lanyuanxiaoyao.bookstore.BookRepository
|
import com.lanyuanxiaoyao.bookstore.BookRepository
|
||||||
import com.lanyuanxiaoyao.bookstore.PageResponse
|
import com.lanyuanxiaoyao.bookstore.PageResponse
|
||||||
|
import com.lanyuanxiaoyao.bookstore.defaultExporterTemplate
|
||||||
import jakarta.annotation.Resource
|
import jakarta.annotation.Resource
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
import jakarta.transaction.Transactional
|
import jakarta.transaction.Transactional
|
||||||
@@ -75,24 +76,12 @@ class BookController {
|
|||||||
@GetMapping("export/{bookId}")
|
@GetMapping("export/{bookId}")
|
||||||
fun export(@PathVariable("bookId") bookId: String, response: HttpServletResponse) {
|
fun export(@PathVariable("bookId") bookId: String, response: HttpServletResponse) {
|
||||||
val book = bookRepository.findById(bookId).orElseThrow()
|
val book = bookRepository.findById(bookId).orElseThrow()
|
||||||
val builder = StringBuilder()
|
val result = defaultExporterTemplate.exportBook(book)
|
||||||
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()
|
|
||||||
}
|
|
||||||
response.setHeader("Access-Control-Expose-Headers", "Content-Type")
|
response.setHeader("Access-Control-Expose-Headers", "Content-Type")
|
||||||
response.setHeader("Content-Type", "text/plain")
|
response.setHeader("Content-Type", "text/plain")
|
||||||
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition")
|
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition")
|
||||||
response.setHeader("Content-Disposition", StrUtil.format("attachment; filename={}", URLUtil.encodeAll("${book.name}.txt")))
|
response.setHeader("Content-Disposition", StrUtil.format("attachment; filename={}", URLUtil.encodeAll("${result.name}.txt")))
|
||||||
IoUtil.copy(builder.toString().byteInputStream(), response.outputStream)
|
IoUtil.copy(result.content.byteInputStream(), response.outputStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ViewItem(
|
data class ViewItem(
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.lanyuanxiaoyao.bookstore.controller
|
package com.lanyuanxiaoyao.bookstore.controller
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil
|
||||||
import cn.hutool.core.util.IdUtil
|
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.Book
|
||||||
import com.lanyuanxiaoyao.bookstore.BookRepository
|
import com.lanyuanxiaoyao.bookstore.BookRepository
|
||||||
import com.lanyuanxiaoyao.bookstore.Chapter
|
import com.lanyuanxiaoyao.bookstore.Chapter
|
||||||
@@ -9,7 +12,9 @@ import com.lanyuanxiaoyao.bookstore.Line
|
|||||||
import com.lanyuanxiaoyao.bookstore.LineRepository
|
import com.lanyuanxiaoyao.bookstore.LineRepository
|
||||||
import com.lanyuanxiaoyao.bookstore.PageResponse
|
import com.lanyuanxiaoyao.bookstore.PageResponse
|
||||||
import com.lanyuanxiaoyao.bookstore.SingleResponse
|
import com.lanyuanxiaoyao.bookstore.SingleResponse
|
||||||
|
import com.lanyuanxiaoyao.bookstore.defaultExporterTemplate
|
||||||
import jakarta.annotation.Resource
|
import jakarta.annotation.Resource
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
import jakarta.transaction.Transactional
|
import jakarta.transaction.Transactional
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.data.domain.PageRequest
|
import org.springframework.data.domain.PageRequest
|
||||||
@@ -40,7 +45,7 @@ class ChapterController {
|
|||||||
fun list(
|
fun list(
|
||||||
@PathVariable("bookId") bookId: String,
|
@PathVariable("bookId") bookId: String,
|
||||||
@RequestParam("page", defaultValue = "1") page: Int,
|
@RequestParam("page", defaultValue = "1") page: Int,
|
||||||
@RequestParam("size", defaultValue = "10") size: Int
|
@RequestParam("size", defaultValue = "10") size: Int,
|
||||||
): PageResponse<ViewItem> {
|
): PageResponse<ViewItem> {
|
||||||
val pageable = chapterRepository.findAll({ root, _, builder ->
|
val pageable = chapterRepository.findAll({ root, _, builder ->
|
||||||
builder.equal(root.get<Book>("book").get<String>("bookId"), bookId)
|
builder.equal(root.get<Book>("book").get<String>("bookId"), bookId)
|
||||||
@@ -101,11 +106,26 @@ class ChapterController {
|
|||||||
lineRepository
|
lineRepository
|
||||||
.findAll({ root, _, builder ->
|
.findAll({ root, _, builder ->
|
||||||
builder.equal(root.get<Chapter>("chapter").get<String>("chapterId"), chapterId)
|
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>" }
|
.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
|
@Transactional
|
||||||
@GetMapping("remove/{chapterId}")
|
@GetMapping("remove/{chapterId}")
|
||||||
fun remove(@PathVariable("chapterId") chapterId: String) {
|
fun remove(@PathVariable("chapterId") chapterId: String) {
|
||||||
@@ -128,6 +148,6 @@ class ChapterController {
|
|||||||
|
|
||||||
data class ImportItem(
|
data class ImportItem(
|
||||||
val override: Boolean = false,
|
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',
|
type: 'action',
|
||||||
label: '编辑正文',
|
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