Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
B
brandkbs2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
shenjunjie
brandkbs2
Commits
e6abc1ff
Commit
e6abc1ff
authored
Jun 24, 2024
by
shenjunjie
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature' into 'dev'
Feature See merge request
!536
parents
8c21bc05
78fec902
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
426 additions
and
5 deletions
+426
-5
src/main/java/com/zhiwei/brandkbs2/common/RedisKeyPrefix.java
+2
-0
src/main/java/com/zhiwei/brandkbs2/controller/app/AppChannelController.java
+15
-0
src/main/java/com/zhiwei/brandkbs2/controller/app/AppSearchController.java
+58
-0
src/main/java/com/zhiwei/brandkbs2/dao/ImportantChannelDao.java
+12
-0
src/main/java/com/zhiwei/brandkbs2/dao/ImportantChannelOverviewDao.java
+12
-0
src/main/java/com/zhiwei/brandkbs2/dao/impl/ImportantChannelDaoImpl.java
+20
-0
src/main/java/com/zhiwei/brandkbs2/dao/impl/ImportantChannelOverviewDaoImpl.java
+20
-0
src/main/java/com/zhiwei/brandkbs2/exception/CustomException.java
+3
-1
src/main/java/com/zhiwei/brandkbs2/exception/ExceptionCatch.java
+2
-2
src/main/java/com/zhiwei/brandkbs2/pojo/ImportantChannel.java
+71
-0
src/main/java/com/zhiwei/brandkbs2/pojo/ImportantChannelOverview.java
+58
-0
src/main/java/com/zhiwei/brandkbs2/service/ChannelService.java
+23
-0
src/main/java/com/zhiwei/brandkbs2/service/impl/ChannelServiceImpl.java
+123
-0
src/main/java/com/zhiwei/brandkbs2/service/impl/ToolsetServiceImpl.java
+3
-2
src/main/java/com/zhiwei/brandkbs2/util/RedisUtil.java
+4
-0
No files found.
src/main/java/com/zhiwei/brandkbs2/common/RedisKeyPrefix.java
View file @
e6abc1ff
...
@@ -115,6 +115,8 @@ public class RedisKeyPrefix {
...
@@ -115,6 +115,8 @@ public class RedisKeyPrefix {
public
static
final
String
YUQING_ANALYZE_HIGH_WORD
=
"BRANDKBS:YUQING:ANALYZE:HIGH:WORD:"
;
public
static
final
String
YUQING_ANALYZE_HIGH_WORD
=
"BRANDKBS:YUQING:ANALYZE:HIGH:WORD:"
;
public
static
final
String
SEARCH_KEYWORD
=
"BRANDKBS:SEARCH:KEYWORD:"
;
public
static
String
projectWarnHotTopKeyAll
(
String
projectId
,
String
type
)
{
public
static
String
projectWarnHotTopKeyAll
(
String
projectId
,
String
type
)
{
return
RedisKeyPrefix
.
generateRedisKey
(
RedisKeyPrefix
.
PROJECT_WARN_HOT_TOP
,
projectId
,
Tools
.
concat
(
type
,
"*"
));
return
RedisKeyPrefix
.
generateRedisKey
(
RedisKeyPrefix
.
PROJECT_WARN_HOT_TOP
,
projectId
,
Tools
.
concat
(
type
,
"*"
));
}
}
...
...
src/main/java/com/zhiwei/brandkbs2/controller/app/AppChannelController.java
View file @
e6abc1ff
...
@@ -315,4 +315,19 @@ public class AppChannelController extends BaseController {
...
@@ -315,4 +315,19 @@ public class AppChannelController extends BaseController {
}
}
return
ResponseResult
.
failure
(
"渠道申请失败"
);
return
ResponseResult
.
failure
(
"渠道申请失败"
);
}
}
@ApiOperation
(
"重要渠道清单-清单列表"
)
@GetMapping
(
"/important-channel/list"
)
public
ResponseResult
getImportantChannelList
(
@RequestParam
(
value
=
"type"
,
defaultValue
=
"市监局"
)
String
type
,
@RequestParam
(
value
=
"keyword"
,
required
=
false
)
String
keyword
)
{
return
ResponseResult
.
success
(
channelService
.
getImportantChannelList
(
type
,
keyword
));
}
@ApiOperation
(
"重要渠道清单-清单列表-详情"
)
@GetMapping
(
"/important-channel/list/detail"
)
public
ResponseResult
getImportantChannelListDetail
(
@RequestParam
(
value
=
"type"
)
String
type
,
@RequestParam
(
value
=
"name"
)
String
name
,
@RequestParam
(
value
=
"keyword"
,
required
=
false
)
String
keyword
)
{
return
ResponseResult
.
success
(
channelService
.
getImportantChannelListDetail
(
type
,
name
,
keyword
));
}
}
}
src/main/java/com/zhiwei/brandkbs2/controller/app/AppSearchController.java
View file @
e6abc1ff
...
@@ -2,6 +2,7 @@ package com.zhiwei.brandkbs2.controller.app;
...
@@ -2,6 +2,7 @@ package com.zhiwei.brandkbs2.controller.app;
import
com.alibaba.fastjson.JSON
;
import
com.alibaba.fastjson.JSON
;
import
com.alibaba.fastjson.JSONArray
;
import
com.alibaba.fastjson.JSONObject
;
import
com.alibaba.fastjson.JSONObject
;
import
com.zhiwei.brandkbs2.aop.LogRecord
;
import
com.zhiwei.brandkbs2.aop.LogRecord
;
import
com.zhiwei.brandkbs2.auth.Auth
;
import
com.zhiwei.brandkbs2.auth.Auth
;
...
@@ -17,10 +18,12 @@ import com.zhiwei.brandkbs2.pojo.dto.*;
...
@@ -17,10 +18,12 @@ import com.zhiwei.brandkbs2.pojo.dto.*;
import
com.zhiwei.brandkbs2.pojo.vo.ChannelListVO
;
import
com.zhiwei.brandkbs2.pojo.vo.ChannelListVO
;
import
com.zhiwei.brandkbs2.pojo.vo.PageVO
;
import
com.zhiwei.brandkbs2.pojo.vo.PageVO
;
import
com.zhiwei.brandkbs2.service.*
;
import
com.zhiwei.brandkbs2.service.*
;
import
com.zhiwei.brandkbs2.util.RedisUtil
;
import
com.zhiwei.brandkbs2.util.Tools
;
import
com.zhiwei.brandkbs2.util.Tools
;
import
com.zhiwei.middleware.event.pojo.dto.BrandkbsEventSearchDTO
;
import
com.zhiwei.middleware.event.pojo.dto.BrandkbsEventSearchDTO
;
import
io.swagger.annotations.Api
;
import
io.swagger.annotations.Api
;
import
io.swagger.annotations.ApiOperation
;
import
io.swagger.annotations.ApiOperation
;
import
io.swagger.annotations.ApiParam
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.commons.lang3.time.DateUtils
;
import
org.apache.commons.lang3.time.DateUtils
;
import
org.apache.commons.lang3.tuple.Pair
;
import
org.apache.commons.lang3.tuple.Pair
;
...
@@ -33,9 +36,11 @@ import org.springframework.web.bind.annotation.*;
...
@@ -33,9 +36,11 @@ import org.springframework.web.bind.annotation.*;
import
org.springframework.web.client.RestTemplate
;
import
org.springframework.web.client.RestTemplate
;
import
javax.annotation.Resource
;
import
javax.annotation.Resource
;
import
java.util.ArrayList
;
import
java.util.Date
;
import
java.util.Date
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.Objects
;
import
java.util.concurrent.TimeUnit
;
/**
/**
* @author cjz
* @author cjz
...
@@ -63,6 +68,8 @@ public class AppSearchController extends BaseController {
...
@@ -63,6 +68,8 @@ public class AppSearchController extends BaseController {
@Value
(
"${ef.search.url}"
)
@Value
(
"${ef.search.url}"
)
private
String
getEfSearchUrl
;
private
String
getEfSearchUrl
;
private
static
final
int
SEARCH_KEYWORD_CACHE_MAX_SIZE
=
10
;
@Resource
(
name
=
"markDataServiceImpl"
)
@Resource
(
name
=
"markDataServiceImpl"
)
MarkDataService
markDataService
;
MarkDataService
markDataService
;
...
@@ -84,6 +91,9 @@ public class AppSearchController extends BaseController {
...
@@ -84,6 +91,9 @@ public class AppSearchController extends BaseController {
@Resource
(
name
=
"extraServiceImpl"
)
@Resource
(
name
=
"extraServiceImpl"
)
ExtraService
extraService
;
ExtraService
extraService
;
@Resource
(
name
=
"redisUtil"
)
RedisUtil
redisUtil
;
@ApiOperation
(
"搜索-查热点"
)
@ApiOperation
(
"搜索-查热点"
)
@LogRecord
(
values
=
"keyword"
,
description
=
"查热点"
,
arguments
=
true
,
entity
=
false
)
@LogRecord
(
values
=
"keyword"
,
description
=
"查热点"
,
arguments
=
true
,
entity
=
false
)
@GetMapping
(
"/hot/list"
)
@GetMapping
(
"/hot/list"
)
...
@@ -94,6 +104,7 @@ public class AppSearchController extends BaseController {
...
@@ -94,6 +104,7 @@ public class AppSearchController extends BaseController {
@RequestParam
(
value
=
"sort"
)
String
sort
,
@RequestParam
(
value
=
"sort"
)
String
sort
,
@RequestParam
(
value
=
"startTime"
,
required
=
false
)
Long
startTime
,
@RequestParam
(
value
=
"startTime"
,
required
=
false
)
Long
startTime
,
@RequestParam
(
value
=
"endTime"
,
required
=
false
)
Long
endTime
)
{
@RequestParam
(
value
=
"endTime"
,
required
=
false
)
Long
endTime
)
{
cacheSearchKeyword
(
keyword
,
"hot"
);
ResponseEntity
<
JSONObject
>
jsonObjectResponseEntity
=
restTemplate
.
getForEntity
(
trendsSearchUrl
,
JSONObject
.
class
,
limit
,
page
,
type
,
keyword
,
sort
,
startTime
,
endTime
);
ResponseEntity
<
JSONObject
>
jsonObjectResponseEntity
=
restTemplate
.
getForEntity
(
trendsSearchUrl
,
JSONObject
.
class
,
limit
,
page
,
type
,
keyword
,
sort
,
startTime
,
endTime
);
JSONObject
result
=
jsonObjectResponseEntity
.
getBody
();
JSONObject
result
=
jsonObjectResponseEntity
.
getBody
();
if
(
Objects
.
nonNull
(
result
))
{
if
(
Objects
.
nonNull
(
result
))
{
...
@@ -109,6 +120,7 @@ public class AppSearchController extends BaseController {
...
@@ -109,6 +120,7 @@ public class AppSearchController extends BaseController {
public
ResponseResult
crisisSearch
(
@RequestParam
(
value
=
"page"
,
defaultValue
=
"1"
)
Integer
page
,
public
ResponseResult
crisisSearch
(
@RequestParam
(
value
=
"page"
,
defaultValue
=
"1"
)
Integer
page
,
@RequestParam
(
value
=
"pageSize"
,
defaultValue
=
"3"
)
Integer
pageSize
,
@RequestParam
(
value
=
"pageSize"
,
defaultValue
=
"3"
)
Integer
pageSize
,
@RequestParam
(
"keyword"
)
String
keyword
)
{
@RequestParam
(
"keyword"
)
String
keyword
)
{
cacheSearchKeyword
(
keyword
,
"crisis"
);
ResponseEntity
<
String
>
responseEntity
=
restTemplate
.
getForEntity
(
crisisSearchUrl
,
String
.
class
,
page
,
pageSize
,
keyword
);
ResponseEntity
<
String
>
responseEntity
=
restTemplate
.
getForEntity
(
crisisSearchUrl
,
String
.
class
,
page
,
pageSize
,
keyword
);
Object
result
=
JSON
.
parseObject
(
responseEntity
.
getBody
()).
get
(
"data"
);
Object
result
=
JSON
.
parseObject
(
responseEntity
.
getBody
()).
get
(
"data"
);
return
ResponseResult
.
success
(
result
);
return
ResponseResult
.
success
(
result
);
...
@@ -132,6 +144,7 @@ public class AppSearchController extends BaseController {
...
@@ -132,6 +144,7 @@ public class AppSearchController extends BaseController {
@RequestParam
(
value
=
"page"
,
defaultValue
=
"1"
)
Integer
page
,
@RequestParam
(
value
=
"page"
,
defaultValue
=
"1"
)
Integer
page
,
@RequestParam
(
value
=
"pageSize"
,
defaultValue
=
"20"
)
Integer
size
)
{
@RequestParam
(
value
=
"pageSize"
,
defaultValue
=
"20"
)
Integer
size
)
{
String
name
=
keyword
.
trim
();
String
name
=
keyword
.
trim
();
cacheSearchKeyword
(
keyword
,
"event"
);
ResponseEntity
<
String
>
responseEntity
=
restTemplate
.
getForEntity
(
getEfSearchUrl
,
String
.
class
,
name
,
page
,
size
);
ResponseEntity
<
String
>
responseEntity
=
restTemplate
.
getForEntity
(
getEfSearchUrl
,
String
.
class
,
name
,
page
,
size
);
JSONObject
result
=
JSON
.
parseObject
(
responseEntity
.
getBody
());
JSONObject
result
=
JSON
.
parseObject
(
responseEntity
.
getBody
());
return
ResponseResult
.
success
(
result
);
return
ResponseResult
.
success
(
result
);
...
@@ -151,6 +164,7 @@ public class AppSearchController extends BaseController {
...
@@ -151,6 +164,7 @@ public class AppSearchController extends BaseController {
if
(
StringUtils
.
isNotEmpty
(
dto
.
getKeyword
())
&&
Tools
.
checkUniteString
(
dto
.
getKeyword
())){
if
(
StringUtils
.
isNotEmpty
(
dto
.
getKeyword
())
&&
Tools
.
checkUniteString
(
dto
.
getKeyword
())){
return
ResponseResult
.
failure
(
"不支持特殊符号字段查询"
);
return
ResponseResult
.
failure
(
"不支持特殊符号字段查询"
);
}
}
cacheSearchKeyword
(
dto
.
getKeyword
(),
"whole"
);
// 针对商业数据库做限制
// 针对商业数据库做限制
if
(
dto
.
isExternalDataSource
())
{
if
(
dto
.
isExternalDataSource
())
{
long
time
=
DateUtils
.
addDays
(
Tools
.
truncDate
(
new
Date
(),
Constant
.
DAY_PATTERN
),
-
89
).
getTime
();
long
time
=
DateUtils
.
addDays
(
Tools
.
truncDate
(
new
Date
(),
Constant
.
DAY_PATTERN
),
-
89
).
getTime
();
...
@@ -220,6 +234,7 @@ public class AppSearchController extends BaseController {
...
@@ -220,6 +234,7 @@ public class AppSearchController extends BaseController {
if
(
StringUtils
.
isNotEmpty
(
markSearchDTO
.
getKeyword
())
&&
Tools
.
checkUniteString
(
markSearchDTO
.
getKeyword
())){
if
(
StringUtils
.
isNotEmpty
(
markSearchDTO
.
getKeyword
())
&&
Tools
.
checkUniteString
(
markSearchDTO
.
getKeyword
())){
return
ResponseResult
.
failure
(
"不支持特殊符号字段查询"
);
return
ResponseResult
.
failure
(
"不支持特殊符号字段查询"
);
}
}
cacheSearchKeyword
(
markSearchDTO
.
getKeyword
(),
"yuqing"
);
PageVO
<
MarkFlowEntity
>
yuqingMarkList
=
markDataService
.
getYuqingMarkList
(
markSearchDTO
);
PageVO
<
MarkFlowEntity
>
yuqingMarkList
=
markDataService
.
getYuqingMarkList
(
markSearchDTO
);
// 仅第一页增加平台进量(声量)统计
// 仅第一页增加平台进量(声量)统计
if
(
1
==
markSearchDTO
.
getPage
())
{
if
(
1
==
markSearchDTO
.
getPage
())
{
...
@@ -238,6 +253,7 @@ public class AppSearchController extends BaseController {
...
@@ -238,6 +253,7 @@ public class AppSearchController extends BaseController {
@LogRecord
(
values
=
"keyword"
,
description
=
"查原始数据"
,
arguments
=
true
,
entity
=
true
)
@LogRecord
(
values
=
"keyword"
,
description
=
"查原始数据"
,
arguments
=
true
,
entity
=
true
)
@PostMapping
(
"/origin/list"
)
@PostMapping
(
"/origin/list"
)
public
ResponseResult
getOriginList
(
@RequestBody
MarkSearchDTO
markSearchDTO
)
{
public
ResponseResult
getOriginList
(
@RequestBody
MarkSearchDTO
markSearchDTO
)
{
cacheSearchKeyword
(
markSearchDTO
.
getKeyword
(),
"yuqing"
);
PageVO
<
MarkFlowEntity
>
originList
=
markDataService
.
getOriginList
(
markSearchDTO
);
PageVO
<
MarkFlowEntity
>
originList
=
markDataService
.
getOriginList
(
markSearchDTO
);
// 仅第一页增加平台进量(声量)统计
// 仅第一页增加平台进量(声量)统计
if
(
1
==
markSearchDTO
.
getPage
()
&&
!
Objects
.
equals
(
"视频"
,
markSearchDTO
.
getSearchType
()))
{
if
(
1
==
markSearchDTO
.
getPage
()
&&
!
Objects
.
equals
(
"视频"
,
markSearchDTO
.
getSearchType
()))
{
...
@@ -276,6 +292,7 @@ public class AppSearchController extends BaseController {
...
@@ -276,6 +292,7 @@ public class AppSearchController extends BaseController {
@LogRecord
(
values
=
"keyword"
,
description
=
"查渠道"
,
arguments
=
true
,
entity
=
true
)
@LogRecord
(
values
=
"keyword"
,
description
=
"查渠道"
,
arguments
=
true
,
entity
=
true
)
@PostMapping
(
value
=
"/channel/channelList"
)
@PostMapping
(
value
=
"/channel/channelList"
)
public
ResponseResult
getChannelList
(
@RequestBody
ChannelSearchDTO
channelSearchDTO
)
{
public
ResponseResult
getChannelList
(
@RequestBody
ChannelSearchDTO
channelSearchDTO
)
{
cacheSearchKeyword
(
channelSearchDTO
.
getKeyword
(),
"channel"
);
return
ResponseResult
.
success
(
channelService
.
getChannelListNew
(
channelSearchDTO
.
getPage
(),
channelSearchDTO
.
getPageSize
(),
return
ResponseResult
.
success
(
channelService
.
getChannelListNew
(
channelSearchDTO
.
getPage
(),
channelSearchDTO
.
getPageSize
(),
channelSearchDTO
.
getKeyword
(),
channelSearchDTO
.
getPlatform
(),
channelSearchDTO
.
getEmotions
(),
channelSearchDTO
.
getMediaTypes
(),
channelSearchDTO
.
getArticlesCount
(),
channelSearchDTO
.
getSorter
()));
channelSearchDTO
.
getKeyword
(),
channelSearchDTO
.
getPlatform
(),
channelSearchDTO
.
getEmotions
(),
channelSearchDTO
.
getMediaTypes
(),
channelSearchDTO
.
getArticlesCount
(),
channelSearchDTO
.
getSorter
()));
}
}
...
@@ -314,6 +331,7 @@ public class AppSearchController extends BaseController {
...
@@ -314,6 +331,7 @@ public class AppSearchController extends BaseController {
@LogRecord
(
values
=
"keyword"
,
description
=
"查事件"
,
arguments
=
true
,
entity
=
true
)
@LogRecord
(
values
=
"keyword"
,
description
=
"查事件"
,
arguments
=
true
,
entity
=
true
)
@PostMapping
(
"/event/newList"
)
@PostMapping
(
"/event/newList"
)
public
ResponseResult
getEventListMiddleware
(
@RequestBody
BrandkbsEventSearchDTO
dto
){
public
ResponseResult
getEventListMiddleware
(
@RequestBody
BrandkbsEventSearchDTO
dto
){
cacheSearchKeyword
(
dto
.
getKeyword
(),
"event"
);
return
ResponseResult
.
success
(
eventService
.
getEventListMiddleware
(
dto
));
return
ResponseResult
.
success
(
eventService
.
getEventListMiddleware
(
dto
));
}
}
...
@@ -324,6 +342,7 @@ public class AppSearchController extends BaseController {
...
@@ -324,6 +342,7 @@ public class AppSearchController extends BaseController {
if
(
StringUtils
.
isNotEmpty
(
markSearchDTO
.
getKeyword
())
&&
Tools
.
checkUniteString
(
markSearchDTO
.
getKeyword
())){
if
(
StringUtils
.
isNotEmpty
(
markSearchDTO
.
getKeyword
())
&&
Tools
.
checkUniteString
(
markSearchDTO
.
getKeyword
())){
return
ResponseResult
.
failure
(
"不支持特殊符号字段查询"
);
return
ResponseResult
.
failure
(
"不支持特殊符号字段查询"
);
}
}
cacheSearchKeyword
(
markSearchDTO
.
getKeyword
(),
"contend"
);
return
ResponseResult
.
success
(
markDataService
.
getContendSearchList
(
markSearchDTO
));
return
ResponseResult
.
success
(
markDataService
.
getContendSearchList
(
markSearchDTO
));
}
}
...
@@ -333,4 +352,43 @@ public class AppSearchController extends BaseController {
...
@@ -333,4 +352,43 @@ public class AppSearchController extends BaseController {
return
ResponseResult
.
success
(
markDataService
.
getContendSearchCriteria
(
contendId
));
return
ResponseResult
.
success
(
markDataService
.
getContendSearchCriteria
(
contendId
));
}
}
@ApiOperation
(
"搜索-搜索关键词历史记录"
)
@GetMapping
(
"/keyword/cache"
)
public
ResponseResult
getSearchKeywordCache
(
@ApiParam
(
name
=
"searchType"
,
value
=
"查舆情:yuqing,查渠道:channel,查事件:event,查热点:hot,查危机:crisis,查竞品:contend,全网搜:whole"
,
required
=
true
)
@RequestParam
(
value
=
"searchType"
,
defaultValue
=
"yuqing"
)
String
searchType
)
{
return
ResponseResult
.
success
(
getKeywordCache
(
searchType
));
}
/**
* 获取搜索关键词历史记录
* @param searchType
* @return
*/
private
List
<
String
>
getKeywordCache
(
String
searchType
){
String
key
=
RedisUtil
.
getSearchKeywordCacheKey
(
UserThreadLocal
.
getProjectId
(),
UserThreadLocal
.
getUserId
(),
searchType
);
String
value
=
redisUtil
.
get
(
key
);
return
JSONArray
.
parseArray
(
value
,
String
.
class
);
}
/**
* 记录搜索关键词历史记录
* @param keyword
* @param searchType
*/
private
void
cacheSearchKeyword
(
String
keyword
,
String
searchType
){
String
key
=
RedisUtil
.
getSearchKeywordCacheKey
(
UserThreadLocal
.
getProjectId
(),
UserThreadLocal
.
getUserId
(),
searchType
);
String
value
=
redisUtil
.
get
(
key
);
List
<
String
>
keywords
=
Objects
.
isNull
(
JSONArray
.
parseArray
(
value
,
String
.
class
))
?
new
ArrayList
<>(
SEARCH_KEYWORD_CACHE_MAX_SIZE
)
:
JSONArray
.
parseArray
(
value
,
String
.
class
);
if
(
StringUtils
.
isNotBlank
(
keyword
)){
keywords
.
remove
(
keyword
);
keywords
.
add
(
0
,
keyword
);
if
(
keywords
.
size
()
>
SEARCH_KEYWORD_CACHE_MAX_SIZE
){
keywords
.
remove
(
SEARCH_KEYWORD_CACHE_MAX_SIZE
);
}
}
redisUtil
.
setExpire
(
key
,
JSONObject
.
toJSONString
(
keywords
),
7
,
TimeUnit
.
DAYS
);
}
}
}
src/main/java/com/zhiwei/brandkbs2/dao/ImportantChannelDao.java
0 → 100644
View file @
e6abc1ff
package
com
.
zhiwei
.
brandkbs2
.
dao
;
import
com.zhiwei.brandkbs2.pojo.ImportantChannel
;
/**
* @ClassName: ImportantChannelDao
* @Description 重要渠道清单dao
* @author: cjz
* @date: 2024-06-14 14:03
*/
public
interface
ImportantChannelDao
extends
BaseMongoDao
<
ImportantChannel
>{
}
src/main/java/com/zhiwei/brandkbs2/dao/ImportantChannelOverviewDao.java
0 → 100644
View file @
e6abc1ff
package
com
.
zhiwei
.
brandkbs2
.
dao
;
import
com.zhiwei.brandkbs2.pojo.ImportantChannelOverview
;
/**
* @ClassName: ImportantChannelOverviewDao
* @Description 重要渠道清单概览dao
* @author: cjz
* @date: 2024-06-17 10:03
*/
public
interface
ImportantChannelOverviewDao
extends
BaseMongoDao
<
ImportantChannelOverview
>{
}
src/main/java/com/zhiwei/brandkbs2/dao/impl/ImportantChannelDaoImpl.java
0 → 100644
View file @
e6abc1ff
package
com
.
zhiwei
.
brandkbs2
.
dao
.
impl
;
import
com.zhiwei.brandkbs2.dao.ImportantChannelDao
;
import
com.zhiwei.brandkbs2.pojo.ImportantChannel
;
import
org.springframework.stereotype.Component
;
/**
* @ClassName: ImportantChannelDaoImpl
* @Description 重要渠道清单dao
* @author: cjz
* @date: 2024-06-14 14:03
*/
@Component
(
"importantChannelDao"
)
public
class
ImportantChannelDaoImpl
extends
BaseMongoDaoImpl
<
ImportantChannel
>
implements
ImportantChannelDao
{
private
static
final
String
COLLECTION_NAME
=
"brandkbs_important_channel"
;
public
ImportantChannelDaoImpl
()
{
super
(
COLLECTION_NAME
);
}
}
src/main/java/com/zhiwei/brandkbs2/dao/impl/ImportantChannelOverviewDaoImpl.java
0 → 100644
View file @
e6abc1ff
package
com
.
zhiwei
.
brandkbs2
.
dao
.
impl
;
import
com.zhiwei.brandkbs2.dao.ImportantChannelOverviewDao
;
import
com.zhiwei.brandkbs2.pojo.ImportantChannelOverview
;
import
org.springframework.stereotype.Component
;
/**
* @ClassName: ImportantChannelOverviewDao
* @Description 重要渠道清单概览dao
* @author: cjz
* @date: 2024-06-17 10:03
*/
@Component
(
"importantChannelOverviewDao"
)
public
class
ImportantChannelOverviewDaoImpl
extends
BaseMongoDaoImpl
<
ImportantChannelOverview
>
implements
ImportantChannelOverviewDao
{
private
static
final
String
COLLECTION_NAME
=
"brandkbs_important_channel_overview"
;
public
ImportantChannelOverviewDaoImpl
()
{
super
(
COLLECTION_NAME
);
}
}
src/main/java/com/zhiwei/brandkbs2/exception/CustomException.java
View file @
e6abc1ff
...
@@ -20,12 +20,14 @@ public class CustomException extends RuntimeException {
...
@@ -20,12 +20,14 @@ public class CustomException extends RuntimeException {
private
final
Exception
exception
;
private
final
Exception
exception
;
public
CustomException
(
ResultCode
resultCode
)
{
public
CustomException
(
ResultCode
resultCode
)
{
super
(
resultCode
.
message
());
this
.
resultCode
=
resultCode
;
this
.
resultCode
=
resultCode
;
this
.
errorMessage
=
null
;
this
.
errorMessage
=
resultCode
.
message
()
;
this
.
exception
=
null
;
this
.
exception
=
null
;
}
}
public
CustomException
(
ResultCode
resultCode
,
String
errorMessage
,
Exception
e
)
{
public
CustomException
(
ResultCode
resultCode
,
String
errorMessage
,
Exception
e
)
{
super
(
errorMessage
);
this
.
resultCode
=
resultCode
;
this
.
resultCode
=
resultCode
;
this
.
errorMessage
=
errorMessage
;
this
.
errorMessage
=
errorMessage
;
this
.
exception
=
e
;
this
.
exception
=
e
;
...
...
src/main/java/com/zhiwei/brandkbs2/exception/ExceptionCatch.java
View file @
e6abc1ff
...
@@ -34,9 +34,9 @@ public class ExceptionCatch {
...
@@ -34,9 +34,9 @@ public class ExceptionCatch {
Exception
exception
=
customException
.
getException
();
Exception
exception
=
customException
.
getException
();
//记录日志
//记录日志
if
(
null
==
exception
)
{
if
(
null
==
exception
)
{
log
.
info
(
"catch exception-custom:{}"
,
customException
.
getErrorMessage
()
);
log
.
error
(
"catch exception-custom:{}"
,
customException
.
getErrorMessage
(),
customException
);
}
else
{
}
else
{
log
.
error
(
"catch exception
-custom
:{}"
,
customException
.
getErrorMessage
(),
exception
);
log
.
error
(
"catch exception:{}"
,
customException
.
getErrorMessage
(),
exception
);
}
}
return
new
ResponseResult
(
resultCode
,
Collections
.
EMPTY_LIST
);
return
new
ResponseResult
(
resultCode
,
Collections
.
EMPTY_LIST
);
}
}
...
...
src/main/java/com/zhiwei/brandkbs2/pojo/ImportantChannel.java
0 → 100644
View file @
e6abc1ff
package
com
.
zhiwei
.
brandkbs2
.
pojo
;
import
lombok.Getter
;
import
lombok.Setter
;
import
java.util.List
;
/**
* @author cjz
* @version 1.0
* @description 重要渠道清单
* @date 2024年6月14日10:06:46
*/
@Getter
@Setter
public
class
ImportantChannel
extends
AbstractBaseMongo
{
/**
* 机构类型
*/
private
String
institution
;
/**
* 机构标签
*/
private
List
<
String
>
institutionTags
;
/**
* 地域
*/
private
String
region
;
/**
* 细分地域
*/
private
String
subdivideRegion
;
/**
* 平台
*/
private
String
platform
;
/**
* 渠道
*/
private
String
source
;
/**
* 媒体类型
*/
private
String
media
;
/**
* 媒体标签
*/
private
List
<
String
>
mediaTags
;
/**
* 主体
*/
private
String
mainBody
;
/**
* 单位名称
*/
private
String
organization
;
/**
* 应用程序
*/
private
String
application
;
/**
* 创建时间
*/
private
Long
cTime
;
/**
* 修改时间
*/
private
Long
uTime
;
}
src/main/java/com/zhiwei/brandkbs2/pojo/ImportantChannelOverview.java
0 → 100644
View file @
e6abc1ff
package
com
.
zhiwei
.
brandkbs2
.
pojo
;
import
com.alibaba.fastjson.JSONObject
;
import
lombok.Getter
;
import
lombok.Setter
;
import
java.util.List
;
/**
* @author cjz
* @version 1.0
* @description 重要渠道清单
* @date 2024年6月14日10:06:46
*/
@Getter
@Setter
public
class
ImportantChannelOverview
extends
AbstractBaseMongo
{
/**
* 机构类型
*/
private
String
type
;
/**
* 机构标签
*/
private
List
<
String
>
tags
;
// /**
// * 媒体类型
// */
// private String media;
// /**
// * 媒体标签
// */
// private List<String> mediaTags;
// /**
// * 地域
// */
// private String region;
//
// /**
// * 单位名称
// */
// private String mainBody;
/**
* 具体信息
*/
private
List
<
JSONObject
>
info
;
// private List<String> source;
/**
* 创建时间
*/
private
Long
cTime
;
/**
* 修改时间
*/
private
Long
uTime
;
}
src/main/java/com/zhiwei/brandkbs2/service/ChannelService.java
View file @
e6abc1ff
...
@@ -309,5 +309,28 @@ public interface ChannelService {
...
@@ -309,5 +309,28 @@ public interface ChannelService {
*/
*/
Map
<
String
,
JSONObject
>
getProjectEmotionChannelListData
()
throws
IOException
;
Map
<
String
,
JSONObject
>
getProjectEmotionChannelListData
()
throws
IOException
;
/**
* 匹配舆情重要渠道
* @param linkedGroupId
* @param source
* @return
*/
JSONObject
matchYuQingSensitiveChannel
(
String
linkedGroupId
,
String
source
);
JSONObject
matchYuQingSensitiveChannel
(
String
linkedGroupId
,
String
source
);
/**
* 获取重要渠道清单列表
* @param type
* @param keyword
* @return
*/
List
<
JSONObject
>
getImportantChannelList
(
String
type
,
String
keyword
);
/**
* 获取重要渠道清单列表详情
* @param type
* @param name
* @param keyword
* @return
*/
JSONObject
getImportantChannelListDetail
(
String
type
,
String
name
,
String
keyword
);
}
}
src/main/java/com/zhiwei/brandkbs2/service/impl/ChannelServiceImpl.java
View file @
e6abc1ff
...
@@ -69,6 +69,7 @@ import java.math.BigDecimal;
...
@@ -69,6 +69,7 @@ import java.math.BigDecimal;
import
java.math.RoundingMode
;
import
java.math.RoundingMode
;
import
java.util.*
;
import
java.util.*
;
import
java.util.concurrent.CompletableFuture
;
import
java.util.concurrent.CompletableFuture
;
import
java.util.regex.Pattern
;
import
java.util.stream.Collectors
;
import
java.util.stream.Collectors
;
/**
/**
...
@@ -120,6 +121,12 @@ public class ChannelServiceImpl implements ChannelService {
...
@@ -120,6 +121,12 @@ public class ChannelServiceImpl implements ChannelService {
@Resource
(
name
=
"eventMiddlewareDao"
)
@Resource
(
name
=
"eventMiddlewareDao"
)
EventMiddlewareDao
eventMiddlewareDao
;
EventMiddlewareDao
eventMiddlewareDao
;
@Resource
(
name
=
"importantChannelDao"
)
private
ImportantChannelDao
importantChannelDao
;
@Resource
(
name
=
"importantChannelOverviewDao"
)
private
ImportantChannelOverviewDao
importantChannelOverviewDao
;
@Resource
(
name
=
"mongoUtil"
)
@Resource
(
name
=
"mongoUtil"
)
MongoUtil
mongoUtil
;
MongoUtil
mongoUtil
;
...
@@ -759,6 +766,122 @@ public class ChannelServiceImpl implements ChannelService {
...
@@ -759,6 +766,122 @@ public class ChannelServiceImpl implements ChannelService {
}
}
@Override
@Override
public
List
<
JSONObject
>
getImportantChannelList
(
String
type
,
String
keyword
)
{
Query
query
=
new
Query
();
query
.
addCriteria
(
Criteria
.
where
(
"type"
).
is
(
type
));
ImportantChannelOverview
overview
=
importantChannelOverviewDao
.
findOne
(
"type"
,
type
);
if
(
StringUtils
.
isNotEmpty
(
keyword
)){
List
<
JSONObject
>
res
=
new
ArrayList
<>();
Pattern
pattern
=
Pattern
.
compile
(
keyword
);
for
(
JSONObject
jsonObject
:
overview
.
getInfo
())
{
List
<
String
>
sources
=
jsonObject
.
getJSONArray
(
"source"
).
toJavaList
(
String
.
class
);
for
(
String
source
:
sources
)
{
if
(
pattern
.
matcher
(
source
).
find
()){
res
.
add
(
jsonObject
);
break
;
}
}
}
return
res
;
}
return
overview
.
getInfo
();
}
@Override
public
JSONObject
getImportantChannelListDetail
(
String
type
,
String
name
,
String
keyword
)
{
String
searchType
=
Arrays
.
asList
(
"18家媒体"
,
"区域媒体"
).
contains
(
type
)
?
"media"
:
"institution"
;
String
searchName
=
Objects
.
equals
(
"18家媒体"
,
type
)
?
"mainBody"
:
"region"
;
List
<
String
>
platformSort
=
Arrays
.
asList
(
"18家媒体"
,
"区域媒体"
).
contains
(
type
)
?
Arrays
.
asList
(
"新浪微博"
,
"微信"
,
"网媒"
,
"B站"
,
"头条"
,
"抖音"
)
:
Arrays
.
asList
(
"微信公众号"
,
"新浪微博"
,
"网媒"
);
// query
Query
query
=
new
Query
();
query
.
addCriteria
(
Criteria
.
where
(
searchType
).
is
(
type
));
query
.
addCriteria
(
Criteria
.
where
(
searchName
).
is
(
name
));
if
(
StringUtils
.
isNotEmpty
(
keyword
)){
importantChannelOverviewDao
.
addKeywordFuzz
(
query
,
keyword
,
"source"
);
}
List
<
ImportantChannel
>
list
=
importantChannelDao
.
findList
(
query
);
// 顺序需按照平台命中 例:微博不为空的优先放前面
List
<
ImportantChannel
>
sortList
=
new
ArrayList
<>();
Map
<
String
,
List
<
ImportantChannel
>>
platformGroup
=
list
.
stream
().
collect
(
Collectors
.
groupingBy
(
ImportantChannel:
:
getPlatform
,
LinkedHashMap:
:
new
,
Collectors
.
toList
()));
for
(
String
platform
:
platformSort
){
if
(
Objects
.
nonNull
(
platformGroup
.
get
(
platform
))){
sortList
.
addAll
(
platformGroup
.
get
(
platform
));
}
}
if
(
Objects
.
equals
(
"区域媒体"
,
type
)){
return
regionMediaDataProcess
(
sortList
);
}
else
if
(
Arrays
.
asList
(
"纪检委"
,
"政法委"
).
contains
(
type
)){
return
provinceDataProcess
(
sortList
);
}
else
{
return
dataProcess
(
sortList
,
type
);
}
}
private
JSONObject
provinceDataProcess
(
List
<
ImportantChannel
>
sortList
){
List
<
JSONObject
>
resList
=
new
ArrayList
<>();
JSONObject
jsonObject
=
new
JSONObject
(
new
LinkedHashMap
<>());
LinkedHashMap
<
String
,
List
<
ImportantChannel
>>
platformMap
=
sortList
.
stream
().
collect
(
Collectors
.
groupingBy
(
ImportantChannel:
:
getPlatform
,
LinkedHashMap:
:
new
,
Collectors
.
toList
()));
for
(
Map
.
Entry
<
String
,
List
<
ImportantChannel
>>
entry
:
platformMap
.
entrySet
())
{
jsonObject
.
put
(
entry
.
getKey
(),
entry
.
getValue
().
stream
().
map
(
ImportantChannel:
:
getSource
).
collect
(
Collectors
.
toList
()));
}
resList
.
add
(
jsonObject
);
JSONObject
res
=
new
JSONObject
();
res
.
put
(
"count"
,
sortList
.
size
());
res
.
put
(
"list"
,
resList
);
return
res
;
}
private
JSONObject
dataProcess
(
List
<
ImportantChannel
>
sortList
,
String
type
){
Map
<
String
,
List
<
ImportantChannel
>>
map
=
Objects
.
equals
(
"18家媒体"
,
type
)
?
sortList
.
stream
().
collect
(
Collectors
.
groupingBy
(
ImportantChannel:
:
getOrganization
,
LinkedHashMap:
:
new
,
Collectors
.
toList
()))
:
sortList
.
stream
().
collect
(
Collectors
.
groupingBy
(
ImportantChannel:
:
getSubdivideRegion
,
LinkedHashMap:
:
new
,
Collectors
.
toList
()));
List
<
JSONObject
>
resList
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
String
,
List
<
ImportantChannel
>>
entry
:
map
.
entrySet
())
{
JSONObject
jsonObject
=
new
JSONObject
(
new
LinkedHashMap
<>());
jsonObject
.
put
(
Objects
.
equals
(
"18家媒体"
,
type
)
?
"单位名称"
:
"细分地域"
,
entry
.
getKey
());
if
(
Objects
.
equals
(
"18家媒体"
,
type
))
{
jsonObject
.
put
(
"应用程序"
,
entry
.
getValue
().
stream
().
map
(
ImportantChannel:
:
getApplication
).
filter
(
Objects:
:
nonNull
).
distinct
().
collect
(
Collectors
.
toList
()));
}
LinkedHashMap
<
String
,
List
<
ImportantChannel
>>
platformMap
=
entry
.
getValue
().
stream
().
collect
(
Collectors
.
groupingBy
(
ImportantChannel:
:
getPlatform
,
LinkedHashMap:
:
new
,
Collectors
.
toList
()));
for
(
Map
.
Entry
<
String
,
List
<
ImportantChannel
>>
platformEntry
:
platformMap
.
entrySet
())
{
jsonObject
.
put
(
platformEntry
.
getKey
(),
platformEntry
.
getValue
().
stream
().
map
(
ImportantChannel:
:
getSource
).
collect
(
Collectors
.
toList
()));
}
resList
.
add
(
jsonObject
);
}
JSONObject
res
=
new
JSONObject
();
res
.
put
(
"count"
,
sortList
.
size
());
res
.
put
(
"list"
,
resList
);
return
res
;
}
private
JSONObject
regionMediaDataProcess
(
List
<
ImportantChannel
>
sortList
){
Map
<
String
,
List
<
ImportantChannel
>>
map
=
sortList
.
stream
().
collect
(
Collectors
.
groupingBy
(
ImportantChannel:
:
getMainBody
,
LinkedHashMap:
:
new
,
Collectors
.
toList
()));
List
<
JSONObject
>
resList
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
String
,
List
<
ImportantChannel
>>
entry
:
map
.
entrySet
())
{
JSONObject
mainBodyJson
=
new
JSONObject
(
new
LinkedHashMap
<>());
mainBodyJson
.
put
(
"主体"
,
entry
.
getKey
());
LinkedHashMap
<
String
,
List
<
ImportantChannel
>>
organizationMap
=
entry
.
getValue
().
stream
().
collect
(
Collectors
.
groupingBy
(
ImportantChannel:
:
getOrganization
,
LinkedHashMap:
:
new
,
Collectors
.
toList
()));
List
<
JSONObject
>
platformJson
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
String
,
List
<
ImportantChannel
>>
organizationEntry
:
organizationMap
.
entrySet
())
{
LinkedHashMap
<
String
,
List
<
ImportantChannel
>>
platformMap
=
organizationEntry
.
getValue
().
stream
().
collect
(
Collectors
.
groupingBy
(
ImportantChannel:
:
getPlatform
,
LinkedHashMap:
:
new
,
Collectors
.
toList
()));
JSONObject
dataJson
=
new
JSONObject
(
new
LinkedHashMap
<>());
dataJson
.
put
(
"单位名称"
,
organizationEntry
.
getKey
());
dataJson
.
put
(
"应用程序"
,
organizationEntry
.
getValue
().
stream
().
map
(
ImportantChannel:
:
getApplication
).
filter
(
Objects:
:
nonNull
).
distinct
().
collect
(
Collectors
.
toList
()));
for
(
Map
.
Entry
<
String
,
List
<
ImportantChannel
>>
platformEntry
:
platformMap
.
entrySet
())
{
dataJson
.
put
(
platformEntry
.
getKey
(),
platformEntry
.
getValue
().
stream
().
map
(
ImportantChannel:
:
getSource
).
collect
(
Collectors
.
toList
()));
}
platformJson
.
add
(
dataJson
);
}
mainBodyJson
.
put
(
"list"
,
platformJson
);
resList
.
add
(
mainBodyJson
);
}
JSONObject
res
=
new
JSONObject
();
res
.
put
(
"count"
,
sortList
.
size
());
res
.
put
(
"list"
,
resList
);
return
res
;
}
@Override
public
JSONObject
getSpreadingTend
(
String
channelId
,
String
type
,
String
contendIds
,
Long
startTime
,
Long
endTime
)
{
public
JSONObject
getSpreadingTend
(
String
channelId
,
String
type
,
String
contendIds
,
Long
startTime
,
Long
endTime
)
{
JSONObject
res
=
new
JSONObject
();
JSONObject
res
=
new
JSONObject
();
// 默认全部 TODO
// 默认全部 TODO
...
...
src/main/java/com/zhiwei/brandkbs2/service/impl/ToolsetServiceImpl.java
View file @
e6abc1ff
...
@@ -964,10 +964,11 @@ public class ToolsetServiceImpl implements ToolsetService {
...
@@ -964,10 +964,11 @@ public class ToolsetServiceImpl implements ToolsetService {
String
linkedGroupId
=
projectService
.
getProjectVOById
(
projectId
).
getBrandLinkedGroupId
();
String
linkedGroupId
=
projectService
.
getProjectVOById
(
projectId
).
getBrandLinkedGroupId
();
jsonObject
=
restTemplate
.
getForEntity
(
articleInfoUrl
,
JSONObject
.
class
,
url
,
linkedGroupId
,
UserThreadLocal
.
getNickname
()).
getBody
();
jsonObject
=
restTemplate
.
getForEntity
(
articleInfoUrl
,
JSONObject
.
class
,
url
,
linkedGroupId
,
UserThreadLocal
.
getNickname
()).
getBody
();
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
log
.
info
(
"url:{},访问链接信息提取接口异常-"
,
url
,
e
);
log
.
error
(
"url:{},访问链接信息提取接口异常-"
,
url
,
e
);
return
null
;
return
null
;
}
}
if
(
Objects
.
isNull
(
jsonObject
)
||
!
jsonObject
.
getBoolean
(
"status"
))
{
if
(
Objects
.
isNull
(
jsonObject
)
||
!
jsonObject
.
getBoolean
(
"status"
))
{
log
.
error
(
"url:{},访问链接信息提取接口异常-返回信息:{}"
,
url
,
JSONObject
.
toJSONString
(
jsonObject
));
return
null
;
return
null
;
}
}
return
jsonObject
.
getJSONObject
(
"data"
);
return
jsonObject
.
getJSONObject
(
"data"
);
...
@@ -994,7 +995,7 @@ public class ToolsetServiceImpl implements ToolsetService {
...
@@ -994,7 +995,7 @@ public class ToolsetServiceImpl implements ToolsetService {
ResponseEntity
<
String
>
response
=
restTemplate
.
postForEntity
(
articleSummaryUrl
,
request
,
String
.
class
);
ResponseEntity
<
String
>
response
=
restTemplate
.
postForEntity
(
articleSummaryUrl
,
request
,
String
.
class
);
return
response
.
getBody
();
return
response
.
getBody
();
}
catch
(
Exception
e
){
}
catch
(
Exception
e
){
log
.
info
(
"访问摘要提取接口异常-"
,
e
);
log
.
error
(
"访问摘要提取接口异常-"
,
e
);
return
errorString
;
return
errorString
;
}
}
}
}
...
...
src/main/java/com/zhiwei/brandkbs2/util/RedisUtil.java
View file @
e6abc1ff
...
@@ -119,6 +119,10 @@ public class RedisUtil {
...
@@ -119,6 +119,10 @@ public class RedisUtil {
return
RedisKeyPrefix
.
YUQING_ANALYZE_HIGH_WORD
+
Tools
.
concat
(
projectId
,
contendId
,
planId
,
startTime
,
endTime
);
return
RedisKeyPrefix
.
YUQING_ANALYZE_HIGH_WORD
+
Tools
.
concat
(
projectId
,
contendId
,
planId
,
startTime
,
endTime
);
}
}
public
static
String
getSearchKeywordCacheKey
(
String
projectId
,
String
userId
,
String
searchType
){
return
RedisKeyPrefix
.
SEARCH_KEYWORD
+
Tools
.
concat
(
projectId
,
userId
,
searchType
);
}
public
void
setExpire
(
String
key
,
String
value
,
long
timeout
,
TimeUnit
unit
)
{
public
void
setExpire
(
String
key
,
String
value
,
long
timeout
,
TimeUnit
unit
)
{
stringRedisTemplate
.
opsForValue
().
set
(
key
,
value
,
timeout
,
unit
);
stringRedisTemplate
.
opsForValue
().
set
(
key
,
value
,
timeout
,
unit
);
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment