feat: 增加首页过滤和搜索
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
@@ -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',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user