1
0

feat: 增加首页过滤和搜索

This commit is contained in:
2024-11-08 12:15:41 +08:00
parent 6925600043
commit d9ebc287f7
6 changed files with 323 additions and 141 deletions

View File

@@ -41,6 +41,7 @@ dependencies {
implementation("cn.bigmodel.openapi:oapi-java-sdk:release-V4-2.3.0")
implementation("com.baidubce:qianfan:0.1.1")
implementation("org.jsoup:jsoup:1.18.1")
implementation("com.blinkfox:fenix-spring-boot-starter:3.0.0")
val hutoolVersion = "5.8.32"
implementation("cn.hutool:hutool-core:$hutoolVersion")

View File

@@ -1,5 +1,6 @@
package com.lanyuanxiaoyao.digtal.market
import com.blinkfox.fenix.EnableFenix
import com.lanyuanxiaoyao.digtal.market.runner.NewsRunner
import com.lanyuanxiaoyao.digtal.market.runner.PushRunner
import com.lanyuanxiaoyao.squirrel.core.common.Management
@@ -20,7 +21,6 @@ import org.springframework.context.ApplicationListener
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.event.ContextClosedEvent
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@@ -41,7 +41,8 @@ data class MailProperties @ConstructorBinding constructor(
val targets: List<String>,
)
@EnableScheduling
// @EnableScheduling
@EnableFenix
@ConfigurationPropertiesScan
@SpringBootApplication
class Application : ApplicationRunner, ApplicationListener<ContextClosedEvent> {

View File

@@ -1,11 +1,11 @@
package com.lanyuanxiaoyao.digtal.market
import com.blinkfox.fenix.specification.FenixJpaSpecificationExecutor
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import java.util.Date
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
@@ -30,12 +30,18 @@ class Article(
)
@Repository
interface ArticleRepository : JpaRepository<Article, String>, JpaSpecificationExecutor<Article> {
interface ArticleRepository : JpaRepository<Article, String>, FenixJpaSpecificationExecutor<Article> {
fun findAllByDescriptionIsNullAndTextIsNotNull(): List<Article>
fun findAllByHtmlIsNotNull(): List<Article>
fun existsByCode(code: String): Boolean
@Query("select distinct article.category from Article article where article.category is not null and article.category <> ''")
fun findAllCategory(): List<String>
@Query("select distinct article.author from Article article where article.author is not null and article.author <> ''")
fun findAllAuthor(): List<String>
@Modifying
@Transactional
@Query("update Article article set article.pushed = :pushed where article.id = :id")

View File

@@ -1,9 +1,13 @@
package com.lanyuanxiaoyao.digtal.market.controller
import cn.hutool.core.date.LocalDateTimeUtil
import com.lanyuanxiaoyao.digtal.market.ArticleRepository
import com.lanyuanxiaoyao.digtal.market.sites
import com.lanyuanxiaoyao.squirrel.core.common.Site
import jakarta.annotation.Resource
import jakarta.persistence.criteria.Predicate
import java.sql.Date
import java.time.Instant
import org.slf4j.LoggerFactory
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
@@ -23,11 +27,66 @@ class OverviewController {
@GetMapping("news")
fun news(
@RequestParam("code", required = false) code: String?,
@RequestParam("filter_keyword", required = false) filterKeyword: String?,
@RequestParam("filter_date", required = false) filterDate: Long?,
@RequestParam("filter_date_start", required = false) filterDateStart: Long?,
@RequestParam("filter_date_end", required = false) filterDateEnd: Long?,
@RequestParam("filter_source", required = false) filterSource: String?,
@RequestParam("filter_author", required = false) filterAuthor: String?,
@RequestParam("filter_category", required = false) filterCategory: String?,
@RequestParam("page", defaultValue = "1") page: Int,
@RequestParam("count", defaultValue = "10") count: Int,
): Map<String, Any> {
val request = PageRequest.of(page - 1, count, Sort.by(Sort.Direction.DESC, "createTime"))
val result = articleRepository.findAll(request)
val pageRequest = PageRequest.of(page - 1, count, Sort.by(Sort.Direction.DESC, "createTime"))
val result = articleRepository.findAll({ root, _, builder ->
val predictions = mutableListOf<Predicate>()
filterKeyword?.let {
predictions.add(
builder.or(
builder.like(root.get("title"), "%${it}%"),
builder.like(root.get("subtitle"), "%${it}%"),
builder.like(root.get("text"), "%${it}%"),
builder.like(root.get("description"), "%${it}%"),
)
)
}
if (filterDate != null) {
val datetime = LocalDateTimeUtil.of(filterDate * 1000)
predictions.add(
builder.between(
root.get<Date>("createTime"),
Date.from(Instant.ofEpochMilli(LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtil.beginOfDay(datetime)))),
Date.from(Instant.ofEpochMilli(LocalDateTimeUtil.toEpochMilli(LocalDateTimeUtil.endOfDay(datetime))))
)
)
} else if (filterDateStart != null && filterDateEnd != null) {
predictions.add(
builder.between(root.get<Date>("createTime"), Date.from(Instant.ofEpochSecond(filterDateStart)), Date.from(Instant.ofEpochSecond(filterDateEnd)))
)
}
filterSource?.let {
val site = sites.firstOrNull { site -> site.name == it }
if (site != null) {
predictions.add(
builder.equal(root.get<String>("code"), site.code)
)
}
}
filterAuthor?.let {
predictions.add(
builder.equal(root.get<String>("author"), it)
)
}
filterCategory?.let {
predictions.add(
builder.equal(root.get<String>("category"), it)
)
}
builder.and(*predictions.toTypedArray())
}, pageRequest)
articleRepository.findAll({ builder ->
builder.build()
}, pageRequest)
return mapOf(
"items" to result.content.map {
val site = sites.find { site -> site.code == it.code }!!
@@ -49,4 +108,13 @@ class OverviewController {
"total" to result.totalElements,
)
}
@GetMapping("all_source")
fun allSource() = sites.map { it.name }
@GetMapping("all_category")
fun allCategory() = articleRepository.findAllCategory()
@GetMapping("all_author")
fun allAuthor() = articleRepository.findAllAuthor()
}

View File

@@ -43,4 +43,7 @@ messenger:
binary-path: /Users/lanyuanxiaoyao/Downloads/chromium/128/macOS-1289987/Chromium.app/Contents/MacOS/Chromium
database:
h2-path: ./database/database.db
json-path: ./database/database.json
json-path: ./database/database.json
fenix:
print-banner: false
print-sql: false

View File

@@ -30,161 +30,264 @@ function overviewTab() {
return {
title: '总览',
tab: {
id: 'news_list',
columnClassName: 'px-2 pt-2',
type: 'service',
type: 'crud',
syncLocation: false,
api: {
method: 'get',
url: '${base}/overview/news',
data: {
page: '${page|default:1}',
count: '${count|default:10}',
filter_keyword: '${filter_keyword|default:undefined}',
filter_date: '${filter_date|default:undefined}',
filter_date_start: '${filter_date_start|default:undefined}',
filter_date_end: '${filter_date_end|default:undefined}',
filter_source: '${filter_source|default:undefined}',
filter_author: '${filter_author|default:undefined}',
filter_category: '${filter_category|default:undefined}',
}
},
body: [
{
type: 'action',
icon: 'fa fa-refresh',
label: '刷新',
className: 'mb-2',
actionType: 'reload',
target: 'news_list',
filterTogglable: true,
filterDefaultVisible: false,
mode: 'list',
headerToolbar: [
'reload',
'filter-toggler',
'pagination',
],
footerToolbar: [],
filter: {
title: '搜索',
mode: 'horizontal',
horizontal: {
leftFixed: 'sm',
},
{
...pagination(),
className: 'float-right',
},
{
type: 'list',
listItem: {
body: [
{
type: 'group',
body: [
{
type: 'grid',
columns: [
{
body: {
type: 'wrapper',
size: 'none',
className: 'py-2',
body: [
{
type: 'wrapper',
className: 'text-md font-bold',
size: 'none',
body: [
'${title}'
]
},
{
type: 'wrapper',
className: 'text-sm font-medium text-secondary',
size: 'none',
body: [
'${subtitle}'
]
},
]
}
},
{
sm: 2,
md: 2,
lg: 2,
columnClassName: 'content-center',
body: {
type: 'operation',
className: 'text-right',
buttons: [
{
visibleOn: '${!iframe}',
type: 'action',
label: '原文',
icon: 'fa fa-paperclip',
actionType: 'url',
url: '${url}',
blank: true,
},
{
visibleOn: '${iframe}',
type: 'action',
label: '原文',
icon: 'fa fa-firefox',
actionType: 'dialog',
dialog: {
title: '原文',
...readOnlyDialogOptions(),
size: 'xl',
body: {
type: 'iframe',
src: '${url}',
height: 1000,
}
}
},
{
type: 'action',
label: '搜索',
icon: 'fa fa-search',
actionType: 'url',
url: 'https://www.bing.com/search?q=${title}',
blank: true,
},
]
}
type: 'input-text',
label: '关键字',
name: 'filter_keyword',
placeholder: '关键字搜索',
},
]
},
{
type: 'group',
body: [
{
type: 'input-date',
label: '日期',
name: 'filter_date',
columnRatio: 4,
},
{
type: 'input-date-range',
label: '日期范围',
name: 'filter_date_start',
extraName: 'filter_date_end',
columnRatio: 8,
},
]
},
{
type: 'group',
body: [
{
type: 'select',
label: '来源',
name: 'filter_source',
multiple: true,
searchable: true,
source: {
method: 'get',
url: '${base}/overview/all_source',
adaptor: (payload, response, api, context) => {
console.log(payload, response, api, context)
return payload.map(i => {
return {label: i, value: i}
})
}
]
}
},
{
type: 'tpl',
className: 'text-current',
tpl: '${description}',
type: 'select',
label: '作者',
name: 'filter_author',
multiple: true,
searchable: true,
source: {
method: 'get',
url: '${base}/overview/all_author',
adaptor: (payload, response, api, context) => {
console.log(payload, response, api, context)
return payload.map(i => {
return {label: i, value: i}
})
}
}
},
{
type: 'tpl',
className: 'text-blue-900 text-sm mt-2',
tpl: '${name}',
type: 'select',
label: '关键词',
name: 'filter_category',
multiple: true,
searchable: true,
source: {
method: 'get',
url: '${base}/overview/all_category',
adaptor: (payload, response, api, context) => {
console.log(payload, response, api, context)
return payload.map(i => {
return {label: i, value: i}
})
}
}
},
]
}
],
actions: [
{
type: 'reset',
label: '清空'
},
{
type: 'submit',
label: '搜索',
level: 'primary',
},
]
},
canAccessSuperData: false,
listItem: {
body: [
{
type: 'grid',
columns: [
{
body: {
type: 'wrapper',
size: 'none',
className: 'py-2',
body: [
{
type: 'wrapper',
className: 'text-md font-bold',
size: 'none',
body: [
'${title}'
]
},
{
type: 'wrapper',
className: 'text-sm font-medium text-secondary',
size: 'none',
body: [
'${subtitle}'
]
},
]
}
},
{
type: 'wrapper',
size: 'none',
className: 'mt-2',
body: [
{
type: 'tag',
label: '${DATETOSTR(DATE(createTime), \'YYYY-MM-DD HH:mm\')}',
displayMode: 'rounded',
color: '#4096ff',
},
{
type: 'tag',
label: '${category}',
displayMode: 'rounded',
color: '#2fa15d',
},
{
type: 'tag',
label: '${author}',
displayMode: 'rounded',
color: '#bd6464',
},
]
},
{
type: 'each',
className: 'mt-2',
source: "${SPLIT(tags, ',')}",
items: {
type: 'tag',
label: '${item}',
displayMode: 'rounded',
color: '#6b3481',
sm: 2,
md: 2,
lg: 2,
columnClassName: 'content-center',
body: {
type: 'operation',
className: 'text-right',
buttons: [
{
visibleOn: '${!iframe}',
type: 'action',
label: '原文',
icon: 'fa fa-paperclip',
actionType: 'url',
url: '${url}',
blank: true,
},
{
visibleOn: '${iframe}',
type: 'action',
label: '原文',
icon: 'fa fa-firefox',
actionType: 'dialog',
dialog: {
title: '原文',
...readOnlyDialogOptions(),
size: 'xl',
body: {
type: 'iframe',
src: '${url}',
height: 1000,
}
}
},
{
type: 'action',
label: '搜索',
icon: 'fa fa-search',
actionType: 'url',
url: 'https://www.bing.com/search?q=${title}',
blank: true,
},
]
}
}
]
},
},
pagination(),
]
}
{
type: 'tpl',
className: 'text-current',
tpl: '${description}',
},
{
type: 'tpl',
className: 'text-blue-900 text-sm mt-2',
tpl: '${name}',
},
{
type: 'wrapper',
size: 'none',
className: 'mt-2',
body: [
{
type: 'tag',
label: '${DATETOSTR(DATE(createTime), \'YYYY-MM-DD HH:mm\')}',
displayMode: 'rounded',
color: '#4096ff',
},
{
type: 'tag',
label: '${category}',
displayMode: 'rounded',
color: '#2fa15d',
},
{
type: 'tag',
label: '${author}',
displayMode: 'rounded',
color: '#bd6464',
},
]
},
{
type: 'each',
className: 'mt-2',
source: "${SPLIT(tags, ',')}",
items: {
type: 'tag',
label: '${item}',
displayMode: 'rounded',
color: '#6b3481',
}
}
]
},
},
}
}