From d9ebc287f72494cf1ba52663d5da784cace94584 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Fri, 8 Nov 2024 12:15:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=92=8C=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + .../digtal/market/Application.kt | 5 +- .../lanyuanxiaoyao/digtal/market/Entity.kt | 10 +- .../market/controller/OverviewController.kt | 72 +++- src/main/resources/application.yml | 5 +- .../static/component/overview-tab.js | 371 +++++++++++------- 6 files changed, 323 insertions(+), 141 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c0734dc..a51339a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") diff --git a/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/Application.kt b/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/Application.kt index 4f03fe6..8bae9e8 100644 --- a/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/Application.kt +++ b/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/Application.kt @@ -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, ) -@EnableScheduling +// @EnableScheduling +@EnableFenix @ConfigurationPropertiesScan @SpringBootApplication class Application : ApplicationRunner, ApplicationListener { diff --git a/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/Entity.kt b/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/Entity.kt index b3c454f..e4a20de 100644 --- a/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/Entity.kt +++ b/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/Entity.kt @@ -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, JpaSpecificationExecutor
{ +interface ArticleRepository : JpaRepository, FenixJpaSpecificationExecutor
{ fun findAllByDescriptionIsNullAndTextIsNotNull(): List
fun findAllByHtmlIsNotNull(): List
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 + + @Query("select distinct article.author from Article article where article.author is not null and article.author <> ''") + fun findAllAuthor(): List + @Modifying @Transactional @Query("update Article article set article.pushed = :pushed where article.id = :id") diff --git a/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/controller/OverviewController.kt b/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/controller/OverviewController.kt index d9751fa..8f78b5e 100644 --- a/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/controller/OverviewController.kt +++ b/src/main/kotlin/com/lanyuanxiaoyao/digtal/market/controller/OverviewController.kt @@ -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 { - 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() + 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("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("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("code"), site.code) + ) + } + } + filterAuthor?.let { + predictions.add( + builder.equal(root.get("author"), it) + ) + } + filterCategory?.let { + predictions.add( + builder.equal(root.get("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() } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 92ab405..7567598 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -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 \ No newline at end of file + json-path: ./database/database.json +fenix: + print-banner: false + print-sql: false \ No newline at end of file diff --git a/src/main/resources/static/component/overview-tab.js b/src/main/resources/static/component/overview-tab.js index 2380044..97b3ccf 100644 --- a/src/main/resources/static/component/overview-tab.js +++ b/src/main/resources/static/component/overview-tab.js @@ -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', + } + } + ] + }, + }, } } \ No newline at end of file